1 /* SPDX-FileCopyrightText: 2020-2021 Tobias Leupold <tobias.leupold@gmx.de>
2 
3    SPDX-License-Identifier: GPL-3.0-or-later OR LicenseRef-KDE-Accepted-GPL
4 */
5 
6 // Local includes
7 #include "BookmarksList.h"
8 #include "SharedObjects.h"
9 #include "Settings.h"
10 #include "MapWidget.h"
11 #include "CoordinatesDialog.h"
12 
13 // KDE includes
14 #include <KLocalizedString>
15 
16 // Qt includes
17 #include <QDebug>
18 #include <QMenu>
19 #include <QInputDialog>
20 #include <QMessageBox>
21 #include <QListWidgetItem>
22 #include <QApplication>
23 
BookmarksList(SharedObjects * sharedObjects,QWidget * parent)24 BookmarksList::BookmarksList(SharedObjects *sharedObjects, QWidget *parent)
25     : QListWidget(parent),
26       m_settings(sharedObjects->settings()),
27       m_elevationEngine(sharedObjects->elevationEngine()),
28       m_mapWidget(sharedObjects->mapWidget())
29 {
30     setSortingEnabled(true);
31 
32     // Load the saved bookmarks
33     m_bookmarks = m_settings->bookmarks();
34     const auto labels = m_bookmarks.keys();
35     for (const auto &label : labels) {
36         auto *item = new QListWidgetItem(label);
37         addItem(item);
38     }
39 
40     // Show the first bookmark's coordinates if we have bookmarks
41     if (! m_bookmarks.isEmpty()) {
42         setCurrentItem(0);
43     }
44 
45     connect(this, &QListWidget::itemClicked, this, &BookmarksList::centerBookmark);
46     connect(this, &QListWidget::currentItemChanged, this, &BookmarksList::itemHighlighted);
47 
48     connect(m_elevationEngine, &ElevationEngine::elevationProcessed,
49             this, &BookmarksList::elevationProcessed);
50     connect(m_elevationEngine, &ElevationEngine::lookupFailed,
51             this, &BookmarksList::restoreAfterElevationLookup);
52 
53     // Context menu
54 
55     m_contextMenu = new QMenu(this);
56 
57     auto *newBookmarkAction = m_contextMenu->addAction(i18n("Add new bookmark for current map "
58                                                             "center"));
59     connect(newBookmarkAction, &QAction::triggered, this, &BookmarksList::newBookmark);
60 
61     auto *newManualBookmarkAction = m_contextMenu->addAction(i18n("Add new manual bookmark"));
62     connect(newManualBookmarkAction, &QAction::triggered, this, &BookmarksList::newManualBookmark);
63 
64     m_contextMenu->addSeparator();
65 
66     m_renameBookmark = m_contextMenu->addAction(i18n("Rename bookmark"));
67     connect(m_renameBookmark, &QAction::triggered, this, &BookmarksList::renameBookmark);
68 
69     m_editCoordinates = m_contextMenu->addAction(i18n("Edit coordinates"));
70     connect(m_editCoordinates, &QAction::triggered, this, &BookmarksList::editCoordinates);
71 
72     m_contextMenu->addSeparator();
73 
74     m_lookupElevation = m_contextMenu->addAction(i18n("Lookup elevation"));
75     connect(m_lookupElevation, &QAction::triggered, this, &BookmarksList::lookupElevation);
76 
77     m_setElevation = m_contextMenu->addAction(i18n("Set elevation manually"));
78     connect(m_setElevation, &QAction::triggered, this, &BookmarksList::setElevation);
79 
80     m_contextMenu->addSeparator();
81 
82     m_deleteBookmark = m_contextMenu->addAction(i18n("Delete bookmark"));
83     connect(m_deleteBookmark, &QAction::triggered, this, &BookmarksList::deleteBookmark);
84 
85     setContextMenuPolicy(Qt::CustomContextMenu);
86     connect(this, &QWidget::customContextMenuRequested, this, &BookmarksList::showContextMenu);
87 }
88 
showContextMenu(const QPoint & point)89 void BookmarksList::showContextMenu(const QPoint &point)
90 {
91     m_contextMenuItem = itemAt(point);
92 
93     const bool itemSelected = m_contextMenuItem != nullptr;
94     m_renameBookmark->setEnabled(itemSelected);
95     m_lookupElevation->setEnabled(itemSelected);
96     m_setElevation->setEnabled(itemSelected);
97     m_deleteBookmark->setEnabled(itemSelected);
98     m_editCoordinates->setEnabled(itemSelected);
99 
100     m_contextMenu->exec(mapToGlobal(point));
101 }
102 
newBookmark()103 void BookmarksList::newBookmark()
104 {
105     auto [ label, okay ] = getString(i18n("New Bookmark"), i18n("Label for the new bookmark:"));
106     if (! okay) {
107         return;
108     }
109 
110     label = label.simplified();
111     const auto originalLabel = label;
112     if (label.isEmpty()) {
113         label = i18n("Untitled");
114     }
115 
116     saveBookmark(label, m_mapWidget->currentCenter());
117 }
118 
newManualBookmark()119 void BookmarksList::newManualBookmark()
120 {
121     CoordinatesDialog dialog(CoordinatesDialog::Mode::ManualBookmark,
122                              m_settings->lookupElevationAutomatically());
123     if (! dialog.exec()) {
124         return;
125     }
126 
127     saveBookmark(dialog.label(),
128                  Coordinates(dialog.lon(), dialog.lat(), dialog.alt(), true));
129 }
130 
saveBookmark(QString label,const Coordinates & coordinates)131 void BookmarksList::saveBookmark(QString label, const Coordinates &coordinates)
132 {
133     const QString originalLabel = label;
134     QString searchLabel = label;
135     int addition = 0;
136     while (! findItems(searchLabel, Qt::MatchExactly).isEmpty()) {
137         addition++;
138         searchLabel = i18nc("Adding a consecutive number to a bookmark label to make it unique",
139                             "%1 (%2)", label, addition);
140     }
141     label = searchLabel;
142 
143     m_bookmarks[label] = coordinates;
144 
145     auto *item = new QListWidgetItem(label);
146     addItem(item);
147     setCurrentItem(item);
148 
149     if (m_settings->lookupElevationAutomatically()) {
150         requestElevation(label);
151     }
152 
153     emit bookmarksChanged();
154 
155     if (label != originalLabel) {
156         QMessageBox::warning(this, i18n("Add new bookmark"),
157             i18n("The bookmark \"%1\" already exists, created \"%2\" instead.",
158                  originalLabel, label));
159     }
160 
161     m_mapWidget->centerCoordinates(coordinates);
162 }
163 
requestElevation(const QString & id)164 void BookmarksList::requestElevation(const QString &id)
165 {
166     QApplication::setOverrideCursor(Qt::WaitCursor);
167     setEnabled(false);
168     m_elevationEngine->request(ElevationEngine::Target::Bookmark, { id },
169                                { m_bookmarks.value(id) });
170 }
171 
itemHighlighted(QListWidgetItem * item,QListWidgetItem *)172 void BookmarksList::itemHighlighted(QListWidgetItem *item, QListWidgetItem *)
173 {
174     if (item == nullptr) {
175         emit showInfo(Coordinates());
176         return;
177     }
178 
179     emit showInfo(m_bookmarks.value(item->text()));
180 }
181 
centerBookmark(QListWidgetItem * item)182 void BookmarksList::centerBookmark(QListWidgetItem *item)
183 {
184     m_mapWidget->centerCoordinates(m_bookmarks.value(item->text()));
185 }
186 
getString(const QString & title,const QString & label,const QString & text)187 BookmarksList::EnteredString BookmarksList::getString(const QString &title, const QString &label,
188                                                       const QString &text)
189 {
190     bool okay = false;
191     auto string = QInputDialog::getText(this, title, label, QLineEdit::Normal, text, &okay);
192     return { string, okay };
193 }
194 
renameBookmark()195 void BookmarksList::renameBookmark()
196 {
197     const auto currentLabel = m_contextMenuItem->text();
198     auto [ newLabel, okay ] = getString(i18n("Rename Bookmark"),
199                                         i18n("New label for the new bookmark:"), currentLabel);
200     if (! okay || newLabel == currentLabel) {
201         return;
202     }
203 
204     newLabel = newLabel.simplified();
205     if (! findItems(newLabel, Qt::MatchExactly).isEmpty()) {
206         QMessageBox::warning(this, i18n("Rename Bookmark"),
207                              i18n("The label \"%1\" is already in use. Please choose another "
208                                   "name.", newLabel));
209         return;
210     }
211 
212     m_bookmarks[newLabel] = m_bookmarks.value(currentLabel);
213     m_bookmarks.remove(currentLabel);
214     m_contextMenuItem->setText(newLabel);
215 
216     emit bookmarksChanged();
217 }
218 
deleteBookmark()219 void BookmarksList::deleteBookmark()
220 {
221     if (QMessageBox::question(this, i18n("Delete bookmark"),
222         i18n("Really delete bookmark \"%1\"?", m_contextMenuItem->text()),
223         QMessageBox::Yes | QMessageBox::No, QMessageBox::No) != QMessageBox::Yes) {
224 
225         return;
226     }
227 
228     m_bookmarks.remove(m_contextMenuItem->text());
229     const auto *item = takeItem(row(m_contextMenuItem));
230     delete item;
231 
232     emit bookmarksChanged();
233 }
234 
elevationProcessed(ElevationEngine::Target target,const QVector<QString> & ids,const QVector<double> & elevations)235 void BookmarksList::elevationProcessed(ElevationEngine::Target target, const QVector<QString> &ids,
236                                        const QVector<double> &elevations)
237 {
238     if (target != ElevationEngine::Target::Bookmark) {
239         return;
240     }
241 
242     restoreAfterElevationLookup();
243     const auto id = ids.at(0);
244     m_bookmarks[id].setAlt(elevations.at(0));
245     emit showInfo(m_bookmarks.value(id));
246 }
247 
restoreAfterElevationLookup()248 void BookmarksList::restoreAfterElevationLookup()
249 {
250     if (! isEnabled()) {
251         QApplication::restoreOverrideCursor();
252         setEnabled(true);
253     }
254 }
255 
lookupElevation()256 void BookmarksList::lookupElevation()
257 {
258     requestElevation(m_contextMenuItem->text());
259 }
260 
setElevation()261 void BookmarksList::setElevation()
262 {
263     const auto id = m_contextMenuItem->text();
264 
265     bool okay = false;
266     auto elevation = QInputDialog::getDouble(this, i18n("Set elevation"),
267                                              i18n("Elevation for \"%1\" (m)", id),
268                                              m_bookmarks.value(id).alt(), KGeoTag::minimalAltitude,
269                                              KGeoTag::maximalAltitude, 1, &okay);
270     if (! okay) {
271         return;
272     }
273 
274     elevationProcessed(ElevationEngine::Target::Bookmark, { id }, { elevation });
275 }
276 
bookmarks() const277 const QHash<QString, Coordinates> *BookmarksList::bookmarks() const
278 {
279     return &m_bookmarks;
280 }
281 
editCoordinates()282 void BookmarksList::editCoordinates()
283 {
284     const auto id = m_contextMenuItem->text();
285     auto &coordinates = m_bookmarks[id];
286 
287     CoordinatesDialog dialog(CoordinatesDialog::Mode::EditCoordinates, false, coordinates,
288                              i18nc("A quoted bookmark label", "\"%1\"", id));
289     if (! dialog.exec()) {
290         return;
291     }
292 
293     coordinates = dialog.coordinates();
294     m_mapWidget->centerCoordinates(coordinates);
295     emit showInfo(coordinates);
296 }
297