Skip to content

Commit

Permalink
Move code for generating the annotation "balloon" style background
Browse files Browse the repository at this point in the history
shape out into a new class so that it can be reused elsewhere
  • Loading branch information
nyalldawson committed Mar 19, 2021
1 parent f79d18f commit 8b94ec5
Show file tree
Hide file tree
Showing 5 changed files with 217 additions and 176 deletions.
2 changes: 2 additions & 0 deletions src/core/CMakeLists.txt
Expand Up @@ -426,6 +426,7 @@ set(QGIS_CORE_SRCS
qgsruntimeprofiler.cpp
qgsscalecalculator.cpp
qgsscaleutils.cpp
qgsshapegenerator.cpp
qgssimplifymethod.cpp
qgssnappingutils.cpp
qgsspatialindex.cpp
Expand Down Expand Up @@ -1030,6 +1031,7 @@ set(QGIS_CORE_HDRS
qgsscalecalculator.h
qgsscaleutils.h
qgssettings.h
qgsshapegenerator.h
qgssimplifymethod.h
qgssnappingconfig.h
qgssnappingutils.h
Expand Down
173 changes: 12 additions & 161 deletions src/core/annotations/qgsannotation.cpp
Expand Up @@ -21,6 +21,7 @@
#include "qgsproject.h"
#include "qgsgeometryutils.h"
#include "qgsstyleentityvisitor.h"
#include "qgsshapegenerator.h"

#include <QPen>
#include <QPainter>
Expand Down Expand Up @@ -56,7 +57,6 @@ void QgsAnnotation::setHasFixedMapPosition( bool fixed )
return;

mHasFixedMapPosition = fixed;
updateBalloon();
emit moved();
}

Expand Down Expand Up @@ -93,7 +93,6 @@ void QgsAnnotation::setFrameOffsetFromReferencePointMm( QPointF offset )
{
mOffsetFromReferencePoint = offset;

updateBalloon();
emit moved();
emit appearanceChanged();
}
Expand All @@ -113,7 +112,6 @@ void QgsAnnotation::setFrameSizeMm( QSizeF size )
{
QSizeF frameSize = minimumFrameSize().expandedTo( size ); //don't allow frame sizes below minimum
mFrameSize = frameSize;
updateBalloon();
emit moved();
emit appearanceChanged();
}
Expand Down Expand Up @@ -214,169 +212,26 @@ QSizeF QgsAnnotation::minimumFrameSize() const
return QSizeF( 0, 0 );
}

void QgsAnnotation::updateBalloon()
void QgsAnnotation::drawFrame( QgsRenderContext &context ) const
{
//first test if the point is in the frame. In that case we don't need a balloon.
if ( !mHasFixedMapPosition ||
( mOffsetFromReferencePoint.x() < 0 && ( mOffsetFromReferencePoint.x() + mFrameSize.width() ) > 0
&& mOffsetFromReferencePoint.y() < 0 && ( mOffsetFromReferencePoint.y() + mFrameSize.height() ) > 0 ) )
{
mBalloonSegment = -1;
return;
}

//edge list
QList<QLineF> segmentList;
segmentList << segment( 0, nullptr );
segmentList << segment( 1, nullptr );
segmentList << segment( 2, nullptr );
segmentList << segment( 3, nullptr );

//find closest edge / closest edge point
double minEdgeDist = std::numeric_limits<double>::max();
int minEdgeIndex = -1;
QLineF minEdge;
QgsPointXY minEdgePoint;
QgsPointXY origin( 0, 0 );

for ( int i = 0; i < 4; ++i )
{
QLineF currentSegment = segmentList.at( i );
QgsPointXY currentMinDistPoint;
double currentMinDist = origin.sqrDistToSegment( currentSegment.x1(), currentSegment.y1(), currentSegment.x2(), currentSegment.y2(), currentMinDistPoint );
bool isPreferredSegment = false;
if ( qgsDoubleNear( currentMinDist, minEdgeDist ) )
{
// two segments are close - work out which looks nicer
const double angle = fmod( origin.azimuth( currentMinDistPoint ) + 360.0, 360.0 );
if ( angle < 45 || angle > 315 )
isPreferredSegment = i == 0;
else if ( angle < 135 )
isPreferredSegment = i == 3;
else if ( angle < 225 )
isPreferredSegment = i == 2;
else
isPreferredSegment = i == 1;
}
else if ( currentMinDist < minEdgeDist )
isPreferredSegment = true;

if ( isPreferredSegment )
{
minEdgeIndex = i;
minEdgePoint = currentMinDistPoint;
minEdgeDist = currentMinDist;
minEdge = currentSegment;
}
}

if ( minEdgeIndex < 0 )
{
if ( !mFillSymbol )
return;
}

mBalloonSegment = minEdgeIndex;
QPointF minEdgeEnd = minEdge.p2();
mBalloonSegmentPoint1 = QPointF( minEdgePoint.x(), minEdgePoint.y() );
if ( std::sqrt( minEdgePoint.sqrDist( minEdgeEnd.x(), minEdgeEnd.y() ) ) < mSegmentPointWidthMm )
{
double x = 0;
double y = 0;
QgsGeometryUtils::pointOnLineWithDistance( minEdge.p2().x(), minEdge.p2().y(), minEdge.p1().x(), minEdge.p1().y(), mSegmentPointWidthMm, x, y );
mBalloonSegmentPoint1 = QPointF( x, y );
}

{
double x = 0;
double y = 0;
QgsGeometryUtils::pointOnLineWithDistance( mBalloonSegmentPoint1.x(), mBalloonSegmentPoint1.y(), minEdge.p2().x(), minEdge.p2().y(), mSegmentPointWidthMm, x, y );
mBalloonSegmentPoint2 = QPointF( x, y );
}

}

QLineF QgsAnnotation::segment( int index, QgsRenderContext *context ) const
{
auto scaleSize = [context]( double size )->double
auto scaleSize = [&context]( double size )->double
{
return context ? context->convertToPainterUnits( size, QgsUnitTypes::RenderMillimeters ) : size;
return context.convertToPainterUnits( size, QgsUnitTypes::RenderMillimeters );
};
if ( mHasFixedMapPosition )
{
switch ( index )
{
case 0:
return QLineF( scaleSize( mOffsetFromReferencePoint.x() ),
scaleSize( mOffsetFromReferencePoint.y() ),
scaleSize( mOffsetFromReferencePoint.x() ) + scaleSize( mFrameSize.width() ),
scaleSize( mOffsetFromReferencePoint.y() ) );
case 1:
return QLineF( scaleSize( mOffsetFromReferencePoint.x() ) + scaleSize( mFrameSize.width() ),
scaleSize( mOffsetFromReferencePoint.y() ),
scaleSize( mOffsetFromReferencePoint.x() ) + scaleSize( mFrameSize.width() ),
scaleSize( mOffsetFromReferencePoint.y() ) + scaleSize( mFrameSize.height() ) );
case 2:
return QLineF( scaleSize( mOffsetFromReferencePoint.x() ) + scaleSize( mFrameSize.width() ),
scaleSize( mOffsetFromReferencePoint.y() ) + scaleSize( mFrameSize.height() ),
scaleSize( mOffsetFromReferencePoint.x() ),
scaleSize( mOffsetFromReferencePoint.y() ) + scaleSize( mFrameSize.height() ) );
case 3:
return QLineF( scaleSize( mOffsetFromReferencePoint.x() ),
scaleSize( mOffsetFromReferencePoint.y() ) + scaleSize( mFrameSize.height() ),
scaleSize( mOffsetFromReferencePoint.x() ),
scaleSize( mOffsetFromReferencePoint.y() ) );
default:
return QLineF();
}
}
else
{
switch ( index )
{
case 0:
return QLineF( 0, 0, scaleSize( mFrameSize.width() ), 0 );
case 1:
return QLineF( scaleSize( mFrameSize.width() ), 0,
scaleSize( mFrameSize.width() ), scaleSize( mFrameSize.height() ) );
case 2:
return QLineF( scaleSize( mFrameSize.width() ), scaleSize( mFrameSize.height() ),
0, scaleSize( mFrameSize.height() ) );
case 3:
return QLineF( 0, scaleSize( mFrameSize.height() ),
0, 0 );
default:
return QLineF();
}
}
}

void QgsAnnotation::drawFrame( QgsRenderContext &context ) const
{
if ( !mFillSymbol )
return;
const QRectF frameRect( mHasFixedMapPosition ? scaleSize( mOffsetFromReferencePoint.x() ) : 0,
mHasFixedMapPosition ? scaleSize( mOffsetFromReferencePoint.y() ) : 0,
scaleSize( mFrameSize.width() ),
scaleSize( mFrameSize.height() ) );
const QgsPointXY origin = mHasFixedMapPosition ? QgsPointXY( 0, 0 ) : QgsPointXY( frameRect.center().x(), frameRect.center().y() );

QPolygonF poly;
poly.reserve( 9 + ( mHasFixedMapPosition ? 3 : 0 ) );
QVector<QPolygonF> rings; //empty list
for ( int i = 0; i < 4; ++i )
{
QLineF currentSegment = segment( i, &context );
poly << QPointF( currentSegment.p1().x(),
currentSegment.p1().y() );
if ( i == mBalloonSegment && mHasFixedMapPosition )
{
poly << QPointF( context.convertToPainterUnits( mBalloonSegmentPoint1.x(), QgsUnitTypes::RenderMillimeters ),
context.convertToPainterUnits( mBalloonSegmentPoint1.y(), QgsUnitTypes::RenderMillimeters ) );
poly << QPointF( 0, 0 );
poly << QPointF( context.convertToPainterUnits( mBalloonSegmentPoint2.x(), QgsUnitTypes::RenderMillimeters ),
context.convertToPainterUnits( mBalloonSegmentPoint2.y(), QgsUnitTypes::RenderMillimeters ) );
}
poly << QPointF( currentSegment.p2().x(), currentSegment.p2().y() );
}
if ( poly.at( 0 ) != poly.at( poly.count() - 1 ) )
poly << poly.at( 0 );
const QPolygonF poly = QgsShapeGenerator::createBalloon( origin, frameRect, context.convertToPainterUnits( mSegmentPointWidthMm, QgsUnitTypes::RenderMillimeters ) );

mFillSymbol->startRender( context );
QVector<QPolygonF> rings; //empty list
mFillSymbol->renderPolygon( poly, &rings, nullptr, context );
mFillSymbol->stopRender( context );
}
Expand Down Expand Up @@ -539,7 +394,6 @@ void QgsAnnotation::_readXml( const QDomElement &annotationElem, const QgsReadWr
mFillSymbol.reset( QgsFillSymbol::createSimple( props ) );
}

updateBalloon();
emit mapLayerChanged();
}

Expand All @@ -555,9 +409,6 @@ void QgsAnnotation::copyCommonProperties( QgsAnnotation *target ) const
target->mMarkerSymbol.reset( mMarkerSymbol ? mMarkerSymbol->clone() : nullptr );
target->mContentsMargins = mContentsMargins;
target->mFillSymbol.reset( mFillSymbol ? mFillSymbol->clone() : nullptr );
target->mBalloonSegment = mBalloonSegment;
target->mBalloonSegmentPoint1 = mBalloonSegmentPoint1;
target->mBalloonSegmentPoint2 = mBalloonSegmentPoint2;
target->mSegmentPointWidthMm = mSegmentPointWidthMm;
target->mMapLayer = mMapLayer;
target->mFeature = mFeature;
Expand Down
15 changes: 0 additions & 15 deletions src/core/annotations/qgsannotation.h
Expand Up @@ -375,12 +375,6 @@ class CORE_EXPORT QgsAnnotation : public QObject

private:

//! Check where to attach the balloon connection between frame and map point
void updateBalloon();

//! Gets the frame line (0 is the top line, 1 right, 2 bottom, 3 left)
QLineF segment( int index, QgsRenderContext *context ) const;

//! Draws the annotation frame to a destination painter
void drawFrame( QgsRenderContext &context ) const;

Expand Down Expand Up @@ -415,15 +409,6 @@ class CORE_EXPORT QgsAnnotation : public QObject
//! Fill symbol used for drawing annotation
std::unique_ptr<QgsFillSymbol> mFillSymbol;

//! Segment number where the connection to the map point is attached. -1 if no balloon needed (e.g. if point is contained in frame)
int mBalloonSegment = -1;

//! First segment point for drawing the connection (ccw direction) (always in mm)
QPointF mBalloonSegmentPoint1;

//! Second segment point for drawing the balloon connection (ccw direction) (always in mm)
QPointF mBalloonSegmentPoint2;

//! Associated layer (or NULLPTR if not attached to a layer)
QgsWeakMapLayerPointer mMapLayer;

Expand Down

0 comments on commit 8b94ec5

Please sign in to comment.