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