File indexing completed on 2025-01-05 03:51:10

0001 /* ============================================================
0002  *
0003  * This file is a part of digiKam project
0004  * https://www.digikam.org
0005  *
0006  * Date        : 2010-10-14
0007  * Description : overlay for assigning names to faces
0008  *
0009  * SPDX-FileCopyrightText: 2010      by Aditya Bhatt <caulier dot gilles at gmail dot com>
0010  * SPDX-FileCopyrightText: 2009-2010 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
0011  * SPDX-FileCopyrightText: 2009-2024 by Gilles Caulier <caulier dot gilles at gmail dot com>
0012  * SPDX-FileCopyrightText: 2008      by Peter Penz <peter dot penz at gmx dot at>
0013  *
0014  * SPDX-License-Identifier: GPL-2.0-or-later
0015  *
0016  * ============================================================ */
0017 
0018 #include "assignnameoverlay.h"
0019 
0020 // Qt includes
0021 
0022 #include <QApplication>
0023 #include <QPushButton>
0024 
0025 // Local includes
0026 
0027 #include "dlayoutbox.h"
0028 #include "digikam_debug.h"
0029 #include "addtagslineedit.h"
0030 #include "albummodel.h"
0031 #include "albumfiltermodel.h"
0032 #include "assignnamewidget.h"
0033 #include "facetagsiface.h"
0034 #include "facepipeline.h"
0035 #include "facetags.h"
0036 #include "itemdelegate.h"
0037 #include "itemmodel.h"
0038 #include "itemcategorizedview.h"
0039 #include "taggingaction.h"
0040 #include "tagscache.h"
0041 #include "searchutilities.h"
0042 #include "applicationsettings.h"
0043 
0044 namespace Digikam
0045 {
0046 
0047 class Q_DECL_HIDDEN AssignNameOverlay::Private
0048 {
0049 public:
0050 
0051     explicit Private()
0052         : tagModel        (AbstractAlbumModel::IgnoreRootAlbum),
0053           assignNameWidget(nullptr)
0054     {
0055     }
0056 
0057     bool isChildWidget(QWidget* widget, const QWidget* const parent) const
0058     {
0059         if (!parent)
0060         {
0061             return false;
0062         }
0063 
0064         // isAncestorOf may not work if widgets are located in different windows
0065 
0066         while (widget)
0067         {
0068             if (widget == parent)
0069             {
0070                 return true;
0071             }
0072 
0073             widget = widget->parentWidget();
0074         }
0075 
0076         return false;
0077     }
0078 
0079 public:
0080 
0081     TagModel                  tagModel;
0082     CheckableAlbumFilterModel filterModel;
0083     TagPropertiesFilterModel  filteredModel;
0084 
0085     AssignNameWidget*         assignNameWidget;
0086 };
0087 
0088 AssignNameOverlay::AssignNameOverlay(QObject* const parent)
0089     : PersistentWidgetDelegateOverlay(parent),
0090       d                              (new Private)
0091 {
0092     d->filteredModel.setSourceAlbumModel(&d->tagModel);
0093     d->filterModel.setSourceFilterModel(&d->filteredModel);
0094 
0095     // Restrict the tag properties filter model to people if configured.
0096 
0097     ApplicationSettings* const settings = ApplicationSettings::instance();
0098 
0099     if (settings)
0100     {
0101         if (settings->showOnlyPersonTagsInPeopleSidebar())
0102         {
0103             d->filteredModel.listOnlyTagsWithProperty(TagPropertyName::person());
0104         }
0105     }
0106 }
0107 
0108 AssignNameOverlay::~AssignNameOverlay()
0109 {
0110     delete d;
0111 }
0112 
0113 AssignNameWidget* AssignNameOverlay::assignNameWidget() const
0114 {
0115     return d->assignNameWidget;
0116 }
0117 
0118 QWidget* AssignNameOverlay::createWidget()
0119 {
0120     DVBox* const vbox    = new DVBox(parentWidget());
0121     d->assignNameWidget  = new AssignNameWidget(vbox);
0122     d->assignNameWidget->setMode(AssignNameWidget::UnconfirmedEditMode);
0123     d->assignNameWidget->setVisualStyle(AssignNameWidget::TranslucentThemedFrameless);
0124     d->assignNameWidget->setTagEntryWidgetMode(AssignNameWidget::AddTagsLineEditMode);
0125     d->assignNameWidget->setLayoutMode(AssignNameWidget::Compact);
0126     d->assignNameWidget->setAlbumModels(&d->tagModel, &d->filteredModel, &d->filterModel);
0127     d->assignNameWidget->lineEdit()->installEventFilter(this);
0128 /*
0129     new StyleSheetDebugger(d->assignNameWidget);
0130 */
0131     return vbox;
0132 }
0133 
0134 void AssignNameOverlay::setActive(bool active)
0135 {
0136     PersistentWidgetDelegateOverlay::setActive(active);
0137 
0138     if (active)
0139     {
0140         connect(assignNameWidget(), SIGNAL(assigned(TaggingAction,ItemInfo,QVariant)),
0141                 this, SLOT(slotAssigned(TaggingAction,ItemInfo,QVariant)));
0142 
0143         connect(assignNameWidget(), SIGNAL(rejected(ItemInfo,QVariant)),
0144                 this, SLOT(slotRejected(ItemInfo,QVariant)));
0145 
0146         connect(assignNameWidget(), SIGNAL(ignored(ItemInfo,QVariant)),
0147                 this, SLOT(slotIgnored(ItemInfo,QVariant)));
0148 
0149         connect(assignNameWidget(), SIGNAL(ignoredClicked(ItemInfo,QVariant)),
0150                 this, SLOT(slotUnknown(ItemInfo,QVariant)));
0151 
0152         connect(assignNameWidget(), SIGNAL(selected(TaggingAction,ItemInfo,QVariant)),
0153                 this, SLOT(enterPersistentMode()));
0154 
0155         connect(assignNameWidget(), SIGNAL(assigned(TaggingAction,ItemInfo,QVariant)),
0156                 this, SLOT(leavePersistentMode()));
0157 
0158         connect(assignNameWidget(), SIGNAL(rejected(ItemInfo,QVariant)),
0159                 this, SLOT(leavePersistentMode()));
0160 
0161         connect(assignNameWidget(), SIGNAL(ignored(ItemInfo,QVariant)),
0162                 this, SLOT(leavePersistentMode()));
0163 
0164         connect(assignNameWidget(), SIGNAL(ignoredClicked(ItemInfo,QVariant)),
0165                 this, SLOT(leavePersistentMode()));
0166 
0167         connect(assignNameWidget(), SIGNAL(assigned(TaggingAction,ItemInfo,QVariant)),
0168                 this, SLOT(storeFocus()));
0169 
0170         connect(assignNameWidget(), SIGNAL(rejected(ItemInfo,QVariant)),
0171                 this, SLOT(storeFocus()));
0172 
0173         connect(assignNameWidget(), SIGNAL(ignoredClicked(ItemInfo,QVariant)),
0174                 this, SLOT(storeFocus()));
0175 
0176         connect(assignNameWidget(), SIGNAL(ignored(ItemInfo,QVariant)),
0177                 this, SLOT(storeFocus()));
0178 
0179 /*
0180         if (view()->model())
0181         {
0182             connect(view()->model(), SIGNAL(dataChanged(QModelIndex,QModelIndex)),
0183                     this, SLOT(slotDataChanged(QModelIndex,QModelIndex)));
0184         }
0185 */
0186     }
0187     else
0188     {
0189         // widget is deleted
0190 
0191 /*
0192         if (view() && view()->model())
0193         {
0194             disconnect(view()->model(), 0, this, 0);
0195         }
0196 */
0197     }
0198 }
0199 
0200 void AssignNameOverlay::visualChange()
0201 {
0202     if (m_widget && m_widget->isVisible())
0203     {
0204         updatePosition();
0205     }
0206 }
0207 
0208 void AssignNameOverlay::hide()
0209 {
0210     PersistentWidgetDelegateOverlay::hide();
0211 }
0212 
0213 void AssignNameOverlay::updatePosition()
0214 {
0215     if (!index().isValid())
0216     {
0217         return;
0218     }
0219 
0220     // See bug #365667.
0221     // Use information view below pixmap.
0222     // Depending of icon-view item options enabled in setup, the free space to use can be different.
0223     // We can continue to show the widget behind bottom of thumbnail view.
0224 
0225     QRect rect = delegate()->imageInformationRect();
0226     rect.setTop(delegate()->pixmapRect().top());
0227 
0228     if (rect.width() < m_widget->minimumSizeHint().width())
0229     {
0230         int offset = (m_widget->minimumSizeHint().width() - rect.width()) / 2;
0231         rect.adjust(-offset, 0, offset, 0);
0232     }
0233 
0234     int yoffset = rect.height() - m_widget->minimumSizeHint().height();
0235     rect.adjust(0, yoffset, 0, 0);
0236 
0237     QRect visualRect = m_view->visualRect(index());
0238     rect.translate(visualRect.topLeft());
0239 
0240     m_widget->setFixedSize(rect.width(), m_widget->minimumSizeHint().height());
0241     m_widget->move(rect.topLeft());
0242 }
0243 
0244 void AssignNameOverlay::updateFace()
0245 {
0246     if (!index().isValid() || !assignNameWidget())
0247     {
0248         return;
0249     }
0250 
0251     QVariant extraData = index().data(ItemModel::ExtraDataRole);
0252 
0253     /**
0254      * The order to plug these functions is important, since
0255      * setUserData() controls how the Overlay appears on
0256      * a particular face.
0257      */
0258     assignNameWidget()->setUserData(ItemModel::retrieveItemInfo(index()), extraData);
0259     assignNameWidget()->setCurrentFace(FaceTagsIface::fromVariant(extraData));
0260 }
0261 
0262 /*
0263 void AssignNameOverlay::slotDataChanged(const QModelIndex& / *topLeft* /, const QModelIndex& / *bottomRight* /)
0264 {
0265     if (m_widget && m_widget->isVisible() && QItemSelectionRange(topLeft, bottomRight).contains(index()))
0266         updateTag();
0267 }
0268 */
0269 
0270 bool AssignNameOverlay::checkIndex(const QModelIndex& index) const
0271 {
0272     if (!index.isValid())
0273     {
0274         return false;
0275     }
0276 
0277     QVariant extraData = index.data(ItemModel::ExtraDataRole);
0278 
0279     if (extraData.isNull())
0280     {
0281         return false;
0282     }
0283 
0284     return (FaceTagsIface::fromVariant(extraData).isIgnoredName() ||
0285             FaceTagsIface::fromVariant(extraData).isUnconfirmedType());
0286 }
0287 
0288 void AssignNameOverlay::showOnIndex(const QModelIndex& index)
0289 {
0290     PersistentWidgetDelegateOverlay::showOnIndex(index);
0291 
0292 /*
0293     // TODO: add again when fading in
0294     // see bug 228810, this is a small workaround
0295 
0296     if (m_widget && m_widget->isVisible() && index().isValid() && (index == index()))
0297     {
0298         addTagsLineEdit()->setVisibleImmediately;
0299     }
0300 */
0301 
0302     updatePosition();
0303     updateFace();
0304 }
0305 
0306 void AssignNameOverlay::viewportLeaveEvent(QObject* o, QEvent* e)
0307 {
0308     if (isPersistent() && m_widget->isVisible())
0309     {
0310         return;
0311     }
0312 
0313     // Do not hide when hovering the pop-up of the line edit.
0314 
0315     if (d->isChildWidget(qApp->widgetAt(QCursor::pos()), assignNameWidget()))
0316     {
0317         return;
0318     }
0319 
0320     PersistentWidgetDelegateOverlay::viewportLeaveEvent(o, e);
0321 }
0322 
0323 void AssignNameOverlay::slotAssigned(const TaggingAction& action, const ItemInfo& info, const QVariant& faceIdentifier)
0324 {
0325     Q_UNUSED(info);
0326     FaceTagsIface face = FaceTagsIface::fromVariant(faceIdentifier);
0327 
0328     //qCDebug(DIGIKAM_GENERAL_LOG) << "Confirming" << face << action.shallAssignTag() << action.tagId();
0329 
0330     if (face.isConfirmedName() || !action.isValid())
0331     {
0332         return;
0333     }
0334 
0335     int tagId = 0;
0336 
0337     if      (action.shallAssignTag())
0338     {
0339         tagId = action.tagId();
0340     }
0341     else if (action.shallCreateNewTag())
0342     {
0343         QStringList faceNames = action.newTagName().split(QLatin1Char('/'),
0344                                                           QT_SKIP_EMPTY_PARTS);
0345         if (!faceNames.isEmpty())
0346         {
0347             tagId             = -1;
0348 
0349             Q_FOREACH (const QString& name, faceNames)
0350             {
0351                 tagId = FaceTags::getOrCreateTagForPerson(name.trimmed(), tagId);
0352             }
0353         }
0354     }
0355 
0356     if (tagId)
0357     {
0358         Q_EMIT confirmFaces(affectedIndexes(index()), tagId);
0359     }
0360 
0361     hide();
0362 }
0363 
0364 void AssignNameOverlay::slotRejected(const ItemInfo& info, const QVariant& faceIdentifier)
0365 {
0366     Q_UNUSED(info);
0367     Q_UNUSED(faceIdentifier);
0368 
0369     Q_EMIT removeFaces(affectedIndexes(index()));
0370     hide();
0371 }
0372 
0373 void AssignNameOverlay::slotIgnored(const ItemInfo& info, const QVariant& faceIdentifier)
0374 {
0375     Q_UNUSED(info);
0376     Q_UNUSED(faceIdentifier);
0377 
0378     Q_EMIT ignoreFaces(affectedIndexes(index()));
0379     hide();
0380 }
0381 
0382 void AssignNameOverlay::slotUnknown(const ItemInfo& info, const QVariant& faceIdentifier)
0383 {
0384     Q_UNUSED(info);
0385     Q_UNUSED(faceIdentifier);
0386 
0387     Q_EMIT unknownFaces(affectedIndexes(index()));
0388     hide();
0389 }
0390 
0391 void AssignNameOverlay::widgetEnterEvent()
0392 {
0393     widgetEnterNotifyMultiple(index());
0394 }
0395 
0396 void AssignNameOverlay::widgetLeaveEvent()
0397 {
0398     widgetLeaveNotifyMultiple();
0399 }
0400 
0401 void AssignNameOverlay::setFocusOnWidget()
0402 {
0403     if (assignNameWidget()->lineEdit())
0404     {
0405         assignNameWidget()->lineEdit()->selectAll();
0406         assignNameWidget()->lineEdit()->setFocus();
0407     }
0408 }
0409 
0410 bool AssignNameOverlay::eventFilter(QObject* o, QEvent* e)
0411 {
0412     switch (e->type())
0413     {
0414         case QEvent::MouseButtonPress:
0415         {
0416             enterPersistentMode();
0417             break;
0418         }
0419 
0420         case QEvent::FocusOut:
0421         {
0422             if (!d->isChildWidget(QApplication::focusWidget(), assignNameWidget()))
0423             {
0424                 leavePersistentMode();
0425             }
0426             break;
0427         }
0428 
0429         default:
0430             break;
0431     }
0432 
0433     return PersistentWidgetDelegateOverlay::eventFilter(o, e);
0434 }
0435 
0436 } // namespace Digikam
0437 
0438 #include "moc_assignnameoverlay.cpp"