1 /* ============================================================
2  *
3  * This file is a part of digiKam project
4  * https://www.digikam.org
5  *
6  * Date        : 2004-11-17
7  * Description : albums history manager.
8  *
9  * Copyright (C) 2004      by Joern Ahrens <joern dot ahrens at kdemail dot net>
10  * Copyright (C) 2006-2021 by Gilles Caulier <caulier dot gilles at gmail dot com>
11  * Copyright (C) 2014      by Mohamed_Anwer <m_dot_anwer at gmx dot com>
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 "albumhistory.h"
27 
28 // Qt includes
29 
30 #include <QString>
31 #include <QWidget>
32 #include <QHash>
33 
34 // Local includes
35 
36 #include "digikam_debug.h"
37 #include "album.h"
38 #include "iteminfo.h"
39 #include "albummanager.h"
40 #include "labelstreeview.h"
41 
42 // NOTE: ust be declared outside namespace
43 
qHash(const QList<Digikam::Album * > & key)44 inline uint qHash(const QList<Digikam::Album*>& key)
45 {
46     if (key.isEmpty())
47     {
48         return 0;
49     }
50 
51     Digikam::Album* const temp = key.first();
52     quint64 myint              = (unsigned long long)temp;
53     uint value                 = ::qHash(myint);
54 
55     for (int it = 1 ; it < key.size() ; ++it)
56     {
57         Digikam::Album* const al = key.at(it);
58         quint64 myint2           = (unsigned long long)al;
59         value                   ^= ::qHash(myint2);
60     }
61 
62     return value;
63 }
64 
65 namespace Digikam
66 {
67 
68 /**
69  * Stores an album along with the sidebar view, where the album
70  * is selected
71  */
72 class Q_DECL_HIDDEN HistoryItem
73 {
74 public:
75 
HistoryItem()76     HistoryItem()
77         : widget(nullptr)
78     {
79     };
80 
HistoryItem(const QList<Album * > & a,QWidget * const w)81     HistoryItem(const QList<Album*>& a, QWidget* const w)
82         : widget(w)
83     {
84         albums.append(a);
85     };
86 
HistoryItem(const QList<Album * > & a,QWidget * const w,const QHash<LabelsTreeView::Labels,QList<int>> & selectedLabels)87     HistoryItem(const QList<Album*>& a, QWidget* const w, const QHash<LabelsTreeView::Labels, QList<int> >& selectedLabels)
88         : widget(w),
89           labels(selectedLabels)
90     {
91         albums.append(a);
92     };
93 
operator ==(const HistoryItem & item)94     bool operator==(const HistoryItem& item)
95     {
96         if (widget != item.widget)
97         {
98             return false;
99         }
100 
101         return (albums == item.albums);
102     }
103 
104     QList<Album*>                              albums;
105     QWidget*                                   widget;
106     QHash<LabelsTreeView::Labels, QList<int> > labels;
107 };
108 
109 // ---------------------------------------------------------------------
110 
111 class Q_DECL_HIDDEN HistoryPosition
112 {
113 public:
114 
HistoryPosition()115     HistoryPosition()
116     {
117     };
118 
HistoryPosition(const ItemInfo & c,const QList<ItemInfo> & s)119     HistoryPosition(const ItemInfo& c, const QList<ItemInfo>& s)
120         : current(c),
121           select (s)
122     {
123     };
124 
operator ==(const HistoryPosition & item)125     bool operator==(const HistoryPosition& item)
126     {
127         return ((current == item.current) && (select == item.select));
128     }
129 
130 public:
131 
132     ItemInfo        current;
133     QList<ItemInfo> select;
134 };
135 
136 // ---------------------------------------------------------------------
137 
138 class Q_DECL_HIDDEN AlbumHistory::Private
139 {
140 public:
141 
Private()142     explicit Private()
143         : moving        (false),
144           blockSelection(false)
145     {
146     }
147 
148     void forward(unsigned int steps = 1);
149 
150 public:
151 
152     bool                                       moving;
153     bool                                       blockSelection;
154 
155     QList<HistoryItem>                         backwardStack;
156     QList<HistoryItem>                         forwardStack;
157     QHash<QList<Album*>, HistoryPosition>      historyPos;
158     QHash<LabelsTreeView::Labels, QList<int> > neededLabels;
159 };
160 
forward(unsigned int steps)161 void AlbumHistory::Private::forward(unsigned int steps)
162 {
163     if (forwardStack.isEmpty() || ((int)steps > forwardStack.count()))
164     {
165         return;
166     }
167 
168     while (steps)
169     {
170         backwardStack << forwardStack.takeFirst();
171         --steps;
172     }
173 
174     moving = true;
175 }
176 
AlbumHistory(QObject * const parent)177 AlbumHistory::AlbumHistory(QObject* const parent)
178     : QObject(parent),
179       d      (new Private)
180 {
181 }
182 
~AlbumHistory()183 AlbumHistory::~AlbumHistory()
184 {
185     clearHistory();
186     delete d;
187 }
188 
clearHistory()189 void AlbumHistory::clearHistory()
190 {
191     d->backwardStack.clear();
192     d->forwardStack.clear();
193     d->historyPos.clear();
194 
195     d->moving = false;
196 }
197 
addAlbums(const QList<Album * > & albums,QWidget * const widget)198 void AlbumHistory::addAlbums(const QList<Album*>& albums, QWidget* const widget)
199 {
200 
201     if (albums.isEmpty() || !widget || d->moving)
202     {
203         d->moving = false;
204 
205         return;
206     }
207 
208     // Same album as before in the history
209 
210     if (!d->backwardStack.isEmpty() && (d->backwardStack.last().albums == albums))
211     {
212         d->backwardStack.last().widget = widget;
213 
214         return;
215     }
216 
217     d->backwardStack << HistoryItem(albums, widget);
218 
219     // The forward stack has to be cleared, if backward stack was changed
220 
221     d->forwardStack.clear();
222 }
223 
224 /**
225  * @brief AlbumHistory::addAlbums
226  *        A special overloaded function for handling AlbumHistory
227  *        for the Labels tree-view
228  *
229  * @author Mohamed_Anwer
230  */
addAlbums(const QList<Album * > & albums,QWidget * const widget,const QHash<LabelsTreeView::Labels,QList<int>> & selectedLabels)231 void AlbumHistory::addAlbums(const QList<Album*>& albums,
232                              QWidget* const widget,
233                              const QHash<LabelsTreeView::Labels, QList<int> >& selectedLabels)
234 {
235 
236     if (albums.isEmpty() || !widget || d->moving)
237     {
238         d->moving = false;
239         return;
240     }
241 
242     if (!d->backwardStack.isEmpty() && d->backwardStack.last().albums.first()->isUsedByLabelsTree())
243     {
244         d->backwardStack.last().widget = widget;
245         d->backwardStack.last().labels = selectedLabels;
246         return;
247     }
248 
249     d->backwardStack << HistoryItem(albums, widget, selectedLabels);
250 
251     // The forward stack has to be cleared, if backward stack was changed
252 
253     d->forwardStack.clear();
254 }
255 
deleteAlbum(Album * const album)256 void AlbumHistory::deleteAlbum(Album* const album)
257 {
258     if (!album || d->backwardStack.isEmpty())
259     {
260         return;
261     }
262 
263     QList<Album*> albums;
264     albums << album;
265 
266     //  Search all HistoryItems, with album and delete them
267 
268     QList<HistoryItem>::iterator it = d->backwardStack.begin();
269 
270     while (it != d->backwardStack.end())
271     {
272         if (it->albums == albums)
273         {
274             it = d->backwardStack.erase(it);
275         }
276         else
277         {
278             ++it;
279         }
280     }
281 
282     it = d->forwardStack.begin();
283 
284     while (it != d->forwardStack.end())
285     {
286         if (it->albums == albums)
287         {
288             it = d->forwardStack.erase(it);
289         }
290         else
291         {
292             ++it;
293         }
294     }
295 
296     if (d->backwardStack.isEmpty() && d->forwardStack.isEmpty())
297     {
298         return;
299     }
300 
301     // If backwardStack is empty, then there is no current album.
302     // So make the first album of the forwardStack the current one.
303 
304     if (d->backwardStack.isEmpty())
305     {
306         d->forward();
307     }
308 
309     // After the album is deleted from the history it has to be ensured,
310     // that neighboring albums are different
311 
312     QList<HistoryItem>::iterator lhs = d->backwardStack.begin();
313     QList<HistoryItem>::iterator rhs = lhs;
314     ++rhs;
315 
316     while (rhs != d->backwardStack.end())
317     {
318         if (*lhs == *rhs)
319         {
320             rhs = d->backwardStack.erase(rhs);
321         }
322         else
323         {
324             ++lhs;
325             rhs = lhs;
326             ++rhs;
327         }
328     }
329 
330     rhs = d->forwardStack.begin();
331 
332     while (rhs != d->forwardStack.end())
333     {
334         if (*lhs == *rhs)
335         {
336             rhs = d->forwardStack.erase(rhs);
337         }
338         else
339         {
340             if (lhs == (d->backwardStack.isEmpty() ?   d->backwardStack.end()
341                                                    : --d->backwardStack.end()))
342             {
343                 lhs = d->forwardStack.begin();
344             }
345             else
346             {
347                 ++lhs;
348                 rhs = lhs;
349             }
350 
351             ++rhs;
352         }
353     }
354 
355     if (d->backwardStack.isEmpty() && !d->forwardStack.isEmpty())
356     {
357         d->forward();
358     }
359 }
360 
getBackwardHistory(QStringList & list) const361 void AlbumHistory::getBackwardHistory(QStringList& list) const
362 {
363     if (d->backwardStack.isEmpty())
364     {
365         return;
366     }
367 
368     QList<HistoryItem>::const_iterator it = d->backwardStack.constBegin();
369 
370     for ( ; it != (d->backwardStack.isEmpty() ?   d->backwardStack.constEnd()
371                                               : --d->backwardStack.constEnd())
372           ; ++it)
373     {
374         if (!(it->albums.isEmpty()))
375         {
376             QString name;
377 
378             for (int iter = 0 ; iter < it->albums.size() ; ++iter)
379             {
380                 name.append(it->albums.at(iter)->title());
381 
382                 if (iter+1 < it->albums.size())
383                 {
384                     name.append(QLatin1Char('/'));
385                 }
386             }
387 
388             list.prepend(name);
389         }
390     }
391 }
392 
getForwardHistory(QStringList & list) const393 void AlbumHistory::getForwardHistory(QStringList& list) const
394 {
395     if (d->forwardStack.isEmpty())
396     {
397         return;
398     }
399 
400     QList<HistoryItem>::const_iterator it;
401 
402     for (it = d->forwardStack.constBegin() ; it != d->forwardStack.constEnd() ; ++it)
403     {
404         if (!(it->albums.isEmpty()))
405         {
406             QString name;
407 
408             for (int iter = 0 ; iter < it->albums.size() ; ++iter)
409             {
410                 name.append(it->albums.at(iter)->title());
411 
412                 if (iter+1 < it->albums.size())
413                 {
414                     name.append(QLatin1Char('/'));
415                 }
416             }
417 
418             list.append(name);
419         }
420     }
421 }
422 
back(QList<Album * > & album,QWidget ** const widget,unsigned int steps)423 void AlbumHistory::back(QList<Album*>& album, QWidget** const widget, unsigned int steps)
424 {
425     *widget = nullptr;
426 
427     if ((d->backwardStack.count() <= 1) || ((int)steps > d->backwardStack.count()))
428     {
429         return;    // Only the current album available
430     }
431 
432     while (steps)
433     {
434         d->forwardStack.prepend(d->backwardStack.takeLast());
435         --steps;
436     }
437 
438     d->moving = true;
439 
440     if (d->backwardStack.isEmpty())
441     {
442         return;
443     }
444 
445     album.append(d->backwardStack.last().albums);
446     *widget         = d->backwardStack.last().widget;
447     d->neededLabels = d->backwardStack.last().labels;
448 }
449 
forward(QList<Album * > & album,QWidget ** const widget,unsigned int steps)450 void AlbumHistory::forward(QList<Album*>& album, QWidget** const widget, unsigned int steps)
451 {
452     *widget = nullptr;
453 
454     if (d->forwardStack.isEmpty() || ((int)steps > d->forwardStack.count()))
455     {
456         return;
457     }
458 
459     d->forward(steps);
460 
461     if (d->backwardStack.isEmpty())
462     {
463         return;
464     }
465 
466     album.append(d->backwardStack.last().albums);
467     *widget         = d->backwardStack.last().widget;
468     d->neededLabels = d->backwardStack.last().labels;
469 }
470 
getCurrentAlbum(Album ** const album,QWidget ** const widget) const471 void AlbumHistory::getCurrentAlbum(Album** const album, QWidget** const widget) const
472 {
473     *album  = nullptr;
474     *widget = nullptr;
475 
476     if (d->backwardStack.isEmpty())
477     {
478         return;
479     }
480 
481     if (!(d->backwardStack.last().albums.isEmpty()))
482     {
483         *album  = d->backwardStack.last().albums.first();
484     }
485 
486     *widget = d->backwardStack.last().widget;
487 }
488 
isForwardEmpty() const489 bool AlbumHistory::isForwardEmpty() const
490 {
491     return d->forwardStack.isEmpty();
492 }
493 
isBackwardEmpty() const494 bool AlbumHistory::isBackwardEmpty() const
495 {
496     // the last album of the backwardStack is the currently shown
497     // album, and therefore not really a previous album
498 
499     return ((d->backwardStack.count() <= 1) ? true : false);
500 }
501 
neededLabels()502 QHash<LabelsTreeView::Labels, QList<int> > AlbumHistory::neededLabels()
503 {
504     return d->neededLabels;
505 }
506 
slotAlbumSelected()507 void AlbumHistory::slotAlbumSelected()
508 {
509     QList<Album*> albumList = AlbumManager::instance()->currentAlbums();
510 
511     if (d->historyPos.contains(albumList))
512     {
513         d->blockSelection = true;
514         emit signalSetCurrent(d->historyPos[albumList].current.id());
515     }
516 }
517 
slotAlbumCurrentChanged()518 void AlbumHistory::slotAlbumCurrentChanged()
519 {
520     QList<Album*> albumList = AlbumManager::instance()->currentAlbums();
521 
522     if (!(albumList.isEmpty()) && d->historyPos.contains(albumList))
523     {
524         if (d->historyPos[albumList].select.size())
525         {
526             emit signalSetSelectedInfos(d->historyPos[albumList].select);
527         }
528     }
529 
530     d->blockSelection = false;
531 }
532 
slotCurrentChange(const ItemInfo & info)533 void AlbumHistory::slotCurrentChange(const ItemInfo& info)
534 {
535     QList<Album*> albumList = AlbumManager::instance()->currentAlbums();
536 
537     if (albumList.isEmpty())
538     {
539         return;
540     }
541 
542     d->historyPos[albumList].current = info;
543 }
544 
slotImageSelected(const ItemInfoList & selectedImages)545 void AlbumHistory::slotImageSelected(const ItemInfoList& selectedImages)
546 {
547     if (d->blockSelection)
548     {
549         return;
550     }
551 
552     QList<Album*> albumList = AlbumManager::instance()->currentAlbums();
553 
554     if (d->historyPos.contains(albumList))
555     {
556         d->historyPos[albumList].select = selectedImages;
557     }
558 }
559 
slotClearSelectPAlbum(const ItemInfo & imageInfo)560 void AlbumHistory::slotClearSelectPAlbum(const ItemInfo& imageInfo)
561 {
562     Album* const album = dynamic_cast<Album*>(AlbumManager::instance()->findPAlbum(imageInfo.albumId()));
563     QList<Album*> albums;
564     albums << album;
565 
566     if (d->historyPos.contains(albums))
567     {
568         d->historyPos[albums].select.clear();
569     }
570 }
571 
slotClearSelectTAlbum(int id)572 void AlbumHistory::slotClearSelectTAlbum(int id)
573 {
574     Album* const album = dynamic_cast<Album*>(AlbumManager::instance()->findTAlbum(id));
575     QList<Album*> albums;
576     albums << album;
577 
578     if (d->historyPos.contains(albums))
579     {
580         d->historyPos[albums].select.clear();
581     }
582 }
583 
slotAlbumDeleted(Album * album)584 void AlbumHistory::slotAlbumDeleted(Album* album)
585 {
586     deleteAlbum(album);
587     QList<Album*> albums;
588     albums << album;
589 
590     if (d->historyPos.contains(albums))
591     {
592         d->historyPos.remove(albums);
593     }
594 }
595 
slotAlbumsCleared()596 void AlbumHistory::slotAlbumsCleared()
597 {
598     clearHistory();
599 }
600 
601 } // namespace Digikam
602