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