Marble

RoutingInputWidget.cpp
1 // SPDX-License-Identifier: LGPL-2.1-or-later
2 //
3 // SPDX-FileCopyrightText: 2010 Dennis Nienhüser <[email protected]>
4 // SPDX-FileCopyrightText: 2012 Illya Kovalevskyy <[email protected]>
5 //
6 
7 #include "RoutingInputWidget.h"
8 
9 #include "MarblePlacemarkModel.h"
10 #include "RouteRequest.h"
11 
12 #ifdef MARBLE_NO_WEBKITWIDGETS
13 #include "NullTinyWebBrowser.h"
14 #else
15 #include "TinyWebBrowser.h"
16 #endif
17 
18 #include "BookmarkManager.h"
19 #include "MarbleModel.h"
20 #include "MarbleWidget.h"
21 #include "routing/RoutingManager.h"
22 #include "GeoDataPlacemark.h"
23 #include "GeoDataFolder.h"
24 #include "GeoDataExtendedData.h"
25 #include "GeoDataData.h"
26 #include "PositionTracking.h"
27 #include "ReverseGeocodingRunnerManager.h"
28 #include "SearchRunnerManager.h"
29 #include "MarbleColors.h"
30 #include "MarbleLineEdit.h"
31 #include "GoToDialog.h"
32 
33 #include <QTimer>
34 #include <QHBoxLayout>
35 #include <QIcon>
36 #include <QPushButton>
37 #include <QMenu>
38 #include <QKeyEvent>
39 #include <QPainter>
40 
41 namespace Marble
42 {
43 
44 /**
45  * A MarbleLineEdit that swallows enter/return pressed
46  * key events
47  */
48 class RoutingInputLineEdit : public MarbleLineEdit
49 {
50 public:
51  explicit RoutingInputLineEdit( QWidget *parent = nullptr );
52 
53 protected:
54  void keyPressEvent(QKeyEvent *) override;
55 };
56 
57 class RoutingInputWidgetPrivate
58 {
59 public:
60  MarbleModel* m_marbleModel;
61 
62  RoutingInputLineEdit *m_lineEdit;
63 
64  QPushButton* m_removeButton;
65 
66  SearchRunnerManager m_placemarkRunnerManager;
67  ReverseGeocodingRunnerManager m_reverseGeocodingRunnerManager;
68 
69  MarblePlacemarkModel *m_placemarkModel;
70 
71  RouteRequest *m_route;
72 
73  int m_index;
74 
75  QTimer m_nominatimTimer;
76 
77  QAction* m_bookmarkAction;
78 
79  QAction* m_mapInput;
80 
81  QAction* m_currentLocationAction;
82 
83  QAction* m_centerAction;
84 
85  QMenu *m_menu;
86 
87  /** Constructor */
88  RoutingInputWidgetPrivate( MarbleModel* model, int index, QWidget *parent );
89 
90  /** Initiate reverse geocoding request to download address */
91  void adjustText();
92 
93  void createMenu( RoutingInputWidget *parent );
94 
95  QMenu* createBookmarkMenu( RoutingInputWidget *parent );
96 
97  static void createBookmarkActions( QMenu* menu, GeoDataFolder* bookmarksFolder, QObject *parent );
98 
99  static QPixmap addDropDownIndicator( const QPixmap &pixmap );
100 
101  void updateDescription();
102 };
103 
104 void RoutingInputWidgetPrivate::updateDescription()
105 {
106  GeoDataPlacemark const placemark = (*m_route)[m_index];
107  GeoDataExtendedData const address = placemark.extendedData();
108  if (address.contains(QStringLiteral("road")) && address.contains(QStringLiteral("city"))) {
109  QString const road = address.value(QStringLiteral("road")).value().toString();
110  QString const city = address.value(QStringLiteral("city")).value().toString();
111 
112  if (address.contains(QStringLiteral("house_number"))) {
113  QString const houseNumber = address.value(QStringLiteral("house_number")).value().toString();
114  QString const name = QObject::tr("%1 %2, %3", "An address with parameters %1=house number, %2=road, %3=city");
115  m_lineEdit->setText( name.arg( houseNumber, road, city ) );
116  } else {
117  QString const name = QObject::tr("%2, %3", "An address with parameters %1=road, %2=city");
118  m_lineEdit->setText( name.arg( road, city ) );
119  }
120  }
121  else if ( m_route->name( m_index ).isEmpty() )
122  {
123  if ( !placemark.address().isEmpty() ) {
124  m_lineEdit->setText( placemark.address() );
125  }
126  else {
127  m_lineEdit->setText( placemark.coordinate().toString().trimmed() );
128  }
129  }
130  else
131  {
132  m_lineEdit->setText( placemark.name() );
133  }
134  m_lineEdit->setCursorPosition( 0 );
135 }
136 
137 RoutingInputLineEdit::RoutingInputLineEdit( QWidget *parent ) :
138  MarbleLineEdit( parent )
139 {
140  setPlaceholderText( QObject::tr( "Address or search term..." ) );
141 }
142 
143 void RoutingInputLineEdit::keyPressEvent(QKeyEvent *event)
144 {
146  bool const returnPressed = event->key() == Qt::Key_Return || event->key() == Qt::Key_Enter;
147  if ( returnPressed ) {
148  event->accept();
149  }
150 }
151 
152 RoutingInputWidgetPrivate::RoutingInputWidgetPrivate( MarbleModel* model, int index, QWidget *parent ) :
153  m_marbleModel( model ),
154  m_lineEdit( nullptr ),
155  m_placemarkRunnerManager( m_marbleModel ),
156  m_reverseGeocodingRunnerManager( m_marbleModel ),
157  m_placemarkModel( nullptr ), m_route( m_marbleModel->routingManager()->routeRequest() ), m_index( index ),
158  m_bookmarkAction( nullptr ), m_mapInput( nullptr ), m_currentLocationAction( nullptr ),
159  m_centerAction( nullptr ),
160  m_menu( nullptr )
161 {
162  m_lineEdit = new RoutingInputLineEdit( parent );
163  m_lineEdit->setDecorator( addDropDownIndicator( m_route->pixmap( m_index ) ) );
164 
165  m_removeButton = new QPushButton( parent );
166  m_removeButton->setIcon(QIcon(QStringLiteral(":/marble/routing/icon-remove.png")));
167  m_removeButton->setToolTip( QObject::tr( "Remove via point" ) );
168  m_removeButton->setFlat( true );
169  m_removeButton->setMaximumWidth( 18 );
170 
171  m_nominatimTimer.setInterval( 1000 );
172  m_nominatimTimer.setSingleShot( true );
173 }
174 
175 void RoutingInputWidgetPrivate::adjustText()
176 {
177  m_nominatimTimer.start();
178 }
179 
180 void RoutingInputWidgetPrivate::createMenu( RoutingInputWidget *parent )
181 {
182  QMenu* result = new QMenu( parent );
183 
184  m_centerAction = result->addAction( QIcon( m_route->pixmap( m_index ) ), QObject::tr( "&Center Map here" ),
185  parent, SLOT(requestActivity()) );
186  result->addSeparator();
187 
188  m_currentLocationAction = result->addAction( QIcon(QStringLiteral(":/icons/gps.png")), QObject::tr("Current &Location"),
189  parent, SLOT(setCurrentLocation()) );
190  m_currentLocationAction->setEnabled( false );
191 
192  m_mapInput = result->addAction(QIcon(QStringLiteral(":/icons/crosshairs.png")), QObject::tr("From &Map..."));
193  m_mapInput->setCheckable( true );
194  QObject::connect( m_mapInput, SIGNAL(triggered(bool)), parent, SLOT(setMapInputModeEnabled(bool)) );
195 
196  m_bookmarkAction = result->addAction(QIcon(QStringLiteral(":/icons/bookmarks.png")), QObject::tr("From &Bookmark"));
197  m_bookmarkAction->setMenu( createBookmarkMenu( parent ) );
198 
199  m_menu = result;
200 }
201 
202 QMenu* RoutingInputWidgetPrivate::createBookmarkMenu( RoutingInputWidget *parent )
203 {
204  QMenu* result = new QMenu( parent );
205  result->addAction(QIcon(QStringLiteral(":/icons/go-home.png")), QObject::tr("&Home"), parent, SLOT(setHomePosition()));
206 
207  QVector<GeoDataFolder*> folders = m_marbleModel->bookmarkManager()->folders();
208 
209  if ( folders.size() == 1 ) {
210  createBookmarkActions( result, folders.first(), parent );
211  } else {
214 
215  for (; i != end; ++i ) {
216  QMenu* menu = result->addMenu(QIcon(QStringLiteral(":/icons/folder-bookmark.png")), (*i)->name());
217  createBookmarkActions( menu, *i, parent );
218  }
219  }
220 
221  return result;
222 }
223 
224 void RoutingInputWidgetPrivate::createBookmarkActions( QMenu* menu, GeoDataFolder* bookmarksFolder, QObject *parent )
225 {
226  QVector<GeoDataPlacemark*> bookmarks = bookmarksFolder->placemarkList();
229 
230  for (; i != end; ++i ) {
231  QAction *bookmarkAction = new QAction( (*i)->name(), parent );
232  bookmarkAction->setData( QVariant::fromValue( (*i)->coordinate() ) );
233  menu->addAction( bookmarkAction );
234  QObject::connect( menu, SIGNAL(triggered(QAction*)), parent, SLOT(setBookmarkPosition(QAction*)) );
235  }
236 }
237 
238 QPixmap RoutingInputWidgetPrivate::addDropDownIndicator(const QPixmap &pixmap)
239 {
240  QPixmap result( pixmap.size() + QSize( 8, pixmap.height() ) );
241  result.fill( QColor( Qt::transparent ) );
242  QPainter painter( &result );
243  painter.drawPixmap( 0, 0, pixmap );
244  QPoint const one( pixmap.width() + 1, pixmap.height() - 8 );
245  QPoint const two( one.x() + 6, one.y() );
246  QPoint const three( one.x() + 3, one.y() + 4 );
247  painter.setRenderHint( QPainter::Antialiasing, true );
248  painter.setPen( Qt::NoPen );
249  painter.setBrush( QColor( Oxygen::aluminumGray4 ) );
250  painter.drawConvexPolygon( QPolygon() << one << two << three );
251  return result;
252 }
253 
255  QWidget( parent ), d( new RoutingInputWidgetPrivate( model, index, this ) )
256 {
257  QHBoxLayout *layout = new QHBoxLayout( this );
259  layout->setSpacing( 0 );
260  layout->setMargin( 0 );
261  layout->addWidget( d->m_lineEdit );
262  layout->addWidget( d->m_removeButton );
263 
264  bool const smallScreen = MarbleGlobal::getInstance()->profiles() & MarbleGlobal::SmallScreen;
265  if ( smallScreen ) {
266  connect( d->m_lineEdit, SIGNAL(decoratorButtonClicked()), this, SLOT(openTargetSelectionDialog()) );
267  } else {
268  d->createMenu( this );
269  connect(d->m_lineEdit, SIGNAL(decoratorButtonClicked()), this, SLOT(showMenu()));
270  }
271 
272  connect( d->m_removeButton, SIGNAL(clicked()), this, SLOT(requestRemoval()) );
273  connect( d->m_marbleModel->bookmarkManager(), SIGNAL(bookmarksChanged()),
274  this, SLOT(reloadBookmarks()) );
275  connect( d->m_marbleModel->positionTracking(), SIGNAL(statusChanged(PositionProviderStatus)),
276  this, SLOT(updateCurrentLocationButton(PositionProviderStatus)) );
277  connect( &d->m_placemarkRunnerManager, SIGNAL(searchResultChanged(QAbstractItemModel*)),
278  this, SLOT(setPlacemarkModel(QAbstractItemModel*)) );
279  connect( &d->m_reverseGeocodingRunnerManager, SIGNAL(reverseGeocodingFinished(GeoDataCoordinates,GeoDataPlacemark)),
280  this, SLOT(retrieveReverseGeocodingResult(GeoDataCoordinates,GeoDataPlacemark)) );
281  connect( d->m_lineEdit, SIGNAL(returnPressed()),
282  this, SLOT(findPlacemarks()) );
283  connect( d->m_lineEdit, SIGNAL(textEdited(QString)),
284  this, SLOT(setInvalid()) );
285  connect( &d->m_placemarkRunnerManager, SIGNAL(searchFinished(QString)),
286  this, SLOT(finishSearch()) );
287  connect( d->m_marbleModel->routingManager()->routeRequest(), SIGNAL(positionChanged(int,GeoDataCoordinates)),
288  this, SLOT(updatePosition(int,GeoDataCoordinates)) );
289  connect( &d->m_nominatimTimer, SIGNAL(timeout()),
290  this, SLOT(reverseGeocoding()) );
291  connect( this, SIGNAL(targetValidityChanged(bool)), this, SLOT(updateCenterButton(bool)) );
292  updateCenterButton( hasTargetPosition() );
293 
294  d->adjustText();
295 }
296 
298 {
299  delete d;
300 }
301 
302 void RoutingInputWidget::reverseGeocoding()
303 {
304  if ( !hasTargetPosition() ) {
305  return;
306  }
307 
308  QString const name = d->m_route->name( d->m_index );
309  if ( name.isEmpty() || name == tr( "Current Location" ) ) {
310  d->m_reverseGeocodingRunnerManager.reverseGeocoding( targetPosition() );
311  } else {
312  d->updateDescription();
313  }
314 }
315 
316 void RoutingInputWidget::setPlacemarkModel( QAbstractItemModel *model )
317 {
318  d->m_placemarkModel = dynamic_cast<MarblePlacemarkModel*>(model);
319 }
320 
322 {
323  if ( d->m_mapInput ) {
324  d->m_mapInput->setChecked( false );
325  }
326  d->m_route->setPosition( d->m_index, position, name );
327  if ( !name.isEmpty() ) {
328  d->updateDescription();
329  }
330  emit targetValidityChanged( true );
331 }
332 
334 {
335  return targetPosition().isValid();
336 }
337 
339 {
340  if ( d->m_index < d->m_route->size() ) {
341  return d->m_route->at( d->m_index );
342  } else {
343  return GeoDataCoordinates();
344  }
345 }
346 
348 {
349  QString text = d->m_lineEdit->text();
350  if ( text.isEmpty() ) {
351  setInvalid();
352  } else {
353  d->m_lineEdit->setBusy(true);
354  d->m_placemarkRunnerManager.findPlacemarks( text );
355  }
356 }
357 
359 {
360  return d->m_placemarkModel;
361 }
362 
363 void RoutingInputWidget::requestActivity()
364 {
365  if ( hasTargetPosition() ) {
366  emit activityRequest( this );
367  }
368 }
369 
370 void RoutingInputWidget::requestRemoval()
371 {
372  emit removalRequest( this );
373 }
374 
376 {
377  return !d->m_lineEdit->text().isEmpty();
378 }
379 
380 void RoutingInputWidget::setMapInputModeEnabled( bool enabled )
381 {
382  emit mapInputModeEnabled( this, enabled );
383 }
384 
385 void RoutingInputWidget::finishSearch()
386 {
387  d->m_lineEdit->setBusy(false);
388  emit searchFinished( this );
389 }
390 
391 void RoutingInputWidget::setInvalid()
392 {
393  d->m_route->setPosition( d->m_index, GeoDataCoordinates() );
394  emit targetValidityChanged( false );
395 }
396 
398 {
399  if ( d->m_mapInput ) {
400  d->m_mapInput->setChecked( false );
401  }
402 }
403 
405 {
406  d->m_index = index;
407  d->m_lineEdit->setBusy(false);
408  d->m_lineEdit->setDecorator( d->addDropDownIndicator( d->m_route->pixmap( index ) ) );
409 }
410 
411 void RoutingInputWidget::updatePosition( int index, const GeoDataCoordinates & )
412 {
413  if ( index == d->m_index ) {
414  d->m_lineEdit->setBusy(false);
416  d->adjustText();
417  }
418 }
419 
421 {
422  d->m_nominatimTimer.stop();
423  d->m_lineEdit->setBusy(false);
424  d->m_route->setPosition( d->m_index, GeoDataCoordinates() );
425  d->m_lineEdit->clear();
426  emit targetValidityChanged( false );
427 }
428 
429 void RoutingInputWidget::retrieveReverseGeocodingResult( const GeoDataCoordinates &, const GeoDataPlacemark &placemark )
430 {
431  (*d->m_route)[d->m_index] = placemark;
432  d->updateDescription();
433 }
434 
436 {
437  if ( d->m_bookmarkAction ) {
438  d->m_bookmarkAction->setMenu( d->createBookmarkMenu( this ) );
439  }
440 }
441 
442 void RoutingInputWidget::setHomePosition()
443 {
444  qreal lon( 0.0 ), lat( 0.0 );
445  int zoom( 0 );
446  d->m_marbleModel->home( lon, lat, zoom );
447  GeoDataCoordinates home( lon, lat, 0.0, GeoDataCoordinates::Degree );
448  setTargetPosition( home );
449  requestActivity();
450 }
451 
452 void RoutingInputWidget::updateCurrentLocationButton( PositionProviderStatus status )
453 {
454  if ( d->m_currentLocationAction ) {
455  d->m_currentLocationAction->setEnabled( status == PositionProviderStatusAvailable );
456  }
457 }
458 
459 void RoutingInputWidget::setCurrentLocation()
460 {
461  setTargetPosition( d->m_marbleModel->positionTracking()->currentLocation() );
462  requestActivity();
463 }
464 
465 void RoutingInputWidget::updateCenterButton( bool hasPosition )
466 {
467  if ( d->m_centerAction ) {
468  d->m_centerAction->setEnabled( hasPosition );
469  }
470 }
471 
472 void RoutingInputWidget::setBookmarkPosition( QAction* bookmark )
473 {
474  if ( !bookmark->data().isNull() ) {
475  setTargetPosition( bookmark->data().value<GeoDataCoordinates>() );
476  requestActivity();
477  }
478 }
479 
480 void RoutingInputWidget::openTargetSelectionDialog()
481 {
482  QPointer<GoToDialog> dialog = new GoToDialog( d->m_marbleModel, this );
483  dialog->setWindowTitle( tr( "Choose Placemark" ) );
484  dialog->setShowRoutingItems( false );
485  dialog->setSearchEnabled( false );
486  if ( dialog->exec() == QDialog::Accepted ) {
487  const GeoDataCoordinates coordinates = dialog->coordinates();
488  setTargetPosition( coordinates );
489  }
490  delete dialog;
491 }
492 
493 void RoutingInputWidget::showMenu()
494 {
495  d->m_menu->exec( mapToGlobal( QPoint( 0, size().height() ) ) );
496 }
497 
498 } // namespace Marble
499 
500 #include "moc_RoutingInputWidget.cpp"
bool isNull() const const
A 3d point representation.
QString name() const const
QVariant fromValue(const T &value)
QSize size() const const
void setMargin(int margin)
T value() const const
QLayout * layout() const const
void setMenu(QMenu *menu)
~RoutingInputWidget() override
Destructor.
QAction * addSeparator()
void abortMapInputRequest()
Cancel a started input request from the map.
void reloadBookmarks()
Reload the bookmarks menu.
QVector::const_iterator constEnd() const const
virtual void keyPressEvent(QKeyEvent *event) override
T & first()
QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
bool hasInput() const
Returns false iff the input text is empty.
void targetValidityChanged(bool targetValid)
hasTargetPosition changed because of selecting a placemark or changing the search term
QAction * addAction(const QString &text)
void setTargetPosition(const GeoDataCoordinates &position, const QString &name=QString())
Set the target position to the given coordinates, eliminating any previously set positions.
void mapInputModeEnabled(RoutingInputWidget *, bool enabled)
User requests position input from the map.
void setIndex(int index)
Change the data index in the route request model.
bool isEmpty() const const
void searchFinished(RoutingInputWidget *)
All runners are finished.
QAction * addMenu(QMenu *menu)
GeoDataCoordinates targetPosition() const
Returns the geoposition selected by the user, or a default constructed geoposition if hasTargetPositi...
Q_SCRIPTABLE CaptureState status()
bool hasTargetPosition() const
Returns true if the user has selected a valid geo position.
void clear()
Remove target position and user input, if any.
void activityRequest(RoutingInputWidget *)
User requests to activate this widget.
Binds a QML item to a specific geodetic location in screen coordinates.
PostalAddress address(const QVariant &location)
QPoint mapToGlobal(const QPoint &pos) const const
bool isValid() const
Returns.
KActionMenu * createMenu(KColorSchemeManager *manager, QObject *parent=nullptr)
int height() const const
Key_Return
void findPlacemarks()
Search for placemarks matching the current input text.
void setCheckable(bool)
a class representing a point of interest on the map
RoutingInputWidget(MarbleModel *model, int index, QWidget *parent=nullptr)
Constructor.
QVariant data() const const
void setData(const QVariant &userData)
void setSizeConstraint(QLayout::SizeConstraint)
MarblePlacemarkModel * searchResultModel()
Returns the placemark model that contains search results.
void setEnabled(bool)
void addWidget(QWidget *w)
const char * name(StandardAction id)
void setSpacing(int)
int size() const const
This class represents a model of all place marks which are currently available through a given Placem...
QVector::const_iterator constBegin() const const
QString tr(const char *sourceText, const char *disambiguation, int n)
void removalRequest(RoutingInputWidget *)
User requests to remove this widget.
QObject * parent() const const
The data model (not based on QAbstractModel) for a MarbleWidget.
Definition: MarbleModel.h:86
const QList< QKeySequence > & end()
transparent
int width() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2023 The KDE developers.
Generated on Fri Dec 1 2023 04:12:38 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.