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 "GeoDataData.h"
20#include "GeoDataExtendedData.h"
21#include "GeoDataFolder.h"
22#include "GeoDataPlacemark.h"
23#include "GoToDialog.h"
24#include "MarbleColors.h"
25#include "MarbleLineEdit.h"
26#include "MarbleModel.h"
27#include "MarbleWidget.h"
28#include "PositionTracking.h"
29#include "ReverseGeocodingRunnerManager.h"
30#include "SearchRunnerManager.h"
31#include "routing/RoutingManager.h"
32
33#include <QHBoxLayout>
34#include <QIcon>
35#include <QKeyEvent>
36#include <QMenu>
37#include <QPainter>
38#include <QPushButton>
39#include <QTimer>
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 = nullptr;
61
62 RoutingInputLineEdit *m_lineEdit = nullptr;
63
64 QPushButton *m_removeButton = nullptr;
65
66 SearchRunnerManager m_placemarkRunnerManager;
67 ReverseGeocodingRunnerManager m_reverseGeocodingRunnerManager;
68
69 MarblePlacemarkModel *m_placemarkModel = nullptr;
70
71 RouteRequest *m_route = nullptr;
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 } else if (m_route->name(m_index).isEmpty()) {
121 if (!placemark.address().isEmpty()) {
122 m_lineEdit->setText(placemark.address());
123 } else {
124 m_lineEdit->setText(placemark.coordinate().toString().trimmed());
125 }
126 } else {
127 m_lineEdit->setText(placemark.name());
128 }
129 m_lineEdit->setCursorPosition(0);
130}
131
132RoutingInputLineEdit::RoutingInputLineEdit(QWidget *parent)
133 : MarbleLineEdit(parent)
134{
135 setPlaceholderText(QObject::tr("Address or search term..."));
136}
137
138void RoutingInputLineEdit::keyPressEvent(QKeyEvent *event)
139{
140 MarbleLineEdit::keyPressEvent(event);
141 bool const returnPressed = event->key() == Qt::Key_Return || event->key() == Qt::Key_Enter;
142 if (returnPressed) {
143 event->accept();
144 }
145}
146
147RoutingInputWidgetPrivate::RoutingInputWidgetPrivate(MarbleModel *model, int index, QWidget *parent)
148 : m_marbleModel(model)
149 , m_lineEdit(nullptr)
150 , m_placemarkRunnerManager(m_marbleModel)
151 , m_reverseGeocodingRunnerManager(m_marbleModel)
152 , m_placemarkModel(nullptr)
153 , m_route(m_marbleModel->routingManager()->routeRequest())
154 , m_index(index)
155 , m_bookmarkAction(nullptr)
156 , m_mapInput(nullptr)
157 , m_currentLocationAction(nullptr)
158 , m_centerAction(nullptr)
159 , m_menu(nullptr)
160{
161 m_lineEdit = new RoutingInputLineEdit(parent);
162 m_lineEdit->setDecorator(addDropDownIndicator(m_route->pixmap(m_index)));
163
164 m_removeButton = new QPushButton(parent);
165 m_removeButton->setIcon(QIcon(QStringLiteral(":/marble/routing/icon-remove.png")));
166 m_removeButton->setToolTip(QObject::tr("Remove via point"));
167 m_removeButton->setFlat(true);
168 m_removeButton->setMaximumWidth(18);
169
170 m_nominatimTimer.setInterval(1000);
171 m_nominatimTimer.setSingleShot(true);
172}
173
174void RoutingInputWidgetPrivate::adjustText()
175{
176 m_nominatimTimer.start();
177}
178
179void RoutingInputWidgetPrivate::createMenu(RoutingInputWidget *parent)
180{
181 auto result = new QMenu(parent);
182
183 m_centerAction = result->addAction(QIcon(m_route->pixmap(m_index)), QObject::tr("&Center Map here"), parent, SLOT(requestActivity()));
184 result->addSeparator();
185
186 m_currentLocationAction = result->addAction(QIcon(QStringLiteral(":/icons/gps.png")), QObject::tr("Current &Location"), parent, SLOT(setCurrentLocation()));
187 m_currentLocationAction->setEnabled(false);
188
189 m_mapInput = result->addAction(QIcon(QStringLiteral(":/icons/crosshairs.png")), QObject::tr("From &Map..."));
190 m_mapInput->setCheckable(true);
191 QObject::connect(m_mapInput, SIGNAL(triggered(bool)), parent, SLOT(setMapInputModeEnabled(bool)));
192
193 m_bookmarkAction = result->addAction(QIcon(QStringLiteral(":/icons/bookmarks.png")), QObject::tr("From &Bookmark"));
194 m_bookmarkAction->setMenu(createBookmarkMenu(parent));
195
196 m_menu = result;
197}
198
199QMenu *RoutingInputWidgetPrivate::createBookmarkMenu(RoutingInputWidget *parent)
200{
201 auto result = new QMenu(parent);
202 result->addAction(QIcon(QStringLiteral(":/icons/go-home.png")), QObject::tr("&Home"), parent, SLOT(setHomePosition()));
203
204 QList<GeoDataFolder *> folders = m_marbleModel->bookmarkManager()->folders();
205
206 if (folders.size() == 1) {
207 createBookmarkActions(result, folders.first(), parent);
208 } else {
211
212 for (; i != end; ++i) {
213 QMenu *menu = result->addMenu(QIcon(QStringLiteral(":/icons/folder-bookmark.png")), (*i)->name());
214 createBookmarkActions(menu, *i, parent);
215 }
216 }
217
218 return result;
219}
220
221void RoutingInputWidgetPrivate::createBookmarkActions(QMenu *menu, GeoDataFolder *bookmarksFolder, QObject *parent)
222{
223 QList<GeoDataPlacemark *> bookmarks = bookmarksFolder->placemarkList();
226
227 for (; i != end; ++i) {
228 auto bookmarkAction = new QAction((*i)->name(), parent);
229 bookmarkAction->setData(QVariant::fromValue((*i)->coordinate()));
230 menu->addAction(bookmarkAction);
231 QObject::connect(menu, SIGNAL(triggered(QAction *)), parent, SLOT(setBookmarkPosition(QAction *)));
232 }
233}
234
235QPixmap RoutingInputWidgetPrivate::addDropDownIndicator(const QPixmap &pixmap)
236{
237 QPixmap result(pixmap.size() + QSize(8, pixmap.height()));
238 result.fill(QColor(Qt::transparent));
239 QPainter painter(&result);
240 painter.drawPixmap(0, 0, pixmap);
241 QPoint const one(pixmap.width() + 1, pixmap.height() - 8);
242 QPoint const two(one.x() + 6, one.y());
243 QPoint const three(one.x() + 3, one.y() + 4);
244 painter.setRenderHint(QPainter::Antialiasing, true);
245 painter.setPen(Qt::NoPen);
246 painter.setBrush(QColor(Oxygen::aluminumGray4));
247 painter.drawConvexPolygon(QPolygon() << one << two << three);
248 return result;
249}
250
251RoutingInputWidget::RoutingInputWidget(MarbleModel *model, int index, QWidget *parent)
252 : QWidget(parent)
253 , d(new RoutingInputWidgetPrivate(model, index, this))
254{
255 auto layout = new QHBoxLayout(this);
257 layout->setSpacing(0);
259 layout->addWidget(d->m_lineEdit);
260 layout->addWidget(d->m_removeButton);
261
262 bool const smallScreen = MarbleGlobal::getInstance()->profiles() & MarbleGlobal::SmallScreen;
263 if (smallScreen) {
264 connect(d->m_lineEdit, &MarbleLineEdit::decoratorButtonClicked, this, &RoutingInputWidget::openTargetSelectionDialog);
265 } else {
266 d->createMenu(this);
267 connect(d->m_lineEdit, &MarbleLineEdit::decoratorButtonClicked, this, &RoutingInputWidget::showMenu);
268 }
269
270 connect(d->m_removeButton, &QAbstractButton::clicked, this, &RoutingInputWidget::requestRemoval);
271 connect(d->m_marbleModel->bookmarkManager(), SIGNAL(bookmarksChanged()), this, SLOT(reloadBookmarks()));
272 connect(d->m_marbleModel->positionTracking(),
273 SIGNAL(statusChanged(PositionProviderStatus)),
274 this,
275 SLOT(updateCurrentLocationButton(PositionProviderStatus)));
276 connect(&d->m_placemarkRunnerManager, SIGNAL(searchResultChanged(QAbstractItemModel *)), this, SLOT(setPlacemarkModel(QAbstractItemModel *)));
277 connect(&d->m_reverseGeocodingRunnerManager,
278 SIGNAL(reverseGeocodingFinished(GeoDataCoordinates, GeoDataPlacemark)),
279 this,
280 SLOT(retrieveReverseGeocodingResult(GeoDataCoordinates, GeoDataPlacemark)));
281 connect(d->m_lineEdit, SIGNAL(returnPressed()), this, SLOT(findPlacemarks()));
282 connect(d->m_lineEdit, &QLineEdit::textEdited, this, &RoutingInputWidget::setInvalid);
283 connect(&d->m_placemarkRunnerManager, SIGNAL(searchFinished(QString)), this, SLOT(finishSearch()));
284 connect(d->m_marbleModel->routingManager()->routeRequest(),
285 SIGNAL(positionChanged(int, GeoDataCoordinates)),
286 this,
287 SLOT(updatePosition(int, GeoDataCoordinates)));
288 connect(&d->m_nominatimTimer, &QTimer::timeout, this, &RoutingInputWidget::reverseGeocoding);
289 connect(this, SIGNAL(targetValidityChanged(bool)), this, SLOT(updateCenterButton(bool)));
290 updateCenterButton(hasTargetPosition());
291
292 d->adjustText();
293}
294
299
300void RoutingInputWidget::reverseGeocoding()
301{
302 if (!hasTargetPosition()) {
303 return;
304 }
305
306 QString const name = d->m_route->name(d->m_index);
307 if (name.isEmpty() || name == tr("Current Location")) {
308 d->m_reverseGeocodingRunnerManager.reverseGeocoding(targetPosition());
309 } else {
310 d->updateDescription();
311 }
312}
313
314void RoutingInputWidget::setPlacemarkModel(QAbstractItemModel *model)
315{
316 d->m_placemarkModel = dynamic_cast<MarblePlacemarkModel *>(model);
317}
318
320{
321 if (d->m_mapInput) {
322 d->m_mapInput->setChecked(false);
323 }
324 d->m_route->setPosition(d->m_index, position, name);
325 if (!name.isEmpty()) {
326 d->updateDescription();
327 }
329}
330
332{
333 return targetPosition().isValid();
334}
335
337{
338 if (d->m_index < d->m_route->size()) {
339 return d->m_route->at(d->m_index);
340 } else {
341 return {};
342 }
343}
344
346{
347 QString text = d->m_lineEdit->text();
348 if (text.isEmpty()) {
349 setInvalid();
350 } else {
351 d->m_lineEdit->setBusy(true);
352 d->m_placemarkRunnerManager.findPlacemarks(text);
353 }
354}
355
357{
358 return d->m_placemarkModel;
359}
360
361void RoutingInputWidget::requestActivity()
362{
363 if (hasTargetPosition()) {
365 }
366}
367
368void RoutingInputWidget::requestRemoval()
369{
371}
372
374{
375 return !d->m_lineEdit->text().isEmpty();
376}
377
378void RoutingInputWidget::setMapInputModeEnabled(bool enabled)
379{
381}
382
383void RoutingInputWidget::finishSearch()
384{
385 d->m_lineEdit->setBusy(false);
387}
388
389void RoutingInputWidget::setInvalid()
390{
391 d->m_route->setPosition(d->m_index, GeoDataCoordinates());
393}
394
396{
397 if (d->m_mapInput) {
398 d->m_mapInput->setChecked(false);
399 }
400}
401
403{
404 d->m_index = index;
405 d->m_lineEdit->setBusy(false);
406 d->m_lineEdit->setDecorator(d->addDropDownIndicator(d->m_route->pixmap(index)));
407}
408
409void RoutingInputWidget::updatePosition(int index, const GeoDataCoordinates &)
410{
411 if (index == d->m_index) {
412 d->m_lineEdit->setBusy(false);
414 d->adjustText();
415 }
416}
417
419{
420 d->m_nominatimTimer.stop();
421 d->m_lineEdit->setBusy(false);
422 d->m_route->setPosition(d->m_index, GeoDataCoordinates());
423 d->m_lineEdit->clear();
425}
426
427void RoutingInputWidget::retrieveReverseGeocodingResult(const GeoDataCoordinates &, const GeoDataPlacemark &placemark)
428{
429 (*d->m_route)[d->m_index] = placemark;
430 d->updateDescription();
431}
432
434{
435 if (d->m_bookmarkAction) {
436 d->m_bookmarkAction->setMenu(d->createBookmarkMenu(this));
437 }
438}
439
440void RoutingInputWidget::setHomePosition()
441{
442 qreal lon(0.0), lat(0.0);
443 int zoom(0);
444 d->m_marbleModel->home(lon, lat, zoom);
445 GeoDataCoordinates home(lon, lat, 0.0, GeoDataCoordinates::Degree);
446 setTargetPosition(home);
447 requestActivity();
448}
449
450void RoutingInputWidget::updateCurrentLocationButton(PositionProviderStatus status)
451{
452 if (d->m_currentLocationAction) {
453 d->m_currentLocationAction->setEnabled(status == PositionProviderStatusAvailable);
454 }
455}
456
457void RoutingInputWidget::setCurrentLocation()
458{
459 setTargetPosition(d->m_marbleModel->positionTracking()->currentLocation());
460 requestActivity();
461}
462
463void RoutingInputWidget::updateCenterButton(bool hasPosition)
464{
465 if (d->m_centerAction) {
466 d->m_centerAction->setEnabled(hasPosition);
467 }
468}
469
470void RoutingInputWidget::setBookmarkPosition(QAction *bookmark)
471{
472 if (!bookmark->data().isNull()) {
473 setTargetPosition(bookmark->data().value<GeoDataCoordinates>());
474 requestActivity();
475 }
476}
477
478void RoutingInputWidget::openTargetSelectionDialog()
479{
480 QPointer<GoToDialog> dialog = new GoToDialog(d->m_marbleModel, this);
481 dialog->setWindowTitle(tr("Choose Placemark"));
482 dialog->setShowRoutingItems(false);
483 dialog->setSearchEnabled(false);
484 if (dialog->exec() == QDialog::Accepted) {
485 const GeoDataCoordinates coordinates = dialog->coordinates();
486 setTargetPosition(coordinates);
487 }
488 delete dialog;
489}
490
491void RoutingInputWidget::showMenu()
492{
493 d->m_menu->exec(mapToGlobal(QPoint(0, size().height())));
494}
495
496} // namespace Marble
497
498#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:84
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()
QString name(GameStandardAction id)
QAction * end(const QObject *recvr, const char *slot, QObject *parent)
PostalAddress address(const QVariant &location)
Binds a QML item to a specific geodetic location in screen coordinates.
void clicked(bool checked)
void setChecked(bool)
QVariant data() const const
void setEnabled(bool)
void setMenu(QMenu *menu)
QString name() const const
void addWidget(QWidget *w)
void setContentsMargins(const QMargins &margins)
void setSizeConstraint(SizeConstraint)
virtual void setSpacing(int)
void clear()
void setCursorPosition(int)
void setText(const QString &)
void textEdited(const QString &text)
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 * exec()
Q_EMITQ_EMIT
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
QObject * parent() const const
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()
void timeout()
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 Mon Nov 4 2024 16:37:03 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.