Marble

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

KDE's Doxygen guidelines are available online.