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