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"