1 /* ============================================================
2  *
3  * This file is a part of digiKam project
4  * https://www.digikam.org
5  *
6  * Date        : 2009-12-01
7  * Description : world map widget library
8  *
9  * Copyright (C) 2010-2021 by Gilles Caulier <caulier dot gilles at gmail dot com>
10  * Copyright (C) 2009-2011 by Michael G. Hansen <mike at mghansen dot de>
11  * Copyright (C)      2014 by Justus Schwartz <justus at gmx dot li>
12  *
13  * This program is free software; you can redistribute it
14  * and/or modify it under the terms of the GNU General
15  * Public License as published by the Free Software Foundation;
16  * either version 2, or (at your option)
17  * any later version.
18  *
19  * This program is distributed in the hope that it will be useful,
20  * but WITHOUT ANY WARRANTY; without even the implied warranty of
21  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
22  * GNU General Public License for more details.
23  *
24  * ============================================================ */
25 
26 #include "mapwidget.h"
27 
28 // C++ includes
29 
30 #include <cmath>
31 
32 // Qt includes
33 
34 #include <QDragEnterEvent>
35 #include <QDropEvent>
36 #include <QItemSelectionModel>
37 #include <QMenu>
38 #include <QPainter>
39 #include <QPointer>
40 #include <QStackedLayout>
41 #include <QTimer>
42 #include <QPushButton>
43 #include <QToolButton>
44 #include <QHBoxLayout>
45 #include <QAction>
46 #include <QFrame>
47 
48 // KDE includes
49 
50 #include <klocalizedstring.h>
51 #include <kconfiggroup.h>
52 
53 // Marbel includes
54 
55 #include <marble/GeoDataLineString.h>
56 #include <marble/GeoDataLatLonBox.h>
57 #include <marble/MarbleGlobal.h>
58 
59 // local includes
60 
61 #include "geoifacecommon.h"
62 #include "geodragdrophandler.h"
63 #include "geomodelhelper.h"
64 #include "trackmanager.h"
65 #include "placeholderwidget.h"
66 #include "tilegrouper.h"
67 #include "digikam_debug.h"
68 #include "abstractmarkertiler.h"
69 #include "backendgooglemaps.h"
70 #include "backendmarble.h"
71 
72 namespace Digikam
73 {
74 
75 /**
76  * @class MapWidget
77  * @brief The central map view class of geolocation interface
78  *
79  * The MapWidget class is the central widget of geolocation interface. It provides a widget which can display maps using
80  * either the Marble or Google Maps backend. Using a model, items can be displayed on the map. For
81  * models containing only a small number of items, the items can be shown directly, but for models with
82  * a larger number of items, the items can also be grouped. Currently, any number of ungrouped models
83  * can be shown, but only one grouped model. Item selection models can also be used along with the models,
84  * to interact with the selection states of the items on the map. In order to use a model with geolocation interface, however,
85  * a model helper has to be implemented, which extracts data from the model that is not provided by the Qt part
86  * of a model's API.
87  *
88  * Now, a brief introduction on how to get geolocation interface working is provided:
89  * @li First, an instance of @c MapWidget has to be created.
90  * @li Next, @c GeoModelHelper has to be subclassed and at least the pure virtual functions have to be implemented.
91  * @li To show the model's data ungrouped, the model helper has to be added to @c MapWidget instance using addUngroupedModel.
92  * @li To show the model's data grouped, an instance of @c AbstractMarkerTiler has to be created and the model helper has to be
93  *     set to it using setMarkerGeoModelHelper. The @c AbstractMarkerTiler has then to be given to MapWidget using setGroupedModel. If
94  *     the items to be displayed do not reside in a model, a subclass of @c AbstractMarkerTiler can be created which returns
95  *     just the number of items in a particular area, and picks representative items for thumbnails.
96  * @li To handle dropping of items from the host applications UI onto the map, @c DragDropHandler has to be subclassed
97  *     as well and added to the model using setDragDropHandler.
98  * @li Finally, setActive() has to be called to tell the widget that it should start displaying things.
99  */
100 
101 class Q_DECL_HIDDEN MapWidget::Private
102 {
103 public:
104 
Private()105     explicit Private()
106       : loadedBackends(),
107         currentBackend(nullptr),
108         currentBackendName(),
109         stackedLayout(nullptr),
110         cacheCenterCoordinate(52.0,6.0),
111         cacheZoom(QLatin1String("marble:900")),
112         configurationMenu(nullptr),
113         actionGroupBackendSelection(nullptr),
114         actionZoomIn(nullptr),
115         actionZoomOut(nullptr),
116         actionShowThumbnails(nullptr),
117         mouseModesHolder(nullptr),
118         controlWidget(nullptr),
119         actionPreviewSingleItems(nullptr),
120         actionPreviewGroupedItems(nullptr),
121         actionShowNumbersOnItems(nullptr),
122         lazyReclusteringRequested(false),
123         dragDropHandler(nullptr),
124         sortMenu(nullptr),
125         actionIncreaseThumbnailSize(nullptr),
126         actionDecreaseThumbnailSize(nullptr),
127         hBoxForAdditionalControlWidgetItems(nullptr),
128         mouseModeActionGroup(nullptr),
129         actionRemoveCurrentRegionSelection(nullptr),
130         actionSetRegionSelectionMode(nullptr),
131         actionSetPanMode(nullptr),
132         actionSetZoomIntoGroupMode(nullptr),
133         actionSetRegionSelectionFromIconMode(nullptr),
134         actionSetFilterMode(nullptr),
135         actionRemoveFilter(nullptr),
136         actionSetSelectThumbnailMode(nullptr),
137         setPanModeButton(nullptr),
138         setSelectionModeButton(nullptr),
139         removeCurrentSelectionButton(nullptr),
140         setZoomModeButton(nullptr),
141         setRegionSelectionFromIconModeButton(nullptr),
142         setFilterModeButton(nullptr),
143         removeFilterModeButton(nullptr),
144         setSelectThumbnailMode(nullptr),
145         thumbnailTimer(nullptr),
146         thumbnailTimerCount(0),
147         thumbnailsHaveBeenLoaded(false),
148         availableExtraActions(),
149         visibleExtraActions(),
150         actionStickyMode(nullptr),
151         buttonStickyMode(nullptr),
152         placeholderWidget(nullptr)
153     {
154     }
155 
156     QList<MapBackend*>      loadedBackends;
157     MapBackend*             currentBackend;
158     QString                 currentBackendName;
159     QStackedLayout*         stackedLayout;
160 
161     /// NOTE: these values are cached in case the backend is not ready:
162     GeoCoordinates          cacheCenterCoordinate;
163     QString                 cacheZoom;
164 
165     /// actions for controlling the widget
166     QMenu*                  configurationMenu;
167     QActionGroup*           actionGroupBackendSelection;
168     QAction*                actionZoomIn;
169     QAction*                actionZoomOut;
170     QAction*                actionShowThumbnails;
171     QWidget*                mouseModesHolder;
172     QPointer<QWidget>       controlWidget;
173     QAction*                actionPreviewSingleItems;
174     QAction*                actionPreviewGroupedItems;
175     QAction*                actionShowNumbersOnItems;
176 
177     bool                    lazyReclusteringRequested;
178 
179     GeoDragDropHandler*     dragDropHandler;
180 
181     QMenu*                  sortMenu;
182     QAction*                actionIncreaseThumbnailSize;
183     QAction*                actionDecreaseThumbnailSize;
184     QWidget*                hBoxForAdditionalControlWidgetItems;
185 
186     QActionGroup*           mouseModeActionGroup;
187     QAction*                actionRemoveCurrentRegionSelection;
188     QAction*                actionSetRegionSelectionMode;
189     QAction*                actionSetPanMode;
190     QAction*                actionSetZoomIntoGroupMode;
191     QAction*                actionSetRegionSelectionFromIconMode;
192     QAction*                actionSetFilterMode;
193     QAction*                actionRemoveFilter;
194     QAction*                actionSetSelectThumbnailMode;
195     QToolButton*            setPanModeButton;
196     QToolButton*            setSelectionModeButton;
197     QToolButton*            removeCurrentSelectionButton;
198     QToolButton*            setZoomModeButton;
199     QToolButton*            setRegionSelectionFromIconModeButton;
200     QToolButton*            setFilterModeButton;
201     QToolButton*            removeFilterModeButton;
202     QToolButton*            setSelectThumbnailMode;
203 
204     QTimer*                 thumbnailTimer;
205     int                     thumbnailTimerCount;
206     bool                    thumbnailsHaveBeenLoaded;
207 
208     GeoExtraActions         availableExtraActions;
209     GeoExtraActions         visibleExtraActions;
210     QAction*                actionStickyMode;
211     QToolButton*            buttonStickyMode;
212 
213     /// NOTE: to be sorted later
214     PlaceholderWidget*      placeholderWidget;
215 };
216 
MapWidget(QWidget * const parent)217 MapWidget::MapWidget(QWidget* const parent)
218     : QWidget(parent),
219       s(new GeoIfaceSharedData),
220       d(new Private)
221 {
222     createActions();
223 
224     s->worldMapWidget = this;
225     s->tileGrouper    = new TileGrouper(s, this);
226 
227     d->stackedLayout  = new QStackedLayout(this);
228     setLayout(d->stackedLayout);
229 
230     d->placeholderWidget = new PlaceholderWidget();
231     d->stackedLayout->addWidget(d->placeholderWidget);
232 
233     d->loadedBackends.append(new BackendGoogleMaps(s, this));
234     d->loadedBackends.append(new BackendMarble(s, this));
235 /*
236     d->loadedBackends.append(new BackendOSM(s, this));
237 */
238     createActionsForBackendSelection();
239 
240     setAcceptDrops(true);
241 }
242 
createActions()243 void MapWidget::createActions()
244 {
245     d->actionZoomIn = new QAction(this);
246     d->actionZoomIn->setIcon(QIcon::fromTheme( QLatin1String("zoom-in") ));
247     d->actionZoomIn->setToolTip(i18n("Zoom in"));
248 
249     connect(d->actionZoomIn, &QAction::triggered,
250             this, &MapWidget::slotZoomIn);
251 
252     d->actionZoomOut = new QAction(this);
253     d->actionZoomOut->setIcon(QIcon::fromTheme( QLatin1String("zoom-out") ));
254     d->actionZoomOut->setToolTip(i18n("Zoom out"));
255 
256     connect(d->actionZoomOut, &QAction::triggered,
257             this, &MapWidget::slotZoomOut);
258 
259     d->actionShowThumbnails = new QAction(this);
260     d->actionShowThumbnails->setToolTip(i18n("Switch between markers and thumbnails."));
261     d->actionShowThumbnails->setCheckable(true);
262     d->actionShowThumbnails->setChecked(true);
263 
264     connect(d->actionShowThumbnails, &QAction::triggered,
265             this, &MapWidget::slotShowThumbnailsChanged);
266 
267     // create backend selection entries:
268 
269     d->actionGroupBackendSelection = new QActionGroup(this);
270     d->actionGroupBackendSelection->setExclusive(true);
271 
272     connect(d->actionGroupBackendSelection, &QActionGroup::triggered,
273             this, &MapWidget::slotChangeBackend);
274 
275     createActionsForBackendSelection();
276 
277     d->configurationMenu         = new QMenu(this);
278     d->actionPreviewSingleItems  = new QAction(i18n("Preview single items"), this);
279     d->actionPreviewSingleItems->setCheckable(true);
280     d->actionPreviewSingleItems->setChecked(true);
281     d->actionPreviewGroupedItems = new QAction(i18n("Preview grouped items"), this);
282     d->actionPreviewGroupedItems->setCheckable(true);
283     d->actionPreviewGroupedItems->setChecked(true);
284     d->actionShowNumbersOnItems  = new QAction(i18n("Show numbers"), this);
285     d->actionShowNumbersOnItems->setCheckable(true);
286     d->actionShowNumbersOnItems->setChecked(true);
287 
288     d->actionIncreaseThumbnailSize = new QAction(i18n("T+"), this);
289     d->actionIncreaseThumbnailSize->setToolTip(i18n("Increase the thumbnail size on the map"));
290     d->actionDecreaseThumbnailSize = new QAction(i18n("T-"), this);
291     d->actionDecreaseThumbnailSize->setToolTip(i18n("Decrease the thumbnail size on the map"));
292 
293     d->actionRemoveCurrentRegionSelection = new QAction(this);
294 /*
295     d->actionRemoveCurrentRegionSelection->setEnabled(false);
296 */
297     d->actionRemoveCurrentRegionSelection->setIcon(QIcon::fromTheme( QLatin1String("edit-clear") ));
298     d->actionRemoveCurrentRegionSelection->setToolTip(i18n("Remove the current region selection"));
299 
300     d->mouseModeActionGroup = new QActionGroup(this);
301     d->mouseModeActionGroup->setExclusive(true);
302 
303     d->actionSetRegionSelectionMode = new QAction(d->mouseModeActionGroup);
304     d->actionSetRegionSelectionMode->setCheckable(true);
305     d->actionSetRegionSelectionMode->setIcon(QIcon::fromTheme( QLatin1String("select-rectangular") ));
306     d->actionSetRegionSelectionMode->setToolTip(i18n("Select images by drawing a rectangle"));
307     d->actionSetRegionSelectionMode->setData(QVariant::fromValue<Digikam::GeoMouseModes>(MouseModeRegionSelection));
308 
309     d->actionSetPanMode = new QAction(d->mouseModeActionGroup);
310     d->actionSetPanMode->setCheckable(true);
311     d->actionSetPanMode->setToolTip(i18n("Pan mode"));
312     d->actionSetPanMode->setIcon(QIcon::fromTheme( QLatin1String("transform-move") ));
313     d->actionSetPanMode->setChecked(true);
314     d->actionSetPanMode->setData(QVariant::fromValue<Digikam::GeoMouseModes>(MouseModePan));
315 
316     d->actionSetZoomIntoGroupMode = new QAction(d->mouseModeActionGroup);
317     d->actionSetZoomIntoGroupMode->setCheckable(true);
318     d->actionSetZoomIntoGroupMode->setToolTip(i18n("Zoom into a group"));
319     d->actionSetZoomIntoGroupMode->setIcon(QIcon::fromTheme( QLatin1String("zoom-fit-best") ));
320     d->actionSetZoomIntoGroupMode->setData(QVariant::fromValue<Digikam::GeoMouseModes>(MouseModeZoomIntoGroup));
321 
322     d->actionSetRegionSelectionFromIconMode = new QAction(d->mouseModeActionGroup);
323     d->actionSetRegionSelectionFromIconMode->setCheckable(true);
324     d->actionSetRegionSelectionFromIconMode->setToolTip(i18n("Create a region selection from a thumbnail"));
325     d->actionSetRegionSelectionFromIconMode->setIcon(QIcon::fromTheme( QLatin1String("edit-node") ));
326     d->actionSetRegionSelectionFromIconMode->setData(QVariant::fromValue<Digikam::GeoMouseModes>(MouseModeRegionSelectionFromIcon));
327 
328     d->actionSetFilterMode = new QAction(d->mouseModeActionGroup);
329     d->actionSetFilterMode->setCheckable(true);
330     d->actionSetFilterMode->setToolTip(i18n("Filter images"));
331     d->actionSetFilterMode->setIcon(QIcon::fromTheme( QLatin1String("view-filter") ));
332     d->actionSetFilterMode->setData(QVariant::fromValue<Digikam::GeoMouseModes>(MouseModeFilter));
333 
334     d->actionRemoveFilter = new QAction(this);
335     d->actionRemoveFilter->setToolTip(i18n("Remove the current filter"));
336     d->actionRemoveFilter->setIcon(QIcon::fromTheme( QLatin1String("window-close") ));
337 
338     d->actionSetSelectThumbnailMode = new QAction(d->mouseModeActionGroup);
339     d->actionSetSelectThumbnailMode->setCheckable(true);
340     d->actionSetSelectThumbnailMode->setToolTip(i18n("Select images"));
341     d->actionSetSelectThumbnailMode->setIcon(QIcon::fromTheme( QLatin1String("edit-select") ));
342     d->actionSetSelectThumbnailMode->setData(QVariant::fromValue<Digikam::GeoMouseModes>(MouseModeSelectThumbnail));
343 
344     d->actionStickyMode = new QAction(this);
345     d->actionStickyMode->setCheckable(true);
346     d->actionStickyMode->setToolTip(i18n("Lock the map position"));
347 
348     connect(d->actionStickyMode, &QAction::triggered,
349             this, &MapWidget::slotStickyModeChanged);
350 
351     connect(d->actionIncreaseThumbnailSize, &QAction::triggered,
352             this, &MapWidget::slotIncreaseThumbnailSize);
353 
354     connect(d->actionDecreaseThumbnailSize, &QAction::triggered,
355             this, &MapWidget::slotDecreaseThumbnailSize);
356 
357     connect(d->actionPreviewSingleItems, &QAction::changed,
358             this, &MapWidget::slotItemDisplaySettingsChanged);
359 
360     connect(d->actionPreviewGroupedItems, &QAction::changed,
361             this, &MapWidget::slotItemDisplaySettingsChanged);
362 
363     connect(d->actionShowNumbersOnItems, &QAction::changed,
364             this, &MapWidget::slotItemDisplaySettingsChanged);
365 
366     connect(d->mouseModeActionGroup, &QActionGroup::triggered,
367             this, &MapWidget::slotMouseModeChanged);
368 
369     connect(d->actionRemoveFilter, &QAction::triggered,
370             this, &MapWidget::signalRemoveCurrentFilter);
371 
372     connect(d->actionRemoveCurrentRegionSelection, &QAction::triggered,
373             this, &MapWidget::slotRemoveCurrentRegionSelection);
374 }
375 
createActionsForBackendSelection()376 void MapWidget::createActionsForBackendSelection()
377 {
378     // delete the existing actions:
379 
380     qDeleteAll(d->actionGroupBackendSelection->actions());
381 
382     // create actions for all backends:
383 
384     for (int i = 0 ; i < d->loadedBackends.size() ; ++i)
385     {
386         const QString backendName    = d->loadedBackends.at(i)->backendName();
387         QAction* const backendAction = new QAction(d->actionGroupBackendSelection);
388         backendAction->setData(backendName);
389         backendAction->setText(d->loadedBackends.at(i)->backendHumanName());
390         backendAction->setCheckable(true);
391     }
392 }
393 
~MapWidget()394 MapWidget::~MapWidget()
395 {
396     // release all widgets:
397 
398     for (int i = 0 ; i < d->stackedLayout->count() ; ++i)
399     {
400         d->stackedLayout->removeWidget(d->stackedLayout->widget(i));
401     }
402 
403     qDeleteAll(d->loadedBackends);
404     d->currentBackend = nullptr;
405     d->loadedBackends.clear();
406     delete d;
407 
408     /// @todo delete s, but make sure it is not accessed by any other objects any more!
409 }
410 
availableBackends() const411 QStringList MapWidget::availableBackends() const
412 {
413     QStringList result;
414 
415     foreach (MapBackend* const backend, d->loadedBackends)
416     {
417         result.append(backend->backendName());
418     }
419 
420     return result;
421 }
422 
setBackend(const QString & backendName)423 bool MapWidget::setBackend(const QString& backendName)
424 {
425     if (backendName == d->currentBackendName)
426     {
427         return true;
428     }
429 
430     saveBackendToCache();
431 
432     // switch to the placeholder widget:
433 
434     setShowPlaceholderWidget(true);
435     removeMapWidgetFromFrame();
436 
437     // disconnect signals from old backend:
438 
439     if (d->currentBackend)
440     {
441         d->currentBackend->setActive(false);
442 
443         disconnect(d->currentBackend, SIGNAL(signalBackendReadyChanged(QString)),
444                    this, SLOT(slotBackendReadyChanged(QString)));
445 
446         disconnect(d->currentBackend, SIGNAL(signalZoomChanged(QString)),
447                    this, SLOT(slotBackendZoomChanged(QString)));
448 
449         disconnect(d->currentBackend, SIGNAL(signalClustersMoved(QIntList,QPair<int,QModelIndex>)),
450                    this, SLOT(slotClustersMoved(QIntList,QPair<int,QModelIndex>)));
451 
452         disconnect(d->currentBackend, SIGNAL(signalClustersClicked(QIntList)),
453                    this, SLOT(slotClustersClicked(QIntList)));
454 
455         disconnect(this, SIGNAL(signalUngroupedModelChanged(int)),
456                    d->currentBackend, SLOT(slotUngroupedModelChanged(int)));
457 
458         if (s->markerModel)
459         {
460             disconnect(s->markerModel, SIGNAL(signalThumbnailAvailableForIndex(QVariant,QPixmap)),
461                        d->currentBackend, SLOT(slotThumbnailAvailableForIndex(QVariant,QPixmap)));
462         }
463 
464         disconnect(d->currentBackend, SIGNAL(signalSelectionHasBeenMade(Digikam::GeoCoordinates::Pair)),
465                    this, SLOT(slotNewSelectionFromMap(Digikam::GeoCoordinates::Pair)));
466 
467     }
468 
469     foreach (MapBackend* const backend, d->loadedBackends)
470     {
471         if (backend->backendName() == backendName)
472         {
473             qCDebug(DIGIKAM_GEOIFACE_LOG) << QString::fromLatin1("setting backend %1").arg(backendName);
474             d->currentBackend     = backend;
475             d->currentBackendName = backendName;
476 
477             connect(d->currentBackend, &MapBackend::signalBackendReadyChanged,
478                     this, &MapWidget::slotBackendReadyChanged);
479 
480             connect(d->currentBackend, &MapBackend::signalZoomChanged,
481                     this, &MapWidget::slotBackendZoomChanged);
482 
483             connect(d->currentBackend, &MapBackend::signalClustersMoved,
484                     this, &MapWidget::slotClustersMoved);
485 
486             connect(d->currentBackend, &MapBackend::signalClustersClicked,
487                     this, &MapWidget::slotClustersClicked);
488 
489             /**
490              * @todo This connection is queued because otherwise QAbstractItemModel::itemSelected does not
491              *       reflect the true state. Maybe monitor another signal instead?
492              */
493             connect(this, SIGNAL(signalUngroupedModelChanged(int)),
494                     d->currentBackend, SLOT(slotUngroupedModelChanged(int)), Qt::QueuedConnection);
495 
496             if (s->markerModel)
497             {
498                 connect(s->markerModel, SIGNAL(signalThumbnailAvailableForIndex(QVariant,QPixmap)),
499                         d->currentBackend, SLOT(slotThumbnailAvailableForIndex(QVariant,QPixmap)));
500             }
501 
502             connect(d->currentBackend, &MapBackend::signalSelectionHasBeenMade,
503                     this, &MapWidget::slotNewSelectionFromMap);
504 
505             if (s->activeState)
506             {
507                 setMapWidgetInFrame(d->currentBackend->mapWidget());
508 
509                 // call this slot manually in case the backend was ready right away:
510 
511                 if (d->currentBackend->isReady())
512                 {
513                     slotBackendReadyChanged(d->currentBackendName);
514                 }
515                 else
516                 {
517                     rebuildConfigurationMenu();
518                 }
519             }
520 
521             d->currentBackend->setActive(s->activeState);
522 
523             return true;
524         }
525     }
526 
527     return false;
528 }
529 
applyCacheToBackend()530 void MapWidget::applyCacheToBackend()
531 {
532     if ((!currentBackendReady()) || (!s->activeState))
533     {
534         return;
535     }
536 
537     /// @todo Only do this if the zoom was changed!
538 
539     qCDebug(DIGIKAM_GEOIFACE_LOG) << d->cacheZoom;
540 
541     setZoom(d->cacheZoom);
542     setCenter(d->cacheCenterCoordinate);
543     d->currentBackend->mouseModeChanged();
544     d->currentBackend->regionSelectionChanged();
545 }
546 
saveBackendToCache()547 void MapWidget::saveBackendToCache()
548 {
549     if (!currentBackendReady())
550     {
551         return;
552     }
553 
554     d->cacheCenterCoordinate = getCenter();
555     d->cacheZoom             = getZoom();
556 }
557 
getCenter() const558 GeoCoordinates MapWidget::getCenter() const
559 {
560     if (!currentBackendReady())
561     {
562         return d->cacheCenterCoordinate;
563     }
564 
565     return d->currentBackend->getCenter();
566 }
567 
setCenter(const GeoCoordinates & coordinate)568 void MapWidget::setCenter(const GeoCoordinates& coordinate)
569 {
570     d->cacheCenterCoordinate = coordinate;
571 
572     if (!currentBackendReady())
573     {
574         return;
575     }
576 
577     d->currentBackend->setCenter(coordinate);
578 }
579 
slotBackendReadyChanged(const QString & backendName)580 void MapWidget::slotBackendReadyChanged(const QString& backendName)
581 {
582     qCDebug(DIGIKAM_GEOIFACE_LOG) << QString::fromLatin1("backend %1 is ready!").arg(backendName);
583 
584     if (backendName != d->currentBackendName)
585     {
586         return;
587     }
588 
589     if (!currentBackendReady())
590     {
591         return;
592     }
593 
594     applyCacheToBackend();
595 
596     setShowPlaceholderWidget(false);
597 
598     if (!d->thumbnailsHaveBeenLoaded)
599     {
600         d->thumbnailTimer      = new QTimer(this);
601         d->thumbnailTimerCount = 0;
602 
603         connect(d->thumbnailTimer, &QTimer::timeout,
604                 this, &MapWidget::stopThumbnailTimer);
605 
606         d->thumbnailTimer->start(2000);
607     }
608 
609     updateMarkers();
610     markClustersAsDirty();
611     rebuildConfigurationMenu();
612 }
613 
stopThumbnailTimer()614 void MapWidget::stopThumbnailTimer()
615 {
616     d->currentBackend->updateMarkers();
617     d->thumbnailTimerCount++;
618 
619     if (d->thumbnailTimerCount == 10)
620     {
621         d->thumbnailTimer->stop();
622         d->thumbnailsHaveBeenLoaded = true;
623     }
624 }
625 
saveSettingsToGroup(KConfigGroup * const group)626 void MapWidget::saveSettingsToGroup(KConfigGroup* const group)
627 {
628     GEOIFACE_ASSERT(group != nullptr);
629 
630     if (!group)
631     {
632         return;
633     }
634 
635     if (!d->currentBackendName.isEmpty())
636     {
637         group->writeEntry("Backend", d->currentBackendName);
638     }
639 
640     group->writeEntry("Center",                    getCenter().geoUrl());
641     group->writeEntry("Zoom",                      getZoom());
642     group->writeEntry("Preview Single Items",      s->previewSingleItems);
643     group->writeEntry("Preview Grouped Items",     s->previewGroupedItems);
644     group->writeEntry("Show numbers on items",     s->showNumbersOnItems);
645     group->writeEntry("Thumbnail Size",            s->thumbnailSize);
646     group->writeEntry("Thumbnail Grouping Radius", s->thumbnailGroupingRadius);
647     group->writeEntry("Marker Grouping Radius",    s->markerGroupingRadius);
648     group->writeEntry("Show Thumbnails",           s->showThumbnails);
649     group->writeEntry("Mouse Mode",                int(s->currentMouseMode));
650 
651     if (d->visibleExtraActions.testFlag(ExtraActionSticky))
652     {
653         group->writeEntry("Sticky Mode State", d->actionStickyMode->isChecked());
654     }
655 
656     for (int i = 0 ; i < d->loadedBackends.size() ; ++i)
657     {
658         d->loadedBackends.at(i)->saveSettingsToGroup(group);
659     }
660 }
661 
readSettingsFromGroup(const KConfigGroup * const group)662 void MapWidget::readSettingsFromGroup(const KConfigGroup* const group)
663 {
664     GEOIFACE_ASSERT(group != nullptr);
665 
666     if (!group)
667     {
668         return;
669     }
670 
671     if (d->currentBackendName.isEmpty())
672     {
673         setBackend(group->readEntry("Backend", "marble"));
674     }
675 
676     // Options concerning the display of markers
677 
678     d->actionPreviewSingleItems->setChecked(group->readEntry("Preview Single Items",   true));
679     d->actionPreviewGroupedItems->setChecked(group->readEntry("Preview Grouped Items", true));
680     d->actionShowNumbersOnItems->setChecked(group->readEntry("Show numbers on items",  true));
681 
682     setThumnailSize(group->readEntry("Thumbnail Size",                       2*GeoIfaceMinThumbnailSize));
683     setThumbnailGroupingRadius(group->readEntry("Thumbnail Grouping Radius", 2*GeoIfaceMinThumbnailGroupingRadius));
684     setMarkerGroupingRadius(group->readEntry("Edit Grouping Radius",         GeoIfaceMinMarkerGroupingRadius));
685     s->showThumbnails = group->readEntry("Show Thumbnails",                  s->showThumbnails);
686     d->actionShowThumbnails->setChecked(s->showThumbnails);
687     d->actionStickyMode->setChecked(group->readEntry("Sticky Mode State",    d->actionStickyMode->isChecked()));
688 
689     // let the backends load their settings
690 
691     for (int i = 0 ; i < d->loadedBackends.size() ; ++i)
692     {
693         d->loadedBackends.at(i)->readSettingsFromGroup(group);
694     }
695 
696     // current map state
697 
698     const GeoCoordinates centerDefault    = GeoCoordinates(52.0, 6.0);
699     const QString centerGeoUrl            = group->readEntry("Center", centerDefault.geoUrl());
700     bool centerGeoUrlValid                = false;
701     const GeoCoordinates centerCoordinate = GeoCoordinates::fromGeoUrl(centerGeoUrl, &centerGeoUrlValid);
702     d->cacheCenterCoordinate              = centerGeoUrlValid ? centerCoordinate : centerDefault;
703     d->cacheZoom                          = group->readEntry("Zoom", d->cacheZoom);
704     s->currentMouseMode                   = GeoMouseModes(group->readEntry("Mouse Mode", int(s->currentMouseMode)));
705 
706     // propagate the loaded values to the map, if appropriate
707 
708     applyCacheToBackend();
709     slotUpdateActionsEnabled();
710 }
711 
rebuildConfigurationMenu()712 void MapWidget::rebuildConfigurationMenu()
713 {
714     d->configurationMenu->clear();
715     const QList<QAction*> backendSelectionActions = d->actionGroupBackendSelection->actions();
716 
717     for (int i = 0 ; i < backendSelectionActions.count() ; ++i)
718     {
719         QAction* const backendAction = backendSelectionActions.at(i);
720 
721         if (backendAction->data().toString() == d->currentBackendName)
722         {
723             backendAction->setChecked(true);
724         }
725 
726         d->configurationMenu->addAction(backendAction);
727     }
728 
729     if (currentBackendReady())
730     {
731         d->currentBackend->addActionsToConfigurationMenu(d->configurationMenu);
732     }
733 
734     if (s->showThumbnails)
735     {
736         d->configurationMenu->addSeparator();
737 
738         if (d->sortMenu)
739         {
740             d->configurationMenu->addMenu(d->sortMenu);
741         }
742 
743         d->configurationMenu->addAction(d->actionPreviewSingleItems);
744         d->configurationMenu->addAction(d->actionPreviewGroupedItems);
745         d->configurationMenu->addAction(d->actionShowNumbersOnItems);
746     }
747 
748     slotUpdateActionsEnabled();
749 }
750 
getControlAction(const QString & actionName)751 QAction* MapWidget::getControlAction(const QString& actionName)
752 {
753     if      (actionName == QLatin1String("zoomin"))
754     {
755         return d->actionZoomIn;
756     }
757     else if (actionName == QLatin1String("zoomout"))
758     {
759         return d->actionZoomOut;
760     }
761     else if (actionName == QLatin1String("mousemode-regionselectionmode"))
762     {
763         return d->actionSetRegionSelectionMode;
764     }
765     else if (actionName == QLatin1String("mousemode-removecurrentregionselection"))
766     {
767         return d->actionRemoveCurrentRegionSelection;
768     }
769     else if (actionName == QLatin1String("mousemode-regionselectionfromiconmode"))
770     {
771         return d->actionSetRegionSelectionFromIconMode;
772     }
773     else if (actionName == QLatin1String("mousemode-removefilter"))
774     {
775         return d->actionRemoveFilter;
776     }
777 
778     return nullptr;
779 }
780 
781 /**
782  * @brief Returns the control widget.
783  */
getControlWidget()784 QWidget* MapWidget::getControlWidget()
785 {
786     if (!d->controlWidget)
787     {
788         d->controlWidget                           = new QWidget(this);
789         QHBoxLayout* const controlWidgetHBoxLayout = new QHBoxLayout(d->controlWidget);
790         controlWidgetHBoxLayout->setContentsMargins(QMargins());
791 
792         QPushButton* const configurationButton = new QPushButton(d->controlWidget);
793         configurationButton->setIcon(QIcon::fromTheme(QLatin1String("globe")));
794         controlWidgetHBoxLayout->addWidget(configurationButton);
795         configurationButton->setToolTip(i18n("Map settings"));
796         configurationButton->setMenu(d->configurationMenu);
797 
798         QToolButton* const zoomInButton = new QToolButton(d->controlWidget);
799         controlWidgetHBoxLayout->addWidget(zoomInButton);
800         zoomInButton->setDefaultAction(d->actionZoomIn);
801 
802         QToolButton* const zoomOutButton = new QToolButton(d->controlWidget);
803         controlWidgetHBoxLayout->addWidget(zoomOutButton);
804         zoomOutButton->setDefaultAction(d->actionZoomOut);
805 
806         QToolButton* const showThumbnailsButton = new QToolButton(d->controlWidget);
807         controlWidgetHBoxLayout->addWidget(showThumbnailsButton);
808         showThumbnailsButton->setDefaultAction(d->actionShowThumbnails);
809 
810         QFrame* const vline1 = new QFrame(d->controlWidget);
811         vline1->setLineWidth(1);
812         vline1->setMidLineWidth(0);
813         vline1->setFrameShape(QFrame::VLine);
814         vline1->setFrameShadow(QFrame::Sunken);
815         vline1->setMinimumSize(2, 0);
816         vline1->updateGeometry();
817         controlWidgetHBoxLayout->addWidget(vline1);
818 
819         QToolButton* const increaseThumbnailSizeButton = new QToolButton(d->controlWidget);
820         controlWidgetHBoxLayout->addWidget(increaseThumbnailSizeButton);
821         increaseThumbnailSizeButton->setDefaultAction(d->actionIncreaseThumbnailSize);
822 
823         QToolButton* const decreaseThumbnailSizeButton = new QToolButton(d->controlWidget);
824         controlWidgetHBoxLayout->addWidget(decreaseThumbnailSizeButton);
825         decreaseThumbnailSizeButton->setDefaultAction(d->actionDecreaseThumbnailSize);
826 
827         /* --- --- --- */
828 
829         d->mouseModesHolder                           = new QWidget(d->controlWidget);
830         QHBoxLayout* const mouseModesHolderHBoxLayout = new QHBoxLayout(d->mouseModesHolder);
831         mouseModesHolderHBoxLayout->setContentsMargins(QMargins());
832         controlWidgetHBoxLayout->addWidget(d->mouseModesHolder);
833 
834         QFrame* const vline2 = new QFrame(d->mouseModesHolder);
835         vline2->setLineWidth(1);
836         vline2->setMidLineWidth(0);
837         vline2->setFrameShape(QFrame::VLine);
838         vline2->setFrameShadow(QFrame::Sunken);
839         vline2->setMinimumSize(2, 0);
840         vline2->updateGeometry();
841         mouseModesHolderHBoxLayout->addWidget(vline2);
842 
843         d->setPanModeButton = new QToolButton(d->mouseModesHolder);
844         mouseModesHolderHBoxLayout->addWidget(d->setPanModeButton);
845         d->setPanModeButton->setDefaultAction(d->actionSetPanMode);
846 
847         d->setSelectionModeButton = new QToolButton(d->mouseModesHolder);
848         mouseModesHolderHBoxLayout->addWidget(d->setSelectionModeButton);
849         d->setSelectionModeButton->setDefaultAction(d->actionSetRegionSelectionMode);
850 
851         d->setRegionSelectionFromIconModeButton = new QToolButton(d->mouseModesHolder);
852         mouseModesHolderHBoxLayout->addWidget(d->setRegionSelectionFromIconModeButton);
853         d->setRegionSelectionFromIconModeButton->setDefaultAction(d->actionSetRegionSelectionFromIconMode);
854 
855         d->removeCurrentSelectionButton = new QToolButton(d->mouseModesHolder);
856         mouseModesHolderHBoxLayout->addWidget(d->removeCurrentSelectionButton);
857         d->removeCurrentSelectionButton->setDefaultAction(d->actionRemoveCurrentRegionSelection);
858 
859         d->setZoomModeButton = new QToolButton(d->mouseModesHolder);
860         mouseModesHolderHBoxLayout->addWidget(d->setZoomModeButton);
861         d->setZoomModeButton->setDefaultAction(d->actionSetZoomIntoGroupMode);
862 
863         d->setFilterModeButton = new QToolButton(d->mouseModesHolder);
864         mouseModesHolderHBoxLayout->addWidget(d->setFilterModeButton);
865         d->setFilterModeButton->setDefaultAction(d->actionSetFilterMode);
866 
867         d->removeFilterModeButton = new QToolButton(d->mouseModesHolder);
868         mouseModesHolderHBoxLayout->addWidget(d->removeFilterModeButton);
869         d->removeFilterModeButton->setDefaultAction(d->actionRemoveFilter);
870 
871         d->setSelectThumbnailMode = new QToolButton(d->mouseModesHolder);
872         mouseModesHolderHBoxLayout->addWidget(d->setSelectThumbnailMode);
873         d->setSelectThumbnailMode->setDefaultAction(d->actionSetSelectThumbnailMode);
874 
875         d->buttonStickyMode = new QToolButton(d->controlWidget);
876         controlWidgetHBoxLayout->addWidget(d->buttonStickyMode);
877         d->buttonStickyMode->setDefaultAction(d->actionStickyMode);
878 
879         d->hBoxForAdditionalControlWidgetItems = new QWidget(d->controlWidget);
880         QHBoxLayout *hBoxForAdditionalControlWidgetItemsHBoxLayout = new QHBoxLayout(d->hBoxForAdditionalControlWidgetItems);
881         hBoxForAdditionalControlWidgetItemsHBoxLayout->setContentsMargins(QMargins());
882         controlWidgetHBoxLayout->addWidget(d->hBoxForAdditionalControlWidgetItems);
883 
884         setVisibleMouseModes(s->visibleMouseModes);
885         setVisibleExtraActions(d->visibleExtraActions);
886 
887         // add stretch after the controls:
888 
889         QHBoxLayout* const hBoxLayout = reinterpret_cast<QHBoxLayout*>(d->controlWidget->layout());
890 
891         if (hBoxLayout)
892         {
893             hBoxLayout->addStretch();
894         }
895     }
896 
897     // make sure the menu exists, even if no backend has been set:
898 
899     rebuildConfigurationMenu();
900 
901     return d->controlWidget;
902 }
903 
slotZoomIn()904 void MapWidget::slotZoomIn()
905 {
906     if (!currentBackendReady())
907     {
908         return;
909     }
910 
911     d->currentBackend->zoomIn();
912 }
913 
slotZoomOut()914 void MapWidget::slotZoomOut()
915 {
916     if (!currentBackendReady())
917     {
918         return;
919     }
920 
921     d->currentBackend->zoomOut();
922 }
923 
slotUpdateActionsEnabled()924 void MapWidget::slotUpdateActionsEnabled()
925 {
926     if (!s->activeState)
927     {
928         // this widget is not active, no need to update the action availability
929         return;
930     }
931 
932     d->actionDecreaseThumbnailSize->setEnabled((s->showThumbnails)&&(s->thumbnailSize>GeoIfaceMinThumbnailSize));
933 
934     /// @todo Define an upper limit for the thumbnail size!
935 
936     d->actionIncreaseThumbnailSize->setEnabled(s->showThumbnails);
937 
938     d->actionSetRegionSelectionMode->setEnabled(s->availableMouseModes.testFlag(MouseModeRegionSelection));
939 
940     d->actionSetPanMode->setEnabled(s->availableMouseModes.testFlag(MouseModePan));
941     d->actionSetZoomIntoGroupMode->setEnabled(s->availableMouseModes.testFlag(MouseModeZoomIntoGroup));
942     d->actionSetRegionSelectionFromIconMode->setEnabled(s->availableMouseModes.testFlag(MouseModeRegionSelectionFromIcon));
943     d->actionSetFilterMode->setEnabled(s->availableMouseModes.testFlag(MouseModeFilter));
944     d->actionSetSelectThumbnailMode->setEnabled(s->availableMouseModes.testFlag(MouseModeSelectThumbnail));
945 
946     // the 'Remove X' actions are only available if the corresponding X is actually there:
947 
948     bool clearRegionSelectionAvailable = s->availableMouseModes.testFlag(MouseModeRegionSelection);
949 
950     if (clearRegionSelectionAvailable && s->markerModel)
951     {
952         clearRegionSelectionAvailable = s->markerModel->getGlobalGroupState() & RegionSelectedMask;
953     }
954 
955     d->actionRemoveCurrentRegionSelection->setEnabled(clearRegionSelectionAvailable);
956     bool clearFilterAvailable = s->availableMouseModes.testFlag(MouseModeRegionSelectionFromIcon);
957 
958     if (clearFilterAvailable && s->markerModel)
959     {
960         clearFilterAvailable = s->markerModel->getGlobalGroupState() & FilteredPositiveMask;
961     }
962 
963     d->actionRemoveFilter->setEnabled(clearFilterAvailable);
964 
965     d->actionStickyMode->setEnabled(d->availableExtraActions.testFlag(ExtraActionSticky));
966 
967     /// @todo Only set the icons if they have to be changed!
968 
969     d->actionStickyMode->setIcon(QIcon::fromTheme(QLatin1String(d->actionStickyMode->isChecked()
970                                                                 ? "document-encrypted"
971                                                                 : "document-decrypt")));
972     d->actionShowThumbnails->setIcon(d->actionShowThumbnails->isChecked()
973                                      ? QIcon::fromTheme(QLatin1String("folder-pictures"))
974                                      : GeoIfaceGlobalObject::instance()->getMarkerPixmap(QLatin1String("marker-icon-16x16")));
975 
976     // make sure the action for the current mouse mode is checked
977 
978     const QList<QAction*> mouseModeActions = d->mouseModeActionGroup->actions();
979 
980     foreach (QAction* const action, mouseModeActions)
981     {
982         if (action->data().value<GeoMouseModes>() == s->currentMouseMode)
983         {
984             action->setChecked(true);
985             break;
986         }
987     }
988 }
989 
slotChangeBackend(QAction * action)990 void MapWidget::slotChangeBackend(QAction* action)
991 {
992     GEOIFACE_ASSERT(action != nullptr);
993 
994     if (!action)
995     {
996         return;
997     }
998 
999     const QString newBackendName = action->data().toString();
1000     setBackend(newBackendName);
1001 }
1002 
updateMarkers()1003 void MapWidget::updateMarkers()
1004 {
1005     if (!currentBackendReady())
1006     {
1007         return;
1008     }
1009 
1010     // tell the backend to update the markers
1011 
1012     d->currentBackend->updateMarkers();
1013 }
1014 
updateClusters()1015 void MapWidget::updateClusters()
1016 {
1017     /// @todo Find a better way to tell the TileGrouper about the backend
1018 
1019     s->tileGrouper->setCurrentBackend(d->currentBackend);
1020     s->tileGrouper->updateClusters();
1021 }
1022 
slotClustersNeedUpdating()1023 void MapWidget::slotClustersNeedUpdating()
1024 {
1025     if (currentBackendReady())
1026     {
1027         d->currentBackend->slotClustersNeedUpdating();
1028     }
1029 }
1030 
1031 /**
1032  * @brief Return color and style information for rendering the cluster
1033  * @param clusterIndex Index of the cluster
1034  * @param fillColor Color used to fill the circle
1035  * @param strokeColor Color used for the stroke around the circle
1036  * @param strokeStyle Style used to draw the stroke around the circle
1037  * @param labelText Text for the label
1038  * @param labelColor Color for the label text
1039  * @param overrideSelection Get the colors for a different selection state
1040  * @param overrideCount Get the colors for a different amount of markers
1041  */
getColorInfos(const int clusterIndex,QColor * fillColor,QColor * strokeColor,Qt::PenStyle * strokeStyle,QString * labelText,QColor * labelColor,const GeoGroupState * const overrideSelection,const int * const overrideCount) const1042 void MapWidget::getColorInfos(const int clusterIndex,
1043                               QColor* fillColor,
1044                               QColor* strokeColor,
1045                               Qt::PenStyle* strokeStyle,
1046                               QString* labelText,
1047                               QColor* labelColor,
1048                               const GeoGroupState* const overrideSelection,
1049                               const int* const overrideCount) const
1050 {
1051     /// @todo Call the new getColorInfos function!
1052 
1053     const GeoIfaceCluster& cluster = s->clusterList.at(clusterIndex);
1054 
1055     /// @todo Check that this number is already valid!
1056 
1057     const int nMarkers             = overrideCount ? *overrideCount : cluster.markerCount;
1058 
1059     getColorInfos(overrideSelection ? *overrideSelection : cluster.groupState,
1060                   nMarkers,
1061                   fillColor, strokeColor, strokeStyle, labelText, labelColor);
1062 }
1063 
getColorInfos(const GeoGroupState groupState,const int nMarkers,QColor * fillColor,QColor * strokeColor,Qt::PenStyle * strokeStyle,QString * labelText,QColor * labelColor) const1064 void MapWidget::getColorInfos(const GeoGroupState groupState,
1065                               const int nMarkers,
1066                               QColor* fillColor, QColor* strokeColor,
1067                               Qt::PenStyle* strokeStyle, QString* labelText,
1068                               QColor* labelColor) const
1069 {
1070     if      (nMarkers < 1000)
1071     {
1072         *labelText = QString::number(nMarkers);
1073     }
1074     else if ((nMarkers >= 1000) && (nMarkers <= 1950))      // cppcheck-suppress knownConditionTrueFalse
1075     {
1076         *labelText = QString::fromLatin1("%L1k").arg(qreal(nMarkers)/1000.0, 0, 'f', 1);
1077     }
1078     else if ((nMarkers >= 1951) && (nMarkers < 19500))      // cppcheck-suppress knownConditionTrueFalse
1079     {
1080         *labelText = QString::fromLatin1("%L1k").arg(qreal(nMarkers)/1000.0, 0, 'f', 0);
1081     }
1082     else
1083     {
1084         // convert to "1E5" notation for numbers >=20k:
1085 
1086         qreal exponent           = floor(log((qreal)nMarkers)/log((qreal)10));
1087         qreal nMarkersFirstDigit = round(qreal(nMarkers)/pow(10,exponent));
1088 
1089         if (nMarkersFirstDigit >= 10)
1090         {
1091             nMarkersFirstDigit=round(nMarkersFirstDigit/10.0);
1092             exponent++;
1093         }
1094 
1095         *labelText = QString::fromLatin1("%1E%2").arg(int(nMarkersFirstDigit)).arg(int(exponent));
1096     }
1097 
1098     *labelColor  = QColor(Qt::black);
1099     *strokeStyle = Qt::NoPen;
1100 
1101     /// @todo On my system, digikam uses QColor(67, 172, 232) as the selection color. Or should we just use blue?
1102 
1103     switch (groupState & SelectedMask)
1104     {
1105         case SelectedNone:
1106             *strokeStyle = Qt::SolidLine;
1107             *strokeColor = QColor(Qt::black);
1108             break;
1109 
1110         case SelectedSome:
1111             *strokeStyle = Qt::DotLine;
1112             *strokeColor = QColor(Qt::blue);//67, 172, 232);
1113             break;
1114 
1115         case SelectedAll:
1116             *strokeStyle = Qt::SolidLine;
1117             *strokeColor = QColor(Qt::blue);//67, 172, 232);
1118             break;
1119     }
1120 
1121     /**
1122      * @todo These are the fill colors for the circles, for cases in which only some or all of the images
1123      * are positively filtered. Filtering is implemented in GeoIface, but the code here has not been adapted yet.
1124      */
1125     QColor fillAll, fillSome, fillNone;
1126 
1127     if      (nMarkers >= 100)
1128     {
1129         fillAll  = QColor(255, 0,   0);
1130         fillSome = QColor(255, 188, 125);
1131         fillNone = QColor(255, 185, 185);
1132     }
1133     else if (nMarkers >= 50)
1134     {
1135         fillAll  = QColor(255, 127, 0);
1136         fillSome = QColor(255, 190, 125);
1137         fillNone = QColor(255, 220, 185);
1138     }
1139     else if (nMarkers >= 10)
1140     {
1141         fillAll  = QColor(255, 255, 0);
1142         fillSome = QColor(255, 255, 105);
1143         fillNone = QColor(255, 255, 185);
1144     }
1145     else if (nMarkers >= 2)
1146     {
1147         fillAll  = QColor(0,   255, 0);
1148         fillSome = QColor(125, 255, 125);
1149         fillNone = QColor(185, 255, 255);
1150     }
1151     else
1152     {
1153         fillAll  = QColor(0,   255, 255);
1154         fillSome = QColor(125, 255, 255);
1155         fillNone = QColor(185, 255, 255);
1156     }
1157 
1158     *fillColor = fillAll;
1159 /*
1160     switch (groupState)
1161     {
1162         case PartialAll:
1163             *fillColor = fillAll;
1164             break;
1165 
1166         case PartialSome:
1167             *fillColor = fillSome;
1168             break;
1169 
1170         case PartialNone:
1171 
1172             if (haveAnySolo)
1173             {
1174                 *fillColor = fillNone;
1175             }
1176             else
1177             {
1178                 *fillColor = fillAll;
1179             }
1180 
1181             break;
1182     }
1183 */
1184 }
1185 
convertZoomToBackendZoom(const QString & someZoom,const QString & targetBackend) const1186 QString MapWidget::convertZoomToBackendZoom(const QString& someZoom,
1187                                             const QString& targetBackend) const
1188 {
1189     const QStringList zoomParts = someZoom.split(QLatin1Char( ':' ));
1190     GEOIFACE_ASSERT(zoomParts.count() == 2);
1191     const QString sourceBackend = zoomParts.first();
1192 
1193     if (sourceBackend == targetBackend)
1194     {
1195         return someZoom;
1196     }
1197 
1198     const int sourceZoom = zoomParts.last().toInt();
1199     int targetZoom       = -1;
1200 
1201     // all of these values were found experimentally!
1202 
1203     if (targetBackend == QLatin1String("marble" ))
1204     {
1205         if      (sourceZoom == 0) { targetZoom =  900; }
1206         else if (sourceZoom == 1) { targetZoom =  970; }
1207         else if (sourceZoom == 2) { targetZoom = 1108; }
1208         else if (sourceZoom == 3) { targetZoom = 1250; }
1209         else if (sourceZoom == 4) { targetZoom = 1384; }
1210         else if (sourceZoom == 5) { targetZoom = 1520; }
1211         else if (sourceZoom == 6) { targetZoom = 1665; }
1212         else if (sourceZoom == 7) { targetZoom = 1800; }
1213         else if (sourceZoom == 8) { targetZoom = 1940; }
1214         else if (sourceZoom == 9) { targetZoom = 2070; }
1215         else if (sourceZoom ==10) { targetZoom = 2220; }
1216         else if (sourceZoom ==11) { targetZoom = 2357; }
1217         else if (sourceZoom ==12) { targetZoom = 2510; }
1218         else if (sourceZoom ==13) { targetZoom = 2635; }
1219         else if (sourceZoom ==14) { targetZoom = 2775; }
1220         else if (sourceZoom ==15) { targetZoom = 2900; }
1221         else if (sourceZoom ==16) { targetZoom = 3051; }
1222         else if (sourceZoom ==17) { targetZoom = 3180; }
1223         else if (sourceZoom ==18) { targetZoom = 3295; }
1224         else if (sourceZoom ==19) { targetZoom = 3450; }
1225         else                      { targetZoom = 3500; } /// @todo Find values for level 20 and up
1226     }
1227 
1228     if (targetBackend == QLatin1String("googlemaps" ))
1229     {
1230         if      (sourceZoom <= 900) { targetZoom =  0; }
1231         else if (sourceZoom <= 970) { targetZoom =  1; }
1232         else if (sourceZoom <=1108) { targetZoom =  2; }
1233         else if (sourceZoom <=1250) { targetZoom =  3; }
1234         else if (sourceZoom <=1384) { targetZoom =  4; }
1235         else if (sourceZoom <=1520) { targetZoom =  5; }
1236         else if (sourceZoom <=1665) { targetZoom =  6; }
1237         else if (sourceZoom <=1800) { targetZoom =  7; }
1238         else if (sourceZoom <=1940) { targetZoom =  8; }
1239         else if (sourceZoom <=2070) { targetZoom =  9; }
1240         else if (sourceZoom <=2220) { targetZoom = 10; }
1241         else if (sourceZoom <=2357) { targetZoom = 11; }
1242         else if (sourceZoom <=2510) { targetZoom = 12; }
1243         else if (sourceZoom <=2635) { targetZoom = 13; }
1244         else if (sourceZoom <=2775) { targetZoom = 14; }
1245         else if (sourceZoom <=2900) { targetZoom = 15; }
1246         else if (sourceZoom <=3051) { targetZoom = 16; }
1247         else if (sourceZoom <=3180) { targetZoom = 17; }
1248         else if (sourceZoom <=3295) { targetZoom = 18; }
1249         else if (sourceZoom <=3450) { targetZoom = 19; }
1250         else                        { targetZoom = 20; } /// @todo Find values for level 20 and up
1251     }
1252 
1253     GEOIFACE_ASSERT(targetZoom >= 0);
1254 
1255     return QString::fromLatin1("%1:%2").arg(targetBackend).arg(targetZoom);
1256 }
1257 
slotBackendZoomChanged(const QString & newZoom)1258 void MapWidget::slotBackendZoomChanged(const QString& newZoom)
1259 {
1260     d->cacheZoom = newZoom;
1261 }
1262 
setZoom(const QString & newZoom)1263 void MapWidget::setZoom(const QString& newZoom)
1264 {
1265     d->cacheZoom = newZoom;
1266 
1267     if (currentBackendReady())
1268     {
1269         d->currentBackend->setZoom(d->cacheZoom);
1270     }
1271 }
1272 
getZoom()1273 QString MapWidget::getZoom()
1274 {
1275     if (currentBackendReady())
1276     {
1277         d->cacheZoom = d->currentBackend->getZoom();
1278     }
1279 
1280     return d->cacheZoom;
1281 }
1282 
getRegionSelection()1283 GeoCoordinates::Pair MapWidget::getRegionSelection()
1284 {
1285     return s->selectionRectangle;
1286 }
1287 
slotClustersMoved(const QIntList & clusterIndices,const QPair<int,QModelIndex> & snapTarget)1288 void MapWidget::slotClustersMoved(const QIntList& clusterIndices,
1289                                   const QPair<int, QModelIndex>& snapTarget)
1290 {
1291     qCDebug(DIGIKAM_GEOIFACE_LOG) << clusterIndices;
1292 
1293     /// @todo We actually expect only one clusterindex
1294 
1295     int             clusterIndex      = clusterIndices.first();
1296     GeoCoordinates  targetCoordinates = s->clusterList.at(clusterIndex).coordinates;
1297     TileIndex::List movedTileIndices;
1298 
1299     if (s->clusterList.at(clusterIndex).groupState == SelectedNone)
1300     {
1301         // a not-selected marker was moved. update all of its items:
1302 
1303         const GeoIfaceCluster& cluster = s->clusterList.at(clusterIndex);
1304 
1305         for (int i = 0 ; i < cluster.tileIndicesList.count() ; ++i)
1306         {
1307             const TileIndex tileIndex = cluster.tileIndicesList.at(i);
1308             movedTileIndices << tileIndex;
1309         }
1310     }
1311     else
1312     {
1313         // selected items were moved. The model helper should know which tiles are selected,
1314         // therefore we give him an empty list
1315     }
1316 
1317     s->markerModel->onIndicesMoved(movedTileIndices, targetCoordinates, snapTarget.second);
1318 
1319     /**
1320      * @todo Clusters are marked as dirty by slotClustersNeedUpdating
1321      * which is called while we update the model
1322      */
1323 }
1324 
addUngroupedModel(GeoModelHelper * const modelHelper)1325 void MapWidget::addUngroupedModel(GeoModelHelper* const modelHelper)
1326 {
1327     s->ungroupedModels << modelHelper;
1328 
1329     /// @todo monitor all model signals!
1330 
1331     connect(modelHelper->model(), SIGNAL(dataChanged(QModelIndex,QModelIndex)),
1332             this, SLOT(slotUngroupedModelChanged()));
1333 
1334     connect(modelHelper->model(), SIGNAL(rowsInserted(QModelIndex,int,int)),
1335             this, SLOT(slotUngroupedModelChanged()));
1336 
1337     connect(modelHelper->model(), SIGNAL(modelReset()),
1338             this, SLOT(slotUngroupedModelChanged()));
1339 
1340     connect(modelHelper, SIGNAL(signalVisibilityChanged()),
1341             this, SLOT(slotUngroupedModelChanged()));
1342 
1343     if (modelHelper->selectionModel())
1344     {
1345         connect(modelHelper->selectionModel(), SIGNAL(currentChanged(QModelIndex,QModelIndex)),
1346                 this, SLOT(slotUngroupedModelChanged()));
1347     }
1348 
1349     emit signalUngroupedModelChanged(s->ungroupedModels.count() - 1);
1350 }
1351 
removeUngroupedModel(GeoModelHelper * const modelHelper)1352 void MapWidget::removeUngroupedModel(GeoModelHelper* const modelHelper)
1353 {
1354     if (!modelHelper)
1355     {
1356         return;
1357     }
1358 
1359     const int modelIndex = s->ungroupedModels.indexOf(modelHelper);
1360 
1361     if (modelIndex < 0)
1362     {
1363         return;
1364     }
1365 
1366     /// @todo monitor all model signals!
1367 
1368     disconnect(modelHelper->model(), SIGNAL(dataChanged(QModelIndex,QModelIndex)),
1369                this, SLOT(slotUngroupedModelChanged()));
1370 
1371     disconnect(modelHelper->model(), SIGNAL(rowsInserted(QModelIndex,int,int)),
1372                this, SLOT(slotUngroupedModelChanged()));
1373 
1374     disconnect(modelHelper->model(), SIGNAL(modelReset()),
1375                this, SLOT(slotUngroupedModelChanged()));
1376 
1377     disconnect(modelHelper, SIGNAL(signalVisibilityChanged()),
1378                this, SLOT(slotUngroupedModelChanged()));
1379 
1380     if (modelHelper->selectionModel())
1381     {
1382         disconnect(modelHelper->selectionModel(), SIGNAL(currentChanged(QModelIndex,QModelIndex)),
1383                    this, SLOT(slotUngroupedModelChanged()));
1384     }
1385 
1386     s->ungroupedModels.removeAt(modelIndex);
1387 
1388     // the indices changed, therefore send out notifications
1389     // sending out a signal with i=s->ungroupedModel.count()
1390     // will cause the backends to see that the last model is missing
1391 
1392     for (int i = modelIndex ; i <= s->ungroupedModels.count() ; ++i)
1393     {
1394         emit signalUngroupedModelChanged(i);
1395     }
1396 }
1397 
setGroupedModel(AbstractMarkerTiler * const markerModel)1398 void MapWidget::setGroupedModel(AbstractMarkerTiler* const markerModel)
1399 {
1400     s->markerModel = markerModel;
1401 
1402     if (s->markerModel)
1403     {
1404         s->markerModel->setActive(s->activeState);
1405 
1406         /// @todo this needs some buffering for the google maps backend
1407 
1408         connect(s->markerModel, SIGNAL(signalTilesOrSelectionChanged()),
1409                 this, SLOT(slotRequestLazyReclustering()));
1410 
1411         if (d->currentBackend)
1412         {
1413             connect(s->markerModel, SIGNAL(signalThumbnailAvailableForIndex(QVariant,QPixmap)),
1414                     d->currentBackend, SLOT(slotThumbnailAvailableForIndex(QVariant,QPixmap)));
1415         }
1416     }
1417 
1418     slotRequestLazyReclustering();
1419 }
1420 
setShowThumbnails(const bool state)1421 void MapWidget::setShowThumbnails(const bool state)
1422 {
1423     s->showThumbnails = state;
1424 
1425     rebuildConfigurationMenu();
1426     slotUpdateActionsEnabled();
1427     slotRequestLazyReclustering();
1428 }
1429 
slotShowThumbnailsChanged()1430 void MapWidget::slotShowThumbnailsChanged()
1431 {
1432     setShowThumbnails(d->actionShowThumbnails->isChecked());
1433 }
1434 
1435 /**
1436  * @brief Request reclustering, repeated calls should generate only one actual update of the clusters
1437  */
slotRequestLazyReclustering()1438 void MapWidget::slotRequestLazyReclustering()
1439 {
1440     if (d->lazyReclusteringRequested)
1441     {
1442         return;
1443     }
1444 
1445     s->tileGrouper->setClustersDirty();
1446 
1447     if (s->activeState)
1448     {
1449         d->lazyReclusteringRequested = true;
1450         QTimer::singleShot(0, this, SLOT(slotLazyReclusteringRequestCallBack()));
1451     }
1452 }
1453 
1454 /**
1455  * @brief Helper function to buffer reclustering
1456  */
slotLazyReclusteringRequestCallBack()1457 void MapWidget::slotLazyReclusteringRequestCallBack()
1458 {
1459     if (!d->lazyReclusteringRequested)
1460     {
1461         return;
1462     }
1463 
1464     d->lazyReclusteringRequested = false;
1465     slotClustersNeedUpdating();
1466 }
1467 
1468 /**
1469  * @todo Clicking on several clusters at once is not actually possible
1470  */
slotClustersClicked(const QIntList & clusterIndices)1471 void MapWidget::slotClustersClicked(const QIntList& clusterIndices)
1472 {
1473     qCDebug(DIGIKAM_GEOIFACE_LOG) << clusterIndices;
1474 
1475     if ((s->currentMouseMode == MouseModeZoomIntoGroup) ||
1476         (s->currentMouseMode == MouseModeRegionSelectionFromIcon) )
1477     {
1478         int maxTileLevel = 0;
1479 
1480         Marble::GeoDataLineString tileString;
1481 
1482         for (int i = 0 ; i < clusterIndices.count() ; ++i)
1483         {
1484             const int clusterIndex               = clusterIndices.at(i);
1485             const GeoIfaceCluster currentCluster = s->clusterList.at(clusterIndex);
1486 
1487             for (int j = 0 ; j < currentCluster.tileIndicesList.count() ; ++j)
1488             {
1489                 const TileIndex& currentTileIndex = currentCluster.tileIndicesList.at(j);
1490 
1491                 for (int corner = 1 ; corner <= 4 ; ++corner)
1492                 {
1493                     GeoCoordinates currentTileCoordinate;
1494 
1495                     if      (corner == 1)
1496                     {
1497                         currentTileCoordinate = currentTileIndex.toCoordinates(TileIndex::CornerNW);
1498                     }
1499                     else if (corner == 2)
1500                     {
1501                         currentTileCoordinate = currentTileIndex.toCoordinates(TileIndex::CornerSW);
1502                     }
1503                     else if (corner == 3)
1504                     {
1505                         currentTileCoordinate = currentTileIndex.toCoordinates(TileIndex::CornerNE);
1506                     }
1507                     else if (corner == 4)
1508                     {
1509                         currentTileCoordinate = currentTileIndex.toCoordinates(TileIndex::CornerSE);
1510                     }
1511 
1512                     const Marble::GeoDataCoordinates tileCoordinate(currentTileCoordinate.lon(),
1513                                                                     currentTileCoordinate.lat(),
1514                                                                     0,
1515                                                                     Marble::GeoDataCoordinates::Degree);
1516 
1517                     if (maxTileLevel < currentTileIndex.level())
1518                     {
1519                         maxTileLevel = currentTileIndex.level();
1520                     }
1521 
1522                     tileString.append(tileCoordinate);
1523                 }
1524             }
1525         }
1526 
1527         Marble::GeoDataLatLonBox latLonBox = Marble::GeoDataLatLonBox::fromLineString(tileString);
1528 
1529         /// @todo Review this section
1530 /*
1531         if (maxTileLevel != 0)
1532         {
1533             //increase the selection boundaries with 0.1 degrees because some thumbnails aren't caught by selection
1534             latLonBox.setWest((latLonBox.west(Marble::GeoDataCoordinates::Degree)-(0.1/maxTileLevel)), Marble::GeoDataCoordinates::Degree);
1535             latLonBox.setNorth((latLonBox.north(Marble::GeoDataCoordinates::Degree)+(0.1/maxTileLevel)), Marble::GeoDataCoordinates::Degree);
1536             latLonBox.setEast((latLonBox.east(Marble::GeoDataCoordinates::Degree)+(0.1/maxTileLevel)), Marble::GeoDataCoordinates::Degree);
1537             latLonBox.setSouth((latLonBox.south(Marble::GeoDataCoordinates::Degree)-(0.1/maxTileLevel)), Marble::GeoDataCoordinates::Degree);
1538         }
1539         else
1540         {
1541 */
1542             latLonBox.setWest((latLonBox.west(Marble::GeoDataCoordinates::Degree)-0.0001), Marble::GeoDataCoordinates::Degree);
1543             latLonBox.setNorth((latLonBox.north(Marble::GeoDataCoordinates::Degree)+0.0001), Marble::GeoDataCoordinates::Degree);
1544             latLonBox.setEast((latLonBox.east(Marble::GeoDataCoordinates::Degree)+0.0001), Marble::GeoDataCoordinates::Degree);
1545             latLonBox.setSouth((latLonBox.south(Marble::GeoDataCoordinates::Degree)-0.0001), Marble::GeoDataCoordinates::Degree);
1546 /*
1547         }
1548 */
1549         if (s->currentMouseMode == MouseModeZoomIntoGroup)
1550         {
1551             /// @todo Very small latLonBoxes can crash Marble
1552 
1553             d->currentBackend->centerOn(latLonBox);
1554         }
1555         else
1556         {
1557             const GeoCoordinates::Pair newSelection(
1558                     GeoCoordinates(latLonBox.north(Marble::GeoDataCoordinates::Degree),
1559                                 latLonBox.west(Marble::GeoDataCoordinates::Degree)),
1560                     GeoCoordinates(latLonBox.south(Marble::GeoDataCoordinates::Degree),
1561                                 latLonBox.east(Marble::GeoDataCoordinates::Degree))
1562                 );
1563 
1564             s->selectionRectangle = newSelection;
1565             d->currentBackend->regionSelectionChanged();
1566             emit signalRegionSelectionChanged();
1567         }
1568     }
1569     else if ((s->currentMouseMode == MouseModeFilter && s->selectionRectangle.first.hasCoordinates()) ||
1570              (s->currentMouseMode == MouseModeSelectThumbnail) )
1571     {
1572         // update the selection and filtering state of the clusters
1573 
1574         for (int i = 0 ; i < clusterIndices.count() ; ++i)
1575         {
1576             const int clusterIndex               = clusterIndices.at(i);
1577             const GeoIfaceCluster currentCluster = s->clusterList.at(clusterIndex);
1578             const TileIndex::List tileIndices    = currentCluster.tileIndicesList;
1579 
1580             /// @todo Isn't this cached in the cluster?
1581 
1582             const QVariant representativeIndex   = getClusterRepresentativeMarker(clusterIndex, s->sortKey);
1583 
1584             AbstractMarkerTiler::ClickInfo clickInfo;
1585             clickInfo.tileIndicesList            = tileIndices;
1586             clickInfo.representativeIndex        = representativeIndex;
1587             clickInfo.groupSelectionState        = currentCluster.groupState;
1588             clickInfo.currentMouseMode           = s->currentMouseMode;
1589             s->markerModel->onIndicesClicked(clickInfo);
1590         }
1591     }
1592 }
1593 
dragEnterEvent(QDragEnterEvent * event)1594 void MapWidget::dragEnterEvent(QDragEnterEvent* event)
1595 {
1596     /// @todo ignore drops if no marker tiler or model can accept them
1597 
1598     if (!d->dragDropHandler)
1599     {
1600         event->ignore();
1601         return;
1602     }
1603 
1604     if (d->dragDropHandler->accepts(event) == Qt::IgnoreAction)
1605     {
1606         event->ignore();
1607         return;
1608     }
1609 
1610     /// @todo need data about the dragged object: #markers, selected, icon, ...
1611 
1612     event->accept();
1613 }
1614 
dragMoveEvent(QDragMoveEvent * event)1615 void MapWidget::dragMoveEvent(QDragMoveEvent* event)
1616 {
1617     if (!d->dragDropHandler)
1618     {
1619         event->ignore();
1620         return;
1621     }
1622 
1623     event->accept();
1624 }
1625 
dropEvent(QDropEvent * event)1626 void MapWidget::dropEvent(QDropEvent* event)
1627 {
1628     // remove the drag marker:
1629 /*
1630     d->currentBackend->updateDragDropMarker(QPoint(), 0);
1631 */
1632     if (!d->dragDropHandler)
1633     {
1634         event->ignore();
1635         return;
1636     }
1637 
1638     GeoCoordinates dropCoordinates;
1639 
1640     if (!d->currentBackend->geoCoordinates(event->pos(), &dropCoordinates))
1641     {
1642         return;
1643     }
1644 
1645     // the drag and drop handler handled the drop if it returned true here
1646 
1647     if (d->dragDropHandler->dropEvent(event, dropCoordinates))
1648     {
1649         event->acceptProposedAction();
1650     }
1651 }
1652 
dragLeaveEvent(QDragLeaveEvent * event)1653 void MapWidget::dragLeaveEvent(QDragLeaveEvent* event)
1654 {
1655     Q_UNUSED(event);
1656 
1657     // remove the marker:
1658 /*
1659     d->currentBackend->updateDragDropMarker(QPoint(), 0);
1660 */
1661 }
1662 
markClustersAsDirty()1663 void MapWidget::markClustersAsDirty()
1664 {
1665     s->tileGrouper->setClustersDirty();
1666 }
1667 
setDragDropHandler(GeoDragDropHandler * const dragDropHandler)1668 void MapWidget::setDragDropHandler(GeoDragDropHandler* const dragDropHandler)
1669 {
1670     d->dragDropHandler = dragDropHandler;
1671 }
1672 
getClusterRepresentativeMarker(const int clusterIndex,const int sortKey)1673 QVariant MapWidget::getClusterRepresentativeMarker(const int clusterIndex, const int sortKey)
1674 {
1675     if (!s->markerModel)
1676     {
1677         return QVariant();
1678     }
1679 
1680     const GeoIfaceCluster cluster          = s->clusterList.at(clusterIndex);
1681     QMap<int, QVariant>::const_iterator it = cluster.representativeMarkers.find(sortKey);
1682 
1683     if (it != cluster.representativeMarkers.end())
1684     {
1685         return *it;
1686     }
1687 
1688     QList<QVariant> repIndices;
1689 
1690     for (int i = 0 ; i < cluster.tileIndicesList.count() ; ++i)
1691     {
1692         repIndices << s->markerModel->getTileRepresentativeMarker(cluster.tileIndicesList.at(i), sortKey);
1693     }
1694 
1695     const QVariant clusterRepresentative                        = s->markerModel->bestRepresentativeIndexFromList(repIndices, sortKey);
1696     s->clusterList[clusterIndex].representativeMarkers[sortKey] = clusterRepresentative;
1697 
1698     return clusterRepresentative;
1699 }
1700 
slotItemDisplaySettingsChanged()1701 void MapWidget::slotItemDisplaySettingsChanged()
1702 {
1703     s->previewSingleItems  = d->actionPreviewSingleItems->isChecked();
1704     s->previewGroupedItems = d->actionPreviewGroupedItems->isChecked();
1705     s->showNumbersOnItems  = d->actionShowNumbersOnItems->isChecked();
1706 
1707     /// @todo Update action availability?
1708 
1709     /// @todo We just need to update the display, no need to recluster?
1710 
1711     slotRequestLazyReclustering();
1712 }
1713 
setSortOptionsMenu(QMenu * const sortMenu)1714 void MapWidget::setSortOptionsMenu(QMenu* const sortMenu)
1715 {
1716     d->sortMenu = sortMenu;
1717 
1718     rebuildConfigurationMenu();
1719 }
1720 
setSortKey(const int sortKey)1721 void MapWidget::setSortKey(const int sortKey)
1722 {
1723     s->sortKey = sortKey;
1724 
1725     // this is probably faster than writing a function that changes all the clusters icons...
1726     /// @todo We just need to update the display, no need to recluster?
1727 
1728     slotRequestLazyReclustering();
1729 }
1730 
getDecoratedPixmapForCluster(const int clusterId,const GeoGroupState * const selectedStateOverride,const int * const countOverride,QPoint * const centerPoint)1731 QPixmap MapWidget::getDecoratedPixmapForCluster(const int clusterId,
1732                                                 const GeoGroupState* const selectedStateOverride,
1733                                                 const int* const countOverride,
1734                                                 QPoint* const centerPoint)
1735 {
1736     GeoIfaceCluster& cluster = s->clusterList[clusterId];
1737     int markerCount          = cluster.markerCount;
1738     GeoGroupState groupState = cluster.groupState;
1739 
1740     if (selectedStateOverride)
1741     {
1742         groupState  = *selectedStateOverride;
1743         markerCount = *countOverride;
1744     }
1745 
1746     const GeoGroupState selectedState = groupState & SelectedMask;
1747 
1748     // first determine all the color and style values
1749 
1750     QColor       fillColor;
1751     QColor       strokeColor;
1752     Qt::PenStyle strokeStyle;
1753     QColor       labelColor;
1754     QString      labelText;
1755     getColorInfos(clusterId, &fillColor, &strokeColor,
1756                   &strokeStyle, &labelText, &labelColor,
1757                   &selectedState,
1758                   &markerCount);
1759 
1760     // determine whether we should use a pixmap or a placeholder
1761 
1762     if (!s->showThumbnails)
1763     {
1764         /// @todo Handle positive filtering and region selection!
1765 
1766         QString pixmapName = fillColor.name().mid(1);
1767 
1768         if (selectedState == SelectedAll)
1769         {
1770             pixmapName += QLatin1String("-selected");
1771         }
1772 
1773         if (selectedState == SelectedSome)
1774         {
1775             pixmapName += QLatin1String("-someselected");
1776         }
1777 
1778         const QPixmap& markerPixmap = GeoIfaceGlobalObject::instance()->getMarkerPixmap(pixmapName);
1779 
1780         // update the display information stored in the cluster:
1781 
1782         cluster.pixmapType          = GeoIfaceCluster::PixmapMarker;
1783         cluster.pixmapOffset        = QPoint(markerPixmap.width()/2, markerPixmap.height()-1);
1784         cluster.pixmapSize          = markerPixmap.size();
1785 
1786         if (centerPoint)
1787         {
1788             *centerPoint = cluster.pixmapOffset;
1789         }
1790 
1791         return markerPixmap;
1792     }
1793 
1794     /// @todo This check is strange, there can be no clusters without a markerModel?
1795 
1796     bool displayThumbnail = (s->markerModel != nullptr);
1797 
1798     if (displayThumbnail)
1799     {
1800         if (markerCount==1)
1801         {
1802             displayThumbnail = s->previewSingleItems;
1803         }
1804         else
1805         {
1806             displayThumbnail = s->previewGroupedItems;
1807         }
1808     }
1809 
1810     if (displayThumbnail)
1811     {
1812         const QVariant representativeMarker = getClusterRepresentativeMarker(clusterId, s->sortKey);
1813         const int undecoratedThumbnailSize  = getUndecoratedThumbnailSize();
1814         QPixmap clusterPixmap               = s->markerModel->pixmapFromRepresentativeIndex(representativeMarker,
1815                                                                                             QSize(undecoratedThumbnailSize,
1816                                                                                                   undecoratedThumbnailSize));
1817 
1818         if (!clusterPixmap.isNull())
1819         {
1820             QPixmap resultPixmap(clusterPixmap.size() + QSize(2, 2));
1821 
1822             // we may draw with partially transparent pixmaps later, so make sure we have a defined
1823             // background color
1824 
1825             resultPixmap.fill(QColor::fromRgb(0xff, 0xff, 0xff));
1826             QPainter painter(&resultPixmap);
1827 /*
1828              painter.setRenderHint(QPainter::Antialiasing);
1829 */
1830             const int borderWidth = (groupState&SelectedSome) ? 2 : 1;
1831             QPen borderPen;
1832             borderPen.setWidth(borderWidth);
1833             borderPen.setJoinStyle(Qt::MiterJoin);
1834 
1835             GeoGroupState globalState = s->markerModel->getGlobalGroupState();
1836 
1837             /// @todo What about partially in the region or positively filtered?
1838 
1839             const bool clusterIsNotInRegionSelection  = (globalState & RegionSelectedMask) &&
1840                                                         ((groupState & RegionSelectedMask) == RegionSelectedNone);
1841             const bool clusterIsNotPositivelyFiltered = (globalState & FilteredPositiveMask) &&
1842                                                         ((groupState & FilteredPositiveMask) == FilteredPositiveNone);
1843 
1844             const bool shouldGrayOut                  = clusterIsNotInRegionSelection || clusterIsNotPositivelyFiltered;
1845             const bool shouldCrossOut                 = clusterIsNotInRegionSelection;
1846 
1847             if (shouldGrayOut)
1848             {
1849                 /// @todo Cache the alphaPixmap!
1850 
1851                 QPixmap alphaPixmap(clusterPixmap.size());
1852                 alphaPixmap.fill(QColor::fromRgb(0x80, 0x80, 0x80));
1853 /*
1854                 NOTE : old Qt4 code ported to Qt5 due to deprecated QPixmap::setAlphaChannel()
1855                 clusterPixmap.setAlphaChannel(alphaPixmap);
1856 */
1857                 QPainter p(&clusterPixmap);
1858                 p.setOpacity(0.2);
1859                 p.drawPixmap(0, 0, alphaPixmap);
1860                 p.end();
1861             }
1862 
1863             painter.drawPixmap(QPoint(1, 1), clusterPixmap);
1864 
1865             if (shouldGrayOut || shouldCrossOut)
1866             {
1867                 // draw a red cross above the pixmap
1868 
1869                 QPen crossPen(Qt::red);
1870 
1871                 if (!shouldCrossOut)
1872                 {
1873                     /// @todo Maybe we should also do a cross for not positively filtered images?
1874 
1875                     crossPen.setColor(Qt::blue);
1876                 }
1877 
1878                 crossPen.setWidth(2);
1879                 painter.setPen(crossPen);
1880 
1881                 const int width  = resultPixmap.size().width();
1882                 const int height = resultPixmap.size().height();
1883                 painter.drawLine(0, 0, width-1, height-1);
1884                 painter.drawLine(width-1, 0, 0, height-1);
1885             }
1886 
1887             if (strokeStyle != Qt::SolidLine)
1888             {
1889                 // paint a white border around the image
1890 
1891                 borderPen.setColor(Qt::white);
1892                 painter.setPen(borderPen);
1893                 painter.drawRect(borderWidth-1, borderWidth-1,
1894                                  resultPixmap.size().width()-borderWidth,
1895                                  resultPixmap.size().height()-borderWidth);
1896             }
1897 
1898             // now draw the selection border
1899 
1900             borderPen.setColor(strokeColor);
1901             borderPen.setStyle(strokeStyle);
1902             painter.setPen(borderPen);
1903             painter.drawRect(borderWidth-1, borderWidth-1,
1904                              resultPixmap.size().width()-borderWidth,
1905                              resultPixmap.size().height()-borderWidth);
1906 
1907             if (s->showNumbersOnItems)
1908             {
1909                 QPen labelPen(labelColor);
1910 
1911                 // note: the pen has to be set, otherwise the bounding rect is 0 x 0!!!
1912 
1913                 painter.setPen(labelPen);
1914                 const QRect textRect(0, 0, resultPixmap.width(), resultPixmap.height());
1915                 QRect textBoundingRect = painter.boundingRect(textRect,
1916                                                               Qt::AlignHCenter | Qt::AlignVCenter,
1917                                                               labelText);
1918                 textBoundingRect.adjust(-1, -1, 1, 1);
1919 
1920                 // fill the bounding rect:
1921 
1922                 painter.setPen(Qt::NoPen);
1923                 painter.setBrush(QColor::fromRgb(0xff, 0xff, 0xff, 0x80));
1924                 painter.drawRect(textBoundingRect);
1925 
1926                 // draw the text:
1927 
1928                 painter.setPen(labelPen);
1929                 painter.setBrush(Qt::NoBrush);
1930                 painter.drawText(textRect, Qt::AlignHCenter|Qt::AlignVCenter, labelText);
1931             }
1932 
1933             // update the display information stored in the cluster:
1934 
1935             cluster.pixmapType   = GeoIfaceCluster::PixmapImage;
1936             cluster.pixmapOffset = QPoint(resultPixmap.width()/2, resultPixmap.height()/2);
1937             cluster.pixmapSize   = resultPixmap.size();
1938 
1939             if (centerPoint)
1940             {
1941                 *centerPoint = cluster.pixmapOffset;
1942             }
1943 
1944             return resultPixmap;
1945         }
1946     }
1947 
1948     // we do not have a thumbnail, draw the circle instead:
1949 
1950     const int circleRadius = s->thumbnailSize/2;
1951     QPen circlePen;
1952     circlePen.setColor(strokeColor);
1953     circlePen.setStyle(strokeStyle);
1954     circlePen.setWidth(2);
1955     QBrush circleBrush(fillColor);
1956     QPen labelPen;
1957     labelPen.setColor(labelColor);
1958     const QRect circleRect(0, 0, 2*circleRadius, 2*circleRadius);
1959 
1960     const int pixmapDiameter = 2*(circleRadius+1);
1961     QPixmap circlePixmap(pixmapDiameter, pixmapDiameter);
1962 
1963     /// @todo cache this somehow
1964 
1965     circlePixmap.fill(QColor(0, 0, 0, 0));
1966 
1967     QPainter circlePainter(&circlePixmap);
1968     circlePainter.setPen(circlePen);
1969     circlePainter.setBrush(circleBrush);
1970     circlePainter.drawEllipse(circleRect);
1971 
1972     circlePainter.setPen(labelPen);
1973     circlePainter.setBrush(Qt::NoBrush);
1974     circlePainter.drawText(circleRect, Qt::AlignHCenter|Qt::AlignVCenter, labelText);
1975 
1976     // update the display information stored in the cluster:
1977 
1978     cluster.pixmapType   = GeoIfaceCluster::PixmapCircle;
1979     cluster.pixmapOffset = QPoint(circlePixmap.width()/2, circlePixmap.height()/2);
1980     cluster.pixmapSize   = circlePixmap.size();
1981 
1982     if (centerPoint)
1983     {
1984         *centerPoint = QPoint(circlePixmap.width()/2, circlePixmap.height()/2);
1985     }
1986 
1987     return circlePixmap;
1988 }
1989 
setThumnailSize(const int newThumbnailSize)1990 void MapWidget::setThumnailSize(const int newThumbnailSize)
1991 {
1992     s->thumbnailSize = qMax(GeoIfaceMinThumbnailSize, newThumbnailSize);
1993 
1994     // make sure the grouping radius is larger than the thumbnail size
1995 
1996     if (2*s->thumbnailGroupingRadius < newThumbnailSize)
1997     {
1998         /// @todo more straightforward way for this?
1999 
2000         s->thumbnailGroupingRadius = newThumbnailSize/2 + newThumbnailSize%2;
2001     }
2002 
2003     if (s->showThumbnails)
2004     {
2005         slotRequestLazyReclustering();
2006     }
2007 
2008     slotUpdateActionsEnabled();
2009 }
2010 
setThumbnailGroupingRadius(const int newGroupingRadius)2011 void MapWidget::setThumbnailGroupingRadius(const int newGroupingRadius)
2012 {
2013     s->thumbnailGroupingRadius = qMax(GeoIfaceMinThumbnailGroupingRadius, newGroupingRadius);
2014 
2015     // make sure the thumbnails are smaller than the grouping radius
2016 
2017     if (2*s->thumbnailGroupingRadius < s->thumbnailSize)
2018     {
2019         s->thumbnailSize = 2*newGroupingRadius;
2020     }
2021 
2022     if (s->showThumbnails)
2023     {
2024         slotRequestLazyReclustering();
2025     }
2026 
2027     slotUpdateActionsEnabled();
2028 }
2029 
setMarkerGroupingRadius(const int newGroupingRadius)2030 void MapWidget::setMarkerGroupingRadius(const int newGroupingRadius)
2031 {
2032     s->markerGroupingRadius = qMax(GeoIfaceMinMarkerGroupingRadius, newGroupingRadius);
2033 
2034     if (!s->showThumbnails)
2035     {
2036         slotRequestLazyReclustering();
2037     }
2038 
2039     slotUpdateActionsEnabled();
2040 }
2041 
slotDecreaseThumbnailSize()2042 void MapWidget::slotDecreaseThumbnailSize()
2043 {
2044     if (!s->showThumbnails)
2045     {
2046         return;
2047     }
2048 
2049     if (s->thumbnailSize>GeoIfaceMinThumbnailSize)
2050     {
2051         const int newThumbnailSize = qMax(GeoIfaceMinThumbnailSize, s->thumbnailSize-5);
2052 
2053         // make sure the grouping radius is also decreased
2054         // this will automatically decrease the thumbnail size as well
2055 
2056         setThumbnailGroupingRadius(newThumbnailSize/2);
2057     }
2058 }
2059 
slotIncreaseThumbnailSize()2060 void MapWidget::slotIncreaseThumbnailSize()
2061 {
2062     if (!s->showThumbnails)
2063     {
2064         return;
2065     }
2066 
2067     setThumnailSize(s->thumbnailSize+5);
2068 }
2069 
getThumbnailSize() const2070 int MapWidget::getThumbnailSize() const
2071 {
2072     return s->thumbnailSize;
2073 }
2074 
getUndecoratedThumbnailSize() const2075 int MapWidget::getUndecoratedThumbnailSize() const
2076 {
2077     return s->thumbnailSize-2;
2078 }
2079 
setRegionSelection(const GeoCoordinates::Pair & region)2080 void MapWidget::setRegionSelection(const GeoCoordinates::Pair& region)
2081 {
2082     s->selectionRectangle = region;
2083 
2084     d->currentBackend->regionSelectionChanged();
2085 
2086     slotUpdateActionsEnabled();
2087 }
2088 
clearRegionSelection()2089 void MapWidget::clearRegionSelection()
2090 {
2091     s->selectionRectangle.first.clear();
2092 
2093     d->currentBackend->regionSelectionChanged();
2094 
2095     slotUpdateActionsEnabled();
2096 }
2097 
slotNewSelectionFromMap(const Digikam::GeoCoordinates::Pair & sel)2098 void MapWidget::slotNewSelectionFromMap(const Digikam::GeoCoordinates::Pair& sel)
2099 {
2100     /// @todo Should the backend update s on its own?
2101 
2102     s->selectionRectangle = sel;
2103 
2104     slotUpdateActionsEnabled();
2105 
2106     emit signalRegionSelectionChanged();
2107 }
2108 
slotRemoveCurrentRegionSelection()2109 void MapWidget::slotRemoveCurrentRegionSelection()
2110 {
2111     clearRegionSelection();
2112     d->currentBackend->regionSelectionChanged();
2113 
2114     slotUpdateActionsEnabled();
2115 
2116     emit signalRegionSelectionChanged();
2117 }
2118 
slotUngroupedModelChanged()2119 void MapWidget::slotUngroupedModelChanged()
2120 {
2121     // determine the index under which we handle this model
2122 
2123     QObject* const senderObject           = sender();
2124     QAbstractItemModel* const senderModel = qobject_cast<QAbstractItemModel*>(senderObject);
2125 
2126     if (senderModel)
2127     {
2128         for (int i = 0 ; i < s->ungroupedModels.count() ; ++i)
2129         {
2130             if (s->ungroupedModels.at(i)->model() == senderModel)
2131             {
2132                 emit signalUngroupedModelChanged(i);
2133 
2134                 break;
2135             }
2136         }
2137         return;
2138     }
2139 
2140     GeoModelHelper* const senderHelper = qobject_cast<GeoModelHelper*>(senderObject);
2141 
2142     if (senderHelper)
2143     {
2144         for (int i = 0 ; i < s->ungroupedModels.count() ; ++i)
2145         {
2146             if (s->ungroupedModels.at(i) == senderHelper)
2147             {
2148                 emit signalUngroupedModelChanged(i);
2149 
2150                 break;
2151             }
2152         }
2153     }
2154 
2155     QItemSelectionModel* const senderSelectionModel = qobject_cast<QItemSelectionModel*>(senderObject);
2156 
2157     if (senderSelectionModel)
2158     {
2159         for (int i = 0 ; i < s->ungroupedModels.count() ; ++i)
2160         {
2161             if (s->ungroupedModels.at(i)->selectionModel()==senderSelectionModel)
2162             {
2163                 emit signalUngroupedModelChanged(i);
2164 
2165                 break;
2166             }
2167         }
2168 
2169         return;
2170     }
2171 }
2172 
addWidgetToControlWidget(QWidget * const newWidget)2173 void MapWidget::addWidgetToControlWidget(QWidget* const newWidget)
2174 {
2175     // make sure the control widget exists
2176 
2177     if (!d->controlWidget)
2178     {
2179         getControlWidget();
2180     }
2181 
2182     QHBoxLayout* const hBoxLayout = reinterpret_cast<QHBoxLayout*>(d->hBoxForAdditionalControlWidgetItems->layout());
2183 
2184     if (hBoxLayout)
2185     {
2186         hBoxLayout->addWidget(newWidget);
2187     }
2188 }
2189 
2190 // Static methods ---------------------------------------------------------
2191 
MarbleWidgetVersion()2192 QString MapWidget::MarbleWidgetVersion()
2193 {
2194     return QString(Marble::MARBLE_VERSION_STRING).section(QLatin1Char(' '), 0, 0);
2195 }
2196 
setActive(const bool state)2197 void MapWidget::setActive(const bool state)
2198 {
2199     const bool oldState = s->activeState;
2200     s->activeState      = state;
2201 
2202     if (d->currentBackend)
2203     {
2204         d->currentBackend->setActive(state);
2205     }
2206 
2207     if (s->markerModel)
2208     {
2209         s->markerModel->setActive(state);
2210     }
2211 
2212     if (state)
2213     {
2214         // do we have a map widget shown?
2215 
2216         if ((d->stackedLayout->count() == 1) && d->currentBackend)
2217         {
2218             setMapWidgetInFrame(d->currentBackend->mapWidget());
2219 
2220             // call this slot manually in case the backend was ready right away:
2221 
2222             if (d->currentBackend->isReady())
2223             {
2224                 slotBackendReadyChanged(d->currentBackendName);
2225             }
2226             else
2227             {
2228                 rebuildConfigurationMenu();
2229             }
2230         }
2231     }
2232 
2233     if (state && !oldState && s->tileGrouper->getClustersDirty())
2234     {
2235         slotRequestLazyReclustering();
2236     }
2237 }
2238 
getActiveState()2239 bool MapWidget::getActiveState()
2240 {
2241     return s->activeState;
2242 }
2243 
setVisibleMouseModes(const GeoMouseModes mouseModes)2244 void MapWidget::setVisibleMouseModes(const GeoMouseModes mouseModes)
2245 {
2246     s->visibleMouseModes = mouseModes;
2247 
2248     if (d->mouseModesHolder)
2249     {
2250         d->mouseModesHolder->setVisible(s->visibleMouseModes);
2251         d->setSelectionModeButton->setVisible(s->visibleMouseModes.testFlag(MouseModeRegionSelection));
2252         d->removeCurrentSelectionButton->setVisible(s->visibleMouseModes.testFlag(MouseModeRegionSelection));
2253         d->setPanModeButton->setVisible(s->visibleMouseModes.testFlag(MouseModePan));
2254         d->setZoomModeButton->setVisible(s->visibleMouseModes.testFlag(MouseModeZoomIntoGroup));
2255         d->setRegionSelectionFromIconModeButton->setVisible(s->visibleMouseModes.testFlag(MouseModeRegionSelectionFromIcon));
2256         d->setFilterModeButton->setVisible(s->visibleMouseModes.testFlag(MouseModeFilter));
2257         d->removeFilterModeButton->setVisible(s->visibleMouseModes.testFlag(MouseModeFilter));
2258         d->setSelectThumbnailMode->setVisible(s->visibleMouseModes.testFlag(MouseModeSelectThumbnail));
2259     }
2260 }
2261 
setAvailableMouseModes(const GeoMouseModes mouseModes)2262 void MapWidget::setAvailableMouseModes(const GeoMouseModes mouseModes)
2263 {
2264     s->availableMouseModes = mouseModes;
2265 }
2266 
getStickyModeState() const2267 bool MapWidget::getStickyModeState() const
2268 {
2269     return d->actionStickyMode->isChecked();
2270 }
2271 
setStickyModeState(const bool state)2272 void MapWidget::setStickyModeState(const bool state)
2273 {
2274     d->actionStickyMode->setChecked(state);
2275 
2276     slotUpdateActionsEnabled();
2277 }
2278 
setVisibleExtraActions(const GeoExtraActions actions)2279 void MapWidget::setVisibleExtraActions(const GeoExtraActions actions)
2280 {
2281     d->visibleExtraActions = actions;
2282 
2283     if (d->buttonStickyMode)
2284     {
2285         d->buttonStickyMode->setVisible(actions.testFlag(ExtraActionSticky));
2286     }
2287 
2288     slotUpdateActionsEnabled();
2289 }
2290 
setEnabledExtraActions(const GeoExtraActions actions)2291 void MapWidget::setEnabledExtraActions(const GeoExtraActions actions)
2292 {
2293     d->availableExtraActions = actions;
2294 
2295     slotUpdateActionsEnabled();
2296 }
2297 
slotStickyModeChanged()2298 void MapWidget::slotStickyModeChanged()
2299 {
2300     slotUpdateActionsEnabled();
2301 
2302     emit signalStickyModeChanged();
2303 }
2304 
setAllowModifications(const bool state)2305 void MapWidget::setAllowModifications(const bool state)
2306 {
2307     s->modificationsAllowed = state;
2308 
2309     slotUpdateActionsEnabled();
2310     slotRequestLazyReclustering();
2311 }
2312 
2313 /**
2314  * @brief Adjusts the visible map area such that all grouped markers are visible.
2315  *
2316  * Note that a call to this function currently has no effect if the widget has been
2317  * set inactive via setActive() or the backend is not yet ready.
2318  *
2319  * @param useSaneZoomLevel Stop zooming at a sane level, if markers are too close together.
2320  */
adjustBoundariesToGroupedMarkers(const bool useSaneZoomLevel)2321 void MapWidget::adjustBoundariesToGroupedMarkers(const bool useSaneZoomLevel)
2322 {
2323     if ((!s->activeState) || (!s->markerModel) || (!currentBackendReady()))
2324     {
2325         return;
2326     }
2327 
2328     Marble::GeoDataLineString tileString;
2329 
2330     /// @todo not sure that this is the best way to find the bounding box of all items
2331 
2332     for (AbstractMarkerTiler::NonEmptyIterator tileIterator(s->markerModel, TileIndex::MaxLevel);
2333          !tileIterator.atEnd();
2334          tileIterator.nextIndex())
2335     {
2336         const TileIndex tileIndex = tileIterator.currentIndex();
2337 
2338         for (int corner = 1 ; corner <= 4 ; ++corner)
2339         {
2340             const GeoCoordinates currentTileCoordinate = tileIndex.toCoordinates(TileIndex::CornerPosition(corner));
2341 
2342             const Marble::GeoDataCoordinates tileCoordinate(currentTileCoordinate.lon(),
2343                                                             currentTileCoordinate.lat(),
2344                                                             0,
2345                                                             Marble::GeoDataCoordinates::Degree);
2346 
2347             tileString.append(tileCoordinate);
2348         }
2349     }
2350 
2351     const Marble::GeoDataLatLonBox latLonBox = Marble::GeoDataLatLonBox::fromLineString(tileString);
2352 
2353     /// @todo use a sane zoom level
2354 
2355     d->currentBackend->centerOn(latLonBox, useSaneZoomLevel);
2356 }
2357 
refreshMap()2358 void MapWidget::refreshMap()
2359 {
2360     slotRequestLazyReclustering();
2361 }
2362 
setShowPlaceholderWidget(const bool state)2363 void MapWidget::setShowPlaceholderWidget(const bool state)
2364 {
2365     if (state)
2366     {
2367         d->stackedLayout->setCurrentIndex(0);
2368     }
2369     else
2370     {
2371         if (d->stackedLayout->count() > 1)
2372         {
2373             d->stackedLayout->setCurrentIndex(1);
2374         }
2375     }
2376 }
2377 
2378 /**
2379  * @brief Set @p widgetForFrame as the widget in the frame, but does not show it.
2380  */
setMapWidgetInFrame(QWidget * const widgetForFrame)2381 void MapWidget::setMapWidgetInFrame(QWidget* const widgetForFrame)
2382 {
2383     if (d->stackedLayout->count() > 1)
2384     {
2385         // widget 0 is the status widget, widget 1 is the map widget
2386 
2387         if (d->stackedLayout->widget(1) == widgetForFrame)
2388         {
2389             return;
2390         }
2391 
2392         // there is some other widget at the target position.
2393         // remove it and add our widget instead
2394 
2395         d->stackedLayout->removeWidget(d->stackedLayout->widget(1));
2396     }
2397 
2398     d->stackedLayout->addWidget(widgetForFrame);
2399 }
2400 
removeMapWidgetFromFrame()2401 void MapWidget::removeMapWidgetFromFrame()
2402 {
2403     if (d->stackedLayout->count() > 1)
2404     {
2405         d->stackedLayout->removeWidget(d->stackedLayout->widget(1));
2406     }
2407 
2408     d->stackedLayout->setCurrentIndex(0);
2409 }
2410 
slotMouseModeChanged(QAction * triggeredAction)2411 void MapWidget::slotMouseModeChanged(QAction* triggeredAction)
2412 {
2413     // determine the new mouse mode:
2414 
2415     const QVariant triggeredActionData = triggeredAction->data();
2416     const GeoMouseModes newMouseMode   = triggeredActionData.value<Digikam::GeoMouseModes>();
2417 
2418     if (newMouseMode == s->currentMouseMode)
2419     {
2420         return;
2421     }
2422 
2423     // store the new mouse mode:
2424 
2425     s->currentMouseMode = newMouseMode;
2426 
2427     if (d->currentBackend)
2428     {
2429         d->currentBackend->mouseModeChanged();
2430     }
2431 
2432     emit signalMouseModeChanged(s->currentMouseMode);
2433 
2434     /// @todo Update action availability?
2435 }
2436 
currentBackendReady() const2437 bool MapWidget::currentBackendReady() const
2438 {
2439     if (!d->currentBackend)
2440     {
2441         return false;
2442     }
2443 
2444     return d->currentBackend->isReady();
2445 }
2446 
setMouseMode(const GeoMouseModes mouseMode)2447 void MapWidget::setMouseMode(const GeoMouseModes mouseMode)
2448 {
2449     s->currentMouseMode = mouseMode;
2450 
2451     if (currentBackendReady())
2452     {
2453         d->currentBackend->mouseModeChanged();
2454     }
2455 
2456     slotUpdateActionsEnabled();
2457 }
2458 
setTrackManager(TrackManager * const trackManager)2459 void MapWidget::setTrackManager(TrackManager* const trackManager)
2460 {
2461     s->trackManager = trackManager;
2462 
2463     // Some backends track the track manager activity even when not active
2464     // therefore they have to be notified.
2465 
2466     foreach (MapBackend* const backend, d->loadedBackends)
2467     {
2468         backend->slotTrackManagerChanged();
2469     }
2470 }
2471 
2472 } // namespace Digikam
2473