1 /* ============================================================
2 *
3 * This file is a part of digiKam project
4 * https://www.digikam.org
5 *
6 * Date : 2011-01-06
7 * Description : Helper functions for geolocation interface interaction
8 *
9 * Copyright (C) 2011 by Michael G. Hansen <mike at mghansen dot de>
10 * Copyright (C) 2011-2021 by Gilles Caulier <caulier dot gilles at gmail dot com>
11 *
12 * This program is free software; you can redistribute it
13 * and/or modify it under the terms of the GNU General
14 * Public License as published by the Free Software Foundation;
15 * either version 2, or (at your option)
16 * any later version.
17 *
18 * This program is distributed in the hope that it will be useful,
19 * but WITHOUT ANY WARRANTY; without even the implied warranty of
20 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 * GNU General Public License for more details.
22 *
23 * ============================================================ */
24
25 #include "gpsiteminfosorter.h"
26
27 // Qt includes
28
29 #include <QList>
30 #include <QMenu>
31 #include <QPointer>
32 #include <QAction>
33
34 // KDE includes
35
36 #include <klocalizedstring.h>
37
38 // Local includes
39
40 #include "mapwidget.h"
41
42 namespace Digikam
43 {
44
45 class Q_DECL_HIDDEN GPSItemInfoSorter::Private
46 {
47 public:
48
Private()49 explicit Private()
50 : mapWidgets (),
51 sortOrder (GPSItemInfoSorter::SortYoungestFirst),
52 sortMenu (nullptr),
53 sortActionOldestFirst (nullptr),
54 sortActionYoungestFirst (nullptr),
55 sortActionRating (nullptr)
56 {
57 }
58
59 QList<QPointer<MapWidget> > mapWidgets;
60 GPSItemInfoSorter::SortOptions sortOrder;
61 QPointer<QMenu> sortMenu;
62 QAction* sortActionOldestFirst;
63 QAction* sortActionYoungestFirst;
64 QAction* sortActionRating;
65
66 };
67
GPSItemInfoSorter(QObject * const parent)68 GPSItemInfoSorter::GPSItemInfoSorter(QObject* const parent)
69 : QObject(parent),
70 d (new Private())
71 {
72 }
73
~GPSItemInfoSorter()74 GPSItemInfoSorter::~GPSItemInfoSorter()
75 {
76 if (d->sortMenu)
77 {
78 delete d->sortMenu;
79 }
80
81 delete d;
82 }
83
fitsBetter(const GPSItemInfo & oldInfo,const GeoGroupState oldState,const GPSItemInfo & newInfo,const GeoGroupState newState,const GeoGroupState globalGroupState,const SortOptions sortOptions)84 bool GPSItemInfoSorter::fitsBetter(const GPSItemInfo& oldInfo,
85 const GeoGroupState oldState,
86 const GPSItemInfo& newInfo,
87 const GeoGroupState newState,
88 const GeoGroupState globalGroupState,
89 const SortOptions sortOptions)
90 {
91 // the best index for a tile is determined like this:
92 // region selected? -> prefer region selected markers
93 // positive filtering on? - > prefer positively filtered markers
94 // next -> depending on sortkey, prefer better rated ones
95 // next -> depending on sortkey, prefer older or younger ones
96 // next -> if the image has a URL, prefer the one with the 'lower' URL
97 // next -> prefer the image with the higher image id
98
99 // region selection part
100
101 if (globalGroupState & RegionSelectedMask)
102 {
103 const bool oldIsRegionSelected = (oldState & RegionSelectedMask);
104 const bool newIsRegionSelected = (newState & RegionSelectedMask);
105
106 if (oldIsRegionSelected != newIsRegionSelected)
107 {
108 return newIsRegionSelected;
109 }
110 }
111
112 // positive filtering part
113
114 if (globalGroupState & FilteredPositiveMask)
115 {
116 const bool oldIsFilteredPositive = (oldState & FilteredPositiveMask);
117 const bool newIsFilteredPositive = (newState & FilteredPositiveMask);
118
119 if (oldIsFilteredPositive != newIsFilteredPositive)
120 {
121 return newIsFilteredPositive;
122 }
123 }
124
125 // care about rating, if requested
126
127 if (sortOptions & SortRating)
128 {
129 const bool oldHasRating = (oldInfo.rating > 0);
130 const bool newHasRating = (newInfo.rating > 0);
131
132 if (oldHasRating != newHasRating)
133 {
134 return newHasRating;
135 }
136
137 if (oldHasRating &&
138 newHasRating &&
139 (oldInfo.rating != newInfo.rating))
140 {
141 return oldInfo.rating < newInfo.rating;
142 }
143
144 // ratings are equal or both have no rating, therefore fall through to the next level
145 }
146
147 // finally, decide by date
148
149 const bool oldHasDate = oldInfo.dateTime.isValid();
150 const bool newHasDate = newInfo.dateTime.isValid();
151
152 if (oldHasDate != newHasDate)
153 {
154 return newHasDate;
155 }
156
157 if (oldHasDate && newHasDate)
158 {
159 if (oldInfo.dateTime != newInfo.dateTime)
160 {
161 if (sortOptions & SortOldestFirst)
162 {
163 return (oldInfo.dateTime > newInfo.dateTime);
164 }
165 else
166 {
167 return (oldInfo.dateTime < newInfo.dateTime);
168 }
169 }
170 }
171
172 // compare the image URL
173
174 if (oldInfo.url.isValid() && newInfo.url.isValid())
175 {
176 return oldInfo.url.url() > newInfo.url.url();
177 }
178
179 // last resort: use the image id for reproducibility
180
181 return (oldInfo.id > newInfo.id);
182 }
183
addToMapWidget(MapWidget * const mapWidget)184 void GPSItemInfoSorter::addToMapWidget(MapWidget* const mapWidget)
185 {
186 initializeSortMenu();
187
188 d->mapWidgets << QPointer<MapWidget>(mapWidget);
189 mapWidget->setSortOptionsMenu(d->sortMenu);
190 }
191
initializeSortMenu()192 void GPSItemInfoSorter::initializeSortMenu()
193 {
194 if (d->sortMenu)
195 {
196 return;
197 }
198
199 d->sortMenu = new QMenu();
200 d->sortMenu->setTitle(i18n("Sorting"));
201 QActionGroup* const sortOrderExclusive = new QActionGroup(d->sortMenu);
202 sortOrderExclusive->setExclusive(true);
203
204 connect(sortOrderExclusive, SIGNAL(triggered(QAction*)),
205 this, SLOT(slotSortOptionTriggered()));
206
207 d->sortActionOldestFirst = new QAction(i18n("Show oldest first"), sortOrderExclusive);
208 d->sortActionOldestFirst->setCheckable(true);
209 d->sortMenu->addAction(d->sortActionOldestFirst);
210
211 d->sortActionYoungestFirst = new QAction(i18n("Show youngest first"), sortOrderExclusive);
212 d->sortActionYoungestFirst->setCheckable(true);
213 d->sortMenu->addAction(d->sortActionYoungestFirst);
214
215 d->sortActionRating = new QAction(i18n("Sort by rating"), this);
216 d->sortActionRating->setCheckable(true);
217 d->sortMenu->addAction(d->sortActionRating);
218
219 connect(d->sortActionRating, SIGNAL(triggered(bool)),
220 this, SLOT(slotSortOptionTriggered()));
221
222 /// @todo Should we initialize the checked state already or wait for a call to setSortOptions?
223 }
224
setSortOptions(const SortOptions sortOptions)225 void GPSItemInfoSorter::setSortOptions(const SortOptions sortOptions)
226 {
227 d->sortOrder = sortOptions;
228
229 for (int i = 0 ; i < d->mapWidgets.count() ; ++i)
230 {
231 if (d->mapWidgets.at(i))
232 {
233 d->mapWidgets.at(i)->setSortKey(d->sortOrder);
234 }
235 }
236
237 d->sortActionRating->setChecked(d->sortOrder & GPSItemInfoSorter::SortRating);
238 d->sortActionOldestFirst->setChecked(d->sortOrder & GPSItemInfoSorter::SortOldestFirst);
239 d->sortActionYoungestFirst->setChecked(!(d->sortOrder & GPSItemInfoSorter::SortOldestFirst));
240 }
241
getSortOptions() const242 GPSItemInfoSorter::SortOptions GPSItemInfoSorter::getSortOptions() const
243 {
244 return d->sortOrder;
245 }
246
slotSortOptionTriggered()247 void GPSItemInfoSorter::slotSortOptionTriggered()
248 {
249 SortOptions newSortKey = SortYoungestFirst;
250
251 if (d->sortActionOldestFirst->isChecked())
252 {
253 newSortKey = SortOldestFirst;
254 }
255
256 if (d->sortActionRating->isChecked())
257 {
258 newSortKey |= SortRating;
259 }
260
261 d->sortOrder = newSortKey;
262
263 for (int i = 0 ; i < d->mapWidgets.count() ; ++i)
264 {
265 if (d->mapWidgets.at(i))
266 {
267 d->mapWidgets.at(i)->setSortKey(d->sortOrder);
268 }
269 }
270 }
271
272 } // namespace Digikam
273