Marble

RoutingInputWidget.cpp
1// SPDX-License-Identifier: LGPL-2.1-or-later
2//
3// SPDX-FileCopyrightText: 2010 Dennis Nienhüser <nienhueser@kde.org>
4// SPDX-FileCopyrightText: 2012 Illya Kovalevskyy <illya.kovalevskyy@gmail.com>
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
41namespace Marble
42{
43
44/**
45 * A MarbleLineEdit that swallows enter/return pressed
46 * key events
47 */
48class RoutingInputLineEdit : public MarbleLineEdit
49{
50public:
51 explicit RoutingInputLineEdit( QWidget *parent = nullptr );
52
53protected:
54 void keyPressEvent(QKeyEvent *) override;
55};
56
57class RoutingInputWidgetPrivate
58{
59public:
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
104void 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
137RoutingInputLineEdit::RoutingInputLineEdit( QWidget *parent ) :
138 MarbleLineEdit( parent )
139{
140 setPlaceholderText( QObject::tr( "Address or search term..." ) );
141}
142
143void RoutingInputLineEdit::keyPressEvent(QKeyEvent *event)
144{
145 MarbleLineEdit::keyPressEvent( event );
146 bool const returnPressed = event->key() == Qt::Key_Return || event->key() == Qt::Key_Enter;
147 if ( returnPressed ) {
148 event->accept();
149 }
150}
151
152RoutingInputWidgetPrivate::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
175void RoutingInputWidgetPrivate::adjustText()
176{
177 m_nominatimTimer.start();
178}
179
180void 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
202QMenu* 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
224void 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
238QPixmap 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
254RoutingInputWidget::RoutingInputWidget( MarbleModel* model, int index, QWidget *parent ) :
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
301
302void 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
316void 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 }
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
363void RoutingInputWidget::requestActivity()
364{
365 if ( hasTargetPosition() ) {
366 emit activityRequest( this );
367 }
368}
369
370void RoutingInputWidget::requestRemoval()
371{
372 emit removalRequest( this );
373}
374
376{
377 return !d->m_lineEdit->text().isEmpty();
378}
379
380void RoutingInputWidget::setMapInputModeEnabled( bool enabled )
381{
383}
384
385void RoutingInputWidget::finishSearch()
386{
387 d->m_lineEdit->setBusy(false);
388 emit searchFinished( this );
389}
390
391void RoutingInputWidget::setInvalid()
392{
393 d->m_route->setPosition( d->m_index, GeoDataCoordinates() );
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
411void 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();
427}
428
429void 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
442void 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
452void RoutingInputWidget::updateCurrentLocationButton( PositionProviderStatus status )
453{
454 if ( d->m_currentLocationAction ) {
455 d->m_currentLocationAction->setEnabled( status == PositionProviderStatusAvailable );
456 }
457}
458
459void RoutingInputWidget::setCurrentLocation()
460{
461 setTargetPosition( d->m_marbleModel->positionTracking()->currentLocation() );
462 requestActivity();
463}
464
465void RoutingInputWidget::updateCenterButton( bool hasPosition )
466{
467 if ( d->m_centerAction ) {
468 d->m_centerAction->setEnabled( hasPosition );
469 }
470}
471
472void RoutingInputWidget::setBookmarkPosition( QAction* bookmark )
473{
474 if ( !bookmark->data().isNull() ) {
475 setTargetPosition( bookmark->data().value<GeoDataCoordinates>() );
476 requestActivity();
477 }
478}
479
480void 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
493void RoutingInputWidget::showMenu()
494{
495 d->m_menu->exec( mapToGlobal( QPoint( 0, size().height() ) ) );
496}
497
498} // namespace Marble
499
500#include "moc_RoutingInputWidget.cpp"
This file contains the headers for MarbleModel.
This file contains the headers for MarbleWidget.
A 3d point representation.
a class representing a point of interest on the map
The data model (not based on QAbstractModel) for a MarbleWidget.
Definition MarbleModel.h:87
BookmarkManager * bookmarkManager()
return instance of BookmarkManager
void home(qreal &lon, qreal &lat, int &zoom) const
get the home point
This class represents a model of all place marks which are currently available through a given Placem...
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.
GeoDataCoordinates at(int index) const
Accessor for the n-th position.
QPixmap pixmap(int index, int size=-1, int margin=2) const
Returns a pixmap which indicates the position of the element.
void reloadBookmarks()
Reload the bookmarks menu.
void targetValidityChanged(bool targetValid)
hasTargetPosition changed because of selecting a placemark or changing the search term
void removalRequest(RoutingInputWidget *)
User requests to remove this widget.
void clear()
Remove target position and user input, if any.
void searchFinished(RoutingInputWidget *)
All runners are finished.
GeoDataCoordinates targetPosition() const
Returns the geoposition selected by the user, or a default constructed geoposition if hasTargetPositi...
bool hasTargetPosition() const
Returns true if the user has selected a valid geo position.
void abortMapInputRequest()
Cancel a started input request from the map.
void setTargetPosition(const GeoDataCoordinates &position, const QString &name=QString())
Set the target position to the given coordinates, eliminating any previously set positions.
MarblePlacemarkModel * searchResultModel()
Returns the placemark model that contains search results.
void findPlacemarks()
Search for placemarks matching the current input text.
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.
void activityRequest(RoutingInputWidget *)
User requests to activate this widget.
bool hasInput() const
Returns false iff the input text is empty.
~RoutingInputWidget() override
Destructor.
RouteRequest * routeRequest()
Returns the current route request.
Q_SCRIPTABLE CaptureState status()
PostalAddress address(const QVariant &location)
const QList< QKeySequence > & end()
QString name(StandardShortcut id)
Binds a QML item to a specific geodetic location in screen coordinates.
void setCheckable(bool)
void setChecked(bool)
QVariant data() const const
void setEnabled(bool)
void setData(const QVariant &data)
void setMenu(QMenu *menu)
QString name() const const
void addWidget(QWidget *w)
void setSizeConstraint(SizeConstraint)
virtual void setSpacing(int)
void clear()
void setCursorPosition(int)
void setText(const QString &)
const_iterator constBegin() const const
const_iterator constEnd() const const
T & first()
qsizetype size() const const
QAction * addAction(const QIcon &icon, const QString &text, Functor functor, const QKeySequence &shortcut)
QAction * addMenu(QMenu *menu)
QAction * addSeparator()
QAction * exec()
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
QObject * parent() const const
T qobject_cast(QObject *object)
QString tr(const char *sourceText, const char *disambiguation, int n)
int height() const const
QSize size() const const
int width() const const
QString arg(Args &&... args) const const
bool isEmpty() const const
transparent
Key_Return
void stop()
QVariant fromValue(T &&value)
bool isNull() const const
T value() const const
QLayout * layout() const const
QPoint mapToGlobal(const QPoint &pos) const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Tue Mar 26 2024 11:18:17 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.