Marble

MarbleWidgetPopupMenu.cpp
1 // SPDX-License-Identifier: LGPL-2.1-or-later
2 //
3 // SPDX-FileCopyrightText: 2006-2007 Torsten Rahn <[email protected]>
4 // SPDX-FileCopyrightText: 2007 Inge Wallin <[email protected]>
5 // SPDX-FileCopyrightText: 2012 Illya Kovalevskyy <[email protected]>
6 // SPDX-FileCopyrightText: 2014 Gábor Péterffy <[email protected]>
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;
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 
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 
176 QString MarbleWidgetPopupMenu::Private::filterEmptyShortDescription(const QString &description)
177 {
178  if(description.isEmpty())
179  return tr("No description available.");
180  return description;
181 }
182 
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 
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 
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 
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 
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 
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 
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 
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 
535 MarbleWidgetPopupMenu::~MarbleWidgetPopupMenu()
536 {
537  delete d;
538 }
539 
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 
547  for (; iter != end; ++iter )
548  {
549  menu->addAction( (*iter)->action() );
550  }
551  return menu;
552 }
553 
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 
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 
639 void MarbleWidgetPopupMenu::resetMenu()
640 {
641  d->m_lmbMenu.clear();
642 }
643 
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 
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 
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 
810 void MarbleWidgetPopupMenu::slotAboutDialog()
811 {
812  QPointer<MarbleAboutDialog> dialog = new MarbleAboutDialog( d->m_widget );
813  dialog->exec();
814  delete dialog;
815 }
816 
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 
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 
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 
860 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 
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 
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 
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 
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 
929 {
930  return d->m_mousePosition;
931 }
932 
933 }
934 
935 #include "moc_MarbleWidgetPopupMenu.cpp"
A 3d point representation.
AlignRight
QPixmap fromImage(const QImage &image, Qt::ImageConversionFlags flags)
QString toUpper() const const
QString number(int n, int base)
QVariant location(const QVariant &res)
CaseInsensitive
void setData(const QString &mimeType, const QByteArray &data)
void setUrls(const QList< QUrl > &urls)
Type type(const QSqlDatabase &db)
QString latToString() const
return a string representation of latitude of the coordinate convenience function that uses the defau...
QObject * sender() const const
QList::const_iterator constBegin() const const
int x() const const
int y() const const
MouseButton
@ Astro
< "RA and DEC" notation (used for astronomical sky coordinates)
QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
const QList< QKeySequence > & addBookmark()
QString lonToString() const
return a string representation of longitude of the coordinate convenience function that uses the defa...
QAction * addAction(const QString &text)
QClipboard * clipboard()
void setPosition(int index, const GeoDataCoordinates &position, const QString &name=QString())
Change the value of the element at the given position.
bool isEmpty() const const
QUrl fromLocalFile(const QString &localFile)
QByteArray toUtf8() const const
int length() const const
int toInt(bool *ok) const const
WindowFullScreen
Binds a QML item to a specific geodetic location in screen coordinates.
bool isValid() const
Returns.
void setCheckable(bool)
QPoint toPoint() const const
void append(const GeoDataCoordinates &coordinates, const QString &name=QString())
Add the given element to the end.
QString & replace(int position, int n, QChar after)
bool startsWith(const QString &s, Qt::CaseSensitivity cs) const const
QVariant data() const const
void setText(const QString &text)
QList::const_iterator constEnd() const const
QString arg(qlonglong a, int fieldWidth, int base, QChar fillChar) const const
QString fromLatin1(const char *str, int size)
QString name(StandardShortcut id)
void addAction(Qt::MouseButton button, QAction *action)
Adds the action to the menu associated with the specified mouse button.
void setMimeData(QMimeData *src, QClipboard::Mode mode)
Points to be included in a route.
Definition: RouteRequest.h:26
QMessageBox::StandardButton information(QWidget *parent, const QString &title, const QString &text, QMessageBox::StandardButtons buttons, QMessageBox::StandardButton defaultButton)
QString tr(const char *sourceText, const char *disambiguation, int n)
QObject * parent() const const
const QList< QKeySequence > & end()
QDebug mDebug()
a function to replace qDebug() in Marble library code
Definition: MarbleDebug.cpp:31
int size() const
Number of points in the route.
QPoint mousePosition() const
mousePosition Position of the last mouse button click
This file is part of the KDE documentation.
Documentation copyright © 1996-2023 The KDE developers.
Generated on Wed Jun 7 2023 03:54:37 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.