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