File indexing completed on 2024-04-28 15:14:32

0001 // SPDX-License-Identifier: LGPL-2.1-or-later
0002 //
0003 // SPDX-FileCopyrightText: 2006-2007 Torsten Rahn <tackat@kde.org>
0004 // SPDX-FileCopyrightText: 2007 Inge Wallin <ingwa@kde.org>
0005 //
0006 
0007 
0008 #include "ControlView.h"
0009 
0010 #include <QCloseEvent>
0011 #include <QLayout>
0012 #include <QPrintDialog>
0013 #include <QPrintPreviewDialog>
0014 #include <QPrinter>
0015 #include <QPainter>
0016 #include <QTextDocument>
0017 #include <QUrl>
0018 #include <QDesktopServices>
0019 #include <QNetworkAccessManager>
0020 #include <QNetworkReply>
0021 #include <QNetworkRequest>
0022 #include <QProcess>
0023 #include <QTimer>
0024 #include <QFileInfo>
0025 #include <QMessageBox>
0026 #include <QMainWindow>
0027 #include <QDockWidget>
0028 #include <QShortcut>
0029 #include <QToolBar>
0030 #include <QMimeData>
0031 #include <QPixmap>
0032 
0033 #ifdef MARBLE_DBUS
0034 #include <QDBusConnection>
0035 #include "MarbleDBusInterface.h"
0036 #endif
0037 
0038 #include "GeoDataLatLonAltBox.h"
0039 #include "GeoSceneDocument.h"
0040 #include "GeoSceneHead.h"
0041 #include "GeoUriParser.h"
0042 #include "MarbleDebug.h"
0043 #include "MarbleDirs.h"
0044 #include "MarbleModel.h"
0045 #include "MarbleMap.h"
0046 #include "MapThemeManager.h"
0047 #include "PrintOptionsWidget.h"
0048 #include "ViewportParams.h"
0049 #include "ViewParams.h"
0050 #include "routing/Route.h"
0051 #include "routing/RoutingManager.h"
0052 #include "routing/RoutingModel.h"
0053 #include "routing/RouteRequest.h"
0054 #include "routing/RoutingWidget.h"
0055 #include "ExternalEditorDialog.h"
0056 #include "CurrentLocationWidget.h"
0057 #include "SearchWidget.h"
0058 #include "TourWidget.h"
0059 #include "MapViewWidget.h"
0060 #include "FileViewWidget.h"
0061 #include "LegendWidget.h"
0062 #include "BookmarkManager.h"
0063 #include "cloudsync/CloudSyncManager.h"
0064 #include "cloudsync/BookmarkSyncManager.h"
0065 #include "cloudsync/RouteSyncManager.h"
0066 #include "cloudsync/ConflictDialog.h"
0067 #include "cloudsync/MergeItem.h"
0068 #include "RenderPlugin.h"
0069 
0070 namespace Marble
0071 {
0072 
0073 ControlView::ControlView( QWidget *parent )
0074    : QWidget( parent ),
0075      m_mapThemeManager( new MapThemeManager( this ) ),
0076      m_searchDock( nullptr ),
0077      m_locationWidget( nullptr ),
0078      m_conflictDialog( nullptr ),
0079      m_togglePanelVisibilityAction( nullptr ),
0080      m_isPanelVisible( true ),
0081      m_tourWidget( nullptr ),
0082      m_annotationDock( nullptr ),
0083      m_annotationPlugin( nullptr )
0084 {
0085     setWindowTitle( tr( "Marble - Virtual Globe" ) );
0086 
0087     resize( 680, 640 );
0088 
0089     m_marbleWidget = new MarbleWidget( this );
0090     m_marbleWidget->setSizePolicy( QSizePolicy( QSizePolicy::MinimumExpanding,
0091                                                 QSizePolicy::MinimumExpanding ) );
0092 #ifdef MARBLE_DBUS
0093     new MarbleDBusInterface( m_marbleWidget );
0094     QDBusConnection::sessionBus().registerObject( "/Marble", m_marbleWidget );
0095     if (!QDBusConnection::sessionBus().registerService( "org.kde.marble" )) {
0096         QString const urlWithPid = QString("org.kde.marble-%1").arg( QCoreApplication::applicationPid() );
0097         if ( !QDBusConnection::sessionBus().registerService( urlWithPid ) ) {
0098             mDebug() << "Failed to register service org.kde.marble and " << urlWithPid << " with the DBus session bus.";
0099         }
0100     }
0101 #endif
0102 
0103 
0104     QVBoxLayout* layout = new QVBoxLayout;
0105     layout->addWidget( m_marbleWidget );
0106     layout->setMargin( 0 );
0107     setLayout( layout );
0108 
0109     m_cloudSyncManager = new CloudSyncManager( this );
0110     m_cloudSyncManager->routeSyncManager()->setRoutingManager( m_marbleWidget->model()->routingManager() );
0111     BookmarkSyncManager* bookmarkSyncManager = m_cloudSyncManager->bookmarkSyncManager();
0112     bookmarkSyncManager->setBookmarkManager( m_marbleWidget->model()->bookmarkManager() );
0113     m_conflictDialog = new ConflictDialog( m_marbleWidget );
0114     connect( bookmarkSyncManager, SIGNAL(mergeConflict(MergeItem*)), this, SLOT(showConflictDialog(MergeItem*)) );
0115     connect( bookmarkSyncManager, SIGNAL(syncComplete()), m_conflictDialog, SLOT(stopAutoResolve()) );
0116     connect( m_conflictDialog, SIGNAL(resolveConflict(MergeItem*)), bookmarkSyncManager, SLOT(resolveConflict(MergeItem*)) );
0117 
0118     setAcceptDrops(true);
0119 }
0120 
0121 ControlView::~ControlView()
0122 {
0123     // nothing to do
0124 }
0125 
0126 QString ControlView::applicationVersion()
0127 {
0128     return MARBLE_VERSION_STRING;
0129 }
0130 
0131 MapThemeManager *ControlView::mapThemeManager()
0132 {
0133     return m_mapThemeManager;
0134 }
0135 
0136 void ControlView::zoomIn()
0137 {
0138     m_marbleWidget->zoomIn();
0139 }
0140 
0141 void ControlView::zoomOut()
0142 {
0143     m_marbleWidget->zoomOut();
0144 }
0145 
0146 void ControlView::moveLeft()
0147 {
0148     m_marbleWidget->moveLeft(Marble::Linear);
0149 }
0150 
0151 void ControlView::moveRight()
0152 {
0153     m_marbleWidget->moveRight(Marble::Linear);
0154 }
0155 
0156 void ControlView::moveUp()
0157 {
0158     m_marbleWidget->moveUp(Marble::Linear);
0159 }
0160 
0161 void ControlView::moveDown()
0162 {
0163     m_marbleWidget->moveDown(Marble::Linear);
0164 }
0165 
0166 QString ControlView::defaultMapThemeId() const
0167 {
0168     QStringList fallBackThemes;
0169       fallBackThemes << "earth/srtm/srtm.dgml";
0170       fallBackThemes << "earth/bluemarble/bluemarble.dgml";
0171       fallBackThemes << "earth/openstreetmap/openstreetmap.dgml";
0172 
0173     const QStringList installedThemes = m_mapThemeManager->mapThemeIds();
0174 
0175     for(const QString &fallback: fallBackThemes) {
0176         if (installedThemes.contains(fallback)) {
0177             return fallback;
0178         }
0179     }
0180 
0181     if (installedThemes.size()) {
0182         return installedThemes.first();
0183     }
0184 
0185     return QString();
0186 }
0187 
0188 void ControlView::printMapScreenShot( const QPointer<QPrintDialog>& printDialog)
0189 {
0190 #ifndef QT_NO_PRINTER
0191         PrintOptionsWidget* printOptions = new PrintOptionsWidget( this );
0192         bool const mapCoversViewport = m_marbleWidget->viewport()->mapCoversViewport();
0193         printOptions->setBackgroundControlsEnabled( !mapCoversViewport );
0194         bool hasLegend = m_marbleWidget->model()->legend() != nullptr;
0195         printOptions->setLegendControlsEnabled( hasLegend );
0196         bool hasRoute = marbleWidget()->model()->routingManager()->routingModel()->rowCount() > 0;
0197         printOptions->setPrintRouteSummary( hasRoute );
0198         printOptions->setPrintDrivingInstructions( hasRoute );
0199         printOptions->setPrintDrivingInstructionsAdvice( hasRoute );
0200         printOptions->setRouteControlsEnabled( hasRoute );
0201         printDialog->setOptionTabs( QList<QWidget*>() << printOptions );
0202 
0203         if ( printDialog->exec() == QDialog::Accepted ) {
0204             QTextDocument document;
0205             QString text = "<html><head><title>Marble Printout</title></head><body>";
0206             QPalette const originalPalette = m_marbleWidget->palette();
0207             bool const wasBackgroundVisible = m_marbleWidget->showBackground();
0208             bool const hideBackground = !mapCoversViewport && !printOptions->printBackground();
0209             if ( hideBackground ) {
0210                 // Temporarily remove the black background and layers painting on it
0211                 m_marbleWidget->setShowBackground( false );
0212                 m_marbleWidget->setPalette( QPalette ( Qt::white ) );
0213                 m_marbleWidget->update();
0214             }
0215 
0216             if ( printOptions->printMap() ) {
0217                 printMap( document, text, printDialog->printer() );
0218             }
0219 
0220             if ( printOptions->printLegend() ) {
0221                 printLegend( document, text );
0222             }
0223 
0224             if ( printOptions->printRouteSummary() ) {
0225                 printRouteSummary( document, text );
0226             }
0227 
0228             if ( printOptions->printDrivingInstructions() ) {
0229                 printDrivingInstructions( document, text );
0230             }
0231 
0232             if ( printOptions->printDrivingInstructionsAdvice() ) {
0233                 printDrivingInstructionsAdvice( document, text );
0234             }
0235 
0236             text += QLatin1String("</body></html>");
0237             document.setHtml( text );
0238             document.print( printDialog->printer() );
0239 
0240             if ( hideBackground ) {
0241                 m_marbleWidget->setShowBackground( wasBackgroundVisible );
0242                 m_marbleWidget->setPalette( originalPalette );
0243                 m_marbleWidget->update();
0244             }
0245     }
0246 #endif
0247 }
0248 
0249 bool ControlView::openGeoUri( const QString& geoUriString )
0250 {
0251     GeoUriParser uriParser( geoUriString );
0252     const bool success = uriParser.parse();
0253     if ( success ) {
0254         if ( uriParser.planet().id() != marbleModel()->planet()->id() ) {
0255             MapThemeManager *manager = mapThemeManager();
0256             for( const QString& planetName: manager->mapThemeIds()) {
0257                 if ( planetName.startsWith(uriParser.planet().id(), Qt::CaseInsensitive)) {
0258                     m_marbleWidget->setMapThemeId(planetName);
0259                     break;
0260                 }
0261             }
0262         }
0263         m_marbleWidget->centerOn( uriParser.coordinates() );
0264         if ( uriParser.coordinates().altitude() > 0.0 )
0265         {
0266             m_marbleWidget->setDistance( uriParser.coordinates().altitude() * METER2KM );
0267         }
0268     }
0269     return success;
0270 }
0271 
0272 QActionGroup *ControlView::createViewSizeActionGroup( QObject* parent )
0273 {
0274     QActionGroup* actionGroup = new QActionGroup( parent );
0275 
0276     QAction *defaultAction = new QAction( tr( "Default (Resizable)" ), parent );
0277     defaultAction->setCheckable( true );
0278     defaultAction->setChecked( true );
0279     actionGroup->addAction(defaultAction);
0280 
0281     QAction *separator = new QAction( parent );
0282     separator->setSeparator( true );
0283     actionGroup->addAction(separator);
0284 
0285     addViewSizeAction( actionGroup, tr("NTSC (%1x%2)"), 720, 486 );
0286     addViewSizeAction( actionGroup, tr("PAL (%1x%2)"), 720, 576 );
0287     addViewSizeAction( actionGroup, tr("NTSC 16:9 (%1x%2)"), 864, 486 );
0288     addViewSizeAction( actionGroup, tr("PAL 16:9 (%1x%2)"), 1024, 576 );
0289     // xgettext:no-c-format
0290     addViewSizeAction( actionGroup, tr("DVD (%1x%2p)"), 852, 480 );
0291     // xgettext:no-c-format
0292     addViewSizeAction( actionGroup, tr("HD (%1x%2p)"), 1280, 720 );
0293     // xgettext:no-c-format
0294     addViewSizeAction( actionGroup, tr("Full HD (%1x%2p)"), 1920, 1080 );
0295     addViewSizeAction( actionGroup, tr("Digital Cinema (%1x%2)"), 2048, 1536 );
0296     /** FIXME: Needs testing, worked with errors.
0297     addViewSizeAction(actionGroup, "4K UHD (%1x%2)", 3840, 2160);
0298     addViewSizeAction(actionGroup, "4K (%1x%2)", 4096, 3072);
0299     */
0300 
0301     return actionGroup;
0302 }
0303 
0304 void ControlView::printPixmap( QPrinter * printer, const QPixmap& pixmap  )
0305 {
0306 #ifndef QT_NO_PRINTER
0307     QSize printSize = pixmap.size();
0308     QRect mapPageRect = printer->pageRect();
0309     printSize.scale( printer->pageRect().size(), Qt::KeepAspectRatio );
0310     QPoint printTopLeft( ( mapPageRect.width() - printSize.width() ) / 2 ,
0311                          ( mapPageRect.height() - printSize.height() ) / 2 );
0312     QRect mapPrintRect( printTopLeft, printSize );
0313 
0314     QPainter painter;
0315     if (!painter.begin(printer))
0316         return;
0317     painter.drawPixmap( mapPrintRect, pixmap, pixmap.rect() );
0318     painter.end();
0319 #endif
0320 }
0321 
0322 // QPointer is used because of issues described in https://blogs.kde.org/2009/03/26/how-crash-almost-every-qtkde-application-and-how-fix-it-0
0323 void ControlView::printPreview()
0324 {
0325 #ifndef QT_NO_PRINTER
0326     QPrinter printer( QPrinter::HighResolution );
0327 
0328     QPointer<QPrintPreviewDialog> preview = new QPrintPreviewDialog( &printer, this );
0329     preview->setWindowFlags ( Qt::Window );
0330     preview->resize(640, 480);
0331     connect( preview, SIGNAL(paintRequested(QPrinter*)), SLOT(paintPrintPreview(QPrinter*)) );
0332     preview->exec();
0333     delete preview;
0334 #endif
0335 }
0336 
0337 void ControlView::paintPrintPreview( QPrinter * printer )
0338 {
0339 #ifndef QT_NO_PRINTER
0340     QPixmap mapPixmap = mapScreenShot();
0341     printPixmap( printer, mapPixmap );
0342 #endif
0343 }
0344 
0345 void ControlView::printMap( QTextDocument &document, QString &text, QPrinter *printer )
0346 {
0347 #ifndef QT_NO_PRINTER
0348     QPixmap image = mapScreenShot();
0349 
0350     if ( m_marbleWidget->viewport()->mapCoversViewport() ) {
0351         // Paint a black frame. Looks better.
0352         QPainter painter(&image);
0353         painter.setPen( Qt::black );
0354         painter.drawRect( 0, 0, image.width() - 2, image.height() - 2 );
0355     }
0356 
0357     QString uri = "marble://screenshot.png";
0358     document.addResource( QTextDocument::ImageResource, QUrl( uri ), QVariant( image) );
0359     QString img = "<img src=\"%1\" width=\"%2\" align=\"center\">";
0360     int width = qRound( printer->pageRect( QPrinter::Point ).width() );
0361     text += img.arg( uri ).arg( width );
0362 #endif
0363 }
0364 
0365 void ControlView::printLegend( QTextDocument &document, QString &text )
0366 {
0367 #ifndef QT_NO_PRINTER
0368     QTextDocument *legend = m_marbleWidget->model()->legend();
0369     if ( legend ) {
0370         legend->adjustSize();
0371         QSize size = legend->size().toSize();
0372         QSize imageSize = size + QSize( 4, 4 );
0373         QImage image( imageSize, QImage::Format_ARGB32);
0374         QPainter painter( &image );
0375         painter.setRenderHint( QPainter::Antialiasing, true );
0376         painter.drawRoundedRect( QRect( QPoint( 0, 0 ), size ), 5, 5 );
0377         legend->drawContents( &painter );
0378         document.addResource( QTextDocument::ImageResource, QUrl( "marble://legend.png" ), QVariant(image) );
0379         QString img = "<p><img src=\"%1\" align=\"center\"></p>";
0380         text += img.arg( "marble://legend.png" );
0381     }
0382 #endif
0383 }
0384 
0385 void ControlView::printRouteSummary( QTextDocument &document, QString &text)
0386 {
0387 #ifndef QT_NO_PRINTER
0388     RoutingModel* routingModel = m_marbleWidget->model()->routingManager()->routingModel();
0389 
0390     if ( !routingModel ) {
0391         return;
0392     }
0393 
0394     RouteRequest* routeRequest = m_marbleWidget->model()->routingManager()->routeRequest();
0395     if ( routeRequest ) {
0396         QString summary = "<h3>Route to %1: %2 %3</h3>";
0397         QString destination;
0398         if ( routeRequest->size() ) {
0399             destination = routeRequest->name( routeRequest->size()-1 );
0400         }
0401 
0402         QString label = "<p>%1 %2</p>";
0403         qreal distance = routingModel->route().distance();
0404         QString unit = distance > 1000 ? "km" : "m";
0405         int precision = distance > 1000 ? 1 : 0;
0406         if ( distance > 1000 ) {
0407             distance /= 1000;
0408         }
0409         summary = summary.arg(destination).arg( distance, 0, 'f', precision ).arg( unit );
0410         text += summary;
0411 
0412         text += QLatin1String("<table cellpadding=\"2\">");
0413         QString pixmapTemplate = "marble://viaPoint-%1.png";
0414         for ( int i=0; i<routeRequest->size(); ++i ) {
0415             text += QLatin1String("<tr><td>");
0416             QPixmap pixmap = routeRequest->pixmap(i);
0417             QString pixmapResource = pixmapTemplate.arg( i );
0418             document.addResource(QTextDocument::ImageResource,
0419                                           QUrl( pixmapResource ), QVariant( pixmap ) );
0420             QString myimg = "<img src=\"%1\">";
0421             text += myimg.arg(pixmapResource) +
0422                     QLatin1String("</td><td>");
0423                     routeRequest->name(i) +
0424                     QLatin1String("</td></tr>");
0425         }
0426         text += QLatin1String("</table>");
0427     }
0428 #endif
0429 }
0430 
0431 void ControlView::printDrivingInstructions( QTextDocument &document, QString &text )
0432 {
0433 #ifndef QT_NO_PRINTER
0434     RoutingModel* routingModel = m_marbleWidget->model()->routingManager()->routingModel();
0435 
0436     if (!routingModel) {
0437         return;
0438     }
0439 
0440     GeoDataLineString total = routingModel->route().path();
0441 
0442     text += QLatin1String("<table cellpadding=\"4\">"
0443             "<tr><th>No.</th><th>Distance</th><th>Instruction</th></tr>");
0444     for ( int i=0; i<routingModel->rowCount(); ++i ) {
0445         QModelIndex index = routingModel->index(i, 0);
0446         GeoDataCoordinates coordinates = index.data( RoutingModel::CoordinateRole ).value<GeoDataCoordinates>();
0447         GeoDataLineString accumulator;
0448         for (int k=0; k<total.size(); ++k) {
0449             accumulator << total.at(k);
0450 
0451             if (total.at(k) == coordinates)
0452                 break;
0453         }
0454 
0455         if ( i%2 == 0 ) {
0456             text += QLatin1String("<tr bgcolor=\"lightGray\"><td align=\"right\" valign=\"middle\">");
0457         }
0458         else {
0459             text += QLatin1String("<tr><td align=\"right\" valign=\"middle\">");
0460         }
0461         text += QString::number(i+1) +
0462                 QLatin1String("</td><td align=\"right\" valign=\"middle\">");
0463 
0464         qreal planetRadius = marbleModel()->planet()->radius();
0465         text += QString::number(accumulator.length(planetRadius) * METER2KM, 'f', 1) +
0466                 /** @todo: support localization */
0467                 QLatin1String(" km</td><td valign=\"middle\">");
0468 
0469         QPixmap instructionIcon = index.data( Qt::DecorationRole ).value<QPixmap>();
0470         if ( !instructionIcon.isNull() ) {
0471             QString uri = QString("marble://turnIcon%1.png").arg(i);
0472             document.addResource( QTextDocument::ImageResource, QUrl( uri ), QVariant( instructionIcon ) );
0473             text += QString("<img src=\"%1\">").arg(uri);
0474         }
0475 
0476         text += routingModel->data( index ).toString() +
0477                 QLatin1String("</td></tr>");
0478     }
0479     text += QLatin1String("</table>");
0480 #endif
0481 }
0482 
0483 void ControlView::printDrivingInstructionsAdvice( QTextDocument &, QString &text )
0484 {
0485 #ifndef QT_NO_PRINTER
0486     text += QLatin1String("<p>") + tr("The Marble development team wishes you a pleasant and safe journey.") + QLatin1String("</p>") +
0487             QLatin1String("<p>") + tr("Caution: Driving instructions may be incomplete or inaccurate.") +
0488             QLatin1Char(' ') + tr("Road construction, weather and other unforeseen variables can result in this suggested route not to be the most expedient or safest route to your destination.") +
0489             QLatin1Char(' ') + tr("Please use common sense while navigating.") + QLatin1String("</p>");
0490 #endif
0491 }
0492 
0493 void ControlView::addViewSizeAction( QActionGroup* actionGroup, const QString &nameTemplate, int width, int height )
0494 {
0495     QString const name = nameTemplate.arg( width ).arg( height );
0496     QAction *action = new QAction( name, actionGroup->parent() );
0497     action->setCheckable( true );
0498     action->setData( QSize( width, height ) );
0499     actionGroup->addAction( action );
0500 }
0501 
0502 
0503 void ControlView::launchExternalMapEditor()
0504 {
0505     QString editor = m_externalEditor;
0506     if ( editor.isEmpty() ) {
0507         QPointer<ExternalEditorDialog> dialog = new ExternalEditorDialog( this );
0508         if( dialog->exec() == QDialog::Accepted ) {
0509             editor = dialog->externalEditor();
0510             if ( dialog->saveDefault() ) {
0511                 m_externalEditor = editor;
0512             }
0513         } else {
0514             return;
0515         }
0516     }
0517 
0518     if (editor == QLatin1String("josm")) {
0519         // JOSM, the java based editor
0520         synchronizeWithExternalMapEditor( editor, "--download=%1,%4,%3,%2" );
0521     }
0522     else if (editor == QLatin1String("merkaartor")) {
0523         // Merkaartor, a Qt based editor
0524         QString argument = "osm://download/load_and_zoom?top=%1&right=%2&bottom=%3&left=%4";
0525         synchronizeWithExternalMapEditor( editor, argument );
0526     }
0527     else {
0528         // Potlatch, the flash based editor running at the osm main website
0529         QString url = "http://www.openstreetmap.org/edit?lat=%1&lon=%2&zoom=%3";
0530         qreal lat = m_marbleWidget->centerLatitude();
0531         qreal lon = m_marbleWidget->centerLongitude();
0532         int zoom = m_marbleWidget->tileZoomLevel();
0533         url = url.arg( lat, 0, 'f', 8 ).arg( lon, 0, 'f', 8 ).arg( zoom );
0534         QDesktopServices::openUrl( QUrl(url) );
0535     }
0536 }
0537 
0538 void ControlView::synchronizeWithExternalMapEditor( const QString &application, const QString &argument )
0539 {
0540     QTimer watchdog; // terminates network connection after a short timeout
0541     watchdog.setSingleShot( true );
0542     QEventLoop localEventLoop;
0543     connect( &watchdog, SIGNAL(timeout()), &localEventLoop, SLOT(quit()) );
0544     QNetworkAccessManager manager;
0545     connect( &manager, SIGNAL(finished(QNetworkReply*)), &localEventLoop, SLOT(quit()) );
0546 
0547     // Wait at most two seconds for the local server to respond
0548     QNetworkReply *reply = manager.get( QNetworkRequest( QUrl( "http://localhost:8111/") ) );
0549     watchdog.start( 2000 );
0550     localEventLoop.exec();
0551 
0552     GeoDataLatLonAltBox box = m_marbleWidget->viewport()->viewLatLonAltBox();
0553     qreal north = box.north( GeoDataCoordinates::Degree );
0554     qreal east  = box.east( GeoDataCoordinates::Degree );
0555     qreal south = box.south( GeoDataCoordinates::Degree );
0556     qreal west  = box.west( GeoDataCoordinates::Degree );
0557 
0558     if( watchdog.isActive() && reply->bytesAvailable() > 0 ) {
0559         // The local server is alive. Tell it to download the current region
0560         watchdog.stop();
0561         QString serverUrl = "http://localhost:8111/load_and_zoom?top=%1&right=%2&bottom=%3&left=%4";
0562         serverUrl = serverUrl.arg( north, 0, 'f', 8 ).arg( east, 0, 'f', 8 );
0563         serverUrl = serverUrl.arg( south, 0, 'f', 8 ).arg( west, 0, 'f', 8 );
0564         mDebug() << "Connecting to local server URL " << serverUrl;
0565         manager.get( QNetworkRequest( QUrl( serverUrl ) ) );
0566 
0567         // Give it five seconds to process the request
0568         watchdog.start( 5000 );
0569         localEventLoop.exec();
0570     } else {
0571         // The local server is not alive. Start the application
0572         QString applicationArgument = argument.arg( south, 0, 'f', 8 ).arg( east, 0, 'f', 8 );
0573         applicationArgument = applicationArgument.arg( north, 0, 'f', 8 ).arg( west, 0, 'f', 8 );
0574         mDebug() << "No local server found. Launching " << application << " with argument " << applicationArgument;
0575         if ( !QProcess::startDetached( application, QStringList() << applicationArgument ) ) {
0576             QString text = tr( "Unable to start the external editor. Check that %1 is installed or choose a different external editor in the settings dialog." );
0577             text = text.arg( application );
0578             QMessageBox::warning( this, tr( "Cannot start external editor" ), text );
0579         }
0580     }
0581 }
0582 
0583 void ControlView::setExternalMapEditor( const QString &editor )
0584 {
0585     m_externalEditor = editor;
0586 }
0587 
0588 QList<QAction*> ControlView::setupDockWidgets( QMainWindow *mainWindow )
0589 {
0590     Q_ASSERT( !m_searchDock && "Please create dock widgets just once" );
0591 
0592     mainWindow->setTabPosition( Qt::LeftDockWidgetArea, QTabWidget::North );
0593     mainWindow->setTabPosition( Qt::RightDockWidgetArea, QTabWidget::North );
0594 
0595     QDockWidget* legendDock = new QDockWidget( tr( "Legend" ), this );
0596     legendDock->setObjectName( "legendDock" );
0597     legendDock->setAllowedAreas( Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea );
0598     LegendWidget* legendWidget = new LegendWidget( this );
0599     legendWidget->setMarbleModel( m_marbleWidget->model() );
0600     connect( legendWidget, SIGNAL(tourLinkClicked(QString)),
0601              this, SLOT(handleTourLinkClicked(QString)) );
0602     connect( legendWidget, SIGNAL(propertyValueChanged(QString,bool)),
0603              marbleWidget(), SLOT(setPropertyValue(QString,bool)) );
0604     legendDock->setWidget( legendWidget );
0605 
0606     bool const smallScreen = MarbleGlobal::getInstance()->profiles() & MarbleGlobal::SmallScreen;
0607     if ( smallScreen ) {
0608         // Show only the legend as a dock widget on small screen, the others are dialogs
0609         mainWindow->addDockWidget( Qt::LeftDockWidgetArea, legendDock );
0610         return QList<QAction*>() << legendDock->toggleViewAction();
0611     }
0612 
0613     QDockWidget *routingDock = new QDockWidget( tr( "Routing" ), mainWindow );
0614     routingDock->setObjectName( "routingDock" );
0615     routingDock->setAllowedAreas( Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea );
0616     RoutingWidget* routingWidget = new RoutingWidget( marbleWidget(), mainWindow );
0617     routingWidget->setRouteSyncManager( cloudSyncManager()->routeSyncManager() );
0618     routingDock->setWidget( routingWidget );
0619     mainWindow->addDockWidget( Qt::LeftDockWidgetArea, routingDock );
0620 
0621     QDockWidget *locationDock = new QDockWidget( tr( "Location" ), this );
0622     locationDock->setObjectName( "locationDock" );
0623     locationDock->setAllowedAreas( Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea );
0624     m_locationWidget = new CurrentLocationWidget( this );
0625     m_locationWidget->setMarbleWidget( marbleWidget() );
0626     locationDock->setWidget( m_locationWidget );
0627     mainWindow->addDockWidget( Qt::LeftDockWidgetArea, locationDock );
0628 
0629     m_searchDock = new QDockWidget( tr( "Search" ), this );
0630     m_searchDock->setObjectName( "searchDock" );
0631     m_searchDock->setAllowedAreas( Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea );
0632     SearchWidget* searchWidget = new SearchWidget( this );
0633     searchWidget->setMarbleWidget( marbleWidget() );
0634     m_searchDock->setWidget( searchWidget );
0635     mainWindow->addDockWidget( Qt::LeftDockWidgetArea, m_searchDock );
0636 
0637     mainWindow->tabifyDockWidget( m_searchDock, routingDock );
0638     mainWindow->tabifyDockWidget( routingDock, locationDock );
0639     m_searchDock->raise();
0640 
0641     QKeySequence searchSequence( Qt::CTRL | Qt::Key_F );
0642     searchWidget->setToolTip( tr( "Search for cities, addresses, points of interest and more (%1)" ).arg( searchSequence.toString() ) );
0643     QShortcut* searchShortcut = new QShortcut( mainWindow );
0644     connect( searchShortcut, SIGNAL(activated()), this, SLOT(showSearch()) );
0645 
0646     QDockWidget *mapViewDock = new QDockWidget( tr( "Map View" ), this );
0647     mapViewDock->setObjectName( "mapViewDock" );
0648     mapViewDock->setAllowedAreas( Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea );
0649     MapViewWidget* mapViewWidget = new MapViewWidget( this );
0650     mapViewWidget->setMarbleWidget( marbleWidget(), m_mapThemeManager );
0651     connect( mapViewWidget, SIGNAL(showMapWizard()), this, SIGNAL(showMapWizard()) );
0652     connect( mapViewWidget, SIGNAL(mapThemeDeleted()), this, SIGNAL(mapThemeDeleted()) );
0653     mapViewDock->setWidget( mapViewWidget );
0654     mainWindow->addDockWidget( Qt::LeftDockWidgetArea, mapViewDock );
0655 
0656     QDockWidget *fileViewDock = new QDockWidget( tr( "Files" ), this );
0657     fileViewDock->setObjectName( "fileViewDock" );
0658     fileViewDock->setAllowedAreas( Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea );
0659     FileViewWidget* fileViewWidget = new FileViewWidget( this );
0660     fileViewWidget->setMarbleWidget( marbleWidget() );
0661     fileViewDock->setWidget( fileViewWidget );
0662     mainWindow->addDockWidget( Qt::LeftDockWidgetArea, fileViewDock );
0663     fileViewDock->hide();
0664 
0665     QDockWidget *tourDock = new QDockWidget( tr( "Tour" ), this );
0666     tourDock->setObjectName( "tourDock" );
0667     tourDock->setAllowedAreas( Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea );
0668     m_tourWidget = new TourWidget( this );
0669     m_tourWidget->setMarbleWidget( marbleWidget() );
0670     tourDock->setWidget( m_tourWidget );
0671     mainWindow->addDockWidget( Qt::LeftDockWidgetArea, tourDock );
0672     tourDock->hide();
0673 
0674     mainWindow->addDockWidget( Qt::LeftDockWidgetArea, legendDock );
0675     mainWindow->tabifyDockWidget( mapViewDock, legendDock );
0676     mapViewDock->raise();
0677 
0678     m_annotationDock = new QDockWidget( QObject::tr( "Edit Maps" ) );
0679     m_annotationDock->setObjectName( "annotateDock" );
0680     m_annotationDock->hide();
0681     m_annotationDock->toggleViewAction()->setVisible( false );
0682 
0683     QList<RenderPlugin *> renderPluginList = marbleWidget()->renderPlugins();
0684     QList<RenderPlugin *>::const_iterator i = renderPluginList.constBegin();
0685     QList<RenderPlugin *>::const_iterator const end = renderPluginList.constEnd();
0686 
0687     for (; i != end; ++i ) {
0688         if ((*i)->nameId() == QLatin1String("annotation")) {
0689             m_annotationPlugin = *i;
0690             QObject::connect(m_annotationPlugin, SIGNAL(enabledChanged(bool)),
0691                              this, SLOT(updateAnnotationDockVisibility()));
0692             QObject::connect(m_annotationPlugin, SIGNAL(visibilityChanged(bool,QString)),
0693                              this, SLOT(updateAnnotationDockVisibility()));
0694             QObject::connect(m_annotationPlugin, SIGNAL(actionGroupsChanged()),
0695                              this, SLOT(updateAnnotationDock()));
0696             updateAnnotationDock();
0697             updateAnnotationDockVisibility();
0698             mainWindow->addDockWidget( Qt::LeftDockWidgetArea, m_annotationDock );
0699         }
0700     }
0701 
0702     mainWindow->tabifyDockWidget( tourDock, m_annotationDock );
0703     mainWindow->tabifyDockWidget( m_annotationDock, fileViewDock );
0704 
0705     QList<QAction*> panelActions;
0706     panelActions << routingDock->toggleViewAction();
0707     panelActions << locationDock->toggleViewAction();
0708     panelActions << m_searchDock->toggleViewAction();
0709     panelActions << mapViewDock->toggleViewAction();
0710     panelActions << fileViewDock->toggleViewAction();
0711     panelActions << m_annotationDock->toggleViewAction();
0712     panelActions << legendDock->toggleViewAction();
0713     panelActions << tourDock->toggleViewAction();
0714 
0715     // Local list of panel view toggle actions
0716     m_panelActions << routingDock->toggleViewAction();
0717     m_panelActions << locationDock->toggleViewAction();
0718     m_panelActions << m_searchDock->toggleViewAction();
0719     m_panelActions << mapViewDock->toggleViewAction();
0720     m_panelActions << fileViewDock->toggleViewAction();
0721     m_panelActions << m_annotationDock->toggleViewAction();
0722     m_panelActions << legendDock->toggleViewAction();
0723     m_panelActions << tourDock->toggleViewAction();
0724     for( QAction* action: m_panelActions ) {
0725         m_panelVisibility << action->isVisible();
0726     }
0727 
0728     // Create Settings->Panels Menu
0729     // Toggle All Panels action
0730     m_togglePanelVisibilityAction = new QAction( tr("Hide &All Panels"), this);
0731     m_togglePanelVisibilityAction->setShortcut( Qt::Key_F9 );
0732     m_togglePanelVisibilityAction->setStatusTip(tr("Show or hide all panels."));
0733     connect(m_togglePanelVisibilityAction, SIGNAL(triggered()), this, SLOT(togglePanelVisibility()));
0734 
0735     // Include a Separator in the List
0736     QAction *panelSeparatorAct = new QAction( this );
0737     panelSeparatorAct->setSeparator( true );
0738 
0739     // Return a list of panel view actions for Marble Menu including show/hide all
0740     QList<QAction*> panelMenuActions;
0741     panelMenuActions << m_togglePanelVisibilityAction;
0742     panelMenuActions << panelSeparatorAct;
0743     for( QAction* action: m_panelActions ) {
0744         panelMenuActions << action;
0745     }
0746 
0747     return panelMenuActions;
0748 }
0749 
0750 CurrentLocationWidget *ControlView::currentLocationWidget()
0751 {
0752     return m_locationWidget;
0753 }
0754 
0755 void ControlView::setWorkOffline( bool offline )
0756 {
0757     marbleWidget()->model()->setWorkOffline( offline );
0758     if ( !offline ) {
0759         marbleWidget()->clearVolatileTileCache();
0760     }
0761 }
0762 
0763 CloudSyncManager *ControlView::cloudSyncManager()
0764 {
0765     return m_cloudSyncManager;
0766 }
0767 
0768 QString ControlView::externalMapEditor() const
0769 {
0770     return m_externalEditor;
0771 }
0772 
0773 void ControlView::addGeoDataFile( const QString &filename )
0774 {
0775     QFileInfo const file( filename );
0776     if ( file.exists() ) {
0777         m_marbleWidget->model()->addGeoDataFile( file.absoluteFilePath() );
0778     } else {
0779         qWarning() << "File" << filename << "does not exist, cannot open it.";
0780     }
0781 }
0782 
0783 void ControlView::showSearch()
0784 {
0785     if ( !m_searchDock ) {
0786         return;
0787     }
0788 
0789     m_searchDock->show();
0790     m_searchDock->raise();
0791     m_searchDock->widget()->setFocus();
0792 }
0793 
0794 void ControlView::showConflictDialog( MergeItem *item )
0795 {
0796     Q_ASSERT( m_conflictDialog );
0797     m_conflictDialog->setMergeItem( item );
0798     m_conflictDialog->open();
0799 }
0800 
0801 void ControlView::updateAnnotationDockVisibility()
0802 {
0803     if( m_annotationPlugin != nullptr && m_annotationDock != nullptr ) {
0804         if( m_annotationPlugin->visible() && m_annotationPlugin->enabled() ) {
0805             m_annotationDock->toggleViewAction()->setVisible( true );
0806         } else {
0807             m_annotationDock->setVisible( false );
0808             m_annotationDock->toggleViewAction()->setVisible( false );
0809         }
0810     }
0811 }
0812 
0813 void ControlView::updateAnnotationDock()
0814 {
0815     const QList<QActionGroup*> *tmp_actionGroups = m_annotationPlugin->actionGroups();
0816     QWidget *widget = new QWidget( m_annotationDock );
0817     QVBoxLayout *layout = new QVBoxLayout;
0818     QToolBar *firstToolbar = new QToolBar( widget );
0819     QToolBar *secondToolbar = new QToolBar( widget );
0820     QSpacerItem *spacer = new QSpacerItem( 0, 0, QSizePolicy::Expanding, QSizePolicy::Expanding);
0821     if( !tmp_actionGroups->isEmpty() ) {
0822         bool firstToolbarFilled = false;
0823         for( QAction *action: tmp_actionGroups->first()->actions() ) {
0824             if (action->objectName() == QLatin1String("toolbarSeparator")) {
0825                 firstToolbarFilled = true;
0826             } else {
0827                 if( !firstToolbarFilled ) {
0828                     firstToolbar->addAction( action );
0829                 } else {
0830                     secondToolbar->addAction( action );
0831                 }
0832             }
0833         }
0834     }
0835     layout->addWidget( firstToolbar );
0836     layout->addWidget( secondToolbar );
0837     layout->addSpacerItem( spacer );
0838     widget->setLayout( layout );
0839     m_annotationDock->setWidget( widget );
0840 }
0841 
0842 void ControlView::togglePanelVisibility()
0843 {
0844     Q_ASSERT( m_panelVisibility.size() == m_panelActions.size() );
0845     if ( m_isPanelVisible ) {
0846         for( int p=0; p<m_panelActions.size(); ++p ) {
0847             // Save state of individual dock visibility
0848             m_panelVisibility[p] = m_panelActions.at(p)->isChecked();
0849 
0850             // hide panel if it is showing
0851             if ( m_panelActions.at(p)->isChecked() ) {
0852                 m_panelActions.at(p)->activate( QAction::Trigger );
0853             }
0854         }
0855 
0856         // Change Menu Item Text
0857         m_togglePanelVisibilityAction->setText( tr("Show &All Panels") );
0858         m_isPanelVisible = false;
0859     } else {
0860         for( int p=0; p<m_panelActions.size(); ++p ) {
0861             // show panel if it was showing before all panels were hidden
0862             if ( m_panelVisibility.at(p) && !m_panelActions.at(p)->isChecked() ) {
0863                 m_panelActions.at(p)->activate( QAction::Trigger );
0864             }
0865         }
0866 
0867         // Change Menu Item Text
0868         m_togglePanelVisibilityAction->setText( tr("Hide &All Panels") );
0869         m_isPanelVisible = true;
0870     }
0871 }
0872 
0873 void ControlView::handleTourLinkClicked(const QString& path)
0874 {
0875     QString tourPath = MarbleDirs::path( path );
0876     if ( !tourPath.isEmpty() ) {
0877         openTour( tourPath );
0878     }
0879 }
0880 
0881 void ControlView::openTour( const QString &filename )
0882 {
0883     if ( m_tourWidget->openTour( filename ) ) {
0884         m_tourWidget->startPlaying();
0885     }
0886 }
0887 
0888 void ControlView::closeEvent( QCloseEvent *event )
0889 {
0890     QCloseEvent newEvent;
0891     QCoreApplication::sendEvent( m_tourWidget, &newEvent );
0892 
0893     if ( newEvent.isAccepted() ) {
0894         event->accept();
0895     } else {
0896         event->ignore();
0897     }
0898 }
0899 
0900 void ControlView::dragEnterEvent(QDragEnterEvent *event)
0901 {
0902     bool success = false;
0903 
0904     const QMimeData *mimeData = event->mimeData();
0905 
0906     GeoUriParser uriParser;
0907 
0908     // prefer urls
0909     if (mimeData->hasUrls()) {
0910         // be generous and take the first usable url
0911         for(const QUrl& url: mimeData->urls()) {
0912             uriParser.setGeoUri(url.url());
0913             success = uriParser.parse();
0914             if (success) {
0915                 break;
0916             }
0917         }
0918     }
0919 
0920     // fall back to own string parsing
0921     if (!success && mimeData->hasText()) {
0922         const QString text = mimeData->text();
0923         // first try human readable coordinates
0924         GeoDataCoordinates::fromString(text, success);
0925         // next geo uri
0926         if (!success) {
0927             uriParser.setGeoUri(text);
0928             success = uriParser.parse();
0929         }
0930     }
0931 
0932     if (success) {
0933         event->acceptProposedAction();
0934     }
0935 }
0936 
0937 void ControlView::dropEvent(QDropEvent *event)
0938 {
0939     bool success = false;
0940 
0941     const QMimeData *mimeData = event->mimeData();
0942 
0943     // prefer urls
0944     if (mimeData->hasUrls()) {
0945         // be generous and take the first usable url
0946         for(const QUrl& url: mimeData->urls()) {
0947             success = openGeoUri(url.url());
0948             if (success) {
0949                 break;
0950             }
0951         }
0952     }
0953 
0954     // fall back to own string parsing
0955     if (!success && mimeData->hasText()) {
0956         const QString text = mimeData->text();
0957         // first try human readable coordinates
0958         const GeoDataCoordinates coordinates = GeoDataCoordinates::fromString(text, success);
0959         if (success) {
0960             const qreal longitude = coordinates.longitude(GeoDataCoordinates::Degree);
0961             const qreal latitude = coordinates.latitude(GeoDataCoordinates::Degree);
0962             m_marbleWidget->centerOn(longitude, latitude);
0963         } else {
0964             success = openGeoUri(text);
0965         }
0966     }
0967     if (success) {
0968         event->acceptProposedAction();
0969     }
0970 }
0971 
0972 }
0973 
0974 #include "moc_ControlView.cpp"