QGIS API Documentation 3.99.0-Master (2fe06baccd8)
Loading...
Searching...
No Matches
qgsmodelarrowitem.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsmodelarrowitem.cpp
3 ----------------------------------
4 Date : March 2020
5 Copyright : (C) 2020 Nyall Dawson
6 Email : nyall dot dawson at gmail dot com
7 ***************************************************************************
8 * *
9 * This program is free software; you can redistribute it and/or modify *
10 * it under the terms of the GNU General Public License as published by *
11 * the Free Software Foundation; either version 2 of the License, or *
12 * (at your option) any later version. *
13 * *
14 ***************************************************************************/
15
16#include "qgsmodelarrowitem.h"
17
18#include <math.h>
19
20#include "qgsapplication.h"
22#include "qgsmodelgraphicitem.h"
24
25#include <QApplication>
26#include <QPainter>
27#include <QPalette>
28
29#include "moc_qgsmodelarrowitem.cpp"
30
32
33
34QgsModelArrowItem::QgsModelArrowItem( QgsModelComponentGraphicItem *startItem, Qt::Edge startEdge, int startIndex, bool startIsOutgoing, Marker startMarker, QgsModelComponentGraphicItem *endItem, Qt::Edge endEdge, int endIndex, bool endIsIncoming, Marker endMarker )
35 : QObject( nullptr )
36 , mStartItem( startItem )
37 , mStartEdge( startEdge )
38 , mStartIndex( startIndex )
39 , mStartIsOutgoing( startIsOutgoing )
40 , mStartMarker( startMarker )
41 , mEndItem( endItem )
42 , mEndEdge( endEdge )
43 , mEndIndex( endIndex )
44 , mEndIsIncoming( endIsIncoming )
45 , mEndMarker( endMarker )
46{
47 setCacheMode( QGraphicsItem::DeviceCoordinateCache );
48 setFlag( QGraphicsItem::ItemIsSelectable, false );
49 mColor = QApplication::palette().color( QPalette::Text );
50 mColor.setAlpha( 150 );
51 setPen( QPen( mColor, 8, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin ) );
52 setZValue( QgsModelGraphicsScene::ArrowLink );
53 updatePath();
54
55 connect( mStartItem, &QgsModelComponentGraphicItem::updateArrowPaths, this, &QgsModelArrowItem::updatePath );
56 connect( mStartItem, &QgsModelComponentGraphicItem::repaintArrows, this, [this] { update(); } );
57 connect( mEndItem, &QgsModelComponentGraphicItem::updateArrowPaths, this, &QgsModelArrowItem::updatePath );
58 connect( mEndItem, &QgsModelComponentGraphicItem::repaintArrows, this, [this] { update(); } );
59}
60
61QgsModelArrowItem::QgsModelArrowItem( QgsModelComponentGraphicItem *startItem, Qt::Edge startEdge, int startIndex, Marker startMarker, QgsModelComponentGraphicItem *endItem, Marker endMarker )
62 : QgsModelArrowItem( startItem, startEdge, startIndex, true, startMarker, endItem, Qt::LeftEdge, -1, true, endMarker )
63{
64}
65
66QgsModelArrowItem::QgsModelArrowItem( QgsModelComponentGraphicItem *startItem, Marker startMarker, QgsModelComponentGraphicItem *endItem, Qt::Edge endEdge, int endIndex, Marker endMarker )
67 : QgsModelArrowItem( startItem, Qt::LeftEdge, -1, true, startMarker, endItem, endEdge, endIndex, true, endMarker )
68{
69}
70
71QgsModelArrowItem::QgsModelArrowItem( QgsModelComponentGraphicItem *startItem, Marker startMarker, QgsModelComponentGraphicItem *endItem, Marker endMarker )
72 : QgsModelArrowItem( startItem, Qt::LeftEdge, -1, true, startMarker, endItem, Qt::LeftEdge, -1, true, endMarker )
73{
74}
75
76
77void QgsModelArrowItem::paint( QPainter *painter, const QStyleOptionGraphicsItem *, QWidget * )
78{
79 QColor color = mStartItem->linkColor( mStartEdge, mStartIndex );
80
81 if ( mStartItem->state() == QgsModelComponentGraphicItem::Selected || mEndItem->state() == QgsModelComponentGraphicItem::Selected )
82 color.setAlpha( 220 );
83 else if ( mStartItem->state() == QgsModelComponentGraphicItem::Hover || mEndItem->state() == QgsModelComponentGraphicItem::Hover )
84 color.setAlpha( 150 );
85 else
86 color.setAlpha( 80 );
87
88 //
89 QPen p = pen();
90 p.setColor( color );
91 p.setWidth( 0 );
92 painter->setPen( p );
93
94 painter->setBrush( color );
95 painter->setRenderHint( QPainter::Antialiasing );
96
97 switch ( mStartMarker )
98 {
99 case Marker::ArrowHead:
100 drawArrowHead( painter, mStartPoint, path().pointAtPercent( 0.0 ) - path().pointAtPercent( 0.05 ) );
101 break;
102
103 // start marker are no longer drawn
104 case Marker::Circle:
105 case Marker::NoMarker:
106 break;
107 }
108
109 switch ( mEndMarker )
110 {
111 case Marker::Circle:
112 painter->drawEllipse( mEndPoint, 3.0, 3.0 );
113 break;
114 case Marker::ArrowHead:
115 drawArrowHead( painter, mEndPoint, path().pointAtPercent( 1.0 ) - path().pointAtPercent( 0.95 ) );
116 break;
117 case Marker::NoMarker:
118 break;
119 }
120
121 painter->setBrush( color );
122 painter->setRenderHint( QPainter::Antialiasing );
123 painter->setBrush( Qt::NoBrush );
124
125 // Set the painter back to regular stroke thickness
126 p = pen();
127 QColor endColor = mEndItem->linkColor( mEndEdge, mEndIndex );
128 color.setAlpha( 255 );
129
130 QLinearGradient gradient;
131 QPointF startPoint = path().pointAtPercent( 0.3 );
132 QPointF endPoint = path().pointAtPercent( 0.7 );
133 gradient.setStart( startPoint );
134 gradient.setFinalStop( endPoint );
135 gradient.setColorAt( 0, color );
136 gradient.setColorAt( 1, endColor );
137
138 p.setBrush( QBrush( gradient ) );
139 p.setWidth( 2 );
140 painter->setPen( p );
141 painter->drawPath( path() );
142}
143
144void QgsModelArrowItem::drawArrowHead( QPainter *painter, const QPointF &position, const QPointF &vector )
145{
146 const float angle = atan2( vector.y(), vector.x() ) * 180.0 / M_PI;
147 painter->translate( position );
148 painter->rotate( angle );
149 QPolygonF arrowHead;
150 arrowHead << QPointF( 0, 0 ) << QPointF( -6, 4 ) << QPointF( -6, -4 ) << QPointF( 0, 0 );
151 painter->drawPolygon( arrowHead );
152 painter->rotate( -angle );
153 painter->translate( -position );
154}
155
156void QgsModelArrowItem::setPenStyle( Qt::PenStyle style )
157{
158 QPen p = pen();
159 p.setStyle( style );
160 setPen( p );
161 update();
162}
163
164void QgsModelArrowItem::updatePath()
165{
166 QList<QPointF> controlPoints;
167
168 // is there a fixed start or end point?
169 QPointF startPt;
170 bool hasStartPt = false;
171
172 // usually arrows attached to an algorithm have a concept of directional flow -- they are either
173 // "inputs" to the item or "outputs". In this case we need to reflect this in how we draw the linking
174 // arrows, because we always have "inputs" on the left/top side and "outputs" on the right/bottom
175 bool startHasSpecificDirectionalFlow = qobject_cast<QgsModelChildAlgorithmGraphicItem *>( mStartItem );
176 bool endHasSpecificDirectionalFlow = qobject_cast<QgsModelChildAlgorithmGraphicItem *>( mEndItem );
177
178 // some specific exceptions to the above
179 if ( qobject_cast<QgsModelCommentGraphicItem *>( mStartItem )
180 || qobject_cast<QgsModelCommentGraphicItem *>( mEndItem ) )
181 {
182 // comments can be freely attached to any side of an algorithm item without directional flow
183 startHasSpecificDirectionalFlow = false;
184 endHasSpecificDirectionalFlow = false;
185 }
186
187 if ( mStartIndex != -1 )
188 {
189 startPt = mStartItem->linkPoint( mStartEdge, mStartIndex, !mStartIsOutgoing );
190 hasStartPt = true;
191 }
192 QPointF endPt;
193 bool hasEndPt = false;
194 if ( mEndIndex != -1 )
195 {
196 endPt = mEndItem->linkPoint( mEndEdge, mEndIndex, mEndIsIncoming );
197 hasEndPt = true;
198 }
199
200 if ( !hasStartPt )
201 {
202 Qt::Edge startEdge;
203 QPointF pt;
204 if ( !hasEndPt )
205 pt = mStartItem->calculateAutomaticLinkPoint( mEndItem, startEdge );
206 else
207 pt = mStartItem->calculateAutomaticLinkPoint( endPt + mEndItem->pos(), startEdge );
208
209 controlPoints.append( pt );
210 mStartPoint = pt;
211 controlPoints.append( bezierPointForCurve( pt, startEdge, !mStartIsOutgoing, startHasSpecificDirectionalFlow ) );
212 }
213 else
214 {
215 mStartPoint = mStartItem->pos() + startPt;
216 controlPoints.append( mStartItem->pos() + startPt );
217 controlPoints.append( bezierPointForCurve( mStartItem->pos() + startPt, mStartEdge == Qt::BottomEdge ? Qt::RightEdge : Qt::LeftEdge, !mStartIsOutgoing, startHasSpecificDirectionalFlow ) );
218 }
219
220 if ( !hasEndPt )
221 {
222 Qt::Edge endEdge;
223 QPointF pt;
224 if ( !hasStartPt )
225 pt = mEndItem->calculateAutomaticLinkPoint( mStartItem, endEdge );
226 else
227 pt = mEndItem->calculateAutomaticLinkPoint( startPt + mStartItem->pos(), endEdge );
228
229 controlPoints.append( bezierPointForCurve( pt, endEdge, mEndIsIncoming, endHasSpecificDirectionalFlow ) );
230 controlPoints.append( pt );
231 mEndPoint = pt;
232 }
233 else
234 {
235 mEndPoint = mEndItem->pos() + endPt;
236 controlPoints.append( bezierPointForCurve( mEndItem->pos() + endPt, mEndEdge == Qt::BottomEdge ? Qt::RightEdge : Qt::LeftEdge, mEndIsIncoming, endHasSpecificDirectionalFlow ) );
237 controlPoints.append( mEndItem->pos() + endPt );
238 }
239
240 QPainterPath path;
241 path.moveTo( controlPoints.at( 0 ) );
242 path.cubicTo( controlPoints.at( 1 ), controlPoints.at( 2 ), controlPoints.at( 3 ) );
243 setPath( path );
244}
245
246QPointF QgsModelArrowItem::bezierPointForCurve( const QPointF &point, Qt::Edge edge, bool incoming, bool hasSpecificDirectionalFlow ) const
247{
248 switch ( edge )
249 {
250 case Qt::LeftEdge:
251 return point + QPointF( hasSpecificDirectionalFlow ? ( incoming ? -50 : 50 ) : -50, 0 );
252
253 case Qt::RightEdge:
254 return point + QPointF( hasSpecificDirectionalFlow ? ( incoming ? -50 : 50 ) : 50, 0 );
255
256 case Qt::TopEdge:
257 return point + QPointF( 0, hasSpecificDirectionalFlow ? ( incoming ? -30 : 30 ) : -30 );
258
259 case Qt::BottomEdge:
260 return point + QPointF( 0, hasSpecificDirectionalFlow ? ( incoming ? -30 : 30 ) : 30 );
261 }
262 return QPointF();
263}
264
265
double ANALYSIS_EXPORT angle(QgsPoint *p1, QgsPoint *p2, QgsPoint *p3, QgsPoint *p4)
Calculates the angle between two segments (in 2 dimension, z-values are ignored).