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 // SPDX-FileCopyrightText: 2012 Illya Kovalevskyy <illya.kovalevskyy@gmail.com>
6 // SPDX-FileCopyrightText: 2014 Gábor Péterffy <peterffy95@gmail.com>
7 //
8 
9 // Self
10 #include "MarbleWidgetPopupMenu.h"
11 
12 // Marble
13 #include "AbstractDataPluginItem.h"
14 #include "AbstractFloatItem.h"
15 #include "MarbleAboutDialog.h"
16 #include "MarbleDirs.h"
17 #include "MarbleWidget.h"
18 #include "MarbleModel.h"
19 #include "GeoDataExtendedData.h"
20 #include "GeoDataFolder.h"
21 #include "GeoDataPlacemark.h"
22 #include "GeoDataLookAt.h"
23 #include "GeoDataData.h"
24 #include "GeoDataSnippet.h"
25 #include "GeoDataStyle.h"
26 #include "GeoDataBalloonStyle.h"
27 #include "GeoDataIconStyle.h"
28 #include "GeoDataPoint.h"
29 #include "GeoDataPhotoOverlay.h"
30 #include "GeoSceneDocument.h"
31 #include "GeoSceneHead.h"
32 #include "MarbleClock.h"
33 #include "MarbleDebug.h"
34 #include "PopupLayer.h"
35 #include "Planet.h"
36 #include "routing/RoutingManager.h"
37 #include "routing/RoutingLayer.h"
38 #include "routing/RouteRequest.h"
39 #include "EditBookmarkDialog.h"
40 #include "BookmarkManager.h"
41 #include "ReverseGeocodingRunnerManager.h"
42 #include "TemplateDocument.h"
43 #include "OsmPlacemarkData.h"
44 #include "StyleBuilder.h"
45 
46 // Qt
47 #include <QApplication>
48 #include <QFile>
49 #include <QMimeData>
50 #include <QPointer>
51 #include <QAction>
52 #include <QClipboard>
53 #include <QMenu>
54 #include <QMessageBox>
55 
56 namespace Marble {
57 /* TRANSLATOR Marble::MarbleWidgetPopupMenu */
58 
59 class Q_DECL_HIDDEN MarbleWidgetPopupMenu::Private {
60 public:
61     const MarbleModel *const m_model;
62     MarbleWidget *const m_widget;
63 
64     QVector<const GeoDataFeature*>  m_featurelist;
65     QList<AbstractDataPluginItem *> m_itemList;
66 
67     QMenu m_lmbMenu;
68     QMenu m_rmbMenu;
69 
70     QAction *m_infoDialogAction;
71     QAction *m_directionsFromHereAction;
72     QAction *m_directionsToHereAction;
73 
74     QAction *const m_copyCoordinateAction;
75     QAction *const m_copyGeoAction;
76 
77     QAction *m_rmbExtensionPoint;
78 
79     ReverseGeocodingRunnerManager m_runnerManager;
80 
81     QPoint m_mousePosition;
82 
83 public:
84     Private( MarbleWidget *widget, const MarbleModel *model, MarbleWidgetPopupMenu* parent );
85     QMenu* createInfoBoxMenu(QWidget *parent);
86 
87     /**
88       * Returns the geo coordinates of the mouse pointer at the last right button menu.
89       * You must not pass 0 as coordinates parameter. The result indicates whether the
90       * coordinates are valid, which will be true if the right button menu was opened at least once.
91       */
92     GeoDataCoordinates mouseCoordinates( QAction* dataContainer ) const;
93 
94     static QString filterEmptyShortDescription( const QString &description );
95     void setupDialogOsm( PopupLayer *popup, const GeoDataPlacemark* placemark );
96     void setupDialogSatellite( const GeoDataPlacemark *placemark );
97     static void setupDialogCity( PopupLayer *popup, const GeoDataPlacemark *placemark );
98     static void setupDialogNation( PopupLayer *popup, const GeoDataPlacemark *placemark );
99     static void setupDialogGeoPlaces( PopupLayer *popup, const GeoDataPlacemark *placemark );
100     static void setupDialogSkyPlaces( PopupLayer *popup, const GeoDataPlacemark *placemark );
101     static void setupDialogPhotoOverlay( PopupLayer *popup, const GeoDataPhotoOverlay *overlay);
102 };
103 
Private(MarbleWidget * widget,const MarbleModel * model,MarbleWidgetPopupMenu * parent)104 MarbleWidgetPopupMenu::Private::Private( MarbleWidget *widget, const MarbleModel *model, MarbleWidgetPopupMenu* parent ) :
105     m_model(model),
106     m_widget(widget),
107     m_lmbMenu( m_widget ),
108     m_rmbMenu( m_widget ),
109     m_directionsFromHereAction( nullptr ),
110     m_directionsToHereAction( nullptr ),
111     m_copyCoordinateAction(new QAction(QIcon(QStringLiteral(":/icons/copy-coordinates.png")), tr("Copy Coordinates"), parent)),
112     m_copyGeoAction(new QAction(QIcon(QStringLiteral(":/icons/copy-coordinates.png")), tr("Copy geo: URL"), parent)),
113     m_rmbExtensionPoint( nullptr ),
114     m_runnerManager( model )
115 {
116     // Property actions (Left mouse button)
117     m_infoDialogAction = new QAction( parent );
118     m_infoDialogAction->setData( 0 );
119 
120     //	Tool actions (Right mouse button)
121     m_directionsFromHereAction = new QAction( tr( "Directions &from here" ), parent );
122     m_directionsToHereAction = new QAction( tr( "Directions &to here" ), parent );
123     RouteRequest* request = m_widget->model()->routingManager()->routeRequest();
124     if ( request ) {
125         m_directionsFromHereAction->setIcon( QIcon( request->pixmap( 0, 16 ) ) );
126         int const lastIndex = qMax( 1, request->size()-1 );
127         m_directionsToHereAction->setIcon( QIcon( request->pixmap( lastIndex, 16 ) ) );
128     }
129     QAction* addBookmark = new QAction( QIcon(QStringLiteral(":/icons/bookmark-new.png")),
130                                         tr( "Add &Bookmark" ), parent );
131     QAction* fullscreenAction = new QAction( tr( "&Full Screen Mode" ), parent );
132     fullscreenAction->setCheckable( true );
133 
134     QAction* aboutDialogAction = new QAction(QIcon(QStringLiteral(":/icons/marble.png")), tr("&About"), parent);
135 
136     QMenu* infoBoxMenu = createInfoBoxMenu(m_widget);
137 
138     const bool smallScreen = MarbleGlobal::getInstance()->profiles() & MarbleGlobal::SmallScreen;
139 
140     if ( !smallScreen ) {
141         m_rmbExtensionPoint = m_rmbMenu.addSeparator();
142     }
143 
144     m_rmbMenu.addAction( m_directionsFromHereAction );
145     m_rmbMenu.addAction( m_directionsToHereAction );
146     m_rmbMenu.addSeparator();
147     m_rmbMenu.addAction( addBookmark );
148     if ( !smallScreen ) {
149         m_rmbMenu.addAction( m_copyCoordinateAction );
150         m_rmbMenu.addAction( m_copyGeoAction );
151     }
152     m_rmbMenu.addAction(QIcon(QStringLiteral(":/icons/addressbook-details.png")), tr("&Address Details"), parent, SLOT(startReverseGeocoding()));
153     m_rmbMenu.addSeparator();
154     m_rmbMenu.addMenu( infoBoxMenu );
155 
156     if ( !smallScreen ) {
157         m_rmbMenu.addAction( aboutDialogAction );
158     } else {
159         m_rmbMenu.addAction( fullscreenAction );
160     }
161 
162     parent->connect( &m_lmbMenu, SIGNAL(aboutToHide()), SLOT(resetMenu()) );
163     parent->connect( m_directionsFromHereAction, SIGNAL(triggered()), SLOT(directionsFromHere()) );
164     parent->connect( m_directionsToHereAction, SIGNAL(triggered()), SLOT(directionsToHere()) );
165     parent->connect( addBookmark, SIGNAL(triggered()), SLOT(addBookmark()) );
166     parent->connect( aboutDialogAction, SIGNAL(triggered()), SLOT(slotAboutDialog()) );
167     parent->connect( m_copyCoordinateAction, SIGNAL(triggered()), SLOT(slotCopyCoordinates()) );
168     parent->connect( m_copyGeoAction, SIGNAL(triggered()), SLOT(slotCopyGeo()) );
169     parent->connect( m_infoDialogAction, SIGNAL(triggered()), SLOT(slotInfoDialog()) );
170     parent->connect( fullscreenAction, SIGNAL(triggered(bool)), parent, SLOT(toggleFullscreen(bool)) );
171 
172     parent->connect( &m_runnerManager, SIGNAL(reverseGeocodingFinished(GeoDataCoordinates,GeoDataPlacemark)),
173              parent, SLOT(showAddressInformation(GeoDataCoordinates,GeoDataPlacemark)) );
174 }
175 
filterEmptyShortDescription(const QString & description)176 QString MarbleWidgetPopupMenu::Private::filterEmptyShortDescription(const QString &description)
177 {
178     if(description.isEmpty())
179         return tr("No description available.");
180     return description;
181 }
182 
setupDialogOsm(PopupLayer * popup,const GeoDataPlacemark * placemark)183 void MarbleWidgetPopupMenu::Private::setupDialogOsm( PopupLayer *popup, const GeoDataPlacemark *placemark )
184 {
185     const GeoDataCoordinates location = placemark->coordinate();
186     popup->setCoordinates(location, Qt::AlignRight | Qt::AlignVCenter);
187 
188     QFile descriptionFile(QStringLiteral(":/marble/webpopup/osm.html"));
189     if (!descriptionFile.open(QIODevice::ReadOnly)) {
190         return;
191     }
192 
193     const QString none = QStringLiteral("none");
194 
195     QString description = descriptionFile.readAll();
196     const OsmPlacemarkData& data = placemark->osmData();
197     if (!data.containsTagKey("addr:street") && !data.containsTagKey("addr:housenumber")){
198         description.replace(QStringLiteral("<br> %postcode%"), QStringLiteral("%postcode%"));
199     }
200     TemplateDocument doc(description);
201 
202     doc[QStringLiteral("name")] = data.tagValue(QStringLiteral("name"));
203 
204     QString natural = data.tagValue(QStringLiteral("natural"));
205     if (!natural.isEmpty()) {
206         natural[0] = natural[0].toUpper();
207         if (natural == QLatin1String("Peak")) {
208             QString elevation = data.tagValue(QStringLiteral("ele"));
209             if (!elevation.isEmpty()) {
210                 natural = natural + QLatin1String(" - ") + elevation + QLatin1String(" m");
211             }
212         }
213         doc[QStringLiteral("details")] = natural;
214     } else {
215         doc[QStringLiteral("detailsVisibility")] = none;
216     }
217 
218     QString amenity;
219     QString shop = data.tagValue(QStringLiteral("shop"));
220     if (!shop.isEmpty()) {
221         shop[0] = shop[0].toUpper();
222 
223         if (shop == QLatin1String("Clothes")) {
224             QString type = data.tagValue(QStringLiteral("clothes"));
225             if (type.isEmpty()) {
226                 type = data.tagValue(QStringLiteral("designation"));
227             }
228             if (!type.isEmpty()) {
229                 type[0] = type[0].toUpper();
230                 amenity = QLatin1String("Shop - ") + shop + QLatin1String(" (") + type + QLatin1Char(')');
231             }
232         }
233         if (amenity.isEmpty()) {
234             amenity = QLatin1String("Shop - ") + shop;
235         }
236     } else {
237         amenity = data.tagValue(QStringLiteral("amenity"));
238         if (!amenity.isEmpty()) {
239             amenity[0] = amenity[0].toUpper();
240         }
241     }
242     if (!amenity.isEmpty()) {
243         doc[QStringLiteral("amenity")] = amenity;
244     } else {
245         doc[QStringLiteral("amenityVisibility")] = none;
246     }
247 
248     QString cuisine = data.tagValue(QStringLiteral("cuisine"));
249     if (!cuisine.isEmpty()) {
250         cuisine[0] = cuisine[0].toUpper();
251         doc[QStringLiteral("cuisine")] = cuisine;
252     } else {
253         doc[QStringLiteral("cuisineVisibility")] = none;
254     }
255 
256     QString openingHours = data.tagValue(QStringLiteral("opening_hours"));
257     if (!openingHours.isEmpty()) {
258         doc[QStringLiteral("openinghours")] = openingHours;
259     } else {
260         doc[QStringLiteral("openinghoursVisibility")] = none;
261     }
262 
263     bool hasContactsData = false;
264 
265     const QStringList addressItemKeys = QStringList()
266         << QStringLiteral("street")
267         << QStringLiteral("housenumber")
268         << QStringLiteral("postcode")
269         << QStringLiteral("city");
270     bool hasAddressItem = false;
271     QStringList addressItems;
272     for (const QString& key: addressItemKeys) {
273         const QString item = data.tagValue(QLatin1String("addr:") + key);
274         if (!item.isEmpty()) {
275             hasAddressItem = true;
276         }
277         addressItems << item;
278     }
279     if (hasAddressItem) {
280         hasContactsData = true;
281         for(int i = 0; i < addressItemKeys.size(); ++i) {
282             doc[addressItemKeys[i]] = addressItems[i];
283         }
284     } else {
285         doc[QStringLiteral("addressVisibility")] = none;
286     }
287 
288     QString phoneData = data.tagValue(QStringLiteral("phone"));
289     if (!phoneData.isEmpty()) {
290         hasContactsData = true;
291         doc[QStringLiteral("phone")] = phoneData;
292     } else {
293         doc[QStringLiteral("phoneVisibility")] = none;
294     }
295 
296     QString websiteData;
297     auto const tags = QStringList() << "website" << "contact:website" << "facebook" << "contact:facebook" << "url";
298     for(const QString &tag: tags) {
299         websiteData = data.tagValue(tag);
300         if (!websiteData.isEmpty()) {
301             break;
302         }
303     }
304     if (!websiteData.isEmpty()) {
305         hasContactsData = true;
306         doc[QStringLiteral("website")] = websiteData;
307     } else {
308         doc[QStringLiteral("websiteVisibility")] = none;
309     }
310 
311     if (!hasContactsData) {
312         doc[QStringLiteral("contactVisibility")] = none;
313     }
314 
315     bool hasFacilitiesData = false;
316 
317     const QString wheelchair = data.tagValue(QStringLiteral("wheelchair"));
318     if (!wheelchair.isEmpty()) {
319         hasFacilitiesData = true;
320         doc[QStringLiteral("wheelchair")] = wheelchair;
321     } else {
322         doc[QStringLiteral("wheelchairVisibility")] = none;
323     }
324 
325     const QString internetAccess = data.tagValue(QStringLiteral("internet_access"));
326     if (!internetAccess.isEmpty()) {
327         hasFacilitiesData = true;
328         doc[QStringLiteral("internetaccess")] = internetAccess;
329     } else {
330         doc[QStringLiteral("internetVisibility")] = none;
331     }
332 
333     const QString smoking = data.tagValue(QStringLiteral("smoking"));
334     if (!smoking.isEmpty()) {
335         hasFacilitiesData = true;
336         doc[QStringLiteral("smoking")] = smoking;
337     } else {
338         doc[QStringLiteral("smokingVisibility")] = none;
339     }
340 
341     if (!hasFacilitiesData) {
342         doc[QStringLiteral("facilitiesVisibility")] = none;
343     }
344 
345     const QString flagPath = m_widget->styleBuilder()->createStyle(StyleParameters(placemark))->iconStyle().iconPath();
346     doc["flag"] = flagPath;
347     popup->setContent(doc.finalText());
348 }
349 
setupDialogSatellite(const GeoDataPlacemark * placemark)350 void MarbleWidgetPopupMenu::Private::setupDialogSatellite( const GeoDataPlacemark *placemark )
351 {
352     PopupLayer *const popup = m_widget->popupLayer();
353     const GeoDataCoordinates location = placemark->coordinate(m_widget->model()->clockDateTime());
354     popup->setCoordinates(location, Qt::AlignRight | Qt::AlignVCenter);
355 
356     const QString description = placemark->description();
357     TemplateDocument doc(description);
358     doc["altitude"] = QString::number(location.altitude(), 'f', 2);
359     doc["latitude"] = location.latToString();
360     doc["longitude"] = location.lonToString();
361     popup->setContent(doc.finalText());
362 }
363 
setupDialogCity(PopupLayer * popup,const GeoDataPlacemark * placemark)364 void MarbleWidgetPopupMenu::Private::setupDialogCity( PopupLayer *popup, const GeoDataPlacemark *placemark )
365 {
366     const GeoDataCoordinates location = placemark->coordinate();
367     popup->setCoordinates(location, Qt::AlignRight | Qt::AlignVCenter);
368 
369     QFile descriptionFile(QStringLiteral(":/marble/webpopup/city.html"));
370     if (!descriptionFile.open(QIODevice::ReadOnly)) {
371         return;
372     }
373 
374     const QString description = descriptionFile.readAll();
375     TemplateDocument doc(description);
376 
377     doc["name"] = placemark->name();
378     QString  roleString;
379     const QString role = placemark->role();
380     if (role == QLatin1String("PPLC")) {
381         roleString = tr("National Capital");
382     } else if (role == QLatin1String("PPL")) {
383         roleString = tr("City");
384     } else if (role == QLatin1String("PPLA")) {
385         roleString = tr("State Capital");
386     } else if (role == QLatin1String("PPLA2")) {
387         roleString = tr("County Capital");
388     } else if (role == QLatin1String("PPLA3") ||
389                role == QLatin1String("PPLA4")) {
390         roleString = tr("Capital");
391     } else if (role == QLatin1String("PPLF") ||
392                role == QLatin1String("PPLG") ||
393                role == QLatin1String("PPLL") ||
394                role == QLatin1String("PPLQ") ||
395                role == QLatin1String("PPLR") ||
396                role == QLatin1String("PPLS") ||
397                role == QLatin1String("PPLW")) {
398         roleString = tr("Village");
399     }
400 
401     doc["category"] = roleString;
402     doc["shortDescription"] = filterEmptyShortDescription(placemark->description());
403     doc["latitude"] = location.latToString();
404     doc["longitude"] = location.lonToString();
405     doc["elevation"] =  QString::number(location.altitude(), 'f', 2);
406     doc["population"] = QString::number(placemark->population());
407     doc["country"] = placemark->countryCode();
408     doc["state"] = placemark->state();
409 
410     QString dst = QStringLiteral("%1").arg((placemark->extendedData().value(QStringLiteral("gmt")).value().toInt() +
411                                             placemark->extendedData().value(QStringLiteral("dst")).value().toInt()) /
412                                             ( double ) 100, 0, 'f', 1 );
413     // There is an issue about UTC.
414     // It's possible to variants (e.g.):
415     // +1.0 and -1.0, but dst does not have + an the start
416     if (dst.startsWith(QLatin1Char('-'))) {
417         doc["timezone"] = dst;
418     } else {
419         doc["timezone"] = QLatin1Char('+') + dst;
420     }
421 
422     const QString flagPath = MarbleDirs::path(
423                 QLatin1String("flags/flag_") + placemark->countryCode().toLower() + QLatin1String(".svg"));
424     doc["flag"] = flagPath;
425 
426     popup->setContent(doc.finalText());
427 }
428 
setupDialogNation(PopupLayer * popup,const GeoDataPlacemark * index)429 void MarbleWidgetPopupMenu::Private::setupDialogNation( PopupLayer *popup, const GeoDataPlacemark *index)
430 {
431     const GeoDataCoordinates location = index->coordinate();
432     popup->setCoordinates(location, Qt::AlignRight | Qt::AlignVCenter);
433 
434     QFile descriptionFile(QStringLiteral(":/marble/webpopup/nation.html"));
435     if (!descriptionFile.open(QIODevice::ReadOnly)) {
436         return;
437     }
438 
439     const QString description = descriptionFile.readAll();
440     TemplateDocument doc(description);
441 
442     doc["name"] = index->name();
443     doc["shortDescription"] = filterEmptyShortDescription(index->description());
444     doc["latitude"] = location.latToString();
445     doc["longitude"] = location.lonToString();
446     doc["elevation"] = QString::number(location.altitude(), 'f', 2);
447     doc["population"] = QString::number(index->population());
448     doc["area"] = QString::number(index->area(), 'f', 2);
449 
450     const QString flagPath = MarbleDirs::path(QString("flags/flag_%1.svg").arg(index->countryCode().toLower()) );
451     doc["flag"] = flagPath;
452 
453     popup->setContent(doc.finalText());
454 }
455 
setupDialogGeoPlaces(PopupLayer * popup,const GeoDataPlacemark * index)456 void MarbleWidgetPopupMenu::Private::setupDialogGeoPlaces( PopupLayer *popup, const GeoDataPlacemark *index)
457 {
458     const GeoDataCoordinates location = index->coordinate();
459     popup->setCoordinates(location, Qt::AlignRight | Qt::AlignVCenter);
460 
461     QFile descriptionFile(QStringLiteral(":/marble/webpopup/geoplace.html"));
462     if (!descriptionFile.open(QIODevice::ReadOnly)) {
463         return;
464     }
465 
466     const QString description = descriptionFile.readAll();
467     TemplateDocument doc(description);
468 
469     doc["name"] = index->name();
470     doc["latitude"] = location.latToString();
471     doc["longitude"] = location.lonToString();
472     doc["elevation"] = QString::number(location.altitude(), 'f', 2);
473     doc["shortDescription"] = filterEmptyShortDescription(index->description());
474 
475     popup->setContent(doc.finalText());
476 }
477 
setupDialogSkyPlaces(PopupLayer * popup,const GeoDataPlacemark * index)478 void MarbleWidgetPopupMenu::Private::setupDialogSkyPlaces( PopupLayer *popup, const GeoDataPlacemark *index)
479 {
480     const GeoDataCoordinates location = index->coordinate();
481     popup->setCoordinates(location, Qt::AlignRight | Qt::AlignVCenter);
482 
483     QFile descriptionFile(QStringLiteral(":/marble/webpopup/skyplace.html"));
484     if (!descriptionFile.open(QIODevice::ReadOnly)) {
485         return;
486     }
487 
488     const QString description = descriptionFile.readAll();
489     TemplateDocument doc(description);
490 
491     doc["name"] = index->name();
492     doc["latitude"] = GeoDataCoordinates::latToString(
493                             location.latitude(), GeoDataCoordinates::Astro, GeoDataCoordinates::Radian, -1, 'f');
494     doc["longitude"] = GeoDataCoordinates::lonToString(
495                             location.longitude(), GeoDataCoordinates::Astro, GeoDataCoordinates::Radian, -1, 'f');
496     doc["shortDescription"] = filterEmptyShortDescription(index->description());
497 
498     popup->setContent(doc.finalText());
499 }
500 
setupDialogPhotoOverlay(PopupLayer * popup,const GeoDataPhotoOverlay * index)501 void MarbleWidgetPopupMenu::Private::setupDialogPhotoOverlay( PopupLayer *popup, const GeoDataPhotoOverlay *index )
502 {
503     const GeoDataCoordinates location = index->point().coordinates();
504     popup->setCoordinates(location, Qt::AlignRight | Qt::AlignVCenter);
505 
506     QFile descriptionFile(QStringLiteral(":/marble/webpopup/photooverlay.html"));
507 
508     if ( !descriptionFile.open(QIODevice::ReadOnly) ) {
509         return;
510     }
511 
512     const QString description = descriptionFile.readAll();
513     TemplateDocument doc(description);
514     doc["name"] = index->name();
515     doc["latitude"] = location.latToString();
516     doc["longitude"] = location.lonToString();
517     doc["elevation"] = QString::number(location.altitude(), 'f', 2);
518     doc["shortDescription"] = filterEmptyShortDescription(index->description());
519     doc["source"] = index->absoluteIconFile();
520     doc["width"] = QString::number(200);
521     doc["height"] = QString::number(100);
522     QString const basePath = index->resolvePath(".");
523     QUrl const baseUrl = (basePath != QLatin1String(".")) ? QUrl::fromLocalFile(basePath + QLatin1Char('/')) : QUrl();
524     popup->setContent(doc.finalText(), baseUrl );
525 }
526 
MarbleWidgetPopupMenu(MarbleWidget * widget,const MarbleModel * model)527 MarbleWidgetPopupMenu::MarbleWidgetPopupMenu(MarbleWidget *widget,
528                                          const MarbleModel *model)
529     : QObject(widget),
530       d( new Private( widget, model, this ) )
531 {
532     // nothing to do
533 }
534 
~MarbleWidgetPopupMenu()535 MarbleWidgetPopupMenu::~MarbleWidgetPopupMenu()
536 {
537     delete d;
538 }
539 
createInfoBoxMenu(QWidget * parent)540 QMenu* MarbleWidgetPopupMenu::Private::createInfoBoxMenu(QWidget* parent)
541 {
542     QMenu* menu = new QMenu(tr("&Info Boxes"), parent);
543     QList<AbstractFloatItem *> floatItemList = m_widget->floatItems();
544 
545     QList<AbstractFloatItem *>::const_iterator iter = floatItemList.constBegin();
546     QList<AbstractFloatItem *>::const_iterator const end = floatItemList.constEnd();
547     for (; iter != end; ++iter )
548     {
549         menu->addAction( (*iter)->action() );
550     }
551     return menu;
552 }
553 
showLmbMenu(int xpos,int ypos)554 void MarbleWidgetPopupMenu::showLmbMenu( int xpos, int ypos )
555 {
556     bool const smallScreen = MarbleGlobal::getInstance()->profiles() & MarbleGlobal::SmallScreen;
557     if ( smallScreen ) {
558         showRmbMenu( xpos, ypos );
559         return;
560     }
561 
562     d->m_mousePosition.setX(xpos);
563     d->m_mousePosition.setY(ypos);
564 
565     const QPoint curpos = QPoint( xpos, ypos );
566     d->m_featurelist = d->m_widget->whichFeatureAt( curpos );
567 
568     int  actionidx = 1;
569 
570     QVector<const GeoDataFeature*>::const_iterator it = d->m_featurelist.constBegin();
571     QVector<const GeoDataFeature*>::const_iterator const itEnd = d->m_featurelist.constEnd();
572     for (; it != itEnd; ++it )
573     {
574         QString name = (*it)->name();
575         QPixmap icon = QPixmap::fromImage( ( *it)->style()->iconStyle().icon() );
576         d->m_infoDialogAction->setData( actionidx );
577         d->m_infoDialogAction->setText( name );
578         d->m_infoDialogAction->setIcon( icon );
579         // Insert as first action in the menu
580         QAction *firstAction = nullptr;
581         if( !d->m_lmbMenu.actions().isEmpty() ) {
582             firstAction = d->m_lmbMenu.actions().first();
583         }
584         d->m_lmbMenu.insertAction( firstAction, d->m_infoDialogAction );
585         actionidx++;
586     }
587 
588     d->m_itemList = d->m_widget->whichItemAt( curpos );
589     QList<AbstractDataPluginItem *>::const_iterator itW = d->m_itemList.constBegin();
590     QList<AbstractDataPluginItem *>::const_iterator const itWEnd = d->m_itemList.constEnd();
591     for (; itW != itWEnd; ++itW )
592     {
593         for ( QAction* action: (*itW)->actions() ) {
594             d->m_lmbMenu.addAction( action );
595         }
596     }
597 
598     switch ( d->m_lmbMenu.actions().size() ) {
599     case 0: // nothing to do, ignore
600         break;
601 
602     case 1: // one action? perform immediately
603         d->m_lmbMenu.actions().first()->activate( QAction::Trigger );
604         d->m_lmbMenu.clear();
605         break;
606 
607     default:
608         d->m_lmbMenu.popup( d->m_widget->mapToGlobal( curpos ) );
609     }
610 }
611 
612 
showRmbMenu(int xpos,int ypos)613 void MarbleWidgetPopupMenu::showRmbMenu( int xpos, int ypos )
614 {
615     qreal lon, lat;
616     const bool visible = d->m_widget->geoCoordinates( xpos, ypos, lon, lat, GeoDataCoordinates::Radian );
617     if ( !visible )
618         return;
619 
620     d->m_mousePosition.setX(xpos);
621     d->m_mousePosition.setY(ypos);
622 
623     QPoint curpos = QPoint( xpos, ypos );
624     d->m_copyCoordinateAction->setData( curpos );
625     d->m_copyGeoAction->setData( curpos );
626 
627     bool const showDirectionButtons = d->m_widget->routingLayer() && d->m_widget->routingLayer()->isInteractive();
628     d->m_directionsFromHereAction->setVisible( showDirectionButtons );
629     d->m_directionsToHereAction->setVisible( showDirectionButtons );
630     RouteRequest* request = d->m_widget->model()->routingManager()->routeRequest();
631     if ( request ) {
632         int const lastIndex = qMax( 1, request->size()-1 );
633         d->m_directionsToHereAction->setIcon( QIcon( request->pixmap( lastIndex, 16 ) ) );
634     }
635 
636     d->m_rmbMenu.popup( d->m_widget->mapToGlobal( curpos ) );
637 }
638 
resetMenu()639 void MarbleWidgetPopupMenu::resetMenu()
640 {
641     d->m_lmbMenu.clear();
642 }
643 
slotInfoDialog()644 void MarbleWidgetPopupMenu::slotInfoDialog()
645 {
646     QAction *action = qobject_cast<QAction *>( sender() );
647     if ( action == nullptr ) {
648         mDebug() << "Warning: slotInfoDialog should be called by a QAction signal";
649         return;
650     }
651 
652     int actionidx = action->data().toInt();
653 
654     if ( actionidx > 0 ) {
655         const GeoDataPlacemark *placemark = dynamic_cast<const GeoDataPlacemark*>(d->m_featurelist.at( actionidx -1 ));
656         const GeoDataPhotoOverlay *overlay = dynamic_cast<const GeoDataPhotoOverlay*>(d->m_featurelist.at( actionidx - 1 ));
657         PopupLayer* popup = d->m_widget->popupLayer();
658         bool isSatellite = false;
659         bool isCity = false;
660         bool isNation = false;
661 
662         const OsmPlacemarkData& data = placemark->osmData();
663 
664         bool hasOsmData = false;
665 
666         QStringList recognizedTags;
667         recognizedTags << "name" << "amenity" << "cuisine" << "opening_hours";
668         recognizedTags << "addr:street" << "addr:housenumber" << "addr:postcode";
669         recognizedTags << "addr:city" << "phone" << "wheelchair" << "internet_access";
670         recognizedTags << "smoking" << "website" << "contact:website" << "facebook";
671         recognizedTags << "contact:facebook" << "url";
672 
673         for(const QString &tag: recognizedTags) {
674             if (data.containsTagKey(tag)) {
675                 hasOsmData = true;
676                 break;
677             }
678         }
679 
680         if ( placemark ) {
681             isSatellite = (placemark->visualCategory() == GeoDataPlacemark::Satellite);
682             isCity = (placemark->visualCategory() >= GeoDataPlacemark::SmallCity &&
683                       placemark->visualCategory() <= GeoDataPlacemark::LargeNationCapital);
684             isNation = (placemark->visualCategory() == GeoDataPlacemark::Nation);
685         }
686 
687         bool isSky = false;
688 
689         if ( d->m_widget->model()->mapTheme() ) {
690             isSky = d->m_widget->model()->mapTheme()->head()->target() == QLatin1String("sky");
691         }
692 
693         popup->setSize(QSizeF(420, 420));
694 
695         if (hasOsmData){
696             d->setupDialogOsm( popup, placemark );
697         } else if (isSatellite) {
698             d->setupDialogSatellite( placemark );
699         } else if (isCity) {
700             Private::setupDialogCity( popup, placemark );
701         } else if (isNation) {
702             Private::setupDialogNation( popup, placemark );
703         } else if (isSky) {
704             Private::setupDialogSkyPlaces( popup, placemark );
705         } else if ( overlay ) {
706             Private::setupDialogPhotoOverlay( popup, overlay );
707         } else if ( placemark && placemark->role().isEmpty() ) {
708             popup->setContent( placemark->description() );
709         } else if ( placemark ) {
710             Private::setupDialogGeoPlaces( popup, placemark );
711         }
712 
713         if ( placemark ) {
714             if ( placemark->style() == nullptr ) {
715                 popup->setBackgroundColor(QColor(Qt::white));
716                 popup->setTextColor(QColor(Qt::black));
717                 return;
718             }
719             if ( placemark->style()->balloonStyle().displayMode() == GeoDataBalloonStyle::Hide ) {
720                 popup->setVisible(false);
721                 return;
722             }
723 
724             QString content = placemark->style()->balloonStyle().text();
725             if (content.length() > 0) {
726                 content.replace(QStringLiteral("$[name]"), placemark->name(), Qt::CaseInsensitive);
727                 content.replace(QStringLiteral("$[description]"), placemark->description(), Qt::CaseInsensitive);
728                 content.replace(QStringLiteral("$[address]"), placemark->address(), Qt::CaseInsensitive);
729                 // @TODO: implement the line calculation, so that snippet().maxLines actually has effect.
730                 content.replace(QStringLiteral("$[snippet]"), placemark->snippet().text(), Qt::CaseInsensitive);
731                 content.replace(QStringLiteral("$[id]"), placemark->id(), Qt::CaseInsensitive);
732                 QString const basePath = placemark->resolvePath(".");
733                 QUrl const baseUrl = (basePath != QLatin1String(".")) ? QUrl::fromLocalFile(basePath + QLatin1Char('/')) : QUrl();
734                 popup->setContent(content, baseUrl );
735             }
736 
737             popup->setBackgroundColor(placemark->style()->balloonStyle().backgroundColor());
738             popup->setTextColor(placemark->style()->balloonStyle().textColor());
739         }
740 
741         popup->popup();
742     }
743 }
744 
slotCopyCoordinates()745 void MarbleWidgetPopupMenu::slotCopyCoordinates()
746 {
747     const GeoDataCoordinates coordinates = d->mouseCoordinates( d->m_copyCoordinateAction );
748     if ( coordinates.isValid() ) {
749 	const qreal longitude_degrees = coordinates.longitude(GeoDataCoordinates::Degree);
750 	const qreal latitude_degrees = coordinates.latitude(GeoDataCoordinates::Degree);
751 
752 	// importing this representation into Marble does not show anything,
753 	// but Merkaartor shows the point
754 	const QString kmlRepresentation = QString::fromLatin1(
755 	  "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
756 	  "<kml xmlns=\"http://www.opengis.net/kml/2.2\">\n"
757 	  "<Document>\n"
758 	  " <Placemark>\n"
759 // 	  "   <name></name>\n"
760 	  "   <Point>\n"
761 	  "     <coordinates>%1,%2</coordinates>\n"
762 	  "   </Point>\n"
763 	  " </Placemark>\n"
764 	  "</Document>\n"
765 	  "</kml>\n"
766 	  ).arg(longitude_degrees, 0, 'f', 10).arg(latitude_degrees, 0, 'f', 10);
767 
768 	  // importing this data into Marble and Merkaartor works
769 	  const QString gpxRepresentation = QString::fromLatin1(
770 	    "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\" ?>\n"
771 	    "<gpx xmlns=\"http://www.topografix.com/GPX/1/1\" creator=\"trippy\" version=\"0.1\"\n"
772 	    " xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n"
773 	    " xsi:schemaLocation=\"http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd\">\n"
774 	    "  <wpt lat=\"%1\" lon=\"%2\">\n"
775 // 	    "   <ele>%3</ele>\n"
776 //      "   <time></time>\n"
777 // 	    "   <name>%4</name>\n"
778 	    "  </wpt>\n"
779 	    "</gpx>\n"
780 	    ).arg(latitude_degrees, 0, 'f', 10).arg(longitude_degrees, 0, 'f', 10);
781 
782 	    QString  positionString = coordinates.toString();
783 
784 	    QMimeData * const myMimeData = new QMimeData();
785 	    myMimeData->setText(positionString);
786 	    myMimeData->setData(QLatin1String("application/vnd.google-earth.kml+xml"), kmlRepresentation.toUtf8());
787 	    myMimeData->setData(QLatin1String("application/gpx+xml"), gpxRepresentation.toUtf8());
788 
789 	    QClipboard * const clipboard = QApplication::clipboard();
790 	    clipboard->setMimeData(myMimeData);
791     }
792 }
793 
slotCopyGeo()794 void MarbleWidgetPopupMenu::slotCopyGeo()
795 {
796     const GeoDataCoordinates coordinates = d->mouseCoordinates( d->m_copyCoordinateAction );
797     if ( coordinates.isValid() ) {
798         const qreal latitude_degrees = coordinates.latitude(GeoDataCoordinates::Degree);
799         const qreal longitude_degrees = coordinates.longitude(GeoDataCoordinates::Degree);
800 
801         QMimeData * const myMimeData = new QMimeData();
802         QList<QUrl> urls = { QUrl(QString("geo:%1,%2").arg(latitude_degrees, 0, 'f', 10).arg(longitude_degrees, 0, 'f', 10)) };
803         myMimeData->setUrls(urls);
804         QClipboard * const clipboard = QApplication::clipboard();
805         clipboard->setMimeData(myMimeData);
806     }
807 }
808 
809 
slotAboutDialog()810 void MarbleWidgetPopupMenu::slotAboutDialog()
811 {
812     QPointer<MarbleAboutDialog> dialog = new MarbleAboutDialog( d->m_widget );
813     dialog->exec();
814     delete dialog;
815 }
816 
addAction(Qt::MouseButton button,QAction * action)817 void MarbleWidgetPopupMenu::addAction( Qt::MouseButton button, QAction* action )
818 {
819     if ( button == Qt::RightButton ) {
820         d->m_rmbMenu.insertAction( d->m_rmbExtensionPoint, action );
821     } else {
822         d->m_lmbMenu.addAction( action );
823     }
824 }
825 
directionsFromHere()826 void MarbleWidgetPopupMenu::directionsFromHere()
827 {
828     RouteRequest* request = d->m_widget->model()->routingManager()->routeRequest();
829     if ( request )
830     {
831         const GeoDataCoordinates coordinates = d->mouseCoordinates( d->m_copyCoordinateAction );
832         if ( coordinates.isValid() ) {
833             if ( request->size() > 0 ) {
834                 request->setPosition( 0, coordinates );
835             } else {
836                 request->append( coordinates );
837             }
838             d->m_widget->model()->routingManager()->retrieveRoute();
839         }
840     }
841 }
842 
directionsToHere()843 void MarbleWidgetPopupMenu::directionsToHere()
844 {
845     RouteRequest* request = d->m_widget->model()->routingManager()->routeRequest();
846     if ( request )
847     {
848         const GeoDataCoordinates coordinates = d->mouseCoordinates( d->m_copyCoordinateAction );
849         if ( coordinates.isValid() ) {
850             if ( request->size() > 1 ) {
851                 request->setPosition( request->size()-1, coordinates );
852             } else {
853                 request->append( coordinates );
854             }
855             d->m_widget->model()->routingManager()->retrieveRoute();
856         }
857     }
858 }
859 
mouseCoordinates(QAction * dataContainer) const860 GeoDataCoordinates MarbleWidgetPopupMenu::Private::mouseCoordinates( QAction* dataContainer ) const
861 {
862     if ( !dataContainer ) {
863         return GeoDataCoordinates();
864     }
865 
866     if ( !m_featurelist.isEmpty() && geodata_cast<GeoDataPlacemark>(m_featurelist.first())) {
867         const GeoDataPlacemark * placemark =  static_cast<const GeoDataPlacemark*>( m_featurelist.first() );
868         return placemark->coordinate( m_model->clock()->dateTime() );
869     } else {
870         QPoint p = dataContainer->data().toPoint();
871         qreal lat( 0.0 ), lon( 0.0 );
872 
873         const bool valid = m_widget->geoCoordinates( p.x(), p.y(), lon, lat, GeoDataCoordinates::Radian );
874         if ( valid ) {
875             return GeoDataCoordinates( lon, lat );
876         }
877     }
878 
879     return GeoDataCoordinates();
880 }
881 
startReverseGeocoding()882 void MarbleWidgetPopupMenu::startReverseGeocoding()
883 {
884     const GeoDataCoordinates coordinates = d->mouseCoordinates( d->m_copyCoordinateAction );
885     if ( coordinates.isValid() ) {
886         d->m_runnerManager.reverseGeocoding( coordinates );
887     }
888 }
889 
showAddressInformation(const GeoDataCoordinates &,const GeoDataPlacemark & placemark)890 void MarbleWidgetPopupMenu::showAddressInformation(const GeoDataCoordinates &, const GeoDataPlacemark &placemark)
891 {
892     QString text = placemark.address();
893     if ( !text.isEmpty() ) {
894         QMessageBox::information( d->m_widget, tr( "Address Details" ), text, QMessageBox::Ok );
895     }
896 }
897 
addBookmark()898 void MarbleWidgetPopupMenu::addBookmark()
899 {
900     const GeoDataCoordinates coordinates = d->mouseCoordinates( d->m_copyCoordinateAction );
901     if ( coordinates.isValid() ) {
902         QPointer<EditBookmarkDialog> dialog = new EditBookmarkDialog( d->m_widget->model()->bookmarkManager(), d->m_widget );
903         dialog->setMarbleWidget( d->m_widget );
904         dialog->setCoordinates( coordinates );
905         dialog->setRange( d->m_widget->lookAt().range() );
906         dialog->setReverseGeocodeName();
907         if ( dialog->exec() == QDialog::Accepted ) {
908             d->m_widget->model()->bookmarkManager()->addBookmark( dialog->folder(), dialog->bookmark() );
909         }
910         delete dialog;
911     }
912 }
913 
toggleFullscreen(bool enabled)914 void MarbleWidgetPopupMenu::toggleFullscreen( bool enabled )
915 {
916     QWidget* parent = d->m_widget;
917     for ( ; parent->parentWidget(); parent = parent->parentWidget() ) {
918         // nothing to do
919     }
920 
921     if ( enabled ) {
922         parent->setWindowState( parent->windowState() | Qt::WindowFullScreen );
923     } else {
924         parent->setWindowState( parent->windowState() & ~Qt::WindowFullScreen );
925     }
926 }
927 
mousePosition() const928 QPoint MarbleWidgetPopupMenu::mousePosition() const
929 {
930     return d->m_mousePosition;
931 }
932 
933 }
934 
935 #include "moc_MarbleWidgetPopupMenu.cpp"
936