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