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

0001 // SPDX-License-Identifier: LGPL-2.1-or-later
0002 //
0003 // SPDX-FileCopyrightText: 2013 Adrian Draghici <draghici.adrian.b@gmail.com>
0004 //
0005 
0006 // Self
0007 #include "GroundOverlayFrame.h"
0008 
0009 // Marble
0010 #include "GeoDataPlacemark.h"
0011 #include "GeoDataGroundOverlay.h"
0012 #include "GeoDataLinearRing.h"
0013 #include "GeoDataPolygon.h"
0014 #include "GeoPainter.h"
0015 #include "ViewportParams.h"
0016 #include "SceneGraphicsTypes.h"
0017 #include "TextureLayer.h"
0018 #include "MarbleDirs.h"
0019 
0020 // Qt
0021 #include <qmath.h>
0022 
0023 
0024 namespace Marble
0025 {
0026 
0027 GroundOverlayFrame::GroundOverlayFrame( GeoDataPlacemark *placemark,
0028                                         GeoDataGroundOverlay *overlay,
0029                                         TextureLayer *textureLayer ) :
0030     SceneGraphicsItem( placemark ),
0031     m_overlay( overlay ),
0032     m_textureLayer( textureLayer ),
0033     m_movedHandle( NoRegion ),
0034     m_hoveredHandle( NoRegion ),
0035     m_editStatus( Resize ),
0036     m_editStatusChangeNeeded( false ),
0037     m_previousRotation( 0.0 ),
0038     m_viewport( nullptr )
0039 {
0040     m_resizeIcons.reserve(16);
0041     // NorthWest
0042     m_resizeIcons.append(QImage(MarbleDirs::systemPath() + QLatin1String("/bitmaps/editarrows/arrow-diagonal-topleft.png")));
0043     m_resizeIcons.append(QImage(MarbleDirs::systemPath() + QLatin1String("/bitmaps/editarrows/arrow-diagonal-topleft-active.png")));
0044     // SouthWest
0045     m_resizeIcons.append(QImage(MarbleDirs::systemPath() + QLatin1String("/bitmaps/editarrows/arrow-diagonal-topright.png")));
0046     m_resizeIcons.append(QImage(MarbleDirs::systemPath() + QLatin1String("/bitmaps/editarrows/arrow-diagonal-topright-active.png")));
0047     // SouthEast
0048     m_resizeIcons.append(QImage(MarbleDirs::systemPath() + QLatin1String("/bitmaps/editarrows/arrow-diagonal-topleft.png")));
0049     m_resizeIcons.append(QImage(MarbleDirs::systemPath() + QLatin1String("/bitmaps/editarrows/arrow-diagonal-topleft-active.png")));
0050     // NorthEast
0051     m_resizeIcons.append(QImage(MarbleDirs::systemPath() + QLatin1String("/bitmaps/editarrows/arrow-diagonal-topright.png")));
0052     m_resizeIcons.append(QImage(MarbleDirs::systemPath() + QLatin1String("/bitmaps/editarrows/arrow-diagonal-topright-active.png")));
0053     // North
0054     m_resizeIcons.append(QImage(MarbleDirs::systemPath() + QLatin1String("/bitmaps/editarrows/arrow-vertical.png")));
0055     m_resizeIcons.append(QImage(MarbleDirs::systemPath() + QLatin1String("/bitmaps/editarrows/arrow-vertical-active.png")));
0056     // South
0057     m_resizeIcons.append(QImage(MarbleDirs::systemPath() + QLatin1String("/bitmaps/editarrows/arrow-vertical.png")));
0058     m_resizeIcons.append(QImage(MarbleDirs::systemPath() + QLatin1String("/bitmaps/editarrows/arrow-vertical-active.png")));
0059     // East
0060     m_resizeIcons.append(QImage(MarbleDirs::systemPath() + QLatin1String("/bitmaps/editarrows/arrow-horizontal.png")));
0061     m_resizeIcons.append(QImage(MarbleDirs::systemPath() + QLatin1String("/bitmaps/editarrows/arrow-horizontal-active.png")));
0062     // West
0063     m_resizeIcons.append(QImage(MarbleDirs::systemPath() + QLatin1String("/bitmaps/editarrows/arrow-horizontal.png")));
0064     m_resizeIcons.append(QImage(MarbleDirs::systemPath() + QLatin1String("/bitmaps/editarrows/arrow-horizontal-active.png")));
0065 
0066 
0067     m_rotateIcons.reserve(16);
0068     // NorthWest
0069     m_rotateIcons.append(QImage(MarbleDirs::systemPath() + QLatin1String("/bitmaps/editarrows/arrow-rotation-topleft.png")));
0070     m_rotateIcons.append(QImage(MarbleDirs::systemPath() + QLatin1String("/bitmaps/editarrows/arrow-rotation-topleft-active.png")));
0071     // SouthWest
0072     m_rotateIcons.append(QImage(MarbleDirs::systemPath() + QLatin1String("/bitmaps/editarrows/arrow-rotation-bottomleft.png")));
0073     m_rotateIcons.append(QImage(MarbleDirs::systemPath() + QLatin1String("/bitmaps/editarrows/arrow-rotation-bottomleft-active.png")));
0074     // SouthEast
0075     m_rotateIcons.append(QImage(MarbleDirs::systemPath() + QLatin1String("/bitmaps/editarrows/arrow-rotation-bottomright.png")));
0076     m_rotateIcons.append(QImage(MarbleDirs::systemPath() + QLatin1String("/bitmaps/editarrows/arrow-rotation-bottomright-active.png")));
0077     // NorthEast
0078     m_rotateIcons.append(QImage(MarbleDirs::systemPath() + QLatin1String("/bitmaps/editarrows/arrow-rotation-topright.png")));
0079     m_rotateIcons.append(QImage(MarbleDirs::systemPath() + QLatin1String("/bitmaps/editarrows/arrow-rotation-topright-active.png")));
0080     // North
0081     m_rotateIcons.append(QImage(MarbleDirs::systemPath() + QLatin1String("/bitmaps/editarrows/arrow-horizontal.png")));
0082     m_rotateIcons.append(QImage(MarbleDirs::systemPath() + QLatin1String("/bitmaps/editarrows/arrow-horizontal-active.png")));
0083     // South
0084     m_rotateIcons.append(QImage(MarbleDirs::systemPath() + QLatin1String("/bitmaps/editarrows/arrow-horizontal.png")));
0085     m_rotateIcons.append(QImage(MarbleDirs::systemPath() + QLatin1String("/bitmaps/editarrows/arrow-horizontal-active.png")));
0086     // East
0087     m_rotateIcons.append(QImage(MarbleDirs::systemPath() + QLatin1String("/bitmaps/editarrows/arrow-vertical.png")));
0088     m_rotateIcons.append(QImage(MarbleDirs::systemPath() + QLatin1String("/bitmaps/editarrows/arrow-vertical-active.png")));
0089     // West
0090     m_rotateIcons.append(QImage(MarbleDirs::systemPath() + QLatin1String("/bitmaps/editarrows/arrow-vertical.png")));
0091     m_rotateIcons.append(QImage(MarbleDirs::systemPath() + QLatin1String("/bitmaps/editarrows/arrow-vertical-active.png")));
0092 
0093     update();
0094     setPaintLayers(QStringList() << "GroundOverlayFrame");
0095 }
0096 
0097 void GroundOverlayFrame::paint(GeoPainter *painter, const ViewportParams *viewport , const QString &layer, int tileZoomLevel)
0098 {
0099     Q_UNUSED(layer);
0100     Q_UNUSED(tileZoomLevel);
0101     m_viewport = viewport;
0102     m_regionList.clear();
0103 
0104     painter->save();
0105     if (const auto polygon = geodata_cast<GeoDataPolygon>(placemark()->geometry())) {
0106         const GeoDataLinearRing &ring = polygon->outerBoundary();
0107         QVector<GeoDataCoordinates> coordinateList;
0108         coordinateList.reserve(8);
0109 
0110         coordinateList.append( ring.at( NorthWest ) );
0111         coordinateList.append( ring.at( SouthWest ) );
0112         coordinateList.append( ring.at( SouthEast ) );
0113         coordinateList.append( ring.at( NorthEast ) );
0114 
0115         GeoDataCoordinates northernHandle = ring.at( NorthEast ).interpolate( ring.at( NorthWest ), 0.5 );
0116         GeoDataCoordinates southernHandle = ring.at( SouthEast ).interpolate( ring.at( SouthWest ), 0.5 );
0117         // Special case handle position to take tessellation
0118         // along latitude circles into account
0119         if (m_overlay->latLonBox().rotation() == 0) {
0120             northernHandle.setLatitude(ring.at( NorthEast ).latitude());
0121             southernHandle.setLatitude(ring.at( SouthEast ).latitude());
0122         }
0123         coordinateList.append( northernHandle );
0124         coordinateList.append( southernHandle );
0125 
0126         coordinateList.append( ring.at( NorthEast ).interpolate( ring.at( SouthEast ), 0.5 ) );
0127         coordinateList.append( ring.at( NorthWest ).interpolate( ring.at( SouthWest ), 0.5 ) );
0128 
0129         m_regionList.reserve(9);
0130         m_regionList.append( painter->regionFromEllipse( coordinateList.at( NorthWest ), 16, 16 ) );
0131         m_regionList.append( painter->regionFromEllipse( coordinateList.at( SouthWest ), 16, 16 ) );
0132         m_regionList.append( painter->regionFromEllipse( coordinateList.at( SouthEast ), 16, 16 ) );
0133         m_regionList.append( painter->regionFromEllipse( coordinateList.at( NorthEast ), 16, 16 ) );
0134         m_regionList.append( painter->regionFromEllipse( coordinateList.at( North ), 16, 16 ) );
0135         m_regionList.append( painter->regionFromEllipse( coordinateList.at( South ), 16, 16 ) );
0136         m_regionList.append( painter->regionFromEllipse( coordinateList.at( East ),  16, 16 ) );
0137         m_regionList.append( painter->regionFromEllipse( coordinateList.at( West ),  16, 16 ) );
0138         m_regionList.append( painter->regionFromPolygon( ring, Qt::OddEvenFill ) );
0139 
0140         // Calculate handle icon orientation due to the projection
0141         qreal xNW, yNW, xSW, ySW;
0142         viewport->screenCoordinates(ring.at( NorthWest ), xNW, yNW);
0143         viewport->screenCoordinates(ring.at( SouthWest ), xSW, ySW);
0144         qreal westernAngle = qAtan2(ySW - yNW, xSW - xNW) - M_PI/2;
0145         qreal xNE, yNE, xSE, ySE;
0146         viewport->screenCoordinates(ring.at( NorthEast ), xNE, yNE);
0147         viewport->screenCoordinates(ring.at( SouthEast ), xSE, ySE);
0148         qreal easternAngle = qAtan2(ySE - yNE, xSE - xNE) - M_PI/2;
0149 
0150         painter->setPen( Qt::DashLine );
0151         painter->setBrush( Qt::NoBrush );
0152         painter->drawPolygon( ring );
0153 
0154         qreal projectedAngle = 0;
0155 
0156         for( int i = NorthWest; i != Polygon; ++i ) {
0157 
0158             // Assign handle icon orientation due to the projection
0159             if (i == NorthWest || i == West || i == SouthWest) {
0160                 projectedAngle = westernAngle;
0161             }
0162             else if (i == NorthEast || i == East || i == SouthEast) {
0163                 projectedAngle = easternAngle;
0164             }
0165             else if (i == North || i == South) {
0166                 projectedAngle = (westernAngle + easternAngle) / 2;
0167             }
0168             QTransform trans;
0169             trans.rotateRadians( projectedAngle );
0170             if ( m_editStatus == Resize ){
0171                 if( m_hoveredHandle != i ) {
0172                     painter->drawImage( coordinateList.at( i ),
0173                                         m_resizeIcons.at( 2*i ).transformed( trans, Qt::SmoothTransformation ) );
0174                 } else {
0175                     painter->drawImage( coordinateList.at( i ),
0176                                         m_resizeIcons.at( 2*i + 1 ).transformed( trans, Qt::SmoothTransformation ) );
0177                 }
0178             } else if ( m_editStatus == Rotate ) {
0179                 if( m_hoveredHandle != i ) {
0180                     painter->drawImage( coordinateList.at( i ),
0181                                         m_rotateIcons.at( 2*i ).transformed( trans, Qt::SmoothTransformation ) );
0182                 } else {
0183                     painter->drawImage( coordinateList.at( i ),
0184                                         m_rotateIcons.at( 2*i + 1 ).transformed( trans, Qt::SmoothTransformation ) );
0185                 }
0186             }
0187         }
0188     }
0189     painter->restore();
0190 }
0191 
0192 bool GroundOverlayFrame::containsPoint( const QPoint &eventPos ) const
0193 {
0194     for ( const QRegion &region: m_regionList ) {
0195         if ( region.contains( eventPos ) ) {
0196             return true;
0197         }
0198     }
0199 
0200     // This is a bugfix to handle the events even if they occur outside of this object,
0201     // so when rotating or resizing the mouseReleaseEvent is handled successfully
0202     // TODO: maybe find a better way?
0203     return m_movedHandle   != NoRegion ||
0204         m_hoveredHandle != NoRegion;
0205 }
0206 
0207 void GroundOverlayFrame::dealWithItemChange( const SceneGraphicsItem *other )
0208 {
0209     Q_UNUSED( other );
0210 }
0211 
0212 void GroundOverlayFrame::move( const GeoDataCoordinates &source, const GeoDataCoordinates &destination )
0213 {
0214     // not implemented yet
0215     Q_UNUSED( source );
0216     Q_UNUSED( destination );
0217 }
0218 
0219 bool GroundOverlayFrame::mousePressEvent( QMouseEvent *event )
0220 {
0221     // React to all ellipse as well as to the polygon.
0222     for ( int i = 0; i < m_regionList.size(); ++i ) {
0223         if ( m_regionList.at(i).contains( event->pos() ) ) {
0224             m_movedHandle = i;
0225 
0226             qreal lon, lat;
0227             m_viewport->geoCoordinates( event->pos().x(),
0228                                         event->pos().y(),
0229                                         lon, lat,
0230                                         GeoDataCoordinates::Radian );
0231             m_movedHandleGeoCoordinates.set( lon, lat );
0232             m_movedHandleScreenCoordinates = event->pos();
0233             m_previousRotation = m_overlay->latLonBox().rotation();
0234 
0235             if ( m_movedHandle == Polygon ) {
0236                 m_editStatusChangeNeeded = true;
0237             }
0238 
0239             return true;
0240         }
0241     }
0242 
0243     return false;
0244 }
0245 
0246 bool GroundOverlayFrame::mouseMoveEvent( QMouseEvent *event )
0247 {
0248     if ( !m_viewport ) {
0249         return false;
0250     }
0251 
0252     // Catch hover events.
0253     if ( m_movedHandle == NoRegion ) {
0254         for ( int i = 0; i < m_regionList.size(); ++i ) {
0255             if ( m_regionList.at(i).contains( event->pos() ) ) {
0256                 if ( i == Polygon ) {
0257                     setRequest( ChangeCursorOverlayBodyHover );
0258                 } else {
0259                     setRequest( ChangeCursorOverlayRotateHover );
0260                 }
0261                 m_hoveredHandle = i;
0262                 return true;
0263             }
0264         }
0265         m_hoveredHandle = NoRegion;
0266         return true;
0267     } else {
0268         m_editStatusChangeNeeded = false;
0269     }
0270 
0271     if (geodata_cast<GeoDataPolygon>(placemark()->geometry())) {
0272         qreal lon, lat;
0273         m_viewport->geoCoordinates( event->pos().x(),
0274                                     event->pos().y(),
0275                                     lon, lat,
0276                                     GeoDataCoordinates::Radian );
0277 
0278         if ( m_editStatus == Resize ) {
0279 
0280             GeoDataCoordinates coord(lon, lat);
0281             GeoDataCoordinates rotatedCoord(coord);
0282 
0283             if (m_overlay->latLonBox().rotation() != 0) {
0284                 rotatedCoord = coord.rotateAround(m_overlay->latLonBox().center(),
0285                                                  -m_overlay->latLonBox().rotation());
0286             }
0287 
0288             if ( m_movedHandle == NorthWest ) {
0289                 m_overlay->latLonBox().setNorth( rotatedCoord.latitude() );
0290                 m_overlay->latLonBox().setWest( rotatedCoord.longitude() );
0291             } else if ( m_movedHandle == SouthWest ) {
0292                 m_overlay->latLonBox().setSouth( rotatedCoord.latitude() );
0293                 m_overlay->latLonBox().setWest( rotatedCoord.longitude() );
0294             } else if ( m_movedHandle == SouthEast ) {
0295                 m_overlay->latLonBox().setSouth( rotatedCoord.latitude() );
0296                 m_overlay->latLonBox().setEast( rotatedCoord.longitude() );
0297             } else if ( m_movedHandle == NorthEast ) {
0298                 m_overlay->latLonBox().setNorth( rotatedCoord.latitude() );
0299                 m_overlay->latLonBox().setEast( rotatedCoord.longitude() );
0300             } else if ( m_movedHandle == North ) {
0301                 m_overlay->latLonBox().setNorth( rotatedCoord.latitude() );
0302             } else if ( m_movedHandle == South ) {
0303                 m_overlay->latLonBox().setSouth( rotatedCoord.latitude() );
0304             } else if ( m_movedHandle == East ) {
0305                 m_overlay->latLonBox().setEast( rotatedCoord.longitude() );
0306             } else if ( m_movedHandle == West ) {
0307                 m_overlay->latLonBox().setWest( rotatedCoord.longitude() );
0308             }
0309 
0310         } else if ( m_editStatus == Rotate ) {
0311             if ( m_movedHandle != Polygon ) {
0312                 QPoint center = m_regionList.at( Polygon ).boundingRect().center();
0313                 qreal angle1 = qAtan2( event->pos().y() - center.y(),
0314                                        event->pos().x() - center.x() );
0315                 qreal angle2 = qAtan2( m_movedHandleScreenCoordinates.y() - center.y(),
0316                                        m_movedHandleScreenCoordinates.x() - center.x() );
0317                 m_overlay->latLonBox().setRotation( angle2 - angle1 + m_previousRotation );
0318             }
0319         }
0320 
0321         if ( m_movedHandle == Polygon ) {
0322             const qreal centerLonDiff = lon - m_movedHandleGeoCoordinates.longitude();
0323             const qreal centerLatDiff = lat - m_movedHandleGeoCoordinates.latitude();
0324 
0325             m_overlay->latLonBox().setBoundaries( m_overlay->latLonBox().north() + centerLatDiff,
0326                                                   m_overlay->latLonBox().south() + centerLatDiff,
0327                                                   m_overlay->latLonBox().east()  + centerLonDiff,
0328                                                   m_overlay->latLonBox().west()  + centerLonDiff );
0329 
0330             m_movedHandleGeoCoordinates.set( lon, lat );
0331         }
0332 
0333         update();
0334         return true;
0335     }
0336     return false;
0337 }
0338 
0339 bool GroundOverlayFrame::mouseReleaseEvent( QMouseEvent *event )
0340 {
0341     Q_UNUSED( event );
0342 
0343     m_movedHandle = NoRegion;
0344     m_textureLayer->reset();
0345 
0346     if( m_editStatusChangeNeeded ) {
0347         if( m_editStatus == Resize ) {
0348             m_editStatus = Rotate;
0349         } else {
0350             m_editStatus = Resize;
0351         }
0352     }
0353 
0354     return true;
0355 }
0356 
0357 void GroundOverlayFrame::update()
0358 {
0359     GeoDataLatLonBox overlayLatLonBox = m_overlay->latLonBox();
0360     GeoDataPolygon *poly = dynamic_cast<GeoDataPolygon*>( placemark()->geometry() );
0361     poly->outerBoundary().clear();
0362 
0363     GeoDataCoordinates rotatedCoord;
0364 
0365     GeoDataCoordinates northWest(overlayLatLonBox.west(), overlayLatLonBox.north());
0366     rotatedCoord = northWest.rotateAround(overlayLatLonBox.center(), overlayLatLonBox.rotation());
0367     poly->outerBoundary().append( rotatedCoord );
0368 
0369     GeoDataCoordinates southWest(overlayLatLonBox.west(), overlayLatLonBox.south());
0370     rotatedCoord = southWest.rotateAround(overlayLatLonBox.center(), overlayLatLonBox.rotation());
0371     poly->outerBoundary().append( rotatedCoord );
0372 
0373     GeoDataCoordinates southEast(overlayLatLonBox.east(), overlayLatLonBox.south());
0374     rotatedCoord = southEast.rotateAround(overlayLatLonBox.center(), overlayLatLonBox.rotation());
0375     poly->outerBoundary().append( rotatedCoord );
0376 
0377     GeoDataCoordinates northEast(overlayLatLonBox.east(), overlayLatLonBox.north());
0378     rotatedCoord = northEast.rotateAround(overlayLatLonBox.center(), overlayLatLonBox.rotation());
0379     poly->outerBoundary().append( rotatedCoord );
0380 }
0381 
0382 void GroundOverlayFrame::dealWithStateChange( SceneGraphicsItem::ActionState previousState )
0383 {
0384     Q_UNUSED( previousState );
0385 }
0386 
0387 const char *GroundOverlayFrame::graphicType() const
0388 {
0389     return SceneGraphicsTypes::SceneGraphicGroundOverlay;
0390 }
0391 
0392 
0393 }