Marble

BookmarkManagerDialog.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 Thibaut Gridel <tgridel@free.fr>
5//
6
7#include "BookmarkManagerDialog.h"
8#include "BookmarkManager.h"
9#include "BookmarkManager_p.h"
10#include "BranchFilterProxyModel.h"
11#include "EditBookmarkDialog.h"
12#include "FileManager.h"
13#include "GeoDataCoordinates.h"
14#include "GeoDataDocument.h"
15#include "GeoDataDocumentWriter.h"
16#include "GeoDataExtendedData.h"
17#include "GeoDataFolder.h"
18#include "GeoDataIconStyle.h"
19#include "GeoDataLookAt.h"
20#include "GeoDataPlacemark.h"
21#include "GeoDataPoint.h"
22#include "GeoDataStyle.h"
23#include "GeoDataTreeModel.h"
24#include "GeoDataTypes.h"
25#include "MarbleDebug.h"
26#include "MarbleDirs.h"
27#include "MarbleModel.h"
28#include "MarblePlacemarkModel.h"
29#include "NewBookmarkFolderDialog.h"
30#include <KmlElementDictionary.h>
31
32#include <QFile>
33#include <QFileDialog>
34#include <QMessageBox>
35#include <QPointer>
36#include <QSortFilterProxyModel>
37
38namespace Marble
39{
40
41/*
42 * The two list views use the model data like this:
43 *
44 * (folder filter)
45 *
46 * QSortFilterProxyModel => Folders View
47 * / |
48 * bookmarks.kml => GeoDataTreeModel | current folder sets filter
49 * \ \ /
50 * BranchFilterModel => Bookmarks View
51 *
52 * (placemark filter) (placemark list)
53 *
54 */
55class BookmarkManagerDialogPrivate
56{
57 Q_DECLARE_TR_FUNCTIONS(BookmarkManagerDialogPrivate)
58
59public:
60 BookmarkManagerDialog *m_parent;
61
62 BookmarkManager *const m_manager;
63
64 GeoDataTreeModel *const m_treeModel;
65
66 QSortFilterProxyModel m_folderFilterModel;
67
68 QPersistentModelIndex m_selectedFolder;
69
70 BranchFilterProxyModel m_branchFilterModel;
71
72 BookmarkManagerDialogPrivate(BookmarkManagerDialog *parent, MarbleModel *model);
73
74 void initializeFoldersView(GeoDataTreeModel *treeModel);
75
76 void initializeBookmarksView(GeoDataTreeModel *treeModel);
77
78 void handleFolderSelection(const QModelIndex &index);
79
80 void updateButtonState();
81
82 void addNewFolder();
83
84 void renameFolder();
85
86 void deleteFolder();
87
88 void editBookmark();
89
90 void deleteBookmark();
91
92 void discardChanges();
93
94 QModelIndex bookmarkTreeIndex(const QModelIndex &bookmark) const;
95
96 QModelIndex folderTreeIndex(const QModelIndex &index) const;
97 GeoDataContainer *selectedFolder();
98
99 void selectFolder(const QString &name = QString(), const QModelIndex &index = QModelIndex());
100
101 void importBookmarksRecursively(GeoDataContainer *source, GeoDataContainer *destination, bool &replaceAll, bool &skipAll);
102
103 GeoDataDocument *bookmarkDocument();
104};
105
106BookmarkManagerDialogPrivate::BookmarkManagerDialogPrivate(BookmarkManagerDialog *parent, MarbleModel *model)
107 : m_parent(parent)
108 , m_manager(model->bookmarkManager())
109 , m_treeModel(model->treeModel())
110 , m_folderFilterModel()
111 , m_branchFilterModel()
112{
113 // nothing to do
114}
115
116/// react to clicking on the folder index (of folderfiltermodel fame)
117/// consequence is selecting this folder, or unselecting it and going to root folder
118void BookmarkManagerDialogPrivate::handleFolderSelection(const QModelIndex &index)
119{
120 if (!index.isValid()) {
121 return;
122 }
123 Q_ASSERT(index.isValid());
124 Q_ASSERT(index.model() == &m_folderFilterModel);
125 if (m_selectedFolder.isValid() && m_parent->foldersTreeView->selectionModel()->selectedIndexes().contains(m_selectedFolder)) {
126 m_selectedFolder = QModelIndex();
127 m_parent->foldersTreeView->selectionModel()->clear();
128 selectFolder();
129 } else {
130 m_selectedFolder = index;
131 m_branchFilterModel.setBranchIndex(m_treeModel, folderTreeIndex(index));
132 m_parent->bookmarksListView->setRootIndex(m_branchFilterModel.mapFromSource(folderTreeIndex(index)));
133 m_parent->bookmarksListView->selectionModel()->clear();
134 }
135}
136
137void BookmarkManagerDialogPrivate::updateButtonState()
138{
139 bool const hasFolderSelection = !m_parent->foldersTreeView->selectionModel()->selectedIndexes().isEmpty();
140 m_parent->renameFolderButton->setEnabled(hasFolderSelection);
141 m_parent->removeFolderButton->setEnabled(hasFolderSelection);
142
143 bool const hasBookmarkSelection = !m_parent->bookmarksListView->selectionModel()->selectedIndexes().isEmpty();
144 m_parent->editBookmarkButton->setEnabled(hasBookmarkSelection);
145 m_parent->removeBookmarkButton->setEnabled(hasBookmarkSelection);
146}
147
148void BookmarkManagerDialogPrivate::addNewFolder()
149{
150 QPointer<NewBookmarkFolderDialog> dialog = new NewBookmarkFolderDialog(m_parent);
151 if (dialog->exec() == QDialog::Accepted && !dialog->folderName().isEmpty()) {
152 m_manager->addNewBookmarkFolder(selectedFolder(), dialog->folderName());
153 selectFolder(dialog->folderName(), m_selectedFolder);
154 }
155 delete dialog;
156}
157
158void BookmarkManagerDialogPrivate::renameFolder()
159{
160 auto folder = geodata_cast<GeoDataFolder>(selectedFolder());
161 if (folder) {
162 QPointer<NewBookmarkFolderDialog> dialog = new NewBookmarkFolderDialog(m_parent);
163 dialog->setFolderName(folder->name());
164 QPersistentModelIndex parentIndex = m_selectedFolder.parent();
165 if (dialog->exec() == QDialog::Accepted) {
166 m_manager->renameBookmarkFolder(folder, dialog->folderName());
167 }
168 selectFolder(dialog->folderName(), parentIndex);
169 delete dialog;
170 }
171}
172
173void BookmarkManagerDialogPrivate::deleteFolder()
174{
175 auto folder = geodata_cast<GeoDataFolder>(selectedFolder());
176 if (folder) {
177 if (folder->size() > 0) {
178 QString const text =
179 tr("The folder %1 is not empty. Removing it will delete all bookmarks it contains. Are you sure you want to delete the folder?")
180 .arg(folder->name());
181 if (QMessageBox::question(m_parent, tr("Remove Folder"), text, QMessageBox::Yes, QMessageBox::No) != QMessageBox::Yes) {
182 return;
183 }
184 }
185
186 // take note of the parentIndex before removing the folder
187 QString parent = static_cast<GeoDataContainer *>(folder->parent())->name();
188 QPersistentModelIndex greatParentIndex = m_selectedFolder.parent().parent();
189 m_manager->removeBookmarkFolder(folder);
190 selectFolder(parent, greatParentIndex);
191 }
192}
193
194void BookmarkManagerDialogPrivate::editBookmark()
195{
196 QModelIndexList selection = m_parent->bookmarksListView->selectionModel()->selectedIndexes();
197 if (selection.size() == 1) {
198 QModelIndex index = m_branchFilterModel.mapToSource(selection.first());
199 Q_ASSERT(index.isValid());
200 auto object = qvariant_cast<GeoDataObject *>(index.data(MarblePlacemarkModel::ObjectPointerRole));
201 Q_ASSERT(object);
202 auto bookmark = geodata_cast<GeoDataPlacemark>(object);
203 // do not try to edit folders
204 if (!bookmark) {
205 return;
206 }
207 Q_ASSERT(bookmark);
208 QModelIndex treeIndex = index;
209 Q_ASSERT(treeIndex.isValid());
210 QModelIndex folderIndex = treeIndex.parent();
211 Q_ASSERT(folderIndex.isValid());
212 auto folderObject = qvariant_cast<GeoDataObject *>(folderIndex.data(MarblePlacemarkModel::ObjectPointerRole));
213 Q_ASSERT(folderObject);
214 auto folder = geodata_cast<GeoDataFolder>(folderObject);
215 Q_ASSERT(folder);
216
217 QPointer<EditBookmarkDialog> dialog = new EditBookmarkDialog(m_manager, m_parent);
218 dialog->setName(bookmark->name());
219 if (bookmark->lookAt()) {
220 dialog->setRange(bookmark->lookAt()->range());
221 }
222 dialog->setCoordinates(bookmark->coordinate());
223 dialog->setDescription(bookmark->description());
224 dialog->setFolderName(folder->name());
225 dialog->setIconLink(bookmark->style()->iconStyle().iconPath());
226 if (dialog->exec() == QDialog::Accepted) {
227 bookmark->setName(dialog->name());
228 bookmark->setDescription(dialog->description());
229 bookmark->setCoordinate(dialog->coordinates());
230 GeoDataStyle::Ptr newStyle(new GeoDataStyle(*bookmark->style()));
231 newStyle->iconStyle().setIconPath(dialog->iconLink());
232 bookmark->setStyle(newStyle);
233 if (bookmark->lookAt()) {
234 bookmark->lookAt()->setCoordinates(dialog->coordinates());
235 bookmark->lookAt()->setRange(dialog->range());
236 } else if (dialog->range()) {
237 auto lookat = new GeoDataLookAt;
238 lookat->setCoordinates(dialog->coordinates());
239 lookat->setRange(dialog->range());
240 bookmark->setAbstractView(lookat);
241 }
242 m_manager->updateBookmark(bookmark);
243
244 if (folder->name() != dialog->folder()->name()) {
245 GeoDataPlacemark newBookmark(*bookmark);
246 m_manager->removeBookmark(bookmark);
247 m_manager->addBookmark(dialog->folder(), newBookmark);
248 }
249 }
250 delete dialog;
251 }
252}
253
254void BookmarkManagerDialogPrivate::deleteBookmark()
255{
256 const QModelIndexList selection = m_parent->bookmarksListView->selectionModel()->selectedIndexes();
257
258 if (selection.size() != 1) {
259 return;
260 }
261
262 const QModelIndex bookmarkIndex = m_branchFilterModel.mapToSource(selection.first());
263 auto folder = geodata_cast<GeoDataFolder>(selectedFolder());
264 if (!folder) {
265 return;
266 }
267
268 auto bookmark = geodata_cast<GeoDataPlacemark>(folder->child(bookmarkIndex.row()));
269 if (!bookmark) {
270 return;
271 }
272
273 m_manager->removeBookmark(bookmark);
274}
275
276void BookmarkManagerDialogPrivate::discardChanges()
277{
278 m_manager->loadFile(QStringLiteral("bookmarks/bookmarks.kml"));
279}
280
281/// selects the folder name from its parent (of folder filter fame)
282void BookmarkManagerDialogPrivate::selectFolder(const QString &name, const QModelIndex &parent)
283{
284 if (parent.isValid()) {
285 Q_ASSERT(parent.model() == &m_folderFilterModel);
286 }
287
288 if (name.isEmpty()) {
289 QModelIndex documentTreeIndex = m_treeModel->index(bookmarkDocument());
290 QModelIndex folderFilterIndex = m_folderFilterModel.mapFromSource(documentTreeIndex);
291 Q_ASSERT(folderFilterIndex.isValid());
292 m_parent->foldersTreeView->setCurrentIndex(folderFilterIndex);
293 handleFolderSelection(folderFilterIndex);
294 return;
295 }
296
297 for (int i = 0; i < m_folderFilterModel.rowCount(parent); ++i) {
298 QModelIndex childIndex = m_folderFilterModel.index(i, 0, parent);
299 if (childIndex.data().toString() == name && m_selectedFolder != childIndex) {
300 m_parent->foldersTreeView->setCurrentIndex(childIndex);
301 handleFolderSelection(childIndex);
302 return;
303 }
304 if (m_folderFilterModel.hasChildren(childIndex)) {
305 selectFolder(name, childIndex);
306 }
307 }
308}
309
310QModelIndex BookmarkManagerDialogPrivate::folderTreeIndex(const QModelIndex &index) const
311{
312 Q_ASSERT(index.isValid());
313 Q_ASSERT(index.model() == &m_folderFilterModel);
314 QModelIndex const treeModelIndex = m_folderFilterModel.mapToSource(index);
315 Q_ASSERT(treeModelIndex.isValid());
316 Q_ASSERT(treeModelIndex.model() == m_treeModel);
317 return treeModelIndex;
318}
319
320GeoDataContainer *BookmarkManagerDialogPrivate::selectedFolder()
321{
322 if (m_selectedFolder.isValid()) {
323 auto object = qvariant_cast<GeoDataObject *>(m_selectedFolder.data(MarblePlacemarkModel::ObjectPointerRole));
324 Q_ASSERT(object);
325 auto container = dynamic_cast<GeoDataContainer *>(object);
326 Q_ASSERT(container);
327 return container;
328 } else {
329 return bookmarkDocument();
330 }
331}
332
333void BookmarkManagerDialogPrivate::initializeFoldersView(GeoDataTreeModel *treeModel)
334{
335 m_folderFilterModel.setFilterKeyColumn(1);
336 const QString regexp = QLatin1StringView(GeoDataTypes::GeoDataFolderType) + QLatin1Char('|') + QLatin1StringView(GeoDataTypes::GeoDataDocumentType);
337 m_folderFilterModel.setFilterRegularExpression(regexp);
338 m_folderFilterModel.setSourceModel(treeModel);
339
340 m_parent->foldersTreeView->setModel(&m_folderFilterModel);
341 m_parent->foldersTreeView->setEditTriggers(QAbstractItemView::NoEditTriggers);
342 m_parent->foldersTreeView->setHeaderHidden(true);
343 for (int i = 1; i < m_treeModel->columnCount(); ++i) {
344 m_parent->foldersTreeView->hideColumn(i);
345 }
346 m_parent->foldersTreeView->setRootIndex(m_folderFilterModel.mapFromSource(m_treeModel->index(bookmarkDocument())));
347
348 m_parent->connect(m_parent->foldersTreeView, SIGNAL(clicked(QModelIndex)), m_parent, SLOT(handleFolderSelection(QModelIndex)));
349 m_parent->connect(m_parent->foldersTreeView->selectionModel(),
350 SIGNAL(selectionChanged(QItemSelection, QItemSelection)),
351 m_parent,
352 SLOT(updateButtonState()));
353 m_parent->connect(m_parent->renameFolderButton, SIGNAL(clicked(bool)), m_parent, SLOT(renameFolder()));
354 m_parent->connect(m_parent->newFolderButton, SIGNAL(clicked(bool)), m_parent, SLOT(addNewFolder()));
355 m_parent->connect(m_parent->removeFolderButton, SIGNAL(clicked(bool)), m_parent, SLOT(deleteFolder()));
356}
357
358void BookmarkManagerDialogPrivate::initializeBookmarksView(GeoDataTreeModel *treeModel)
359{
360 m_branchFilterModel.setSourceModel(treeModel);
361
362 m_parent->bookmarksListView->setModel(&m_branchFilterModel);
363 m_parent->bookmarksListView->setEditTriggers(QAbstractItemView::NoEditTriggers);
364
365 m_parent->connect(m_parent->bookmarksListView->selectionModel(),
366 SIGNAL(selectionChanged(QItemSelection, QItemSelection)),
367 m_parent,
368 SLOT(updateButtonState()));
369 m_parent->connect(m_parent->editBookmarkButton, SIGNAL(clicked(bool)), m_parent, SLOT(editBookmark()));
370 m_parent->connect(m_parent->removeBookmarkButton, SIGNAL(clicked(bool)), m_parent, SLOT(deleteBookmark()));
371}
372
373BookmarkManagerDialog::BookmarkManagerDialog(MarbleModel *model, QWidget *parent)
374 : QDialog(parent)
375 , d(new BookmarkManagerDialogPrivate(this, model))
376{
377 setupUi(this);
378 bool const smallScreen = MarbleGlobal::getInstance()->profiles() & MarbleGlobal::SmallScreen;
379 importButton->setVisible(!smallScreen);
380 exportButton->setVisible(!smallScreen);
381 foldersLabel->setVisible(!smallScreen);
382 bookmarkLabel->setVisible(!smallScreen);
383
384 d->initializeFoldersView(d->m_treeModel);
385 d->initializeBookmarksView(d->m_treeModel);
386 d->updateButtonState();
387
388 connect(this, SIGNAL(accepted()), SLOT(saveBookmarks()));
389 connect(this, SIGNAL(rejected()), SLOT(discardChanges()));
390 connect(exportButton, SIGNAL(clicked()), this, SLOT(exportBookmarks()));
391 connect(importButton, SIGNAL(clicked()), this, SLOT(importBookmarks()));
392
393 d->selectFolder();
394}
395
396BookmarkManagerDialog::~BookmarkManagerDialog()
397{
398 delete d;
399}
400
401void BookmarkManagerDialog::saveBookmarks()
402{
403 d->m_manager->updateBookmarkFile();
404}
405
406void BookmarkManagerDialog::exportBookmarks()
407{
409 tr("Export Bookmarks"), // krazy:exclude=qclasses
411 tr("KML files (*.kml)"));
412
413 if (!fileName.isEmpty()) {
414 if (!GeoDataDocumentWriter::write(fileName, *d->bookmarkDocument())) {
415 mDebug() << "Could not write the bookmarks file" << fileName;
416 QString const text = tr("Unable to save bookmarks. Please check that the file is writable.");
417 QMessageBox::warning(this, tr("Bookmark Export"), text);
418 }
419 }
420}
421
422void BookmarkManagerDialog::importBookmarks()
423{
424 QString const file = QFileDialog::getOpenFileName(this, tr("Import Bookmarks"), QDir::homePath(), tr("KML Files (*.kml)"));
425 if (file.isEmpty()) {
426 return;
427 }
428
429 GeoDataDocument *import = BookmarkManager::openFile(file);
430 if (!import) {
431 QString const text = tr("The file %1 cannot be opened as a KML file.").arg(file);
432 QMessageBox::warning(this, tr("Bookmark Import"), text);
433 return;
434 }
435 GeoDataDocument *current = d->bookmarkDocument();
436
437 bool skipAll = false;
438 bool replaceAll = false;
439 d->importBookmarksRecursively(import, current, skipAll, replaceAll);
440
441 d->selectFolder();
442}
443
444void BookmarkManagerDialogPrivate::importBookmarksRecursively(GeoDataContainer *source, GeoDataContainer *destination, bool &replaceAll, bool &skipAll)
445{
446 for (GeoDataFolder *newFolder : source->folderList()) {
447 GeoDataFolder *existingFolder = m_manager->addNewBookmarkFolder(destination, newFolder->name());
448 importBookmarksRecursively(newFolder, existingFolder, skipAll, replaceAll);
449 for (GeoDataPlacemark *newPlacemark : newFolder->placemarkList()) {
450 bool added = skipAll;
451
452 GeoDataCoordinates newCoordinate = newPlacemark->coordinate();
453 GeoDataPlacemark *existingPlacemark = m_manager->bookmarkAt(m_manager->document(), newCoordinate);
454 if (existingPlacemark) {
455 if (skipAll) {
456 continue;
457 }
458
459 // Avoid message boxes for equal bookmarks, just skip them
460 if (existingPlacemark->name() == newPlacemark->name() && existingPlacemark->description() == newPlacemark->description()) {
461 continue;
462 }
463
465 QString const intro = tr("The file contains a bookmark that already exists among your Bookmarks.");
466 QString const newBookmark = tr("Imported bookmark");
467 QString const existingBookmark = tr("Existing bookmark");
468 QString const question = tr("Do you want to replace the existing bookmark with the imported one?");
470 "<p>%1</p><table><tr><td>%2</td><td><b>%3 / %4</b></td></tr>"
471 "<tr><td>%5</td><td><b>%6 / %7</b></td></tr></table><p>%8</p>");
472 html = html.arg(intro,
473 existingBookmark,
474 existingFolder->name(),
475 existingPlacemark->name(),
476 newBookmark,
477 newFolder->name(),
478 newPlacemark->name(),
479 question);
480 messageBox->setText(html);
481
482 QAbstractButton *replaceButton = messageBox->addButton(tr("Replace"), QMessageBox::ActionRole);
483 QAbstractButton *replaceAllButton = messageBox->addButton(tr("Replace All"), QMessageBox::ActionRole);
484 QAbstractButton *skipButton = messageBox->addButton(tr("Skip"), QMessageBox::ActionRole);
485 QAbstractButton *skipAllButton = messageBox->addButton(tr("Skip All"), QMessageBox::ActionRole);
488
489 if (!replaceAll) {
490 messageBox->exec();
491 }
492 if (messageBox->clickedButton() == replaceAllButton) {
493 replaceAll = true;
494 } else if (messageBox->clickedButton() == skipAllButton) {
495 skipAll = true;
496 added = true;
497 } else if (messageBox->clickedButton() == skipButton) {
498 delete messageBox;
499 continue;
500 } else if (messageBox->clickedButton() != replaceButton) {
501 delete messageBox;
502 return;
503 }
504
505 if (messageBox->clickedButton() == replaceButton || replaceAll) {
506 m_manager->removeBookmark(existingPlacemark);
507 m_manager->addBookmark(existingFolder, *newPlacemark);
508
509 mDebug() << "Placemark " << newPlacemark->name() << " replaces " << existingPlacemark->name();
510 delete messageBox;
511 break;
512 }
513 delete messageBox;
514 }
515
516 if (!added) {
517 m_manager->addBookmark(existingFolder, *newPlacemark);
518 }
519 }
520 }
521}
522
523GeoDataDocument *BookmarkManagerDialogPrivate::bookmarkDocument()
524{
525 return m_manager->document();
526}
527
528void BookmarkManagerDialog::setButtonBoxVisible(bool visible)
529{
530 buttonBox->setVisible(visible);
531 if (!visible) {
532 disconnect(this, SIGNAL(rejected()), this, SLOT(discardChanges()));
533 connect(this, SIGNAL(rejected()), SLOT(saveBookmarks()));
534 }
535}
536
537}
538
539#include "moc_BookmarkManagerDialog.cpp"
This file contains the headers for MarbleModel.
QString name(GameStandardAction id)
ButtonCode messageBox(QWidget *parent, DialogType type, const QString &text, const QString &title, const KGuiItem &primaryAction, const KGuiItem &secondaryAction, const KGuiItem &cancelAction=KStandardGuiItem::cancel(), const QString &dontShowAskAgainName=QString(), Options options=Notify)
Binds a QML item to a specific geodetic location in screen coordinates.
QString homePath()
QString getOpenFileName(QWidget *parent, const QString &caption, const QString &dir, const QString &filter, QString *selectedFilter, Options options)
QString getSaveFileName(QWidget *parent, const QString &caption, const QString &dir, const QString &filter, QString *selectedFilter, Options options)
StandardButton question(QWidget *parent, const QString &title, const QString &text, StandardButtons buttons, StandardButton defaultButton)
StandardButton warning(QWidget *parent, const QString &title, const QString &text, StandardButtons buttons, StandardButton defaultButton)
QVariant data(int role) const const
bool isValid() const const
const QAbstractItemModel * model() const const
QModelIndex parent() const const
int row() const const
QVariant data(int role) const const
bool isValid() const const
QModelIndex parent() const const
void setFilterKeyColumn(int column)
void setFilterRegularExpression(const QRegularExpression &regularExpression)
virtual bool hasChildren(const QModelIndex &parent) const const override
virtual QModelIndex index(int row, int column, const QModelIndex &parent) const const override
virtual QModelIndex mapFromSource(const QModelIndex &sourceIndex) const const override
virtual QModelIndex mapToSource(const QModelIndex &proxyIndex) const const override
virtual int rowCount(const QModelIndex &parent) const const override
virtual void setSourceModel(QAbstractItemModel *sourceModel) override
QString arg(Args &&... args) const const
bool isEmpty() const const
QFuture< ArgsType< Signal > > connect(Sender *sender, Signal signal)
QString toString() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Fri Nov 8 2024 12:02:42 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.