File indexing completed on 2025-01-19 03:53:21

0001 /* ============================================================
0002  *
0003  * This file is a part of digiKam project
0004  * https://www.digikam.org
0005  *
0006  * Date        : 2004-06-15
0007  * Description : Albums manager interface - Tag Album helpers.
0008  *
0009  * SPDX-FileCopyrightText: 2006-2024 by Gilles Caulier <caulier dot gilles at gmail dot com>
0010  * SPDX-FileCopyrightText: 2006-2011 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
0011  * SPDX-FileCopyrightText: 2015      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 "albummanager_p.h"
0018 
0019 namespace Digikam
0020 {
0021 
0022 void AlbumManager::scanTAlbums()
0023 {
0024     d->scanTAlbumsTimer->stop();
0025 
0026     // list TAlbums directly from the db
0027     // first insert all the current TAlbums into a map for quick lookup
0028 
0029     typedef QMap<int, TAlbum*> TagMap;
0030     TagMap                     tmap;
0031 
0032     tmap.insert(0, d->rootTAlbum);
0033 
0034     AlbumIterator it(d->rootTAlbum);
0035 
0036     while (it.current())
0037     {
0038         TAlbum* const t = (TAlbum*)(*it);
0039         tmap.insert(t->id(), t);
0040         ++it;
0041     }
0042 
0043     // Retrieve the list of tags from the database
0044 
0045     TagInfo::List tList = CoreDbAccess().db()->scanTags();
0046 
0047     // sort the list. needed because we want the tags can be read in any order,
0048     // but we want to make sure that we are ensure to find the parent TAlbum
0049     // for a new TAlbum
0050 
0051     {
0052         QHash<int, TAlbum*> tagHash;
0053 
0054         // insert items into a dict for quick lookup
0055 
0056         for (TagInfo::List::const_iterator iter = tList.constBegin() ; iter != tList.constEnd() ; ++iter)
0057         {
0058             TagInfo info        = *iter;
0059             TAlbum* const album = new TAlbum(info.name, info.id);
0060             album->m_icon       = info.icon;
0061             album->m_iconId     = info.iconId;
0062             album->m_pid        = info.pid;
0063             tagHash.insert(info.id, album);
0064         }
0065 
0066         tList.clear();
0067 
0068         // also add root tag
0069 
0070         TAlbum* const rootTag = new TAlbum(QLatin1String("root"), 0, true);
0071         tagHash.insert(0, rootTag);
0072 
0073         // build tree
0074 
0075         for (QHash<int, TAlbum*>::const_iterator iter = tagHash.constBegin() ; iter != tagHash.constEnd() ; ++iter)
0076         {
0077             TAlbum* album = *iter;
0078 
0079             if (album->m_id == 0)
0080             {
0081                 continue;
0082             }
0083 
0084             TAlbum* const parent = tagHash.value(album->m_pid);
0085 
0086             if (parent)
0087             {
0088                 album->setParent(parent);
0089             }
0090             else
0091             {
0092                 qCWarning(DIGIKAM_GENERAL_LOG) << "Failed to find parent tag for tag "
0093                            << album->m_title
0094                            << " with pid "
0095                            << album->m_pid;
0096             }
0097         }
0098 
0099         tagHash.clear();
0100 
0101         // now insert the items into the list. becomes sorted
0102 
0103         AlbumIterator it2(rootTag);
0104 
0105         while (it2.current())
0106         {
0107             TAlbum* const album = dynamic_cast<TAlbum*>(it2.current());
0108 
0109             if (album)
0110             {
0111                 TagInfo info;
0112                 info.id     = album->m_id;
0113                 info.pid    = album->m_pid;
0114                 info.name   = album->m_title;
0115                 info.icon   = album->m_icon;
0116                 info.iconId = album->m_iconId;
0117                 tList.append(info);
0118             }
0119 
0120             ++it2;
0121         }
0122 
0123         // this will also delete all child albums
0124 
0125         delete rootTag;
0126     }
0127 
0128     for (TagInfo::List::const_iterator it3 = tList.constBegin() ; it3 != tList.constEnd() ; ++it3)
0129     {
0130         TagInfo info = *it3;
0131 
0132         // check if we have already added this tag
0133 
0134         if (tmap.contains(info.id))
0135         {
0136             continue;
0137         }
0138 
0139         // Its a new album. Find the parent of the album
0140 
0141         TagMap::const_iterator iter = tmap.constFind(info.pid);
0142 
0143         if (iter == tmap.constEnd())
0144         {
0145             qCWarning(DIGIKAM_GENERAL_LOG) << "Failed to find parent tag for tag "
0146                                            << info.name
0147                                            << " with pid "
0148                                            << info.pid;
0149             continue;
0150         }
0151 
0152         TAlbum* const parent = iter.value();
0153 
0154         // Create the new TAlbum
0155 
0156         TAlbum* const album = new TAlbum(info.name, info.id, false);
0157         album->m_icon       = info.icon;
0158         album->m_iconId     = info.iconId;
0159         insertTAlbum(album, parent);
0160 
0161         // also insert it in the map we are doing lookup of parent tags
0162 
0163         tmap.insert(info.id, album);
0164     }
0165 
0166     if (!tList.isEmpty())
0167     {
0168         Q_EMIT signalAlbumsUpdated(Album::TAG);
0169     }
0170 
0171     getTagItemsCount();
0172 }
0173 
0174 void AlbumManager::getTagItemsCount()
0175 {
0176     d->tagItemCountTimer->stop();
0177 
0178     if (!ApplicationSettings::instance()->getShowFolderTreeViewItemsCount())
0179     {
0180         personItemsCount();
0181         return;
0182     }
0183 
0184     tagItemsCount();
0185     personItemsCount();
0186 }
0187 
0188 void AlbumManager::tagItemsCount()
0189 {
0190     if (d->tagListJob)
0191     {
0192         disconnect(d->tagListJob, nullptr, this, nullptr);
0193 
0194         d->tagListJob->cancel();
0195         d->tagListJob = nullptr;
0196     }
0197 
0198     TagsDBJobInfo jInfo;
0199     jInfo.setFoldersJob();
0200 
0201     d->tagListJob = DBJobsManager::instance()->startTagsJobThread(jInfo);
0202 
0203     connect(d->tagListJob, SIGNAL(finished()),
0204             this, SLOT(slotTagsJobResult()));
0205 
0206     connect(d->tagListJob, SIGNAL(foldersData(QHash<int,int>)),
0207             this, SLOT(slotTagsJobData(QHash<int,int>)));
0208 }
0209 
0210 AlbumList AlbumManager::allTAlbums() const
0211 {
0212     AlbumList list;
0213 
0214     if (d->rootTAlbum)
0215     {
0216         list.append(d->rootTAlbum);
0217     }
0218 
0219     AlbumIterator it(d->rootTAlbum);
0220 
0221     while (it.current())
0222     {
0223         list.append(*it);
0224         ++it;
0225     }
0226 
0227     return list;
0228 }
0229 
0230 /**
0231  * This method is not yet used
0232  */
0233 QList<TAlbum*> AlbumManager::currentTAlbums() const
0234 {
0235     QList<TAlbum*> talbums;
0236     QList<Album*>::iterator it;
0237 
0238     for (it = d->currentAlbums.begin() ; it != d->currentAlbums.end() ; ++it)
0239     {
0240         TAlbum* const temp = dynamic_cast<TAlbum*>(*it);
0241 
0242         if (temp)
0243         {
0244             talbums.append(temp);
0245         }
0246     }
0247 
0248     return talbums;
0249 }
0250 
0251 TAlbum* AlbumManager::findTAlbum(int id) const
0252 {
0253     if (!d->rootTAlbum)
0254     {
0255         return nullptr;
0256     }
0257 
0258     int gid = d->rootTAlbum->globalID() + id;
0259 
0260     return static_cast<TAlbum*>((d->allAlbumsIdHash.value(gid)));
0261 }
0262 
0263 TAlbum* AlbumManager::findTAlbum(const QString& tagPath) const
0264 {
0265     // handle gracefully with or without leading slash
0266 
0267     bool withLeadingSlash = tagPath.startsWith(QLatin1Char('/'));
0268     AlbumIterator it(d->rootTAlbum);
0269 
0270     while (it.current())
0271     {
0272         TAlbum* const talbum = static_cast<TAlbum*>(*it);
0273 
0274         if (talbum->tagPath(withLeadingSlash) == tagPath)
0275         {
0276             return talbum;
0277         }
0278 
0279         ++it;
0280     }
0281 
0282     return nullptr;
0283 
0284 }
0285 
0286 TAlbum* AlbumManager::createTAlbum(TAlbum* parent, const QString& name,
0287                                    const QString& iconkde, QString& errMsg)
0288 {
0289     if (!parent)
0290     {
0291         errMsg = i18n("No parent found for tag");
0292         return nullptr;
0293     }
0294 
0295     // sanity checks
0296 
0297     if (name.isEmpty())
0298     {
0299         errMsg = i18n("Tag name cannot be empty");
0300         return nullptr;
0301     }
0302 
0303     if (name.contains(QLatin1Char('/')))
0304     {
0305         errMsg = i18n("Tag name cannot contain '/'");
0306         return nullptr;
0307     }
0308 
0309     // first check if we have another album with the same name
0310 
0311     if (hasDirectChildAlbumWithTitle(parent, name))
0312     {
0313         errMsg = i18n("Tag name already exists");
0314         return nullptr;
0315     }
0316 
0317     ChangingDB changing(d);
0318     int id = CoreDbAccess().db()->addTag(parent->id(), name, iconkde, 0);
0319 
0320     if (id == -1)
0321     {
0322         errMsg = i18n("Failed to add tag to database");
0323         return nullptr;
0324     }
0325 
0326     TAlbum* const album = new TAlbum(name, id, false);
0327     album->m_icon       = iconkde;
0328 
0329     insertTAlbum(album, parent);
0330 
0331     TAlbum* const personParentTag = findTAlbum(FaceTags::personParentTag());
0332 
0333     if (personParentTag && personParentTag->isAncestorOf(album))
0334     {
0335         FaceTags::ensureIsPerson(album->id());
0336     }
0337 
0338     Q_EMIT signalAlbumsUpdated(Album::TAG);
0339 
0340     return album;
0341 }
0342 
0343 AlbumList AlbumManager::findOrCreateTAlbums(const QStringList& tagPaths)
0344 {
0345     // find tag ids for tag paths in list, create if they don't exist
0346 
0347     QList<int> tagIDs = TagsCache::instance()->getOrCreateTags(tagPaths);
0348 
0349     // create TAlbum objects for the newly created tags
0350 
0351     scanTAlbums();
0352 
0353     AlbumList resultList;
0354 
0355     for (QList<int>::const_iterator it = tagIDs.constBegin() ; it != tagIDs.constEnd() ; ++it)
0356     {
0357         resultList.append(findTAlbum(*it));
0358     }
0359 
0360     return resultList;
0361 }
0362 
0363 bool AlbumManager::deleteTAlbum(TAlbum* album, QString& errMsg, QList<qlonglong>* imageIds)
0364 {
0365     if (!album)
0366     {
0367         errMsg = i18n("No such album");
0368         return false;
0369     }
0370 
0371     if (album == d->rootTAlbum)
0372     {
0373         errMsg = i18n("Cannot delete root tag");
0374         return false;
0375     }
0376 
0377     if (FaceTags::isSystemPersonTagId(album->id()))
0378     {
0379         errMsg = i18n("Cannot delete required face tag");
0380         return false;
0381     }
0382 
0383     QList<int> toBeDeletedTagIds;
0384     toBeDeletedTagIds << album->id();
0385 
0386     Album* subAlbum = nullptr;
0387     AlbumIterator it(album);
0388 
0389     while ((subAlbum = it.current()) != nullptr)
0390     {
0391         toBeDeletedTagIds << subAlbum->id();
0392         ++it;
0393     }
0394 
0395     QApplication::setOverrideCursor(Qt::WaitCursor);
0396 
0397     if (imageIds)
0398     {
0399         // Recursively from all tags to delete.
0400 
0401         Q_FOREACH (const qlonglong& id, CoreDbAccess().db()->getItemIDsInTag(album->id(), true))
0402         {
0403             if (!imageIds->contains(id))
0404             {
0405                 *imageIds << id;
0406             }
0407         }
0408     }
0409 
0410     Q_FOREACH (int tagId, toBeDeletedTagIds)
0411     {
0412         if (FaceTags::isPerson(tagId))
0413         {
0414             const QList<qlonglong>& imgListIds = CoreDbAccess().db()->getItemIDsInTag(tagId);
0415 
0416             Q_FOREACH (const qlonglong& imageId, imgListIds)
0417             {
0418                 const QList<FaceTagsIface>& facesList = FaceTagsEditor().databaseFaces(imageId);
0419 
0420                 Q_FOREACH (const FaceTagsIface& face, facesList)
0421                 {
0422                     if (face.tagId() == tagId)
0423                     {
0424                         FaceTagsIface unknownFace(FaceTagsIface::UnknownName, imageId,
0425                                                   FaceTags::unknownPersonTagId(), face.region());
0426 
0427                         FaceTagsEditor().removeFace(face);
0428                         FaceTagsEditor().addManually(unknownFace);
0429                     }
0430                 }
0431             }
0432         }
0433     }
0434 
0435     QApplication::restoreOverrideCursor();
0436 
0437     {
0438         CoreDbAccess access;
0439         ChangingDB changing(d);
0440 
0441         Q_FOREACH (int tagId, toBeDeletedTagIds)
0442         {
0443             access.db()->deleteTag(tagId);
0444         }
0445     }
0446 
0447     removeTAlbum(album);
0448     Q_EMIT signalAlbumsUpdated(Album::TAG);
0449 
0450     return true;
0451 }
0452 
0453 bool AlbumManager::renameTAlbum(TAlbum* album,
0454                                 const QString& name,
0455                                 QString& errMsg)
0456 {
0457     if (!album)
0458     {
0459         errMsg = i18n("No such album");
0460         return false;
0461     }
0462 
0463     if (album == d->rootTAlbum)
0464     {
0465         errMsg = i18n("Cannot edit root tag");
0466         return false;
0467     }
0468 
0469     if (name.contains(QLatin1Char('/')))
0470     {
0471         errMsg = i18n("Tag name cannot contain '/'");
0472         return false;
0473     }
0474 
0475     // first check if we have another sibling with the same name
0476 
0477     if (hasDirectChildAlbumWithTitle(album->m_parent, name))
0478     {
0479         errMsg = i18n("Another tag with the same name already exists.\n"
0480                       "Please choose another name.");
0481         return false;
0482     }
0483 
0484     ChangingDB changing(d);
0485     CoreDbAccess().db()->setTagName(album->id(), name);
0486     album->setTitle(name);
0487     Q_EMIT signalAlbumRenamed(album);
0488 
0489     askUserForWriteChangedTAlbumToFiles(album);
0490 
0491     return true;
0492 }
0493 
0494 bool AlbumManager::moveTAlbum(TAlbum* album, TAlbum* newParent, QString& errMsg)
0495 {
0496     if (!album)
0497     {
0498         errMsg = i18n("No such album");
0499         return false;
0500     }
0501 
0502     if (!newParent)
0503     {
0504         errMsg = i18n("Attempt to move TAlbum to nowhere");
0505         return false;
0506     }
0507 
0508     if (album == d->rootTAlbum)
0509     {
0510         errMsg = i18n("Cannot move root tag");
0511         return false;
0512     }
0513 
0514     if (hasDirectChildAlbumWithTitle(newParent, album->title()))
0515     {
0516         QPointer<QMessageBox> msgBox = new QMessageBox(QMessageBox::Warning,
0517                  qApp->applicationName(),
0518                  i18n("Another tag with the same name already exists.\n"
0519                       "Do you want to merge the tags?"),
0520                  QMessageBox::Yes | QMessageBox::No,
0521                  qApp->activeWindow());
0522 
0523         int result = msgBox->exec();
0524         delete msgBox;
0525 
0526         if (result == QMessageBox::Yes)
0527         {
0528             QString destPath(newParent->tagPath());
0529 
0530             if (destPath != QLatin1String("/"))
0531             {
0532                 destPath += QLatin1Char('/');
0533             }
0534 
0535             TAlbum* const destAlbum = findTAlbum(destPath +
0536                                                  album->title());
0537 
0538             return mergeTAlbum(album, destAlbum, false, errMsg);
0539         }
0540         else
0541         {
0542             return true;
0543         }
0544     }
0545 
0546     d->currentlyMovingAlbum = album;
0547     Q_EMIT signalAlbumAboutToBeMoved(album);
0548 
0549     Q_EMIT signalAlbumAboutToBeDeleted(album);
0550 
0551     if (album->parent())
0552     {
0553         album->parent()->removeChild(album);
0554     }
0555 
0556     album->setParent(nullptr);
0557     Q_EMIT signalAlbumDeleted(album);
0558     Q_EMIT signalAlbumHasBeenDeleted(reinterpret_cast<quintptr>(album));
0559 
0560     Q_EMIT signalAlbumAboutToBeAdded(album, newParent, newParent->lastChild());
0561     ChangingDB changing(d);
0562     CoreDbAccess().db()->setTagParentID(album->id(), newParent->id());
0563     album->setParent(newParent);
0564     Q_EMIT signalAlbumAdded(album);
0565 
0566     Q_EMIT signalAlbumMoved(album);
0567     Q_EMIT signalAlbumsUpdated(Album::TAG);
0568 
0569     d->currentlyMovingAlbum       = nullptr;
0570     TAlbum* const personParentTag = findTAlbum(FaceTags::personParentTag());
0571 
0572     if (personParentTag && personParentTag->isAncestorOf(album))
0573     {
0574         FaceTags::ensureIsPerson(album->id());
0575     }
0576 
0577     askUserForWriteChangedTAlbumToFiles(album);
0578 
0579     return true;
0580 }
0581 
0582 bool AlbumManager::mergeTAlbum(TAlbum* album, TAlbum* destAlbum, bool dialog, QString& errMsg)
0583 {
0584     if (!album || !destAlbum)
0585     {
0586         errMsg = i18n("No such album");
0587         return false;
0588     }
0589 
0590     if ((album == d->rootTAlbum) || (destAlbum == d->rootTAlbum))
0591     {
0592         errMsg = i18n("Cannot merge root tag");
0593         return false;
0594     }
0595 
0596     if (FaceTags::isSystemPersonTagId(album->id()))
0597     {
0598         errMsg = i18n("Cannot merge required face tag");
0599         return false;
0600     }
0601 
0602     if (album->firstChild())
0603     {
0604         errMsg = i18n("Only a tag without children can be merged!");
0605         return false;
0606     }
0607 
0608     if (dialog)
0609     {
0610         int result = d->askMergeMessageBoxResult;
0611 
0612         if (result == -1)
0613         {
0614             QPointer<QMessageBox> msgBox = new QMessageBox(QMessageBox::Warning,
0615                      qApp->applicationName(),
0616                      i18n("Do you want to merge tag '%1' into tag '%2'?",
0617                           album->title(), destAlbum->title()),
0618                      QMessageBox::Yes | QMessageBox::No,
0619                      qApp->activeWindow());
0620             QCheckBox* const chkBox      = new QCheckBox(i18n("Don't ask again at this session"), msgBox);
0621             msgBox->setCheckBox(chkBox);
0622 
0623             result = msgBox->exec();
0624 
0625             if (chkBox->isChecked())
0626             {
0627                 d->askMergeMessageBoxResult = result;
0628             }
0629 
0630             delete msgBox;
0631         }
0632 
0633         if (result == QMessageBox::No)
0634         {
0635             return true;
0636         }
0637     }
0638 
0639     int oldId   = album->id();
0640     int mergeId = destAlbum->id();
0641 
0642     if (oldId == mergeId)
0643     {
0644         return true;
0645     }
0646 
0647     QApplication::setOverrideCursor(Qt::WaitCursor);
0648     QList<qlonglong> imageIds = CoreDbAccess().db()->getItemIDsInTag(oldId);
0649 
0650     {
0651         CoreDbOperationGroup group;
0652         group.setMaximumTime(200);
0653 
0654         Q_FOREACH (const qlonglong& imageId, imageIds)
0655         {
0656             QList<FaceTagsIface> facesList = FaceTagsEditor().databaseFaces(imageId);
0657             bool foundFace                 = false;
0658 
0659             Q_FOREACH (const FaceTagsIface& face, facesList)
0660             {
0661                 if (face.tagId() == oldId)
0662                 {
0663                     foundFace = true;
0664                     FaceTagsEditor().removeFace(face);
0665                     FaceTagsEditor().add(imageId, mergeId, face.region(), false);
0666                 }
0667             }
0668 
0669             if (!foundFace)
0670             {
0671                 ItemInfo info(imageId);
0672                 info.removeTag(oldId);
0673                 info.setTag(mergeId);
0674                 group.allowLift();
0675             }
0676         }
0677     }
0678 
0679     QApplication::restoreOverrideCursor();
0680 
0681     if (!deleteTAlbum(album, errMsg))
0682     {
0683         return false;
0684     }
0685 
0686     askUserForWriteChangedTAlbumToFiles(imageIds);
0687 
0688     return true;
0689 }
0690 
0691 bool AlbumManager::updateTAlbumIcon(TAlbum* album, const QString& iconKDE,
0692                                     qlonglong iconID, QString& errMsg)
0693 {
0694     if (!album)
0695     {
0696         errMsg = i18n("No such tag");
0697         return false;
0698     }
0699 
0700     if (album == d->rootTAlbum)
0701     {
0702         errMsg = i18n("Cannot edit root tag");
0703         return false;
0704     }
0705 
0706     {
0707         CoreDbAccess access;
0708         ChangingDB changing(d);
0709         access.db()->setTagIcon(album->id(), iconKDE, iconID);
0710         album->m_icon   = iconKDE;
0711         album->m_iconId = iconID;
0712     }
0713 
0714     Q_EMIT signalAlbumIconChanged(album);
0715 
0716     return true;
0717 }
0718 
0719 AlbumList AlbumManager::getRecentlyAssignedTags(bool includeInternal) const
0720 {
0721     QList<int> tagIDs = CoreDbAccess().db()->getRecentlyAssignedTags();
0722 
0723     AlbumList resultList;
0724 
0725     for (QList<int>::const_iterator it = tagIDs.constBegin() ; it != tagIDs.constEnd() ; ++it)
0726     {
0727         TAlbum* const album = findTAlbum(*it);
0728 
0729         if (album)
0730         {
0731             if (!includeInternal && album->isInternalTag())
0732             {
0733                 continue;
0734             }
0735 
0736             resultList.append(album);
0737         }
0738     }
0739 
0740     return resultList;
0741 }
0742 
0743 QStringList AlbumManager::tagPaths(const QList<int>& tagIDs,
0744                                    bool leadingSlash,
0745                                    bool includeInternal) const
0746 {
0747     QStringList tagPaths;
0748 
0749     for (QList<int>::const_iterator it = tagIDs.constBegin() ; it != tagIDs.constEnd() ; ++it)
0750     {
0751         TAlbum* album = findTAlbum(*it);
0752 
0753         if (album)
0754         {
0755             if (!includeInternal && album->isInternalTag())
0756             {
0757                 continue;
0758             }
0759 
0760             tagPaths.append(album->tagPath(leadingSlash));
0761         }
0762     }
0763 
0764     return tagPaths;
0765 }
0766 
0767 QStringList AlbumManager::tagNames(const QList<int>& tagIDs, bool includeInternal) const
0768 {
0769     QStringList tagNames;
0770 
0771     Q_FOREACH (int id, tagIDs)
0772     {
0773         TAlbum* const album = findTAlbum(id);
0774 
0775         if (album)
0776         {
0777             if (!includeInternal && album->isInternalTag())
0778             {
0779                 continue;
0780             }
0781 
0782             tagNames << album->title();
0783         }
0784     }
0785 
0786     return tagNames;
0787 }
0788 
0789 QHash<int, QString> AlbumManager::tagPaths(bool leadingSlash, bool includeInternal) const
0790 {
0791     QHash<int, QString> hash;
0792     AlbumIterator it(d->rootTAlbum);
0793 
0794     while (it.current())
0795     {
0796         TAlbum* const t = (TAlbum*)(*it);
0797 
0798         if (includeInternal || !t->isInternalTag())
0799         {
0800             hash.insert(t->id(), t->tagPath(leadingSlash));
0801         }
0802 
0803         ++it;
0804     }
0805 
0806     return hash;
0807 }
0808 
0809 QHash<int, QString> AlbumManager::tagNames(bool includeInternal) const
0810 {
0811     QHash<int, QString> hash;
0812     AlbumIterator it(d->rootTAlbum);
0813 
0814     while (it.current())
0815     {
0816         TAlbum* const t = (TAlbum*)(*it);
0817 
0818         if (includeInternal || !t->isInternalTag())
0819         {
0820             hash.insert(t->id(), t->title());
0821         }
0822 
0823         ++it;
0824     }
0825 
0826     return hash;
0827 }
0828 
0829 QList<int> AlbumManager::subTags(int tagId, bool recursive) const
0830 {
0831     TAlbum* const album = this->findTAlbum(tagId);
0832 
0833     return album->childAlbumIds(recursive);
0834 }
0835 
0836 int AlbumManager::findTopId(int tagId) const
0837 {
0838     TAlbum* const album = findTAlbum(tagId);
0839 
0840     if (!album || album->isInternalTag())
0841     {
0842         return -1;
0843     }
0844 
0845     int topId     = tagId;
0846     Album* parent = album;
0847 
0848     while (!parent->isRoot())
0849     {
0850         topId  = parent->id();
0851         parent = parent->parent();
0852     }
0853 
0854     return topId;
0855 }
0856 
0857 AlbumList AlbumManager::findTagsWithProperty(const QString& property)
0858 {
0859     AlbumList list;
0860 
0861     QList<int> ids = TagsCache::instance()->tagsWithProperty(property);
0862 
0863     Q_FOREACH (int id, ids)
0864     {
0865         TAlbum* const album = findTAlbum(id);
0866 
0867         if (album)
0868         {
0869             list << album;
0870         }
0871     }
0872 
0873     return list;
0874 }
0875 
0876 AlbumList AlbumManager::findTagsWithProperty(const QString& property, const QString& value)
0877 {
0878     AlbumList list;
0879 
0880     AlbumIterator it(d->rootTAlbum);
0881 
0882     while (it.current())
0883     {
0884         if (static_cast<TAlbum*>(*it)->property(property) == value)
0885         {
0886             list << *it;
0887         }
0888 
0889         ++it;
0890     }
0891 
0892     return list;
0893 }
0894 
0895 QHash<int, int> AlbumManager::getTAlbumsCount() const
0896 {
0897     return d->tAlbumsCount;
0898 }
0899 
0900 void AlbumManager::insertTAlbum(TAlbum* album, TAlbum* parent)
0901 {
0902     if (!album)
0903     {
0904         return;
0905     }
0906 
0907     Q_EMIT signalAlbumAboutToBeAdded(album, parent, parent ? parent->lastChild() : nullptr);
0908 
0909     if (parent)
0910     {
0911         album->setParent(parent);
0912     }
0913 
0914     d->allAlbumsIdHash.insert(album->globalID(), album);
0915 
0916     Q_EMIT signalAlbumAdded(album);
0917 }
0918 
0919 void AlbumManager::removeTAlbum(TAlbum* album)
0920 {
0921     if (!album)
0922     {
0923         return;
0924     }
0925 
0926     // remove all children of this album
0927 
0928     Album* child        = album->firstChild();
0929     TAlbum* toBeRemoved = nullptr;
0930 
0931     while (child)
0932     {
0933         Album* const next = child->next();
0934         toBeRemoved       = dynamic_cast<TAlbum*>(child);
0935 
0936         if (toBeRemoved)
0937         {
0938             removeTAlbum(toBeRemoved);
0939             toBeRemoved = nullptr;
0940         }
0941 
0942         child             = next;
0943     }
0944 
0945     Q_EMIT signalAlbumAboutToBeDeleted(album);
0946     d->allAlbumsIdHash.remove(album->globalID());
0947 
0948     if (!d->currentAlbums.isEmpty())
0949     {
0950         if (album == d->currentAlbums.first())
0951         {
0952             d->currentAlbums.clear();
0953             Q_EMIT signalAlbumCurrentChanged(d->currentAlbums);
0954         }
0955     }
0956 
0957     Q_EMIT signalAlbumDeleted(album);
0958 
0959     quintptr deletedAlbum = reinterpret_cast<quintptr>(album);
0960     delete album;
0961 
0962     Q_EMIT signalAlbumHasBeenDeleted(deletedAlbum);
0963 }
0964 
0965 void AlbumManager::slotTagsJobResult()
0966 {
0967     if (!d->tagListJob)
0968     {
0969         return;
0970     }
0971 
0972     if (d->tagListJob->hasErrors())
0973     {
0974         qCWarning(DIGIKAM_GENERAL_LOG) << "Failed to list face tags";
0975 
0976         // Pop-up a message about the error.
0977 
0978         DNotificationWrapper(QString(), d->personListJob->errorsList().first(),
0979                              nullptr, i18n("digiKam"));
0980     }
0981 
0982     d->tagListJob = nullptr;
0983 }
0984 
0985 void AlbumManager::slotTagsJobData(const QHash<int, int>& tagsStatHash)
0986 {
0987     if (tagsStatHash.isEmpty())
0988     {
0989         return;
0990     }
0991 
0992     d->tAlbumsCount = tagsStatHash;
0993     Q_EMIT signalTAlbumsDirty(tagsStatHash);
0994 }
0995 
0996 void AlbumManager::slotTagChange(const TagChangeset& changeset)
0997 {
0998     if (d->changingDB || !d->rootTAlbum)
0999     {
1000         return;
1001     }
1002 
1003     switch (changeset.operation())
1004     {
1005         case TagChangeset::Added:
1006         case TagChangeset::Moved:
1007         case TagChangeset::Deleted:
1008         case TagChangeset::Reparented:
1009         {
1010             if (!d->scanTAlbumsTimer->isActive())
1011             {
1012                 d->scanTAlbumsTimer->start();
1013             }
1014 
1015             break;
1016         }
1017 
1018         case TagChangeset::Renamed:
1019         case TagChangeset::IconChanged:
1020         {
1021             /**
1022              * @todo what happens here?
1023              */
1024             break;
1025         }
1026 
1027         case TagChangeset::PropertiesChanged:
1028         {
1029             TAlbum* const tag = findTAlbum(changeset.tagId());
1030 
1031             if (tag)
1032             {
1033                 Q_EMIT signalTagPropertiesChanged(tag);
1034             }
1035 
1036             break;
1037         }
1038 
1039         case TagChangeset::Unknown:
1040         {
1041             break;
1042         }
1043     }
1044 }
1045 
1046 void AlbumManager::slotImageTagChange(const ImageTagChangeset& changeset)
1047 {
1048     if (!d->rootTAlbum)
1049     {
1050         return;
1051     }
1052 
1053     switch (changeset.operation())
1054     {
1055         case ImageTagChangeset::Added:
1056         case ImageTagChangeset::Removed:
1057         case ImageTagChangeset::RemovedAll:
1058 
1059         // Add properties changed.
1060         // Reason: in people sidebar, the images are not
1061         // connected with the ImageTag table but by
1062         // ImageTagProperties entries.
1063         // Thus, the count of entries in face tags are not
1064         // updated. This adoption should fix the problem.
1065 
1066         case ImageTagChangeset::PropertiesChanged:
1067         {
1068             Q_FOREACH (int id, changeset.tags())
1069             {
1070                 if (!d->toUpdatedFaces.contains(id))
1071                 {
1072                     d->toUpdatedFaces << id;
1073                 }
1074             }
1075 
1076             if (!d->tagItemCountTimer->isActive())
1077             {
1078                 d->tagItemCountTimer->start();
1079             }
1080 
1081             break;
1082         }
1083 
1084         default:
1085         {
1086             break;
1087         }
1088     }
1089 }
1090 
1091 void AlbumManager::askUserForWriteChangedTAlbumToFiles(TAlbum* const album)
1092 {
1093     QList<qlonglong> imageIds = CoreDbAccess().db()->getItemIDsInTag(album->id(), true);
1094     askUserForWriteChangedTAlbumToFiles(imageIds);
1095 }
1096 
1097 void AlbumManager::askUserForWriteChangedTAlbumToFiles(const QList<qlonglong>& imageIds)
1098 {
1099     MetaEngineSettings* const settings = MetaEngineSettings::instance();
1100 
1101     if ((!settings->settings().saveTags &&
1102          !settings->settings().saveFaceTags) || imageIds.isEmpty())
1103     {
1104         return;
1105     }
1106 
1107     if (settings->settings().useLazySync)
1108     {
1109         MetadataHubMngr::instance()->addPendingIds(imageIds);
1110 
1111         return;
1112     }
1113 
1114     if (imageIds.count() > 100)
1115     {
1116         int result = d->longTimeMessageBoxResult;
1117 
1118         if (result == -1)
1119         {
1120             QPointer<QMessageBox> msgBox = new QMessageBox(QMessageBox::Warning,
1121                      qApp->applicationName(),
1122                      i18n("This operation can take a long time in the background.\n"
1123                           "Do you want to write the metadata to %1 files now?",
1124                           imageIds.count()),
1125                      QMessageBox::Yes | QMessageBox::No,
1126                      qApp->activeWindow());
1127             QCheckBox* const chkBox      = new QCheckBox(i18n("Don't ask again at this session"), msgBox);
1128             msgBox->setCheckBox(chkBox);
1129 
1130             result = msgBox->exec();
1131 
1132             if (chkBox->isChecked())
1133             {
1134                 d->longTimeMessageBoxResult = result;
1135             }
1136 
1137             delete msgBox;
1138         }
1139 
1140         if (result != QMessageBox::Yes)
1141         {
1142             return;
1143         }
1144     }
1145 
1146     ItemInfoList infos(imageIds);
1147     MetadataSynchronizer* const tool = new MetadataSynchronizer(infos, MetadataSynchronizer::WriteFromDatabaseToFile);
1148     tool->start();
1149 }
1150 
1151 } // namespace Digikam