1 /* ============================================================
2 *
3 * This file is a part of digiKam project
4 * https://www.digikam.org
5 *
6 * Date : 2011-01-24
7 * Description : Tags Action Manager
8 *
9 * Copyright (C) 2011-2021 by Gilles Caulier <caulier dot gilles at gmail dot com>
10 *
11 * This program is free software; you can redistribute it
12 * and/or modify it under the terms of the GNU General
13 * Public License as published by the Free Software Foundation;
14 * either version 2, or (at your option)
15 * any later version.
16 *
17 * This program is distributed in the hope that it will be useful,
18 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 * GNU General Public License for more details.
21 *
22 * ============================================================ */
23
24 #include "tagsactionmngr.h"
25
26 // Qt includes
27
28 #include <QList>
29 #include <QShortcut>
30 #include <QIcon>
31 #include <QKeySequence>
32 #include <QApplication>
33 #include <QAction>
34
35 // KDE includes
36
37 #include <klocalizedstring.h>
38 #include <kactioncollection.h>
39
40 // Local includes
41
42 #include "digikam_debug.h"
43 #include "album.h"
44 #include "coredb.h"
45 #include "albummanager.h"
46 #include "coredbaccess.h"
47 #include "coredbconstants.h"
48 #include "coredbwatch.h"
49 #include "coredbinfocontainers.h"
50 #include "digikamapp.h"
51 #include "dxmlguiwindow.h"
52 #include "itemiconview.h"
53 #include "imagewindow.h"
54 #include "lighttablewindow.h"
55 #include "picklabelwidget.h"
56 #include "colorlabelwidget.h"
57 #include "tagscache.h"
58 #include "tagproperties.h"
59 #include "ratingwidget.h"
60 #include "syncjob.h"
61
62 namespace Digikam
63 {
64
65 TagsActionMngr* TagsActionMngr::m_defaultManager = nullptr;
66
defaultManager()67 TagsActionMngr* TagsActionMngr::defaultManager()
68 {
69 return m_defaultManager;
70 }
71
72 class Q_DECL_HIDDEN TagsActionMngr::Private
73 {
74 public:
75
Private()76 explicit Private()
77 : ratingShortcutPrefix(QLatin1String("rateshortcut")),
78 tagShortcutPrefix (QLatin1String("tagshortcut")),
79 pickShortcutPrefix (QLatin1String("pickshortcut")),
80 colorShortcutPrefix (QLatin1String("colorshortcut"))
81 {
82 }
83
84 QMultiMap<int, QAction*> tagsActionMap;
85 QList<KActionCollection*> actionCollectionList;
86
87 const QString ratingShortcutPrefix;
88 const QString tagShortcutPrefix;
89 const QString pickShortcutPrefix;
90 const QString colorShortcutPrefix;
91 };
92
93 // -------------------------------------------------------------------------------------------------
94
TagsActionMngr(QWidget * const parent)95 TagsActionMngr::TagsActionMngr(QWidget* const parent)
96 : QObject(parent),
97 d (new Private)
98 {
99 if (!m_defaultManager)
100 {
101 m_defaultManager = this;
102 }
103
104 connect(AlbumManager::instance(), SIGNAL(signalAlbumDeleted(Album*)),
105 this, SLOT(slotAlbumDeleted(Album*)));
106 }
107
~TagsActionMngr()108 TagsActionMngr::~TagsActionMngr()
109 {
110 delete d;
111
112 if (m_defaultManager == this)
113 {
114 m_defaultManager = nullptr;
115 }
116 }
117
ratingShortcutPrefix() const118 QString TagsActionMngr::ratingShortcutPrefix() const
119 {
120 return d->ratingShortcutPrefix;
121 }
122
tagShortcutPrefix() const123 QString TagsActionMngr::tagShortcutPrefix() const
124 {
125 return d->tagShortcutPrefix;
126 }
127
pickShortcutPrefix() const128 QString TagsActionMngr::pickShortcutPrefix() const
129 {
130 return d->pickShortcutPrefix;
131 }
132
colorShortcutPrefix() const133 QString TagsActionMngr::colorShortcutPrefix() const
134 {
135 return d->colorShortcutPrefix;
136 }
137
registerTagsActionCollections()138 void TagsActionMngr::registerTagsActionCollections()
139 {
140 d->actionCollectionList.append(DigikamApp::instance()->actionCollection());
141 d->actionCollectionList.append(ImageWindow::imageWindow()->actionCollection());
142 d->actionCollectionList.append(LightTableWindow::lightTableWindow()->actionCollection());
143
144 // Create Tags shortcuts.
145
146 QList<int> tagIds = TagsCache::instance()->tagsWithProperty(TagPropertyName::tagKeyboardShortcut());
147
148 foreach (int tagId, tagIds)
149 {
150 createTagActionShortcut(tagId);
151 }
152 }
153
actionCollections() const154 QList<KActionCollection*> TagsActionMngr::actionCollections() const
155 {
156 return d->actionCollectionList;
157 }
158
registerLabelsActions(KActionCollection * const ac)159 void TagsActionMngr::registerLabelsActions(KActionCollection* const ac)
160 {
161 // Create Rating shortcuts.
162
163 for (int i = RatingMin ; i <= RatingMax ; ++i)
164 {
165 createRatingActionShortcut(ac, i);
166 }
167
168 // Create Color Label shortcuts.
169
170 for (int i = NoColorLabel ; i <= WhiteLabel ; ++i)
171 {
172 createColorLabelActionShortcut(ac, i);
173 }
174
175 // Create Pick Label shortcuts.
176
177 for (int i = NoPickLabel ; i <= AcceptedLabel ; ++i)
178 {
179 createPickLabelActionShortcut(ac, i);
180 }
181 }
182
registerActionsToWidget(QWidget * const wdg)183 void TagsActionMngr::registerActionsToWidget(QWidget* const wdg)
184 {
185 DXmlGuiWindow* const win = dynamic_cast<DXmlGuiWindow*>(qApp->activeWindow());
186
187 if (win)
188 {
189 foreach (QAction* const ac, win->actionCollection()->actions())
190 {
191 if (ac->objectName().startsWith(d->ratingShortcutPrefix) ||
192 ac->objectName().startsWith(d->tagShortcutPrefix) ||
193 ac->objectName().startsWith(d->pickShortcutPrefix) ||
194 ac->objectName().startsWith(d->colorShortcutPrefix))
195 {
196 wdg->addAction(ac);
197 }
198 }
199 }
200 }
201
createRatingActionShortcut(KActionCollection * const ac,int rating)202 bool TagsActionMngr::createRatingActionShortcut(KActionCollection* const ac, int rating)
203 {
204 if (ac)
205 {
206 QAction* const action = ac->addAction(QString::fromUtf8("%1-%2").arg(d->ratingShortcutPrefix).arg(rating));
207 action->setText(i18n("Assign Rating \"%1 Star\"", rating));
208 ac->setDefaultShortcut(action, QKeySequence(QString::fromUtf8("CTRL+%1").arg(rating)));
209 action->setIcon(RatingWidget::buildIcon(rating, 32));
210 action->setData(rating);
211
212 connect(action, SIGNAL(triggered()),
213 this, SLOT(slotAssignFromShortcut()));
214
215 return true;
216 }
217
218 return false;
219 }
220
createPickLabelActionShortcut(KActionCollection * const ac,int pickId)221 bool TagsActionMngr::createPickLabelActionShortcut(KActionCollection* const ac, int pickId)
222 {
223 if (ac)
224 {
225 QAction* const action = ac->addAction(QString::fromUtf8("%1-%2").arg(d->pickShortcutPrefix).arg(pickId));
226 action->setText(i18n("Assign Pick Label \"%1\"", PickLabelWidget::labelPickName((PickLabel)pickId)));
227 ac->setDefaultShortcut(action, QKeySequence(QString::fromUtf8("ALT+%1").arg(pickId)));
228 action->setIcon(PickLabelWidget::buildIcon((PickLabel)pickId));
229 action->setData(pickId);
230
231 connect(action, SIGNAL(triggered()),
232 this, SLOT(slotAssignFromShortcut()));
233
234 return true;
235 }
236
237 return false;
238 }
239
createColorLabelActionShortcut(KActionCollection * const ac,int colorId)240 bool TagsActionMngr::createColorLabelActionShortcut(KActionCollection* const ac, int colorId)
241 {
242 if (ac)
243 {
244 QAction* const action = ac->addAction(QString::fromUtf8("%1-%2").arg(d->colorShortcutPrefix).arg(colorId));
245 action->setText(i18n("Assign Color Label \"%1\"", ColorLabelWidget::labelColorName((ColorLabel)colorId)));
246 ac->setDefaultShortcut(action, QKeySequence(QString::fromUtf8("ALT+CTRL+%1").arg(colorId)));
247 action->setIcon(ColorLabelWidget::buildIcon((ColorLabel)colorId, 32));
248 action->setData(colorId);
249
250 connect(action, SIGNAL(triggered()),
251 this, SLOT(slotAssignFromShortcut()));
252
253 return true;
254 }
255
256 return false;
257 }
258
createTagActionShortcut(int tagId)259 bool TagsActionMngr::createTagActionShortcut(int tagId)
260 {
261 if (!tagId)
262 {
263 return false;
264 }
265
266 TAlbum* const talbum = AlbumManager::instance()->findTAlbum(tagId);
267
268 if (!talbum)
269 {
270 return false;
271 }
272
273 QString value = TagsCache::instance()->propertyValue(tagId, TagPropertyName::tagKeyboardShortcut());
274
275 if (value.isEmpty())
276 {
277 return false;
278 }
279
280 QKeySequence ks(value);
281
282 // FIXME: tag icons can be files on disk, or system icon names. Only the latter will work here.
283
284 QIcon icon(SyncJob::getTagThumbnail(talbum));
285
286 qCDebug(DIGIKAM_GENERAL_LOG) << "Create Shortcut " << ks.toString()
287 << " to Tag " << talbum->title()
288 << " (" << tagId << ")";
289
290 foreach (KActionCollection* const ac, d->actionCollectionList)
291 {
292 QAction* const action = ac->addAction(QString::fromUtf8("%1-%2").arg(d->tagShortcutPrefix).arg(tagId));
293 action->setText(i18n("Assign Tag \"%1\"", talbum->title()));
294 action->setParent(this);
295 ac->setDefaultShortcut(action, ks);
296 action->setIcon(icon);
297 action->setData(tagId);
298
299 connect(action, SIGNAL(triggered()),
300 this, SLOT(slotAssignFromShortcut()));
301
302 connect(action, SIGNAL(changed()),
303 this, SLOT(slotTagActionChanged()));
304
305 d->tagsActionMap.insert(tagId, action);
306 }
307
308 return true;
309 }
310
slotTagActionChanged()311 void TagsActionMngr::slotTagActionChanged()
312 {
313 QAction* const action = dynamic_cast<QAction*>(sender());
314
315 if (!action)
316 {
317 return;
318 }
319
320 int tagId = action->data().toInt();
321
322 QKeySequence ks;
323 QStringList lst = action->shortcut().toString().split(QLatin1Char(','));
324
325 if (!lst.isEmpty())
326 {
327 ks = QKeySequence(lst.first());
328 }
329
330 updateTagShortcut(tagId, ks, false);
331 }
332
updateTagShortcut(int tagId,const QKeySequence & ks,bool delAction)333 void TagsActionMngr::updateTagShortcut(int tagId, const QKeySequence& ks, bool delAction)
334 {
335 if (!tagId)
336 {
337 return;
338 }
339
340 qCDebug(DIGIKAM_GENERAL_LOG) << "Tag Shortcut " << tagId << "Changed to " << ks;
341
342 QString value = TagsCache::instance()->propertyValue(tagId, TagPropertyName::tagKeyboardShortcut());
343
344 if (value == ks.toString())
345 {
346 return;
347 }
348
349 TagProperties tprop(tagId);
350
351 if (ks.isEmpty())
352 {
353 removeTagActionShortcut(tagId, delAction);
354 tprop.removeProperties(TagPropertyName::tagKeyboardShortcut());
355 }
356 else
357 {
358 removeTagActionShortcut(tagId, delAction);
359 tprop.setProperty(TagPropertyName::tagKeyboardShortcut(), ks.toString());
360 createTagActionShortcut(tagId);
361 }
362 }
363
slotAlbumDeleted(Album * album)364 void TagsActionMngr::slotAlbumDeleted(Album* album)
365 {
366 TAlbum* const talbum = dynamic_cast<TAlbum*>(album);
367
368 if (!talbum)
369 {
370 return;
371 }
372
373 removeTagActionShortcut(talbum->id());
374 qCDebug(DIGIKAM_GENERAL_LOG) << "Delete Shortcut assigned to tag " << album->id();
375 }
376
removeTagActionShortcut(int tagId,bool delAction)377 bool TagsActionMngr::removeTagActionShortcut(int tagId, bool delAction)
378 {
379 if (!d->tagsActionMap.contains(tagId))
380 {
381 return false;
382 }
383
384 foreach (QAction* const act, d->tagsActionMap.values(tagId))
385 {
386 if (act)
387 {
388 KActionCollection* const ac = dynamic_cast<KActionCollection*>(act->parent());
389
390 if (ac)
391 {
392 ac->takeAction(act);
393 }
394
395 if (delAction)
396 {
397 delete act;
398 }
399 }
400 }
401
402 d->tagsActionMap.remove(tagId);
403
404 return true;
405 }
406
slotAssignFromShortcut()407 void TagsActionMngr::slotAssignFromShortcut()
408 {
409 QAction* const action = dynamic_cast<QAction*>(sender());
410
411 if (!action)
412 {
413 return;
414 }
415
416 int val = action->data().toInt();
417 qCDebug(DIGIKAM_GENERAL_LOG) << "Shortcut value: " << val;
418
419 QWidget* const w = qApp->activeWindow();
420 DigikamApp* const dkw = dynamic_cast<DigikamApp*>(w);
421
422 if (dkw)
423 {
424 //qCDebug(DIGIKAM_GENERAL_LOG) << "Handling by DigikamApp";
425
426 if (action->objectName().startsWith(d->ratingShortcutPrefix))
427 {
428 dkw->view()->slotAssignRating(val);
429 }
430 else if (action->objectName().startsWith(d->pickShortcutPrefix))
431 {
432 dkw->view()->slotAssignPickLabel(val);
433 }
434 else if (action->objectName().startsWith(d->colorShortcutPrefix))
435 {
436 dkw->view()->slotAssignColorLabel(val);
437 }
438 else if (action->objectName().startsWith(d->tagShortcutPrefix))
439 {
440 dkw->view()->toggleTag(val);
441 }
442
443 return;
444 }
445
446 ImageWindow* const imw = dynamic_cast<ImageWindow*>(w);
447
448 if (imw)
449 {
450 //qCDebug(DIGIKAM_GENERAL_LOG) << "Handling by ImageWindow";
451
452 if (action->objectName().startsWith(d->ratingShortcutPrefix))
453 {
454 imw->slotAssignRating(val);
455 }
456 else if (action->objectName().startsWith(d->pickShortcutPrefix))
457 {
458 imw->slotAssignPickLabel(val);
459 }
460 else if (action->objectName().startsWith(d->colorShortcutPrefix))
461 {
462 imw->slotAssignColorLabel(val);
463 }
464 else if (action->objectName().startsWith(d->tagShortcutPrefix))
465 {
466 imw->toggleTag(val);
467 }
468
469 return;
470 }
471
472 LightTableWindow* const ltw = dynamic_cast<LightTableWindow*>(w);
473
474 if (ltw)
475 {
476 //qCDebug(DIGIKAM_GENERAL_LOG) << "Handling by LightTableWindow";
477
478 if (action->objectName().startsWith(d->ratingShortcutPrefix))
479 {
480 ltw->slotAssignRating(val);
481 }
482 else if (action->objectName().startsWith(d->pickShortcutPrefix))
483 {
484 ltw->slotAssignPickLabel(val);
485 }
486 else if (action->objectName().startsWith(d->colorShortcutPrefix))
487 {
488 ltw->slotAssignColorLabel(val);
489 }
490 else if (action->objectName().startsWith(d->tagShortcutPrefix))
491 {
492 ltw->toggleTag(val);
493 }
494
495 return;
496 }
497
498 // emit signal to DInfoInterface to broadcast to another component:
499
500 emit signalShortcutPressed(action->objectName(), val);
501 }
502
503 } // namespace Digikam
504