File indexing completed on 2024-05-05 03:50:40

0001 // SPDX-License-Identifier: LGPL-2.1-or-later
0002 //
0003 // SPDX-FileCopyrightText: 2014 Calin Cruceru <crucerucalincristian@gmail.com>
0004 //
0005 
0006 // Self
0007 #include "PolylineAnnotation.h"
0008 
0009 // Qt
0010 #include <QApplication>
0011 #include <QPalette>
0012 #include <qmath.h>
0013 
0014 // Marble
0015 #include "SceneGraphicsTypes.h"
0016 #include "GeoPainter.h"
0017 #include "PolylineNode.h"
0018 #include "GeoDataLineString.h"
0019 #include "GeoDataPlacemark.h"
0020 #include "ViewportParams.h"
0021 #include "MarbleColors.h"
0022 #include "MergingPolylineNodesAnimation.h"
0023 #include "osm/OsmPlacemarkData.h"
0024 
0025 
0026 namespace Marble
0027 {
0028 
0029 const int PolylineAnnotation::regularDim = 15;
0030 const int PolylineAnnotation::selectedDim = 15;
0031 const int PolylineAnnotation::mergedDim = 20;
0032 const int PolylineAnnotation::hoveredDim = 20;
0033 const QColor PolylineAnnotation::regularColor = Oxygen::aluminumGray3;
0034 const QColor PolylineAnnotation::mergedColor = Oxygen::emeraldGreen6;
0035 
0036 PolylineAnnotation::PolylineAnnotation( GeoDataPlacemark *placemark ) :
0037     SceneGraphicsItem( placemark ),
0038     m_viewport( nullptr ),
0039     m_regionsInitialized( false ),
0040     m_busy( false ),
0041     m_interactingObj( InteractingNothing ),
0042     m_clickedNodeIndex( -1 ),
0043     m_hoveredNodeIndex( -1 ),
0044     m_virtualHoveredNode( -1 )
0045 
0046 {
0047     setPaintLayers(QStringList() << "PolylineAnnotation");
0048 }
0049 
0050 PolylineAnnotation::~PolylineAnnotation()
0051 {
0052     delete m_animation;
0053 }
0054 
0055 void PolylineAnnotation::paint(GeoPainter *painter, const ViewportParams *viewport , const QString &layer, int tileZoomLevel)
0056 {
0057     Q_UNUSED(layer);
0058     Q_UNUSED(tileZoomLevel);
0059     m_viewport = viewport;
0060     Q_ASSERT(geodata_cast<GeoDataLineString>(placemark()->geometry()));
0061 
0062     painter->save();
0063     if ( state() == SceneGraphicsItem::DrawingPolyline || !m_regionsInitialized ) {
0064         setupRegionsLists( painter );
0065         m_regionsInitialized = true;
0066     } else {
0067         updateRegions( painter );
0068     }
0069 
0070     if ( hasFocus() ) {
0071         drawNodes( painter );
0072     }
0073     painter->restore();
0074 }
0075 
0076 void PolylineAnnotation::setupRegionsLists( GeoPainter *painter )
0077 {
0078     Q_ASSERT( state() == SceneGraphicsItem::DrawingPolyline || !m_regionsInitialized );
0079     const GeoDataLineString line = static_cast<const GeoDataLineString>( *placemark()->geometry() );
0080 
0081     // Add polyline nodes.
0082     QVector<GeoDataCoordinates>::ConstIterator itBegin = line.constBegin();
0083     QVector<GeoDataCoordinates>::ConstIterator itEnd = line.constEnd();
0084 
0085     m_nodesList.clear();
0086     m_nodesList.reserve(line.size());
0087     for ( ; itBegin != itEnd; ++itBegin ) {
0088         const PolylineNode newNode = PolylineNode( painter->regionFromEllipse( *itBegin, regularDim, regularDim ) );
0089         m_nodesList.append( newNode );
0090     }
0091 
0092     // Add region from polyline so that events on polyline's 'lines' could be caught.
0093     m_polylineRegion = painter->regionFromPolyline( line, 15 );
0094 }
0095 
0096 void PolylineAnnotation::updateRegions( GeoPainter *painter )
0097 {
0098     if ( m_busy ) {
0099         return;
0100     }
0101 
0102     const GeoDataLineString line = static_cast<const GeoDataLineString>( *placemark()->geometry() );
0103 
0104     if ( state() == SceneGraphicsItem::AddingNodes ) {
0105         // Create and update virtual nodes lists when being in the AddingPolgonNodes state, to
0106         // avoid overhead in other states.
0107         m_virtualNodesList.clear();
0108         for ( int i = 0; i < line.size() - 1; ++i ) {
0109             const QRegion newRegion( painter->regionFromEllipse( line.at(i).interpolate( line.at(i+1), 0.5 ),
0110                                                                  hoveredDim, hoveredDim ) );
0111             m_virtualNodesList.append( PolylineNode( newRegion ) );
0112         }
0113     }
0114 
0115 
0116     // Update the polyline region;
0117     m_polylineRegion = painter->regionFromPolyline( line, 15 );
0118 
0119     // Update the node lists.
0120     for ( int i = 0; i < m_nodesList.size(); ++i ) {
0121         const QRegion newRegion = m_nodesList.at(i).isSelected() ?
0122                                   painter->regionFromEllipse( line.at(i), selectedDim, selectedDim ) :
0123                                   painter->regionFromEllipse( line.at(i), regularDim, regularDim );
0124         m_nodesList[i].setRegion( newRegion );
0125     }
0126 }
0127 
0128 void PolylineAnnotation::drawNodes( GeoPainter *painter )
0129 {
0130     // These are the 'real' dimensions of the drawn nodes. The ones which have class scope are used
0131     // to generate the regions and they are a little bit larger, because, for example, it would be
0132     // a little bit too hard to select nodes.
0133     static const int d_regularDim = 10;
0134     static const int d_selectedDim = 10;
0135     static const int d_mergedDim = 20;
0136     static const int d_hoveredDim = 20;
0137 
0138     const GeoDataLineString line = static_cast<const GeoDataLineString>( *placemark()->geometry() );
0139 
0140     QColor glowColor = QApplication::palette().highlightedText().color();
0141     glowColor.setAlpha(120);
0142     auto const selectedColor = QApplication::palette().highlight().color();
0143     auto const hoveredColor = selectedColor;
0144 
0145     for ( int i = 0; i < line.size(); ++i ) {
0146         // The order here is important, because a merged node can be at the same time selected.
0147         if ( m_nodesList.at(i).isBeingMerged() ) {
0148             painter->setBrush( mergedColor );
0149             painter->drawEllipse( line.at(i), d_mergedDim, d_mergedDim );
0150         } else if ( m_nodesList.at(i).isSelected() ) {
0151             painter->setBrush( selectedColor );
0152             painter->drawEllipse( line.at(i), d_selectedDim, d_selectedDim );
0153 
0154             if ( m_nodesList.at(i).isEditingHighlighted() ||
0155                  m_nodesList.at(i).isMergingHighlighted() ) {
0156                 QPen defaultPen = painter->pen();
0157                 QPen newPen;
0158                 newPen.setWidth( defaultPen.width() + 3 );
0159                 newPen.setColor( glowColor );
0160 
0161                 painter->setBrush( Qt::NoBrush );
0162                 painter->setPen( newPen );
0163                 painter->drawEllipse( line.at(i), d_selectedDim + 2, d_selectedDim + 2 );
0164                 painter->setPen( defaultPen );
0165             }
0166         } else {
0167             painter->setBrush( regularColor );
0168             painter->drawEllipse( line.at(i), d_regularDim, d_regularDim );
0169 
0170             if ( m_nodesList.at(i).isEditingHighlighted() ||
0171                  m_nodesList.at(i).isMergingHighlighted() ) {
0172                 QPen defaultPen = painter->pen();
0173                 QPen newPen;
0174                 newPen.setWidth( defaultPen.width() + 3 );
0175                 newPen.setColor( glowColor );
0176 
0177                 painter->setPen( newPen );
0178                 painter->setBrush( Qt::NoBrush );
0179                 painter->drawEllipse( line.at(i), d_regularDim + 2, d_regularDim + 2 );
0180                 painter->setPen( defaultPen );
0181             }
0182         }
0183     }
0184 
0185     if ( m_virtualHoveredNode != -1 ) {
0186         painter->setBrush( hoveredColor );
0187 
0188         GeoDataCoordinates newCoords;
0189         if ( m_virtualHoveredNode + 1 ) {
0190             newCoords = line.at( m_virtualHoveredNode + 1 ).interpolate( line.at( m_virtualHoveredNode ), 0.5 );
0191         } else {
0192             newCoords = line.first().interpolate( line.last(), 0.5 );
0193         }
0194         painter->drawEllipse( newCoords, d_hoveredDim, d_hoveredDim );
0195     }
0196 }
0197 
0198 bool PolylineAnnotation::containsPoint( const QPoint &point ) const
0199 {
0200     if ( state() == SceneGraphicsItem::Editing ) {
0201         return nodeContains( point ) != -1 || polylineContains( point );
0202     } else if ( state() == SceneGraphicsItem::MergingNodes ) {
0203         return nodeContains( point ) != -1;
0204     } else if ( state() == SceneGraphicsItem::AddingNodes ) {
0205         return virtualNodeContains( point ) != -1 ||
0206                nodeContains( point ) != -1 ||
0207                polylineContains( point );
0208     }
0209 
0210     return false;
0211 }
0212 
0213 int PolylineAnnotation::nodeContains( const QPoint &point ) const
0214 {
0215     if ( !hasFocus() ) {
0216         return -1;
0217     }
0218 
0219     for ( int i = 0; i < m_nodesList.size(); ++i ) {
0220         if ( m_nodesList.at(i).containsPoint( point ) ) {
0221             return i;
0222         }
0223     }
0224 
0225     return -1;
0226 }
0227 
0228 int PolylineAnnotation::virtualNodeContains( const QPoint &point ) const
0229 {
0230     if ( !hasFocus() ) {
0231         return -1;
0232     }
0233 
0234     for ( int i = 0; i < m_virtualNodesList.size(); ++i ) {
0235         if ( m_virtualNodesList.at(i).containsPoint( point ) )
0236             return i;
0237     }
0238 
0239     return -1;
0240 }
0241 
0242 bool PolylineAnnotation::polylineContains( const QPoint &point ) const
0243 {
0244     return m_polylineRegion.contains( point );
0245 }
0246 
0247 void PolylineAnnotation::dealWithItemChange( const SceneGraphicsItem *other )
0248 {
0249     Q_UNUSED( other );
0250 
0251     // So far we only deal with item changes when hovering nodes, so that
0252     // they do not remain hovered when changing the item we interact with.
0253     if ( state() == SceneGraphicsItem::Editing ) {
0254         if ( m_hoveredNodeIndex != -1 &&
0255              m_hoveredNodeIndex < static_cast<GeoDataLineString*>( placemark()->geometry() )->size() ) {
0256             m_nodesList[m_hoveredNodeIndex].setFlag( PolylineNode::NodeIsEditingHighlighted, false );
0257         }
0258 
0259         m_hoveredNodeIndex = -1;
0260     } else if ( state() == SceneGraphicsItem::MergingNodes ) {
0261         if ( m_hoveredNodeIndex != -1 ) {
0262             m_nodesList[m_hoveredNodeIndex].setFlag( PolylineNode::NodeIsMergingHighlighted, false );
0263         }
0264 
0265         m_hoveredNodeIndex = -1;
0266     } else if ( state() == SceneGraphicsItem::AddingNodes ) {
0267         m_virtualHoveredNode = -1;
0268     }
0269 }
0270 
0271 void PolylineAnnotation::move( const GeoDataCoordinates &source, const GeoDataCoordinates &destination )
0272 {
0273     GeoDataLineString *lineString = static_cast<GeoDataLineString*>( placemark()->geometry() );
0274     GeoDataLineString oldLineString = *lineString;
0275     OsmPlacemarkData *osmData = nullptr;
0276     if ( placemark()->hasOsmData() ) {
0277         osmData = &placemark()->osmData();
0278     }
0279     lineString->clear();
0280 
0281     const qreal deltaLat = destination.latitude() - source.latitude();
0282     const qreal deltaLon = destination.longitude() - source.longitude();
0283 
0284     Quaternion latRectAxis = Quaternion::fromEuler( 0, destination.longitude(), 0);
0285     Quaternion latAxis = Quaternion::fromEuler( -deltaLat, 0, 0);
0286     Quaternion lonAxis = Quaternion::fromEuler(0, deltaLon, 0);
0287     Quaternion rotAxis = latRectAxis * latAxis * latRectAxis.inverse() * lonAxis;
0288 
0289     for ( int i = 0; i < oldLineString.size(); ++i ) {
0290         const GeoDataCoordinates movedPoint = oldLineString.at(i).rotateAround(rotAxis);
0291         if ( osmData ) {
0292             osmData->changeNodeReference( oldLineString.at( i ), movedPoint );
0293         }
0294         lineString->append( movedPoint );
0295     }
0296 }
0297 
0298 void PolylineAnnotation::setBusy( bool enabled )
0299 {
0300     m_busy = enabled;
0301 
0302     if ( !enabled && m_animation && state() == SceneGraphicsItem::MergingNodes ) {
0303         if ( m_firstMergedNode != -1 && m_secondMergedNode != -1 ) {
0304             // Update the PolylineNodes lists after the animation has finished its execution.
0305             m_nodesList[m_secondMergedNode].setFlag( PolylineNode::NodeIsMergingHighlighted, false );
0306             m_hoveredNodeIndex = -1;
0307 
0308             // Remove the merging node flag and add the NodeIsSelected flag if either one of the
0309             // merged nodes had been selected before merging them.
0310             m_nodesList[m_secondMergedNode].setFlag( PolylineNode::NodeIsMerged, false );
0311             if ( m_nodesList[m_firstMergedNode].isSelected() ) {
0312                 m_nodesList[m_secondMergedNode].setFlag( PolylineNode::NodeIsSelected );
0313             }
0314             m_nodesList.removeAt( m_firstMergedNode );
0315 
0316             m_firstMergedNode = -1;
0317             m_secondMergedNode = -1;
0318         }
0319 
0320         delete m_animation;
0321     }
0322 }
0323 
0324 bool PolylineAnnotation::isBusy() const
0325 {
0326     return m_busy;
0327 }
0328 
0329 void PolylineAnnotation::deselectAllNodes()
0330 {
0331     if ( state() != SceneGraphicsItem::Editing ) {
0332         return;
0333     }
0334 
0335     for ( int i = 0 ; i < m_nodesList.size(); ++i ) {
0336         m_nodesList[i].setFlag( PolylineNode::NodeIsSelected, false );
0337     }
0338 }
0339 
0340 void PolylineAnnotation::deleteAllSelectedNodes()
0341 {
0342     if ( state() != SceneGraphicsItem::Editing ) {
0343         return;
0344     }
0345 
0346     GeoDataLineString *line = static_cast<GeoDataLineString*>( placemark()->geometry() );
0347     OsmPlacemarkData *osmData = nullptr;
0348     if ( placemark()->hasOsmData() ) {
0349         osmData = &placemark()->osmData();
0350     }
0351     for ( int i = 0; i < line->size(); ++i ) {
0352         if ( m_nodesList.at(i).isSelected() ) {
0353             if ( m_nodesList.size() <= 2 ) {
0354                 setRequest( SceneGraphicsItem::RemovePolylineRequest );
0355                 return;
0356             }
0357             if ( osmData ) {
0358                 osmData->removeNodeReference( line->at( i ) );
0359             }
0360             m_nodesList.removeAt( i );
0361             line->remove( i );
0362             --i;
0363         }
0364     }
0365 }
0366 
0367 void PolylineAnnotation::deleteClickedNode()
0368 {
0369     if ( state() != SceneGraphicsItem::Editing ) {
0370         return;
0371     }
0372 
0373     GeoDataLineString *line = static_cast<GeoDataLineString*>( placemark()->geometry() );
0374     OsmPlacemarkData *osmData = nullptr;
0375     if ( placemark()->hasOsmData() ) {
0376         osmData = &placemark()->osmData();
0377     }
0378     if ( m_nodesList.size() <= 2 ) {
0379         setRequest( SceneGraphicsItem::RemovePolylineRequest );
0380         return;
0381     }
0382 
0383     if ( osmData ) {
0384         osmData->removeMemberReference( m_clickedNodeIndex );
0385     }
0386 
0387     m_nodesList.removeAt( m_clickedNodeIndex );
0388     line->remove( m_clickedNodeIndex );
0389  }
0390 
0391 void PolylineAnnotation::changeClickedNodeSelection()
0392 {
0393     if ( state() != SceneGraphicsItem::Editing ) {
0394         return;
0395     }
0396 
0397     m_nodesList[m_clickedNodeIndex].setFlag( PolylineNode::NodeIsSelected,
0398                                              !m_nodesList[m_clickedNodeIndex].isSelected() );
0399 }
0400 
0401 bool PolylineAnnotation::hasNodesSelected() const
0402 {
0403     for ( int i = 0; i < m_nodesList.size(); ++i ) {
0404         if ( m_nodesList.at(i).isSelected() ) {
0405             return true;
0406         }
0407     }
0408 
0409     return false;
0410 }
0411 
0412 bool PolylineAnnotation::clickedNodeIsSelected() const
0413 {
0414     return m_nodesList[m_clickedNodeIndex].isSelected();
0415 }
0416 
0417 QPointer<MergingPolylineNodesAnimation> PolylineAnnotation::animation()
0418 {
0419     return m_animation;
0420 }
0421 
0422 bool PolylineAnnotation::mousePressEvent( QMouseEvent *event )
0423 {
0424     if ( !m_viewport || m_busy ) {
0425         return false;
0426     }
0427 
0428     setRequest( SceneGraphicsItem::NoRequest );
0429 
0430     if ( state() == SceneGraphicsItem::Editing ) {
0431         return processEditingOnPress( event );
0432     } else if ( state() == SceneGraphicsItem::MergingNodes ) {
0433         return processMergingOnPress( event );
0434     } else if ( state() == SceneGraphicsItem::AddingNodes ) {
0435         return processAddingNodesOnPress( event );
0436     }
0437 
0438     return false;
0439 }
0440 
0441 bool PolylineAnnotation::mouseMoveEvent( QMouseEvent *event )
0442 {
0443     if ( !m_viewport || m_busy ) {
0444         return false;
0445     }
0446 
0447     setRequest( SceneGraphicsItem::NoRequest );
0448 
0449     if ( state() == SceneGraphicsItem::Editing ) {
0450         return processEditingOnMove( event );
0451     } else if ( state() == SceneGraphicsItem::MergingNodes ) {
0452         return processMergingOnMove( event );
0453     } else if ( state() == SceneGraphicsItem::AddingNodes ) {
0454         return processAddingNodesOnMove( event );
0455     }
0456 
0457     return false;
0458 }
0459 
0460 bool PolylineAnnotation::mouseReleaseEvent( QMouseEvent *event )
0461 {
0462     if ( !m_viewport || m_busy ) {
0463         return false;
0464     }
0465 
0466     setRequest( SceneGraphicsItem::NoRequest );
0467 
0468     if ( state() == SceneGraphicsItem::Editing ) {
0469         return processEditingOnRelease( event );
0470     } else if ( state() == SceneGraphicsItem::MergingNodes ) {
0471         return processMergingOnRelease( event );
0472     } else if ( state() == SceneGraphicsItem::AddingNodes ) {
0473         return processAddingNodesOnRelease( event );
0474     }
0475 
0476     return false;
0477 }
0478 
0479 void PolylineAnnotation::dealWithStateChange( SceneGraphicsItem::ActionState previousState )
0480 {
0481     // Dealing with cases when exiting a state has an effect on this item.
0482     if ( previousState == SceneGraphicsItem::DrawingPolyline ) {
0483         // nothing so far
0484     } else if ( previousState == SceneGraphicsItem::Editing ) {
0485         // Make sure that when changing the state, there is no highlighted node.
0486         if ( m_hoveredNodeIndex != -1 ) {
0487             m_nodesList[m_hoveredNodeIndex].setFlag( PolylineNode::NodeIsEditingHighlighted, false );
0488         }
0489 
0490         m_clickedNodeIndex = -1;
0491         m_hoveredNodeIndex = -1;
0492     } else if ( previousState == SceneGraphicsItem::MergingNodes ) {
0493         // If there was only a node selected for being merged and the state changed,
0494         // deselect it.
0495         if ( m_firstMergedNode != -1 ) {
0496             m_nodesList[m_firstMergedNode].setFlag( PolylineNode::NodeIsMerged, false );
0497         }
0498 
0499         // Make sure that when changing the state, there is no highlighted node.
0500         if ( m_hoveredNodeIndex != -1 ) {
0501             if ( m_hoveredNodeIndex != -1 ) {
0502                 m_nodesList[m_hoveredNodeIndex].setFlag( PolylineNode::NodeIsEditingHighlighted, false );
0503             }
0504         }
0505 
0506         m_hoveredNodeIndex = -1;
0507         delete m_animation;
0508     } else if ( previousState == SceneGraphicsItem::AddingNodes ) {
0509         m_virtualNodesList.clear();
0510         m_virtualHoveredNode = -1;
0511         m_adjustedNode = -1;
0512     }
0513 
0514     // Dealing with cases when entering a state has an effect on this item, or
0515     // initializations are needed.
0516     if ( state() == SceneGraphicsItem::Editing ) {
0517         m_interactingObj = InteractingNothing;
0518         m_clickedNodeIndex = -1;
0519         m_hoveredNodeIndex = -1;
0520     } else if ( state() == SceneGraphicsItem::MergingNodes ) {
0521         m_firstMergedNode = -1;
0522         m_secondMergedNode = -1;
0523         m_hoveredNodeIndex = -1;
0524         m_animation = nullptr;
0525     } else if ( state() == SceneGraphicsItem::AddingNodes ) {
0526         m_virtualHoveredNode = -1;
0527         m_adjustedNode = -1;
0528     }
0529 }
0530 
0531 bool PolylineAnnotation::processEditingOnPress( QMouseEvent *mouseEvent )
0532 {
0533     if ( mouseEvent->button() != Qt::LeftButton && mouseEvent->button() != Qt::RightButton ) {
0534         return false;
0535     }
0536 
0537     qreal lat, lon;
0538     m_viewport->geoCoordinates( mouseEvent->pos().x(),
0539                                 mouseEvent->pos().y(),
0540                                 lon, lat,
0541                                 GeoDataCoordinates::Radian );
0542     m_movedPointCoords.set( lon, lat );
0543 
0544     // First check if one of the nodes has been clicked.
0545     m_clickedNodeIndex = nodeContains( mouseEvent->pos() );
0546     if ( m_clickedNodeIndex != -1 ) {
0547         if ( mouseEvent->button() == Qt::RightButton ) {
0548             setRequest( SceneGraphicsItem::ShowNodeRmbMenu );
0549         } else {
0550             Q_ASSERT( mouseEvent->button() == Qt::LeftButton );
0551             m_interactingObj = InteractingNode;
0552         }
0553 
0554         return true;
0555     }
0556 
0557     // Then check if the 'interior' of the polyline has been clicked (by interior
0558     // I mean its lines excepting its nodes).
0559     if ( polylineContains( mouseEvent->pos() ) ) {
0560         if ( mouseEvent->button() == Qt::RightButton ) {
0561             setRequest( SceneGraphicsItem::ShowPolylineRmbMenu );
0562         } else {
0563             Q_ASSERT( mouseEvent->button() == Qt::LeftButton );
0564             m_interactingObj = InteractingPolyline;
0565         }
0566 
0567         return true;
0568     }
0569 
0570     return false;
0571 }
0572 
0573 bool PolylineAnnotation::processEditingOnMove( QMouseEvent *mouseEvent )
0574 {
0575     if ( !m_viewport ) {
0576         return false;
0577     }
0578 
0579     qreal lon, lat;
0580     m_viewport->geoCoordinates( mouseEvent->pos().x(),
0581                                 mouseEvent->pos().y(),
0582                                 lon, lat,
0583                                 GeoDataCoordinates::Radian );
0584     const GeoDataCoordinates newCoords( lon, lat );
0585 
0586     if ( m_interactingObj == InteractingNode ) {
0587         GeoDataLineString *line = static_cast<GeoDataLineString*>( placemark()->geometry() );
0588         OsmPlacemarkData *osmData = nullptr;
0589         if ( placemark()->hasOsmData() ) {
0590             osmData = &placemark()->osmData();
0591         }
0592 
0593         // Keeping the OsmPlacemarkData synchronized with the geometry
0594         if ( osmData ) {
0595             osmData->changeNodeReference( line->at( m_clickedNodeIndex ), newCoords );
0596         }
0597         line->at(m_clickedNodeIndex) = newCoords;
0598 
0599         return true;
0600     } else if ( m_interactingObj == InteractingPolyline ) {
0601         GeoDataLineString *lineString = static_cast<GeoDataLineString*>( placemark()->geometry() );
0602         OsmPlacemarkData *osmData = nullptr;
0603         if ( placemark()->hasOsmData() ) {
0604             osmData = &placemark()->osmData();
0605         }
0606         const GeoDataLineString oldLineString = *lineString;
0607         lineString->clear();
0608 
0609         const qreal deltaLat = lat - m_movedPointCoords.latitude();
0610         const qreal deltaLon = lon - m_movedPointCoords.longitude();
0611 
0612         Quaternion latRectAxis = Quaternion::fromEuler( 0, lon, 0);
0613         Quaternion latAxis = Quaternion::fromEuler( -deltaLat, 0, 0);
0614         Quaternion lonAxis = Quaternion::fromEuler(0, deltaLon, 0);
0615         Quaternion rotAxis = latRectAxis * latAxis * latRectAxis.inverse() * lonAxis;
0616 
0617         for ( int i = 0; i < oldLineString.size(); ++i ) {
0618             const GeoDataCoordinates movedPoint = oldLineString.at(i).rotateAround(rotAxis);
0619             if ( osmData ) {
0620                 osmData->changeNodeReference( oldLineString.at( i ), movedPoint );
0621             }
0622             lineString->append( movedPoint );
0623         }
0624 
0625         m_movedPointCoords = newCoords;
0626         return true;
0627     }
0628 
0629     return dealWithHovering( mouseEvent );
0630 }
0631 
0632 bool PolylineAnnotation::processEditingOnRelease( QMouseEvent *mouseEvent )
0633 {
0634     static const int mouseMoveOffset = 1;
0635 
0636     if ( mouseEvent->button() != Qt::LeftButton ) {
0637         return false;
0638     }
0639 
0640     if ( m_interactingObj == InteractingNode ) {
0641         qreal x, y;
0642         m_viewport->screenCoordinates( m_movedPointCoords.longitude(),
0643                                        m_movedPointCoords.latitude(),
0644                                        x, y );
0645         // The node gets selected only if it is clicked and not moved.
0646         if ( qFabs(mouseEvent->pos().x() - x) > mouseMoveOffset ||
0647              qFabs(mouseEvent->pos().y() - y) > mouseMoveOffset ) {
0648             m_interactingObj = InteractingNothing;
0649             return true;
0650         }
0651 
0652         m_nodesList[m_clickedNodeIndex].setFlag( PolylineNode::NodeIsSelected,
0653                                                  !m_nodesList.at(m_clickedNodeIndex).isSelected() );
0654         m_interactingObj = InteractingNothing;
0655         return true;
0656     } else if ( m_interactingObj == InteractingPolyline ) {
0657         // Nothing special happens at polyline release.
0658         m_interactingObj = InteractingNothing;
0659         return true;
0660     }
0661 
0662     return false;
0663 }
0664 
0665 bool PolylineAnnotation::processMergingOnPress( QMouseEvent *mouseEvent )
0666 {
0667     if ( mouseEvent->button() != Qt::LeftButton ) {
0668         return false;
0669     }
0670 
0671     GeoDataLineString line = static_cast<GeoDataLineString>( *placemark()->geometry() );
0672 
0673     const int index = nodeContains( mouseEvent->pos() );
0674     if ( index == -1 ) {
0675         return false;
0676     }
0677 
0678     // If this is the first node selected to be merged.
0679     if ( m_firstMergedNode == -1 ) {
0680         m_firstMergedNode = index;
0681         m_nodesList[index].setFlag( PolylineNode::NodeIsMerged );
0682    } else {
0683         Q_ASSERT( m_firstMergedNode != -1 );
0684 
0685         // Clicking two times the same node results in unmarking it for merging.
0686         if ( m_firstMergedNode == index ) {
0687             m_nodesList[index].setFlag( PolylineNode::NodeIsMerged, false );
0688             m_firstMergedNode = -1;
0689             return true;
0690         }
0691 
0692         // If these two nodes are the last ones remained as part of the polyline, remove
0693         // the whole polyline.
0694         if ( line.size() <= 2 ) {
0695             setRequest( SceneGraphicsItem::RemovePolylineRequest );
0696             return true;
0697         }
0698         m_nodesList[index].setFlag( PolylineNode::NodeIsMerged );
0699         m_secondMergedNode = index;
0700 
0701         delete m_animation;
0702         m_animation = new MergingPolylineNodesAnimation( this );
0703         setRequest( SceneGraphicsItem::StartPolylineAnimation );
0704     }
0705 
0706     return true;
0707 }
0708 
0709 bool PolylineAnnotation::processMergingOnMove( QMouseEvent *mouseEvent )
0710 {
0711     return dealWithHovering( mouseEvent );
0712 }
0713 
0714 bool PolylineAnnotation::processMergingOnRelease( QMouseEvent *mouseEvent )
0715 {
0716     Q_UNUSED( mouseEvent );
0717     return true;
0718 }
0719 
0720 bool PolylineAnnotation::processAddingNodesOnPress( QMouseEvent *mouseEvent )
0721 {
0722     if ( mouseEvent->button() != Qt::LeftButton ) {
0723         return false;
0724     }
0725 
0726     GeoDataLineString *line = static_cast<GeoDataLineString*>( placemark()->geometry() );
0727 
0728     // If a virtual node has just been clicked, add it to the polyline and start 'adjusting'
0729     // its position.
0730     const int virtualIndex = virtualNodeContains( mouseEvent->pos() );
0731     if ( virtualIndex != -1 && m_adjustedNode == -1 ) {
0732         Q_ASSERT( m_virtualHoveredNode == virtualIndex );
0733 
0734         line->insert( virtualIndex + 1, line->at( virtualIndex ).interpolate( line->at( virtualIndex + 1 ), 0.5 ) );
0735         m_nodesList.insert( virtualIndex + 1, PolylineNode() );
0736 
0737         m_adjustedNode = virtualIndex + 1;
0738         m_virtualHoveredNode = -1;
0739         return true;
0740     }
0741 
0742     // If a virtual node which has been previously clicked and selected to become a
0743     // 'real node' is clicked one more time, it stops from being 'adjusted'.
0744     const int realIndex = nodeContains( mouseEvent->pos() );
0745     if ( realIndex != -1 && m_adjustedNode != -1 ) {
0746         m_adjustedNode = -1;
0747         return true;
0748     }
0749 
0750     return false;
0751 }
0752 
0753 bool PolylineAnnotation::processAddingNodesOnMove( QMouseEvent *mouseEvent )
0754 {
0755     Q_ASSERT( mouseEvent->button() == Qt::NoButton );
0756 
0757     const int index = virtualNodeContains( mouseEvent->pos() );
0758 
0759     // If we are adjusting a virtual node which has just been clicked and became real, just
0760     // change its coordinates when moving it, as we do with nodes in Editing state on move.
0761     if ( m_adjustedNode != -1 ) {
0762         // The virtual node which has just been added is always the last within
0763         // GeoDataLinearRing's container.qreal lon, lat;
0764         qreal lon, lat;
0765         m_viewport->geoCoordinates( mouseEvent->pos().x(),
0766                                     mouseEvent->pos().y(),
0767                                     lon, lat,
0768                                     GeoDataCoordinates::Radian );
0769         const GeoDataCoordinates newCoords( lon, lat );
0770         GeoDataLineString *line = static_cast<GeoDataLineString*>( placemark()->geometry() );
0771         line->at(m_adjustedNode) = newCoords;
0772 
0773         return true;
0774 
0775     // If we are hovering a virtual node, store its index in order to be painted in drawNodes
0776     // method.
0777     } else if ( index != -1 ) {
0778         m_virtualHoveredNode = index;
0779         return true;
0780     }
0781 
0782     return false;
0783 }
0784 
0785 bool PolylineAnnotation::processAddingNodesOnRelease( QMouseEvent *mouseEvent )
0786 {
0787     Q_UNUSED( mouseEvent );
0788     return m_adjustedNode == -1;
0789 }
0790 
0791 bool PolylineAnnotation::dealWithHovering( QMouseEvent *mouseEvent )
0792 {
0793     const PolylineNode::PolyNodeFlag flag = state() == SceneGraphicsItem::Editing ?
0794                                                        PolylineNode::NodeIsEditingHighlighted :
0795                                                        PolylineNode::NodeIsMergingHighlighted;
0796 
0797     const int index = nodeContains( mouseEvent->pos() );
0798     if ( index != -1 ) {
0799         if ( !m_nodesList.at(index).isEditingHighlighted() &&
0800              !m_nodesList.at(index).isMergingHighlighted() ) {
0801             // Deal with the case when two nodes are very close to each other.
0802             if ( m_hoveredNodeIndex != -1 ) {
0803                 m_nodesList[m_hoveredNodeIndex].setFlag( flag, false );
0804             }
0805 
0806             m_hoveredNodeIndex = index;
0807             m_nodesList[index].setFlag( flag );
0808             setRequest( ChangeCursorPolylineNodeHover );
0809         }
0810 
0811         return true;
0812     } else if ( m_hoveredNodeIndex != -1 ) {
0813         m_nodesList[m_hoveredNodeIndex].setFlag( flag, false );
0814         m_hoveredNodeIndex = -1;
0815 
0816         return true;
0817     }
0818 
0819     // This means that the interior of the polyline has been hovered so we catch this event too.
0820     setRequest( ChangeCursorPolylineLineHover );
0821     return true;
0822 }
0823 
0824 const char *PolylineAnnotation::graphicType() const
0825 {
0826     return SceneGraphicsTypes::SceneGraphicPolylineAnnotation;
0827 }
0828 
0829 }