1 /* ============================================================
2 *
3 * This file is a part of digiKam project
4 * https://www.digikam.org
5 *
6 * Date : 2006-05-16
7 * Description : A tool to edit geolocation
8 *
9 * Copyright (C) 2006-2021 by Gilles Caulier <caulier dot gilles at gmail dot com>
10 * Copyright (C) 2010-2014 by Michael G. Hansen <mike at mghansen dot de>
11 * Copyright (C) 2010 by Gabriel Voicu <ping dot gabi at gmail dot com>
12 * Copyright (C) 2014 by Justus Schwartz <justus at gmx dot li>
13 *
14 * This program is free software; you can redistribute it
15 * and/or modify it under the terms of the GNU General
16 * Public License as published by the Free Software Foundation;
17 * either version 2, or (at your option)
18 * any later version.
19 *
20 * This program is distributed in the hope that it will be useful,
21 * but WITHOUT ANY WARRANTY; without even the implied warranty of
22 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23 * GNU General Public License for more details.
24 *
25 * ============================================================ */
26
27 #include "geolocationedit.h"
28
29 // Qt includes
30
31 #include <QtConcurrentMap>
32 #include <QButtonGroup>
33 #include <QCheckBox>
34 #include <QCloseEvent>
35 #include <QFuture>
36 #include <QFutureWatcher>
37 #include <QGroupBox>
38 #include <QHBoxLayout>
39 #include <QHeaderView>
40 #include <QLabel>
41 #include <QPushButton>
42 #include <QGridLayout>
43 #include <QPointer>
44 #include <QRadioButton>
45 #include <QSplitter>
46 #include <QStackedLayout>
47 #include <QStackedWidget>
48 #include <QTimer>
49 #include <QToolButton>
50 #include <QTreeView>
51 #include <QMenu>
52 #include <QUndoView>
53 #include <QAction>
54 #include <QApplication>
55 #include <QComboBox>
56 #include <QDialogButtonBox>
57
58 // KDE includes
59
60 #include <kconfiggroup.h>
61 #include <ksharedconfig.h>
62 #include <klocalizedstring.h>
63
64 // Local includes
65
66 #include "dlayoutbox.h"
67 #include "digikam_config.h"
68 #include "itemmarkertiler.h"
69 #include "trackmanager.h"
70 #include "gpscommon.h"
71 #include "gpsitemmodel.h"
72 #include "mapdragdrophandler.h"
73 #include "gpsitemlist.h"
74 #include "gpsitemlistdragdrophandler.h"
75 #include "gpsitemlistcontextmenu.h"
76 #include "gpscorrelatorwidget.h"
77 #include "digikam_debug.h"
78 #include "dmessagebox.h"
79 #include "rgwidget.h"
80 #include "kmlwidget.h"
81 #include "statusprogressbar.h"
82 #include "searchwidget.h"
83 #include "backend-rg.h"
84 #include "gpsitemdetails.h"
85 #include "gpsgeoifacemodelhelper.h"
86 #include "dxmlguiwindow.h"
87 #include "gpsbookmarkowner.h"
88 #include "gpsbookmarkmodelhelper.h"
89
90 #ifdef GPSSYNC_MODELTEST
91 # include <modeltest.h>
92 #endif
93
94 using namespace Digikam;
95
96 namespace DigikamGenericGeolocationEditPlugin
97 {
98
99 struct SaveChangedImagesHelper
100 {
101 public:
102
SaveChangedImagesHelperDigikamGenericGeolocationEditPlugin::SaveChangedImagesHelper103 explicit SaveChangedImagesHelper(GPSItemModel* const model)
104 : imageModel(model)
105 {
106 }
107
operator ()DigikamGenericGeolocationEditPlugin::SaveChangedImagesHelper108 QPair<QUrl, QString> operator()(const QPersistentModelIndex& itemIndex)
109 {
110 GPSItemContainer* const item = imageModel->itemFromIndex(itemIndex);
111
112 if (!item)
113 {
114 return QPair<QUrl, QString>(QUrl(), QString());
115 }
116
117 return QPair<QUrl, QString>(item->url(), item->saveChanges());
118 }
119
120 public:
121
122 typedef QPair<QUrl, QString> result_type;
123 GPSItemModel* const imageModel;
124 };
125
126 // ---------------------------------------------------------------------------------
127
128 struct LoadFileMetadataHelper
129 {
130 public:
131
LoadFileMetadataHelperDigikamGenericGeolocationEditPlugin::LoadFileMetadataHelper132 explicit LoadFileMetadataHelper(GPSItemModel* const model)
133 : imageModel(model)
134 {
135 }
136
operator ()DigikamGenericGeolocationEditPlugin::LoadFileMetadataHelper137 QPair<QUrl, QString> operator()(const QPersistentModelIndex& itemIndex)
138 {
139 GPSItemContainer* const item = imageModel->itemFromIndex(itemIndex);
140
141 if (!item)
142 {
143 return QPair<QUrl, QString>(QUrl(), QString());
144 }
145
146 item->loadImageData();
147
148 return QPair<QUrl, QString>(item->url(), QString());
149 }
150
151 public:
152
153 typedef QPair<QUrl, QString> result_type;
154 GPSItemModel* const imageModel;
155 };
156
157 // ---------------------------------------------------------------------------------
158
159 class Q_DECL_HIDDEN GeolocationEdit::Private
160 {
161 public:
162
Private()163 explicit Private()
164 {
165 imageModel = nullptr;
166 selectionModel = nullptr;
167 uiEnabled = true;
168 listViewContextMenu = nullptr;
169 trackManager = nullptr;
170 fileIOFutureWatcher = nullptr;
171 fileIOCountDone = 0;
172 fileIOCountTotal = 0;
173 fileIOCloseAfterSaving = false;
174 buttonBox = nullptr;
175 VSplitter = nullptr;
176 HSplitter = nullptr;
177 treeView = nullptr;
178 stackedWidget = nullptr;
179 tabBar = nullptr;
180 splitterSize = 0;
181 undoStack = nullptr;
182 undoView = nullptr;
183 progressBar = nullptr;
184 progressCancelButton = nullptr;
185 progressCancelObject = nullptr;
186 detailsWidget = nullptr;
187 correlatorWidget = nullptr;
188 rgWidget = nullptr;
189 searchWidget = nullptr;
190 kmlWidget = nullptr;
191 mapSplitter = nullptr;
192 mapWidget = nullptr;
193 mapWidget2 = nullptr;
194 mapDragDropHandler = nullptr;
195 mapModelHelper = nullptr;
196 geoifaceMarkerModel = nullptr;
197 sortActionOldestFirst = nullptr;
198 sortActionYoungestFirst = nullptr;
199 sortMenu = nullptr;
200 mapLayout = MapLayoutOne;
201 cbMapLayout = nullptr;
202 bookmarkOwner = nullptr;
203 actionBookmarkVisibility = nullptr;
204 iface = nullptr;
205 }
206
207 // General things
208 GPSItemModel* imageModel;
209 QItemSelectionModel* selectionModel;
210 bool uiEnabled;
211 GPSItemListContextMenu* listViewContextMenu;
212 TrackManager* trackManager;
213
214 // Loading and saving
215 QFuture<QPair<QUrl,QString> > fileIOFuture;
216 QFutureWatcher<QPair<QUrl,QString> >* fileIOFutureWatcher;
217 int fileIOCountDone;
218 int fileIOCountTotal;
219 bool fileIOCloseAfterSaving;
220
221 // UI
222 QDialogButtonBox* buttonBox;
223 QSplitter* VSplitter;
224 QSplitter* HSplitter;
225 GPSItemList* treeView;
226 QStackedWidget* stackedWidget;
227 QTabBar* tabBar;
228 int splitterSize;
229 QUndoStack* undoStack;
230 QUndoView* undoView;
231
232 // UI: progress
233 StatusProgressBar* progressBar;
234 QPushButton* progressCancelButton;
235 QObject* progressCancelObject;
236 QString progressCancelSlot;
237
238 // UI: tab widgets
239 GPSItemDetails* detailsWidget;
240 GPSCorrelatorWidget* correlatorWidget;
241 RGWidget* rgWidget;
242 SearchWidget* searchWidget;
243 KmlWidget* kmlWidget;
244
245 // map: UI
246 MapLayout mapLayout;
247 QSplitter* mapSplitter;
248 MapWidget* mapWidget;
249 MapWidget* mapWidget2;
250
251 // map: helpers
252 MapDragDropHandler* mapDragDropHandler;
253 GPSGeoIfaceModelHelper* mapModelHelper;
254 ItemMarkerTiler* geoifaceMarkerModel;
255
256 // map: actions
257 QAction* sortActionOldestFirst;
258 QAction* sortActionYoungestFirst;
259 QMenu* sortMenu;
260 QComboBox* cbMapLayout;
261
262 GPSBookmarkOwner* bookmarkOwner;
263 QAction* actionBookmarkVisibility;
264
265 DInfoInterface* iface;
266 };
267
GeolocationEdit(QWidget * const parent,DInfoInterface * const iface)268 GeolocationEdit::GeolocationEdit(QWidget* const parent, DInfoInterface* const iface)
269 : DPluginDialog(parent, QLatin1String("Geolocation Edit Settings")),
270 d (new Private)
271 {
272 setAttribute(Qt::WA_DeleteOnClose, true);
273 setWindowTitle(i18nc("@title", "Geolocation Editor"));
274 setMinimumSize(300, 400);
275 setModal(true);
276
277 d->iface = iface;
278 d->imageModel = new GPSItemModel(this);
279 d->selectionModel = new QItemSelectionModel(d->imageModel);
280 d->trackManager = new TrackManager(this);
281
282 #ifdef GPSSYNC_MODELTEST
283
284 new ModelTest(d->imageModel, this);
285
286 #endif
287
288 d->bookmarkOwner = new GPSBookmarkOwner(d->imageModel, this);
289 d->undoStack = new QUndoStack(this);
290 d->stackedWidget = new QStackedWidget();
291 d->searchWidget = new SearchWidget(d->bookmarkOwner,
292 d->imageModel,
293 d->selectionModel,
294 d->stackedWidget);
295
296 GPSItemContainer::setHeaderData(d->imageModel);
297 d->mapModelHelper = new GPSGeoIfaceModelHelper(d->imageModel, d->selectionModel, this);
298 d->mapModelHelper->addUngroupedModelHelper(d->bookmarkOwner->bookmarkModelHelper());
299
300 d->mapModelHelper->addUngroupedModelHelper(d->searchWidget->getModelHelper());
301 d->mapDragDropHandler = new MapDragDropHandler(d->imageModel, d->mapModelHelper);
302 d->geoifaceMarkerModel = new ItemMarkerTiler(d->mapModelHelper, this);
303
304 d->actionBookmarkVisibility = new QAction(this);
305 d->actionBookmarkVisibility->setIcon(QIcon::fromTheme(QLatin1String("bookmark-new")));
306 d->actionBookmarkVisibility->setToolTip(i18nc("@info", "Display bookmarked positions on the map."));
307 d->actionBookmarkVisibility->setCheckable(true);
308
309 connect(d->actionBookmarkVisibility, SIGNAL(changed()),
310 this, SLOT(slotBookmarkVisibilityToggled()));
311
312 QVBoxLayout* const mainLayout = new QVBoxLayout(this);
313 setLayout(mainLayout);
314
315 DHBox* const hboxMain = new DHBox(this);
316 mainLayout->addWidget(hboxMain, 10);
317
318 d->HSplitter = new QSplitter(Qt::Horizontal, hboxMain);
319 d->HSplitter->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
320
321 // ------------------------------------------------------------------------------------------------
322
323 DHBox* const hbox = new DHBox(this);
324 QLabel* const labelMapLayout = new QLabel(i18nc("@label", "Layout:"), hbox);
325 d->cbMapLayout = new QComboBox(hbox);
326 d->cbMapLayout->addItem(i18nc("@item", "One map"), QVariant::fromValue(MapLayoutOne));
327 d->cbMapLayout->addItem(i18nc("@item", "Two maps - horizontal"), QVariant::fromValue(MapLayoutHorizontal));
328 d->cbMapLayout->addItem(i18nc("@item", "Two maps - vertical"), QVariant::fromValue(MapLayoutVertical));
329 labelMapLayout->setBuddy(d->cbMapLayout);
330
331 d->progressBar = new StatusProgressBar(hbox);
332 d->progressBar->setVisible(false);
333 d->progressBar->setProgressBarMode(StatusProgressBar::ProgressBarMode);
334 d->progressBar->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum);
335
336 d->progressCancelButton = new QPushButton(hbox);
337 d->progressCancelButton->setVisible(false);
338 d->progressCancelButton->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
339 d->progressCancelButton->setIcon(QIcon::fromTheme(QLatin1String("dialog-cancel")));
340
341 connect(d->progressCancelButton, SIGNAL(clicked()),
342 this, SLOT(slotProgressCancelButtonClicked()));
343
344 m_buttons->addButton(QDialogButtonBox::Apply);
345 m_buttons->addButton(QDialogButtonBox::Close);
346 m_buttons->setParent(hbox);
347
348 connect(m_buttons->button(QDialogButtonBox::Apply), &QPushButton::clicked,
349 this, &GeolocationEdit::slotApplyClicked);
350
351 connect(m_buttons->button(QDialogButtonBox::Close), &QPushButton::clicked,
352 this, &GeolocationEdit::close);
353
354 mainLayout->addWidget(hbox, 0);
355
356 // ------------------------------------------------------------------------------------------------
357
358 d->VSplitter = new QSplitter(Qt::Vertical, d->HSplitter);
359 d->HSplitter->addWidget(d->VSplitter);
360 d->HSplitter->setStretchFactor(0, 10);
361
362 d->sortMenu = new QMenu(this);
363 d->sortMenu->setTitle(i18nc("@title", "Sorting"));
364 QActionGroup* const sortOrderExclusive = new QActionGroup(d->sortMenu);
365 sortOrderExclusive->setExclusive(true);
366
367 connect(sortOrderExclusive, SIGNAL(triggered(QAction*)),
368 this, SLOT(slotSortOptionTriggered(QAction*)));
369
370 d->sortActionOldestFirst = new QAction(i18nc("@action", "Show oldest first"), sortOrderExclusive);
371 d->sortActionOldestFirst->setCheckable(true);
372 d->sortMenu->addAction(d->sortActionOldestFirst);
373
374 d->sortActionYoungestFirst = new QAction(i18nc("@action", "Show youngest first"), sortOrderExclusive);
375 d->sortMenu->addAction(d->sortActionYoungestFirst);
376 d->sortActionYoungestFirst->setCheckable(true);
377
378 QWidget* mapVBox = nullptr;
379 d->mapWidget = makeMapWidget(&mapVBox);
380 d->searchWidget->setPrimaryMapWidget(d->mapWidget);
381 d->mapSplitter = new QSplitter(this);
382 d->mapSplitter->addWidget(mapVBox);
383 d->VSplitter->addWidget(d->mapSplitter);
384
385 d->treeView = new GPSItemList(this);
386 d->treeView->setModelAndSelectionModel(d->imageModel, d->selectionModel);
387 d->treeView->setDragDropHandler(new GPSItemListDragDropHandler(this));
388 d->treeView->setDragEnabled(true);
389
390 /**
391 * TODO: save and restore the state of the header
392 * TODO: add a context menu to the header to select which columns should be visible
393 * TODO: add sorting by column
394 */
395
396 d->treeView->setSelectionMode(QAbstractItemView::ExtendedSelection);
397 d->treeView->setSortingEnabled(true);
398 d->VSplitter->addWidget(d->treeView);
399
400 d->listViewContextMenu = new GPSItemListContextMenu(d->treeView, d->bookmarkOwner);
401 d->HSplitter->addWidget(d->stackedWidget);
402 d->HSplitter->setCollapsible(1, true);
403 d->splitterSize = 0;
404
405 DVBox* const vboxTabBar = new DVBox(hboxMain);
406 vboxTabBar->layout()->setContentsMargins(QMargins());
407 vboxTabBar->layout()->setSpacing(0);
408
409 d->tabBar = new QTabBar(vboxTabBar);
410 d->tabBar->setShape(QTabBar::RoundedEast);
411
412 dynamic_cast<QVBoxLayout*>(vboxTabBar->layout())->addStretch(200);
413
414 d->tabBar->addTab(i18nc("@item: map tool", "Details"));
415 d->tabBar->addTab(i18nc("@item: map tool", "GPS Correlator"));
416 d->tabBar->addTab(i18nc("@item: map tool", "Undo/Redo"));
417 d->tabBar->addTab(i18nc("@item: map tool", "Reverse Geocoding"));
418 d->tabBar->addTab(i18nc("@item: map tool", "Search"));
419 d->tabBar->addTab(i18nc("@item: map tool", "KML Export"));
420
421 d->tabBar->installEventFilter(this);
422
423 d->detailsWidget = new GPSItemDetails(d->stackedWidget, d->imageModel);
424 d->stackedWidget->addWidget(d->detailsWidget);
425
426 d->correlatorWidget = new GPSCorrelatorWidget(d->stackedWidget, d->imageModel, d->trackManager);
427 d->stackedWidget->addWidget(d->correlatorWidget);
428
429 d->undoView = new QUndoView(d->undoStack, d->stackedWidget);
430 d->stackedWidget->addWidget(d->undoView);
431
432 d->rgWidget = new RGWidget(d->imageModel, d->selectionModel, d->iface->tagFilterModel(), d->stackedWidget);
433 d->stackedWidget->addWidget(d->rgWidget);
434
435 d->stackedWidget->addWidget(d->searchWidget);
436
437 d->kmlWidget = new KmlWidget(this, d->imageModel, d->iface);
438 d->stackedWidget->addWidget(d->kmlWidget);
439
440 // ---------------------------------------------------------------
441
442 connect(d->treeView, SIGNAL(signalImageActivated(QModelIndex)),
443 this, SLOT(slotImageActivated(QModelIndex)));
444
445 connect(d->correlatorWidget, SIGNAL(signalSetUIEnabled(bool)),
446 this, SLOT(slotSetUIEnabled(bool)));
447
448 connect(d->correlatorWidget, SIGNAL(signalSetUIEnabled(bool,QObject*const,QString)),
449 this, SLOT(slotSetUIEnabled(bool,QObject*const,QString)));
450
451 connect(d->correlatorWidget, SIGNAL(signalProgressSetup(int,QString)),
452 this, SLOT(slotProgressSetup(int,QString)));
453
454 connect(d->correlatorWidget, SIGNAL(signalProgressChanged(int)),
455 this, SLOT(slotProgressChanged(int)));
456
457 connect(d->correlatorWidget, SIGNAL(signalUndoCommand(GPSUndoCommand*)),
458 this, SLOT(slotGPSUndoCommand(GPSUndoCommand*)));
459
460 connect(d->correlatorWidget, SIGNAL(signalTrackListChanged(Digikam::GeoCoordinates)),
461 this, SLOT(slotTrackListChanged(Digikam::GeoCoordinates)));
462
463 connect(d->mapModelHelper, SIGNAL(signalUndoCommand(GPSUndoCommand*)),
464 this, SLOT(slotGPSUndoCommand(GPSUndoCommand*)));
465
466 connect(d->rgWidget, SIGNAL(signalSetUIEnabled(bool)),
467 this, SLOT(slotSetUIEnabled(bool)));
468
469 connect(d->rgWidget, SIGNAL(signalSetUIEnabled(bool,QObject*const,QString)),
470 this, SLOT(slotSetUIEnabled(bool,QObject*const,QString)));
471
472 connect(d->rgWidget, SIGNAL(signalProgressSetup(int,QString)),
473 this, SLOT(slotProgressSetup(int,QString)));
474
475 connect(d->rgWidget, SIGNAL(signalProgressChanged(int)),
476 this, SLOT(slotProgressChanged(int)));
477
478 connect(d->rgWidget, SIGNAL(signalUndoCommand(GPSUndoCommand*)),
479 this, SLOT(slotGPSUndoCommand(GPSUndoCommand*)));
480
481 connect(d->searchWidget, SIGNAL(signalUndoCommand(GPSUndoCommand*)),
482 this, SLOT(slotGPSUndoCommand(GPSUndoCommand*)));
483
484 connect(d->listViewContextMenu, SIGNAL(signalSetUIEnabled(bool)),
485 this, SLOT(slotSetUIEnabled(bool)));
486
487 connect(d->listViewContextMenu, SIGNAL(signalSetUIEnabled(bool,QObject*const,QString)),
488 this, SLOT(slotSetUIEnabled(bool,QObject*const,QString)));
489
490 connect(d->listViewContextMenu, SIGNAL(signalProgressSetup(int,QString)),
491 this, SLOT(slotProgressSetup(int,QString)));
492
493 connect(d->listViewContextMenu, SIGNAL(signalProgressChanged(int)),
494 this, SLOT(slotProgressChanged(int)));
495
496 connect(d->listViewContextMenu, SIGNAL(signalUndoCommand(GPSUndoCommand*)),
497 this, SLOT(slotGPSUndoCommand(GPSUndoCommand*)));
498
499 connect(d->tabBar, SIGNAL(currentChanged(int)),
500 this, SLOT(slotCurrentTabChanged(int)));
501
502 connect(d->bookmarkOwner->bookmarkModelHelper(),
503 SIGNAL(signalUndoCommand(GPSUndoCommand*)),
504 this, SLOT(slotGPSUndoCommand(GPSUndoCommand*)));
505
506 connect(d->detailsWidget, SIGNAL(signalUndoCommand(GPSUndoCommand*)),
507 this, SLOT(slotGPSUndoCommand(GPSUndoCommand*)));
508
509 connect(d->cbMapLayout, SIGNAL(activated(int)),
510 this, SLOT(slotLayoutChanged(int)));
511
512 connect(this, SIGNAL(signalMetadataChangedForUrl(QUrl)),
513 d->iface, SLOT(slotMetadataChangedForUrl(QUrl)));
514
515 readSettings();
516
517 d->mapWidget->setActive(true);
518
519 setItems(d->iface->currentGPSItems());
520 }
521
~GeolocationEdit()522 GeolocationEdit::~GeolocationEdit()
523 {
524 delete d;
525 }
526
eventFilter(QObject * const o,QEvent * const e)527 bool GeolocationEdit::eventFilter(QObject* const o, QEvent* const e)
528 {
529 if ((o == d->tabBar) && (e->type() == QEvent::MouseButtonPress))
530 {
531 QMouseEvent const* m = static_cast<QMouseEvent*>(e);
532
533 QPoint p (m->x(), m->y());
534 const int var = d->tabBar->tabAt(p);
535
536 if (var < 0)
537 {
538 return false;
539 }
540
541 QList<int> sizes = d->HSplitter->sizes();
542
543 if (d->splitterSize == 0)
544 {
545 if (sizes.at(1) == 0)
546 {
547 sizes[1] = d->stackedWidget->widget(var)->minimumSizeHint().width();
548 }
549 else if (d->tabBar->currentIndex() == var)
550 {
551 d->splitterSize = sizes.at(1);
552 sizes[1] = 0;
553 }
554 }
555 else
556 {
557 sizes[1] = d->splitterSize;
558 d->splitterSize = 0;
559 }
560
561 d->tabBar->setCurrentIndex(var);
562 d->stackedWidget->setCurrentIndex(var);
563 d->HSplitter->setSizes(sizes);
564 d->detailsWidget->slotSetActive((d->stackedWidget->currentWidget() == d->detailsWidget) &&
565 (d->splitterSize == 0));
566
567 return true;
568 }
569
570 return QWidget::eventFilter(o, e);
571 }
572
slotCurrentTabChanged(int index)573 void GeolocationEdit::slotCurrentTabChanged(int index)
574 {
575 d->tabBar->setCurrentIndex(index);
576 d->stackedWidget->setCurrentIndex(index);
577 d->detailsWidget->slotSetActive(d->stackedWidget->currentWidget() == d->detailsWidget);
578 }
579
setCurrentTab(int index)580 void GeolocationEdit::setCurrentTab(int index)
581 {
582 d->tabBar->setCurrentIndex(index);
583 d->stackedWidget->setCurrentIndex(index);
584 QList<int> sizes = d->HSplitter->sizes();
585
586 if (d->splitterSize >= 0)
587 {
588 sizes[1] = d->splitterSize;
589 d->splitterSize = 0;
590 }
591
592 d->HSplitter->setSizes(sizes);
593 d->detailsWidget->slotSetActive((d->stackedWidget->currentWidget() == d->detailsWidget) &&
594 (d->splitterSize == 0));
595 }
596
setImages(const QList<QUrl> & images)597 void GeolocationEdit::setImages(const QList<QUrl>& images)
598 {
599 QList<GPSItemContainer*> items;
600
601 foreach (const QUrl& u, images)
602 {
603 items << new GPSItemContainer(u);
604 }
605
606 setItems(items);
607 }
608
setItems(const QList<GPSItemContainer * > & items)609 void GeolocationEdit::setItems(const QList<GPSItemContainer*>& items)
610 {
611 foreach (GPSItemContainer* const newItem, items)
612 {
613 newItem->loadImageData();
614 d->imageModel->addItem(newItem);
615 }
616
617 QList<QPersistentModelIndex> imagesToLoad;
618
619 for (int i = 0 ; i < d->imageModel->rowCount() ; ++i)
620 {
621 imagesToLoad << d->imageModel->index(i, 0);
622 }
623
624 slotSetUIEnabled(false);
625 slotProgressSetup(imagesToLoad.count(), i18nc("@info", "Loading metadata -"));
626
627 // initiate the saving
628
629 d->fileIOCountDone = 0;
630 d->fileIOCountTotal = imagesToLoad.count();
631 d->fileIOFutureWatcher = new QFutureWatcher<QPair<QUrl, QString> >(this);
632
633 connect(d->fileIOFutureWatcher, SIGNAL(resultsReadyAt(int,int)),
634 this, SLOT(slotFileMetadataLoaded(int,int)));
635
636 d->fileIOFuture = QtConcurrent::mapped(imagesToLoad, LoadFileMetadataHelper(d->imageModel));
637 d->fileIOFutureWatcher->setFuture(d->fileIOFuture);
638 }
639
slotFileMetadataLoaded(int beginIndex,int endIndex)640 void GeolocationEdit::slotFileMetadataLoaded(int beginIndex, int endIndex)
641 {
642 qCDebug(DIGIKAM_DPLUGIN_GENERIC_LOG) << beginIndex << endIndex;
643 d->fileIOCountDone += (endIndex-beginIndex);
644 slotProgressChanged(d->fileIOCountDone);
645
646 if (d->fileIOCountDone == d->fileIOCountTotal)
647 {
648 slotSetUIEnabled(true);
649 }
650 }
651
readSettings()652 void GeolocationEdit::readSettings()
653 {
654 KSharedConfig::Ptr config = KSharedConfig::openConfig();
655 KConfigGroup group = config->group("Geolocation Edit Settings");
656
657 // --------------------------
658
659 // TODO: sanely determine a default backend
660
661 const KConfigGroup groupMapWidget = KConfigGroup(&group, "Map Widget");
662 d->mapWidget->readSettingsFromGroup(&groupMapWidget);
663
664 const KConfigGroup groupCorrelatorWidget = KConfigGroup(&group, "Correlator Widget");
665 d->correlatorWidget->readSettingsFromGroup(&groupCorrelatorWidget);
666
667 const KConfigGroup groupTreeView = KConfigGroup(&group, "Tree View");
668 d->treeView->readSettingsFromGroup(&groupTreeView);
669
670 const KConfigGroup groupSearchWidget = KConfigGroup(&group, "Search Widget");
671 d->searchWidget->readSettingsFromGroup(&groupSearchWidget);
672
673 const KConfigGroup groupRGWidget = KConfigGroup(&group, "Reverse Geocoding Widget");
674 d->rgWidget->readSettingsFromGroup(&groupRGWidget);
675
676 const KConfigGroup groupDialog = KConfigGroup(&group, "Dialog");
677
678 // --------------------------
679
680 setCurrentTab(group.readEntry("Current Tab", 0));
681 const bool showOldestFirst = group.readEntry("Show oldest images first", false);
682
683 if (showOldestFirst)
684 {
685 d->sortActionOldestFirst->setChecked(true);
686 d->mapWidget->setSortKey(1);
687 }
688 else
689 {
690 d->sortActionYoungestFirst->setChecked(true);
691 d->mapWidget->setSortKey(0);
692 }
693
694 d->actionBookmarkVisibility->setChecked(group.readEntry("Bookmarks visible", false));
695 slotBookmarkVisibilityToggled();
696
697 if (group.hasKey("SplitterState V1"))
698 {
699 const QByteArray splitterState = QByteArray::fromBase64(group.readEntry("SplitterState V1", QByteArray()));
700
701 if (!splitterState.isEmpty())
702 {
703 d->VSplitter->restoreState(splitterState);
704 }
705 }
706
707 if (group.hasKey("SplitterState H1"))
708 {
709 const QByteArray splitterState = QByteArray::fromBase64(group.readEntry("SplitterState H1", QByteArray()));
710
711 if (!splitterState.isEmpty())
712 {
713 d->HSplitter->restoreState(splitterState);
714 }
715 }
716
717 d->splitterSize = group.readEntry("Splitter H1 CollapsedSize", 0);
718
719 // ----------------------------------
720
721 d->mapLayout = MapLayout(group.readEntry("Map Layout", QVariant::fromValue(int(MapLayoutOne))).value<int>());
722 d->cbMapLayout->setCurrentIndex(d->mapLayout);
723 adjustMapLayout(false);
724
725 if (d->mapWidget2)
726 {
727 const KConfigGroup groupMapWidget2 = KConfigGroup(&group, "Map Widget 2");
728 d->mapWidget2->readSettingsFromGroup(&groupMapWidget2);
729
730 d->mapWidget2->setActive(true);
731 }
732 }
733
saveSettings()734 void GeolocationEdit::saveSettings()
735 {
736 KSharedConfig::Ptr config = KSharedConfig::openConfig();
737 KConfigGroup group = config->group("Geolocation Edit Settings");
738
739 // --------------------------
740
741 KConfigGroup groupMapWidget = KConfigGroup(&group, "Map Widget");
742 d->mapWidget->saveSettingsToGroup(&groupMapWidget);
743
744 if (d->mapWidget2)
745 {
746 KConfigGroup groupMapWidget2 = KConfigGroup(&group, "Map Widget 2");
747 d->mapWidget2->saveSettingsToGroup(&groupMapWidget2);
748 }
749
750 KConfigGroup groupCorrelatorWidget = KConfigGroup(&group, "Correlator Widget");
751 d->correlatorWidget->saveSettingsToGroup(&groupCorrelatorWidget);
752
753 KConfigGroup groupTreeView = KConfigGroup(&group, "Tree View");
754 d->treeView->saveSettingsToGroup(&groupTreeView);
755
756 KConfigGroup groupSearchWidget = KConfigGroup(&group, "Search Widget");
757 d->searchWidget->saveSettingsToGroup(&groupSearchWidget);
758
759 KConfigGroup groupRGWidget = KConfigGroup(&group, "Reverse Geocoding Widget");
760 d->rgWidget->saveSettingsToGroup(&groupRGWidget);
761
762 // --------------------------
763
764 group.writeEntry("Current Tab", d->tabBar->currentIndex());
765 group.writeEntry("Show oldest images first", d->sortActionOldestFirst->isChecked());
766 group.writeEntry("SplitterState V1", d->VSplitter->saveState().toBase64());
767 group.writeEntry("SplitterState H1", d->HSplitter->saveState().toBase64());
768 group.writeEntry("Splitter H1 CollapsedSize", d->splitterSize);
769 group.writeEntry("Map Layout", QVariant::fromValue(int(d->mapLayout)));
770 group.writeEntry("Bookmarks visible", d->actionBookmarkVisibility->isChecked());
771
772 config->sync();
773 }
774
reject()775 void GeolocationEdit::reject()
776 {
777 close();
778 }
779
closeEvent(QCloseEvent * e)780 void GeolocationEdit::closeEvent(QCloseEvent *e)
781 {
782 if (!e)
783 {
784 return;
785 }
786
787 // is the UI locked?
788
789 if (!d->uiEnabled)
790 {
791 // please wait until we are done ...
792
793 e->ignore();
794
795 return;
796 }
797
798 // are there any modified images?
799
800 int dirtyImagesCount = 0;
801
802 for (int i = 0 ; i < d->imageModel->rowCount() ; ++i)
803 {
804 const QModelIndex itemIndex = d->imageModel->index(i, 0);
805 GPSItemContainer* const item = d->imageModel->itemFromIndex(itemIndex);
806
807 if (item->isDirty() || item->isTagListDirty())
808 {
809 ++dirtyImagesCount;
810 }
811 }
812
813 if (dirtyImagesCount > 0)
814 {
815 const QString message = i18ncp("@info",
816 "You have 1 modified image.",
817 "You have %1 modified images.",
818 dirtyImagesCount
819 );
820
821 const int chosenAction = DMessageBox::showYesNo(QMessageBox::Warning,
822 this,
823 i18nc("@title", "Unsaved changes"),
824 i18nc("@info", "%1 Would you like to save the changes you made to them?", message)
825 );
826
827 if (chosenAction == QMessageBox::No)
828 {
829 saveSettings();
830 e->accept();
831 return;
832 }
833
834 if (chosenAction == QMessageBox::Yes)
835 {
836 // the user wants to save his changes.
837 // this will initiate the saving process and then close the dialog.
838
839 saveChanges(true);
840 }
841
842 // do not close the dialog for now
843
844 e->ignore();
845 return;
846 }
847
848 saveSettings();
849 e->accept();
850 }
851
slotImageActivated(const QModelIndex & index)852 void GeolocationEdit::slotImageActivated(const QModelIndex& index)
853 {
854 d->detailsWidget->slotSetCurrentImage(index);
855
856 if (!index.isValid())
857 {
858 return;
859 }
860
861 GPSItemContainer* const item = d->imageModel->itemFromIndex(index);
862
863 if (!item)
864 {
865 return;
866 }
867
868 const GeoCoordinates imageCoordinates = item->coordinates();
869
870 if (imageCoordinates.hasCoordinates())
871 {
872 d->mapWidget->setCenter(imageCoordinates);
873 }
874 }
875
slotSetUIEnabled(const bool enabledState,QObject * const cancelObject,const QString & cancelSlot)876 void GeolocationEdit::slotSetUIEnabled(const bool enabledState,
877 QObject* const cancelObject,
878 const QString& cancelSlot)
879 {
880 if (enabledState)
881 {
882 // hide the progress bar
883
884 d->progressBar->setVisible(false);
885 d->progressCancelButton->setVisible(false);
886 d->progressBar->setProgressValue(d->progressBar->progressTotalSteps());
887 }
888
889 /// TODO: disable the worldmapwidget and the images list (at least disable editing operations)
890
891 d->progressCancelObject = cancelObject;
892 d->progressCancelSlot = cancelSlot;
893 d->uiEnabled = enabledState;
894 m_buttons->setEnabled(enabledState);
895 d->correlatorWidget->setUIEnabledExternal(enabledState);
896 d->detailsWidget->setUIEnabledExternal(enabledState);
897 d->rgWidget->setUIEnabled(enabledState);
898 d->treeView->setEditEnabled(enabledState);
899 d->listViewContextMenu->setEnabled(enabledState);
900 d->mapWidget->setAllowModifications(enabledState);
901 }
902
slotSetUIEnabled(const bool enabledState)903 void GeolocationEdit::slotSetUIEnabled(const bool enabledState)
904 {
905 slotSetUIEnabled(enabledState, nullptr, QString());
906 }
907
saveChanges(const bool closeAfterwards)908 void GeolocationEdit::saveChanges(const bool closeAfterwards)
909 {
910 /**
911 * TODO: actually save the changes are there any modified images?
912 */
913
914 QList<QPersistentModelIndex> dirtyImages;
915
916 for (int i = 0 ; i < d->imageModel->rowCount() ; ++i)
917 {
918 const QModelIndex itemIndex = d->imageModel->index(i, 0);
919 GPSItemContainer* const item = d->imageModel->itemFromIndex(itemIndex);
920
921 if (item->isDirty() || item->isTagListDirty())
922 {
923 dirtyImages << itemIndex;
924 }
925 }
926
927 if (dirtyImages.isEmpty())
928 {
929 if (closeAfterwards)
930 {
931 close();
932 }
933
934 return;
935 }
936
937 /// TODO: disable the UI and provide progress and cancel information
938
939 slotSetUIEnabled(false);
940 slotProgressSetup(dirtyImages.count(), i18nc("@info", "Saving changes -"));
941
942 // initiate the saving
943
944 d->fileIOCountDone = 0;
945 d->fileIOCountTotal = dirtyImages.count();
946 d->fileIOCloseAfterSaving = closeAfterwards;
947 d->fileIOFutureWatcher = new QFutureWatcher<QPair<QUrl, QString> >(this);
948
949 connect(d->fileIOFutureWatcher, SIGNAL(resultsReadyAt(int,int)),
950 this, SLOT(slotFileChangesSaved(int,int)));
951
952 d->fileIOFuture = QtConcurrent::mapped(dirtyImages, SaveChangedImagesHelper(d->imageModel));
953 d->fileIOFutureWatcher->setFuture(d->fileIOFuture);
954 }
955
slotFileChangesSaved(int beginIndex,int endIndex)956 void GeolocationEdit::slotFileChangesSaved(int beginIndex, int endIndex)
957 {
958 qCDebug(DIGIKAM_DPLUGIN_GENERIC_LOG) << beginIndex << endIndex;
959
960 d->fileIOCountDone += (endIndex-beginIndex);
961 slotProgressChanged(d->fileIOCountDone);
962
963 if (d->fileIOCountDone == d->fileIOCountTotal)
964 {
965 slotSetUIEnabled(true);
966
967 // any errors?
968
969 QList<QPair<QUrl, QString> > errorList;
970
971 for (int i = 0 ; i < d->fileIOFuture.resultCount() ; ++i)
972 {
973 if (!d->fileIOFuture.resultAt(i).second.isEmpty())
974 {
975 errorList << d->fileIOFuture.resultAt(i);
976 }
977
978 if (!d->iface->supportAlbums())
979 {
980 // To rescan item metadata from host.
981
982 emit signalMetadataChangedForUrl(d->fileIOFuture.resultAt(i).first);
983 }
984 }
985
986 if (!errorList.isEmpty())
987 {
988 QStringList errorStrings;
989
990 for (int i = 0 ; i < errorList.count() ; ++i)
991 {
992 errorStrings << QString::fromLatin1("%1: %2")
993 .arg(errorList.at(i).first.toLocalFile())
994 .arg(errorList.at(i).second);
995 }
996
997 DMessageBox::showInformationList(QMessageBox::Critical,
998 this,
999 i18nc("@title", "Error"),
1000 i18nc("@info", "Failed to save some information:"),
1001 errorStrings);
1002 }
1003
1004 // done saving files
1005
1006 if (d->fileIOCloseAfterSaving)
1007 {
1008 close();
1009 }
1010 }
1011 }
1012
slotApplyClicked()1013 void GeolocationEdit::slotApplyClicked()
1014 {
1015 // save the changes, but do not close afterwards
1016
1017 saveChanges(false);
1018 }
1019
slotProgressChanged(const int currentProgress)1020 void GeolocationEdit::slotProgressChanged(const int currentProgress)
1021 {
1022 d->progressBar->setProgressValue(currentProgress);
1023 }
1024
slotProgressSetup(const int maxProgress,const QString & progressText)1025 void GeolocationEdit::slotProgressSetup(const int maxProgress, const QString& progressText)
1026 {
1027 d->progressBar->setProgressText(progressText);
1028 d->progressBar->setProgressTotalSteps(maxProgress);
1029 d->progressBar->setProgressValue(0);
1030 d->progressBar->setNotify(true);
1031 d->progressBar->setNotificationTitle(i18nc("@title", "Edit Geolocation"), QIcon::fromTheme(QLatin1String("globe")));
1032 d->progressBar->setVisible(true);
1033 d->progressCancelButton->setVisible(d->progressCancelObject != nullptr);
1034 }
1035
slotGPSUndoCommand(GPSUndoCommand * undoCommand)1036 void GeolocationEdit::slotGPSUndoCommand(GPSUndoCommand* undoCommand)
1037 {
1038 d->undoStack->push(undoCommand);
1039 }
1040
slotSortOptionTriggered(QAction *)1041 void GeolocationEdit::slotSortOptionTriggered(QAction* /*sortAction*/)
1042 {
1043 int newSortKey = 0;
1044
1045 if (d->sortActionOldestFirst->isChecked())
1046 {
1047 newSortKey |= 1;
1048 }
1049
1050 d->mapWidget->setSortKey(newSortKey);
1051 }
1052
slotProgressCancelButtonClicked()1053 void GeolocationEdit::slotProgressCancelButtonClicked()
1054 {
1055 if (d->progressCancelObject)
1056 {
1057 QTimer::singleShot(0, d->progressCancelObject, d->progressCancelSlot.toUtf8().constData());
1058
1059 d->progressBar->setProgressValue(d->progressBar->progressTotalSteps());
1060 }
1061 }
1062
slotBookmarkVisibilityToggled()1063 void GeolocationEdit::slotBookmarkVisibilityToggled()
1064 {
1065 d->bookmarkOwner->bookmarkModelHelper()->setVisible(d->actionBookmarkVisibility->isChecked());
1066 }
1067
slotLayoutChanged(int lay)1068 void GeolocationEdit::slotLayoutChanged(int lay)
1069 {
1070 d->mapLayout = (MapLayout)lay;
1071 adjustMapLayout(true);
1072 }
1073
slotTrackListChanged(const GeoCoordinates & coordinate)1074 void GeolocationEdit::slotTrackListChanged(const GeoCoordinates& coordinate)
1075 {
1076 d->mapWidget->setCenter(coordinate);
1077 }
1078
makeMapWidget(QWidget ** const pvbox)1079 MapWidget* GeolocationEdit::makeMapWidget(QWidget** const pvbox)
1080 {
1081 QWidget* const dummyWidget = new QWidget(this);
1082 QVBoxLayout* const vbox = new QVBoxLayout(dummyWidget);
1083 MapWidget* const mapWidget = new MapWidget(dummyWidget);
1084 mapWidget->setAvailableMouseModes(MouseModePan | MouseModeZoomIntoGroup | MouseModeSelectThumbnail);
1085 mapWidget->setVisibleMouseModes(MouseModePan | MouseModeZoomIntoGroup | MouseModeSelectThumbnail);
1086 mapWidget->setMouseMode(MouseModeSelectThumbnail);
1087 mapWidget->setGroupedModel(d->geoifaceMarkerModel);
1088 mapWidget->setDragDropHandler(d->mapDragDropHandler);
1089 mapWidget->addUngroupedModel(d->bookmarkOwner->bookmarkModelHelper());
1090 mapWidget->addUngroupedModel(d->searchWidget->getModelHelper());
1091 mapWidget->setTrackManager(d->trackManager);
1092 mapWidget->setSortOptionsMenu(d->sortMenu);
1093
1094 vbox->addWidget(mapWidget);
1095 vbox->addWidget(mapWidget->getControlWidget());
1096
1097 QToolButton* const bookmarkVisibilityButton = new QToolButton(mapWidget);
1098 bookmarkVisibilityButton->setDefaultAction(d->actionBookmarkVisibility);
1099 mapWidget->addWidgetToControlWidget(bookmarkVisibilityButton);
1100
1101 *pvbox = dummyWidget;
1102
1103 return mapWidget;
1104 }
1105
adjustMapLayout(const bool syncSettings)1106 void GeolocationEdit::adjustMapLayout(const bool syncSettings)
1107 {
1108 if (d->mapLayout == MapLayoutOne)
1109 {
1110 if (d->mapSplitter->count() > 1)
1111 {
1112 delete d->mapSplitter->widget(1);
1113 d->mapWidget2 = nullptr;
1114 }
1115 }
1116 else
1117 {
1118 if (d->mapSplitter->count() == 1)
1119 {
1120 QWidget* mapHolder = nullptr;
1121 d->mapWidget2 = makeMapWidget(&mapHolder);
1122 d->mapSplitter->addWidget(mapHolder);
1123
1124 if (syncSettings)
1125 {
1126 KSharedConfig::Ptr config = KSharedConfig::openConfig();
1127 KConfigGroup group = config->group("Geolocation Edit Settings");
1128 const KConfigGroup groupMapWidget = KConfigGroup(&group, "Map Widget");
1129 d->mapWidget2->readSettingsFromGroup(&groupMapWidget);
1130 d->mapWidget2->setActive(true);
1131 }
1132 }
1133
1134 if (d->mapLayout == MapLayoutHorizontal)
1135 {
1136 d->mapSplitter->setOrientation(Qt::Horizontal);
1137 }
1138 else
1139 {
1140 d->mapSplitter->setOrientation(Qt::Vertical);
1141 }
1142 }
1143 }
1144
1145 } // namespace DigikamGenericGeolocationEditPlugin
1146