File indexing completed on 2024-05-05 03:50:38
0001 // SPDX-License-Identifier: LGPL-2.1-or-later 0002 // 0003 // SPDX-FileCopyrightText: 2009 Andrew Manson <g.real.ate@gmail.com> 0004 // SPDX-FileCopyrightText: 2013 Thibaut Gridel <tgridel@free.fr> 0005 // SPDX-FileCopyrightText: 2014 Calin Cruceru <crucerucalincristian@gmail.com> 0006 // 0007 0008 // Self 0009 #include "AreaAnnotation.h" 0010 0011 // Qt 0012 #include <qmath.h> 0013 #include <QPair> 0014 #include <QApplication> 0015 #include <QPalette> 0016 0017 // Marble 0018 #include "GeoDataPlacemark.h" 0019 #include "GeoDataLinearRing.h" 0020 #include "GeoDataPolygon.h" 0021 #include "GeoPainter.h" 0022 #include "ViewportParams.h" 0023 #include "SceneGraphicsTypes.h" 0024 #include "MarbleColors.h" 0025 #include "MergingPolygonNodesAnimation.h" 0026 #include "PolylineNode.h" 0027 #include "osm/OsmPlacemarkData.h" 0028 0029 0030 namespace Marble { 0031 0032 const int AreaAnnotation::regularDim = 15; 0033 const int AreaAnnotation::selectedDim = 15; 0034 const int AreaAnnotation::mergedDim = 20; 0035 const int AreaAnnotation::hoveredDim = 20; 0036 const QColor AreaAnnotation::regularColor = Oxygen::aluminumGray3; 0037 const QColor AreaAnnotation::mergedColor = Oxygen::emeraldGreen6; 0038 0039 AreaAnnotation::AreaAnnotation( GeoDataPlacemark *placemark ) : 0040 SceneGraphicsItem( placemark ), 0041 m_viewport( nullptr ), 0042 m_regionsInitialized( false ), 0043 m_busy( false ), 0044 m_hoveredNode( -1, -1 ), 0045 m_interactingObj( InteractingNothing ), 0046 m_virtualHovered( -1, -1 ) 0047 { 0048 setPaintLayers(QStringList() << "AreaAnnotation"); 0049 } 0050 0051 AreaAnnotation::~AreaAnnotation() 0052 { 0053 delete m_animation; 0054 } 0055 0056 void AreaAnnotation::paint(GeoPainter *painter, const ViewportParams *viewport , const QString &layer, int tileZoomLevel) 0057 { 0058 Q_UNUSED(layer); 0059 Q_UNUSED(tileZoomLevel); 0060 m_viewport = viewport; 0061 Q_ASSERT(geodata_cast<GeoDataPolygon>(placemark()->geometry())); 0062 0063 painter->save(); 0064 if ( state() == SceneGraphicsItem::DrawingPolygon || !m_regionsInitialized ) { 0065 setupRegionsLists( painter ); 0066 m_regionsInitialized = true; 0067 } else { 0068 updateRegions( painter ); 0069 } 0070 0071 if ( hasFocus() ) { 0072 drawNodes( painter ); 0073 } 0074 painter->restore(); 0075 } 0076 0077 bool AreaAnnotation::containsPoint( const QPoint &point ) const 0078 { 0079 if ( m_busy ) { 0080 return false; 0081 } 0082 0083 if ( state() == SceneGraphicsItem::Editing ) { 0084 return ( polygonContains( point ) && innerBoundsContain( point ) == -1 ) || 0085 outerNodeContains( point ) != -1 || 0086 innerNodeContains( point ) != QPair<int, int>( -1, -1 ); 0087 0088 } else if ( state() == SceneGraphicsItem::AddingPolygonHole ) { 0089 return polygonContains( point ) && outerNodeContains( point ) == -1 && 0090 innerNodeContains( point ) == QPair<int, int>( -1, -1 ); 0091 0092 } else if ( state() == SceneGraphicsItem::MergingNodes ) { 0093 return outerNodeContains( point ) != -1 || 0094 innerNodeContains( point ) != QPair<int, int>( -1, -1 ); 0095 0096 } else if ( state() == SceneGraphicsItem::AddingNodes ) { 0097 return ( polygonContains( point ) && innerBoundsContain( point ) == -1 ) || 0098 virtualNodeContains( point ) != QPair<int, int>( -1, -1 ) || 0099 innerNodeContains( point ) != QPair<int, int>( -1, -1 ) || 0100 outerNodeContains( point ) != -1; 0101 } 0102 0103 return false; 0104 } 0105 0106 void AreaAnnotation::dealWithItemChange( const SceneGraphicsItem *other ) 0107 { 0108 Q_UNUSED( other ); 0109 0110 // So far we only deal with item changes when hovering nodes, so that 0111 // they do not remain hovered when changing the item we interact with. 0112 if ( state() == SceneGraphicsItem::Editing ) { 0113 if ( m_hoveredNode != QPair<int, int>( -1, -1 ) ) { 0114 const int i = m_hoveredNode.first; 0115 const int j = m_hoveredNode.second; 0116 0117 if ( j == -1 ) { 0118 m_outerNodesList[i].setFlag( PolylineNode::NodeIsEditingHighlighted, false ); 0119 } else { 0120 m_innerNodesList[i][j].setFlag( PolylineNode::NodeIsEditingHighlighted, false ); 0121 } 0122 } 0123 0124 m_hoveredNode = QPair<int, int>( -1, -1 ); 0125 } else if ( state() == SceneGraphicsItem::MergingNodes ) { 0126 if ( m_hoveredNode != QPair<int, int>( -1, -1 ) ) { 0127 const int i = m_hoveredNode.first; 0128 const int j = m_hoveredNode.second; 0129 0130 if ( j == -1 ) { 0131 m_outerNodesList[i].setFlag( PolylineNode::NodeIsMergingHighlighted, false ); 0132 } else { 0133 m_innerNodesList[i][j].setFlag( PolylineNode::NodeIsMergingHighlighted, false ); 0134 } 0135 } 0136 0137 m_hoveredNode = QPair<int, int>( -1, -1 ); 0138 } else if ( state() == SceneGraphicsItem::AddingNodes ) { 0139 m_virtualHovered = QPair<int, int>( -1, -1 ); 0140 } 0141 } 0142 0143 void AreaAnnotation::move( const GeoDataCoordinates &source, const GeoDataCoordinates &destination ) 0144 { 0145 GeoDataPolygon *polygon = static_cast<GeoDataPolygon*>( placemark()->geometry() ); 0146 GeoDataLinearRing outerRing = polygon->outerBoundary(); 0147 QVector<GeoDataLinearRing> innerRings = polygon->innerBoundaries(); 0148 OsmPlacemarkData *osmData = nullptr; 0149 if ( placemark()->hasOsmData() ) { 0150 osmData = &placemark()->osmData(); 0151 } 0152 0153 polygon->outerBoundary().clear(); 0154 polygon->innerBoundaries().clear(); 0155 0156 const qreal deltaLat = destination.latitude() - source.latitude(); 0157 const qreal deltaLon = destination.longitude() - source.longitude(); 0158 0159 Quaternion latRectAxis = Quaternion::fromEuler( 0, destination.longitude(), 0); 0160 Quaternion latAxis = Quaternion::fromEuler( -deltaLat, 0, 0); 0161 Quaternion lonAxis = Quaternion::fromEuler(0, deltaLon, 0); 0162 Quaternion rotAxis = latRectAxis * latAxis * latRectAxis.inverse() * lonAxis; 0163 0164 for ( int i = 0; i < outerRing.size(); ++i ) { 0165 const GeoDataCoordinates movedPoint = outerRing.at(i).rotateAround(rotAxis); 0166 // Keeping the OsmPlacemarkData synchronized with the geometry 0167 if ( osmData ) { 0168 osmData->memberReference( -1 ).changeNodeReference( outerRing.at( i ), movedPoint ); 0169 } 0170 polygon->outerBoundary().append( movedPoint ); 0171 } 0172 0173 for ( int i = 0; i < innerRings.size(); ++i ) { 0174 GeoDataLinearRing newRing( Tessellate ); 0175 for ( int j = 0; j < innerRings.at(i).size(); ++j ) { 0176 const GeoDataCoordinates movedPoint = innerRings.at(i).at(j).rotateAround(rotAxis); 0177 if ( osmData ) { 0178 osmData->memberReference( i ).changeNodeReference( innerRings.at( i ).at( j ), movedPoint ); 0179 } 0180 newRing.append( movedPoint ); 0181 } 0182 polygon->innerBoundaries().append( newRing ); 0183 } 0184 } 0185 0186 void AreaAnnotation::setBusy( bool enabled ) 0187 { 0188 m_busy = enabled; 0189 0190 if ( !enabled && m_animation && state() == SceneGraphicsItem::MergingNodes ) { 0191 // Update the PolylineNodes lists after the animation has finished its execution. 0192 const int ff = m_firstMergedNode.first; 0193 const int fs = m_firstMergedNode.second; 0194 const int sf = m_secondMergedNode.first; 0195 const int ss = m_secondMergedNode.second; 0196 0197 if ( ff != -1 && fs == -1 && sf != -1 && ss == -1 ) { 0198 m_outerNodesList[sf].setFlag( PolylineNode::NodeIsMergingHighlighted, false ); 0199 m_hoveredNode = QPair<int, int>( -1, -1 ); 0200 0201 // Remove the merging node flag and add the NodeIsSelected flag if either one of the 0202 // merged nodes had been selected before merging them. 0203 m_outerNodesList[sf].setFlag( PolylineNode::NodeIsMerged, false ); 0204 if ( m_outerNodesList.at(ff).isSelected() ) { 0205 m_outerNodesList[sf].setFlag( PolylineNode::NodeIsSelected ); 0206 } 0207 m_outerNodesList.removeAt( ff ); 0208 0209 m_firstMergedNode = QPair<int, int>( -1, -1 ); 0210 m_secondMergedNode = QPair<int, int>( -1, -1 ); 0211 } else if ( ff != -1 && fs != -1 && sf != -1 && ss != -1 ) { 0212 m_innerNodesList[sf][ss].setFlag( PolylineNode::NodeIsMergingHighlighted, false ); 0213 m_hoveredNode = QPair<int, int>( -1, -1 ); 0214 0215 m_innerNodesList[sf][ss].setFlag( PolylineNode::NodeIsMerged, false ); 0216 if ( m_innerNodesList.at(ff).at(fs).isSelected() ) { 0217 m_innerNodesList[sf][ss].setFlag( PolylineNode::NodeIsSelected ); 0218 } 0219 m_innerNodesList[sf].removeAt( fs ); 0220 0221 m_firstMergedNode = QPair<int, int>( -1, -1 ); 0222 m_secondMergedNode = QPair<int, int>( -1, -1 ); 0223 } 0224 0225 delete m_animation; 0226 } 0227 } 0228 0229 bool AreaAnnotation::isBusy() const 0230 { 0231 return m_busy; 0232 } 0233 0234 void AreaAnnotation::deselectAllNodes() 0235 { 0236 if ( state() != SceneGraphicsItem::Editing ) { 0237 return; 0238 } 0239 0240 for ( int i = 0 ; i < m_outerNodesList.size(); ++i ) { 0241 m_outerNodesList[i].setFlag( PolylineNode::NodeIsSelected, false ); 0242 } 0243 0244 for ( int i = 0; i < m_innerNodesList.size(); ++i ) { 0245 for ( int j = 0; j < m_innerNodesList.at(i).size(); ++j ) { 0246 m_innerNodesList[i][j].setFlag( PolylineNode::NodeIsSelected, false ); 0247 } 0248 } 0249 } 0250 0251 void AreaAnnotation::deleteAllSelectedNodes() 0252 { 0253 if ( state() != SceneGraphicsItem::Editing ) { 0254 return; 0255 } 0256 0257 GeoDataPolygon *polygon = static_cast<GeoDataPolygon*>( placemark()->geometry() ); 0258 GeoDataLinearRing &outerRing = polygon->outerBoundary(); 0259 QVector<GeoDataLinearRing> &innerRings = polygon->innerBoundaries(); 0260 OsmPlacemarkData *osmData = nullptr; 0261 OsmPlacemarkData initialOsmData; 0262 if ( placemark()->hasOsmData() ) { 0263 osmData = &placemark()->osmData(); 0264 initialOsmData = placemark()->osmData(); 0265 } 0266 0267 // If it proves inefficient, try something different. 0268 GeoDataLinearRing initialOuterRing = polygon->outerBoundary(); 0269 QVector<GeoDataLinearRing> initialInnerRings = polygon->innerBoundaries(); 0270 const QVector<PolylineNode> initialOuterNodes = m_outerNodesList; 0271 const QVector< QVector<PolylineNode> > initialInnerNodes = m_innerNodesList; 0272 0273 for ( int i = 0; i < outerRing.size(); ++i ) { 0274 if ( m_outerNodesList.at(i).isSelected() ) { 0275 if ( m_outerNodesList.size() <= 3 ) { 0276 setRequest( SceneGraphicsItem::RemovePolygonRequest ); 0277 return; 0278 } 0279 if ( osmData ) { 0280 osmData->memberReference( -1 ).removeNodeReference( initialOuterRing.at( i ) ); 0281 } 0282 m_outerNodesList.removeAt( i ); 0283 outerRing.remove( i ); 0284 --i; 0285 } 0286 } 0287 0288 for ( int i = 0; i < innerRings.size(); ++i ) { 0289 for ( int j = 0; j < innerRings.at(i).size(); ++j ) { 0290 if ( m_innerNodesList.at(i).at(j).isSelected() ) { 0291 if ( m_innerNodesList.at(i).size() <= 3 ) { 0292 0293 if ( osmData ) { 0294 osmData->removeMemberReference( i ); 0295 } 0296 innerRings.remove( i ); 0297 m_innerNodesList.removeAt( i ); 0298 --i; 0299 break; 0300 } 0301 0302 if ( osmData ) { 0303 osmData->memberReference( i ).removeNodeReference( initialInnerRings.at( i ).at( j ) ); 0304 } 0305 innerRings[i].remove( j ); 0306 m_innerNodesList[i].removeAt( j ); 0307 --j; 0308 } 0309 } 0310 } 0311 0312 if ( !isValidPolygon() ) { 0313 if ( osmData ) { 0314 placemark()->setOsmData( initialOsmData ); 0315 } 0316 polygon->outerBoundary() = initialOuterRing; 0317 polygon->innerBoundaries() = initialInnerRings; 0318 m_outerNodesList = initialOuterNodes; 0319 m_innerNodesList = initialInnerNodes; 0320 setRequest( SceneGraphicsItem::InvalidShapeWarning ); 0321 } 0322 } 0323 0324 void AreaAnnotation::deleteClickedNode() 0325 { 0326 if ( state() != SceneGraphicsItem::Editing ) { 0327 return; 0328 } 0329 0330 GeoDataPolygon *polygon = static_cast<GeoDataPolygon*>( placemark()->geometry() ); 0331 GeoDataLinearRing &outerRing = polygon->outerBoundary(); 0332 QVector<GeoDataLinearRing> &innerRings = polygon->innerBoundaries(); 0333 OsmPlacemarkData *osmData = nullptr; 0334 OsmPlacemarkData initialOsmData; 0335 if ( placemark()->hasOsmData() ) { 0336 osmData = &placemark()->osmData(); 0337 initialOsmData = placemark()->osmData(); 0338 } 0339 0340 // If it proves inefficient, try something different. 0341 GeoDataLinearRing initialOuterRing = polygon->outerBoundary(); 0342 QVector<GeoDataLinearRing> initialInnerRings = polygon->innerBoundaries(); 0343 const QVector<PolylineNode> initialOuterNodes = m_outerNodesList; 0344 const QVector< QVector<PolylineNode> > initialInnerNodes = m_innerNodesList; 0345 0346 int i = m_clickedNodeIndexes.first; 0347 int j = m_clickedNodeIndexes.second; 0348 0349 m_hoveredNode = QPair<int, int>( -1, -1 ); 0350 0351 if ( i != -1 && j == -1 ) { 0352 if ( m_outerNodesList.size() <= 3 ) { 0353 setRequest( SceneGraphicsItem::RemovePolygonRequest ); 0354 return; 0355 } 0356 0357 // Keep the OsmPlacemarkData synchronized with the geometry 0358 if ( osmData ) { 0359 osmData->removeNodeReference( outerRing.at( i ) ); 0360 } 0361 outerRing.remove( i ); 0362 m_outerNodesList.removeAt( i ); 0363 } else if ( i != -1 && j != -1 ) { 0364 if ( m_innerNodesList.at(i).size() <= 3 ) { 0365 if ( osmData ) { 0366 osmData->removeMemberReference( i ); 0367 } 0368 innerRings.remove( i ); 0369 m_innerNodesList.removeAt( i ); 0370 return; 0371 } 0372 if ( osmData ) { 0373 osmData->memberReference( i ).removeNodeReference( innerRings.at( i ).at( j ) ); 0374 } 0375 innerRings[i].remove( j ); 0376 m_innerNodesList[i].removeAt( j ); 0377 } 0378 0379 if ( !isValidPolygon() ) { 0380 if ( osmData ) { 0381 placemark()->setOsmData( initialOsmData ); 0382 } 0383 polygon->outerBoundary() = initialOuterRing; 0384 polygon->innerBoundaries() = initialInnerRings; 0385 m_outerNodesList = initialOuterNodes; 0386 m_innerNodesList = initialInnerNodes; 0387 setRequest( SceneGraphicsItem::InvalidShapeWarning ); 0388 } 0389 } 0390 0391 void AreaAnnotation::changeClickedNodeSelection() 0392 { 0393 if ( state() != SceneGraphicsItem::Editing ) { 0394 return; 0395 } 0396 0397 const int i = m_clickedNodeIndexes.first; 0398 const int j = m_clickedNodeIndexes.second; 0399 0400 if ( i != -1 && j == -1 ) { 0401 m_outerNodesList[i].setFlag( PolylineNode::NodeIsSelected, 0402 !m_outerNodesList.at(i).isSelected() ); 0403 } else if ( i != -1 && j != -1 ) { 0404 m_innerNodesList[i][j].setFlag( PolylineNode::NodeIsSelected, 0405 !m_innerNodesList.at(i).at(j).isSelected() ); 0406 } 0407 } 0408 0409 bool AreaAnnotation::hasNodesSelected() const 0410 { 0411 for ( int i = 0; i < m_outerNodesList.size(); ++i ) { 0412 if ( m_outerNodesList.at(i).isSelected() ) { 0413 return true; 0414 } 0415 } 0416 0417 for ( int i = 0; i < m_innerNodesList.size(); ++i ) { 0418 for ( int j = 0; j < m_innerNodesList.at(i).size(); ++j ) { 0419 if ( m_innerNodesList.at(i).at(j).isSelected() ) { 0420 return true; 0421 } 0422 } 0423 } 0424 0425 return false; 0426 } 0427 0428 bool AreaAnnotation::clickedNodeIsSelected() const 0429 { 0430 const int i = m_clickedNodeIndexes.first; 0431 const int j = m_clickedNodeIndexes.second; 0432 0433 return ( i != -1 && j == -1 && m_outerNodesList.at(i).isSelected() ) || 0434 ( i != -1 && j != -1 && m_innerNodesList.at(i).at(j).isSelected() ); 0435 } 0436 0437 QPointer<MergingPolygonNodesAnimation> AreaAnnotation::animation() 0438 { 0439 return m_animation; 0440 } 0441 0442 bool AreaAnnotation::mousePressEvent( QMouseEvent *event ) 0443 { 0444 if ( !m_viewport || m_busy ) { 0445 return false; 0446 } 0447 0448 setRequest( SceneGraphicsItem::NoRequest ); 0449 0450 if ( state() == SceneGraphicsItem::Editing ) { 0451 return processEditingOnPress( event ); 0452 } else if ( state() == SceneGraphicsItem::AddingPolygonHole ) { 0453 return processAddingHoleOnPress( event ); 0454 } else if ( state() == SceneGraphicsItem::MergingNodes ) { 0455 return processMergingOnPress( event ); 0456 } else if ( state() == SceneGraphicsItem::AddingNodes ) { 0457 return processAddingNodesOnPress( event ); 0458 } 0459 0460 return false; 0461 } 0462 0463 bool AreaAnnotation::mouseMoveEvent( QMouseEvent *event ) 0464 { 0465 if ( !m_viewport || m_busy ) { 0466 return false; 0467 } 0468 0469 setRequest( SceneGraphicsItem::NoRequest ); 0470 0471 if ( state() == SceneGraphicsItem::Editing ) { 0472 return processEditingOnMove( event ); 0473 } else if ( state() == SceneGraphicsItem::AddingPolygonHole ) { 0474 return processAddingHoleOnMove( event ); 0475 } else if ( state() == SceneGraphicsItem::MergingNodes ) { 0476 return processMergingOnMove( event ); 0477 } else if ( state() == SceneGraphicsItem::AddingNodes ) { 0478 return processAddingNodesOnMove( event ); 0479 } 0480 0481 return false; 0482 } 0483 0484 bool AreaAnnotation::mouseReleaseEvent( QMouseEvent *event ) 0485 { 0486 if ( !m_viewport || m_busy ) { 0487 return false; 0488 } 0489 0490 setRequest( SceneGraphicsItem::NoRequest ); 0491 0492 if ( state() == SceneGraphicsItem::Editing ) { 0493 return processEditingOnRelease( event ); 0494 } else if ( state() == SceneGraphicsItem::AddingPolygonHole ) { 0495 return processAddingHoleOnRelease( event ); 0496 } else if ( state() == SceneGraphicsItem::MergingNodes ) { 0497 return processMergingOnRelease( event ); 0498 } else if ( state() == SceneGraphicsItem::AddingNodes ) { 0499 return processAddingNodesOnRelease( event ); 0500 } 0501 0502 return false; 0503 } 0504 0505 void AreaAnnotation::dealWithStateChange( SceneGraphicsItem::ActionState previousState ) 0506 { 0507 // Dealing with cases when exiting a state has an effect on this item. 0508 if ( previousState == SceneGraphicsItem::Editing ) { 0509 // Make sure that when changing the state, there is no highlighted node. 0510 if ( m_hoveredNode != QPair<int, int>( -1, -1 ) ) { 0511 const int i = m_hoveredNode.first; 0512 const int j = m_hoveredNode.second; 0513 0514 if ( j == -1 ) { 0515 m_outerNodesList[i].setFlag( PolylineNode::NodeIsEditingHighlighted, false ); 0516 } else { 0517 m_innerNodesList[i][j].setFlag( PolylineNode::NodeIsEditingHighlighted, false ); 0518 } 0519 } 0520 0521 m_clickedNodeIndexes = QPair<int, int>( -1, -1 ); 0522 m_hoveredNode = QPair<int, int>( -1, -1 ); 0523 } else if ( previousState == SceneGraphicsItem::AddingPolygonHole ) { 0524 // Check if a polygon hole was being drawn before changing state. 0525 GeoDataPolygon *polygon = static_cast<GeoDataPolygon*>( placemark()->geometry() ); 0526 QVector<GeoDataLinearRing> &innerBounds = polygon->innerBoundaries(); 0527 0528 if ( innerBounds.size() && innerBounds.last().size() <= 2 ) { 0529 // If only two nodes were added, remove this inner boundary entirely. 0530 innerBounds.remove( innerBounds.size() - 1 ); 0531 m_innerNodesList.removeLast(); 0532 return; 0533 } 0534 } else if ( previousState == SceneGraphicsItem::MergingNodes ) { 0535 // If there was only a node selected for being merged and the state changed, 0536 // deselect it. 0537 const int i = m_firstMergedNode.first; 0538 const int j = m_firstMergedNode.second; 0539 0540 if ( i != -1 && j != -1 ) { 0541 m_innerNodesList[i][j].setFlag( PolylineNode::NodeIsMerged, false ); 0542 } else if ( i != -1 && j == -1 ) { 0543 m_outerNodesList[i].setFlag( PolylineNode::NodeIsMerged, false ); 0544 } 0545 0546 // Make sure that when changing the state, there is no highlighted node. 0547 if ( m_hoveredNode != QPair<int, int>( -1, -1 ) ) { 0548 int i = m_hoveredNode.first; 0549 int j = m_hoveredNode.second; 0550 0551 if ( j == -1 ) { 0552 m_outerNodesList[i].setFlag( PolylineNode::NodeIsMergingHighlighted, false ); 0553 } else { 0554 m_innerNodesList[i][j].setFlag( PolylineNode::NodeIsMergingHighlighted, false ); 0555 } 0556 } 0557 0558 m_firstMergedNode = QPair<int, int>( -1, -1 ); 0559 m_hoveredNode = QPair<int, int>( -1, -1 ); 0560 delete m_animation; 0561 } else if ( previousState == SceneGraphicsItem::AddingNodes ) { 0562 m_outerVirtualNodes.clear(); 0563 m_innerVirtualNodes.clear(); 0564 m_virtualHovered = QPair<int, int>( -1, -1 ); 0565 m_adjustedNode = -2; 0566 } 0567 0568 // Dealing with cases when entering a state has an effect on this item, or 0569 // initializations are needed. 0570 if ( state() == SceneGraphicsItem::Editing ) { 0571 m_interactingObj = InteractingNothing; 0572 m_clickedNodeIndexes = QPair<int, int>( -1, -1 ); 0573 m_hoveredNode = QPair<int, int>( -1, -1 ); 0574 } else if ( state() == SceneGraphicsItem::AddingPolygonHole ) { 0575 // Nothing to do so far when entering this state. 0576 GeoDataPolygon *polygon = static_cast<GeoDataPolygon*>( placemark()->geometry() ); 0577 QVector<GeoDataLinearRing> &innerBounds = polygon->innerBoundaries(); 0578 0579 m_innerNodesList.append(QVector<PolylineNode>()); 0580 innerBounds.append( GeoDataLinearRing( Tessellate ) ); 0581 } else if ( state() == SceneGraphicsItem::MergingNodes ) { 0582 m_firstMergedNode = QPair<int, int>( -1, -1 ); 0583 m_secondMergedNode = QPair<int, int>( -1, -1 ); 0584 m_hoveredNode = QPair<int, int>( -1, -1 ); 0585 m_animation = nullptr; 0586 } else if ( state() == SceneGraphicsItem::AddingNodes ) { 0587 m_virtualHovered = QPair<int, int>( -1, -1 ); 0588 m_adjustedNode = -2; 0589 } 0590 } 0591 0592 const char *AreaAnnotation::graphicType() const 0593 { 0594 return SceneGraphicsTypes::SceneGraphicAreaAnnotation; 0595 } 0596 0597 bool AreaAnnotation::isValidPolygon() const 0598 { 0599 const GeoDataPolygon *poly = static_cast<const GeoDataPolygon*>( placemark()->geometry() ); 0600 const QVector<GeoDataLinearRing> &innerRings = poly->innerBoundaries(); 0601 0602 for ( const GeoDataLinearRing &innerRing: innerRings ) { 0603 for ( int i = 0; i < innerRing.size(); ++i ) { 0604 if ( !poly->outerBoundary().contains( innerRing.at(i) ) ) { 0605 return false; 0606 } 0607 } 0608 } 0609 0610 return true; 0611 } 0612 0613 void AreaAnnotation::setupRegionsLists( GeoPainter *painter ) 0614 { 0615 const GeoDataPolygon *polygon = static_cast<const GeoDataPolygon*>( placemark()->geometry() ); 0616 const GeoDataLinearRing &outerRing = polygon->outerBoundary(); 0617 const QVector<GeoDataLinearRing> &innerRings = polygon->innerBoundaries(); 0618 0619 // Add the outer boundary nodes. 0620 QVector<GeoDataCoordinates>::ConstIterator itBegin = outerRing.constBegin(); 0621 QVector<GeoDataCoordinates>::ConstIterator itEnd = outerRing.constEnd(); 0622 0623 m_outerNodesList.clear(); 0624 m_innerNodesList.clear(); 0625 m_boundariesList.clear(); 0626 0627 for ( ; itBegin != itEnd; ++itBegin ) { 0628 const PolylineNode newNode = PolylineNode( painter->regionFromEllipse( *itBegin, regularDim, regularDim ) ); 0629 m_outerNodesList.append( newNode ); 0630 } 0631 0632 for ( const GeoDataLinearRing &innerRing: innerRings ) { 0633 QVector<GeoDataCoordinates>::ConstIterator itBegin = innerRing.constBegin(); 0634 QVector<GeoDataCoordinates>::ConstIterator itEnd = innerRing.constEnd(); 0635 QVector<PolylineNode> innerNodes; 0636 innerNodes.reserve(innerRing.size()); 0637 0638 for ( ; itBegin != itEnd; ++itBegin ) { 0639 const PolylineNode newNode = PolylineNode( painter->regionFromEllipse( *itBegin, regularDim, regularDim ) ); 0640 innerNodes.append( newNode ); 0641 } 0642 m_innerNodesList.append( innerNodes ); 0643 } 0644 0645 // Add the outer boundary to the boundaries list. 0646 m_boundariesList.append( painter->regionFromPolygon( outerRing, Qt::OddEvenFill ) ); 0647 } 0648 0649 void AreaAnnotation::updateRegions( GeoPainter *painter ) 0650 { 0651 if ( m_busy ) { 0652 return; 0653 } 0654 0655 const GeoDataPolygon *polygon = static_cast<const GeoDataPolygon*>( placemark()->geometry() ); 0656 const GeoDataLinearRing &outerRing = polygon->outerBoundary(); 0657 const QVector<GeoDataLinearRing> &innerRings = polygon->innerBoundaries(); 0658 0659 0660 if ( state() == SceneGraphicsItem::AddingNodes ) { 0661 // Create and update virtual nodes lists when being in the AddingPolgonNodes state, to 0662 // avoid overhead in other states. 0663 m_outerVirtualNodes.clear(); 0664 const QRegion firstRegion( painter->regionFromEllipse( outerRing.first().interpolate( 0665 outerRing.last(), 0.5 ), hoveredDim, hoveredDim ) ); 0666 m_outerVirtualNodes.append( PolylineNode( firstRegion ) ); 0667 for ( int i = 0; i < outerRing.size() - 1; ++i ) { 0668 const QRegion newRegion( painter->regionFromEllipse( outerRing.at(i).interpolate( 0669 outerRing.at(i+1), 0.5 ), hoveredDim, hoveredDim ) ); 0670 m_outerVirtualNodes.append( PolylineNode( newRegion ) ); 0671 } 0672 0673 m_innerVirtualNodes.clear(); 0674 m_innerVirtualNodes.reserve(innerRings.size()); 0675 for ( int i = 0; i < innerRings.size(); ++i ) { 0676 m_innerVirtualNodes.append(QVector<PolylineNode>()); 0677 const QRegion firstRegion( painter->regionFromEllipse( innerRings.at(i).first().interpolate( 0678 innerRings.at(i).last(), 0.5 ), hoveredDim, hoveredDim ) ); 0679 m_innerVirtualNodes[i].append( PolylineNode( firstRegion ) ); 0680 for ( int j = 0; j < innerRings.at(i).size() - 1; ++j ) { 0681 const QRegion newRegion( painter->regionFromEllipse( innerRings.at(i).at(j).interpolate( 0682 innerRings.at(i).at(j+1), 0.5 ), hoveredDim, hoveredDim ) ); 0683 m_innerVirtualNodes[i].append( PolylineNode( newRegion ) ); 0684 } 0685 } 0686 } 0687 0688 // Update the boundaries list. 0689 m_boundariesList.clear(); 0690 m_boundariesList.reserve(1 + innerRings.size()); 0691 0692 m_boundariesList.append( painter->regionFromPolygon( outerRing, Qt::OddEvenFill ) ); 0693 for ( const GeoDataLinearRing &ring: innerRings ) { 0694 m_boundariesList.append( painter->regionFromPolygon( ring, Qt::OddEvenFill ) ); 0695 } 0696 0697 // Update the outer and inner nodes lists. 0698 Q_ASSERT( m_outerNodesList.size() == outerRing.size() ); 0699 for ( int i = 0; i < m_outerNodesList.size(); ++i ) { 0700 const QRegion newRegion = m_outerNodesList.at(i).isSelected() ? 0701 painter->regionFromEllipse( outerRing.at(i), selectedDim, selectedDim ) : 0702 painter->regionFromEllipse( outerRing.at(i), regularDim, regularDim ); 0703 m_outerNodesList[i].setRegion( newRegion ); 0704 } 0705 0706 Q_ASSERT( m_innerNodesList.size() == innerRings.size() ); 0707 for ( int i = 0; i < m_innerNodesList.size(); ++i ) { 0708 Q_ASSERT( m_innerNodesList.at(i).size() == innerRings.at(i).size() ); 0709 for ( int j = 0; j < m_innerNodesList.at(i).size(); ++j ) { 0710 const QRegion newRegion = m_innerNodesList.at(i).at(j).isSelected() ? 0711 painter->regionFromEllipse( innerRings.at(i).at(j), selectedDim, selectedDim ) : 0712 painter->regionFromEllipse( innerRings.at(i).at(j), regularDim, regularDim ); 0713 m_innerNodesList[i][j].setRegion( newRegion ); 0714 } 0715 } 0716 } 0717 0718 void AreaAnnotation::drawNodes( GeoPainter *painter ) 0719 { 0720 // These are the 'real' dimensions of the drawn nodes. The ones which have class scope are used 0721 // to generate the regions and they are a little bit larger, because, for example, it would be 0722 // a little bit too hard to select nodes. 0723 static const int d_regularDim = 10; 0724 static const int d_selectedDim = 10; 0725 static const int d_mergedDim = 20; 0726 static const int d_hoveredDim = 20; 0727 0728 const GeoDataPolygon *polygon = static_cast<const GeoDataPolygon*>( placemark()->geometry() ); 0729 const GeoDataLinearRing &outerRing = polygon->outerBoundary(); 0730 const QVector<GeoDataLinearRing> &innerRings = polygon->innerBoundaries(); 0731 0732 QColor glowColor = QApplication::palette().highlightedText().color(); 0733 glowColor.setAlpha(120); 0734 0735 auto const selectedColor = QApplication::palette().highlight().color(); 0736 auto const hoveredColor = selectedColor; 0737 for ( int i = 0; i < outerRing.size(); ++i ) { 0738 // The order here is important, because a merged node can be at the same time selected. 0739 if ( m_outerNodesList.at(i).isBeingMerged() ) { 0740 painter->setBrush( mergedColor ); 0741 painter->drawEllipse( outerRing.at(i), d_mergedDim, d_mergedDim ); 0742 } else if ( m_outerNodesList.at(i).isSelected() ) { 0743 painter->setBrush( selectedColor ); 0744 painter->drawEllipse( outerRing.at(i), d_selectedDim, d_selectedDim ); 0745 0746 if ( m_outerNodesList.at(i).isEditingHighlighted() || 0747 m_outerNodesList.at(i).isMergingHighlighted() ) { 0748 QPen defaultPen = painter->pen(); 0749 QPen newPen; 0750 newPen.setWidth( defaultPen.width() + 3 ); 0751 newPen.setColor( glowColor ); 0752 0753 painter->setBrush( Qt::NoBrush ); 0754 painter->setPen( newPen ); 0755 painter->drawEllipse( outerRing.at(i), d_selectedDim + 2, d_selectedDim + 2 ); 0756 painter->setPen( defaultPen ); 0757 } 0758 } else { 0759 painter->setBrush( regularColor ); 0760 painter->drawEllipse( outerRing.at(i), d_regularDim, d_regularDim ); 0761 0762 if ( m_outerNodesList.at(i).isEditingHighlighted() || 0763 m_outerNodesList.at(i).isMergingHighlighted() ) { 0764 QPen defaultPen = painter->pen(); 0765 QPen newPen; 0766 newPen.setWidth( defaultPen.width() + 3 ); 0767 newPen.setColor( glowColor ); 0768 0769 painter->setPen( newPen ); 0770 painter->setBrush( Qt::NoBrush ); 0771 painter->drawEllipse( outerRing.at(i), d_regularDim + 2, d_regularDim + 2 ); 0772 painter->setPen( defaultPen ); 0773 } 0774 } 0775 } 0776 0777 for ( int i = 0; i < innerRings.size(); ++i ) { 0778 for ( int j = 0; j < innerRings.at(i).size(); ++j ) { 0779 if ( m_innerNodesList.at(i).at(j).isBeingMerged() ) { 0780 painter->setBrush( mergedColor ); 0781 painter->drawEllipse( innerRings.at(i).at(j), d_mergedDim, d_mergedDim ); 0782 } else if ( m_innerNodesList.at(i).at(j).isSelected() ) { 0783 painter->setBrush( selectedColor ); 0784 painter->drawEllipse( innerRings.at(i).at(j), d_selectedDim, d_selectedDim ); 0785 0786 if ( m_innerNodesList.at(i).at(j).isEditingHighlighted() || 0787 m_innerNodesList.at(i).at(j).isMergingHighlighted() ) { 0788 QPen defaultPen = painter->pen(); 0789 QPen newPen; 0790 newPen.setWidth( defaultPen.width() + 3 ); 0791 newPen.setColor( glowColor ); 0792 0793 painter->setBrush( Qt::NoBrush ); 0794 painter->setPen( newPen ); 0795 painter->drawEllipse( innerRings.at(i).at(j), d_selectedDim + 2, d_selectedDim + 2 ); 0796 painter->setPen( defaultPen ); 0797 } 0798 } else { 0799 painter->setBrush( regularColor ); 0800 painter->drawEllipse( innerRings.at(i).at(j), d_regularDim, d_regularDim ); 0801 0802 if ( m_innerNodesList.at(i).at(j).isEditingHighlighted() || 0803 m_innerNodesList.at(i).at(j).isMergingHighlighted() ) { 0804 QPen defaultPen = painter->pen(); 0805 QPen newPen; 0806 newPen.setWidth( defaultPen.width() + 3 ); 0807 newPen.setColor( glowColor ); 0808 0809 painter->setBrush( Qt::NoBrush ); 0810 painter->setPen( newPen ); 0811 painter->drawEllipse( innerRings.at(i).at(j), d_regularDim + 2, d_regularDim + 2 ); 0812 painter->setPen( defaultPen ); 0813 } 0814 } 0815 } 0816 } 0817 0818 if ( m_virtualHovered != QPair<int, int>( -1, -1 ) ) { 0819 const int i = m_virtualHovered.first; 0820 const int j = m_virtualHovered.second; 0821 0822 painter->setBrush( hoveredColor ); 0823 0824 if ( i != -1 && j == -1 ) { 0825 const GeoDataCoordinates coords = i ? 0826 outerRing.at(i).interpolate( outerRing.at(i - 1), 0.5 ) : 0827 outerRing.first().interpolate( outerRing.last(), 0.5 ); 0828 painter->drawEllipse( coords, d_hoveredDim, d_hoveredDim ); 0829 } else { 0830 Q_ASSERT( i != -1 && j != -1 ); 0831 0832 const GeoDataCoordinates coords = j ? 0833 innerRings.at(i).at(j).interpolate( innerRings.at(i).at(j - 1), 0.5 ) : 0834 innerRings.at(i).first().interpolate( innerRings.at(i).last(), 0.5 ); 0835 painter->drawEllipse( coords, d_hoveredDim, d_hoveredDim ); 0836 } 0837 } 0838 } 0839 0840 int AreaAnnotation::outerNodeContains( const QPoint &point ) const 0841 { 0842 if ( !hasFocus() ) { 0843 return -1; 0844 } 0845 0846 for ( int i = 0; i < m_outerNodesList.size(); ++i ) { 0847 if ( m_outerNodesList.at(i).containsPoint( point ) ) { 0848 return i; 0849 } 0850 } 0851 0852 return -1; 0853 } 0854 0855 QPair<int, int> AreaAnnotation::innerNodeContains( const QPoint &point ) const 0856 { 0857 if ( !hasFocus() ) { 0858 return QPair<int, int>( -1, -1 ); 0859 } 0860 0861 for ( int i = 0; i < m_innerNodesList.size(); ++i ) { 0862 for ( int j = 0; j < m_innerNodesList.at(i).size(); ++j ) { 0863 if ( m_innerNodesList.at(i).at(j).containsPoint( point ) ) { 0864 return QPair<int, int>( i, j ); 0865 } 0866 } 0867 } 0868 0869 return QPair<int, int>( -1, -1 ); 0870 } 0871 0872 QPair<int, int> AreaAnnotation::virtualNodeContains( const QPoint &point ) const 0873 { 0874 if ( !hasFocus() ) { 0875 return QPair<int, int>( -1, -1 ); 0876 } 0877 0878 for ( int i = 0; i < m_outerVirtualNodes.size(); ++i ) { 0879 if ( m_outerVirtualNodes.at(i).containsPoint( point ) ) { 0880 return QPair<int, int>( i, -1 ); 0881 } 0882 } 0883 0884 for ( int i = 0; i < m_innerVirtualNodes.size(); ++i ) { 0885 for ( int j = 0; j < m_innerVirtualNodes.at(i).size(); ++j ) { 0886 if ( m_innerVirtualNodes.at(i).at(j).containsPoint( point ) ) { 0887 return QPair<int, int>( i, j ); 0888 } 0889 } 0890 } 0891 0892 return QPair<int, int>( -1, -1 ); 0893 } 0894 0895 int AreaAnnotation::innerBoundsContain( const QPoint &point ) const 0896 { 0897 // There are no inner boundaries. 0898 if ( m_boundariesList.size() == 1 ) { 0899 return -1; 0900 } 0901 0902 // Starting from 1 because on index 0 is stored the region representing the whole polygon. 0903 for ( int i = 1; i < m_boundariesList.size(); ++i ) { 0904 if ( m_boundariesList.at(i).contains( point ) ) { 0905 return i; 0906 } 0907 } 0908 0909 return -1; 0910 } 0911 0912 bool AreaAnnotation::polygonContains( const QPoint &point ) const 0913 { 0914 return m_boundariesList.first().contains( point ); 0915 } 0916 0917 bool AreaAnnotation::processEditingOnPress( QMouseEvent *mouseEvent ) 0918 { 0919 if ( mouseEvent->button() != Qt::LeftButton && mouseEvent->button() != Qt::RightButton ) { 0920 return false; 0921 } 0922 0923 qreal lat, lon; 0924 m_viewport->geoCoordinates( mouseEvent->pos().x(), 0925 mouseEvent->pos().y(), 0926 lon, lat, 0927 GeoDataCoordinates::Radian ); 0928 m_movedPointCoords.set( lon, lat ); 0929 0930 // First check if one of the nodes from outer boundary has been clicked. 0931 const int outerIndex = outerNodeContains( mouseEvent->pos() ); 0932 if ( outerIndex != -1 ) { 0933 m_clickedNodeIndexes = QPair<int, int>( outerIndex, -1 ); 0934 0935 if ( mouseEvent->button() == Qt::RightButton ) { 0936 setRequest( SceneGraphicsItem::ShowNodeRmbMenu ); 0937 } else { 0938 m_interactingObj = InteractingNode; 0939 } 0940 0941 return true; 0942 } 0943 0944 // Then check if one of the nodes which form an inner boundary has been clicked. 0945 const QPair<int, int> innerIndexes = innerNodeContains( mouseEvent->pos() ); 0946 if ( innerIndexes.first != -1 && innerIndexes.second != -1 ) { 0947 m_clickedNodeIndexes = innerIndexes; 0948 0949 if ( mouseEvent->button() == Qt::RightButton ) { 0950 setRequest( SceneGraphicsItem::ShowNodeRmbMenu ); 0951 } else { 0952 m_interactingObj = InteractingNode; 0953 } 0954 return true; 0955 } 0956 0957 // If neither outer boundary nodes nor inner boundary nodes contain the event position, 0958 // then check if the interior of the polygon (excepting its 'holes') contains this point. 0959 if ( polygonContains( mouseEvent->pos() ) && 0960 innerBoundsContain( mouseEvent->pos() ) == -1 ) { 0961 if ( mouseEvent->button() == Qt::RightButton ) { 0962 setRequest( SceneGraphicsItem::ShowPolygonRmbMenu ); 0963 } else { 0964 m_interactingObj = InteractingPolygon; 0965 } 0966 return true; 0967 } 0968 0969 return false; 0970 } 0971 0972 bool AreaAnnotation::processEditingOnMove( QMouseEvent *mouseEvent ) 0973 { 0974 if ( !m_viewport ) { 0975 return false; 0976 } 0977 0978 qreal lon, lat; 0979 m_viewport->geoCoordinates( mouseEvent->pos().x(), 0980 mouseEvent->pos().y(), 0981 lon, lat, 0982 GeoDataCoordinates::Radian ); 0983 const GeoDataCoordinates newCoords( lon, lat ); 0984 0985 const qreal deltaLat = lat - m_movedPointCoords.latitude(); 0986 const qreal deltaLon = lon - m_movedPointCoords.longitude(); 0987 0988 if ( m_interactingObj == InteractingNode ) { 0989 GeoDataPolygon *polygon = static_cast<GeoDataPolygon*>( placemark()->geometry() ); 0990 GeoDataLinearRing &outerRing = polygon->outerBoundary(); 0991 QVector<GeoDataLinearRing> &innerRings = polygon->innerBoundaries(); 0992 OsmPlacemarkData *osmData = nullptr; 0993 if ( placemark()->hasOsmData() ) { 0994 osmData = &placemark()->osmData(); 0995 } 0996 0997 const int i = m_clickedNodeIndexes.first; 0998 const int j = m_clickedNodeIndexes.second; 0999 1000 if ( j == -1 ) { 1001 // Keeping the osmPlacemarkData synchronized with the geometry 1002 if ( osmData ) { 1003 osmData->memberReference( -1 ).changeNodeReference( outerRing.at( i ), newCoords ); 1004 } 1005 outerRing[i] = newCoords; 1006 } else { 1007 Q_ASSERT( i != -1 && j != -1 ); 1008 if ( osmData ) { 1009 osmData->memberReference( i ).changeNodeReference( innerRings.at( i ).at( j ), newCoords ); 1010 } 1011 innerRings[i].at(j) = newCoords; 1012 } 1013 1014 return true; 1015 } else if ( m_interactingObj == InteractingPolygon ) { 1016 GeoDataPolygon *polygon = static_cast<GeoDataPolygon*>( placemark()->geometry() ); 1017 GeoDataLinearRing outerRing = polygon->outerBoundary(); 1018 QVector<GeoDataLinearRing> innerRings = polygon->innerBoundaries(); 1019 OsmPlacemarkData *osmData = nullptr; 1020 if ( placemark()->hasOsmData() ) { 1021 osmData = &placemark()->osmData(); 1022 } 1023 1024 Quaternion latRectAxis = Quaternion::fromEuler( 0, lon, 0); 1025 Quaternion latAxis = Quaternion::fromEuler( -deltaLat, 0, 0); 1026 Quaternion lonAxis = Quaternion::fromEuler(0, deltaLon, 0); 1027 Quaternion rotAxis = latRectAxis * latAxis * latRectAxis.inverse() * lonAxis; 1028 1029 1030 polygon->outerBoundary().clear(); 1031 polygon->innerBoundaries().clear(); 1032 1033 for ( int i = 0; i < outerRing.size(); ++i ) { 1034 const GeoDataCoordinates movedPoint = outerRing.at(i).rotateAround(rotAxis); 1035 if ( osmData ) { 1036 osmData->memberReference( -1 ).changeNodeReference( outerRing.at( i ), movedPoint ); 1037 } 1038 polygon->outerBoundary().append( movedPoint ); 1039 } 1040 1041 for ( int i = 0; i < innerRings.size(); ++i ) { 1042 GeoDataLinearRing newRing( Tessellate ); 1043 for ( int j = 0; j < innerRings.at(i).size(); ++j ) { 1044 const GeoDataCoordinates movedPoint = innerRings.at(i).at(j).rotateAround(rotAxis); 1045 if ( osmData ) { 1046 osmData->memberReference( i ).changeNodeReference( innerRings.at( i ).at( j ) , movedPoint ); 1047 } 1048 newRing.append( movedPoint ); 1049 } 1050 polygon->innerBoundaries().append( newRing ); 1051 } 1052 1053 m_movedPointCoords = newCoords; 1054 return true; 1055 } else if ( m_interactingObj == InteractingNothing ) { 1056 return dealWithHovering( mouseEvent ); 1057 } 1058 1059 return false; 1060 } 1061 1062 bool AreaAnnotation::processEditingOnRelease( QMouseEvent *mouseEvent ) 1063 { 1064 static const int mouseMoveOffset = 1; 1065 1066 if ( mouseEvent->button() != Qt::LeftButton ) { 1067 return false; 1068 } 1069 1070 if ( m_interactingObj == InteractingNode ) { 1071 qreal x, y; 1072 m_viewport->screenCoordinates( m_movedPointCoords.longitude(), 1073 m_movedPointCoords.latitude(), 1074 x, y ); 1075 // The node gets selected only if it is clicked and not moved. 1076 if ( qFabs(mouseEvent->pos().x() - x) > mouseMoveOffset || 1077 qFabs(mouseEvent->pos().y() - y) > mouseMoveOffset ) { 1078 m_interactingObj = InteractingNothing; 1079 return true; 1080 } 1081 1082 const int i = m_clickedNodeIndexes.first; 1083 const int j = m_clickedNodeIndexes.second; 1084 1085 if ( j == -1 ) { 1086 m_outerNodesList[i].setFlag( PolylineNode::NodeIsSelected, 1087 !m_outerNodesList[i].isSelected() ); 1088 } else { 1089 m_innerNodesList[i][j].setFlag ( PolylineNode::NodeIsSelected, 1090 !m_innerNodesList.at(i).at(j).isSelected() ); 1091 } 1092 1093 m_interactingObj = InteractingNothing; 1094 return true; 1095 } else if ( m_interactingObj == InteractingPolygon ) { 1096 // Nothing special happens at polygon release. 1097 m_interactingObj = InteractingNothing; 1098 return true; 1099 } 1100 1101 return false; 1102 } 1103 1104 bool AreaAnnotation::processAddingHoleOnPress( QMouseEvent *mouseEvent ) 1105 { 1106 if ( mouseEvent->button() != Qt::LeftButton ) { 1107 return false; 1108 } 1109 1110 qreal lon, lat; 1111 m_viewport->geoCoordinates( mouseEvent->pos().x(), 1112 mouseEvent->pos().y(), 1113 lon, lat, 1114 GeoDataCoordinates::Radian ); 1115 const GeoDataCoordinates newCoords( lon, lat ); 1116 1117 GeoDataPolygon *polygon = static_cast<GeoDataPolygon*>( placemark()->geometry() ); 1118 QVector<GeoDataLinearRing> &innerBounds = polygon->innerBoundaries(); 1119 1120 innerBounds.last().append( newCoords ); 1121 m_innerNodesList.last().append( PolylineNode() ); 1122 1123 return true; 1124 } 1125 1126 bool AreaAnnotation::processAddingHoleOnMove( QMouseEvent *mouseEvent ) 1127 { 1128 Q_UNUSED( mouseEvent ); 1129 return true; 1130 } 1131 1132 bool AreaAnnotation::processAddingHoleOnRelease( QMouseEvent *mouseEvent ) 1133 { 1134 Q_UNUSED( mouseEvent ); 1135 return true; 1136 } 1137 1138 bool AreaAnnotation::processMergingOnPress( QMouseEvent *mouseEvent ) 1139 { 1140 if ( mouseEvent->button() != Qt::LeftButton ) { 1141 return false; 1142 } 1143 1144 GeoDataPolygon *polygon = static_cast<GeoDataPolygon*>( placemark()->geometry() ); 1145 GeoDataLinearRing initialOuterRing = polygon->outerBoundary(); 1146 OsmPlacemarkData *osmData = nullptr; 1147 OsmPlacemarkData initialOsmData; 1148 if ( placemark()->hasOsmData() ) { 1149 osmData = &placemark()->osmData(); 1150 } 1151 1152 GeoDataLinearRing &outerRing = polygon->outerBoundary(); 1153 QVector<GeoDataLinearRing> &innerRings = polygon->innerBoundaries(); 1154 1155 const int outerIndex = outerNodeContains( mouseEvent->pos() ); 1156 // If the selected node is an outer boundary node. 1157 if ( outerIndex != -1 ) { 1158 // If this is the first node selected to be merged. 1159 if ( m_firstMergedNode.first == -1 && m_firstMergedNode.second == -1 ) { 1160 m_firstMergedNode = QPair<int, int>( outerIndex, -1 ); 1161 m_outerNodesList[outerIndex].setFlag( PolylineNode::NodeIsMerged ); 1162 // If the first selected node was an inner boundary node, raise the request for showing 1163 // warning. 1164 } else if ( m_firstMergedNode.first != -1 && m_firstMergedNode.second != -1 ) { 1165 setRequest( SceneGraphicsItem::OuterInnerMergingWarning ); 1166 m_innerNodesList[m_firstMergedNode.first][m_firstMergedNode.second].setFlag( 1167 PolylineNode::NodeIsMerged, false ); 1168 1169 if ( m_hoveredNode.first != -1 ) { 1170 // We can be sure that the hovered node is an outer node. 1171 Q_ASSERT( m_hoveredNode.second == -1 ); 1172 m_outerNodesList[m_hoveredNode.first].setFlag( PolylineNode::NodeIsMergingHighlighted, false ); 1173 } 1174 1175 m_hoveredNode = m_firstMergedNode = QPair<int, int>( -1, -1 ); 1176 } else { 1177 Q_ASSERT( m_firstMergedNode.first != -1 && m_firstMergedNode.second == -1 ); 1178 1179 // Clicking two times the same node results in unmarking it for merging. 1180 if ( m_firstMergedNode.first == outerIndex ) { 1181 m_outerNodesList[outerIndex].setFlag( PolylineNode::NodeIsMerged, false ); 1182 m_firstMergedNode = QPair<int, int>( -1, -1 ); 1183 return true; 1184 } 1185 1186 // If two nodes which form a triangle are merged, the whole triangle should be 1187 // destroyed. 1188 if ( outerRing.size() <= 3 ) { 1189 setRequest( SceneGraphicsItem::RemovePolygonRequest ); 1190 return true; 1191 } 1192 GeoDataCoordinates mergedNode = outerRing.at(m_firstMergedNode.first).interpolate( outerRing.at(outerIndex), 1193 0.5 ); 1194 // Keeping the osm data synchronized with the geometry 1195 if ( osmData ) { 1196 osmData->memberReference( -1 ).changeNodeReference( outerRing.at( outerIndex ), mergedNode ); 1197 osmData->memberReference( -1 ).removeNodeReference( outerRing.at( m_firstMergedNode.first ) ); 1198 } 1199 1200 outerRing[outerIndex] = mergedNode; 1201 outerRing.remove( m_firstMergedNode.first ); 1202 if ( !isValidPolygon() ) { 1203 if ( osmData ) { 1204 placemark()->setOsmData( initialOsmData ); 1205 } 1206 polygon->outerBoundary() = initialOuterRing; 1207 m_outerNodesList[m_firstMergedNode.first].setFlag( PolylineNode::NodeIsMerged, false ); 1208 1209 // Remove highlight effect before showing warning 1210 if ( m_hoveredNode.first != -1 ) { 1211 m_outerNodesList[m_hoveredNode.first].setFlag( PolylineNode::NodeIsMergingHighlighted, false ); 1212 } 1213 1214 m_hoveredNode = m_firstMergedNode = QPair<int, int>( -1, -1 ); 1215 setRequest( SceneGraphicsItem::InvalidShapeWarning ); 1216 return true; 1217 } 1218 1219 // Do not modify it here. The animation has access to the object. It will modify the polygon. 1220 polygon->outerBoundary() = initialOuterRing; 1221 1222 m_outerNodesList[outerIndex].setFlag( PolylineNode::NodeIsMerged ); 1223 m_secondMergedNode = QPair<int, int>( outerIndex, -1 ); 1224 1225 delete m_animation; 1226 m_animation = new MergingPolygonNodesAnimation( this ); 1227 setRequest( SceneGraphicsItem::StartPolygonAnimation ); 1228 } 1229 1230 return true; 1231 } 1232 1233 // If the selected node is an inner boundary node. 1234 const QPair<int, int> innerIndexes = innerNodeContains( mouseEvent->pos() ); 1235 if ( innerIndexes.first != -1 && innerIndexes.second != -1 ) { 1236 const int i = m_firstMergedNode.first; 1237 const int j = m_firstMergedNode.second; 1238 1239 // If this is the first selected node. 1240 if ( i == -1 && j == -1 ) { 1241 m_firstMergedNode = innerIndexes; 1242 m_innerNodesList[innerIndexes.first][innerIndexes.second].setFlag( PolylineNode::NodeIsMerged ); 1243 // If the first selected node has been an outer boundary one, raise the request for showing warning. 1244 } else if ( i != -1 && j == -1 ) { 1245 setRequest( SceneGraphicsItem::OuterInnerMergingWarning ); 1246 m_outerNodesList[i].setFlag( PolylineNode::NodeIsMerged, false ); 1247 1248 if ( m_hoveredNode.first != -1 ) { 1249 // We can now be sure that the highlighted node is a node from polygon's outer boundary 1250 Q_ASSERT( m_hoveredNode.second != -1 ); 1251 m_outerNodesList[m_hoveredNode.first].setFlag( PolylineNode::NodeIsMergingHighlighted, false ); 1252 } 1253 m_firstMergedNode = QPair<int, int>( -1, -1 ); 1254 } else { 1255 Q_ASSERT( i != -1 && j != -1 ); 1256 if ( i != innerIndexes.first ) { 1257 setRequest( SceneGraphicsItem::InnerInnerMergingWarning ); 1258 m_innerNodesList[i][j].setFlag( PolylineNode::NodeIsMerged, false ); 1259 1260 if ( m_hoveredNode.first != -1 && m_hoveredNode.second != -1 ) { 1261 m_innerNodesList[m_hoveredNode.first][m_hoveredNode.second].setFlag( 1262 PolylineNode::NodeIsMergingHighlighted, false ); 1263 } 1264 1265 m_hoveredNode = m_firstMergedNode = QPair<int, int>( -1, -1 ); 1266 return true; 1267 } 1268 1269 // Clicking two times the same node results in unmarking it for merging. 1270 if ( m_firstMergedNode == innerIndexes ) { 1271 m_innerNodesList[i][j].setFlag( PolylineNode::NodeIsMerged, false ); 1272 m_firstMergedNode = QPair<int, int>( -1, -1 ); 1273 return true; 1274 } 1275 1276 // If two nodes which form an inner boundary of a polygon with a size smaller than 1277 // 3 are merged, remove the whole inner boundary. 1278 if ( innerRings.at(i).size() <= 3 ) { 1279 innerRings.remove( i ); 1280 m_innerNodesList.removeAt( i ); 1281 1282 m_firstMergedNode = m_secondMergedNode = m_hoveredNode = QPair<int, int>( -1, -1 ); 1283 return true; 1284 } 1285 1286 m_innerNodesList[innerIndexes.first][innerIndexes.second].setFlag( PolylineNode::NodeIsMerged ); 1287 m_secondMergedNode = innerIndexes; 1288 1289 m_animation = new MergingPolygonNodesAnimation( this ); 1290 setRequest( SceneGraphicsItem::StartPolygonAnimation ); 1291 } 1292 1293 return true; 1294 } 1295 1296 return false; 1297 } 1298 1299 bool AreaAnnotation::processMergingOnMove( QMouseEvent *mouseEvent ) 1300 { 1301 return dealWithHovering( mouseEvent ); 1302 } 1303 1304 bool AreaAnnotation::processMergingOnRelease( QMouseEvent *mouseEvent ) 1305 { 1306 Q_UNUSED( mouseEvent ); 1307 return true; 1308 } 1309 1310 bool AreaAnnotation::processAddingNodesOnPress( QMouseEvent *mouseEvent ) 1311 { 1312 if ( mouseEvent->button() != Qt::LeftButton ) { 1313 return false; 1314 } 1315 1316 GeoDataPolygon *polygon = static_cast<GeoDataPolygon*>( placemark()->geometry() ); 1317 GeoDataLinearRing &outerRing = polygon->outerBoundary(); 1318 QVector<GeoDataLinearRing> &innerRings = polygon->innerBoundaries(); 1319 1320 // If a virtual node has just been clicked, add it to the polygon's outer boundary 1321 // and start 'adjusting' its position. 1322 const QPair<int, int> index = virtualNodeContains( mouseEvent->pos() ); 1323 if ( index != QPair<int, int>( -1, -1 ) && m_adjustedNode == -2 ) { 1324 Q_ASSERT( m_virtualHovered == index ); 1325 const int i = index.first; 1326 const int j = index.second; 1327 1328 if ( i != -1 && j == -1 ) { 1329 GeoDataLinearRing newRing( Tessellate ); 1330 QVector<PolylineNode> newList; 1331 newList.reserve(outerRing.size()); 1332 for ( int k = i; k < i + outerRing.size(); ++k ) { 1333 newRing.append( outerRing.at(k % outerRing.size()) ); 1334 1335 PolylineNode newNode; 1336 newNode.setFlags( m_outerNodesList.at(k % outerRing.size()).flags() ); 1337 newList.append( newNode ); 1338 } 1339 GeoDataCoordinates newCoords = newRing.first().interpolate( newRing.last(), 0.5 ); 1340 newRing.append( newCoords ); 1341 1342 m_outerNodesList = newList; 1343 m_outerNodesList.append( PolylineNode( QRegion() ) ); 1344 1345 polygon->outerBoundary() = newRing; 1346 m_adjustedNode = -1; 1347 } else { 1348 Q_ASSERT( i != -1 && j != -1 ); 1349 1350 GeoDataLinearRing newRing( Tessellate ); 1351 QVector<PolylineNode> newList; 1352 newList.reserve(innerRings.at(i).size()); 1353 for ( int k = j; k < j + innerRings.at(i).size(); ++k ) { 1354 newRing.append( innerRings.at(i).at(k % innerRings.at(i).size()) ); 1355 1356 PolylineNode newNode; 1357 newNode.setFlags( m_innerNodesList.at(i).at(k % innerRings.at(i).size()).flags() ); 1358 newList.append( newNode ); 1359 } 1360 GeoDataCoordinates newCoords = newRing.first().interpolate( newRing.last(), 0.5 ); 1361 newRing.append( newCoords ); 1362 1363 m_innerNodesList[i] = newList; 1364 m_innerNodesList[i].append( PolylineNode( QRegion() ) ); 1365 1366 polygon->innerBoundaries()[i] = newRing; 1367 m_adjustedNode = i; 1368 } 1369 1370 m_virtualHovered = QPair<int, int>( -1, -1 ); 1371 return true; 1372 } 1373 1374 // If a virtual node which has been previously clicked and selected to become a 1375 // 'real node' is clicked one more time, it stops from being 'adjusted'. 1376 const int outerIndex = outerNodeContains( mouseEvent->pos() ); 1377 if ( outerIndex != -1 && m_adjustedNode != -2 ) { 1378 m_adjustedNode = -2; 1379 return true; 1380 } 1381 1382 const QPair<int,int> innerIndex = innerNodeContains( mouseEvent->pos() ); 1383 if ( innerIndex != QPair<int, int>( -1, -1 ) && m_adjustedNode != -2 ) { 1384 m_adjustedNode = -2; 1385 return true; 1386 } 1387 1388 return false; 1389 } 1390 1391 bool AreaAnnotation::processAddingNodesOnMove( QMouseEvent *mouseEvent ) 1392 { 1393 Q_ASSERT( mouseEvent->button() == Qt::NoButton ); 1394 1395 const QPair<int, int> index = virtualNodeContains( mouseEvent->pos() ); 1396 // If we are adjusting a virtual node which has just been clicked and became real, just 1397 // change its coordinates when moving it, as we do with nodes in Editing state on move. 1398 if ( m_adjustedNode != -2 ) { 1399 // The virtual node which has just been added is always the last within 1400 // GeoDataLinearRing's container.qreal lon, lat; 1401 qreal lon, lat; 1402 m_viewport->geoCoordinates( mouseEvent->pos().x(), 1403 mouseEvent->pos().y(), 1404 lon, lat, 1405 GeoDataCoordinates::Radian ); 1406 const GeoDataCoordinates newCoords( lon, lat ); 1407 GeoDataPolygon *polygon = static_cast<GeoDataPolygon*>( placemark()->geometry() ); 1408 1409 if ( m_adjustedNode == -1 ) { 1410 polygon->outerBoundary().last() = newCoords; 1411 } else { 1412 Q_ASSERT( m_adjustedNode >= 0 ); 1413 polygon->innerBoundaries()[m_adjustedNode].last() = newCoords; 1414 } 1415 1416 return true; 1417 1418 // If we are hovering a virtual node, store its index in order to be painted in drawNodes 1419 // method. 1420 } else if ( index != QPair<int, int>( -1, -1 ) ) { 1421 m_virtualHovered = index; 1422 return true; 1423 } 1424 1425 // This means that the interior of the polygon has been hovered. Let the event propagate 1426 // since there may be overlapping polygons. 1427 return false; 1428 } 1429 1430 bool AreaAnnotation::processAddingNodesOnRelease( QMouseEvent *mouseEvent ) 1431 { 1432 Q_UNUSED( mouseEvent ); 1433 return m_adjustedNode == -2; 1434 } 1435 1436 bool AreaAnnotation::dealWithHovering( QMouseEvent *mouseEvent ) 1437 { 1438 const PolylineNode::PolyNodeFlag flag = state() == SceneGraphicsItem::Editing ? 1439 PolylineNode::NodeIsEditingHighlighted : 1440 PolylineNode::NodeIsMergingHighlighted; 1441 1442 const int outerIndex = outerNodeContains( mouseEvent->pos() ); 1443 if ( outerIndex != -1 ) { 1444 if ( !m_outerNodesList.at(outerIndex).isEditingHighlighted() && 1445 !m_outerNodesList.at(outerIndex).isMergingHighlighted() ) { 1446 // Deal with the case when two nodes are very close to each other. 1447 if ( m_hoveredNode != QPair<int, int>( -1, -1 ) ) { 1448 const int i = m_hoveredNode.first; 1449 const int j = m_hoveredNode.second; 1450 1451 if ( j == -1 ) { 1452 m_outerNodesList[i].setFlag( flag, false ); 1453 } else { 1454 m_innerNodesList[i][j].setFlag( flag, false ); 1455 } 1456 } 1457 1458 m_hoveredNode = QPair<int, int>( outerIndex, -1 ); 1459 m_outerNodesList[outerIndex].setFlag( flag ); 1460 setRequest( ChangeCursorPolygonNodeHover ); 1461 } 1462 1463 return true; 1464 } else if ( m_hoveredNode != QPair<int, int>( -1, -1 ) && m_hoveredNode.second == -1 ) { 1465 m_outerNodesList[m_hoveredNode.first].setFlag( flag, false ); 1466 m_hoveredNode = QPair<int, int>( -1, -1 ); 1467 1468 return true; 1469 } 1470 1471 const QPair<int, int> innerIndex = innerNodeContains( mouseEvent->pos() ); 1472 if ( innerIndex != QPair<int, int>( -1, -1 ) ) { 1473 if ( !m_innerNodesList.at(innerIndex.first).at(innerIndex.second).isEditingHighlighted() && 1474 !m_innerNodesList.at(innerIndex.first).at(innerIndex.second).isMergingHighlighted()) { 1475 // Deal with the case when two nodes are very close to each other. 1476 if ( m_hoveredNode != QPair<int, int>( -1, -1 ) ) { 1477 const int i = m_hoveredNode.first; 1478 const int j = m_hoveredNode.second; 1479 1480 if ( j == -1 ) { 1481 m_outerNodesList[i].setFlag( flag, false ); 1482 } else { 1483 m_innerNodesList[i][j].setFlag( flag, false ); 1484 } 1485 } 1486 1487 m_hoveredNode = innerIndex; 1488 m_innerNodesList[innerIndex.first][innerIndex.second].setFlag( flag ); 1489 setRequest( ChangeCursorPolygonNodeHover ); 1490 } 1491 1492 return true; 1493 } else if ( m_hoveredNode != QPair<int, int>( -1, -1 ) && m_hoveredNode.second != -1 ) { 1494 m_innerNodesList[m_hoveredNode.first][m_hoveredNode.second].setFlag( flag, false ); 1495 m_hoveredNode = QPair<int, int>( -1, -1 ); 1496 1497 return true; 1498 } 1499 1500 // This means that the interior of the polygon has been covered so we catch this event too. 1501 setRequest( ChangeCursorPolygonBodyHover ); 1502 return true; 1503 } 1504 1505 }