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