QGIS API Documentation 4.1.0-Master (31622b25bb0)
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//
34// QgsModelDesignerArrowBadgeItem
35//
36
37QgsModelDesignerArrowBadgeItem::QgsModelDesignerArrowBadgeItem( QgsModelArrowItem *link )
38 : QGraphicsRectItem( link )
39{
40 setZValue( QgsModelGraphicsScene::ZValues::ArrowDecoration );
41}
42
43void QgsModelDesignerArrowBadgeItem::setCenter( const QPointF &center )
44{
45 const double width = rect().width();
46 const double height = rect().height();
47 setRect( center.x() - width * 0.5, center.y() - height * 0.5, width, height );
48}
49
50QgsModelArrowItem *QgsModelDesignerArrowBadgeItem::arrow()
51{
52 return dynamic_cast< QgsModelArrowItem * >( parentItem() );
53}
54
55void QgsModelDesignerArrowBadgeItem::paint( QPainter *painter, const QStyleOptionGraphicsItem *, QWidget * )
56{
57 QgsModelArrowItem *arrow = QgsModelDesignerArrowBadgeItem::arrow();
58 if ( !arrow )
59 return;
60
61 // find mid-point color for arrow, and match badge to mid-point color
62 const QColor startColor = arrow->startItem()->linkColor( arrow->startEdge(), arrow->startIndex() );
63 const QColor endColor = arrow->endItem()->linkColor( arrow->endEdge(), arrow->endIndex() );
64 const QColor backgroundColor = QColor::fromRgbF( 0.5f * ( startColor.redF() + endColor.redF() ), 0.5f * ( startColor.greenF() + endColor.greenF() ), 0.5f * ( startColor.blueF() + endColor.blueF() ) );
65 const QColor strokeColor = backgroundColor.darker( 150 );
66
67 const bool hadAntialiasing = painter->testRenderHint( QPainter::Antialiasing );
68 painter->setRenderHint( QPainter::Antialiasing, true );
69 // First draw a rounded rectangle as background
70 painter->setBrush( QBrush( backgroundColor ) );
71 QPen pen( strokeColor );
72 pen.setCosmetic( true );
73 pen.setWidth( 1 );
74 painter->setPen( pen );
75 painter->drawRoundedRect( rect(), BORDER_RADIUS, BORDER_RADIUS );
76
77 const bool isDarkBackground = backgroundColor.lightness() < 128;
78
79 // And finally draw the text on top
80 QFont font;
81 font.setPointSize( FONT_SIZE );
82 font.setBold( true );
83 painter->setFont( font );
84 painter->setPen( QPen( isDarkBackground ? QColor( 255, 255, 255 ) : QColor( 0, 0, 0 ) ) );
85 painter->setBrush( Qt::NoBrush );
86
87 painter->drawText( rect(), Qt::AlignCenter, textForValue( mValue ) );
88
89 painter->setRenderHint( QPainter::Antialiasing, hadAntialiasing );
90}
91
92void QgsModelDesignerArrowBadgeItem::setValue( const QVariant &value )
93{
94 mValue = value;
95 resizeToContents();
96 update();
97}
98
99QVariant QgsModelDesignerArrowBadgeItem::value() const
100{
101 return mValue;
102}
103
104QString QgsModelDesignerArrowBadgeItem::textForValue( const QVariant &value )
105{
106 if ( QgsVariantUtils::isNull( value ) )
107 return QString();
108
109 if ( QgsVariantUtils::isNumericType( static_cast< QMetaType::Type>( value.userType() ) ) )
110 {
111 return value.toString();
112 }
113
114 // limit size of badge
115 const QString stringValue = value.toString();
116 return QgsStringUtils::truncateMiddleOfString( stringValue, 10 );
117}
118
119void QgsModelDesignerArrowBadgeItem::resizeToContents()
120{
121 QFont font;
122 font.setPointSize( FONT_SIZE );
123 font.setBold( true );
124 QFontMetrics fm( font );
125 const QRectF boundingRect = fm.boundingRect( textForValue( mValue ) );
126 const double width = boundingRect.width() + 2 * BORDER_RADIUS + CONTENTS_MARGIN * 2;
127 const double height = boundingRect.height() + 2 * BORDER_RADIUS + CONTENTS_MARGIN * 2;
128
129 const QPointF center = rect().center();
130 setRect( center.x() - width * 0.5, center.y() - height * 0.5, width, height );
131}
132
133
134//
135// QgsModelArrowItem
136//
137
138QgsModelArrowItem::QgsModelArrowItem(
139 QgsModelComponentGraphicItem *startItem,
140 Qt::Edge startEdge,
141 int startIndex,
142 bool startIsOutgoing,
143 Marker startMarker,
144 QgsModelComponentGraphicItem *endItem,
145 Qt::Edge endEdge,
146 int endIndex,
147 bool endIsIncoming,
148 Marker endMarker
149)
150 : QObject( nullptr )
151 , mStartItem( startItem )
152 , mStartEdge( startEdge )
153 , mStartIndex( startIndex )
154 , mStartIsOutgoing( startIsOutgoing )
155 , mStartMarker( startMarker )
156 , mEndItem( endItem )
157 , mEndEdge( endEdge )
158 , mEndIndex( endIndex )
159 , mEndIsIncoming( endIsIncoming )
160 , mEndMarker( endMarker )
161{
162 setCacheMode( QGraphicsItem::DeviceCoordinateCache );
163 setFlag( QGraphicsItem::ItemIsSelectable, false );
164 mColor = QApplication::palette().color( QPalette::Text );
165 mColor.setAlpha( 150 );
166 setPen( QPen( mColor, 8, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin ) );
167 setZValue( QgsModelGraphicsScene::ArrowLink );
168 updatePath();
169
170 connect( mStartItem, &QgsModelComponentGraphicItem::updateArrowPaths, this, &QgsModelArrowItem::updatePath );
171 connect( mStartItem, &QgsModelComponentGraphicItem::repaintArrows, this, [this] { update(); } );
172 connect( mEndItem, &QgsModelComponentGraphicItem::updateArrowPaths, this, &QgsModelArrowItem::updatePath );
173 connect( mEndItem, &QgsModelComponentGraphicItem::repaintArrows, this, [this] { update(); } );
174}
175
176QgsModelArrowItem::QgsModelArrowItem( QgsModelComponentGraphicItem *startItem, Qt::Edge startEdge, int startIndex, Marker startMarker, QgsModelComponentGraphicItem *endItem, Marker endMarker )
177 : QgsModelArrowItem( startItem, startEdge, startIndex, true, startMarker, endItem, Qt::LeftEdge, -1, true, endMarker )
178{}
179
180QgsModelArrowItem::QgsModelArrowItem( QgsModelComponentGraphicItem *startItem, Marker startMarker, QgsModelComponentGraphicItem *endItem, Qt::Edge endEdge, int endIndex, Marker endMarker )
181 : QgsModelArrowItem( startItem, Qt::LeftEdge, -1, true, startMarker, endItem, endEdge, endIndex, true, endMarker )
182{}
183
184QgsModelArrowItem::QgsModelArrowItem( QgsModelComponentGraphicItem *startItem, Marker startMarker, QgsModelComponentGraphicItem *endItem, Marker endMarker )
185 : QgsModelArrowItem( startItem, Qt::LeftEdge, -1, true, startMarker, endItem, Qt::LeftEdge, -1, true, endMarker )
186{}
187
188
189void QgsModelArrowItem::paint( QPainter *painter, const QStyleOptionGraphicsItem *, QWidget * )
190{
191 QColor color = mStartItem->linkColor( mStartEdge, mStartIndex );
192
193 if ( mStartItem->state() == QgsModelComponentGraphicItem::Selected || mEndItem->state() == QgsModelComponentGraphicItem::Selected )
194 color.setAlpha( 220 );
195 else if ( mStartItem->state() == QgsModelComponentGraphicItem::Hover || mEndItem->state() == QgsModelComponentGraphicItem::Hover )
196 color.setAlpha( 150 );
197 else
198 color.setAlpha( 80 );
199
200 //
201 QPen p = pen();
202 p.setColor( color );
203 p.setWidth( 0 );
204 painter->setPen( p );
205
206 painter->setBrush( color );
207 painter->setRenderHint( QPainter::Antialiasing );
208
209 switch ( mStartMarker )
210 {
211 case Marker::ArrowHead:
212 drawArrowHead( painter, mStartPoint, path().pointAtPercent( 0.0 ) - path().pointAtPercent( 0.05 ) );
213 break;
214
215 // start marker are no longer drawn
216 case Marker::Circle:
217 case Marker::NoMarker:
218 break;
219 }
220
221 switch ( mEndMarker )
222 {
223 case Marker::Circle:
224 painter->drawEllipse( mEndPoint, 3.0, 3.0 );
225 break;
226 case Marker::ArrowHead:
227 drawArrowHead( painter, mEndPoint, path().pointAtPercent( 1.0 ) - path().pointAtPercent( 0.95 ) );
228 break;
229 case Marker::NoMarker:
230 break;
231 }
232
233 painter->setBrush( color );
234 painter->setRenderHint( QPainter::Antialiasing );
235 painter->setBrush( Qt::NoBrush );
236
237 // Set the painter back to regular stroke thickness
238 p = pen();
239 QColor endColor = mEndItem->linkColor( mEndEdge, mEndIndex );
240 color.setAlpha( 255 );
241
242 QLinearGradient gradient;
243 QPointF startPoint = path().pointAtPercent( 0.3 );
244 QPointF endPoint = path().pointAtPercent( 0.7 );
245 gradient.setStart( startPoint );
246 gradient.setFinalStop( endPoint );
247 gradient.setColorAt( 0, color );
248 gradient.setColorAt( 1, endColor );
249
250 p.setBrush( QBrush( gradient ) );
251 p.setWidth( 2 );
252 painter->setPen( p );
253 painter->drawPath( path() );
254}
255
256void QgsModelArrowItem::drawArrowHead( QPainter *painter, const QPointF &position, const QPointF &vector )
257{
258 const float angle = atan2( vector.y(), vector.x() ) * 180.0 / M_PI;
259 painter->translate( position );
260 painter->rotate( angle );
261 QPolygonF arrowHead;
262 arrowHead << QPointF( 0, 0 ) << QPointF( -6, 4 ) << QPointF( -6, -4 ) << QPointF( 0, 0 );
263 painter->drawPolygon( arrowHead );
264 painter->rotate( -angle );
265 painter->translate( -position );
266}
267
268void QgsModelArrowItem::setPenStyle( Qt::PenStyle style )
269{
270 QPen p = pen();
271 p.setStyle( style );
272 setPen( p );
273 update();
274}
275
276QgsModelComponentGraphicItem *QgsModelArrowItem::startItem()
277{
278 return mStartItem;
279}
280
281QgsModelComponentGraphicItem *QgsModelArrowItem::endItem()
282{
283 return mEndItem;
284}
285
286QgsModelDesignerArrowBadgeItem *QgsModelArrowItem::badgeItem()
287{
288 return mBadgeItem;
289}
290
291void QgsModelArrowItem::setShowBadge( bool visible )
292{
293 if ( visible && !mBadgeItem )
294 {
295 mBadgeItem = new QgsModelDesignerArrowBadgeItem( this );
296 mBadgeItem->setCenter( path().pointAtPercent( 0.5 ) );
297 }
298 else if ( !visible && mBadgeItem )
299 {
300 scene()->removeItem( mBadgeItem );
301 delete mBadgeItem;
302 mBadgeItem = nullptr;
303 }
304}
305
306void QgsModelArrowItem::updatePath()
307{
308 QList<QPointF> controlPoints;
309
310 // is there a fixed start or end point?
311 QPointF startPt;
312 bool hasStartPt = false;
313
314 // usually arrows attached to an algorithm have a concept of directional flow -- they are either
315 // "inputs" to the item or "outputs". In this case we need to reflect this in how we draw the linking
316 // arrows, because we always have "inputs" on the left/top side and "outputs" on the right/bottom
317 bool startHasSpecificDirectionalFlow = qobject_cast<QgsModelChildAlgorithmGraphicItem *>( mStartItem );
318 bool endHasSpecificDirectionalFlow = qobject_cast<QgsModelChildAlgorithmGraphicItem *>( mEndItem );
319
320 // some specific exceptions to the above
321 if ( qobject_cast<QgsModelCommentGraphicItem *>( mStartItem ) || qobject_cast<QgsModelCommentGraphicItem *>( mEndItem ) )
322 {
323 // comments can be freely attached to any side of an algorithm item without directional flow
324 startHasSpecificDirectionalFlow = false;
325 endHasSpecificDirectionalFlow = false;
326 }
327
328 if ( mStartIndex != -1 )
329 {
330 startPt = mStartItem->linkPoint( mStartEdge, mStartIndex, !mStartIsOutgoing );
331 hasStartPt = true;
332 }
333 QPointF endPt;
334 bool hasEndPt = false;
335 if ( mEndIndex != -1 )
336 {
337 endPt = mEndItem->linkPoint( mEndEdge, mEndIndex, mEndIsIncoming );
338 hasEndPt = true;
339 }
340
341 if ( !hasStartPt )
342 {
343 Qt::Edge startEdge;
344 QPointF pt;
345 if ( !hasEndPt )
346 pt = mStartItem->calculateAutomaticLinkPoint( mEndItem, startEdge );
347 else
348 pt = mStartItem->calculateAutomaticLinkPoint( endPt + mEndItem->pos(), startEdge );
349
350 controlPoints.append( pt );
351 mStartPoint = pt;
352 controlPoints.append( bezierPointForCurve( pt, startEdge, !mStartIsOutgoing, startHasSpecificDirectionalFlow ) );
353 }
354 else
355 {
356 mStartPoint = mStartItem->pos() + startPt;
357 controlPoints.append( mStartItem->pos() + startPt );
358 controlPoints.append( bezierPointForCurve( mStartItem->pos() + startPt, mStartEdge == Qt::BottomEdge ? Qt::RightEdge : Qt::LeftEdge, !mStartIsOutgoing, startHasSpecificDirectionalFlow ) );
359 }
360
361 if ( !hasEndPt )
362 {
363 Qt::Edge endEdge;
364 QPointF pt;
365 if ( !hasStartPt )
366 pt = mEndItem->calculateAutomaticLinkPoint( mStartItem, endEdge );
367 else
368 pt = mEndItem->calculateAutomaticLinkPoint( startPt + mStartItem->pos(), endEdge );
369
370 controlPoints.append( bezierPointForCurve( pt, endEdge, mEndIsIncoming, endHasSpecificDirectionalFlow ) );
371 controlPoints.append( pt );
372 mEndPoint = pt;
373 }
374 else
375 {
376 mEndPoint = mEndItem->pos() + endPt;
377 controlPoints.append( bezierPointForCurve( mEndItem->pos() + endPt, mEndEdge == Qt::BottomEdge ? Qt::RightEdge : Qt::LeftEdge, mEndIsIncoming, endHasSpecificDirectionalFlow ) );
378 controlPoints.append( mEndItem->pos() + endPt );
379 }
380
381 QPainterPath path;
382 path.moveTo( controlPoints.at( 0 ) );
383 path.cubicTo( controlPoints.at( 1 ), controlPoints.at( 2 ), controlPoints.at( 3 ) );
384 setPath( path );
385 if ( mBadgeItem )
386 {
387 mBadgeItem->setCenter( path.pointAtPercent( 0.5 ) );
388 }
389}
390
391QPointF QgsModelArrowItem::bezierPointForCurve( const QPointF &point, Qt::Edge edge, bool incoming, bool hasSpecificDirectionalFlow ) const
392{
393 switch ( edge )
394 {
395 case Qt::LeftEdge:
396 return point + QPointF( hasSpecificDirectionalFlow ? ( incoming ? -50 : 50 ) : -50, 0 );
397
398 case Qt::RightEdge:
399 return point + QPointF( hasSpecificDirectionalFlow ? ( incoming ? -50 : 50 ) : 50, 0 );
400
401 case Qt::TopEdge:
402 return point + QPointF( 0, hasSpecificDirectionalFlow ? ( incoming ? -30 : 30 ) : -30 );
403
404 case Qt::BottomEdge:
405 return point + QPointF( 0, hasSpecificDirectionalFlow ? ( incoming ? -30 : 30 ) : 30 );
406 }
407 return QPointF();
408}
409
410
static QString truncateMiddleOfString(const QString &string, int maxLength)
Truncates a string to the specified maximum character length.
static bool isNull(const QVariant &variant, bool silenceNullWarnings=false)
Returns true if the specified variant should be considered a NULL value.
static bool isNumericType(QMetaType::Type metaType)
Returns true if the specified metaType is a numeric type.
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).