1 // SPDX-License-Identifier: LGPL-2.1-or-later
2 //
3 // SPDX-FileCopyrightText: 2014 Calin Cruceru <crucerucalincristian@gmail.com>
4 //
5 
6 // Self
7 #include "PolylineAnnotation.h"
8 
9 // Qt
10 #include <QApplication>
11 #include <QPalette>
12 #include <qmath.h>
13 
14 // Marble
15 #include "SceneGraphicsTypes.h"
16 #include "GeoPainter.h"
17 #include "PolylineNode.h"
18 #include "GeoDataLineString.h"
19 #include "GeoDataPlacemark.h"
20 #include "ViewportParams.h"
21 #include "MarbleColors.h"
22 #include "MergingPolylineNodesAnimation.h"
23 #include "osm/OsmPlacemarkData.h"
24 
25 
26 namespace Marble
27 {
28 
29 const int PolylineAnnotation::regularDim = 15;
30 const int PolylineAnnotation::selectedDim = 15;
31 const int PolylineAnnotation::mergedDim = 20;
32 const int PolylineAnnotation::hoveredDim = 20;
33 const QColor PolylineAnnotation::regularColor = Oxygen::aluminumGray3;
34 const QColor PolylineAnnotation::mergedColor = Oxygen::emeraldGreen6;
35 
PolylineAnnotation(GeoDataPlacemark * placemark)36 PolylineAnnotation::PolylineAnnotation( GeoDataPlacemark *placemark ) :
37     SceneGraphicsItem( placemark ),
38     m_viewport( nullptr ),
39     m_regionsInitialized( false ),
40     m_busy( false ),
41     m_interactingObj( InteractingNothing ),
42     m_clickedNodeIndex( -1 ),
43     m_hoveredNodeIndex( -1 ),
44     m_virtualHoveredNode( -1 )
45 
46 {
47     setPaintLayers(QStringList() << "PolylineAnnotation");
48 }
49 
~PolylineAnnotation()50 PolylineAnnotation::~PolylineAnnotation()
51 {
52     delete m_animation;
53 }
54 
paint(GeoPainter * painter,const ViewportParams * viewport,const QString & layer,int tileZoomLevel)55 void PolylineAnnotation::paint(GeoPainter *painter, const ViewportParams *viewport , const QString &layer, int tileZoomLevel)
56 {
57     Q_UNUSED(layer);
58     Q_UNUSED(tileZoomLevel);
59     m_viewport = viewport;
60     Q_ASSERT(geodata_cast<GeoDataLineString>(placemark()->geometry()));
61 
62     painter->save();
63     if ( state() == SceneGraphicsItem::DrawingPolyline || !m_regionsInitialized ) {
64         setupRegionsLists( painter );
65         m_regionsInitialized = true;
66     } else {
67         updateRegions( painter );
68     }
69 
70     if ( hasFocus() ) {
71         drawNodes( painter );
72     }
73     painter->restore();
74 }
75 
setupRegionsLists(GeoPainter * painter)76 void PolylineAnnotation::setupRegionsLists( GeoPainter *painter )
77 {
78     Q_ASSERT( state() == SceneGraphicsItem::DrawingPolyline || !m_regionsInitialized );
79     const GeoDataLineString line = static_cast<const GeoDataLineString>( *placemark()->geometry() );
80 
81     // Add polyline nodes.
82     QVector<GeoDataCoordinates>::ConstIterator itBegin = line.constBegin();
83     QVector<GeoDataCoordinates>::ConstIterator itEnd = line.constEnd();
84 
85     m_nodesList.clear();
86     m_nodesList.reserve(line.size());
87     for ( ; itBegin != itEnd; ++itBegin ) {
88         const PolylineNode newNode = PolylineNode( painter->regionFromEllipse( *itBegin, regularDim, regularDim ) );
89         m_nodesList.append( newNode );
90     }
91 
92     // Add region from polyline so that events on polyline's 'lines' could be caught.
93     m_polylineRegion = painter->regionFromPolyline( line, 15 );
94 }
95 
updateRegions(GeoPainter * painter)96 void PolylineAnnotation::updateRegions( GeoPainter *painter )
97 {
98     if ( m_busy ) {
99         return;
100     }
101 
102     const GeoDataLineString line = static_cast<const GeoDataLineString>( *placemark()->geometry() );
103 
104     if ( state() == SceneGraphicsItem::AddingNodes ) {
105         // Create and update virtual nodes lists when being in the AddingPolgonNodes state, to
106         // avoid overhead in other states.
107         m_virtualNodesList.clear();
108         for ( int i = 0; i < line.size() - 1; ++i ) {
109             const QRegion newRegion( painter->regionFromEllipse( line.at(i).interpolate( line.at(i+1), 0.5 ),
110                                                                  hoveredDim, hoveredDim ) );
111             m_virtualNodesList.append( PolylineNode( newRegion ) );
112         }
113     }
114 
115 
116     // Update the polyline region;
117     m_polylineRegion = painter->regionFromPolyline( line, 15 );
118 
119     // Update the node lists.
120     for ( int i = 0; i < m_nodesList.size(); ++i ) {
121         const QRegion newRegion = m_nodesList.at(i).isSelected() ?
122                                   painter->regionFromEllipse( line.at(i), selectedDim, selectedDim ) :
123                                   painter->regionFromEllipse( line.at(i), regularDim, regularDim );
124         m_nodesList[i].setRegion( newRegion );
125     }
126 }
127 
drawNodes(GeoPainter * painter)128 void PolylineAnnotation::drawNodes( GeoPainter *painter )
129 {
130     // These are the 'real' dimensions of the drawn nodes. The ones which have class scope are used
131     // to generate the regions and they are a little bit larger, because, for example, it would be
132     // a little bit too hard to select nodes.
133     static const int d_regularDim = 10;
134     static const int d_selectedDim = 10;
135     static const int d_mergedDim = 20;
136     static const int d_hoveredDim = 20;
137 
138     const GeoDataLineString line = static_cast<const GeoDataLineString>( *placemark()->geometry() );
139 
140     QColor glowColor = QApplication::palette().highlightedText().color();
141     glowColor.setAlpha(120);
142     auto const selectedColor = QApplication::palette().highlight().color();
143     auto const hoveredColor = selectedColor;
144 
145     for ( int i = 0; i < line.size(); ++i ) {
146         // The order here is important, because a merged node can be at the same time selected.
147         if ( m_nodesList.at(i).isBeingMerged() ) {
148             painter->setBrush( mergedColor );
149             painter->drawEllipse( line.at(i), d_mergedDim, d_mergedDim );
150         } else if ( m_nodesList.at(i).isSelected() ) {
151             painter->setBrush( selectedColor );
152             painter->drawEllipse( line.at(i), d_selectedDim, d_selectedDim );
153 
154             if ( m_nodesList.at(i).isEditingHighlighted() ||
155                  m_nodesList.at(i).isMergingHighlighted() ) {
156                 QPen defaultPen = painter->pen();
157                 QPen newPen;
158                 newPen.setWidth( defaultPen.width() + 3 );
159                 newPen.setColor( glowColor );
160 
161                 painter->setBrush( Qt::NoBrush );
162                 painter->setPen( newPen );
163                 painter->drawEllipse( line.at(i), d_selectedDim + 2, d_selectedDim + 2 );
164                 painter->setPen( defaultPen );
165             }
166         } else {
167             painter->setBrush( regularColor );
168             painter->drawEllipse( line.at(i), d_regularDim, d_regularDim );
169 
170             if ( m_nodesList.at(i).isEditingHighlighted() ||
171                  m_nodesList.at(i).isMergingHighlighted() ) {
172                 QPen defaultPen = painter->pen();
173                 QPen newPen;
174                 newPen.setWidth( defaultPen.width() + 3 );
175                 newPen.setColor( glowColor );
176 
177                 painter->setPen( newPen );
178                 painter->setBrush( Qt::NoBrush );
179                 painter->drawEllipse( line.at(i), d_regularDim + 2, d_regularDim + 2 );
180                 painter->setPen( defaultPen );
181             }
182         }
183     }
184 
185     if ( m_virtualHoveredNode != -1 ) {
186         painter->setBrush( hoveredColor );
187 
188         GeoDataCoordinates newCoords;
189         if ( m_virtualHoveredNode + 1 ) {
190             newCoords = line.at( m_virtualHoveredNode + 1 ).interpolate( line.at( m_virtualHoveredNode ), 0.5 );
191         } else {
192             newCoords = line.first().interpolate( line.last(), 0.5 );
193         }
194         painter->drawEllipse( newCoords, d_hoveredDim, d_hoveredDim );
195     }
196 }
197 
containsPoint(const QPoint & point) const198 bool PolylineAnnotation::containsPoint( const QPoint &point ) const
199 {
200     if ( state() == SceneGraphicsItem::Editing ) {
201         return nodeContains( point ) != -1 || polylineContains( point );
202     } else if ( state() == SceneGraphicsItem::MergingNodes ) {
203         return nodeContains( point ) != -1;
204     } else if ( state() == SceneGraphicsItem::AddingNodes ) {
205         return virtualNodeContains( point ) != -1 ||
206                nodeContains( point ) != -1 ||
207                polylineContains( point );
208     }
209 
210     return false;
211 }
212 
nodeContains(const QPoint & point) const213 int PolylineAnnotation::nodeContains( const QPoint &point ) const
214 {
215     if ( !hasFocus() ) {
216         return -1;
217     }
218 
219     for ( int i = 0; i < m_nodesList.size(); ++i ) {
220         if ( m_nodesList.at(i).containsPoint( point ) ) {
221             return i;
222         }
223     }
224 
225     return -1;
226 }
227 
virtualNodeContains(const QPoint & point) const228 int PolylineAnnotation::virtualNodeContains( const QPoint &point ) const
229 {
230     if ( !hasFocus() ) {
231         return -1;
232     }
233 
234     for ( int i = 0; i < m_virtualNodesList.size(); ++i ) {
235         if ( m_virtualNodesList.at(i).containsPoint( point ) )
236             return i;
237     }
238 
239     return -1;
240 }
241 
polylineContains(const QPoint & point) const242 bool PolylineAnnotation::polylineContains( const QPoint &point ) const
243 {
244     return m_polylineRegion.contains( point );
245 }
246 
dealWithItemChange(const SceneGraphicsItem * other)247 void PolylineAnnotation::dealWithItemChange( const SceneGraphicsItem *other )
248 {
249     Q_UNUSED( other );
250 
251     // So far we only deal with item changes when hovering nodes, so that
252     // they do not remain hovered when changing the item we interact with.
253     if ( state() == SceneGraphicsItem::Editing ) {
254         if ( m_hoveredNodeIndex != -1 &&
255              m_hoveredNodeIndex < static_cast<GeoDataLineString*>( placemark()->geometry() )->size() ) {
256             m_nodesList[m_hoveredNodeIndex].setFlag( PolylineNode::NodeIsEditingHighlighted, false );
257         }
258 
259         m_hoveredNodeIndex = -1;
260     } else if ( state() == SceneGraphicsItem::MergingNodes ) {
261         if ( m_hoveredNodeIndex != -1 ) {
262             m_nodesList[m_hoveredNodeIndex].setFlag( PolylineNode::NodeIsMergingHighlighted, false );
263         }
264 
265         m_hoveredNodeIndex = -1;
266     } else if ( state() == SceneGraphicsItem::AddingNodes ) {
267         m_virtualHoveredNode = -1;
268     }
269 }
270 
move(const GeoDataCoordinates & source,const GeoDataCoordinates & destination)271 void PolylineAnnotation::move( const GeoDataCoordinates &source, const GeoDataCoordinates &destination )
272 {
273     GeoDataLineString *lineString = static_cast<GeoDataLineString*>( placemark()->geometry() );
274     GeoDataLineString oldLineString = *lineString;
275     OsmPlacemarkData *osmData = nullptr;
276     if ( placemark()->hasOsmData() ) {
277         osmData = &placemark()->osmData();
278     }
279     lineString->clear();
280 
281     const qreal deltaLat = destination.latitude() - source.latitude();
282     const qreal deltaLon = destination.longitude() - source.longitude();
283 
284     Quaternion latRectAxis = Quaternion::fromEuler( 0, destination.longitude(), 0);
285     Quaternion latAxis = Quaternion::fromEuler( -deltaLat, 0, 0);
286     Quaternion lonAxis = Quaternion::fromEuler(0, deltaLon, 0);
287     Quaternion rotAxis = latRectAxis * latAxis * latRectAxis.inverse() * lonAxis;
288 
289     for ( int i = 0; i < oldLineString.size(); ++i ) {
290         const GeoDataCoordinates movedPoint = oldLineString.at(i).rotateAround(rotAxis);
291         if ( osmData ) {
292             osmData->changeNodeReference( oldLineString.at( i ), movedPoint );
293         }
294         lineString->append( movedPoint );
295     }
296 }
297 
setBusy(bool enabled)298 void PolylineAnnotation::setBusy( bool enabled )
299 {
300     m_busy = enabled;
301 
302     if ( !enabled && m_animation && state() == SceneGraphicsItem::MergingNodes ) {
303         if ( m_firstMergedNode != -1 && m_secondMergedNode != -1 ) {
304             // Update the PolylineNodes lists after the animation has finished its execution.
305             m_nodesList[m_secondMergedNode].setFlag( PolylineNode::NodeIsMergingHighlighted, false );
306             m_hoveredNodeIndex = -1;
307 
308             // Remove the merging node flag and add the NodeIsSelected flag if either one of the
309             // merged nodes had been selected before merging them.
310             m_nodesList[m_secondMergedNode].setFlag( PolylineNode::NodeIsMerged, false );
311             if ( m_nodesList[m_firstMergedNode].isSelected() ) {
312                 m_nodesList[m_secondMergedNode].setFlag( PolylineNode::NodeIsSelected );
313             }
314             m_nodesList.removeAt( m_firstMergedNode );
315 
316             m_firstMergedNode = -1;
317             m_secondMergedNode = -1;
318         }
319 
320         delete m_animation;
321     }
322 }
323 
isBusy() const324 bool PolylineAnnotation::isBusy() const
325 {
326     return m_busy;
327 }
328 
deselectAllNodes()329 void PolylineAnnotation::deselectAllNodes()
330 {
331     if ( state() != SceneGraphicsItem::Editing ) {
332         return;
333     }
334 
335     for ( int i = 0 ; i < m_nodesList.size(); ++i ) {
336         m_nodesList[i].setFlag( PolylineNode::NodeIsSelected, false );
337     }
338 }
339 
deleteAllSelectedNodes()340 void PolylineAnnotation::deleteAllSelectedNodes()
341 {
342     if ( state() != SceneGraphicsItem::Editing ) {
343         return;
344     }
345 
346     GeoDataLineString *line = static_cast<GeoDataLineString*>( placemark()->geometry() );
347     OsmPlacemarkData *osmData = nullptr;
348     if ( placemark()->hasOsmData() ) {
349         osmData = &placemark()->osmData();
350     }
351     for ( int i = 0; i < line->size(); ++i ) {
352         if ( m_nodesList.at(i).isSelected() ) {
353             if ( m_nodesList.size() <= 2 ) {
354                 setRequest( SceneGraphicsItem::RemovePolylineRequest );
355                 return;
356             }
357             if ( osmData ) {
358                 osmData->removeNodeReference( line->at( i ) );
359             }
360             m_nodesList.removeAt( i );
361             line->remove( i );
362             --i;
363         }
364     }
365 }
366 
deleteClickedNode()367 void PolylineAnnotation::deleteClickedNode()
368 {
369     if ( state() != SceneGraphicsItem::Editing ) {
370         return;
371     }
372 
373     GeoDataLineString *line = static_cast<GeoDataLineString*>( placemark()->geometry() );
374     OsmPlacemarkData *osmData = nullptr;
375     if ( placemark()->hasOsmData() ) {
376         osmData = &placemark()->osmData();
377     }
378     if ( m_nodesList.size() <= 2 ) {
379         setRequest( SceneGraphicsItem::RemovePolylineRequest );
380         return;
381     }
382 
383     if ( osmData ) {
384         osmData->removeMemberReference( m_clickedNodeIndex );
385     }
386 
387     m_nodesList.removeAt( m_clickedNodeIndex );
388     line->remove( m_clickedNodeIndex );
389  }
390 
changeClickedNodeSelection()391 void PolylineAnnotation::changeClickedNodeSelection()
392 {
393     if ( state() != SceneGraphicsItem::Editing ) {
394         return;
395     }
396 
397     m_nodesList[m_clickedNodeIndex].setFlag( PolylineNode::NodeIsSelected,
398                                              !m_nodesList[m_clickedNodeIndex].isSelected() );
399 }
400 
hasNodesSelected() const401 bool PolylineAnnotation::hasNodesSelected() const
402 {
403     for ( int i = 0; i < m_nodesList.size(); ++i ) {
404         if ( m_nodesList.at(i).isSelected() ) {
405             return true;
406         }
407     }
408 
409     return false;
410 }
411 
clickedNodeIsSelected() const412 bool PolylineAnnotation::clickedNodeIsSelected() const
413 {
414     return m_nodesList[m_clickedNodeIndex].isSelected();
415 }
416 
animation()417 QPointer<MergingPolylineNodesAnimation> PolylineAnnotation::animation()
418 {
419     return m_animation;
420 }
421 
mousePressEvent(QMouseEvent * event)422 bool PolylineAnnotation::mousePressEvent( QMouseEvent *event )
423 {
424     if ( !m_viewport || m_busy ) {
425         return false;
426     }
427 
428     setRequest( SceneGraphicsItem::NoRequest );
429 
430     if ( state() == SceneGraphicsItem::Editing ) {
431         return processEditingOnPress( event );
432     } else if ( state() == SceneGraphicsItem::MergingNodes ) {
433         return processMergingOnPress( event );
434     } else if ( state() == SceneGraphicsItem::AddingNodes ) {
435         return processAddingNodesOnPress( event );
436     }
437 
438     return false;
439 }
440 
mouseMoveEvent(QMouseEvent * event)441 bool PolylineAnnotation::mouseMoveEvent( QMouseEvent *event )
442 {
443     if ( !m_viewport || m_busy ) {
444         return false;
445     }
446 
447     setRequest( SceneGraphicsItem::NoRequest );
448 
449     if ( state() == SceneGraphicsItem::Editing ) {
450         return processEditingOnMove( event );
451     } else if ( state() == SceneGraphicsItem::MergingNodes ) {
452         return processMergingOnMove( event );
453     } else if ( state() == SceneGraphicsItem::AddingNodes ) {
454         return processAddingNodesOnMove( event );
455     }
456 
457     return false;
458 }
459 
mouseReleaseEvent(QMouseEvent * event)460 bool PolylineAnnotation::mouseReleaseEvent( QMouseEvent *event )
461 {
462     if ( !m_viewport || m_busy ) {
463         return false;
464     }
465 
466     setRequest( SceneGraphicsItem::NoRequest );
467 
468     if ( state() == SceneGraphicsItem::Editing ) {
469         return processEditingOnRelease( event );
470     } else if ( state() == SceneGraphicsItem::MergingNodes ) {
471         return processMergingOnRelease( event );
472     } else if ( state() == SceneGraphicsItem::AddingNodes ) {
473         return processAddingNodesOnRelease( event );
474     }
475 
476     return false;
477 }
478 
dealWithStateChange(SceneGraphicsItem::ActionState previousState)479 void PolylineAnnotation::dealWithStateChange( SceneGraphicsItem::ActionState previousState )
480 {
481     // Dealing with cases when exiting a state has an effect on this item.
482     if ( previousState == SceneGraphicsItem::DrawingPolyline ) {
483         // nothing so far
484     } else if ( previousState == SceneGraphicsItem::Editing ) {
485         // Make sure that when changing the state, there is no highlighted node.
486         if ( m_hoveredNodeIndex != -1 ) {
487             m_nodesList[m_hoveredNodeIndex].setFlag( PolylineNode::NodeIsEditingHighlighted, false );
488         }
489 
490         m_clickedNodeIndex = -1;
491         m_hoveredNodeIndex = -1;
492     } else if ( previousState == SceneGraphicsItem::MergingNodes ) {
493         // If there was only a node selected for being merged and the state changed,
494         // deselect it.
495         if ( m_firstMergedNode != -1 ) {
496             m_nodesList[m_firstMergedNode].setFlag( PolylineNode::NodeIsMerged, false );
497         }
498 
499         // Make sure that when changing the state, there is no highlighted node.
500         if ( m_hoveredNodeIndex != -1 ) {
501             if ( m_hoveredNodeIndex != -1 ) {
502                 m_nodesList[m_hoveredNodeIndex].setFlag( PolylineNode::NodeIsEditingHighlighted, false );
503             }
504         }
505 
506         m_hoveredNodeIndex = -1;
507         delete m_animation;
508     } else if ( previousState == SceneGraphicsItem::AddingNodes ) {
509         m_virtualNodesList.clear();
510         m_virtualHoveredNode = -1;
511         m_adjustedNode = -1;
512     }
513 
514     // Dealing with cases when entering a state has an effect on this item, or
515     // initializations are needed.
516     if ( state() == SceneGraphicsItem::Editing ) {
517         m_interactingObj = InteractingNothing;
518         m_clickedNodeIndex = -1;
519         m_hoveredNodeIndex = -1;
520     } else if ( state() == SceneGraphicsItem::MergingNodes ) {
521         m_firstMergedNode = -1;
522         m_secondMergedNode = -1;
523         m_hoveredNodeIndex = -1;
524         m_animation = nullptr;
525     } else if ( state() == SceneGraphicsItem::AddingNodes ) {
526         m_virtualHoveredNode = -1;
527         m_adjustedNode = -1;
528     }
529 }
530 
processEditingOnPress(QMouseEvent * mouseEvent)531 bool PolylineAnnotation::processEditingOnPress( QMouseEvent *mouseEvent )
532 {
533     if ( mouseEvent->button() != Qt::LeftButton && mouseEvent->button() != Qt::RightButton ) {
534         return false;
535     }
536 
537     qreal lat, lon;
538     m_viewport->geoCoordinates( mouseEvent->pos().x(),
539                                 mouseEvent->pos().y(),
540                                 lon, lat,
541                                 GeoDataCoordinates::Radian );
542     m_movedPointCoords.set( lon, lat );
543 
544     // First check if one of the nodes has been clicked.
545     m_clickedNodeIndex = nodeContains( mouseEvent->pos() );
546     if ( m_clickedNodeIndex != -1 ) {
547         if ( mouseEvent->button() == Qt::RightButton ) {
548             setRequest( SceneGraphicsItem::ShowNodeRmbMenu );
549         } else {
550             Q_ASSERT( mouseEvent->button() == Qt::LeftButton );
551             m_interactingObj = InteractingNode;
552         }
553 
554         return true;
555     }
556 
557     // Then check if the 'interior' of the polyline has been clicked (by interior
558     // I mean its lines excepting its nodes).
559     if ( polylineContains( mouseEvent->pos() ) ) {
560         if ( mouseEvent->button() == Qt::RightButton ) {
561             setRequest( SceneGraphicsItem::ShowPolylineRmbMenu );
562         } else {
563             Q_ASSERT( mouseEvent->button() == Qt::LeftButton );
564             m_interactingObj = InteractingPolyline;
565         }
566 
567         return true;
568     }
569 
570     return false;
571 }
572 
processEditingOnMove(QMouseEvent * mouseEvent)573 bool PolylineAnnotation::processEditingOnMove( QMouseEvent *mouseEvent )
574 {
575     if ( !m_viewport ) {
576         return false;
577     }
578 
579     qreal lon, lat;
580     m_viewport->geoCoordinates( mouseEvent->pos().x(),
581                                 mouseEvent->pos().y(),
582                                 lon, lat,
583                                 GeoDataCoordinates::Radian );
584     const GeoDataCoordinates newCoords( lon, lat );
585 
586     if ( m_interactingObj == InteractingNode ) {
587         GeoDataLineString *line = static_cast<GeoDataLineString*>( placemark()->geometry() );
588         OsmPlacemarkData *osmData = nullptr;
589         if ( placemark()->hasOsmData() ) {
590             osmData = &placemark()->osmData();
591         }
592 
593         // Keeping the OsmPlacemarkData synchronized with the geometry
594         if ( osmData ) {
595             osmData->changeNodeReference( line->at( m_clickedNodeIndex ), newCoords );
596         }
597         line->at(m_clickedNodeIndex) = newCoords;
598 
599         return true;
600     } else if ( m_interactingObj == InteractingPolyline ) {
601         GeoDataLineString *lineString = static_cast<GeoDataLineString*>( placemark()->geometry() );
602         OsmPlacemarkData *osmData = nullptr;
603         if ( placemark()->hasOsmData() ) {
604             osmData = &placemark()->osmData();
605         }
606         const GeoDataLineString oldLineString = *lineString;
607         lineString->clear();
608 
609         const qreal deltaLat = lat - m_movedPointCoords.latitude();
610         const qreal deltaLon = lon - m_movedPointCoords.longitude();
611 
612         Quaternion latRectAxis = Quaternion::fromEuler( 0, lon, 0);
613         Quaternion latAxis = Quaternion::fromEuler( -deltaLat, 0, 0);
614         Quaternion lonAxis = Quaternion::fromEuler(0, deltaLon, 0);
615         Quaternion rotAxis = latRectAxis * latAxis * latRectAxis.inverse() * lonAxis;
616 
617         for ( int i = 0; i < oldLineString.size(); ++i ) {
618             const GeoDataCoordinates movedPoint = oldLineString.at(i).rotateAround(rotAxis);
619             if ( osmData ) {
620                 osmData->changeNodeReference( oldLineString.at( i ), movedPoint );
621             }
622             lineString->append( movedPoint );
623         }
624 
625         m_movedPointCoords = newCoords;
626         return true;
627     }
628 
629     return dealWithHovering( mouseEvent );
630 }
631 
processEditingOnRelease(QMouseEvent * mouseEvent)632 bool PolylineAnnotation::processEditingOnRelease( QMouseEvent *mouseEvent )
633 {
634     static const int mouseMoveOffset = 1;
635 
636     if ( mouseEvent->button() != Qt::LeftButton ) {
637         return false;
638     }
639 
640     if ( m_interactingObj == InteractingNode ) {
641         qreal x, y;
642         m_viewport->screenCoordinates( m_movedPointCoords.longitude(),
643                                        m_movedPointCoords.latitude(),
644                                        x, y );
645         // The node gets selected only if it is clicked and not moved.
646         if ( qFabs(mouseEvent->pos().x() - x) > mouseMoveOffset ||
647              qFabs(mouseEvent->pos().y() - y) > mouseMoveOffset ) {
648             m_interactingObj = InteractingNothing;
649             return true;
650         }
651 
652         m_nodesList[m_clickedNodeIndex].setFlag( PolylineNode::NodeIsSelected,
653                                                  !m_nodesList.at(m_clickedNodeIndex).isSelected() );
654         m_interactingObj = InteractingNothing;
655         return true;
656     } else if ( m_interactingObj == InteractingPolyline ) {
657         // Nothing special happens at polyline release.
658         m_interactingObj = InteractingNothing;
659         return true;
660     }
661 
662     return false;
663 }
664 
processMergingOnPress(QMouseEvent * mouseEvent)665 bool PolylineAnnotation::processMergingOnPress( QMouseEvent *mouseEvent )
666 {
667     if ( mouseEvent->button() != Qt::LeftButton ) {
668         return false;
669     }
670 
671     GeoDataLineString line = static_cast<GeoDataLineString>( *placemark()->geometry() );
672 
673     const int index = nodeContains( mouseEvent->pos() );
674     if ( index == -1 ) {
675         return false;
676     }
677 
678     // If this is the first node selected to be merged.
679     if ( m_firstMergedNode == -1 ) {
680         m_firstMergedNode = index;
681         m_nodesList[index].setFlag( PolylineNode::NodeIsMerged );
682    } else {
683         Q_ASSERT( m_firstMergedNode != -1 );
684 
685         // Clicking two times the same node results in unmarking it for merging.
686         if ( m_firstMergedNode == index ) {
687             m_nodesList[index].setFlag( PolylineNode::NodeIsMerged, false );
688             m_firstMergedNode = -1;
689             return true;
690         }
691 
692         // If these two nodes are the last ones remained as part of the polyline, remove
693         // the whole polyline.
694         if ( line.size() <= 2 ) {
695             setRequest( SceneGraphicsItem::RemovePolylineRequest );
696             return true;
697         }
698         m_nodesList[index].setFlag( PolylineNode::NodeIsMerged );
699         m_secondMergedNode = index;
700 
701         delete m_animation;
702         m_animation = new MergingPolylineNodesAnimation( this );
703         setRequest( SceneGraphicsItem::StartPolylineAnimation );
704     }
705 
706     return true;
707 }
708 
processMergingOnMove(QMouseEvent * mouseEvent)709 bool PolylineAnnotation::processMergingOnMove( QMouseEvent *mouseEvent )
710 {
711     return dealWithHovering( mouseEvent );
712 }
713 
processMergingOnRelease(QMouseEvent * mouseEvent)714 bool PolylineAnnotation::processMergingOnRelease( QMouseEvent *mouseEvent )
715 {
716     Q_UNUSED( mouseEvent );
717     return true;
718 }
719 
processAddingNodesOnPress(QMouseEvent * mouseEvent)720 bool PolylineAnnotation::processAddingNodesOnPress( QMouseEvent *mouseEvent )
721 {
722     if ( mouseEvent->button() != Qt::LeftButton ) {
723         return false;
724     }
725 
726     GeoDataLineString *line = static_cast<GeoDataLineString*>( placemark()->geometry() );
727 
728     // If a virtual node has just been clicked, add it to the polyline and start 'adjusting'
729     // its position.
730     const int virtualIndex = virtualNodeContains( mouseEvent->pos() );
731     if ( virtualIndex != -1 && m_adjustedNode == -1 ) {
732         Q_ASSERT( m_virtualHoveredNode == virtualIndex );
733 
734         line->insert( virtualIndex + 1, line->at( virtualIndex ).interpolate( line->at( virtualIndex + 1 ), 0.5 ) );
735         m_nodesList.insert( virtualIndex + 1, PolylineNode() );
736 
737         m_adjustedNode = virtualIndex + 1;
738         m_virtualHoveredNode = -1;
739         return true;
740     }
741 
742     // If a virtual node which has been previously clicked and selected to become a
743     // 'real node' is clicked one more time, it stops from being 'adjusted'.
744     const int realIndex = nodeContains( mouseEvent->pos() );
745     if ( realIndex != -1 && m_adjustedNode != -1 ) {
746         m_adjustedNode = -1;
747         return true;
748     }
749 
750     return false;
751 }
752 
processAddingNodesOnMove(QMouseEvent * mouseEvent)753 bool PolylineAnnotation::processAddingNodesOnMove( QMouseEvent *mouseEvent )
754 {
755     Q_ASSERT( mouseEvent->button() == Qt::NoButton );
756 
757     const int index = virtualNodeContains( mouseEvent->pos() );
758 
759     // If we are adjusting a virtual node which has just been clicked and became real, just
760     // change its coordinates when moving it, as we do with nodes in Editing state on move.
761     if ( m_adjustedNode != -1 ) {
762         // The virtual node which has just been added is always the last within
763         // GeoDataLinearRing's container.qreal lon, lat;
764         qreal lon, lat;
765         m_viewport->geoCoordinates( mouseEvent->pos().x(),
766                                     mouseEvent->pos().y(),
767                                     lon, lat,
768                                     GeoDataCoordinates::Radian );
769         const GeoDataCoordinates newCoords( lon, lat );
770         GeoDataLineString *line = static_cast<GeoDataLineString*>( placemark()->geometry() );
771         line->at(m_adjustedNode) = newCoords;
772 
773         return true;
774 
775     // If we are hovering a virtual node, store its index in order to be painted in drawNodes
776     // method.
777     } else if ( index != -1 ) {
778         m_virtualHoveredNode = index;
779         return true;
780     }
781 
782     return false;
783 }
784 
processAddingNodesOnRelease(QMouseEvent * mouseEvent)785 bool PolylineAnnotation::processAddingNodesOnRelease( QMouseEvent *mouseEvent )
786 {
787     Q_UNUSED( mouseEvent );
788     return m_adjustedNode == -1;
789 }
790 
dealWithHovering(QMouseEvent * mouseEvent)791 bool PolylineAnnotation::dealWithHovering( QMouseEvent *mouseEvent )
792 {
793     const PolylineNode::PolyNodeFlag flag = state() == SceneGraphicsItem::Editing ?
794                                                        PolylineNode::NodeIsEditingHighlighted :
795                                                        PolylineNode::NodeIsMergingHighlighted;
796 
797     const int index = nodeContains( mouseEvent->pos() );
798     if ( index != -1 ) {
799         if ( !m_nodesList.at(index).isEditingHighlighted() &&
800              !m_nodesList.at(index).isMergingHighlighted() ) {
801             // Deal with the case when two nodes are very close to each other.
802             if ( m_hoveredNodeIndex != -1 ) {
803                 m_nodesList[m_hoveredNodeIndex].setFlag( flag, false );
804             }
805 
806             m_hoveredNodeIndex = index;
807             m_nodesList[index].setFlag( flag );
808             setRequest( ChangeCursorPolylineNodeHover );
809         }
810 
811         return true;
812     } else if ( m_hoveredNodeIndex != -1 ) {
813         m_nodesList[m_hoveredNodeIndex].setFlag( flag, false );
814         m_hoveredNodeIndex = -1;
815 
816         return true;
817     }
818 
819     // This means that the interior of the polyline has been hovered so we catch this event too.
820     setRequest( ChangeCursorPolylineLineHover );
821     return true;
822 }
823 
graphicType() const824 const char *PolylineAnnotation::graphicType() const
825 {
826     return SceneGraphicsTypes::SceneGraphicPolylineAnnotation;
827 }
828 
829 }
830