File indexing completed on 2025-01-19 03:51:18

0001 /* ============================================================
0002  *
0003  * This file is a part of digiKam project
0004  * https://www.digikam.org
0005  *
0006  * Date        : 2006-05-16
0007  * Description : A tool to edit geolocation
0008  *
0009  * SPDX-FileCopyrightText: 2006-2024 by Gilles Caulier <caulier dot gilles at gmail dot com>
0010  * SPDX-FileCopyrightText: 2010-2014 by Michael G. Hansen <mike at mghansen dot de>
0011  * SPDX-FileCopyrightText: 2010      by Gabriel Voicu <ping dot gabi at gmail dot com>
0012  * SPDX-FileCopyrightText: 2014      by Justus Schwartz <justus at gmx dot li>
0013  *
0014  * SPDX-License-Identifier: GPL-2.0-or-later
0015  *
0016  * ============================================================ */
0017 
0018 #include "geolocationedit.h"
0019 
0020 // Qt includes
0021 
0022 #include <QtConcurrentMap>
0023 #include <QButtonGroup>
0024 #include <QCheckBox>
0025 #include <QCloseEvent>
0026 #include <QFuture>
0027 #include <QFutureWatcher>
0028 #include <QGroupBox>
0029 #include <QHBoxLayout>
0030 #include <QHeaderView>
0031 #include <QLabel>
0032 #include <QPushButton>
0033 #include <QGridLayout>
0034 #include <QPointer>
0035 #include <QRadioButton>
0036 #include <QSplitter>
0037 #include <QStackedLayout>
0038 #include <QStackedWidget>
0039 #include <QTimer>
0040 #include <QToolButton>
0041 #include <QTreeView>
0042 #include <QMenu>
0043 #include <QUndoView>
0044 #include <QAction>
0045 #include <QApplication>
0046 #include <QComboBox>
0047 #include <QDialogButtonBox>
0048 #include <QActionGroup>
0049 
0050 // KDE includes
0051 
0052 #include <kconfiggroup.h>
0053 #include <ksharedconfig.h>
0054 #include <klocalizedstring.h>
0055 
0056 // Local includes
0057 
0058 #include "dlayoutbox.h"
0059 #include "digikam_config.h"
0060 #include "itemmarkertiler.h"
0061 #include "gpscommon.h"
0062 #include "gpsitemmodel.h"
0063 #include "mapdragdrophandler.h"
0064 #include "gpsitemlist.h"
0065 #include "gpsitemlistdragdrophandler.h"
0066 #include "gpsitemlistcontextmenu.h"
0067 #include "gpscorrelatorwidget.h"
0068 #include "digikam_debug.h"
0069 #include "dmessagebox.h"
0070 #include "rgwidget.h"
0071 #include "kmlwidget.h"
0072 #include "statusprogressbar.h"
0073 #include "searchresultwidget.h"
0074 #include "backend-rg.h"
0075 #include "gpsitemdetails.h"
0076 #include "gpsgeoifacemodelhelper.h"
0077 #include "dxmlguiwindow.h"
0078 #include "gpsbookmarkowner.h"
0079 #include "gpsbookmarkmodelhelper.h"
0080 
0081 #ifdef GPSSYNC_MODELTEST
0082 #   include <modeltest.h>
0083 #endif
0084 
0085 using namespace Digikam;
0086 
0087 namespace DigikamGenericGeolocationEditPlugin
0088 {
0089 
0090 struct SaveChangedImagesHelper
0091 {
0092 public:
0093 
0094     explicit SaveChangedImagesHelper(GPSItemModel* const model)
0095         : imageModel(model)
0096     {
0097     }
0098 
0099     QPair<QUrl, QString> operator()(const QPersistentModelIndex& itemIndex)
0100     {
0101         GPSItemContainer* const item = imageModel->itemFromIndex(itemIndex);
0102 
0103         if (!item)
0104         {
0105             return QPair<QUrl, QString>(QUrl(), QString());
0106         }
0107 
0108         return QPair<QUrl, QString>(item->url(), item->saveChanges());
0109     }
0110 
0111 public:
0112 
0113     typedef QPair<QUrl, QString> result_type;
0114     GPSItemModel* const          imageModel;
0115 };
0116 
0117 // ---------------------------------------------------------------------------------
0118 
0119 struct LoadFileMetadataHelper
0120 {
0121 public:
0122 
0123     explicit LoadFileMetadataHelper(GPSItemModel* const model)
0124         : imageModel(model)
0125     {
0126     }
0127 
0128     QPair<QUrl, QString> operator()(const QPersistentModelIndex& itemIndex)
0129     {
0130         GPSItemContainer* const item = imageModel->itemFromIndex(itemIndex);
0131 
0132         if (!item)
0133         {
0134             return QPair<QUrl, QString>(QUrl(), QString());
0135         }
0136 
0137         item->loadImageData();
0138 
0139         return QPair<QUrl, QString>(item->url(), QString());
0140     }
0141 
0142 public:
0143 
0144     typedef QPair<QUrl, QString> result_type;
0145     GPSItemModel* const          imageModel;
0146 };
0147 
0148 // ---------------------------------------------------------------------------------
0149 
0150 class Q_DECL_HIDDEN GeolocationEdit::Private
0151 {
0152 public:
0153 
0154     explicit Private()
0155       : imageModel               (nullptr),
0156         selectionModel           (nullptr),
0157         uiEnabled                (true),
0158         listViewContextMenu      (nullptr),
0159         trackManager             (nullptr),
0160         fileIOFutureWatcher      (nullptr),
0161         fileIOCountDone          (0),
0162         fileIOCountTotal         (0),
0163         fileIOCloseAfterSaving   (false),
0164         buttonBox                (nullptr),
0165         VSplitter                (nullptr),
0166         HSplitter                (nullptr),
0167         treeView                 (nullptr),
0168         stackedWidget            (nullptr),
0169         tabBar                   (nullptr),
0170         splitterSize             (0),
0171         undoStack                (nullptr),
0172         undoView                 (nullptr),
0173         progressBar              (nullptr),
0174         progressCancelButton     (nullptr),
0175         progressCancelObject     (nullptr),
0176         detailsWidget            (nullptr),
0177         correlatorWidget         (nullptr),
0178         rgWidget                 (nullptr),
0179         searchWidget             (nullptr),
0180         kmlWidget                (nullptr),
0181         mapLayout                (MapLayoutOne),
0182         mapSplitter              (nullptr),
0183         mapWidget                (nullptr),
0184         mapWidget2               (nullptr),
0185         mapDragDropHandler       (nullptr),
0186         mapModelHelper           (nullptr),
0187         geoifaceMarkerModel      (nullptr),
0188         sortActionOldestFirst    (nullptr),
0189         sortActionYoungestFirst  (nullptr),
0190         sortMenu                 (nullptr),
0191         cbMapLayout              (nullptr),
0192         bookmarkOwner            (nullptr),
0193         actionBookmarkVisibility (nullptr),
0194         iface                    (nullptr)
0195     {
0196     }
0197 
0198     // General things
0199     GPSItemModel*                            imageModel;
0200     QItemSelectionModel*                     selectionModel;
0201     bool                                     uiEnabled;
0202     GPSItemListContextMenu*                  listViewContextMenu;
0203     TrackManager*                            trackManager;
0204 
0205     // Loading and saving
0206     QFuture<QPair<QUrl,QString> >            fileIOFuture;
0207     QFutureWatcher<QPair<QUrl,QString> >*    fileIOFutureWatcher;
0208     int                                      fileIOCountDone;
0209     int                                      fileIOCountTotal;
0210     bool                                     fileIOCloseAfterSaving;
0211 
0212     // UI
0213     QDialogButtonBox*                        buttonBox;
0214     QSplitter*                               VSplitter;
0215     QSplitter*                               HSplitter;
0216     GPSItemList*                             treeView;
0217     QStackedWidget*                          stackedWidget;
0218     QTabBar*                                 tabBar;
0219     int                                      splitterSize;
0220     QUndoStack*                              undoStack;
0221     QUndoView*                               undoView;
0222 
0223     // UI: progress
0224     StatusProgressBar*                       progressBar;
0225     QPushButton*                             progressCancelButton;
0226     QObject*                                 progressCancelObject;
0227     QString                                  progressCancelSlot;
0228 
0229     // UI: tab widgets
0230     GPSItemDetails*                          detailsWidget;
0231     GPSCorrelatorWidget*                     correlatorWidget;
0232     RGWidget*                                rgWidget;
0233     SearchResultWidget*                      searchWidget;
0234     KmlWidget*                               kmlWidget;
0235 
0236     // map: UI
0237     MapLayout                                mapLayout;
0238     QSplitter*                               mapSplitter;
0239     MapWidget*                               mapWidget;
0240     MapWidget*                               mapWidget2;
0241 
0242     // map: helpers
0243     MapDragDropHandler*                      mapDragDropHandler;
0244     GPSGeoIfaceModelHelper*                  mapModelHelper;
0245     ItemMarkerTiler*                         geoifaceMarkerModel;
0246 
0247     // map: actions
0248     QAction*                                 sortActionOldestFirst;
0249     QAction*                                 sortActionYoungestFirst;
0250     QMenu*                                   sortMenu;
0251     QComboBox*                               cbMapLayout;
0252 
0253     GPSBookmarkOwner*                        bookmarkOwner;
0254     QAction*                                 actionBookmarkVisibility;
0255 
0256     DInfoInterface*                          iface;
0257 };
0258 
0259 GeolocationEdit::GeolocationEdit(QWidget* const parent, DInfoInterface* const iface)
0260     : DPluginDialog(parent, QLatin1String("Geolocation Edit Settings")),
0261       d            (new Private)
0262 {
0263     setAttribute(Qt::WA_DeleteOnClose, true);
0264     setWindowTitle(i18nc("@title:window", "Geolocation Editor"));
0265     setMinimumSize(300, 400);
0266     setModal(true);
0267 
0268     d->iface          = iface;
0269     d->imageModel     = new GPSItemModel(this);
0270     d->selectionModel = new QItemSelectionModel(d->imageModel);
0271     d->trackManager   = new TrackManager(this);
0272 
0273 #ifdef GPSSYNC_MODELTEST
0274 
0275     new ModelTest(d->imageModel, this);
0276 
0277 #endif
0278 
0279     d->bookmarkOwner = new GPSBookmarkOwner(d->imageModel, this);
0280     d->undoStack     = new QUndoStack(this);
0281     d->stackedWidget = new QStackedWidget();
0282     d->searchWidget  = new SearchResultWidget(d->bookmarkOwner,
0283                                         d->imageModel,
0284                                         d->selectionModel,
0285                                         d->stackedWidget);
0286 
0287     GPSItemContainer::setHeaderData(d->imageModel);
0288     d->mapModelHelper      = new GPSGeoIfaceModelHelper(d->imageModel, d->selectionModel, this);
0289     d->mapModelHelper->addUngroupedModelHelper(d->bookmarkOwner->bookmarkModelHelper());
0290 
0291     d->mapModelHelper->addUngroupedModelHelper(d->searchWidget->getModelHelper());
0292     d->mapDragDropHandler  = new MapDragDropHandler(d->imageModel, d->mapModelHelper);
0293     d->geoifaceMarkerModel = new ItemMarkerTiler(d->mapModelHelper, this);
0294 
0295     d->actionBookmarkVisibility = new QAction(this);
0296     d->actionBookmarkVisibility->setIcon(QIcon::fromTheme(QLatin1String("bookmark-new")));
0297     d->actionBookmarkVisibility->setToolTip(i18nc("@info", "Display bookmarked positions on the map."));
0298     d->actionBookmarkVisibility->setCheckable(true);
0299 
0300     connect(d->actionBookmarkVisibility, SIGNAL(changed()),
0301             this, SLOT(slotBookmarkVisibilityToggled()));
0302 
0303     QVBoxLayout* const mainLayout = new QVBoxLayout(this);
0304     setLayout(mainLayout);
0305 
0306     DHBox* const hboxMain = new DHBox(this);
0307     mainLayout->addWidget(hboxMain, 10);
0308 
0309     d->HSplitter          = new QSplitter(Qt::Horizontal, hboxMain);
0310     d->HSplitter->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
0311 
0312     // ------------------------------------------------------------------------------------------------
0313 
0314     DHBox* const hbox            = new DHBox(this);
0315     QLabel* const labelMapLayout = new QLabel(i18nc("@label", "Layout:"), hbox);
0316     d->cbMapLayout               = new QComboBox(hbox);
0317     d->cbMapLayout->addItem(i18nc("@item", "One map"),                   QVariant::fromValue(MapLayoutOne));
0318     d->cbMapLayout->addItem(i18nc("@item", "Two maps - left and right"), QVariant::fromValue(MapLayoutVertical));
0319     d->cbMapLayout->addItem(i18nc("@item", "Two maps - top and bottom"), QVariant::fromValue(MapLayoutHorizontal));
0320     labelMapLayout->setBuddy(d->cbMapLayout);
0321 
0322     d->progressBar          = new StatusProgressBar(hbox);
0323     d->progressBar->setVisible(false);
0324     d->progressBar->setProgressBarMode(StatusProgressBar::ProgressBarMode);
0325     d->progressBar->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum);
0326 
0327     d->progressCancelButton = new QPushButton(hbox);
0328     d->progressCancelButton->setVisible(false);
0329     d->progressCancelButton->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
0330     d->progressCancelButton->setIcon(QIcon::fromTheme(QLatin1String("dialog-cancel")));
0331 
0332     connect(d->progressCancelButton, SIGNAL(clicked()),
0333             this, SLOT(slotProgressCancelButtonClicked()));
0334 
0335     m_buttons->addButton(QDialogButtonBox::Yes);
0336     m_buttons->addButton(QDialogButtonBox::Apply);
0337     m_buttons->addButton(QDialogButtonBox::Close);
0338     m_buttons->button(QDialogButtonBox::Yes)->setText(i18n("OK"));
0339     m_buttons->button(QDialogButtonBox::Yes)->setAutoDefault(false);
0340     m_buttons->button(QDialogButtonBox::Apply)->setAutoDefault(false);
0341     m_buttons->button(QDialogButtonBox::Close)->setAutoDefault(false);
0342     m_buttons->setParent(hbox);
0343 
0344     connect(m_buttons->button(QDialogButtonBox::Yes), &QPushButton::clicked,
0345             this, &GeolocationEdit::slotOkClicked);
0346 
0347     connect(m_buttons->button(QDialogButtonBox::Apply), &QPushButton::clicked,
0348             this, &GeolocationEdit::slotApplyClicked);
0349 
0350     connect(m_buttons->button(QDialogButtonBox::Close), &QPushButton::clicked,
0351             this, &GeolocationEdit::close);
0352 
0353     mainLayout->addWidget(hbox, 0);
0354 
0355     // ------------------------------------------------------------------------------------------------
0356 
0357     d->VSplitter = new QSplitter(Qt::Vertical, d->HSplitter);
0358     d->HSplitter->addWidget(d->VSplitter);
0359     d->HSplitter->setStretchFactor(0, 10);
0360 
0361     d->sortMenu = new QMenu(this);
0362     d->sortMenu->setTitle(i18nc("@title", "Sorting"));
0363     QActionGroup* const sortOrderExclusive = new QActionGroup(d->sortMenu);
0364     sortOrderExclusive->setExclusive(true);
0365 
0366     connect(sortOrderExclusive, SIGNAL(triggered(QAction*)),
0367             this, SLOT(slotSortOptionTriggered(QAction*)));
0368 
0369     d->sortActionOldestFirst = new QAction(i18nc("@action", "Show oldest first"), sortOrderExclusive);
0370     d->sortActionOldestFirst->setCheckable(true);
0371     d->sortMenu->addAction(d->sortActionOldestFirst);
0372 
0373     d->sortActionYoungestFirst = new QAction(i18nc("@action", "Show youngest first"), sortOrderExclusive);
0374     d->sortMenu->addAction(d->sortActionYoungestFirst);
0375     d->sortActionYoungestFirst->setCheckable(true);
0376 
0377     QWidget* mapVBox = nullptr;
0378     d->mapWidget     = makeMapWidget(&mapVBox);
0379     d->searchWidget->setPrimaryMapWidget(d->mapWidget);
0380     d->mapSplitter   = new QSplitter(this);
0381     d->mapSplitter->addWidget(mapVBox);
0382     d->VSplitter->addWidget(d->mapSplitter);
0383 
0384     d->treeView      = new GPSItemList(this);
0385     d->treeView->setModelAndSelectionModel(d->imageModel, d->selectionModel);
0386     d->treeView->setDragDropHandler(new GPSItemListDragDropHandler(this));
0387     d->treeView->setDragEnabled(true);
0388 
0389     /**
0390      * TODO: save and restore the state of the header
0391      * TODO: add a context menu to the header to select which columns should be visible
0392      * TODO: add sorting by column
0393      */
0394 
0395     d->treeView->setSelectionMode(QAbstractItemView::ExtendedSelection);
0396     d->treeView->setSortingEnabled(true);
0397     d->VSplitter->addWidget(d->treeView);
0398 
0399     d->listViewContextMenu  = new GPSItemListContextMenu(d->treeView, d->bookmarkOwner);
0400     d->HSplitter->addWidget(d->stackedWidget);
0401     d->HSplitter->setCollapsible(1, true);
0402     d->splitterSize         = 0;
0403 
0404     DVBox* const vboxTabBar = new DVBox(hboxMain);
0405     vboxTabBar->layout()->setContentsMargins(QMargins());
0406     vboxTabBar->layout()->setSpacing(0);
0407 
0408     d->tabBar = new QTabBar(vboxTabBar);
0409     d->tabBar->setShape(QTabBar::RoundedEast);
0410 
0411     dynamic_cast<QVBoxLayout*>(vboxTabBar->layout())->addStretch(200);
0412 
0413     d->tabBar->addTab(i18nc("@item: map tool", "Details"));
0414     d->tabBar->addTab(i18nc("@item: map tool", "GPS Correlator"));
0415     d->tabBar->addTab(i18nc("@item: map tool", "Undo/Redo"));
0416     d->tabBar->addTab(i18nc("@item: map tool", "Reverse Geocoding"));
0417     d->tabBar->addTab(i18nc("@item: map tool", "Search"));
0418     d->tabBar->addTab(i18nc("@item: map tool", "KML Export"));
0419 
0420     d->tabBar->installEventFilter(this);
0421 
0422     d->detailsWidget    = new GPSItemDetails(d->stackedWidget, d->imageModel);
0423     d->stackedWidget->addWidget(d->detailsWidget);
0424 
0425     d->correlatorWidget = new GPSCorrelatorWidget(d->stackedWidget, d->imageModel, d->selectionModel, d->trackManager);
0426     d->stackedWidget->addWidget(d->correlatorWidget);
0427 
0428     d->undoView         = new QUndoView(d->undoStack, d->stackedWidget);
0429     d->stackedWidget->addWidget(d->undoView);
0430 
0431     d->rgWidget         = new RGWidget(d->imageModel, d->selectionModel, d->iface->tagFilterModel(), d->stackedWidget);
0432     d->stackedWidget->addWidget(d->rgWidget);
0433 
0434     d->stackedWidget->addWidget(d->searchWidget);
0435 
0436     d->kmlWidget        = new KmlWidget(this, d->imageModel, d->iface);
0437     d->stackedWidget->addWidget(d->kmlWidget);
0438 
0439     // ---------------------------------------------------------------
0440 
0441     connect(d->treeView, SIGNAL(signalImageActivated(QModelIndex)),
0442             this, SLOT(slotImageActivated(QModelIndex)));
0443 
0444     connect(d->correlatorWidget, SIGNAL(signalSetUIEnabled(bool)),
0445             this, SLOT(slotSetUIEnabled(bool)));
0446 
0447     connect(d->correlatorWidget, SIGNAL(signalSetUIEnabled(bool,QObject*const,QString)),
0448             this, SLOT(slotSetUIEnabled(bool,QObject*const,QString)));
0449 
0450     connect(d->correlatorWidget, SIGNAL(signalProgressSetup(int,QString)),
0451             this, SLOT(slotProgressSetup(int,QString)));
0452 
0453     connect(d->correlatorWidget, SIGNAL(signalProgressChanged(int)),
0454             this, SLOT(slotProgressChanged(int)));
0455 
0456     connect(d->correlatorWidget, SIGNAL(signalUndoCommand(GPSUndoCommand*)),
0457             this, SLOT(slotGPSUndoCommand(GPSUndoCommand*)));
0458 
0459     connect(d->correlatorWidget, SIGNAL(signalTrackListChanged(Digikam::GeoCoordinates)),
0460             this, SLOT(slotTrackListChanged(Digikam::GeoCoordinates)));
0461 
0462     connect(d->mapModelHelper, SIGNAL(signalUndoCommand(GPSUndoCommand*)),
0463             this, SLOT(slotGPSUndoCommand(GPSUndoCommand*)));
0464 
0465     connect(d->rgWidget, SIGNAL(signalSetUIEnabled(bool)),
0466             this, SLOT(slotSetUIEnabled(bool)));
0467 
0468     connect(d->rgWidget, SIGNAL(signalSetUIEnabled(bool,QObject*const,QString)),
0469             this, SLOT(slotSetUIEnabled(bool,QObject*const,QString)));
0470 
0471     connect(d->rgWidget, SIGNAL(signalProgressSetup(int,QString)),
0472             this, SLOT(slotProgressSetup(int,QString)));
0473 
0474     connect(d->rgWidget, SIGNAL(signalProgressChanged(int)),
0475             this, SLOT(slotProgressChanged(int)));
0476 
0477     connect(d->rgWidget, SIGNAL(signalUndoCommand(GPSUndoCommand*)),
0478             this, SLOT(slotGPSUndoCommand(GPSUndoCommand*)));
0479 
0480     connect(d->searchWidget, SIGNAL(signalUndoCommand(GPSUndoCommand*)),
0481             this, SLOT(slotGPSUndoCommand(GPSUndoCommand*)));
0482 
0483     connect(d->listViewContextMenu, SIGNAL(signalSetUIEnabled(bool)),
0484             this, SLOT(slotSetUIEnabled(bool)));
0485 
0486     connect(d->listViewContextMenu, SIGNAL(signalSetUIEnabled(bool,QObject*const,QString)),
0487             this, SLOT(slotSetUIEnabled(bool,QObject*const,QString)));
0488 
0489     connect(d->listViewContextMenu, SIGNAL(signalProgressSetup(int,QString)),
0490             this, SLOT(slotProgressSetup(int,QString)));
0491 
0492     connect(d->listViewContextMenu, SIGNAL(signalProgressChanged(int)),
0493             this, SLOT(slotProgressChanged(int)));
0494 
0495     connect(d->listViewContextMenu, SIGNAL(signalUndoCommand(GPSUndoCommand*)),
0496             this, SLOT(slotGPSUndoCommand(GPSUndoCommand*)));
0497 
0498     connect(d->tabBar, SIGNAL(currentChanged(int)),
0499             this, SLOT(slotCurrentTabChanged(int)));
0500 
0501     connect(d->bookmarkOwner->bookmarkModelHelper(),
0502             SIGNAL(signalUndoCommand(GPSUndoCommand*)),
0503             this, SLOT(slotGPSUndoCommand(GPSUndoCommand*)));
0504 
0505     connect(d->detailsWidget, SIGNAL(signalUndoCommand(GPSUndoCommand*)),
0506             this, SLOT(slotGPSUndoCommand(GPSUndoCommand*)));
0507 
0508     connect(d->cbMapLayout, SIGNAL(activated(int)),
0509             this, SLOT(slotLayoutChanged(int)));
0510 
0511     connect(this, SIGNAL(signalMetadataChangedForUrl(QUrl)),
0512             d->iface, SLOT(slotMetadataChangedForUrl(QUrl)));
0513 
0514     readSettings();
0515 
0516     d->mapWidget->setActive(true);
0517 
0518     setItems(d->iface->currentGPSItems());
0519 }
0520 
0521 GeolocationEdit::~GeolocationEdit()
0522 {
0523     delete d;
0524 }
0525 
0526 bool GeolocationEdit::eventFilter(QObject* const o, QEvent* const e)
0527 {
0528     if ((o == d->tabBar) && (e->type() == QEvent::MouseButtonPress))
0529     {
0530         QMouseEvent const* m = static_cast<QMouseEvent*>(e);
0531 
0532         QPoint p(
0533 
0534 #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
0535 
0536                  m->position().toPoint()
0537 #else
0538 
0539                  m->x(), m->y()
0540 
0541 #endif
0542 
0543         );
0544 
0545         const int var = d->tabBar->tabAt(p);
0546 
0547         if (var < 0)
0548         {
0549             return false;
0550         }
0551 
0552         QList<int> sizes = d->HSplitter->sizes();
0553 
0554         if (d->splitterSize == 0)
0555         {
0556             if      (sizes.at(1) == 0)
0557             {
0558                 sizes[1] = d->stackedWidget->widget(var)->minimumSizeHint().width();
0559             }
0560             else if (d->tabBar->currentIndex() == var)
0561             {
0562                 d->splitterSize = sizes.at(1);
0563                 sizes[1]        = 0;
0564             }
0565         }
0566         else
0567         {
0568             sizes[1]        = d->splitterSize;
0569             d->splitterSize = 0;
0570         }
0571 
0572         d->tabBar->setCurrentIndex(var);
0573         d->stackedWidget->setCurrentIndex(var);
0574         d->HSplitter->setSizes(sizes);
0575         d->detailsWidget->slotSetActive((d->stackedWidget->currentWidget() == d->detailsWidget) &&
0576                                         (d->splitterSize == 0));
0577 
0578         return true;
0579     }
0580 
0581     return QWidget::eventFilter(o, e);
0582 }
0583 
0584 void GeolocationEdit::slotCurrentTabChanged(int index)
0585 {
0586     d->tabBar->setCurrentIndex(index);
0587     d->stackedWidget->setCurrentIndex(index);
0588     d->detailsWidget->slotSetActive(d->stackedWidget->currentWidget() == d->detailsWidget);
0589 }
0590 
0591 void GeolocationEdit::setCurrentTab(int index)
0592 {
0593     d->tabBar->setCurrentIndex(index);
0594     d->stackedWidget->setCurrentIndex(index);
0595     QList<int> sizes = d->HSplitter->sizes();
0596 
0597     if (d->splitterSize >= 0)
0598     {
0599         sizes[1] = d->splitterSize;
0600         d->splitterSize = 0;
0601     }
0602 
0603     d->HSplitter->setSizes(sizes);
0604     d->detailsWidget->slotSetActive((d->stackedWidget->currentWidget() == d->detailsWidget) &&
0605                                     (d->splitterSize == 0));
0606 }
0607 
0608 void GeolocationEdit::setImages(const QList<QUrl>& images)
0609 {
0610     QList<GPSItemContainer*> items;
0611 
0612     Q_FOREACH (const QUrl& u, images)
0613     {
0614         items << new GPSItemContainer(u);
0615     }
0616 
0617     setItems(items);
0618 }
0619 
0620 void GeolocationEdit::setItems(const QList<GPSItemContainer*>& items)
0621 {
0622     Q_FOREACH (GPSItemContainer* const newItem, items)
0623     {
0624         newItem->loadImageData();
0625         d->imageModel->addItem(newItem);
0626     }
0627 
0628     QList<QPersistentModelIndex> imagesToLoad;
0629 
0630     for (int i = 0 ; i < d->imageModel->rowCount() ; ++i)
0631     {
0632         imagesToLoad << d->imageModel->index(i, 0);
0633     }
0634 
0635     slotSetUIEnabled(false);
0636     slotProgressSetup(imagesToLoad.count(), i18nc("@info", "Loading metadata -"));
0637 
0638     // initiate the saving
0639 
0640     d->fileIOCountDone     = 0;
0641     d->fileIOCountTotal    = imagesToLoad.count();
0642     d->fileIOFutureWatcher = new QFutureWatcher<QPair<QUrl, QString> >(this);
0643 
0644     connect(d->fileIOFutureWatcher, SIGNAL(resultsReadyAt(int,int)),
0645             this, SLOT(slotFileMetadataLoaded(int,int)));
0646 
0647     d->fileIOFuture = QtConcurrent::mapped(imagesToLoad, LoadFileMetadataHelper(d->imageModel));
0648     d->fileIOFutureWatcher->setFuture(d->fileIOFuture);
0649 }
0650 
0651 void GeolocationEdit::slotFileMetadataLoaded(int beginIndex, int endIndex)
0652 {
0653     qCDebug(DIGIKAM_DPLUGIN_GENERIC_LOG) << beginIndex << endIndex;
0654     d->fileIOCountDone += (endIndex-beginIndex);
0655     slotProgressChanged(d->fileIOCountDone);
0656 
0657     if (d->fileIOCountDone == d->fileIOCountTotal)
0658     {
0659         slotSetUIEnabled(true);
0660     }
0661 }
0662 
0663 void GeolocationEdit::readSettings()
0664 {
0665     KSharedConfig::Ptr config = KSharedConfig::openConfig();
0666     KConfigGroup group        = config->group(QLatin1String("Geolocation Edit Settings"));
0667 
0668     // --------------------------
0669 
0670     // TODO: sanely determine a default backend
0671 
0672     const KConfigGroup groupMapWidget = KConfigGroup(&group, QLatin1String("Map Widget"));
0673     d->mapWidget->readSettingsFromGroup(&groupMapWidget);
0674 
0675     const KConfigGroup groupCorrelatorWidget = KConfigGroup(&group, QLatin1String("Correlator Widget"));
0676     d->correlatorWidget->readSettingsFromGroup(&groupCorrelatorWidget);
0677 
0678     const KConfigGroup groupTreeView = KConfigGroup(&group, QLatin1String("Tree View"));
0679     d->treeView->readSettingsFromGroup(&groupTreeView);
0680 
0681     const KConfigGroup groupSearchResultWidget = KConfigGroup(&group, QLatin1String("Search Widget"));
0682     d->searchWidget->readSettingsFromGroup(&groupSearchResultWidget);
0683 
0684     const KConfigGroup groupRGWidget = KConfigGroup(&group, QLatin1String("Reverse Geocoding Widget"));
0685     d->rgWidget->readSettingsFromGroup(&groupRGWidget);
0686 
0687     const KConfigGroup groupDialog = KConfigGroup(&group, QLatin1String("Dialog"));
0688 
0689     // --------------------------
0690 
0691     setCurrentTab(group.readEntry("Current Tab", 0));
0692     const bool showOldestFirst = group.readEntry("Show oldest images first", false);
0693 
0694     if (showOldestFirst)
0695     {
0696         d->sortActionOldestFirst->setChecked(true);
0697         d->mapWidget->setSortKey(1);
0698     }
0699     else
0700     {
0701         d->sortActionYoungestFirst->setChecked(true);
0702         d->mapWidget->setSortKey(0);
0703     }
0704 
0705     d->actionBookmarkVisibility->setChecked(group.readEntry("Bookmarks visible", false));
0706     slotBookmarkVisibilityToggled();
0707 
0708     if (group.hasKey("SplitterState V1"))
0709     {
0710         const QByteArray splitterState = QByteArray::fromBase64(group.readEntry("SplitterState V1", QByteArray()));
0711 
0712         if (!splitterState.isEmpty())
0713         {
0714             d->VSplitter->restoreState(splitterState);
0715         }
0716     }
0717 
0718     if (group.hasKey("SplitterState H1"))
0719     {
0720         const QByteArray splitterState = QByteArray::fromBase64(group.readEntry("SplitterState H1", QByteArray()));
0721 
0722         if (!splitterState.isEmpty())
0723         {
0724             d->HSplitter->restoreState(splitterState);
0725         }
0726     }
0727 
0728     d->splitterSize = group.readEntry("Splitter H1 CollapsedSize", 0);
0729 
0730     // ----------------------------------
0731 
0732     d->mapLayout = MapLayout(group.readEntry("Map Layout", QVariant::fromValue(int(MapLayoutOne))).value<int>());
0733     d->cbMapLayout->setCurrentIndex(d->mapLayout);
0734     adjustMapLayout(false);
0735 
0736     if (d->mapWidget2)
0737     {
0738         const KConfigGroup groupMapWidget2 = KConfigGroup(&group, QLatin1String("Map Widget 2"));
0739         d->mapWidget2->readSettingsFromGroup(&groupMapWidget2);
0740 
0741         d->mapWidget2->setActive(true);
0742     }
0743 }
0744 
0745 void GeolocationEdit::saveSettings()
0746 {
0747     KSharedConfig::Ptr config = KSharedConfig::openConfig();
0748     KConfigGroup group = config->group(QLatin1String("Geolocation Edit Settings"));
0749 
0750     // --------------------------
0751 
0752     KConfigGroup groupMapWidget = KConfigGroup(&group, QLatin1String("Map Widget"));
0753     d->mapWidget->saveSettingsToGroup(&groupMapWidget);
0754 
0755     if (d->mapWidget2)
0756     {
0757         KConfigGroup groupMapWidget2 = KConfigGroup(&group, QLatin1String("Map Widget 2"));
0758         d->mapWidget2->saveSettingsToGroup(&groupMapWidget2);
0759     }
0760 
0761     KConfigGroup groupCorrelatorWidget = KConfigGroup(&group, QLatin1String("Correlator Widget"));
0762     d->correlatorWidget->saveSettingsToGroup(&groupCorrelatorWidget);
0763 
0764     KConfigGroup groupTreeView = KConfigGroup(&group, QLatin1String("Tree View"));
0765     d->treeView->saveSettingsToGroup(&groupTreeView);
0766 
0767     KConfigGroup groupSearchResultWidget = KConfigGroup(&group, QLatin1String("Search Widget"));
0768     d->searchWidget->saveSettingsToGroup(&groupSearchResultWidget);
0769 
0770     KConfigGroup groupRGWidget = KConfigGroup(&group, QLatin1String("Reverse Geocoding Widget"));
0771     d->rgWidget->saveSettingsToGroup(&groupRGWidget);
0772 
0773     // --------------------------
0774 
0775     group.writeEntry("Current Tab",               d->tabBar->currentIndex());
0776     group.writeEntry("Show oldest images first",  d->sortActionOldestFirst->isChecked());
0777     group.writeEntry("SplitterState V1",          d->VSplitter->saveState().toBase64());
0778     group.writeEntry("SplitterState H1",          d->HSplitter->saveState().toBase64());
0779     group.writeEntry("Splitter H1 CollapsedSize", d->splitterSize);
0780     group.writeEntry("Map Layout",                QVariant::fromValue(int(d->mapLayout)));
0781     group.writeEntry("Bookmarks visible",         d->actionBookmarkVisibility->isChecked());
0782 
0783     config->sync();
0784 }
0785 
0786 void GeolocationEdit::reject()
0787 {
0788     close();
0789 }
0790 
0791 void GeolocationEdit::closeEvent(QCloseEvent *e)
0792 {
0793     if (!e)
0794     {
0795         return;
0796     }
0797 
0798     // is the UI locked?
0799 
0800     if (!d->uiEnabled)
0801     {
0802         // please wait until we are done ...
0803 
0804         e->ignore();
0805 
0806         return;
0807     }
0808 
0809     // are there any modified images?
0810 
0811     int dirtyImagesCount = 0;
0812 
0813     for (int i = 0 ; i < d->imageModel->rowCount() ; ++i)
0814     {
0815         const QModelIndex itemIndex  = d->imageModel->index(i, 0);
0816         GPSItemContainer* const item = d->imageModel->itemFromIndex(itemIndex);
0817 
0818         if (item->isDirty() || item->isTagListDirty())
0819         {
0820             ++dirtyImagesCount;
0821         }
0822     }
0823 
0824     if (dirtyImagesCount > 0)
0825     {
0826         const QString message = i18ncp("@info",
0827                     "You have 1 modified image.",
0828                     "You have %1 modified images.",
0829                     dirtyImagesCount
0830                 );
0831 
0832         const int chosenAction = DMessageBox::showYesNo(QMessageBox::Warning,
0833                                                         this,
0834                                                         i18nc("@title:window", "Unsaved Changes"),
0835                                                         i18nc("@info", "%1 Would you like to save the changes you made to them?", message)
0836                                                        );
0837 
0838         if (chosenAction == QMessageBox::No)
0839         {
0840             saveSettings();
0841             e->accept();
0842             return;
0843         }
0844 
0845         if (chosenAction == QMessageBox::Yes)
0846         {
0847             // the user wants to save his changes.
0848             // this will initiate the saving process and then close the dialog.
0849 
0850             saveChanges(true);
0851         }
0852 
0853         // do not close the dialog for now
0854 
0855         e->ignore();
0856         return;
0857     }
0858 
0859     saveSettings();
0860     e->accept();
0861 }
0862 
0863 void GeolocationEdit::slotImageActivated(const QModelIndex& index)
0864 {
0865     d->detailsWidget->slotSetCurrentImage(index);
0866 
0867     if (!index.isValid())
0868     {
0869         return;
0870     }
0871 
0872     GPSItemContainer* const item = d->imageModel->itemFromIndex(index);
0873 
0874     if (!item)
0875     {
0876         return;
0877     }
0878 
0879     const GeoCoordinates imageCoordinates = item->coordinates();
0880 
0881     if (imageCoordinates.hasCoordinates())
0882     {
0883         d->mapWidget->setCenter(imageCoordinates);
0884     }
0885 }
0886 
0887 void GeolocationEdit::slotSetUIEnabled(const bool enabledState,
0888                                        QObject* const cancelObject,
0889                                        const QString& cancelSlot)
0890 {
0891     if (enabledState)
0892     {
0893         // hide the progress bar
0894 
0895         d->progressBar->setVisible(false);
0896         d->progressCancelButton->setVisible(false);
0897         d->progressBar->setProgressValue(d->progressBar->progressTotalSteps());
0898     }
0899 
0900     /// TODO: disable the worldmapwidget and the images list (at least disable editing operations)
0901 
0902     d->progressCancelObject = cancelObject;
0903     d->progressCancelSlot   = cancelSlot;
0904     d->uiEnabled            = enabledState;
0905     m_buttons->setEnabled(enabledState);
0906     d->correlatorWidget->setUIEnabledExternal(enabledState);
0907     d->detailsWidget->setUIEnabledExternal(enabledState);
0908     d->rgWidget->setUIEnabled(enabledState);
0909     d->treeView->setEditEnabled(enabledState);
0910     d->listViewContextMenu->setEnabled(enabledState);
0911     d->mapWidget->setAllowModifications(enabledState);
0912 }
0913 
0914 void GeolocationEdit::slotSetUIEnabled(const bool enabledState)
0915 {
0916     slotSetUIEnabled(enabledState, nullptr, QString());
0917 }
0918 
0919 void GeolocationEdit::saveChanges(const bool closeAfterwards)
0920 {
0921     /**
0922      * TODO: actually save the changes are there any modified images?
0923      */
0924 
0925     QList<QPersistentModelIndex> dirtyImages;
0926 
0927     for (int i = 0 ; i < d->imageModel->rowCount() ; ++i)
0928     {
0929         const QModelIndex itemIndex  = d->imageModel->index(i, 0);
0930         GPSItemContainer* const item = d->imageModel->itemFromIndex(itemIndex);
0931 
0932         if (item->isDirty() || item->isTagListDirty())
0933         {
0934             dirtyImages << itemIndex;
0935         }
0936     }
0937 
0938     if (dirtyImages.isEmpty())
0939     {
0940         if (closeAfterwards)
0941         {
0942             close();
0943         }
0944 
0945         return;
0946     }
0947 
0948     /// TODO: disable the UI and provide progress and cancel information
0949 
0950     slotSetUIEnabled(false);
0951     slotProgressSetup(dirtyImages.count(), i18nc("@info", "Saving changes -"));
0952 
0953     // initiate the saving
0954 
0955     d->fileIOCountDone        = 0;
0956     d->fileIOCountTotal       = dirtyImages.count();
0957     d->fileIOCloseAfterSaving = closeAfterwards;
0958     d->fileIOFutureWatcher    = new QFutureWatcher<QPair<QUrl, QString> >(this);
0959 
0960     connect(d->fileIOFutureWatcher, SIGNAL(resultsReadyAt(int,int)),
0961             this, SLOT(slotFileChangesSaved(int,int)));
0962 
0963     d->fileIOFuture = QtConcurrent::mapped(dirtyImages, SaveChangedImagesHelper(d->imageModel));
0964     d->fileIOFutureWatcher->setFuture(d->fileIOFuture);
0965 }
0966 
0967 void GeolocationEdit::slotFileChangesSaved(int beginIndex, int endIndex)
0968 {
0969     qCDebug(DIGIKAM_DPLUGIN_GENERIC_LOG) << beginIndex << endIndex;
0970 
0971     d->fileIOCountDone += (endIndex-beginIndex);
0972     slotProgressChanged(d->fileIOCountDone);
0973 
0974     if (d->fileIOCountDone == d->fileIOCountTotal)
0975     {
0976         slotSetUIEnabled(true);
0977 
0978         // any errors?
0979 
0980         QList<QPair<QUrl, QString> > errorList;
0981 
0982         for (int i = 0 ; i < d->fileIOFuture.resultCount() ; ++i)
0983         {
0984             if (!d->fileIOFuture.resultAt(i).second.isEmpty())
0985             {
0986                 errorList << d->fileIOFuture.resultAt(i);
0987             }
0988 
0989             if (!d->iface->supportAlbums())
0990             {
0991                 // To rescan item metadata from host.
0992 
0993                 Q_EMIT signalMetadataChangedForUrl(d->fileIOFuture.resultAt(i).first);
0994             }
0995         }
0996 
0997         if (!errorList.isEmpty())
0998         {
0999             QStringList errorStrings;
1000 
1001             for (int i = 0 ; i < errorList.count() ; ++i)
1002             {
1003                 errorStrings << QString::fromLatin1("%1: %2")
1004                     .arg(errorList.at(i).first.toLocalFile())
1005                     .arg(errorList.at(i).second);
1006             }
1007 
1008             DMessageBox::showInformationList(QMessageBox::Critical,
1009                                              this,
1010                                              i18nc("@title:window", "Error"),
1011                                              i18nc("@info", "Failed to save some information:"),
1012                                              errorStrings);
1013         }
1014 
1015         // done saving files
1016 
1017         if (d->fileIOCloseAfterSaving)
1018         {
1019             close();
1020         }
1021     }
1022 }
1023 
1024 void GeolocationEdit::slotOkClicked()
1025 {
1026     // save the changes, and close afterwards
1027 
1028     saveChanges(true);
1029 }
1030 
1031 void GeolocationEdit::slotApplyClicked()
1032 {
1033     // save the changes, but do not close afterwards
1034 
1035     saveChanges(false);
1036 }
1037 
1038 void GeolocationEdit::slotProgressChanged(const int currentProgress)
1039 {
1040     d->progressBar->setProgressValue(currentProgress);
1041 }
1042 
1043 void GeolocationEdit::slotProgressSetup(const int maxProgress, const QString& progressText)
1044 {
1045     d->progressBar->setProgressText(progressText);
1046     d->progressBar->setProgressTotalSteps(maxProgress);
1047     d->progressBar->setProgressValue(0);
1048     d->progressBar->setNotify(true);
1049     d->progressBar->setNotificationTitle(i18nc("@title", "Edit Geolocation"), QIcon::fromTheme(QLatin1String("globe")));
1050     d->progressBar->setVisible(true);
1051     d->progressCancelButton->setVisible(d->progressCancelObject != nullptr);
1052 }
1053 
1054 void GeolocationEdit::slotGPSUndoCommand(GPSUndoCommand* undoCommand)
1055 {
1056     d->undoStack->push(undoCommand);
1057 }
1058 
1059 void GeolocationEdit::slotSortOptionTriggered(QAction* /*sortAction*/)
1060 {
1061     int newSortKey = 0;
1062 
1063     if (d->sortActionOldestFirst->isChecked())
1064     {
1065         newSortKey |= 1;
1066     }
1067 
1068     d->mapWidget->setSortKey(newSortKey);
1069 }
1070 
1071 void GeolocationEdit::slotProgressCancelButtonClicked()
1072 {
1073     if (d->progressCancelObject)
1074     {
1075         QTimer::singleShot(0, d->progressCancelObject, d->progressCancelSlot.toUtf8().constData());
1076 
1077         d->progressBar->setProgressValue(d->progressBar->progressTotalSteps());
1078     }
1079 }
1080 
1081 void GeolocationEdit::slotBookmarkVisibilityToggled()
1082 {
1083     d->bookmarkOwner->bookmarkModelHelper()->setVisible(d->actionBookmarkVisibility->isChecked());
1084 }
1085 
1086 void GeolocationEdit::slotLayoutChanged(int lay)
1087 {
1088     d->mapLayout = (MapLayout)lay;
1089     adjustMapLayout(true);
1090 }
1091 
1092 void GeolocationEdit::slotTrackListChanged(const GeoCoordinates& coordinate)
1093 {
1094     d->mapWidget->setCenter(coordinate);
1095 }
1096 
1097 MapWidget* GeolocationEdit::makeMapWidget(QWidget** const pvbox)
1098 {
1099     QWidget* const dummyWidget = new QWidget(this);
1100     QVBoxLayout* const vbox    = new QVBoxLayout(dummyWidget);
1101     MapWidget* const mapWidget = new MapWidget(dummyWidget);
1102     mapWidget->setAvailableMouseModes(MouseModePan | MouseModeZoomIntoGroup | MouseModeSelectThumbnail);
1103     mapWidget->setVisibleMouseModes(MouseModePan | MouseModeZoomIntoGroup | MouseModeSelectThumbnail);
1104     mapWidget->setMouseMode(MouseModeSelectThumbnail);
1105     mapWidget->setGroupedModel(d->geoifaceMarkerModel);
1106     mapWidget->setDragDropHandler(d->mapDragDropHandler);
1107     mapWidget->addUngroupedModel(d->bookmarkOwner->bookmarkModelHelper());
1108     mapWidget->addUngroupedModel(d->searchWidget->getModelHelper());
1109     mapWidget->setTrackManager(d->trackManager);
1110     mapWidget->setSortOptionsMenu(d->sortMenu);
1111 
1112     vbox->addWidget(mapWidget);
1113     vbox->addWidget(mapWidget->getControlWidget());
1114 
1115     QToolButton* const bookmarkVisibilityButton = new QToolButton(mapWidget);
1116     bookmarkVisibilityButton->setDefaultAction(d->actionBookmarkVisibility);
1117     mapWidget->addWidgetToControlWidget(bookmarkVisibilityButton);
1118 
1119     *pvbox = dummyWidget;
1120 
1121     return mapWidget;
1122 }
1123 
1124 void GeolocationEdit::adjustMapLayout(const bool syncSettings)
1125 {
1126     if (d->mapLayout == MapLayoutOne)
1127     {
1128         if (d->mapSplitter->count() > 1)
1129         {
1130             delete d->mapSplitter->widget(1);
1131             d->mapWidget2 = nullptr;
1132         }
1133     }
1134     else
1135     {
1136         if (d->mapSplitter->count() == 1)
1137         {
1138             QWidget* mapHolder = nullptr;
1139             d->mapWidget2      = makeMapWidget(&mapHolder);
1140             d->mapSplitter->addWidget(mapHolder);
1141 
1142             if (syncSettings)
1143             {
1144                 KSharedConfig::Ptr config         = KSharedConfig::openConfig();
1145                 KConfigGroup group                = config->group(QLatin1String("Geolocation Edit Settings"));
1146                 const KConfigGroup groupMapWidget = KConfigGroup(&group, QLatin1String("Map Widget"));
1147                 d->mapWidget2->readSettingsFromGroup(&groupMapWidget);
1148                 d->mapWidget2->setActive(true);
1149             }
1150         }
1151 
1152         if (d->mapLayout == MapLayoutHorizontal)
1153         {
1154             d->mapSplitter->setOrientation(Qt::Horizontal);
1155         }
1156         else
1157         {
1158             d->mapSplitter->setOrientation(Qt::Vertical);
1159         }
1160     }
1161 }
1162 
1163 } // namespace DigikamGenericGeolocationEditPlugin
1164 
1165 #include "moc_geolocationedit.cpp"