diff --git a/libs/tags/tagmodificationhelper.cpp b/libs/tags/tagmodificationhelper.cpp index d09ee2de5d..126fb77dc6 100644 --- a/libs/tags/tagmodificationhelper.cpp +++ b/libs/tags/tagmodificationhelper.cpp @@ -1,694 +1,694 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2000-12-05 * Description : helper class used to modify tag albums in views * * Copyright (C) 2009-2010 by Johannes Wienke * Copyright (C) 2010-2017 by Gilles Caulier * * This program is free software; you can redistribute it * and/or modify it under the terms of the GNU General * Public License as published by the Free Software Foundation; * either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * ============================================================ */ #include "tagmodificationhelper.h" // Qt includes #include #include #include // KDE includes #include // Local includes #include "digikam_debug.h" #include "album.h" #include "coredb.h" #include "coredbtransaction.h" #include "imageinfo.h" #include "imagetagpair.h" #include "metadatahub.h" #include "scancontroller.h" #include "statusprogressbar.h" #include "tagsactionmngr.h" #include "tagproperties.h" #include "tageditdlg.h" #include "facetags.h" #include "facedbaccess.h" #include "facedb.h" namespace Digikam { class TagModificationHelper::Private { public: Private() { parentTag = 0; dialogParent = 0; } AlbumPointer parentTag; QWidget* dialogParent; }; TagModificationHelper::TagModificationHelper(QObject* const parent, QWidget* const dialogParent) : QObject(parent), d(new Private) { d->dialogParent = dialogParent; } TagModificationHelper::~TagModificationHelper() { delete d; } void TagModificationHelper::bindTag(QAction* action, TAlbum* album) const { action->setData(QVariant::fromValue(AlbumPointer(album))); } TAlbum* TagModificationHelper::boundTag(QObject* sender) const { QAction* action = 0; if ( (action = qobject_cast(sender)) ) { return action->data().value >(); } return 0; } void TagModificationHelper::bindMultipleTags(QAction* action, QList tags) { action->setData(QVariant::fromValue(tags)); } QList TagModificationHelper::boundMultipleTags(QObject* sender) { QAction* action = 0; if ( (action = qobject_cast(sender)) ) { return (action->data().value >()); } return QList(); } TAlbum* TagModificationHelper::slotTagNew(TAlbum* parent, const QString& title, const QString& iconName) { // ensure that there is a parent AlbumPointer p(parent); if (!p) { p = AlbumManager::instance()->findTAlbum(0); if (!p) { qCDebug(DIGIKAM_GENERAL_LOG) << "Could not find root tag album"; return 0; } } QString editTitle = title; QString editIconName = iconName; QKeySequence ks; if (title.isEmpty()) { bool doCreate = TagEditDlg::tagCreate(d->dialogParent, p, editTitle, editIconName, ks); if (!doCreate || !p) { return 0; } } QMap errMap; AlbumList tList = TagEditDlg::createTAlbum(p, editTitle, editIconName, ks, errMap); TagEditDlg::showtagsListCreationError(d->dialogParent, errMap); if (errMap.isEmpty() && !tList.isEmpty()) { TAlbum* const tag = static_cast(tList.last()); emit tagCreated(tag); return tag; } else { return 0; } } TAlbum* TagModificationHelper::slotTagNew() { return slotTagNew(boundTag(sender())); } void TagModificationHelper::slotTagEdit(TAlbum* t) { if (!t) { return; } AlbumPointer tag(t); QString title, icon; QKeySequence ks; bool doEdit = TagEditDlg::tagEdit(d->dialogParent, tag, title, icon, ks); if (!doEdit || !tag) { return; } if (tag && tag->title() != title) { QString errMsg; if (AlbumManager::instance()->renameTAlbum(tag, title, errMsg)) { // TODO: make an option to edit the full name of a face tag if (FaceTags::isPerson(tag->id())) { TagProperties props(tag->id()); props.setProperty(TagPropertyName::person(), title); props.setProperty(TagPropertyName::faceEngineName(), title); } } else { QMessageBox::critical(qApp->activeWindow(), qApp->applicationName(), errMsg); } } if (tag && tag->icon() != icon) { QString errMsg; if (!AlbumManager::instance()->updateTAlbumIcon(tag, icon, 0, errMsg)) { QMessageBox::critical(qApp->activeWindow(), qApp->applicationName(), errMsg); } } if (tag && tag->property(TagPropertyName::tagKeyboardShortcut()) != ks.toString()) { TagsActionMngr::defaultManager()->updateTagShortcut(tag->id(), ks); } emit tagEdited(tag); } void TagModificationHelper::slotTagEdit() { slotTagEdit(boundTag(sender())); } void TagModificationHelper::slotTagDelete(TAlbum* t) { if (!t || t->isRoot()) { return; } AlbumPointer tag(t); // find number of subtags int children = 0; AlbumIterator iter(tag); while (iter.current()) { ++children; ++iter; } // ask for deletion of children if (children) { int result = QMessageBox::warning(d->dialogParent, qApp->applicationName(), i18np("Tag '%2' has one subtag. " "Deleting this will also delete " "the subtag.\n" "Do you want to continue?", "Tag '%2' has %1 subtags. " "Deleting this will also delete " "the subtags.\n" "Do you want to continue?", children, tag->title()), QMessageBox::Yes | QMessageBox::Cancel); if (result != QMessageBox::Yes || !tag) { return; } } QString message; QList assignedItems = CoreDbAccess().db()->getItemIDsInTag(tag->id()); if (!assignedItems.isEmpty()) { message = i18np("Tag '%2' is assigned to one item. " "Do you want to continue?", "Tag '%2' is assigned to %1 items. " "Do you want to continue?", assignedItems.count(), tag->title()); } else { message = i18n("Delete '%1' tag?", tag->title()); } int result = QMessageBox::warning(qApp->activeWindow(), i18n("Delete Tag"), message, QMessageBox::Yes | QMessageBox::Cancel); if (result == QMessageBox::Yes && tag) { emit aboutToDeleteTag(tag); QString errMsg; if (!AlbumManager::instance()->deleteTAlbum(tag, errMsg)) { QMessageBox::critical(qApp->activeWindow(), qApp->applicationName(), errMsg); } } } void TagModificationHelper::slotTagDelete() { slotTagDelete(boundTag(sender())); } void TagModificationHelper::slotMultipleTagDel(QList& tags) { QString tagWithChildrens; QString tagWithoutImages; QString tagWithImages; QMultiMap sortedTags; foreach(TAlbum* const t, tags) { if (!t || t->isRoot()) { continue; } AlbumPointer tag(t); // find number of subtags int children = 0; AlbumIterator iter(tag); while (iter.current()) { ++children; ++iter; } if (children) tagWithChildrens.append(tag->title() + QLatin1String(" ")); QList assignedItems = CoreDbAccess().db()->getItemIDsInTag(tag->id()); if (!assignedItems.isEmpty()) { tagWithImages.append(tag->title() + QLatin1String(" ")); } else { tagWithoutImages.append(tag->title() + QLatin1String(" ")); } /** * Tags must be deleted from children to parents, if we don't want * to step on invalid index. Use QMultiMap to order them by distance * to root tag */ Album* parent = t; int depth = 0; while (!parent->isRoot()) { parent = parent->parent(); depth++; } sortedTags.insert(depth,tag); } // ask for deletion of children if (!tagWithChildrens.isEmpty()) { int result = QMessageBox::warning(qApp->activeWindow(), qApp->applicationName(), i18n("Tags '%1' have one or more subtags. " "Deleting them will also delete " "the subtags.\n" "Do you want to continue?", tagWithChildrens), QMessageBox::Yes | QMessageBox::Cancel); if (result != QMessageBox::Yes) { return; } } QString message; if (!tagWithImages.isEmpty()) { message = i18n("Tags '%1' are assigned to one or more items. " "Do you want to continue?", tagWithImages); } else { message = i18n("Delete '%1' tag(s)?", tagWithoutImages); } int result = QMessageBox::warning(qApp->activeWindow(), i18n("Delete Tag"), message, QMessageBox::Yes | QMessageBox::Cancel); if (result == QMessageBox::Yes) { QMultiMap::iterator it; /** * QMultimap doesn't provide reverse iterator, -1 is required * because end() points after the last element */ for (it = sortedTags.end()-1; it != sortedTags.begin()-1; --it) { emit aboutToDeleteTag(it.value()); QString errMsg; if (!AlbumManager::instance()->deleteTAlbum(it.value(), errMsg)) { QMessageBox::critical(qApp->activeWindow(), qApp->applicationName(), errMsg); } } } } void TagModificationHelper::slotMultipleTagDel() { QList lst = boundMultipleTags(sender()); qCDebug(DIGIKAM_GENERAL_LOG) << lst.size(); slotMultipleTagDel(lst); } void TagModificationHelper::slotFaceTagDelete(TAlbum* t) { QList tag; tag.append(t); slotMultipleFaceTagDel(tag); } void TagModificationHelper::slotFaceTagDelete() { slotFaceTagDelete(boundTag(sender())); } void TagModificationHelper::slotMultipleFaceTagDel(QList& tags) { QString tagsWithChildren; QString tagsWithImages; // We use a set here since else one tag could occur more than once // which could lead to undefined behaviour. QSet allPersonTagsToDelete; int tagsWithChildrenCount = 0; QSet allAssignedItems; int tagsWithImagesCount = 0; foreach(TAlbum* const selectedTag, tags) { if (!selectedTag || selectedTag->isRoot()) { continue; } // find tags and subtags with person property QSet personTagsToDelete = getFaceTags(selectedTag).toSet(); // If there is more than one person tag in the list, // the tag to remove has at least one sub tag that is a face tag. // Thus, we have to warn. // // If there is only one face tag, it is either the original tag, // or it is the only sub tag of the original tag. // Behave, like the face tag itself was selected. if(personTagsToDelete.size() > 1) { if (tagsWithChildrenCount > 0) { tagsWithChildren.append(QLatin1String(",")); } tagsWithChildren.append(selectedTag->title()); ++tagsWithChildrenCount; } // Get the assigned faces for all person tags to delete foreach (TAlbum* const tAlbum, personTagsToDelete) { // If the global set does not yet contain the tag if (!allPersonTagsToDelete.contains(tAlbum)) { QSet assignedItems = CoreDbAccess().db()->getImagesWithImageTagProperty( tAlbum->id(), Digikam::ImageTagPropertyName::tagRegion()).toSet(); assignedItems.unite(CoreDbAccess().db()->getImagesWithImageTagProperty( tAlbum->id(), Digikam::ImageTagPropertyName::autodetectedFace()).toSet()); if(!assignedItems.isEmpty()) { // Add the items to the global set for potential untagging allAssignedItems.unite(assignedItems); if (tagsWithImagesCount > 0) { tagsWithImages.append(QLatin1String(",")); } tagsWithImages.append(tAlbum->title()); ++tagsWithImagesCount; } } } // Add the found tags to the global set. allPersonTagsToDelete.unite(personTagsToDelete); } // ask for deletion of children if (tagsWithChildrenCount) { - QString message = i18np("Face tag '%2' has at least one face tag child." + QString message = i18np("Face tag '%2' has at least one face tag child. " "Deleting it will also delete the children.\n" "Do you want to continue?", - "Face tags '%2' have at least one face tag child." + "Face tags '%2' have at least one face tag child. " "Deleting it will also delete the children.\n" "Do you want to continue?", tagsWithChildrenCount, tagsWithChildren); bool removeChildren = QMessageBox::Yes == (QMessageBox::warning(qApp->activeWindow(), qApp->applicationName(), message, QMessageBox::Yes | QMessageBox::Cancel)); if (!removeChildren) { return; } } QString message; if (!allAssignedItems.isEmpty()) { message = i18np("Face tag '%2' is assigned to at least one item. " "Do you want to continue?", "Face tags '%2' are assigned to at least one item. " "Do you want to continue?", tagsWithImagesCount, tagsWithImages); } else { message = i18np("Remove face tag?", "Remove face tags?", tags.size()); } bool removeFaceTag = QMessageBox::Yes == (QMessageBox::warning(qApp->activeWindow(), qApp->applicationName(), message, QMessageBox::Yes | QMessageBox::Cancel)); if (removeFaceTag) { // Now we ask the user if we should also remove the tags from the images. QString msg = i18np("Remove the tag corresponding to this face tag from the images?", "Remove the %1 tags corresponding to this face tags from the images?", allPersonTagsToDelete.size()); bool removeTagFromImages = QMessageBox::Yes == (QMessageBox::warning(qApp->activeWindow(), qApp->applicationName(), msg, QMessageBox::Yes | QMessageBox::No)); MetadataHub metadataHub; // remove the face region from images and unassign the tag if wished foreach (qlonglong imageId, allAssignedItems) { foreach (TAlbum* const tagToRemove, allPersonTagsToDelete) { ImageTagPair imageTagAssociation(imageId,tagToRemove->id()); if (imageTagAssociation.isAssigned()) { imageTagAssociation.removeProperties(ImageTagPropertyName::autodetectedFace()); imageTagAssociation.removeProperties(ImageTagPropertyName::tagRegion()); if (removeTagFromImages) { imageTagAssociation.unAssignTag(); // Load the current metadata and sync the tags ImageInfo info(imageId); if (!info.isNull()) { metadataHub.load(info); if (!metadataHub.writeToMetadata(info)) { qCWarning(DIGIKAM_GENERAL_LOG) << "Failed writing tags to image " << info.filePath(); } } } } } } foreach (TAlbum * const tAlbum, allPersonTagsToDelete) { TagProperties props(tAlbum->id()); // Delete TagPropertyName::person() and TagPropertyName::faceEngineName() // fetch the UUID to delete the identity from facesdb props.removeProperties(TagPropertyName::person()); props.removeProperties(TagPropertyName::faceEngineName()); QString uuid = props.value(TagPropertyName::faceEngineUuid()); qCDebug(DIGIKAM_GENERAL_LOG) << "Deleting person tag properties for tag " << tAlbum->title() << " with uuid " << uuid; if (!uuid.isEmpty()) { // Delete the UUID props.removeProperties(TagPropertyName::faceEngineUuid()); // delete the faces db identity with this uuid. FacesEngine::FaceDbAccess access; access.db()->deleteIdentity(uuid); } } } } void TagModificationHelper::slotMultipleFaceTagDel() { QList lst = boundMultipleTags(sender()); qCDebug(DIGIKAM_GENERAL_LOG) << lst.size(); slotMultipleFaceTagDel(lst); } void TagModificationHelper::slotTagToFaceTag(TAlbum* tAlbum) { if (!tAlbum) { return; } if (!FaceTags::isPerson(tAlbum->id())) { FaceTags::ensureIsPerson(tAlbum->id()); } } void TagModificationHelper::slotTagToFaceTag() { slotTagToFaceTag(boundTag(sender())); } void TagModificationHelper::slotMultipleTagsToFaceTags(QList& tags) { foreach(TAlbum* const selectedTag, tags) { slotTagToFaceTag(selectedTag); } } void TagModificationHelper::slotMultipleTagsToFaceTags() { QList lst = boundMultipleTags(sender()); qCDebug(DIGIKAM_GENERAL_LOG) << lst.size(); slotMultipleTagsToFaceTags(lst); } QList TagModificationHelper::getFaceTags(TAlbum* rootTag) { if (!rootTag) { return QList(); } QList tags; tags.append(rootTag); return getFaceTags(tags).toList(); } QSet TagModificationHelper::getFaceTags(QList tags) { QSet faceTags; foreach(TAlbum* const tAlbum, tags) { if (FaceTags::isPerson(tAlbum->id())) { faceTags.insert(tAlbum); } AlbumPointer tag(tAlbum); AlbumIterator iter(tag); // Get all shild tags which have the person property. while (iter.current()) { TAlbum * tAlbum = dynamic_cast(iter.current()); if (FaceTags::isPerson(tAlbum->id())) { faceTags.insert(tAlbum); } ++iter; } } return faceTags; } } // namespace Digikam