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