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