1 /* ============================================================
2 *
3 * This file is a part of digiKam project
4 * https://www.digikam.org
5 *
6 * Date        : 2010-10-14
7 * Description : overlay for assigning names to faces
8 *
9 * Copyright (C) 2010      by Aditya Bhatt <caulier dot gilles at gmail dot com>
10 * Copyright (C) 2009-2010 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
11 * Copyright (C) 2009-2021 by Gilles Caulier <caulier dot gilles at gmail dot com>
12 * Copyright (C) 2008      by Peter Penz <peter dot penz at gmx dot at>
13 *
14 * This program is free software; you can redistribute it
15 * and/or modify it under the terms of the GNU General
16 * Public License as published by the Free Software Foundation;
17 * either version 2, or (at your option)
18 * any later version.
19 *
20 * This program is distributed in the hope that it will be useful,
21 * but WITHOUT ANY WARRANTY; without even the implied warranty of
22 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
23 * GNU General Public License for more details.
24 *
25 * ============================================================ */
26 
27 #include "assignnameoverlay.h"
28 
29 // Qt includes
30 
31 #include <QApplication>
32 #include <QPushButton>
33 
34 // Local includes
35 
36 #include "dlayoutbox.h"
37 #include "digikam_debug.h"
38 #include "addtagslineedit.h"
39 #include "albummodel.h"
40 #include "albumfiltermodel.h"
41 #include "assignnamewidget.h"
42 #include "facetagsiface.h"
43 #include "facepipeline.h"
44 #include "facetags.h"
45 #include "itemdelegate.h"
46 #include "itemmodel.h"
47 #include "itemcategorizedview.h"
48 #include "taggingaction.h"
49 #include "tagscache.h"
50 #include "searchutilities.h"
51 #include "applicationsettings.h"
52 
53 namespace Digikam
54 {
55 
56 class Q_DECL_HIDDEN AssignNameOverlay::Private
57 {
58 public:
59 
Private()60     explicit Private()
61         : tagModel        (AbstractAlbumModel::IgnoreRootAlbum),
62           assignNameWidget(nullptr)
63     {
64     }
65 
isChildWidget(QWidget * widget,QWidget * const parent) const66     bool isChildWidget(QWidget* widget, QWidget* const parent) const
67     {
68         if (!parent)
69         {
70             return false;
71         }
72 
73         // isAncestorOf may not work if widgets are located in different windows
74 
75         while (widget)
76         {
77             if (widget == parent)
78             {
79                 return true;
80             }
81 
82             widget = widget->parentWidget();
83         }
84 
85         return false;
86     }
87 
88 public:
89 
90     TagModel                  tagModel;
91     CheckableAlbumFilterModel filterModel;
92     TagPropertiesFilterModel  filteredModel;
93 
94     AssignNameWidget*         assignNameWidget;
95 };
96 
AssignNameOverlay(QObject * const parent)97 AssignNameOverlay::AssignNameOverlay(QObject* const parent)
98     : PersistentWidgetDelegateOverlay(parent),
99       d                              (new Private)
100 {
101     d->filteredModel.setSourceAlbumModel(&d->tagModel);
102     d->filterModel.setSourceFilterModel(&d->filteredModel);
103 
104     // Restrict the tag properties filter model to people if configured.
105 
106     ApplicationSettings* const settings = ApplicationSettings::instance();
107 
108     if (settings)
109     {
110         if (settings->showOnlyPersonTagsInPeopleSidebar())
111         {
112             d->filteredModel.listOnlyTagsWithProperty(TagPropertyName::person());
113         }
114     }
115 }
116 
~AssignNameOverlay()117 AssignNameOverlay::~AssignNameOverlay()
118 {
119     delete d;
120 }
121 
assignNameWidget() const122 AssignNameWidget* AssignNameOverlay::assignNameWidget() const
123 {
124     return d->assignNameWidget;
125 }
126 
createWidget()127 QWidget* AssignNameOverlay::createWidget()
128 {
129     DVBox* const vbox    = new DVBox(parentWidget());
130     QWidget* const space = new QWidget(vbox);
131     d->assignNameWidget  = new AssignNameWidget(vbox);
132     d->assignNameWidget->setMode(AssignNameWidget::UnconfirmedEditMode);
133     d->assignNameWidget->setVisualStyle(AssignNameWidget::TranslucentThemedFrameless);
134     d->assignNameWidget->setTagEntryWidgetMode(AssignNameWidget::AddTagsLineEditMode);
135     d->assignNameWidget->setLayoutMode(AssignNameWidget::Compact);
136     d->assignNameWidget->setModel(&d->tagModel, &d->filteredModel, &d->filterModel);
137     d->assignNameWidget->lineEdit()->installEventFilter(this);
138 
139     vbox->setStretchFactor(space, 4);
140 /*
141     new StyleSheetDebugger(d->assignNameWidget);
142 */
143     return vbox;
144 }
145 
setActive(bool active)146 void AssignNameOverlay::setActive(bool active)
147 {
148     PersistentWidgetDelegateOverlay::setActive(active);
149 
150     if (active)
151     {
152         connect(assignNameWidget(), SIGNAL(assigned(TaggingAction,ItemInfo,QVariant)),
153                 this, SLOT(slotAssigned(TaggingAction,ItemInfo,QVariant)));
154 
155         connect(assignNameWidget(), SIGNAL(rejected(ItemInfo,QVariant)),
156                 this, SLOT(slotRejected(ItemInfo,QVariant)));
157 
158         connect(assignNameWidget(), SIGNAL(ignoredClicked(ItemInfo,QVariant)),
159                 this, SLOT(slotUnknown(ItemInfo,QVariant)));
160 
161         connect(assignNameWidget(), SIGNAL(selected(TaggingAction,ItemInfo,QVariant)),
162                 this, SLOT(enterPersistentMode()));
163 
164         connect(assignNameWidget(), SIGNAL(assigned(TaggingAction,ItemInfo,QVariant)),
165                 this, SLOT(leavePersistentMode()));
166 
167         connect(assignNameWidget(), SIGNAL(rejected(ItemInfo,QVariant)),
168                 this, SLOT(leavePersistentMode()));
169 
170         connect(assignNameWidget(), SIGNAL(ignoredClicked(ItemInfo,QVariant)),
171                 this, SLOT(leavePersistentMode()));
172 
173         connect(assignNameWidget(), SIGNAL(assigned(TaggingAction,ItemInfo,QVariant)),
174                 this, SLOT(storeFocus()));
175 
176         connect(assignNameWidget(), SIGNAL(rejected(ItemInfo,QVariant)),
177                 this, SLOT(storeFocus()));
178 
179         connect(assignNameWidget(), SIGNAL(ignoredClicked(ItemInfo,QVariant)),
180                 this, SLOT(storeFocus()));
181 
182 /*
183         if (view()->model())
184         {
185             connect(view()->model(), SIGNAL(dataChanged(QModelIndex,QModelIndex)),
186                     this, SLOT(slotDataChanged(QModelIndex,QModelIndex)));
187         }
188 */
189     }
190     else
191     {
192         // widget is deleted
193 
194 /*
195         if (view() && view()->model())
196         {
197             disconnect(view()->model(), 0, this, 0);
198         }
199 */
200     }
201 }
202 
visualChange()203 void AssignNameOverlay::visualChange()
204 {
205     if (m_widget && m_widget->isVisible())
206     {
207         updatePosition();
208     }
209 }
210 
hide()211 void AssignNameOverlay::hide()
212 {
213     PersistentWidgetDelegateOverlay::hide();
214 }
215 
updatePosition()216 void AssignNameOverlay::updatePosition()
217 {
218     if (!index().isValid())
219     {
220         return;
221     }
222 
223     // See bug #365667.
224     // Use information view below pixmap.
225     // Depending of icon-view item options enabled in setup, the free space to use can be different.
226     // We can continue to show the widget behind bottom of thumbnail view.
227 
228     QRect rect = delegate()->imageInformationRect();
229     rect.setTop(delegate()->pixmapRect().top());
230 
231     if (rect.width() < m_widget->minimumSizeHint().width())
232     {
233         int offset = (m_widget->minimumSizeHint().width() - rect.width()) / 2;
234         rect.adjust(-offset, 0, offset, 0);
235     }
236 
237     QRect visualRect = m_view->visualRect(index());
238     rect.translate(visualRect.topLeft());
239 
240     m_widget->setFixedSize(rect.width(), rect.height());
241     m_widget->move(rect.topLeft());
242 }
243 
updateFace()244 void AssignNameOverlay::updateFace()
245 {
246     if (!index().isValid() || !assignNameWidget())
247     {
248         return;
249     }
250 
251     QVariant extraData = index().data(ItemModel::ExtraDataRole);
252 
253     /**
254      * The order to plug these functions is important, since
255      * setUserData() controls how the Overlay appears on
256      * a particular face.
257      */
258     assignNameWidget()->setUserData(ItemModel::retrieveItemInfo(index()), extraData);
259     assignNameWidget()->setCurrentFace(FaceTagsIface::fromVariant(extraData));
260 }
261 
262 /*
263 void AssignNameOverlay::slotDataChanged(const QModelIndex& / *topLeft* /, const QModelIndex& / *bottomRight* /)
264 {
265     if (m_widget && m_widget->isVisible() && QItemSelectionRange(topLeft, bottomRight).contains(index()))
266         updateTag();
267 }
268 */
269 
checkIndex(const QModelIndex & index) const270 bool AssignNameOverlay::checkIndex(const QModelIndex& index) const
271 {
272     if (!index.isValid())
273     {
274         return false;
275     }
276 
277     QVariant extraData = index.data(ItemModel::ExtraDataRole);
278 
279     if (extraData.isNull())
280     {
281         return false;
282     }
283 
284     return (FaceTagsIface::fromVariant(extraData).isIgnoredName() ||
285             FaceTagsIface::fromVariant(extraData).isUnconfirmedType());
286 }
287 
showOnIndex(const QModelIndex & index)288 void AssignNameOverlay::showOnIndex(const QModelIndex& index)
289 {
290     PersistentWidgetDelegateOverlay::showOnIndex(index);
291 
292 /*
293     // TODO: add again when fading in
294     // see bug 228810, this is a small workaround
295 
296     if (m_widget && m_widget->isVisible() && index().isValid() && (index == index()))
297     {
298         addTagsLineEdit()->setVisibleImmediately;
299     }
300 */
301 
302     updatePosition();
303     updateFace();
304 }
305 
viewportLeaveEvent(QObject * o,QEvent * e)306 void AssignNameOverlay::viewportLeaveEvent(QObject* o, QEvent* e)
307 {
308     if (isPersistent() && m_widget->isVisible())
309     {
310         return;
311     }
312 
313     // Do not hide when hovering the pop-up of the line edit.
314 
315     if (d->isChildWidget(qApp->widgetAt(QCursor::pos()), assignNameWidget()))
316     {
317         return;
318     }
319 
320     PersistentWidgetDelegateOverlay::viewportLeaveEvent(o, e);
321 }
322 
slotAssigned(const TaggingAction & action,const ItemInfo & info,const QVariant & faceIdentifier)323 void AssignNameOverlay::slotAssigned(const TaggingAction& action, const ItemInfo& info, const QVariant& faceIdentifier)
324 {
325     Q_UNUSED(info);
326     FaceTagsIface face = FaceTagsIface::fromVariant(faceIdentifier);
327 
328     //qCDebug(DIGIKAM_GENERAL_LOG) << "Confirming" << face << action.shallAssignTag() << action.tagId();
329 
330     if (face.isConfirmedName() || !action.isValid())
331     {
332         return;
333     }
334 
335     int tagId = 0;
336 
337     if      (action.shallAssignTag())
338     {
339         tagId = action.tagId();
340     }
341     else if (action.shallCreateNewTag())
342     {
343         tagId = FaceTags::getOrCreateTagForPerson(action.newTagName(), -1);
344     }
345 
346     if (tagId)
347     {
348         emit confirmFaces(affectedIndexes(index()), tagId);
349     }
350 
351     hide();
352 }
353 
slotRejected(const ItemInfo & info,const QVariant & faceIdentifier)354 void AssignNameOverlay::slotRejected(const ItemInfo& info, const QVariant& faceIdentifier)
355 {
356     Q_UNUSED(info);
357     Q_UNUSED(faceIdentifier);
358 
359     emit removeFaces(affectedIndexes(index()));
360     hide();
361 }
362 
slotUnknown(const ItemInfo & info,const QVariant & faceIdentifier)363 void AssignNameOverlay::slotUnknown(const ItemInfo& info, const QVariant& faceIdentifier)
364 {
365     Q_UNUSED(info);
366     Q_UNUSED(faceIdentifier);
367 
368     emit unknownFaces(affectedIndexes(index()));
369     hide();
370 }
371 
widgetEnterEvent()372 void AssignNameOverlay::widgetEnterEvent()
373 {
374     widgetEnterNotifyMultiple(index());
375 }
376 
widgetLeaveEvent()377 void AssignNameOverlay::widgetLeaveEvent()
378 {
379     widgetLeaveNotifyMultiple();
380 }
381 
setFocusOnWidget()382 void AssignNameOverlay::setFocusOnWidget()
383 {
384     if (assignNameWidget()->lineEdit())
385     {
386         assignNameWidget()->lineEdit()->selectAll();
387         assignNameWidget()->lineEdit()->setFocus();
388     }
389 }
390 
eventFilter(QObject * o,QEvent * e)391 bool AssignNameOverlay::eventFilter(QObject* o, QEvent* e)
392 {
393     switch (e->type())
394     {
395         case QEvent::MouseButtonPress:
396         {
397             enterPersistentMode();
398             break;
399         }
400 
401         case QEvent::FocusOut:
402         {
403             if (!d->isChildWidget(QApplication::focusWidget(), assignNameWidget()))
404             {
405                 leavePersistentMode();
406             }
407             break;
408         }
409 
410         default:
411             break;
412     }
413 
414     return PersistentWidgetDelegateOverlay::eventFilter(o, e);
415 }
416 
417 } // namespace Digikam
418