1 // SPDX-License-Identifier: LGPL-2.1-or-later
2 //
3 // SPDX-FileCopyrightText: 2006-2007 Torsten Rahn <tackat@kde.org>
4 // SPDX-FileCopyrightText: 2007 Inge Wallin <ingwa@kde.org>
5 //
6 
7 
8 #include "ControlView.h"
9 
10 #include <QCloseEvent>
11 #include <QLayout>
12 #include <QPrintDialog>
13 #include <QPrintPreviewDialog>
14 #include <QPrinter>
15 #include <QPainter>
16 #include <QTextDocument>
17 #include <QUrl>
18 #include <QDesktopServices>
19 #include <QNetworkAccessManager>
20 #include <QNetworkReply>
21 #include <QNetworkRequest>
22 #include <QProcess>
23 #include <QTimer>
24 #include <QFileInfo>
25 #include <QMessageBox>
26 #include <QMainWindow>
27 #include <QDockWidget>
28 #include <QShortcut>
29 #include <QToolBar>
30 #include <QMimeData>
31 #include <QPixmap>
32 
33 #ifdef MARBLE_DBUS
34 #include <QDBusConnection>
35 #include "MarbleDBusInterface.h"
36 #endif
37 
38 #include "GeoDataLatLonAltBox.h"
39 #include "GeoSceneDocument.h"
40 #include "GeoSceneHead.h"
41 #include "GeoUriParser.h"
42 #include "MarbleDebug.h"
43 #include "MarbleDirs.h"
44 #include "MarbleModel.h"
45 #include "MarbleMap.h"
46 #include "MapThemeManager.h"
47 #include "PrintOptionsWidget.h"
48 #include "ViewportParams.h"
49 #include "ViewParams.h"
50 #include "routing/Route.h"
51 #include "routing/RoutingManager.h"
52 #include "routing/RoutingModel.h"
53 #include "routing/RouteRequest.h"
54 #include "routing/RoutingWidget.h"
55 #include "ExternalEditorDialog.h"
56 #include "CurrentLocationWidget.h"
57 #include "SearchWidget.h"
58 #include "TourWidget.h"
59 #include "MapViewWidget.h"
60 #include "FileViewWidget.h"
61 #include "LegendWidget.h"
62 #include "BookmarkManager.h"
63 #include "cloudsync/CloudSyncManager.h"
64 #include "cloudsync/BookmarkSyncManager.h"
65 #include "cloudsync/RouteSyncManager.h"
66 #include "cloudsync/ConflictDialog.h"
67 #include "cloudsync/MergeItem.h"
68 #include "RenderPlugin.h"
69 
70 namespace Marble
71 {
72 
ControlView(QWidget * parent)73 ControlView::ControlView( QWidget *parent )
74    : QWidget( parent ),
75      m_mapThemeManager( new MapThemeManager( this ) ),
76      m_searchDock( nullptr ),
77      m_locationWidget( nullptr ),
78      m_conflictDialog( nullptr ),
79      m_togglePanelVisibilityAction( nullptr ),
80      m_isPanelVisible( true ),
81      m_tourWidget( nullptr ),
82      m_annotationDock( nullptr ),
83      m_annotationPlugin( nullptr )
84 {
85     setWindowTitle( tr( "Marble - Virtual Globe" ) );
86 
87     resize( 680, 640 );
88 
89     m_marbleWidget = new MarbleWidget( this );
90     m_marbleWidget->setSizePolicy( QSizePolicy( QSizePolicy::MinimumExpanding,
91                                                 QSizePolicy::MinimumExpanding ) );
92 #ifdef MARBLE_DBUS
93     new MarbleDBusInterface( m_marbleWidget );
94     QDBusConnection::sessionBus().registerObject( "/Marble", m_marbleWidget );
95     if (!QDBusConnection::sessionBus().registerService( "org.kde.marble" )) {
96         QString const urlWithPid = QString("org.kde.marble-%1").arg( QCoreApplication::applicationPid() );
97         if ( !QDBusConnection::sessionBus().registerService( urlWithPid ) ) {
98             mDebug() << "Failed to register service org.kde.marble and " << urlWithPid << " with the DBus session bus.";
99         }
100     }
101 #endif
102 
103 
104     QVBoxLayout* layout = new QVBoxLayout;
105     layout->addWidget( m_marbleWidget );
106     layout->setMargin( 0 );
107     setLayout( layout );
108 
109     m_cloudSyncManager = new CloudSyncManager( this );
110     m_cloudSyncManager->routeSyncManager()->setRoutingManager( m_marbleWidget->model()->routingManager() );
111     BookmarkSyncManager* bookmarkSyncManager = m_cloudSyncManager->bookmarkSyncManager();
112     bookmarkSyncManager->setBookmarkManager( m_marbleWidget->model()->bookmarkManager() );
113     m_conflictDialog = new ConflictDialog( m_marbleWidget );
114     connect( bookmarkSyncManager, SIGNAL(mergeConflict(MergeItem*)), this, SLOT(showConflictDialog(MergeItem*)) );
115     connect( bookmarkSyncManager, SIGNAL(syncComplete()), m_conflictDialog, SLOT(stopAutoResolve()) );
116     connect( m_conflictDialog, SIGNAL(resolveConflict(MergeItem*)), bookmarkSyncManager, SLOT(resolveConflict(MergeItem*)) );
117 
118     setAcceptDrops(true);
119 }
120 
~ControlView()121 ControlView::~ControlView()
122 {
123     // nothing to do
124 }
125 
applicationVersion()126 QString ControlView::applicationVersion()
127 {
128     return "2.2.20 (2.3 development version)";
129 }
130 
mapThemeManager()131 MapThemeManager *ControlView::mapThemeManager()
132 {
133     return m_mapThemeManager;
134 }
135 
zoomIn()136 void ControlView::zoomIn()
137 {
138     m_marbleWidget->zoomIn();
139 }
140 
zoomOut()141 void ControlView::zoomOut()
142 {
143     m_marbleWidget->zoomOut();
144 }
145 
moveLeft()146 void ControlView::moveLeft()
147 {
148     m_marbleWidget->moveLeft();
149 }
150 
moveRight()151 void ControlView::moveRight()
152 {
153     m_marbleWidget->moveRight();
154 }
155 
moveUp()156 void ControlView::moveUp()
157 {
158     m_marbleWidget->moveUp();
159 }
160 
moveDown()161 void ControlView::moveDown()
162 {
163     m_marbleWidget->moveDown();
164 }
165 
defaultMapThemeId() const166 QString ControlView::defaultMapThemeId() const
167 {
168     QStringList fallBackThemes;
169       fallBackThemes << "earth/srtm/srtm.dgml";
170       fallBackThemes << "earth/bluemarble/bluemarble.dgml";
171       fallBackThemes << "earth/openstreetmap/openstreetmap.dgml";
172 
173     const QStringList installedThemes = m_mapThemeManager->mapThemeIds();
174 
175     for(const QString &fallback: fallBackThemes) {
176         if (installedThemes.contains(fallback)) {
177             return fallback;
178         }
179     }
180 
181     if (installedThemes.size()) {
182         return installedThemes.first();
183     }
184 
185     return QString();
186 }
187 
printMapScreenShot(const QPointer<QPrintDialog> & printDialog)188 void ControlView::printMapScreenShot( const QPointer<QPrintDialog>& printDialog)
189 {
190 #ifndef QT_NO_PRINTER
191         PrintOptionsWidget* printOptions = new PrintOptionsWidget( this );
192         bool const mapCoversViewport = m_marbleWidget->viewport()->mapCoversViewport();
193         printOptions->setBackgroundControlsEnabled( !mapCoversViewport );
194         bool hasLegend = m_marbleWidget->model()->legend() != nullptr;
195         printOptions->setLegendControlsEnabled( hasLegend );
196         bool hasRoute = marbleWidget()->model()->routingManager()->routingModel()->rowCount() > 0;
197         printOptions->setPrintRouteSummary( hasRoute );
198         printOptions->setPrintDrivingInstructions( hasRoute );
199         printOptions->setPrintDrivingInstructionsAdvice( hasRoute );
200         printOptions->setRouteControlsEnabled( hasRoute );
201         printDialog->setOptionTabs( QList<QWidget*>() << printOptions );
202 
203         if ( printDialog->exec() == QDialog::Accepted ) {
204             QTextDocument document;
205             QString text = "<html><head><title>Marble Printout</title></head><body>";
206             QPalette const originalPalette = m_marbleWidget->palette();
207             bool const wasBackgroundVisible = m_marbleWidget->showBackground();
208             bool const hideBackground = !mapCoversViewport && !printOptions->printBackground();
209             if ( hideBackground ) {
210                 // Temporarily remove the black background and layers painting on it
211                 m_marbleWidget->setShowBackground( false );
212                 m_marbleWidget->setPalette( QPalette ( Qt::white ) );
213                 m_marbleWidget->update();
214             }
215 
216             if ( printOptions->printMap() ) {
217                 printMap( document, text, printDialog->printer() );
218             }
219 
220             if ( printOptions->printLegend() ) {
221                 printLegend( document, text );
222             }
223 
224             if ( printOptions->printRouteSummary() ) {
225                 printRouteSummary( document, text );
226             }
227 
228             if ( printOptions->printDrivingInstructions() ) {
229                 printDrivingInstructions( document, text );
230             }
231 
232             if ( printOptions->printDrivingInstructionsAdvice() ) {
233                 printDrivingInstructionsAdvice( document, text );
234             }
235 
236             text += QLatin1String("</body></html>");
237             document.setHtml( text );
238             document.print( printDialog->printer() );
239 
240             if ( hideBackground ) {
241                 m_marbleWidget->setShowBackground( wasBackgroundVisible );
242                 m_marbleWidget->setPalette( originalPalette );
243                 m_marbleWidget->update();
244             }
245     }
246 #endif
247 }
248 
openGeoUri(const QString & geoUriString)249 bool ControlView::openGeoUri( const QString& geoUriString )
250 {
251     GeoUriParser uriParser( geoUriString );
252     const bool success = uriParser.parse();
253     if ( success ) {
254         if ( uriParser.planet().id() != marbleModel()->planet()->id() ) {
255             MapThemeManager *manager = mapThemeManager();
256             for( const QString& planetName: manager->mapThemeIds()) {
257                 if ( planetName.startsWith(uriParser.planet().id(), Qt::CaseInsensitive)) {
258                     m_marbleWidget->setMapThemeId(planetName);
259                     break;
260                 }
261             }
262         }
263         m_marbleWidget->centerOn( uriParser.coordinates() );
264         if ( uriParser.coordinates().altitude() > 0.0 )
265         {
266             m_marbleWidget->setDistance( uriParser.coordinates().altitude() * METER2KM );
267         }
268     }
269     return success;
270 }
271 
createViewSizeActionGroup(QObject * parent)272 QActionGroup *ControlView::createViewSizeActionGroup( QObject* parent )
273 {
274     QActionGroup* actionGroup = new QActionGroup( parent );
275 
276     QAction *defaultAction = new QAction( tr( "Default (Resizable)" ), parent );
277     defaultAction->setCheckable( true );
278     defaultAction->setChecked( true );
279     actionGroup->addAction(defaultAction);
280 
281     QAction *separator = new QAction( parent );
282     separator->setSeparator( true );
283     actionGroup->addAction(separator);
284 
285     addViewSizeAction( actionGroup, tr("NTSC (%1x%2)"), 720, 486 );
286     addViewSizeAction( actionGroup, tr("PAL (%1x%2)"), 720, 576 );
287     addViewSizeAction( actionGroup, tr("NTSC 16:9 (%1x%2)"), 864, 486 );
288     addViewSizeAction( actionGroup, tr("PAL 16:9 (%1x%2)"), 1024, 576 );
289     // xgettext:no-c-format
290     addViewSizeAction( actionGroup, tr("DVD (%1x%2p)"), 852, 480 );
291     // xgettext:no-c-format
292     addViewSizeAction( actionGroup, tr("HD (%1x%2p)"), 1280, 720 );
293     // xgettext:no-c-format
294     addViewSizeAction( actionGroup, tr("Full HD (%1x%2p)"), 1920, 1080 );
295     addViewSizeAction( actionGroup, tr("Digital Cinema (%1x%2)"), 2048, 1536 );
296     /** FIXME: Needs testing, worked with errors.
297     addViewSizeAction(actionGroup, "4K UHD (%1x%2)", 3840, 2160);
298     addViewSizeAction(actionGroup, "4K (%1x%2)", 4096, 3072);
299     */
300 
301     return actionGroup;
302 }
303 
printPixmap(QPrinter * printer,const QPixmap & pixmap)304 void ControlView::printPixmap( QPrinter * printer, const QPixmap& pixmap  )
305 {
306 #ifndef QT_NO_PRINTER
307     QSize printSize = pixmap.size();
308     QRect mapPageRect = printer->pageRect();
309     printSize.scale( printer->pageRect().size(), Qt::KeepAspectRatio );
310     QPoint printTopLeft( ( mapPageRect.width() - printSize.width() ) / 2 ,
311                          ( mapPageRect.height() - printSize.height() ) / 2 );
312     QRect mapPrintRect( printTopLeft, printSize );
313 
314     QPainter painter;
315     if (!painter.begin(printer))
316         return;
317     painter.drawPixmap( mapPrintRect, pixmap, pixmap.rect() );
318     painter.end();
319 #endif
320 }
321 
322 // 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
printPreview()323 void ControlView::printPreview()
324 {
325 #ifndef QT_NO_PRINTER
326     QPrinter printer( QPrinter::HighResolution );
327 
328     QPointer<QPrintPreviewDialog> preview = new QPrintPreviewDialog( &printer, this );
329     preview->setWindowFlags ( Qt::Window );
330     preview->resize(640, 480);
331     connect( preview, SIGNAL(paintRequested(QPrinter*)), SLOT(paintPrintPreview(QPrinter*)) );
332     preview->exec();
333     delete preview;
334 #endif
335 }
336 
paintPrintPreview(QPrinter * printer)337 void ControlView::paintPrintPreview( QPrinter * printer )
338 {
339 #ifndef QT_NO_PRINTER
340     QPixmap mapPixmap = mapScreenShot();
341     printPixmap( printer, mapPixmap );
342 #endif
343 }
344 
printMap(QTextDocument & document,QString & text,QPrinter * printer)345 void ControlView::printMap( QTextDocument &document, QString &text, QPrinter *printer )
346 {
347 #ifndef QT_NO_PRINTER
348     QPixmap image = mapScreenShot();
349 
350     if ( m_marbleWidget->viewport()->mapCoversViewport() ) {
351         // Paint a black frame. Looks better.
352         QPainter painter(&image);
353         painter.setPen( Qt::black );
354         painter.drawRect( 0, 0, image.width() - 2, image.height() - 2 );
355     }
356 
357     QString uri = "marble://screenshot.png";
358     document.addResource( QTextDocument::ImageResource, QUrl( uri ), QVariant( image) );
359     QString img = "<img src=\"%1\" width=\"%2\" align=\"center\">";
360     int width = qRound( printer->pageRect( QPrinter::Point ).width() );
361     text += img.arg( uri ).arg( width );
362 #endif
363 }
364 
printLegend(QTextDocument & document,QString & text)365 void ControlView::printLegend( QTextDocument &document, QString &text )
366 {
367 #ifndef QT_NO_PRINTER
368     QTextDocument *legend = m_marbleWidget->model()->legend();
369     if ( legend ) {
370         legend->adjustSize();
371         QSize size = legend->size().toSize();
372         QSize imageSize = size + QSize( 4, 4 );
373         QImage image( imageSize, QImage::Format_ARGB32);
374         QPainter painter( &image );
375         painter.setRenderHint( QPainter::Antialiasing, true );
376         painter.drawRoundedRect( QRect( QPoint( 0, 0 ), size ), 5, 5 );
377         legend->drawContents( &painter );
378         document.addResource( QTextDocument::ImageResource, QUrl( "marble://legend.png" ), QVariant(image) );
379         QString img = "<p><img src=\"%1\" align=\"center\"></p>";
380         text += img.arg( "marble://legend.png" );
381     }
382 #endif
383 }
384 
printRouteSummary(QTextDocument & document,QString & text)385 void ControlView::printRouteSummary( QTextDocument &document, QString &text)
386 {
387 #ifndef QT_NO_PRINTER
388     RoutingModel* routingModel = m_marbleWidget->model()->routingManager()->routingModel();
389 
390     if ( !routingModel ) {
391         return;
392     }
393 
394     RouteRequest* routeRequest = m_marbleWidget->model()->routingManager()->routeRequest();
395     if ( routeRequest ) {
396         QString summary = "<h3>Route to %1: %2 %3</h3>";
397         QString destination;
398         if ( routeRequest->size() ) {
399             destination = routeRequest->name( routeRequest->size()-1 );
400         }
401 
402         QString label = "<p>%1 %2</p>";
403         qreal distance = routingModel->route().distance();
404         QString unit = distance > 1000 ? "km" : "m";
405         int precision = distance > 1000 ? 1 : 0;
406         if ( distance > 1000 ) {
407             distance /= 1000;
408         }
409         summary = summary.arg(destination).arg( distance, 0, 'f', precision ).arg( unit );
410         text += summary;
411 
412         text += QLatin1String("<table cellpadding=\"2\">");
413         QString pixmapTemplate = "marble://viaPoint-%1.png";
414         for ( int i=0; i<routeRequest->size(); ++i ) {
415             text += QLatin1String("<tr><td>");
416             QPixmap pixmap = routeRequest->pixmap(i);
417             QString pixmapResource = pixmapTemplate.arg( i );
418             document.addResource(QTextDocument::ImageResource,
419                                           QUrl( pixmapResource ), QVariant( pixmap ) );
420             QString myimg = "<img src=\"%1\">";
421             text += myimg.arg(pixmapResource) +
422                     QLatin1String("</td><td>");
423                     routeRequest->name(i) +
424                     QLatin1String("</td></tr>");
425         }
426         text += QLatin1String("</table>");
427     }
428 #endif
429 }
430 
printDrivingInstructions(QTextDocument & document,QString & text)431 void ControlView::printDrivingInstructions( QTextDocument &document, QString &text )
432 {
433 #ifndef QT_NO_PRINTER
434     RoutingModel* routingModel = m_marbleWidget->model()->routingManager()->routingModel();
435 
436     if (!routingModel) {
437         return;
438     }
439 
440     GeoDataLineString total = routingModel->route().path();
441 
442     text += QLatin1String("<table cellpadding=\"4\">"
443             "<tr><th>No.</th><th>Distance</th><th>Instruction</th></tr>");
444     for ( int i=0; i<routingModel->rowCount(); ++i ) {
445         QModelIndex index = routingModel->index(i, 0);
446         GeoDataCoordinates coordinates = index.data( RoutingModel::CoordinateRole ).value<GeoDataCoordinates>();
447         GeoDataLineString accumulator;
448         for (int k=0; k<total.size(); ++k) {
449             accumulator << total.at(k);
450 
451             if (total.at(k) == coordinates)
452                 break;
453         }
454 
455         if ( i%2 == 0 ) {
456             text += QLatin1String("<tr bgcolor=\"lightGray\"><td align=\"right\" valign=\"middle\">");
457         }
458         else {
459             text += QLatin1String("<tr><td align=\"right\" valign=\"middle\">");
460         }
461         text += QString::number(i+1) +
462                 QLatin1String("</td><td align=\"right\" valign=\"middle\">");
463 
464         qreal planetRadius = marbleModel()->planet()->radius();
465         text += QString::number(accumulator.length(planetRadius) * METER2KM, 'f', 1) +
466                 /** @todo: support localization */
467                 QLatin1String(" km</td><td valign=\"middle\">");
468 
469         QPixmap instructionIcon = index.data( Qt::DecorationRole ).value<QPixmap>();
470         if ( !instructionIcon.isNull() ) {
471             QString uri = QString("marble://turnIcon%1.png").arg(i);
472             document.addResource( QTextDocument::ImageResource, QUrl( uri ), QVariant( instructionIcon ) );
473             text += QString("<img src=\"%1\">").arg(uri);
474         }
475 
476         text += routingModel->data( index ).toString() +
477                 QLatin1String("</td></tr>");
478     }
479     text += QLatin1String("</table>");
480 #endif
481 }
482 
printDrivingInstructionsAdvice(QTextDocument &,QString & text)483 void ControlView::printDrivingInstructionsAdvice( QTextDocument &, QString &text )
484 {
485 #ifndef QT_NO_PRINTER
486     text += QLatin1String("<p>") + tr("The Marble development team wishes you a pleasant and safe journey.") + QLatin1String("</p>") +
487             QLatin1String("<p>") + tr("Caution: Driving instructions may be incomplete or inaccurate.") +
488             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.") +
489             QLatin1Char(' ') + tr("Please use common sense while navigating.") + QLatin1String("</p>");
490 #endif
491 }
492 
addViewSizeAction(QActionGroup * actionGroup,const QString & nameTemplate,int width,int height)493 void ControlView::addViewSizeAction( QActionGroup* actionGroup, const QString &nameTemplate, int width, int height )
494 {
495     QString const name = nameTemplate.arg( width ).arg( height );
496     QAction *action = new QAction( name, actionGroup->parent() );
497     action->setCheckable( true );
498     action->setData( QSize( width, height ) );
499     actionGroup->addAction( action );
500 }
501 
502 
launchExternalMapEditor()503 void ControlView::launchExternalMapEditor()
504 {
505     QString editor = m_externalEditor;
506     if ( editor.isEmpty() ) {
507         QPointer<ExternalEditorDialog> dialog = new ExternalEditorDialog( this );
508         if( dialog->exec() == QDialog::Accepted ) {
509             editor = dialog->externalEditor();
510             if ( dialog->saveDefault() ) {
511                 m_externalEditor = editor;
512             }
513         } else {
514             return;
515         }
516     }
517 
518     if (editor == QLatin1String("josm")) {
519         // JOSM, the java based editor
520         synchronizeWithExternalMapEditor( editor, "--download=%1,%4,%3,%2" );
521     }
522     else if (editor == QLatin1String("merkaartor")) {
523         // Merkaartor, a Qt based editor
524         QString argument = "osm://download/load_and_zoom?top=%1&right=%2&bottom=%3&left=%4";
525         synchronizeWithExternalMapEditor( editor, argument );
526     }
527     else {
528         // Potlatch, the flash based editor running at the osm main website
529         QString url = "http://www.openstreetmap.org/edit?lat=%1&lon=%2&zoom=%3";
530         qreal lat = m_marbleWidget->centerLatitude();
531         qreal lon = m_marbleWidget->centerLongitude();
532         int zoom = m_marbleWidget->tileZoomLevel();
533         url = url.arg( lat, 0, 'f', 8 ).arg( lon, 0, 'f', 8 ).arg( zoom );
534         QDesktopServices::openUrl( QUrl(url) );
535     }
536 }
537 
synchronizeWithExternalMapEditor(const QString & application,const QString & argument)538 void ControlView::synchronizeWithExternalMapEditor( const QString &application, const QString &argument )
539 {
540     QTimer watchdog; // terminates network connection after a short timeout
541     watchdog.setSingleShot( true );
542     QEventLoop localEventLoop;
543     connect( &watchdog, SIGNAL(timeout()), &localEventLoop, SLOT(quit()) );
544     QNetworkAccessManager manager;
545     connect( &manager, SIGNAL(finished(QNetworkReply*)), &localEventLoop, SLOT(quit()) );
546 
547     // Wait at most two seconds for the local server to respond
548     QNetworkReply *reply = manager.get( QNetworkRequest( QUrl( "http://localhost:8111/") ) );
549     watchdog.start( 2000 );
550     localEventLoop.exec();
551 
552     GeoDataLatLonAltBox box = m_marbleWidget->viewport()->viewLatLonAltBox();
553     qreal north = box.north( GeoDataCoordinates::Degree );
554     qreal east  = box.east( GeoDataCoordinates::Degree );
555     qreal south = box.south( GeoDataCoordinates::Degree );
556     qreal west  = box.west( GeoDataCoordinates::Degree );
557 
558     if( watchdog.isActive() && reply->bytesAvailable() > 0 ) {
559         // The local server is alive. Tell it to download the current region
560         watchdog.stop();
561         QString serverUrl = "http://localhost:8111/load_and_zoom?top=%1&right=%2&bottom=%3&left=%4";
562         serverUrl = serverUrl.arg( north, 0, 'f', 8 ).arg( east, 0, 'f', 8 );
563         serverUrl = serverUrl.arg( south, 0, 'f', 8 ).arg( west, 0, 'f', 8 );
564         mDebug() << "Connecting to local server URL " << serverUrl;
565         manager.get( QNetworkRequest( QUrl( serverUrl ) ) );
566 
567         // Give it five seconds to process the request
568         watchdog.start( 5000 );
569         localEventLoop.exec();
570     } else {
571         // The local server is not alive. Start the application
572         QString applicationArgument = argument.arg( south, 0, 'f', 8 ).arg( east, 0, 'f', 8 );
573         applicationArgument = applicationArgument.arg( north, 0, 'f', 8 ).arg( west, 0, 'f', 8 );
574         mDebug() << "No local server found. Launching " << application << " with argument " << applicationArgument;
575         if ( !QProcess::startDetached( application, QStringList() << applicationArgument ) ) {
576             QString text = tr( "Unable to start the external editor. Check that %1 is installed or choose a different external editor in the settings dialog." );
577             text = text.arg( application );
578             QMessageBox::warning( this, tr( "Cannot start external editor" ), text );
579         }
580     }
581 }
582 
setExternalMapEditor(const QString & editor)583 void ControlView::setExternalMapEditor( const QString &editor )
584 {
585     m_externalEditor = editor;
586 }
587 
setupDockWidgets(QMainWindow * mainWindow)588 QList<QAction*> ControlView::setupDockWidgets( QMainWindow *mainWindow )
589 {
590     Q_ASSERT( !m_searchDock && "Please create dock widgets just once" );
591 
592     mainWindow->setTabPosition( Qt::LeftDockWidgetArea, QTabWidget::North );
593     mainWindow->setTabPosition( Qt::RightDockWidgetArea, QTabWidget::North );
594 
595     QDockWidget* legendDock = new QDockWidget( tr( "Legend" ), this );
596     legendDock->setObjectName( "legendDock" );
597     legendDock->setAllowedAreas( Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea );
598     LegendWidget* legendWidget = new LegendWidget( this );
599     legendWidget->setMarbleModel( m_marbleWidget->model() );
600     connect( legendWidget, SIGNAL(tourLinkClicked(QString)),
601              this, SLOT(handleTourLinkClicked(QString)) );
602     connect( legendWidget, SIGNAL(propertyValueChanged(QString,bool)),
603              marbleWidget(), SLOT(setPropertyValue(QString,bool)) );
604     legendDock->setWidget( legendWidget );
605 
606     bool const smallScreen = MarbleGlobal::getInstance()->profiles() & MarbleGlobal::SmallScreen;
607     if ( smallScreen ) {
608         // Show only the legend as a dock widget on small screen, the others are dialogs
609         mainWindow->addDockWidget( Qt::LeftDockWidgetArea, legendDock );
610         return QList<QAction*>() << legendDock->toggleViewAction();
611     }
612 
613     QDockWidget *routingDock = new QDockWidget( tr( "Routing" ), mainWindow );
614     routingDock->setObjectName( "routingDock" );
615     routingDock->setAllowedAreas( Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea );
616     RoutingWidget* routingWidget = new RoutingWidget( marbleWidget(), mainWindow );
617     routingWidget->setRouteSyncManager( cloudSyncManager()->routeSyncManager() );
618     routingDock->setWidget( routingWidget );
619     mainWindow->addDockWidget( Qt::LeftDockWidgetArea, routingDock );
620 
621     QDockWidget *locationDock = new QDockWidget( tr( "Location" ), this );
622     locationDock->setObjectName( "locationDock" );
623     locationDock->setAllowedAreas( Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea );
624     m_locationWidget = new CurrentLocationWidget( this );
625     m_locationWidget->setMarbleWidget( marbleWidget() );
626     locationDock->setWidget( m_locationWidget );
627     mainWindow->addDockWidget( Qt::LeftDockWidgetArea, locationDock );
628 
629     m_searchDock = new QDockWidget( tr( "Search" ), this );
630     m_searchDock->setObjectName( "searchDock" );
631     m_searchDock->setAllowedAreas( Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea );
632     SearchWidget* searchWidget = new SearchWidget( this );
633     searchWidget->setMarbleWidget( marbleWidget() );
634     m_searchDock->setWidget( searchWidget );
635     mainWindow->addDockWidget( Qt::LeftDockWidgetArea, m_searchDock );
636 
637     mainWindow->tabifyDockWidget( m_searchDock, routingDock );
638     mainWindow->tabifyDockWidget( routingDock, locationDock );
639     m_searchDock->raise();
640 
641     QKeySequence searchSequence( Qt::CTRL | Qt::Key_F );
642     searchWidget->setToolTip( tr( "Search for cities, addresses, points of interest and more (%1)" ).arg( searchSequence.toString() ) );
643     QShortcut* searchShortcut = new QShortcut( mainWindow );
644     connect( searchShortcut, SIGNAL(activated()), this, SLOT(showSearch()) );
645 
646     QDockWidget *mapViewDock = new QDockWidget( tr( "Map View" ), this );
647     mapViewDock->setObjectName( "mapViewDock" );
648     mapViewDock->setAllowedAreas( Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea );
649     MapViewWidget* mapViewWidget = new MapViewWidget( this );
650     mapViewWidget->setMarbleWidget( marbleWidget(), m_mapThemeManager );
651     connect( mapViewWidget, SIGNAL(showMapWizard()), this, SIGNAL(showMapWizard()) );
652     connect( mapViewWidget, SIGNAL(mapThemeDeleted()), this, SIGNAL(mapThemeDeleted()) );
653     mapViewDock->setWidget( mapViewWidget );
654     mainWindow->addDockWidget( Qt::LeftDockWidgetArea, mapViewDock );
655 
656     QDockWidget *fileViewDock = new QDockWidget( tr( "Files" ), this );
657     fileViewDock->setObjectName( "fileViewDock" );
658     fileViewDock->setAllowedAreas( Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea );
659     FileViewWidget* fileViewWidget = new FileViewWidget( this );
660     fileViewWidget->setMarbleWidget( marbleWidget() );
661     fileViewDock->setWidget( fileViewWidget );
662     mainWindow->addDockWidget( Qt::LeftDockWidgetArea, fileViewDock );
663     fileViewDock->hide();
664 
665     QDockWidget *tourDock = new QDockWidget( tr( "Tour" ), this );
666     tourDock->setObjectName( "tourDock" );
667     tourDock->setAllowedAreas( Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea );
668     m_tourWidget = new TourWidget( this );
669     m_tourWidget->setMarbleWidget( marbleWidget() );
670     tourDock->setWidget( m_tourWidget );
671     mainWindow->addDockWidget( Qt::LeftDockWidgetArea, tourDock );
672     tourDock->hide();
673 
674     mainWindow->addDockWidget( Qt::LeftDockWidgetArea, legendDock );
675     mainWindow->tabifyDockWidget( mapViewDock, legendDock );
676     mapViewDock->raise();
677 
678     m_annotationDock = new QDockWidget( QObject::tr( "Edit Maps" ) );
679     m_annotationDock->setObjectName( "annotateDock" );
680     m_annotationDock->hide();
681     m_annotationDock->toggleViewAction()->setVisible( false );
682 
683     QList<RenderPlugin *> renderPluginList = marbleWidget()->renderPlugins();
684     QList<RenderPlugin *>::const_iterator i = renderPluginList.constBegin();
685     QList<RenderPlugin *>::const_iterator const end = renderPluginList.constEnd();
686 
687     for (; i != end; ++i ) {
688         if ((*i)->nameId() == QLatin1String("annotation")) {
689             m_annotationPlugin = *i;
690             QObject::connect(m_annotationPlugin, SIGNAL(enabledChanged(bool)),
691                              this, SLOT(updateAnnotationDockVisibility()));
692             QObject::connect(m_annotationPlugin, SIGNAL(visibilityChanged(bool,QString)),
693                              this, SLOT(updateAnnotationDockVisibility()));
694             QObject::connect(m_annotationPlugin, SIGNAL(actionGroupsChanged()),
695                              this, SLOT(updateAnnotationDock()));
696             updateAnnotationDock();
697             updateAnnotationDockVisibility();
698             mainWindow->addDockWidget( Qt::LeftDockWidgetArea, m_annotationDock );
699         }
700     }
701 
702     mainWindow->tabifyDockWidget( tourDock, m_annotationDock );
703     mainWindow->tabifyDockWidget( m_annotationDock, fileViewDock );
704 
705     QList<QAction*> panelActions;
706     panelActions << routingDock->toggleViewAction();
707     panelActions << locationDock->toggleViewAction();
708     panelActions << m_searchDock->toggleViewAction();
709     panelActions << mapViewDock->toggleViewAction();
710     panelActions << fileViewDock->toggleViewAction();
711     panelActions << m_annotationDock->toggleViewAction();
712     panelActions << legendDock->toggleViewAction();
713     panelActions << tourDock->toggleViewAction();
714 
715     // Local list of panel view toggle actions
716     m_panelActions << routingDock->toggleViewAction();
717     m_panelActions << locationDock->toggleViewAction();
718     m_panelActions << m_searchDock->toggleViewAction();
719     m_panelActions << mapViewDock->toggleViewAction();
720     m_panelActions << fileViewDock->toggleViewAction();
721     m_panelActions << m_annotationDock->toggleViewAction();
722     m_panelActions << legendDock->toggleViewAction();
723     m_panelActions << tourDock->toggleViewAction();
724     for( QAction* action: m_panelActions ) {
725         m_panelVisibility << action->isVisible();
726     }
727 
728     // Create Settings->Panels Menu
729     // Toggle All Panels action
730     m_togglePanelVisibilityAction = new QAction( tr("Hide &All Panels"), this);
731     m_togglePanelVisibilityAction->setShortcut( Qt::Key_F9 );
732     m_togglePanelVisibilityAction->setStatusTip(tr("Show or hide all panels."));
733     connect(m_togglePanelVisibilityAction, SIGNAL(triggered()), this, SLOT(togglePanelVisibility()));
734 
735     // Include a Separator in the List
736     QAction *panelSeparatorAct = new QAction( this );
737     panelSeparatorAct->setSeparator( true );
738 
739     // Return a list of panel view actions for Marble Menu including show/hide all
740     QList<QAction*> panelMenuActions;
741     panelMenuActions << m_togglePanelVisibilityAction;
742     panelMenuActions << panelSeparatorAct;
743     for( QAction* action: m_panelActions ) {
744         panelMenuActions << action;
745     }
746 
747     return panelMenuActions;
748 }
749 
currentLocationWidget()750 CurrentLocationWidget *ControlView::currentLocationWidget()
751 {
752     return m_locationWidget;
753 }
754 
setWorkOffline(bool offline)755 void ControlView::setWorkOffline( bool offline )
756 {
757     marbleWidget()->model()->setWorkOffline( offline );
758     if ( !offline ) {
759         marbleWidget()->clearVolatileTileCache();
760     }
761 }
762 
cloudSyncManager()763 CloudSyncManager *ControlView::cloudSyncManager()
764 {
765     return m_cloudSyncManager;
766 }
767 
externalMapEditor() const768 QString ControlView::externalMapEditor() const
769 {
770     return m_externalEditor;
771 }
772 
addGeoDataFile(const QString & filename)773 void ControlView::addGeoDataFile( const QString &filename )
774 {
775     QFileInfo const file( filename );
776     if ( file.exists() ) {
777         m_marbleWidget->model()->addGeoDataFile( file.absoluteFilePath() );
778     } else {
779         qWarning() << "File" << filename << "does not exist, cannot open it.";
780     }
781 }
782 
showSearch()783 void ControlView::showSearch()
784 {
785     if ( !m_searchDock ) {
786         return;
787     }
788 
789     m_searchDock->show();
790     m_searchDock->raise();
791     m_searchDock->widget()->setFocus();
792 }
793 
showConflictDialog(MergeItem * item)794 void ControlView::showConflictDialog( MergeItem *item )
795 {
796     Q_ASSERT( m_conflictDialog );
797     m_conflictDialog->setMergeItem( item );
798     m_conflictDialog->open();
799 }
800 
updateAnnotationDockVisibility()801 void ControlView::updateAnnotationDockVisibility()
802 {
803     if( m_annotationPlugin != nullptr && m_annotationDock != nullptr ) {
804         if( m_annotationPlugin->visible() && m_annotationPlugin->enabled() ) {
805             m_annotationDock->toggleViewAction()->setVisible( true );
806         } else {
807             m_annotationDock->setVisible( false );
808             m_annotationDock->toggleViewAction()->setVisible( false );
809         }
810     }
811 }
812 
updateAnnotationDock()813 void ControlView::updateAnnotationDock()
814 {
815     const QList<QActionGroup*> *tmp_actionGroups = m_annotationPlugin->actionGroups();
816     QWidget *widget = new QWidget( m_annotationDock );
817     QVBoxLayout *layout = new QVBoxLayout;
818     QToolBar *firstToolbar = new QToolBar( widget );
819     QToolBar *secondToolbar = new QToolBar( widget );
820     QSpacerItem *spacer = new QSpacerItem( 0, 0, QSizePolicy::Expanding, QSizePolicy::Expanding);
821     if( !tmp_actionGroups->isEmpty() ) {
822         bool firstToolbarFilled = false;
823         for( QAction *action: tmp_actionGroups->first()->actions() ) {
824             if (action->objectName() == QLatin1String("toolbarSeparator")) {
825                 firstToolbarFilled = true;
826             } else {
827                 if( !firstToolbarFilled ) {
828                     firstToolbar->addAction( action );
829                 } else {
830                     secondToolbar->addAction( action );
831                 }
832             }
833         }
834     }
835     layout->addWidget( firstToolbar );
836     layout->addWidget( secondToolbar );
837     layout->addSpacerItem( spacer );
838     widget->setLayout( layout );
839     m_annotationDock->setWidget( widget );
840 }
841 
togglePanelVisibility()842 void ControlView::togglePanelVisibility()
843 {
844     Q_ASSERT( m_panelVisibility.size() == m_panelActions.size() );
845     if ( m_isPanelVisible ) {
846         for( int p=0; p<m_panelActions.size(); ++p ) {
847             // Save state of individual dock visibility
848             m_panelVisibility[p] = m_panelActions.at(p)->isChecked();
849 
850             // hide panel if it is showing
851             if ( m_panelActions.at(p)->isChecked() ) {
852                 m_panelActions.at(p)->activate( QAction::Trigger );
853             }
854         }
855 
856         // Change Menu Item Text
857         m_togglePanelVisibilityAction->setText( tr("Show &All Panels") );
858         m_isPanelVisible = false;
859     } else {
860         for( int p=0; p<m_panelActions.size(); ++p ) {
861             // show panel if it was showing before all panels were hidden
862             if ( m_panelVisibility.at(p) && !m_panelActions.at(p)->isChecked() ) {
863                 m_panelActions.at(p)->activate( QAction::Trigger );
864             }
865         }
866 
867         // Change Menu Item Text
868         m_togglePanelVisibilityAction->setText( tr("Hide &All Panels") );
869         m_isPanelVisible = true;
870     }
871 }
872 
handleTourLinkClicked(const QString & path)873 void ControlView::handleTourLinkClicked(const QString& path)
874 {
875     QString tourPath = MarbleDirs::path( path );
876     if ( !tourPath.isEmpty() ) {
877         openTour( tourPath );
878     }
879 }
880 
openTour(const QString & filename)881 void ControlView::openTour( const QString &filename )
882 {
883     if ( m_tourWidget->openTour( filename ) ) {
884         m_tourWidget->startPlaying();
885     }
886 }
887 
closeEvent(QCloseEvent * event)888 void ControlView::closeEvent( QCloseEvent *event )
889 {
890     QCloseEvent newEvent;
891     QCoreApplication::sendEvent( m_tourWidget, &newEvent );
892 
893     if ( newEvent.isAccepted() ) {
894         event->accept();
895     } else {
896         event->ignore();
897     }
898 }
899 
dragEnterEvent(QDragEnterEvent * event)900 void ControlView::dragEnterEvent(QDragEnterEvent *event)
901 {
902     bool success = false;
903 
904     const QMimeData *mimeData = event->mimeData();
905 
906     GeoUriParser uriParser;
907 
908     // prefer urls
909     if (mimeData->hasUrls()) {
910         // be generous and take the first usable url
911         for(const QUrl& url: mimeData->urls()) {
912             uriParser.setGeoUri(url.url());
913             success = uriParser.parse();
914             if (success) {
915                 break;
916             }
917         }
918     }
919 
920     // fall back to own string parsing
921     if (!success && mimeData->hasText()) {
922         const QString text = mimeData->text();
923         // first try human readable coordinates
924         GeoDataCoordinates::fromString(text, success);
925         // next geo uri
926         if (!success) {
927             uriParser.setGeoUri(text);
928             success = uriParser.parse();
929         }
930     }
931 
932     if (success) {
933         event->acceptProposedAction();
934     }
935 }
936 
dropEvent(QDropEvent * event)937 void ControlView::dropEvent(QDropEvent *event)
938 {
939     bool success = false;
940 
941     const QMimeData *mimeData = event->mimeData();
942 
943     // prefer urls
944     if (mimeData->hasUrls()) {
945         // be generous and take the first usable url
946         for(const QUrl& url: mimeData->urls()) {
947             success = openGeoUri(url.url());
948             if (success) {
949                 break;
950             }
951         }
952     }
953 
954     // fall back to own string parsing
955     if (!success && mimeData->hasText()) {
956         const QString text = mimeData->text();
957         // first try human readable coordinates
958         const GeoDataCoordinates coordinates = GeoDataCoordinates::fromString(text, success);
959         if (success) {
960             const qreal longitude = coordinates.longitude(GeoDataCoordinates::Degree);
961             const qreal latitude = coordinates.latitude(GeoDataCoordinates::Degree);
962             m_marbleWidget->centerOn(longitude, latitude);
963         } else {
964             success = openGeoUri(text);
965         }
966     }
967     if (success) {
968         event->acceptProposedAction();
969     }
970 }
971 
972 }
973 
974 #include "moc_ControlView.cpp"
975