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