diff --git a/NEWS b/NEWS index a30b19d63e..3430355071 100644 --- a/NEWS +++ b/NEWS @@ -1,103 +1,104 @@ digiKam 6.1.0 - Release date: 2019-03-10 ***************************************************************************************************** NEW FEATURES: General : New plugins interface for digiKam and Showfoto named dplugins. General : All export tools become generic plugins and are shared with Showfoto. General : Update internal libpgf to last 07193. General : Add compatiblity with OpenCV version 4. General : MacOS and AppImage bundles are now published with Qt 5.11.3. General : Add new optional configuration option to compile with Faces Engine Neural Network. BQM : Add new advanced settings in resize tool. BQM : All Batch Queue Manager tools become Bqm plugins. Editor : All Image Editor tools become Editor plugins and are shared with Showfoto. Item View : Add sort items by modification date. ***************************************************************************************************** BUGFIXES: 001 ==> 279216 - "Resize image" feature is missing some useful options [patch]. 002 ==> 368779 - Missing translations to Plugin Categories columns in setup page. 003 ==> 165852 - Google Youtube video uploader. 004 ==> 404578 - Links broken on downloadpage for 6.0.0. 005 ==> 402069 - FAQ link on Support page is broken. 006 ==> 404503 - There is a typo in support section of website. 007 ==> 404621 - I can not import the album Google Photo. 008 ==> 404642 - digiKam flatpak: Please include mysql driver (for using an external mysql db). 009 ==> 404690 - There is an unwanted bar in the left screen edge in full screen view. 010 ==> 404736 - Merging tags keeps popping up confirmation dialogs every second. 011 ==> 404737 - digiKam does not compile with opencv 3.4.1. 012 ==> 404735 - F2 should edit tag name when tag name has focus. 013 ==> 404748 - Default Album rename dialog should be larger (or save its size when changed). 014 ==> 304811 - Offer a "stretch histogram" functionality to automatically adjust brightness & gamma. 015 ==> 374464 - Can start print wizzard. 016 ==> 316687 - ImageMagick-6.8.3.9 could not be found. 017 ==> 305137 - wish for integrated(?) clip-generator. 018 ==> 404821 - Presentation offset from full screen when OpenGL transitions selected. 019 ==> 404894 - 6.0.0 x86-64 appimage startup complains about mssing ']' (startup bash script being called). 020 ==> 368262 - Google services tool does not use kaccounts. 021 ==> 376913 - Can't create a new album in picasa/googlephoto. 022 ==> 404896 - Vertical video are displayed horizontally. 023 ==> 404859 - Using Batch Queue Manager to make a JPG copy of the images on completion the "arw" images are no longer visible in the originating album. 024 ==> 404893 - Digikam::DigikamApp::slotSolidDeviceChanged: slotSolidDeviceChanged: messages referring to directory that digikam should not care about. 025 ==> 264296 - Lack of right-click delete of points in Curves [patch]. 026 ==> 404954 - Places, Devices, and Removable Devices no longer show in "Select Target Location". 027 ==> 404962 - List of subfolders: incorrect encoding (spaces -> %20, accents). 028 ==> 402724 - digiKam Settings/Configuration: missing section "Plugins" in Windows/6.0.0B3. 029 ==> 404987 - Ability to select which import/export options are included in menu. 030 ==> 404999 - Inconsistency in facetag font size. 031 ==> 244259 - Last image is displayed twice when Advanced Slideshow with KenBurns effect is run more than once. 032 ==> 405043 - Add volume control to video playback. 033 ==> 401253 - Face detect crashes every time. 034 ==> 405042 - Ability to loop video playback. 035 ==> 405138 - Can not disable webservice plugin. 036 ==> 405137 - Original items visible in Thumbnails view. 037 ==> 400606 - Dead space above thumbnails. 038 ==> 380434 - 5.6.0-pre pkg does not detect filesystem changes. 039 ==> 405250 - Menus gone missing. 040 ==> 388198 - Menu Help -> What's this is not used. 041 ==> 392570 - Missing Option to display complete filename. 042 ==> 375474 - Renaming People Tag Causes Unpredictable Sort Order In People Menu. 043 ==> 398868 - Video upside down in Preview (Thumbnail ok). 044 ==> 380065 - "Open with" menu entry missing [patch]. 045 ==> 405258 - Provide an OpenWith... function to get a specific ImageEditor. 046 ==> 402807 - Progress manager doesn't seem to be involved in the fingerprint scanning (v6.0 beta 3). 047 ==> 278935 - Please make XMP Sidecar filename configurable [patch]. 048 ==> 405231 - Monitor Color Profile is not applied in "Presentation". 049 ==> 405347 - Selecting by aspect ratio: abs function in sqlite lowercase, in mariadb uppercase. 050 ==> 405327 - Position and size of faces display depends on configuration setting. 051 ==> 405234 - Refresh does no work. 052 ==> 403649 - Filesystem changes are not visible in album view [patch]. 053 ==> 400768 - Many different trash cans hard & slow to use. 054 ==> 296864 - SETUP : Create interface for changing physical location or path of album. 055 ==> 397189 - digikam is crashing when adding this photo. 056 ==> 405378 - Missing libz.dll on launch. 057 ==> 401306 - digikam-git r41326 doesn't compile with OpenCV 4 058 ==> 379049 - IPTC and XMP metadata not always read when adding a new album. 059 ==> 404939 - AppImage package: integration with the OS with AppImageLauncher. 060 ==> 405514 - Configure Shortcuts : Shortcuts file contains space in assigned_tags. 061 ==> 405149 - The "File Name" in the thumbnail view is not visible. 062 ==> 405512 - Meta Key Not Useful For Key Modifier In Windows. 063 ==> 405513 - Configure Shortcuts : Defaults Button Does Not Apply To New Schemes. 064 ==> 388334 - Auto Filter In People Tag List Confused With Shortcut Keys. 065 ==> 405518 - digiKam can not be added to the Gnome Dash as favorite. 066 ==> 405342 - Increase slideshow caption font size and keyboard shortcut or button to display/hide it. 067 ==> 405636 - When importing picture not all people tags are present. 068 ==> 379916 - Some people tags are missing. 069 ==> 386967 - digiKam with Adobe Bridge keywords under Windows. 070 ==> 402433 - Google Maps Doesn't Zoom With Mouse Wheel. 071 ==> 396920 - EXIF info not written when files from some cameras (Pentax K3) are edited and saved. 072 ==> 375468 - Cannot turn on Menubar. 073 ==> 387253 - No more menu bar in the Gnome desktop environment. 074 ==> 397405 - Large empty space in Digikams main icon view since ~20180707 appimage. 075 ==> 399237 - Thumbnails view shows an empty dock for thumbnails. 076 ==> 393935 - Segmentation fault during face detection. 077 ==> 388533 - UpdateSchemaFromV7ToV9 fails. 078 ==> 281493 - digiKam fails to install on Windows XP. 079 ==> 405537 - Strange behavior with tag in attached file. 080 ==> 405233 - digikam: symbol lookup error: /tmp/.mount_digika5N9rGH/usr/lib/libQt5XcbQpa.so.5: undefined symbol: FT_Property_Set. 081 ==> 404853 - digikam-6.0.0 faces engine fails to compile on PowerPC. 082 ==> 405625 - digiKam 6.0.0 faces engine fails to compile on PowerPC with AltiVec enabled. -083 ==> +083 ==> 372340 - Tagged face areas on portait (vertical) oriented images are mispositioned. +084 ==> diff --git a/core/libs/fileactionmanager/fileworkeriface.cpp b/core/libs/fileactionmanager/fileworkeriface.cpp index 89342d032b..22ffd8f142 100644 --- a/core/libs/fileactionmanager/fileworkeriface.cpp +++ b/core/libs/fileactionmanager/fileworkeriface.cpp @@ -1,388 +1,402 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2012-01-18 * Description : database worker interface * * Copyright (C) 2012 by Marcel Wiesweg * * 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 "fileworkeriface.h" // KDE includes #include // Local includes #include "digikam_debug.h" #include "metaenginesettings.h" #include "fileactionmngr_p.h" #include "itemattributeswatch.h" #include "iteminfotasksplitter.h" #include "scancontroller.h" #include "digikam_globals.h" #include "jpegutils.h" #include "dimg.h" #include "facetagseditor.h" namespace Digikam { void FileActionMngrFileWorker::writeOrientationToFiles(FileActionItemInfoList infos, int orientation) { QStringList failedItems; foreach (const ItemInfo& info, infos) { if (state() == WorkerObject::Deactivating) { break; } QString path = info.filePath(); DMetadata metadata(path); DMetadata::ImageOrientation o = (DMetadata::ImageOrientation)orientation; metadata.setItemOrientation(o); if (!metadata.applyChanges()) { failedItems.append(info.name()); } else { emit imageDataChanged(path, true, true); QUrl url = QUrl::fromLocalFile(path); ItemAttributesWatch::instance()->fileMetadataChanged(url); } infos.writtenToOne(); } if (!failedItems.isEmpty()) { emit imageChangeFailed(i18n("Failed to revise Exif orientation these files:"), failedItems); } infos.finishedWriting(); } void FileActionMngrFileWorker::writeMetadataToFiles(FileActionItemInfoList infos) { d->startingToWrite(infos); ScanController::instance()->suspendCollectionScan(); foreach (const ItemInfo& info, infos) { MetadataHub hub; if (state() == WorkerObject::Deactivating) { break; } hub.load(info); QString filePath = info.filePath(); if (MetaEngineSettings::instance()->settings().useLazySync) { hub.write(filePath, MetadataHub::WRITE_ALL); } else { ScanController::FileMetadataWrite writeScope(info); writeScope.changed(hub.write(filePath, MetadataHub::WRITE_ALL)); } // hub emits fileMetadataChanged infos.writtenToOne(); } ScanController::instance()->resumeCollectionScan(); infos.finishedWriting(); } void FileActionMngrFileWorker::writeMetadata(FileActionItemInfoList infos, int flags) { d->startingToWrite(infos); ScanController::instance()->suspendCollectionScan(); foreach (const ItemInfo& info, infos) { MetadataHub hub; if (state() == WorkerObject::Deactivating) { break; } hub.load(info); // apply to file metadata if (MetaEngineSettings::instance()->settings().useLazySync) { hub.writeToMetadata(info, (MetadataHub::WriteComponents)flags); } else { ScanController::FileMetadataWrite writeScope(info); writeScope.changed(hub.writeToMetadata(info, (MetadataHub::WriteComponents)flags)); } // hub emits fileMetadataChanged infos.writtenToOne(); } ScanController::instance()->resumeCollectionScan(); infos.finishedWriting(); } void FileActionMngrFileWorker::transform(FileActionItemInfoList infos, int action) { d->startingToWrite(infos); QStringList failedItems; ScanController::instance()->suspendCollectionScan(); foreach (const ItemInfo& info, infos) { if (state() == WorkerObject::Deactivating) { break; } QString path = info.filePath(); QString format = info.format(); MetaEngine::ImageOrientation currentOrientation = (MetaEngine::ImageOrientation)info.orientation(); bool isRaw = info.format().startsWith(QLatin1String("RAW")); bool rotateAsJpeg = false; bool rotateLossy = false; MetaEngineSettingsContainer::RotationBehaviorFlags behavior; behavior = MetaEngineSettings::instance()->settings().rotationBehavior; bool rotateByMetadata = (behavior & MetaEngineSettingsContainer::RotateByMetadataFlag); // Check if rotation by content, as desired, is feasible // We'll later check again if it was successful if (behavior & MetaEngineSettingsContainer::RotatingPixels) { if (format == QLatin1String("JPG") && JPEGUtils::isJpegImage(path)) { rotateAsJpeg = true; } if (behavior & MetaEngineSettingsContainer::RotateByLossyRotation) { DImg::FORMAT format = DImg::fileFormat(path); switch (format) { case DImg::JPEG: case DImg::PNG: case DImg::TIFF: case DImg::JP2K: case DImg::PGF: rotateLossy = true; default: break; } } } - ajustFaceRectangles(info, action); - MetaEngineRotation matrix; matrix *= currentOrientation; matrix *= (MetaEngineRotation::TransformationAction)action; MetaEngine::ImageOrientation finalOrientation = matrix.exifOrientation(); bool rotatedPixels = false; if (rotateAsJpeg) { JPEGUtils::JpegRotator rotator(path); rotator.setCurrentOrientation(currentOrientation); if (action == MetaEngineRotation::NoTransformation) { rotatedPixels = rotator.autoExifTransform(); } else { rotatedPixels = rotator.exifTransform((MetaEngineRotation::TransformationAction)action); } if (!rotatedPixels) { failedItems.append(info.name()); } } else if (rotateLossy) { // Non-JPEG image: DImg DImg image; if (!image.load(path)) { failedItems.append(info.name()); } else { if (action == MetaEngineRotation::NoTransformation) { image.rotateAndFlip(currentOrientation); } else { image.transform(action); } // TODO: Atomic operation!! // prepare metadata, including to reset Exif tag image.prepareMetadataToSave(path, image.format(), true); if (image.save(path, image.detectedFormat())) { rotatedPixels = true; } else { failedItems.append(info.name()); } } } if (rotatedPixels) { + ajustFaceRectangles(info, finalOrientation); // reset for DB. Metadata is already edited. finalOrientation = MetaEngine::ORIENTATION_NORMAL; } if (rotateByMetadata) { // Setting the rotation flag on Raws with embedded JPEG is a mess // Can apply to the RAW data, or to the embedded JPEG, or to both. if (!isRaw) { DMetadata metadata(path); metadata.setItemOrientation(finalOrientation); metadata.applyChanges(); } } // DB rotation ItemInfo(info).setOrientation(finalOrientation); if (!failedItems.contains(info.name())) { emit imageDataChanged(path, true, true); ItemAttributesWatch::instance()->fileMetadataChanged(info.fileUrl()); } infos.writtenToOne(); } if (!failedItems.isEmpty()) { emit imageChangeFailed(i18n("Failed to transform these files:"), failedItems); } infos.finishedWriting(); ScanController::instance()->resumeCollectionScan(); } -void FileActionMngrFileWorker::ajustFaceRectangles(const ItemInfo& info, int action) +void FileActionMngrFileWorker::ajustFaceRectangles(const ItemInfo& info, int orientation) { /** * Get all faces from database and rotate them */ QList facesList = FaceTagsEditor().databaseFaces(info.id()); if (facesList.isEmpty()) { return; } QMultiMap ajustedFaces; foreach (const FaceTagsIface& dface, facesList) { - QString name = FaceTags::faceNameForTag(dface.tagId()); - QRect oldrect = dface.region().toRect(); - QRect newRect; + QString name = FaceTags::faceNameForTag(dface.tagId()); + QRect faceRect = dface.region().toRect(); + QSize imgSize = info.dimensions(); - switch (action) + switch (orientation) { - default: - case MetaEngineRotation::NoTransformation: - newRect = oldrect; + case MetaEngine::ORIENTATION_HFLIP: + faceRect = TagRegion::ajustToFlippedImg(faceRect, imgSize, 0); + break; + + case MetaEngine::ORIENTATION_ROT_180: + faceRect = TagRegion::ajustToFlippedImg(faceRect, imgSize, 0); + faceRect = TagRegion::ajustToFlippedImg(faceRect, imgSize, 1); + break; + + case MetaEngine::ORIENTATION_VFLIP: + faceRect = TagRegion::ajustToFlippedImg(faceRect, imgSize, 1); break; - case MetaEngineRotation::Rotate90: - newRect = TagRegion::ajustToRotatedImg(oldrect, info.dimensions(), 0); + + case MetaEngine::ORIENTATION_ROT_90_HFLIP: + faceRect = TagRegion::ajustToRotatedImg(faceRect, imgSize, 0); + imgSize.transpose(); + faceRect = TagRegion::ajustToFlippedImg(faceRect, imgSize, 0); break; - case MetaEngineRotation::Rotate180: - newRect = TagRegion::ajustToFlippedImg(oldrect, info.dimensions(), 0); - newRect = TagRegion::ajustToFlippedImg(newRect, info.dimensions(), 1); + + case MetaEngine::ORIENTATION_ROT_90: + faceRect = TagRegion::ajustToRotatedImg(faceRect, imgSize, 0); break; - case MetaEngineRotation::Rotate270: - newRect = TagRegion::ajustToRotatedImg(oldrect, info.dimensions(), 1); + + case MetaEngine::ORIENTATION_ROT_90_VFLIP: + faceRect = TagRegion::ajustToRotatedImg(faceRect, imgSize, 0); + imgSize.transpose(); + faceRect = TagRegion::ajustToFlippedImg(faceRect, imgSize, 1); break; - case MetaEngineRotation::FlipHorizontal: - newRect = TagRegion::ajustToFlippedImg(oldrect, info.dimensions(), 0); + + case MetaEngine::ORIENTATION_ROT_270: + faceRect = TagRegion::ajustToRotatedImg(faceRect, imgSize, 1); break; - case MetaEngineRotation::FlipVertical: - newRect = TagRegion::ajustToFlippedImg(oldrect, info.dimensions(), 1); + + default: break; } - ajustedFaces.insertMulti(name, newRect); + ajustedFaces.insertMulti(name, faceRect); } /** * Delete all old faces and add rotated ones */ FaceTagsEditor().removeAllFaces(info.id()); QMap::ConstIterator it = ajustedFaces.constBegin(); - for (; it != ajustedFaces.constEnd() ; ++it) + for ( ; it != ajustedFaces.constEnd() ; ++it) { int tagId = FaceTags::getOrCreateTagForPerson(it.key()); if (!tagId) { qCDebug(DIGIKAM_GENERAL_LOG) << "Failed to create a person tag for name" << it.key(); } TagRegion region(it.value()); FaceTagsEditor().add(info.id(), tagId, region, false); } /** * Write medatada */ MetadataHub hub; hub.load(info); QSize tempS = info.dimensions(); hub.loadFaceTags(info, QSize(tempS.height(), tempS.width())); hub.write(info.filePath(), MetadataHub::WRITE_ALL); } } // namespace Digikam diff --git a/core/libs/fileactionmanager/fileworkeriface.h b/core/libs/fileactionmanager/fileworkeriface.h index 9d09e560eb..5f359ccbc9 100644 --- a/core/libs/fileactionmanager/fileworkeriface.h +++ b/core/libs/fileactionmanager/fileworkeriface.h @@ -1,83 +1,83 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2012-01-18 * Description : file worker interface * * Copyright (C) 2012 by Marcel Wiesweg * * 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. * * ============================================================ */ #ifndef DIGIKAM_FILE_WORKER_IFACE_H #define DIGIKAM_FILE_WORKER_IFACE_H // Local includes #include "fileactionmngr.h" #include "fileactionimageinfolist.h" #include "iteminfo.h" #include "workerobject.h" namespace Digikam { class MetadataHub; class FileWorkerInterface : public WorkerObject { Q_OBJECT public Q_SLOTS: virtual void writeOrientationToFiles(FileActionItemInfoList, int){}; virtual void writeMetadataToFiles(FileActionItemInfoList) {}; virtual void writeMetadata(FileActionItemInfoList, int) {}; virtual void transform(FileActionItemInfoList, int) {}; Q_SIGNALS: void imageDataChanged(const QString& path, bool removeThumbnails, bool notifyCache); void imageChangeFailed(const QString& message, const QStringList& fileNames); }; // --------------------------------------------------------------------------------------------- class FileActionMngrFileWorker : public FileWorkerInterface { public: explicit FileActionMngrFileWorker(FileActionMngr::Private* const d) : d(d) { } public: void writeOrientationToFiles(FileActionItemInfoList infos, int orientation); void writeMetadataToFiles(FileActionItemInfoList infos); void writeMetadata(FileActionItemInfoList infos, int flags); void transform(FileActionItemInfoList infos, int orientation); - void ajustFaceRectangles(const ItemInfo& info, int action); + void ajustFaceRectangles(const ItemInfo& info, int orientation); private: FileActionMngr::Private* const d; }; } // namespace Digikam #endif // DIGIKAM_FILE_WORKER_IFACE_H diff --git a/core/utilities/facemanagement/facegroup.cpp b/core/utilities/facemanagement/facegroup.cpp index 19fd5126ad..831f0134cc 100644 --- a/core/utilities/facemanagement/facegroup.cpp +++ b/core/utilities/facemanagement/facegroup.cpp @@ -1,912 +1,1015 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2010-09-17 * Description : Managing of face tag region items on a GraphicsDImgView * * Copyright (C) 2010 by Aditya Bhatt * Copyright (C) 2010-2011 by Marcel Wiesweg * * 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 "facegroup.h" // Qt includes #include #include #include // Local includes +#include "metaenginesettings.h" #include "addtagscombobox.h" #include "albummodel.h" #include "albumfiltermodel.h" #include "albummanager.h" #include "assignnamewidget.h" #include "clickdragreleaseitem.h" #include "dimgpreviewitem.h" #include "facepipeline.h" #include "facetags.h" #include "facetagseditor.h" #include "graphicsdimgview.h" #include "iteminfo.h" #include "regionframeitem.h" #include "taggingaction.h" #include "itemvisibilitycontroller.h" #include "digikam_debug.h" namespace Digikam { enum FaceGroupState { NoFaces, LoadingFaces, FacesLoaded }; //------------------------------------------------------------------------------- class Q_DECL_HIDDEN FaceItem : public RegionFrameItem { public: explicit FaceItem(QGraphicsItem* const parent = 0); void setFace(const FaceTagsIface& face); FaceTagsIface face() const; void setHudWidget(AssignNameWidget* const widget); AssignNameWidget* widget() const; void switchMode(AssignNameWidget::Mode mode); void setEditable(bool allowEdit); void updateCurrentTag(); protected: FaceTagsIface m_face; AssignNameWidget* m_widget; HidingStateChanger* m_changer; }; //------------------------------------------------------------------------------- FaceItem::FaceItem(QGraphicsItem* const parent) : RegionFrameItem(parent), m_widget(0), m_changer(0) { } void FaceItem::setFace(const FaceTagsIface& face) { m_face = face; updateCurrentTag(); setEditable(!m_face.isConfirmedName()); } FaceTagsIface FaceItem::face() const { return m_face; } void FaceItem::setHudWidget(AssignNameWidget* const widget) { m_widget = widget; updateCurrentTag(); RegionFrameItem::setHudWidget(widget); // Ensure that all HUD widgets are stacked before the frame items hudWidget()->setZValue(1); } AssignNameWidget* FaceItem::widget() const { return m_widget; } void FaceItem::switchMode(AssignNameWidget::Mode mode) { if (!m_widget || m_widget->mode() == mode) { return; } if (!m_changer) { m_changer = new AssignNameWidgetHidingStateChanger(this); } m_changer->changeValue(mode); } void FaceItem::setEditable(bool allowEdit) { changeFlags(ShowResizeHandles | MoveByDrag, allowEdit); } void FaceItem::updateCurrentTag() { if (m_widget) { m_widget->setCurrentFace(m_face); } } //------------------------------------------------------------------------------- AssignNameWidgetHidingStateChanger::AssignNameWidgetHidingStateChanger(FaceItem* const item) : HidingStateChanger(item->widget(), "mode", item) { // The WidgetProxyItem addItem(item->hudWidget()); connect(this, SIGNAL(stateChanged()), this, SLOT(slotStateChanged())); } void AssignNameWidgetHidingStateChanger::slotStateChanged() { FaceItem* const item = static_cast(parent()); // Show resize handles etc. only in edit modes item->setEditable(item->widget()->mode() != AssignNameWidget::ConfirmedMode); } //------------------------------------------------------------------------------- class Q_DECL_HIDDEN FaceGroup::Private { public: explicit Private(FaceGroup* const q) : q(q) { view = 0; autoSuggest = false; showOnHover = false; manuallyAddWrapItem = 0; manuallyAddedItem = 0; visibilityController = 0; state = NoFaces; tagModel = 0; filterModel = 0; filteredModel = 0; } void applyVisible(); FaceItem* createItem(const FaceTagsIface& face); FaceItem* addItem(const FaceTagsIface& face); AssignNameWidget* createAssignNameWidget(const FaceTagsIface& face, const QVariant& identifier); AssignNameWidget::Mode assignWidgetMode(FaceTagsIface::Type type); void checkModels(); QList hotItems(const QPointF& scenePos); public: GraphicsDImgView* view; ItemInfo info; bool autoSuggest; bool showOnHover; QList items; ClickDragReleaseItem* manuallyAddWrapItem; FaceItem* manuallyAddedItem; FaceGroupState state; ItemVisibilityController* visibilityController; TagModel* tagModel; CheckableAlbumFilterModel* filterModel; TagPropertiesFilterModel* filteredModel; FacePipeline editPipeline; FaceGroup* const q; }; FaceGroup::FaceGroup(GraphicsDImgView* const view) : QObject(view), d(new Private(this)) { d->view = view; d->visibilityController = new ItemVisibilityController(this); d->visibilityController->setShallBeShown(false); connect(AlbumManager::instance(), SIGNAL(signalAlbumRenamed(Album*)), this, SLOT(slotAlbumRenamed(Album*))); connect(AlbumManager::instance(), SIGNAL(signalAlbumsUpdated(int)), this, SLOT(slotAlbumsUpdated(int))); connect(view->previewItem(), SIGNAL(stateChanged(int)), this, SLOT(itemStateChanged(int))); d->editPipeline.plugDatabaseEditor(); d->editPipeline.plugTrainer(); d->editPipeline.construct(); } FaceGroup::~FaceGroup() { delete d; } void FaceGroup::itemStateChanged(int itemState) { switch (itemState) { case DImgPreviewItem::NoImage: case DImgPreviewItem::Loading: case DImgPreviewItem::ImageLoadingFailed: d->visibilityController->hide(); break; case DImgPreviewItem::ImageLoaded: if (d->state == FacesLoaded) { d->visibilityController->show(); } break; } } bool FaceGroup::isVisible() const { return d->visibilityController->shallBeShown(); } bool FaceGroup::hasVisibleItems() const { return d->visibilityController->hasVisibleItems(); } ItemInfo FaceGroup::info() const { return d->info; } QList FaceGroup::items() const { QList items; foreach (FaceItem* const item, d->items) { items << item; } return items; } void FaceGroup::setAutoSuggest(bool doAutoSuggest) { if (d->autoSuggest == doAutoSuggest) { return; } d->autoSuggest = doAutoSuggest; } bool FaceGroup::autoSuggest() const { return d->autoSuggest; } void FaceGroup::setShowOnHover(bool show) { d->showOnHover = show; } bool FaceGroup::showOnHover() const { return d->showOnHover; } void FaceGroup::Private::applyVisible() { if (state == NoFaces) { // If not yet loaded, load. load() will transitionToVisible after loading. q->load(); } else if (state == FacesLoaded) { // show existing faces, if we have an image if (view->previewItem()->isLoaded()) { visibilityController->show(); } } } void FaceGroup::setVisible(bool visible) { d->visibilityController->setShallBeShown(visible); d->applyVisible(); } void FaceGroup::setVisibleItem(RegionFrameItem* item) { d->visibilityController->setItemThatShallBeShown(item); d->applyVisible(); } void FaceGroup::setInfo(const ItemInfo& info) { if (d->info == info && d->state != NoFaces) { return; } clear(); d->info = info; if (d->visibilityController->shallBeShown()) { load(); } } void FaceGroup::aboutToSetInfo(const ItemInfo& info) { if (d->info == info) { return; } - applyItemGeometryChanges(); + //applyItemGeometryChanges(); clear(); } void FaceGroup::aboutToSetInfoAfterRotate(const ItemInfo& info) { if (d->info == info) { return; } //applyItemGeometryChanges(); clear(); } static QPointF closestPointOfRect(const QPointF& p, const QRectF& r) { QPointF cp = p; if (p.x() < r.left()) { cp.setX(r.left()); } else if (p.x() > r.right()) { cp.setX(r.right()); } if (p.y() < r.top()) { cp.setY(r.top()); } else if (p.y() > r.bottom()) { cp.setY(r.bottom()); } return cp; } RegionFrameItem* FaceGroup::closestItem(const QPointF& p, qreal* const manhattanLength) const { RegionFrameItem* closestItem = 0; qreal minDistance = 0; qreal minCenterDistance = 0; foreach (RegionFrameItem* const item, d->items) { QRectF r = item->boundingRect().translated(item->pos()); qreal distance = (p - closestPointOfRect(p, r)).manhattanLength(); if (!closestItem || distance < minDistance || (distance == 0 && (p - r.center()).manhattanLength() < minCenterDistance)) { closestItem = item; minDistance = distance; if (distance == 0) { minCenterDistance = (p - r.center()).manhattanLength(); } } } if (manhattanLength) { *manhattanLength = minDistance; } return closestItem; } QList FaceGroup::Private::hotItems(const QPointF& scenePos) { if (!q->hasVisibleItems()) { return QList(); } const int distance = 15; QRectF hotSceneRect = QRectF(scenePos, QSize(0, 0)).adjusted(-distance, -distance, distance, distance); QList closeItems = view->scene()->items(hotSceneRect, Qt::IntersectsItemBoundingRect); closeItems.removeOne(view->previewItem()); return closeItems; /* qreal distance; d->faceGroup->closestItem(mapToScene(e->pos()), &distance); if (distance < 15) return false; */ } bool FaceGroup::acceptsMouseClick(const QPointF& scenePos) { return d->hotItems(scenePos).isEmpty(); } void FaceGroup::itemHoverMoveEvent(QGraphicsSceneHoverEvent* e) { if (d->showOnHover && !isVisible()) { qreal distance; RegionFrameItem* const item = closestItem(e->scenePos(), &distance); // There's a possible nuisance when the direct mouse way from hovering pos to HUD widget // is not part of the condition. Maybe, we should add a exemption for this case. if (distance < 25) { setVisibleItem(item); } else { // get all items close to pos QList hotItems = d->hotItems(e->scenePos()); // this will be the one item shown by mouse over QList visible = d->visibilityController->visibleItems(ItemVisibilityController::ExcludeFadingOut); foreach (QGraphicsItem* const item, hotItems) { foreach (QObject* const parent, visible) { if (static_cast(parent)->isAncestorOf(item)) { return; } } } setVisibleItem(0); } } } void FaceGroup::itemHoverLeaveEvent(QGraphicsSceneHoverEvent*) { } void FaceGroup::itemHoverEnterEvent(QGraphicsSceneHoverEvent*) { } void FaceGroup::leaveEvent(QEvent*) { if (d->showOnHover && !isVisible()) { setVisibleItem(0); } } void FaceGroup::enterEvent(QEvent*) { } FaceItem* FaceGroup::Private::createItem(const FaceTagsIface& face) { FaceItem* const item = new FaceItem(view->previewItem()); item->setFace(face); - item->setOriginalRect(face.region().toRect()); + + QRect faceRect = face.region().toRect(); + + if (MetaEngineSettings::instance()->settings().exifRotate) + { + QSize imgSize = info.dimensions(); + + switch (info.orientation()) + { + case MetaEngine::ORIENTATION_HFLIP: + faceRect = TagRegion::ajustToFlippedImg(faceRect, imgSize, 0); + break; + + case MetaEngine::ORIENTATION_ROT_180: + faceRect = TagRegion::ajustToFlippedImg(faceRect, imgSize, 0); + faceRect = TagRegion::ajustToFlippedImg(faceRect, imgSize, 1); + break; + + case MetaEngine::ORIENTATION_VFLIP: + faceRect = TagRegion::ajustToFlippedImg(faceRect, imgSize, 1); + break; + + case MetaEngine::ORIENTATION_ROT_90_HFLIP: + faceRect = TagRegion::ajustToRotatedImg(faceRect, imgSize, 0); + imgSize.transpose(); + faceRect = TagRegion::ajustToFlippedImg(faceRect, imgSize, 0); + break; + + case MetaEngine::ORIENTATION_ROT_90: + faceRect = TagRegion::ajustToRotatedImg(faceRect, imgSize, 0); + break; + + case MetaEngine::ORIENTATION_ROT_90_VFLIP: + faceRect = TagRegion::ajustToRotatedImg(faceRect, imgSize, 0); + imgSize.transpose(); + faceRect = TagRegion::ajustToFlippedImg(faceRect, imgSize, 1); + break; + + case MetaEngine::ORIENTATION_ROT_270: + faceRect = TagRegion::ajustToRotatedImg(faceRect, imgSize, 1); + break; + + default: + break; + } + } + + item->setOriginalRect(faceRect); item->setVisible(false); return item; } FaceItem* FaceGroup::Private::addItem(const FaceTagsIface& face) { FaceItem* const item = createItem(face); // for identification, use index in our list AssignNameWidget* const assignWidget = createAssignNameWidget(face, items.size()); item->setHudWidget(assignWidget); //new StyleSheetDebugger(assignWidget); visibilityController->addItem(item); items << item; return item; } void FaceGroup::Private::checkModels() { if (!tagModel) { tagModel = new TagModel(AbstractAlbumModel::IgnoreRootAlbum, q); } if (!filterModel) { filterModel = new CheckableAlbumFilterModel(q); } if (!filteredModel) { filteredModel = new TagPropertiesFilterModel(q); } } AssignNameWidget::Mode FaceGroup::Private::assignWidgetMode(FaceTagsIface::Type type) { switch (type) { case FaceTagsIface::UnknownName: case FaceTagsIface::UnconfirmedName: return AssignNameWidget::UnconfirmedEditMode; case FaceTagsIface::ConfirmedName: return AssignNameWidget::ConfirmedMode; default: return AssignNameWidget::InvalidMode; } } AssignNameWidget* FaceGroup::Private::createAssignNameWidget(const FaceTagsIface& face, const QVariant& identifier) { AssignNameWidget* const assignWidget = new AssignNameWidget(view); assignWidget->setMode(assignWidgetMode(face.type())); assignWidget->setTagEntryWidgetMode(AssignNameWidget::AddTagsComboBoxMode); assignWidget->setVisualStyle(AssignNameWidget::TranslucentDarkRound); assignWidget->setLayoutMode(AssignNameWidget::TwoLines); assignWidget->setUserData(info, identifier); checkModels(); assignWidget->setModel(tagModel, filteredModel, filterModel); assignWidget->setParentTag(AlbumManager::instance()->findTAlbum(FaceTags::personParentTag())); q->connect(assignWidget, SIGNAL(assigned(TaggingAction,ItemInfo,QVariant)), q, SLOT(slotAssigned(TaggingAction,ItemInfo,QVariant))); q->connect(assignWidget, SIGNAL(rejected(ItemInfo,QVariant)), q, SLOT(slotRejected(ItemInfo,QVariant))); q->connect(assignWidget, SIGNAL(labelClicked(ItemInfo,QVariant)), q, SLOT(slotLabelClicked(ItemInfo,QVariant))); return assignWidget; } void FaceGroup::load() { if (d->state != NoFaces) { return; } d->state = LoadingFaces; if (d->info.isNull()) { d->state = FacesLoaded; return; } QList faces = FaceTagsEditor().databaseFaces(d->info.id()); d->visibilityController->clear(); foreach (const FaceTagsIface& face, faces) { d->addItem(face); } d->state = FacesLoaded; if (d->view->previewItem()->isLoaded()) { d->visibilityController->show(); } } void FaceGroup::clear() { cancelAddItem(); d->visibilityController->clear(); foreach (RegionFrameItem* const item, d->items) { delete item; } d->items.clear(); d->state = NoFaces; } void FaceGroup::rejectAll() { foreach (FaceItem* const item, d->items) { d->editPipeline.remove(d->info, item->face()); item->setFace(FaceTagsIface()); d->visibilityController->hideAndRemoveItem(item); } clear(); } void FaceGroup::slotAlbumsUpdated(int type) { if (type != Album::TAG) { return; } if (d->items.isEmpty()) { return; } clear(); load(); } void FaceGroup::slotAlbumRenamed(Album* album) { if (!album || album->type() != Album::TAG) { return; } foreach (FaceItem* const item, d->items) { if (!item->face().isNull() && item->face().tagId() == album->id()) { item->updateCurrentTag(); } } } void FaceGroup::slotAssigned(const TaggingAction& action, const ItemInfo&, const QVariant& faceIdentifier) { FaceItem* const item = d->items[faceIdentifier.toInt()]; FaceTagsIface face = item->face(); - TagRegion currentRegion = TagRegion(item->originalRect()); + QRect faceRect = convertItemRectToFaceRect(item->originalRect()); + TagRegion currentRegion = TagRegion(faceRect); if (!face.isConfirmedName() || face.region() != currentRegion || action.shallCreateNewTag() || (action.shallAssignTag() && action.tagId() != face.tagId())) { int tagId = 0; if (action.shallAssignTag()) { tagId = action.tagId(); } else if (action.shallCreateNewTag()) { tagId = FaceTags::getOrCreateTagForPerson(action.newTagName(), action.parentTagId()); } if (FaceTags::isTheUnknownPerson(tagId)) { qCDebug(DIGIKAM_GENERAL_LOG) << "Refusing to assign the unknown person to an image"; return; } if (!tagId) { qCDebug(DIGIKAM_GENERAL_LOG) << "Failed to get person tag"; return; } if (tagId) { face = d->editPipeline.confirm(d->info, face, d->view->previewItem()->image(), tagId, currentRegion); } } item->setFace(face); item->switchMode(AssignNameWidget::ConfirmedMode); } void FaceGroup::slotRejected(const ItemInfo&, const QVariant& faceIdentifier) { FaceItem* const item = d->items[faceIdentifier.toInt()]; d->editPipeline.remove(d->info, item->face()); item->setFace(FaceTagsIface()); d->visibilityController->hideAndRemoveItem(item); } void FaceGroup::slotLabelClicked(const ItemInfo&, const QVariant& faceIdentifier) { FaceItem* const item = d->items[faceIdentifier.toInt()]; item->switchMode(AssignNameWidget::ConfirmedEditMode); } void FaceGroup::startAutoSuggest() { if (!d->autoSuggest) { return; } } void FaceGroup::addFace() { if (d->manuallyAddWrapItem) { return; } d->manuallyAddWrapItem = new ClickDragReleaseItem(d->view->previewItem()); d->manuallyAddWrapItem->setFocus(); d->view->setFocus(); connect(d->manuallyAddWrapItem, SIGNAL(started(QPointF)), this, SLOT(slotAddItemStarted(QPointF))); connect(d->manuallyAddWrapItem, SIGNAL(moving(QRectF)), this, SLOT(slotAddItemMoving(QRectF))); connect(d->manuallyAddWrapItem, SIGNAL(finished(QRectF)), this, SLOT(slotAddItemFinished(QRectF))); connect(d->manuallyAddWrapItem, SIGNAL(cancelled()), this, SLOT(cancelAddItem())); } void FaceGroup::slotAddItemStarted(const QPointF& pos) { Q_UNUSED(pos); } void FaceGroup::slotAddItemMoving(const QRectF& rect) { if (!d->manuallyAddedItem) { d->manuallyAddedItem = d->createItem(FaceTagsIface()); d->visibilityController->addItem(d->manuallyAddedItem); d->visibilityController->showItem(d->manuallyAddedItem); } d->manuallyAddedItem->setRectInSceneCoordinatesAdjusted(rect); } void FaceGroup::slotAddItemFinished(const QRectF& rect) { if (d->manuallyAddedItem) { d->manuallyAddedItem->setRectInSceneCoordinatesAdjusted(rect); - FaceTagsIface face = d->editPipeline.addManually(d->info, d->view->previewItem()->image(), - TagRegion(d->manuallyAddedItem->originalRect())); + QRect faceRect = convertItemRectToFaceRect(d->manuallyAddedItem->originalRect()); + FaceTagsIface face = d->editPipeline.addManually(d->info, d->view->previewItem()->image(), + TagRegion(faceRect)); FaceItem* const item = d->addItem(face); d->visibilityController->setItemDirectlyVisible(item, true); item->switchMode(AssignNameWidget::UnconfirmedEditMode); d->manuallyAddWrapItem->stackBefore(item); } cancelAddItem(); } void FaceGroup::cancelAddItem() { delete d->manuallyAddedItem; d->manuallyAddedItem = 0; if (d->manuallyAddWrapItem) { d->view->scene()->removeItem(d->manuallyAddWrapItem); d->manuallyAddWrapItem->deleteLater(); d->manuallyAddWrapItem = 0; } } void FaceGroup::applyItemGeometryChanges() { foreach (FaceItem* const item, d->items) { if (item->face().isNull()) { continue; } TagRegion currentRegion = TagRegion(item->originalRect()); if (item->face().region() != currentRegion) { d->editPipeline.editRegion(d->info, d->view->previewItem()->image(), item->face(), currentRegion); } } } +QRect FaceGroup::convertItemRectToFaceRect(const QRect& rect) const +{ + QRect faceRect = rect; + + if (MetaEngineSettings::instance()->settings().exifRotate) + { + QSize imgSize = d->info.dimensions(); + + switch (d->info.orientation()) + { + case MetaEngine::ORIENTATION_HFLIP: + faceRect = TagRegion::ajustToFlippedImg(faceRect, imgSize, 0); + break; + + case MetaEngine::ORIENTATION_ROT_180: + faceRect = TagRegion::ajustToFlippedImg(faceRect, imgSize, 1); + faceRect = TagRegion::ajustToFlippedImg(faceRect, imgSize, 0); + break; + + case MetaEngine::ORIENTATION_VFLIP: + faceRect = TagRegion::ajustToFlippedImg(faceRect, imgSize, 1); + break; + + case MetaEngine::ORIENTATION_ROT_90_HFLIP: + imgSize.transpose(); + faceRect = TagRegion::ajustToFlippedImg(faceRect, imgSize, 0); + faceRect = TagRegion::ajustToRotatedImg(faceRect, imgSize, 1); + break; + + case MetaEngine::ORIENTATION_ROT_90: + imgSize.transpose(); + faceRect = TagRegion::ajustToRotatedImg(faceRect, imgSize, 1); + break; + + case MetaEngine::ORIENTATION_ROT_90_VFLIP: + imgSize.transpose(); + faceRect = TagRegion::ajustToFlippedImg(faceRect, imgSize, 1); + faceRect = TagRegion::ajustToRotatedImg(faceRect, imgSize, 1); + break; + + case MetaEngine::ORIENTATION_ROT_270: + imgSize.transpose(); + faceRect = TagRegion::ajustToRotatedImg(faceRect, imgSize, 0); + break; + + default: + break; + } + } + + return faceRect; +} + /* void ItemPreviewView::trainFaces() { QList trainList; foreach (Face f, d->currentFaces) { if (f.name() != "" && !d->faceIface->isFaceTrained(getItemInfo().id(), f.toRect(), f.name())) trainList += f; } qCDebug(DIGIKAM_GENERAL_LOG) << "Number of training faces" << trainList.size(); if (trainList.size()!=0) { d->faceIface->trainWithFaces(trainList); d->faceIface->markFacesAsTrained(getItemInfo().id(), trainList); } } void ItemPreviewView::suggestFaces() { // Assign tentative names to the face list QList recogList; foreach (Face f, d->currentFaces) { if (!d->faceIface->isFaceRecognized(getItemInfo().id(), f.toRect(), f.name()) && f.name().isEmpty()) { f.setName(d->faceIface->recognizedName(f)); d->faceIface->markFaceAsRecognized(getItemInfo().id(), f.toRect(), f.name()); // If the face wasn't recognized (too distant) don't suggest anything if (f.name().isEmpty()) continue; else recogList += f; } } qCDebug(DIGIKAM_GENERAL_LOG) << "Number of suggestions = " << recogList.size(); qCDebug(DIGIKAM_GENERAL_LOG) << "Number of faceitems = " << d->faceitems.size(); // Now find the relevant face items and suggest faces for (int i = 0 ; i < recogList.size() ; ++i) { for (int j = 0 ; j < d->faceitems.size() ; ++j) { if (recogList[i].toRect() == d->faceitems[j]->originalRect()) { qCDebug(DIGIKAM_GENERAL_LOG) << "Suggesting a name " << recogList[i].name(); d->faceitems[j]->suggest(recogList[i].name()); break; } } } } */ } // namespace Digikam diff --git a/core/utilities/facemanagement/facegroup.h b/core/utilities/facemanagement/facegroup.h index 590209f380..4d2694cdac 100644 --- a/core/utilities/facemanagement/facegroup.h +++ b/core/utilities/facemanagement/facegroup.h @@ -1,165 +1,166 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2010-09-17 * Description : Managing of face tag region items on a GraphicsDImgView * * Copyright (C) 2010-2011 by Marcel Wiesweg * * 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. * * ============================================================ */ #ifndef DIGIKAM_FACE_GROUP_H #define DIGIKAM_FACE_GROUP_H // Qt includes #include #include // Local includes #include "itemvisibilitycontroller.h" class QGraphicsSceneHoverEvent; namespace Digikam { class Album; class ItemInfo; class GraphicsDImgView; class RegionFrameItem; class TaggingAction; class FaceGroup : public QObject { Q_OBJECT Q_PROPERTY(bool visible READ isVisible WRITE setVisible) public: /** * Constructs a new face group, managing RegionFrameItems for faces * of a particular image, displayed on a GraphicsDImgView. */ explicit FaceGroup(GraphicsDImgView* const view); ~FaceGroup(); bool isVisible() const; ItemInfo info() const; bool hasVisibleItems() const; QList items() const; /** * Auto suggest applies if an image has not been scanned, * or an unknown face is registered. * In this case, a new scan will be triggered. */ void setAutoSuggest(bool doAutoSuggest); bool autoSuggest() const; /** * Even if visible() is false, show the item under the mouse cursor */ void setShowOnHover(bool show); bool showOnHover() const; /** * Returns the item in this group closest to scene position p. * If given, manhattanLength is set to the manhattan length between * p and the closest point of the returned item's bounding rectangle. * In particular, if p is inside the item's rectangle, manhattanLength is 0. */ RegionFrameItem* closestItem(const QPointF& p, qreal* const manhattanLength = 0) const; bool acceptsMouseClick(const QPointF& scenePos); void itemHoverEnterEvent(QGraphicsSceneHoverEvent* event); void itemHoverMoveEvent(QGraphicsSceneHoverEvent* event); void itemHoverLeaveEvent(QGraphicsSceneHoverEvent* event); void leaveEvent(QEvent*); void enterEvent(QEvent*); public Q_SLOTS: /** Shows or hides the frames */ void setVisible(bool visible); void setVisibleItem(RegionFrameItem* item); /** Sets the current ItemInfo */ void setInfo(const ItemInfo& info); /** Prepares to load a new info. * Closes the face group for editing. * Pass a null info if about to close. */ void aboutToSetInfo(const ItemInfo& info); void aboutToSetInfoAfterRotate(const ItemInfo& info); /** Enters a special state where by click + drag a new face can be created */ void addFace(); /** Rejects (clears) all faces on the current image */ void rejectAll(); protected: void load(); void clear(); void applyItemGeometryChanges(); protected Q_SLOTS: void itemStateChanged(int); void startAutoSuggest(); void slotAlbumsUpdated(int type); void slotAlbumRenamed(Album* album); void slotAssigned(const TaggingAction& action, const ItemInfo& info, const QVariant& faceIdentifier); void slotRejected(const ItemInfo& info, const QVariant& faceIdentifier); void slotLabelClicked(const ItemInfo& info, const QVariant& faceIdentifier); void slotAddItemStarted(const QPointF& pos); void slotAddItemMoving(const QRectF& rect); void slotAddItemFinished(const QRectF& rect); void cancelAddItem(); private: FaceGroup(); // Disable + QRect convertItemRectToFaceRect(const QRect& rect) const; class Private; Private* const d; }; // --------------------------------------------------------------------------------- class FaceItem; class AssignNameWidgetHidingStateChanger : public HidingStateChanger { Q_OBJECT public: explicit AssignNameWidgetHidingStateChanger(FaceItem* const item); protected Q_SLOTS: void slotStateChanged(); }; } // namespace Digikam #endif // DIGIKAM_FACE_GROUP_H