File indexing completed on 2025-01-05 03:53:47

0001 /* ============================================================
0002  *
0003  * This file is a part of digiKam project
0004  * https://www.digikam.org
0005  *
0006  * Date        : 2004-11-17
0007  * Description : albums history manager.
0008  *
0009  * SPDX-FileCopyrightText: 2004      by Joern Ahrens <joern dot ahrens at kdemail dot net>
0010  * SPDX-FileCopyrightText: 2006-2024 by Gilles Caulier <caulier dot gilles at gmail dot com>
0011  * SPDX-FileCopyrightText: 2014      by Mohamed_Anwer <m_dot_anwer at gmx dot com>
0012  *
0013  * SPDX-License-Identifier: GPL-2.0-or-later
0014  *
0015  * ============================================================ */
0016 
0017 #include "albumhistory.h"
0018 
0019 // Qt includes
0020 
0021 #include <QMutexLocker>
0022 #include <QString>
0023 #include <QWidget>
0024 #include <QHash>
0025 
0026 // Local includes
0027 
0028 #include "digikam_debug.h"
0029 #include "album.h"
0030 #include "albummanager.h"
0031 
0032 // NOTE: ust be declared outside namespace
0033 
0034 inline uint qHash(const QList<Digikam::Album*>& key)
0035 {
0036     if (key.isEmpty())
0037     {
0038         return 0;
0039     }
0040 
0041     Digikam::Album* const temp = key.first();
0042     quint64 myint              = (unsigned long long)temp;
0043     uint value                 = ::qHash(myint);
0044 
0045     for (int it = 1 ; it < key.size() ; ++it)
0046     {
0047         Digikam::Album* const al = key.at(it);
0048         quint64 myint2           = (unsigned long long)al;
0049         value                   ^= ::qHash(myint2);
0050     }
0051 
0052     return value;
0053 }
0054 
0055 namespace Digikam
0056 {
0057 
0058 /**
0059  * Stores an album along with the sidebar view, where the album
0060  * is selected
0061  */
0062 class Q_DECL_HIDDEN HistoryItem
0063 {
0064 public:
0065 
0066     HistoryItem()
0067         : widget(nullptr)
0068     {
0069     };
0070 
0071     HistoryItem(const QList<Album*>& a, QWidget* const w)
0072         : widget(w)
0073     {
0074         albums.append(a);
0075     };
0076 
0077     HistoryItem(const QList<Album*>& a, QWidget* const w, const QHash<LabelsTreeView::Labels, QList<int> >& selectedLabels)
0078         : widget(w),
0079           labels(selectedLabels)
0080     {
0081         albums.append(a);
0082     };
0083 
0084     bool operator==(const HistoryItem& item)
0085     {
0086         if (widget != item.widget)
0087         {
0088             return false;
0089         }
0090 
0091         return (albums == item.albums);
0092     }
0093 
0094     QList<Album*>                              albums;
0095     QWidget*                                   widget;
0096     QHash<LabelsTreeView::Labels, QList<int> > labels;
0097 };
0098 
0099 // ---------------------------------------------------------------------
0100 
0101 class Q_DECL_HIDDEN HistoryPosition
0102 {
0103 public:
0104 
0105     HistoryPosition()
0106     {
0107     };
0108 
0109     HistoryPosition(const ItemInfo& c, const QList<ItemInfo>& s)
0110         : current(c),
0111           select (s)
0112     {
0113     };
0114 
0115     bool operator==(const HistoryPosition& item)
0116     {
0117         return ((current == item.current) && (select == item.select));
0118     }
0119 
0120 public:
0121 
0122     ItemInfo        current;
0123     QList<ItemInfo> select;
0124 };
0125 
0126 // ---------------------------------------------------------------------
0127 
0128 class Q_DECL_HIDDEN AlbumHistory::Private
0129 {
0130 public:
0131 
0132     explicit Private()
0133         : moving        (false),
0134           blockSelection(false)
0135     {
0136     }
0137 
0138     void forward(unsigned int steps = 1);
0139 
0140 public:
0141 
0142     bool                                       moving;
0143     bool                                       blockSelection;
0144 
0145     QMutex                                     mutex;
0146 
0147     QList<HistoryItem>                         backwardStack;
0148     QList<HistoryItem>                         forwardStack;
0149     QHash<QList<Album*>, HistoryPosition>      historyPos;
0150     QHash<LabelsTreeView::Labels, QList<int> > neededLabels;
0151 };
0152 
0153 void AlbumHistory::Private::forward(unsigned int steps)
0154 {
0155     if (forwardStack.isEmpty() || ((int)steps > forwardStack.count()))
0156     {
0157         return;
0158     }
0159 
0160     while (steps)
0161     {
0162         backwardStack << forwardStack.takeFirst();
0163         --steps;
0164     }
0165 
0166     moving = true;
0167 }
0168 
0169 AlbumHistory::AlbumHistory(QObject* const parent)
0170     : QObject(parent),
0171       d      (new Private)
0172 {
0173 }
0174 
0175 AlbumHistory::~AlbumHistory()
0176 {
0177     clearHistory();
0178     delete d;
0179 }
0180 
0181 void AlbumHistory::clearHistory()
0182 {
0183     QMutexLocker locker(&d->mutex);
0184 
0185     d->backwardStack.clear();
0186     d->forwardStack.clear();
0187     d->historyPos.clear();
0188 
0189     d->moving = false;
0190 }
0191 
0192 void AlbumHistory::addAlbums(const QList<Album*>& albums, QWidget* const widget)
0193 {
0194     QMutexLocker locker(&d->mutex);
0195 
0196     if (albums.isEmpty() || !widget || d->moving)
0197     {
0198         d->moving = false;
0199 
0200         return;
0201     }
0202 
0203     // Same album as before in the history
0204 
0205     if (!d->backwardStack.isEmpty() && (d->backwardStack.last().albums == albums))
0206     {
0207         d->backwardStack.last().widget = widget;
0208 
0209         return;
0210     }
0211 
0212     d->backwardStack << HistoryItem(albums, widget);
0213 
0214     // The forward stack has to be cleared, if backward stack was changed
0215 
0216     d->forwardStack.clear();
0217 }
0218 
0219 /**
0220  * @brief AlbumHistory::addAlbums
0221  *        A special overloaded function for handling AlbumHistory
0222  *        for the Labels tree-view
0223  *
0224  * @author Mohamed_Anwer
0225  */
0226 void AlbumHistory::addAlbums(const QList<Album*>& albums,
0227                              QWidget* const widget,
0228                              const QHash<LabelsTreeView::Labels, QList<int> >& selectedLabels)
0229 {
0230     QMutexLocker locker(&d->mutex);
0231 
0232     if (albums.isEmpty() || !widget || d->moving)
0233     {
0234         d->moving = false;
0235         return;
0236     }
0237 
0238     if (!d->backwardStack.isEmpty() && d->backwardStack.last().albums.first()->isUsedByLabelsTree())
0239     {
0240         d->backwardStack.last().widget = widget;
0241         d->backwardStack.last().labels = selectedLabels;
0242         return;
0243     }
0244 
0245     d->backwardStack << HistoryItem(albums, widget, selectedLabels);
0246 
0247     // The forward stack has to be cleared, if backward stack was changed
0248 
0249     d->forwardStack.clear();
0250 }
0251 
0252 void AlbumHistory::deleteAlbum(Album* const album)
0253 {
0254     QMutexLocker locker(&d->mutex);
0255 
0256     if (!album || d->backwardStack.isEmpty())
0257     {
0258         return;
0259     }
0260 
0261     QList<Album*> albums;
0262     albums << album;
0263 
0264     //  Search all HistoryItems, with album and delete them
0265 
0266     QList<HistoryItem>::iterator it = d->backwardStack.begin();
0267 
0268     while (it != d->backwardStack.end())
0269     {
0270         if (it->albums == albums)
0271         {
0272             it = d->backwardStack.erase(it);
0273         }
0274         else
0275         {
0276             ++it;
0277         }
0278     }
0279 
0280     it = d->forwardStack.begin();
0281 
0282     while (it != d->forwardStack.end())
0283     {
0284         if (it->albums == albums)
0285         {
0286             it = d->forwardStack.erase(it);
0287         }
0288         else
0289         {
0290             ++it;
0291         }
0292     }
0293 
0294     if (d->backwardStack.isEmpty() && d->forwardStack.isEmpty())
0295     {
0296         return;
0297     }
0298 
0299     // If backwardStack is empty, then there is no current album.
0300     // So make the first album of the forwardStack the current one.
0301 
0302     if (d->backwardStack.isEmpty())
0303     {
0304         d->forward();
0305     }
0306 
0307     // After the album is deleted from the history it has to be ensured,
0308     // that neighboring albums are different
0309 
0310     QList<HistoryItem>::iterator lhs = d->backwardStack.begin();
0311     QList<HistoryItem>::iterator rhs = lhs;
0312     ++rhs;
0313 
0314     while (rhs != d->backwardStack.end())
0315     {
0316         if (*lhs == *rhs)
0317         {
0318             rhs = d->backwardStack.erase(rhs);
0319         }
0320         else
0321         {
0322             ++lhs;
0323             rhs = lhs;
0324             ++rhs;
0325         }
0326     }
0327 
0328     rhs = d->forwardStack.begin();
0329 
0330     while (rhs != d->forwardStack.end())
0331     {
0332         if (*lhs == *rhs)
0333         {
0334             rhs = d->forwardStack.erase(rhs);
0335         }
0336         else
0337         {
0338             if (lhs == (d->backwardStack.isEmpty() ?   d->backwardStack.end()
0339                                                    : --d->backwardStack.end()))
0340             {
0341                 lhs = d->forwardStack.begin();
0342             }
0343             else
0344             {
0345                 ++lhs;
0346                 rhs = lhs;
0347             }
0348 
0349             ++rhs;
0350         }
0351     }
0352 
0353     if (d->backwardStack.isEmpty() && !d->forwardStack.isEmpty())
0354     {
0355         d->forward();
0356     }
0357 }
0358 
0359 void AlbumHistory::getBackwardHistory(QStringList& list) const
0360 {
0361     QMutexLocker locker(&d->mutex);
0362 
0363     if (d->backwardStack.isEmpty())
0364     {
0365         return;
0366     }
0367 
0368     QList<HistoryItem>::const_iterator it = d->backwardStack.constBegin();
0369 
0370     for ( ; it != (d->backwardStack.isEmpty() ?   d->backwardStack.constEnd()
0371                                               : --d->backwardStack.constEnd())
0372           ; ++it)
0373     {
0374         if (!(it->albums.isEmpty()))
0375         {
0376             QString name;
0377 
0378             for (int iter = 0 ; iter < it->albums.size() ; ++iter)
0379             {
0380                 name.append(it->albums.at(iter)->title());
0381 
0382                 if (iter+1 < it->albums.size())
0383                 {
0384                     name.append(QLatin1Char('/'));
0385                 }
0386             }
0387 
0388             list.prepend(name);
0389         }
0390     }
0391 }
0392 
0393 void AlbumHistory::getForwardHistory(QStringList& list) const
0394 {
0395     QMutexLocker locker(&d->mutex);
0396 
0397     if (d->forwardStack.isEmpty())
0398     {
0399         return;
0400     }
0401 
0402     QList<HistoryItem>::const_iterator it;
0403 
0404     for (it = d->forwardStack.constBegin() ; it != d->forwardStack.constEnd() ; ++it)
0405     {
0406         if (!(it->albums.isEmpty()))
0407         {
0408             QString name;
0409 
0410             for (int iter = 0 ; iter < it->albums.size() ; ++iter)
0411             {
0412                 name.append(it->albums.at(iter)->title());
0413 
0414                 if (iter+1 < it->albums.size())
0415                 {
0416                     name.append(QLatin1Char('/'));
0417                 }
0418             }
0419 
0420             list.append(name);
0421         }
0422     }
0423 }
0424 
0425 void AlbumHistory::back(QList<Album*>& album, QWidget** const widget, unsigned int steps)
0426 {
0427     QMutexLocker locker(&d->mutex);
0428 
0429     *widget = nullptr;
0430 
0431     if ((d->backwardStack.count() <= 1) || ((int)steps > d->backwardStack.count()))
0432     {
0433         return;    // Only the current album available
0434     }
0435 
0436     while (steps)
0437     {
0438         d->forwardStack.prepend(d->backwardStack.takeLast());
0439         --steps;
0440     }
0441 
0442     d->moving = true;
0443 
0444     if (d->backwardStack.isEmpty())
0445     {
0446         return;
0447     }
0448 
0449     album.append(d->backwardStack.last().albums);
0450     *widget         = d->backwardStack.last().widget;
0451     d->neededLabels = d->backwardStack.last().labels;
0452 }
0453 
0454 void AlbumHistory::forward(QList<Album*>& album, QWidget** const widget, unsigned int steps)
0455 {
0456     QMutexLocker locker(&d->mutex);
0457 
0458     *widget = nullptr;
0459 
0460     if (d->forwardStack.isEmpty() || ((int)steps > d->forwardStack.count()))
0461     {
0462         return;
0463     }
0464 
0465     d->forward(steps);
0466 
0467     if (d->backwardStack.isEmpty())
0468     {
0469         return;
0470     }
0471 
0472     album.append(d->backwardStack.last().albums);
0473     *widget         = d->backwardStack.last().widget;
0474     d->neededLabels = d->backwardStack.last().labels;
0475 }
0476 
0477 void AlbumHistory::getCurrentAlbum(Album** const album, QWidget** const widget) const
0478 {
0479     QMutexLocker locker(&d->mutex);
0480 
0481     *album  = nullptr;
0482     *widget = nullptr;
0483 
0484     if (d->backwardStack.isEmpty())
0485     {
0486         return;
0487     }
0488 
0489     if (!(d->backwardStack.last().albums.isEmpty()))
0490     {
0491         *album  = d->backwardStack.last().albums.first();
0492     }
0493 
0494     *widget = d->backwardStack.last().widget;
0495 }
0496 
0497 bool AlbumHistory::isForwardEmpty() const
0498 {
0499     QMutexLocker locker(&d->mutex);
0500 
0501     return d->forwardStack.isEmpty();
0502 }
0503 
0504 bool AlbumHistory::isBackwardEmpty() const
0505 {
0506     QMutexLocker locker(&d->mutex);
0507 
0508     // the last album of the backwardStack is the currently shown
0509     // album, and therefore not really a previous album
0510 
0511     return ((d->backwardStack.count() <= 1) ? true : false);
0512 }
0513 
0514 QHash<LabelsTreeView::Labels, QList<int> > AlbumHistory::neededLabels()
0515 {
0516     return d->neededLabels;
0517 }
0518 
0519 void AlbumHistory::slotAlbumSelected()
0520 {
0521     QList<Album*> albumList = AlbumManager::instance()->currentAlbums();
0522 
0523     if (d->historyPos.contains(albumList))
0524     {
0525         d->blockSelection = true;
0526         Q_EMIT signalSetCurrent(d->historyPos[albumList].current.id());
0527     }
0528 }
0529 
0530 void AlbumHistory::slotAlbumCurrentChanged()
0531 {
0532     QList<Album*> albumList = AlbumManager::instance()->currentAlbums();
0533 
0534     if (!(albumList.isEmpty()) && d->historyPos.contains(albumList))
0535     {
0536         if (d->historyPos[albumList].select.size())
0537         {
0538             Q_EMIT signalSetSelectedInfos(d->historyPos[albumList].select);
0539         }
0540     }
0541 
0542     d->blockSelection = false;
0543 }
0544 
0545 void AlbumHistory::slotCurrentChange(const ItemInfo& info)
0546 {
0547     QList<Album*> albumList = AlbumManager::instance()->currentAlbums();
0548 
0549     if (albumList.isEmpty())
0550     {
0551         return;
0552     }
0553 
0554     d->historyPos[albumList].current = info;
0555 }
0556 
0557 void AlbumHistory::slotImageSelected(const ItemInfoList& selectedImages)
0558 {
0559     if (d->blockSelection)
0560     {
0561         return;
0562     }
0563 
0564     QList<Album*> albumList = AlbumManager::instance()->currentAlbums();
0565 
0566     if (d->historyPos.contains(albumList))
0567     {
0568         d->historyPos[albumList].select = selectedImages;
0569     }
0570 }
0571 
0572 void AlbumHistory::slotClearSelectPAlbum(const ItemInfo& imageInfo)
0573 {
0574     Album* const album = dynamic_cast<Album*>(AlbumManager::instance()->findPAlbum(imageInfo.albumId()));
0575     QList<Album*> albums;
0576     albums << album;
0577 
0578     if (d->historyPos.contains(albums))
0579     {
0580         d->historyPos[albums].select.clear();
0581     }
0582 }
0583 
0584 void AlbumHistory::slotClearSelectTAlbum(int id)
0585 {
0586     Album* const album = dynamic_cast<Album*>(AlbumManager::instance()->findTAlbum(id));
0587     QList<Album*> albums;
0588     albums << album;
0589 
0590     if (d->historyPos.contains(albums))
0591     {
0592         d->historyPos[albums].select.clear();
0593     }
0594 }
0595 
0596 void AlbumHistory::slotAlbumDeleted(Album* album)
0597 {
0598     deleteAlbum(album);
0599     QList<Album*> albums;
0600     albums << album;
0601 
0602     if (d->historyPos.contains(albums))
0603     {
0604         d->historyPos.remove(albums);
0605     }
0606 }
0607 
0608 void AlbumHistory::slotAlbumsCleared()
0609 {
0610     clearHistory();
0611 }
0612 
0613 } // namespace Digikam
0614 
0615 #include "moc_albumhistory.cpp"