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