1 /* ============================================================
2 *
3 * This file is a part of digiKam project
4 * https://www.digikam.org
5 *
6 * Date : 2003-03-09
7 * Description : Captions, Tags, and Rating properties editor
8 *
9 * Copyright (C) 2003-2005 by Renchi Raju <renchi dot raju at gmail dot com>
10 * Copyright (C) 2003-2021 by Gilles Caulier <caulier dot gilles at gmail dot com>
11 * Copyright (C) 2006-2011 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
12 * Copyright (C) 2009-2011 by Andi Clemens <andi dot clemens at gmail dot com>
13 * Copyright (C) 2009-2011 by Johannes Wienke <languitar at semipol dot de>
14 * Copyright (C) 2015 by Veaceslav Munteanu <veaceslav dot munteanu90 at gmail dot com>
15 *
16 * This program is free software; you can redistribute it
17 * and/or modify it under the terms of the GNU General
18 * Public License as published by the Free Software Foundation;
19 * either version 2, or (at your option)
20 * any later version.
21 *
22 * This program is distributed in the hope that it will be useful,
23 * but WITHOUT ANY WARRANTY; without even the implied warranty of
24 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
25 * GNU General Public License for more details.
26 *
27 * ============================================================ */
28
29 #include "itemdescedittab.h"
30
31 // Qt includes
32
33 #include <QTextEdit>
34 #include <QStyle>
35 #include <QGridLayout>
36 #include <QScrollArea>
37 #include <QTimer>
38 #include <QToolButton>
39 #include <QApplication>
40 #include <QPushButton>
41 #include <QMenu>
42 #include <QIcon>
43 #include <QCheckBox>
44 #include <QMessageBox>
45 #include <QPointer>
46
47 // KDE includes
48
49 #include <kconfiggroup.h>
50 #include <klocalizedstring.h>
51
52 // Local includes
53
54 #include "digikam_debug.h"
55 #include "addtagslineedit.h"
56 #include "applicationsettings.h"
57 #include "albumthumbnailloader.h"
58 #include "captionedit.h"
59 #include "collectionscanner.h"
60 #include "coredbtransaction.h"
61 #include "dnotificationwrapper.h"
62 #include "ddatetimeedit.h"
63 #include "digikamapp.h"
64 #include "fileactionmngr.h"
65 #include "ratingwidget.h"
66 #include "scancontroller.h"
67 #include "tagcheckview.h"
68 #include "templateselector.h"
69 #include "templateviewer.h"
70 #include "itemattributeswatch.h"
71 #include "statusprogressbar.h"
72 #include "tagmodificationhelper.h"
73 #include "template.h"
74 #include "iteminfolist.h"
75 #include "iteminfo.h"
76 #include "colorlabelwidget.h"
77 #include "picklabelwidget.h"
78 #include "fileactionprogress.h"
79 #include "tagsmanager.h"
80 #include "tagtreeview.h"
81 #include "searchtextbardb.h"
82 #include "disjointmetadata.h"
83 #include "altlangstredit.h"
84
85 namespace Digikam
86 {
87
88 class Q_DECL_HIDDEN ItemDescEditTab::Private
89 {
90
91 public:
92
93 enum DescEditTab
94 {
95 DESCRIPTIONS = 0,
96 TAGS,
97 INFOS
98 };
99
Private()100 explicit Private()
101 : modified (false),
102 ignoreItemAttributesWatch (false),
103 ignoreTagChanges (false),
104 togglingSearchSettings (false),
105 recentTagsBtn (nullptr),
106 assignedTagsBtn (nullptr),
107 revertBtn (nullptr),
108 openTagMngr (nullptr),
109 moreMenu (nullptr),
110 applyBtn (nullptr),
111 moreButton (nullptr),
112 applyToAllVersionsButton (nullptr),
113 lastSelectedWidget (nullptr),
114 titleEdit (nullptr),
115 captionsEdit (nullptr),
116 dateTimeEdit (nullptr),
117 tabWidget (nullptr),
118 tagsSearchBar (nullptr),
119 newTagEdit (nullptr),
120 tagCheckView (nullptr),
121 templateSelector (nullptr),
122 templateViewer (nullptr),
123 ratingWidget (nullptr),
124 colorLabelSelector (nullptr),
125 pickLabelSelector (nullptr),
126 tagModel (nullptr),
127 metadataChangeTimer (nullptr)
128 {
129 }
130
131 bool modified;
132 bool ignoreItemAttributesWatch;
133 bool ignoreTagChanges;
134 bool togglingSearchSettings;
135
136 QToolButton* recentTagsBtn;
137 QToolButton* assignedTagsBtn;
138 QToolButton* revertBtn;
139 QPushButton* openTagMngr;
140
141 QMenu* moreMenu;
142
143 QPushButton* applyBtn;
144 QPushButton* moreButton;
145 QPushButton* applyToAllVersionsButton;
146
147 QWidget* lastSelectedWidget;
148
149 AltLangStrEdit* titleEdit;
150
151 CaptionEdit* captionsEdit;
152
153 DDateTimeEdit* dateTimeEdit;
154
155 QTabWidget* tabWidget;
156
157 SearchTextBarDb* tagsSearchBar;
158 AddTagsLineEdit* newTagEdit;
159
160 ItemInfoList currInfos;
161
162 TagCheckView* tagCheckView;
163
164 TemplateSelector* templateSelector;
165 TemplateViewer* templateViewer;
166
167 RatingWidget* ratingWidget;
168 ColorLabelSelector* colorLabelSelector;
169 PickLabelSelector* pickLabelSelector;
170
171 DisjointMetadata hub;
172
173 TagModel* tagModel;
174
175 QTimer* metadataChangeTimer;
176 QList<int> metadataChangeIds;
177 };
178
ItemDescEditTab(QWidget * const parent)179 ItemDescEditTab::ItemDescEditTab(QWidget* const parent)
180 : DVBox(parent),
181 d (new Private)
182 {
183 const int spacing = QApplication::style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing);
184
185 setContentsMargins(QMargins());
186 setSpacing(spacing);
187 d->tabWidget = new QTabWidget(this);
188
189 d->metadataChangeTimer = new QTimer(this);
190 d->metadataChangeTimer->setSingleShot(true);
191 d->metadataChangeTimer->setInterval(250);
192
193 // Captions/Date/Rating view -----------------------------------
194
195 QScrollArea* const sv = new QScrollArea(d->tabWidget);
196 sv->setFrameStyle(QFrame::NoFrame);
197 sv->setWidgetResizable(true);
198
199 QWidget* const captionTagsArea = new QWidget(sv->viewport());
200 QGridLayout* const grid1 = new QGridLayout(captionTagsArea);
201 sv->setWidget(captionTagsArea);
202
203 d->titleEdit = new AltLangStrEdit(captionTagsArea);
204 d->titleEdit->setTitle(i18nc("@title: comment title string", "Title:"));
205 d->titleEdit->setPlaceholderText(i18n("Enter title here."));
206
207 d->captionsEdit = new CaptionEdit(captionTagsArea);
208
209 DHBox* const dateBox = new DHBox(captionTagsArea);
210 new QLabel(i18n("Date:"), dateBox);
211 d->dateTimeEdit = new DDateTimeEdit(dateBox, QLatin1String("datepicker"));
212
213 DHBox* const pickBox = new DHBox(captionTagsArea);
214 new QLabel(i18n("Pick Label:"), pickBox);
215 d->pickLabelSelector = new PickLabelSelector(pickBox);
216 pickBox->layout()->setAlignment(d->pickLabelSelector, Qt::AlignVCenter | Qt::AlignRight);
217
218 DHBox* const colorBox = new DHBox(captionTagsArea);
219 new QLabel(i18n("Color Label:"), colorBox);
220 d->colorLabelSelector = new ColorLabelSelector(colorBox);
221 colorBox->layout()->setAlignment(d->colorLabelSelector, Qt::AlignVCenter | Qt::AlignRight);
222
223 DHBox* const rateBox = new DHBox(captionTagsArea);
224 new QLabel(i18n("Rating:"), rateBox);
225 d->ratingWidget = new RatingWidget(rateBox);
226 rateBox->layout()->setAlignment(d->ratingWidget, Qt::AlignVCenter | Qt::AlignRight);
227
228 // Buttons -----------------------------------------
229
230 DHBox* const applyButtonBox = new DHBox(this);
231 applyButtonBox->setSpacing(spacing);
232
233 d->applyBtn = new QPushButton(i18n("Apply"), applyButtonBox);
234 d->applyBtn->setIcon(QIcon::fromTheme(QLatin1String("dialog-ok-apply")));
235 d->applyBtn->setEnabled(false);
236 d->applyBtn->setToolTip(i18n("Apply all changes to images"));
237 //buttonsBox->setStretchFactor(d->applyBtn, 10);
238
239 DHBox* const buttonsBox = new DHBox(this);
240 buttonsBox->setSpacing(spacing);
241
242 d->revertBtn = new QToolButton(buttonsBox);
243 d->revertBtn->setIcon(QIcon::fromTheme(QLatin1String("document-revert")));
244 d->revertBtn->setToolTip(i18n("Revert all changes"));
245 d->revertBtn->setEnabled(false);
246
247 d->applyToAllVersionsButton = new QPushButton(i18n("Apply to all versions"), buttonsBox);
248 d->applyToAllVersionsButton->setIcon(QIcon::fromTheme(QLatin1String("dialog-ok-apply")));
249 d->applyToAllVersionsButton->setEnabled(false);
250 d->applyToAllVersionsButton->setToolTip(i18n("Apply all changes to all versions of this image"));
251
252 d->moreButton = new QPushButton(i18nc("@action: more actions to apply changes", "More"), buttonsBox);
253 d->moreMenu = new QMenu(captionTagsArea);
254 d->moreButton->setMenu(d->moreMenu);
255
256 // --------------------------------------------------
257
258 grid1->addWidget(d->titleEdit, 0, 0, 1, 2);
259 grid1->addWidget(d->captionsEdit, 1, 0, 1, 2);
260 grid1->addWidget(dateBox, 2, 0, 1, 2);
261 grid1->addWidget(pickBox, 3, 0, 1, 2);
262 grid1->addWidget(colorBox, 4, 0, 1, 2);
263 grid1->addWidget(rateBox, 5, 0, 1, 2);
264 grid1->setRowStretch(1, 10);
265 grid1->setContentsMargins(spacing, spacing, spacing, spacing);
266 grid1->setSpacing(spacing);
267
268 d->tabWidget->insertTab(Private::DESCRIPTIONS, sv, i18n("Description"));
269
270 // Tags view ---------------------------------------------------
271
272 QScrollArea* const sv3 = new QScrollArea(d->tabWidget);
273 sv3->setFrameStyle(QFrame::NoFrame);
274 sv3->setWidgetResizable(true);
275
276 QWidget* const tagsArea = new QWidget(sv3->viewport());
277 QGridLayout* const grid3 = new QGridLayout(tagsArea);
278 sv3->setWidget(tagsArea);
279
280 d->tagModel = new TagModel(AbstractAlbumModel::IncludeRootAlbum, this);
281 d->tagModel->setCheckable(true);
282 d->tagModel->setRootCheckable(false);
283 d->tagCheckView = new TagCheckView(tagsArea, d->tagModel);
284 d->tagCheckView->setCheckNewTags(true);
285
286 d->openTagMngr = new QPushButton(i18n("Open Tag Manager"));
287
288 d->newTagEdit = new AddTagsLineEdit(tagsArea);
289 d->newTagEdit->setSupportingTagModel(d->tagModel);
290 d->newTagEdit->setTagTreeView(d->tagCheckView);
291 //, "ItemDescEditTabNewTagEdit",
292 //d->newTagEdit->setCaseSensitive(false);
293 d->newTagEdit->setPlaceholderText(i18n("Enter tag here."));
294 d->newTagEdit->setWhatsThis(i18n("Enter the text used to create tags here. "
295 "'/' can be used to create a hierarchy of tags. "
296 "',' can be used to create more than one hierarchy at the same time."));
297
298 DHBox* const tagsSearch = new DHBox(tagsArea);
299 tagsSearch->setSpacing(spacing);
300
301 d->tagsSearchBar = new SearchTextBarDb(tagsSearch, QLatin1String("ItemDescEditTabTagsSearchBar"));
302
303 d->tagsSearchBar->setModel(d->tagCheckView->filteredModel(),
304 AbstractAlbumModel::AlbumIdRole,
305 AbstractAlbumModel::AlbumTitleRole);
306
307 d->tagsSearchBar->setFilterModel(d->tagCheckView->albumFilterModel());
308
309 d->assignedTagsBtn = new QToolButton(tagsSearch);
310 d->assignedTagsBtn->setToolTip(i18n("Tags already assigned"));
311 d->assignedTagsBtn->setIcon(QIcon::fromTheme(QLatin1String("tag-assigned")));
312 d->assignedTagsBtn->setCheckable(true);
313
314 d->recentTagsBtn = new QToolButton(tagsSearch);
315 QMenu* const recentTagsMenu = new QMenu(d->recentTagsBtn);
316 d->recentTagsBtn->setToolTip(i18n("Recent Tags"));
317 d->recentTagsBtn->setIcon(QIcon::fromTheme(QLatin1String("tag-recents")));
318 d->recentTagsBtn->setIconSize(QSize(16, 16));
319 d->recentTagsBtn->setMenu(recentTagsMenu);
320 d->recentTagsBtn->setPopupMode(QToolButton::InstantPopup);
321
322 grid3->addWidget(d->openTagMngr, 0, 0, 1, 2);
323 grid3->addWidget(d->newTagEdit, 1, 0, 1, 2);
324 grid3->addWidget(d->tagCheckView, 2, 0, 1, 2);
325 grid3->addWidget(tagsSearch, 3, 0, 1, 2);
326 grid3->setRowStretch(1, 10);
327
328 d->tabWidget->insertTab(Private::TAGS, sv3, i18n("Tags"));
329
330 // Information Management View --------------------------------------
331
332 QScrollArea* const sv2 = new QScrollArea(d->tabWidget);
333 sv2->setFrameStyle(QFrame::NoFrame);
334 sv2->setWidgetResizable(true);
335
336 QWidget* const infoArea = new QWidget(sv2->viewport());
337 QGridLayout* const grid2 = new QGridLayout(infoArea);
338 sv2->setWidget(infoArea);
339
340 d->templateSelector = new TemplateSelector(infoArea);
341 d->templateViewer = new TemplateViewer(infoArea);
342 d->templateViewer->setObjectName(QLatin1String("ItemDescEditTab Expander"));
343
344 grid2->addWidget(d->templateSelector, 0, 0, 1, 2);
345 grid2->addWidget(d->templateViewer, 1, 0, 1, 2);
346 grid2->setRowStretch(1, 10);
347 grid2->setContentsMargins(spacing, spacing, spacing, spacing);
348 grid2->setSpacing(spacing);
349
350 d->tabWidget->insertTab(Private::INFOS, sv2, i18n("Information"));
351
352 // --------------------------------------------------
353
354 connect(d->openTagMngr, SIGNAL(clicked()),
355 this, SLOT(slotOpenTagsManager()));
356
357 connect(d->tagCheckView->checkableModel(), SIGNAL(checkStateChanged(Album*,Qt::CheckState)),
358 this, SLOT(slotTagStateChanged(Album*,Qt::CheckState)));
359
360 connect(d->titleEdit, SIGNAL(signalModified(QString,QString)),
361 this, SLOT(slotTitleChanged()));
362
363 connect(d->titleEdit, SIGNAL(signalValueAdded(QString,QString)),
364 this, SLOT(slotTitleChanged()));
365
366 connect(d->titleEdit, SIGNAL(signalValueDeleted(QString)),
367 this, SLOT(slotTitleChanged()));
368
369 connect(d->captionsEdit, SIGNAL(signalModified()),
370 this, SLOT(slotCommentChanged()));
371
372 connect(d->dateTimeEdit, SIGNAL(dateTimeChanged(QDateTime)),
373 this, SLOT(slotDateTimeChanged(QDateTime)));
374
375 connect(d->pickLabelSelector, SIGNAL(signalPickLabelChanged(int)),
376 this, SLOT(slotPickLabelChanged(int)));
377
378 connect(d->colorLabelSelector, SIGNAL(signalColorLabelChanged(int)),
379 this, SLOT(slotColorLabelChanged(int)));
380
381 connect(d->ratingWidget, SIGNAL(signalRatingChanged(int)),
382 this, SLOT(slotRatingChanged(int)));
383
384 connect(d->templateSelector, SIGNAL(signalTemplateSelected()),
385 this, SLOT(slotTemplateSelected()));
386
387 connect(d->tagsSearchBar, SIGNAL(signalSearchTextSettings(SearchTextSettings)),
388 this, SLOT(slotTagsSearchChanged(SearchTextSettings)));
389
390 connect(d->assignedTagsBtn, SIGNAL(toggled(bool)),
391 this, SLOT(slotAssignedTagsToggled(bool)));
392
393 connect(d->newTagEdit, SIGNAL(taggingActionActivated(TaggingAction)),
394 this, SLOT(slotTaggingActionActivated(TaggingAction)));
395
396 connect(d->applyBtn, SIGNAL(clicked()),
397 this, SLOT(slotApplyAllChanges()));
398
399 connect(d->applyToAllVersionsButton, SIGNAL(clicked()),
400 this, SLOT(slotApplyChangesToAllVersions()));
401
402 connect(d->revertBtn, SIGNAL(clicked()),
403 this, SLOT(slotRevertAllChanges()));
404
405 connect(d->moreMenu, SIGNAL(aboutToShow()),
406 this, SLOT(slotMoreMenu()));
407
408 connect(d->metadataChangeTimer, SIGNAL(timeout()),
409 this, SLOT(slotReloadForMetadataChange()));
410
411 connect(this, SIGNAL(askToApplyChanges(QList<ItemInfo>,DisjointMetadata*)),
412 this, SLOT(slotAskToApplyChanges(QList<ItemInfo>,DisjointMetadata*)),
413 Qt::QueuedConnection);
414
415 // Initialize ---------------------------------------------
416
417 d->titleEdit->textEdit()->installEventFilter(this);
418 d->captionsEdit->textEdit()->installEventFilter(this);
419
420 d->dateTimeEdit->installEventFilter(this);
421 d->pickLabelSelector->installEventFilter(this);
422 d->colorLabelSelector->installEventFilter(this);
423 d->ratingWidget->installEventFilter(this);
424
425 // TODO update, what does this filter?
426
427 d->tagCheckView->installEventFilter(this);
428 d->newTagEdit->installEventFilter(this);
429 updateRecentTags();
430
431 // Connect to attribute watch ------------------------------
432
433 ItemAttributesWatch* const watch = ItemAttributesWatch::instance();
434
435 connect(watch, SIGNAL(signalImageTagsChanged(qlonglong)),
436 this, SLOT(slotImageTagsChanged(qlonglong)));
437
438 connect(watch, SIGNAL(signalImagesChanged(int)),
439 this, SLOT(slotImagesChanged(int)));
440
441 connect(watch, SIGNAL(signalImageRatingChanged(qlonglong)),
442 this, SLOT(slotImageRatingChanged(qlonglong)));
443
444 connect(watch, SIGNAL(signalImageDateChanged(qlonglong)),
445 this, SLOT(slotImageDateChanged(qlonglong)));
446
447 connect(watch, SIGNAL(signalImageCaptionChanged(qlonglong)),
448 this, SLOT(slotImageCaptionChanged(qlonglong)));
449 }
450
~ItemDescEditTab()451 ItemDescEditTab::~ItemDescEditTab()
452 {
453 delete d;
454 }
455
readSettings(KConfigGroup & group)456 void ItemDescEditTab::readSettings(KConfigGroup& group)
457 {
458 d->tabWidget->setCurrentIndex(group.readEntry(QLatin1String("ItemDescEdit Tab"), (int)Private::DESCRIPTIONS));
459 d->titleEdit->setCurrentLanguageCode(group.readEntry(QLatin1String("ItemDescEditTab TitleLang"), QString()));
460 d->captionsEdit->setCurrentLanguageCode(group.readEntry(QLatin1String("ItemDescEditTab CaptionsLang"), QString()));
461
462 d->templateViewer->readSettings(group);
463
464 d->tagCheckView->setConfigGroup(group);
465 d->tagCheckView->setEntryPrefix(QLatin1String("ItemDescEditTab TagCheckView"));
466 d->tagCheckView->loadState();
467 d->tagsSearchBar->setConfigGroup(group);
468 d->tagsSearchBar->setEntryPrefix(QLatin1String("ItemDescEditTab SearchBar"));
469 d->tagsSearchBar->loadState();
470 }
471
writeSettings(KConfigGroup & group)472 void ItemDescEditTab::writeSettings(KConfigGroup& group)
473 {
474 group.writeEntry(QLatin1String("ItemDescEdit Tab"), d->tabWidget->currentIndex());
475 group.writeEntry(QLatin1String("ItemDescEditTab TitleLang"), d->titleEdit->currentLanguageCode());
476 group.writeEntry(QLatin1String("ItemDescEditTab CaptionsLang"), d->captionsEdit->currentLanguageCode());
477
478 d->templateViewer->writeSettings(group);
479
480 d->tagCheckView->saveState();
481 d->tagsSearchBar->saveState();
482 }
483
setFocusToLastSelectedWidget()484 void ItemDescEditTab::setFocusToLastSelectedWidget()
485 {
486 if (d->lastSelectedWidget)
487 {
488 d->lastSelectedWidget->setFocus();
489 }
490
491 d->lastSelectedWidget = nullptr;
492 }
493
setFocusToTagsView()494 void ItemDescEditTab::setFocusToTagsView()
495 {
496 d->lastSelectedWidget = qobject_cast<QWidget*>(d->tagCheckView);
497 d->tagCheckView->setFocus();
498 d->tabWidget->setCurrentIndex(Private::TAGS);
499 }
500
setFocusToNewTagEdit()501 void ItemDescEditTab::setFocusToNewTagEdit()
502 {
503 // select "Tags" tab and focus the NewTagLineEdit widget
504
505 d->tabWidget->setCurrentIndex(Private::TAGS);
506 d->newTagEdit->setFocus();
507 }
508
setFocusToTitlesEdit()509 void ItemDescEditTab::setFocusToTitlesEdit()
510 {
511 d->tabWidget->setCurrentIndex(Private::DESCRIPTIONS);
512 d->titleEdit->textEdit()->setFocus();
513 }
514
setFocusToCommentsEdit()515 void ItemDescEditTab::setFocusToCommentsEdit()
516 {
517 d->tabWidget->setCurrentIndex(Private::DESCRIPTIONS);
518 d->captionsEdit->textEdit()->setFocus();
519 }
520
activateAssignedTagsButton()521 void ItemDescEditTab::activateAssignedTagsButton()
522 {
523 d->tabWidget->setCurrentIndex(Private::TAGS);
524 d->assignedTagsBtn->click();
525 }
526
singleSelection() const527 bool ItemDescEditTab::singleSelection() const
528 {
529 return (d->currInfos.count() == 1);
530 }
531
slotChangingItems()532 void ItemDescEditTab::slotChangingItems()
533 {
534 if (!d->modified)
535 {
536 return;
537 }
538
539 if (d->currInfos.isEmpty())
540 {
541 return;
542 }
543
544 if (!ApplicationSettings::instance()->getApplySidebarChangesDirectly())
545 {
546 // Open dialog via queued connection out-of-scope, see bug 302311
547
548 DisjointMetadata* const hub2 = new DisjointMetadata();
549 hub2->setDataFields(d->hub.dataFields());
550
551 emit askToApplyChanges(d->currInfos, hub2);
552
553 reset();
554 }
555 else
556 {
557 slotApplyAllChanges();
558 }
559 }
560
slotAskToApplyChanges(const QList<ItemInfo> & infos,DisjointMetadata * hub)561 void ItemDescEditTab::slotAskToApplyChanges(const QList<ItemInfo>& infos, DisjointMetadata* hub)
562 {
563 int changedFields = 0;
564
565 if (hub->titlesChanged())
566 {
567 ++changedFields;
568 }
569
570 if (hub->commentsChanged())
571 {
572 ++changedFields;
573 }
574
575 if (hub->dateTimeChanged())
576 {
577 ++changedFields;
578 }
579
580 if (hub->ratingChanged())
581 {
582 ++changedFields;
583 }
584
585 if (hub->pickLabelChanged())
586 {
587 ++changedFields;
588 }
589
590 if (hub->colorLabelChanged())
591 {
592 ++changedFields;
593 }
594
595 if (hub->tagsChanged())
596 {
597 ++changedFields;
598 }
599
600 QString text;
601
602 if (changedFields == 1)
603 {
604 if (hub->commentsChanged())
605 {
606 text = i18np("You have edited the image caption. ",
607 "You have edited the captions of %1 images. ",
608 infos.count());
609 }
610 else if (hub->titlesChanged())
611 {
612 text = i18np("You have edited the image title. ",
613 "You have edited the titles of %1 images. ",
614 infos.count());
615 }
616 else if (hub->dateTimeChanged())
617 {
618 text = i18np("You have edited the date of the image. ",
619 "You have edited the date of %1 images. ",
620 infos.count());
621 }
622 else if (hub->pickLabelChanged())
623 {
624 text = i18np("You have edited the pick label of the image. ",
625 "You have edited the pick label of %1 images. ",
626 infos.count());
627 }
628 else if (hub->colorLabelChanged())
629 {
630 text = i18np("You have edited the color label of the image. ",
631 "You have edited the color label of %1 images. ",
632 infos.count());
633 }
634 else if (hub->ratingChanged())
635 {
636 text = i18np("You have edited the rating of the image. ",
637 "You have edited the rating of %1 images. ",
638 infos.count());
639 }
640 else if (hub->tagsChanged())
641 {
642 text = i18np("You have edited the tags of the image. ",
643 "You have edited the tags of %1 images. ",
644 infos.count());
645 }
646
647 text += i18n("Do you want to apply your changes?");
648 }
649 else
650 {
651 text = i18np("<p>You have edited the metadata of the image: </p>",
652 "<p>You have edited the metadata of %1 images: </p>",
653 infos.count());
654
655 text += QLatin1String("<p><ul>");
656
657 if (hub->titlesChanged())
658 {
659 text += i18n("<li>title</li>");
660 }
661
662 if (hub->commentsChanged())
663 {
664 text += i18n("<li>caption</li>");
665 }
666
667 if (hub->dateTimeChanged())
668 {
669 text += i18n("<li>date</li>");
670 }
671
672 if (hub->pickLabelChanged())
673 {
674 text += i18n("<li>pick label</li>");
675 }
676
677 if (hub->colorLabelChanged())
678 {
679 text += i18n("<li>color label</li>");
680 }
681
682 if (hub->ratingChanged())
683 {
684 text += i18n("<li>rating</li>");
685 }
686
687 if (hub->tagsChanged())
688 {
689 text += i18n("<li>tags</li>");
690 }
691
692 text += QLatin1String("</ul></p>");
693
694 text += i18n("<p>Do you want to apply your changes?</p>");
695 }
696
697 QCheckBox* const alwaysCBox = new QCheckBox(i18n("Always apply changes without confirmation"));
698
699 QPointer<QMessageBox> msgBox = new QMessageBox(QMessageBox::Information,
700 i18n("Apply changes?"),
701 text,
702 QMessageBox::Yes | QMessageBox::No,
703 qApp->activeWindow());
704 msgBox->setCheckBox(alwaysCBox);
705 msgBox->setDefaultButton(QMessageBox::No);
706 msgBox->setEscapeButton(QMessageBox::No);
707
708 // Pop-up a message in desktop notification manager
709
710 DNotificationWrapper(QString(), i18n("Apply changes?"),
711 DigikamApp::instance(), DigikamApp::instance()->windowTitle());
712
713 int returnCode = msgBox->exec();
714 bool alwaysApply = msgBox->checkBox()->isChecked();
715 delete msgBox;
716
717 if (alwaysApply)
718 {
719 ApplicationSettings::instance()->setApplySidebarChangesDirectly(true);
720 }
721
722 if (returnCode == QMessageBox::No)
723 {
724 delete hub;
725 return;
726 }
727
728 // otherwise apply:
729
730 FileActionMngr::instance()->applyMetadata(infos, hub);
731 }
732
reset()733 void ItemDescEditTab::reset()
734 {
735 d->modified = false;
736 d->hub.resetChanged();
737 d->applyBtn->setEnabled(false);
738 d->revertBtn->setEnabled(false);
739 d->applyToAllVersionsButton->setEnabled(false);
740 }
741
slotApplyAllChanges()742 void ItemDescEditTab::slotApplyAllChanges()
743 {
744 if (!d->modified)
745 {
746 return;
747 }
748
749 if (d->currInfos.isEmpty())
750 {
751 return;
752 }
753
754 FileActionMngr::instance()->applyMetadata(d->currInfos, d->hub);
755 reset();
756 }
757
slotRevertAllChanges()758 void ItemDescEditTab::slotRevertAllChanges()
759 {
760 if (!d->modified)
761 {
762 return;
763 }
764
765 if (d->currInfos.isEmpty())
766 {
767 return;
768 }
769
770 setInfos(d->currInfos);
771 }
772
setItem(const ItemInfo & info)773 void ItemDescEditTab::setItem(const ItemInfo& info)
774 {
775 slotChangingItems();
776 ItemInfoList list;
777
778 if (!info.isNull())
779 {
780 list << info;
781 }
782
783 setInfos(list);
784 }
785
setItems(const ItemInfoList & infos)786 void ItemDescEditTab::setItems(const ItemInfoList& infos)
787 {
788 slotChangingItems();
789 setInfos(infos);
790 }
791
setInfos(const ItemInfoList & infos)792 void ItemDescEditTab::setInfos(const ItemInfoList& infos)
793 {
794 if (infos.isEmpty())
795 {
796 d->hub.reset();
797 d->captionsEdit->blockSignals(true);
798 d->captionsEdit->reset();
799 d->captionsEdit->blockSignals(false);
800 d->titleEdit->blockSignals(true);
801 d->titleEdit->reset();
802 d->titleEdit->blockSignals(false);
803 d->currInfos.clear();
804 resetMetadataChangeInfo();
805 setEnabled(false);
806 return;
807 }
808
809 setEnabled(true);
810 d->currInfos = infos;
811 d->modified = false;
812 resetMetadataChangeInfo();
813 d->hub.reset();
814 d->applyBtn->setEnabled(false);
815 d->revertBtn->setEnabled(false);
816
817 if (d->currInfos.count() > 1000)
818 {
819 QApplication::setOverrideCursor(Qt::WaitCursor);
820 }
821
822 // First, we read all tags of the items into the cache with one SQL query.
823 // This is faster than each item individually.
824
825 d->currInfos.loadTagIds();
826
827 foreach (const ItemInfo& info, d->currInfos)
828 {
829 d->hub.load(info);
830 }
831
832 updateComments();
833 updatePickLabel();
834 updateColorLabel();
835 updateRating();
836 updateDate();
837 updateTemplate();
838 updateTagsView();
839 updateRecentTags();
840 setFocusToLastSelectedWidget();
841
842 if (d->currInfos.count() > 1000)
843 {
844 QApplication::restoreOverrideCursor();
845 }
846 }
847
slotReadFromFileMetadataToDatabase()848 void ItemDescEditTab::slotReadFromFileMetadataToDatabase()
849 {
850 initProgressIndicator();
851
852 emit signalProgressMessageChanged(i18n("Reading metadata from files. Please wait..."));
853
854 d->ignoreItemAttributesWatch = true;
855 int i = 0;
856
857 ScanController::instance()->suspendCollectionScan();
858
859 CollectionScanner scanner;
860
861 foreach (const ItemInfo& info, d->currInfos)
862 {
863 scanner.scanFile(info, CollectionScanner::Rescan);
864
865 emit signalProgressValueChanged(i++/(float)d->currInfos.count());
866
867 qApp->processEvents();
868 }
869
870 ScanController::instance()->resumeCollectionScan();
871 d->ignoreItemAttributesWatch = false;
872
873 emit signalProgressFinished();
874
875 // reload everything
876
877 setInfos(d->currInfos);
878 }
879
slotWriteToFileMetadataFromDatabase()880 void ItemDescEditTab::slotWriteToFileMetadataFromDatabase()
881 {
882 initProgressIndicator();
883
884 emit signalProgressMessageChanged(i18n("Writing metadata to files. Please wait..."));
885
886 int i = 0;
887
888 foreach (const ItemInfo& info, d->currInfos)
889 {
890 MetadataHub fileHub;
891
892 // read in from database
893
894 fileHub.load(info);
895
896 // write out to file DMetadata
897
898 fileHub.write(info.filePath());
899
900 emit signalProgressValueChanged(i++ / (float)d->currInfos.count());
901 qApp->processEvents();
902 }
903
904 emit signalProgressFinished();
905 }
906
eventFilter(QObject * o,QEvent * e)907 bool ItemDescEditTab::eventFilter(QObject* o, QEvent* e)
908 {
909 if (e->type() == QEvent::KeyPress)
910 {
911 QKeyEvent* const k = static_cast<QKeyEvent*>(e);
912
913 if ((k->key() == Qt::Key_Enter) || (k->key() == Qt::Key_Return))
914 {
915 if (k->modifiers() == Qt::ControlModifier)
916 {
917 d->lastSelectedWidget = qobject_cast<QWidget*>(o);
918 emit signalNextItem();
919 return true;
920 }
921 else if (k->modifiers() == Qt::ShiftModifier)
922 {
923 d->lastSelectedWidget = qobject_cast<QWidget*>(o);
924 emit signalPrevItem();
925 return true;
926 }
927 }
928
929 if (k->key() == Qt::Key_PageUp)
930 {
931 d->lastSelectedWidget = qobject_cast<QWidget*>(o);
932 emit signalPrevItem();
933 return true;
934 }
935
936 if (k->key() == Qt::Key_PageDown)
937 {
938 d->lastSelectedWidget = qobject_cast<QWidget*>(o);
939 emit signalNextItem();
940 return true;
941 }
942
943 if ((d->newTagEdit == o) &&
944 !d->newTagEdit->completer()->popup()->isVisible())
945 {
946 if (k->key() == Qt::Key_Up)
947 {
948 d->lastSelectedWidget = qobject_cast<QWidget*>(o);
949 emit signalPrevItem();
950 return true;
951 }
952
953 if (k->key() == Qt::Key_Down)
954 {
955 d->lastSelectedWidget = qobject_cast<QWidget*>(o);
956 emit signalNextItem();
957 return true;
958 }
959 }
960 }
961
962 return DVBox::eventFilter(o, e);
963 }
964
populateTags()965 void ItemDescEditTab::populateTags()
966 {
967 // TODO update, this wont work... crashes
968 //KConfigGroup group;
969 //d->tagCheckView->loadViewState(group);
970 }
971
slotTagStateChanged(Album * album,Qt::CheckState checkState)972 void ItemDescEditTab::slotTagStateChanged(Album* album, Qt::CheckState checkState)
973 {
974 TAlbum* const tag = dynamic_cast<TAlbum*>(album);
975
976 if (!tag || d->ignoreTagChanges)
977 {
978 return;
979 }
980
981 switch (checkState)
982 {
983 case Qt::Checked:
984 d->hub.setTag(tag->id());
985 break;
986
987 default:
988 d->hub.setTag(tag->id(), DisjointMetadataDataFields::MetadataInvalid);
989 break;
990 }
991
992 slotModified();
993 }
994
slotCommentChanged()995 void ItemDescEditTab::slotCommentChanged()
996 {
997 d->hub.setComments(d->captionsEdit->values());
998 setMetadataWidgetStatus(d->hub.commentsStatus(), d->captionsEdit);
999 slotModified();
1000 }
1001
slotTitleChanged()1002 void ItemDescEditTab::slotTitleChanged()
1003 {
1004 CaptionsMap titles;
1005
1006 titles.fromAltLangMap(d->titleEdit->values());
1007 d->hub.setTitles(titles);
1008 setMetadataWidgetStatus(d->hub.titlesStatus(), d->titleEdit);
1009 slotModified();
1010 }
1011
slotDateTimeChanged(const QDateTime & dateTime)1012 void ItemDescEditTab::slotDateTimeChanged(const QDateTime& dateTime)
1013 {
1014 d->hub.setDateTime(dateTime);
1015 setMetadataWidgetStatus(d->hub.dateTimeStatus(), d->dateTimeEdit);
1016 slotModified();
1017 }
1018
slotTemplateSelected()1019 void ItemDescEditTab::slotTemplateSelected()
1020 {
1021 d->hub.setMetadataTemplate(d->templateSelector->getTemplate());
1022 d->templateViewer->setTemplate(d->templateSelector->getTemplate());
1023 setMetadataWidgetStatus(d->hub.templateStatus(), d->templateSelector);
1024 slotModified();
1025 }
1026
slotPickLabelChanged(int pickId)1027 void ItemDescEditTab::slotPickLabelChanged(int pickId)
1028 {
1029 d->hub.setPickLabel(pickId);
1030
1031 // no handling for MetadataDisjoint needed for pick label,
1032 // we set it to 0 when disjoint, see below
1033
1034 slotModified();
1035 }
1036
slotColorLabelChanged(int colorId)1037 void ItemDescEditTab::slotColorLabelChanged(int colorId)
1038 {
1039 d->hub.setColorLabel(colorId);
1040
1041 // no handling for MetadataDisjoint needed for color label,
1042 // we set it to 0 when disjoint, see below
1043
1044 slotModified();
1045 }
1046
slotRatingChanged(int rating)1047 void ItemDescEditTab::slotRatingChanged(int rating)
1048 {
1049 d->hub.setRating(rating);
1050
1051 // no handling for MetadataDisjoint needed for rating,
1052 // we set it to 0 when disjoint, see below
1053
1054 slotModified();
1055 }
1056
slotModified()1057 void ItemDescEditTab::slotModified()
1058 {
1059 d->modified = true;
1060 d->applyBtn->setEnabled(true);
1061 d->revertBtn->setEnabled(true);
1062
1063 if (d->currInfos.size() == 1)
1064 {
1065 d->applyToAllVersionsButton->setEnabled(true);
1066 }
1067 }
1068
slotTaggingActionActivated(const TaggingAction & action)1069 void ItemDescEditTab::slotTaggingActionActivated(const TaggingAction& action)
1070 {
1071 TAlbum* assigned = nullptr;
1072
1073 if (action.shallAssignTag())
1074 {
1075 assigned = AlbumManager::instance()->findTAlbum(action.tagId());
1076
1077 if (assigned)
1078 {
1079 d->tagModel->setChecked(assigned, true);
1080 d->tagCheckView->checkableAlbumFilterModel()->updateFilter();
1081 }
1082 }
1083 else if (action.shallCreateNewTag())
1084 {
1085 TAlbum* const parent = AlbumManager::instance()->findTAlbum(action.parentTagId());
1086
1087 // tag is assigned automatically
1088
1089 assigned = d->tagCheckView->tagModificationHelper()->slotTagNew(parent, action.newTagName());
1090 }
1091
1092 if (assigned)
1093 {
1094 d->tagCheckView->scrollTo(d->tagCheckView->albumFilterModel()->indexForAlbum(assigned));
1095 QTimer::singleShot(0, d->newTagEdit, SLOT(clear()));
1096 }
1097 }
1098
assignPickLabel(int pickId)1099 void ItemDescEditTab::assignPickLabel(int pickId)
1100 {
1101 d->pickLabelSelector->setPickLabel((PickLabel)pickId);
1102 }
1103
assignColorLabel(int colorId)1104 void ItemDescEditTab::assignColorLabel(int colorId)
1105 {
1106 d->colorLabelSelector->setColorLabel((ColorLabel)colorId);
1107 }
1108
assignRating(int rating)1109 void ItemDescEditTab::assignRating(int rating)
1110 {
1111 d->ratingWidget->setRating(rating);
1112 }
1113
setTagState(TAlbum * const tag,DisjointMetadataDataFields::Status status)1114 void ItemDescEditTab::setTagState(TAlbum* const tag, DisjointMetadataDataFields::Status status)
1115 {
1116 if (!tag)
1117 {
1118 return;
1119 }
1120
1121 switch (status)
1122 {
1123 case DisjointMetadataDataFields::MetadataDisjoint:
1124 d->tagModel->setCheckState(tag, Qt::PartiallyChecked);
1125 break;
1126
1127 case DisjointMetadataDataFields::MetadataAvailable:
1128 d->tagModel->setChecked(tag, true);
1129 break;
1130
1131 case DisjointMetadataDataFields::MetadataInvalid:
1132 d->tagModel->setChecked(tag, false);
1133 break;
1134
1135 default:
1136 qCWarning(DIGIKAM_GENERAL_LOG) << "Untreated tag status enum value " << status;
1137 d->tagModel->setCheckState(tag, Qt::PartiallyChecked);
1138 break;
1139 }
1140 }
1141
updateTagsView()1142 void ItemDescEditTab::updateTagsView()
1143 {
1144 // Avoid that the automatic tag toggling handles these calls and
1145 // modification is indicated to this widget
1146
1147 TagCheckView::ToggleAutoTags toggle = d->tagCheckView->getToggleAutoTags();
1148 d->tagCheckView->setToggleAutoTags(TagCheckView::NoToggleAuto);
1149 d->ignoreTagChanges = true;
1150
1151 // first reset the tags completely
1152
1153 d->tagModel->resetAllCheckedAlbums();
1154
1155 // Then update checked state for all tags of the currently selected images
1156
1157 const QMap<int, DisjointMetadataDataFields::Status> hubMap = d->hub.tags();
1158
1159 for (QMap<int, DisjointMetadataDataFields::Status>::const_iterator it = hubMap.begin() ;
1160 it != hubMap.end() ; ++it)
1161 {
1162 TAlbum* const tag = AlbumManager::instance()->findTAlbum(it.key());
1163 setTagState(tag, it.value());
1164 }
1165
1166 d->ignoreTagChanges = false;
1167 d->tagCheckView->setToggleAutoTags(toggle);
1168
1169 // The condition is a temporary fix not to destroy name filtering on image change.
1170 // See comments in these methods.
1171
1172 if (d->assignedTagsBtn->isChecked())
1173 {
1174 slotAssignedTagsToggled(d->assignedTagsBtn->isChecked());
1175 }
1176 }
1177
updateComments()1178 void ItemDescEditTab::updateComments()
1179 {
1180 d->captionsEdit->blockSignals(true);
1181 d->captionsEdit->setValues(d->hub.comments());
1182 setMetadataWidgetStatus(d->hub.commentsStatus(), d->captionsEdit);
1183 d->captionsEdit->blockSignals(false);
1184
1185 d->titleEdit->blockSignals(true);
1186 d->titleEdit->setValues(d->hub.titles().toAltLangMap());
1187 setMetadataWidgetStatus(d->hub.titlesStatus(), d->titleEdit);
1188 d->titleEdit->blockSignals(false);
1189 }
1190
updatePickLabel()1191 void ItemDescEditTab::updatePickLabel()
1192 {
1193 d->pickLabelSelector->blockSignals(true);
1194
1195 if (d->hub.pickLabelStatus() == DisjointMetadataDataFields::MetadataDisjoint)
1196 {
1197 d->pickLabelSelector->setPickLabel(NoPickLabel);
1198 }
1199 else
1200 {
1201 d->pickLabelSelector->setPickLabel((PickLabel)d->hub.pickLabel());
1202 }
1203
1204 d->pickLabelSelector->blockSignals(false);
1205 }
1206
updateColorLabel()1207 void ItemDescEditTab::updateColorLabel()
1208 {
1209 d->colorLabelSelector->blockSignals(true);
1210
1211 if (d->hub.colorLabelStatus() == DisjointMetadataDataFields::MetadataDisjoint)
1212 {
1213 d->colorLabelSelector->setColorLabel(NoColorLabel);
1214 }
1215 else
1216 {
1217 d->colorLabelSelector->setColorLabel((ColorLabel)d->hub.colorLabel());
1218 }
1219
1220 d->colorLabelSelector->blockSignals(false);
1221 }
1222
updateRating()1223 void ItemDescEditTab::updateRating()
1224 {
1225 d->ratingWidget->blockSignals(true);
1226
1227 if (d->hub.ratingStatus() == DisjointMetadataDataFields::MetadataDisjoint)
1228 {
1229 d->ratingWidget->setRating(0);
1230 }
1231 else
1232 {
1233 d->ratingWidget->setRating(d->hub.rating());
1234 }
1235
1236 d->ratingWidget->blockSignals(false);
1237 }
1238
updateDate()1239 void ItemDescEditTab::updateDate()
1240 {
1241 d->dateTimeEdit->blockSignals(true);
1242 d->dateTimeEdit->setDateTime(d->hub.dateTime());
1243 setMetadataWidgetStatus(d->hub.dateTimeStatus(), d->dateTimeEdit);
1244 d->dateTimeEdit->blockSignals(false);
1245 }
1246
updateTemplate()1247 void ItemDescEditTab::updateTemplate()
1248 {
1249 d->templateSelector->blockSignals(true);
1250 d->templateSelector->setTemplate(d->hub.metadataTemplate());
1251 d->templateViewer->setTemplate(d->hub.metadataTemplate());
1252 setMetadataWidgetStatus(d->hub.templateStatus(), d->templateSelector);
1253 d->templateSelector->blockSignals(false);
1254 }
1255
setMetadataWidgetStatus(int status,QWidget * const widget)1256 void ItemDescEditTab::setMetadataWidgetStatus(int status, QWidget* const widget)
1257 {
1258 if (status == DisjointMetadataDataFields::MetadataDisjoint)
1259 {
1260 // For text widgets: Set text color to color of disabled text.
1261
1262 QPalette palette = widget->palette();
1263 palette.setColor(QPalette::Text, palette.color(QPalette::Disabled, QPalette::Text));
1264 widget->setPalette(palette);
1265 }
1266 else
1267 {
1268 widget->setPalette(QPalette());
1269 }
1270 }
1271
slotMoreMenu()1272 void ItemDescEditTab::slotMoreMenu()
1273 {
1274 d->moreMenu->clear();
1275
1276 if (singleSelection())
1277 {
1278 d->moreMenu->addAction(i18n("Read metadata from file to database"), this, SLOT(slotReadFromFileMetadataToDatabase()));
1279 QAction* const writeAction = d->moreMenu->addAction(i18n("Write metadata to each file"), this,
1280 SLOT(slotWriteToFileMetadataFromDatabase()));
1281
1282 // we do not need a "Write to file" action here because the apply button will do just that
1283 // if selection is a single file.
1284 // Adding the option will confuse users: Does the apply button not write to file?
1285 // Removing the option will confuse users: There is not option to write to file! (not visible in single selection)
1286 // Disabling will confuse users: Why is it disabled?
1287
1288 writeAction->setEnabled(false);
1289 }
1290 else
1291 {
1292 // We need to make clear that this action is different from the Apply button,
1293 // which saves the same changes to all files. These batch operations operate on each single file.
1294
1295 d->moreMenu->addAction(i18n("Read metadata from each file to database"), this, SLOT(slotReadFromFileMetadataToDatabase()));
1296 d->moreMenu->addAction(i18n("Write metadata to each file"), this, SLOT(slotWriteToFileMetadataFromDatabase()));
1297 }
1298 }
1299
slotOpenTagsManager()1300 void ItemDescEditTab::slotOpenTagsManager()
1301 {
1302 TagsManager* const tagMngr = TagsManager::instance();
1303 tagMngr->show();
1304 tagMngr->activateWindow();
1305 tagMngr->raise();
1306 }
1307
slotImagesChanged(int albumId)1308 void ItemDescEditTab::slotImagesChanged(int albumId)
1309 {
1310 if (d->ignoreItemAttributesWatch || d->modified)
1311 {
1312 return;
1313 }
1314
1315 Album* const a = AlbumManager::instance()->findAlbum(albumId);
1316
1317 if (d->currInfos.isEmpty() || !a || a->isRoot() || (a->type() != Album::TAG))
1318 {
1319 return;
1320 }
1321
1322 setInfos(d->currInfos);
1323 }
1324
slotImageTagsChanged(qlonglong imageId)1325 void ItemDescEditTab::slotImageTagsChanged(qlonglong imageId)
1326 {
1327 metadataChange(imageId);
1328 }
1329
slotImageRatingChanged(qlonglong imageId)1330 void ItemDescEditTab::slotImageRatingChanged(qlonglong imageId)
1331 {
1332 metadataChange(imageId);
1333 }
1334
slotImageCaptionChanged(qlonglong imageId)1335 void ItemDescEditTab::slotImageCaptionChanged(qlonglong imageId)
1336 {
1337 metadataChange(imageId);
1338 }
1339
slotImageDateChanged(qlonglong imageId)1340 void ItemDescEditTab::slotImageDateChanged(qlonglong imageId)
1341 {
1342 metadataChange(imageId);
1343 }
1344
1345 // private common code for above methods
1346
metadataChange(qlonglong imageId)1347 void ItemDescEditTab::metadataChange(qlonglong imageId)
1348 {
1349 if (d->ignoreItemAttributesWatch || d->modified)
1350 {
1351 // Don't lose modifications
1352
1353 return;
1354 }
1355
1356 d->metadataChangeIds << imageId;
1357 d->metadataChangeTimer->start();
1358 }
1359
resetMetadataChangeInfo()1360 void ItemDescEditTab::resetMetadataChangeInfo()
1361 {
1362 d->metadataChangeTimer->stop();
1363 d->metadataChangeIds.clear();
1364 }
1365
slotReloadForMetadataChange()1366 void ItemDescEditTab::slotReloadForMetadataChange()
1367 {
1368 // NOTE: What to do if d->modified? Reloading is no option.
1369 // It may be a little change the user wants to ignore, or a large conflict.
1370
1371 if (d->currInfos.isEmpty() || d->modified)
1372 {
1373 resetMetadataChangeInfo();
1374 return;
1375 }
1376
1377 if (singleSelection())
1378 {
1379 if (d->metadataChangeIds.contains(d->currInfos.first().id()))
1380 {
1381 setInfos(d->currInfos);
1382 }
1383 }
1384 else
1385 {
1386 // if image id is in our list, update
1387
1388 foreach (const ItemInfo& info, d->currInfos)
1389 {
1390 if (d->metadataChangeIds.contains(info.id()))
1391 {
1392 setInfos(d->currInfos);
1393 break;
1394 }
1395 }
1396 }
1397 }
1398
updateRecentTags()1399 void ItemDescEditTab::updateRecentTags()
1400 {
1401 QMenu* const menu = dynamic_cast<QMenu*>(d->recentTagsBtn->menu());
1402
1403 if (!menu)
1404 {
1405 return;
1406 }
1407
1408 menu->clear();
1409
1410 AlbumList recentTags = AlbumManager::instance()->getRecentlyAssignedTags();
1411
1412 if (recentTags.isEmpty())
1413 {
1414 QAction* const noTagsAction = menu->addAction(i18n("No Recently Assigned Tags"));
1415 noTagsAction->setEnabled(false);
1416 }
1417 else
1418 {
1419 for (AlbumList::const_iterator it = recentTags.constBegin() ;
1420 it != recentTags.constEnd() ; ++it)
1421 {
1422 TAlbum* const album = static_cast<TAlbum*>(*it);
1423
1424 if (album)
1425 {
1426 AlbumThumbnailLoader* const loader = AlbumThumbnailLoader::instance();
1427 QPixmap icon;
1428
1429 if (!loader->getTagThumbnail(album, icon))
1430 {
1431 if (icon.isNull())
1432 {
1433 icon = loader->getStandardTagIcon(album, AlbumThumbnailLoader::SmallerSize);
1434 }
1435 }
1436
1437 TAlbum* const parent = dynamic_cast<TAlbum*> (album->parent());
1438
1439 if (parent)
1440 {
1441 QString text = album->title() + QLatin1String(" (") + parent->prettyUrl() + QLatin1Char(')');
1442 QAction* const action = menu->addAction(icon, text);
1443 int id = album->id();
1444
1445 connect(action, &QAction::triggered,
1446 this, [this, id]() { slotRecentTagsMenuActivated(id); });
1447 }
1448 else
1449 {
1450 qCDebug(DIGIKAM_GENERAL_LOG) << "Tag" << album
1451 << "do not have a valid parent";
1452 }
1453 }
1454 }
1455 }
1456 }
1457
slotRecentTagsMenuActivated(int id)1458 void ItemDescEditTab::slotRecentTagsMenuActivated(int id)
1459 {
1460 AlbumManager* const albumMan = AlbumManager::instance();
1461
1462 if (id > 0)
1463 {
1464 TAlbum* const album = albumMan->findTAlbum(id);
1465
1466 if (album)
1467 {
1468 d->tagModel->setChecked(album, true);
1469 d->tagCheckView->checkableAlbumFilterModel()->updateFilter();
1470 }
1471 }
1472 }
1473
slotTagsSearchChanged(const SearchTextSettings & settings)1474 void ItemDescEditTab::slotTagsSearchChanged(const SearchTextSettings& settings)
1475 {
1476 Q_UNUSED(settings);
1477
1478 // if we filter, we should reset the assignedTagsBtn again.
1479
1480 if (d->assignedTagsBtn->isChecked() && !d->togglingSearchSettings)
1481 {
1482 d->togglingSearchSettings = true;
1483 d->assignedTagsBtn->setChecked(false);
1484 d->togglingSearchSettings = false;
1485 }
1486 }
1487
slotAssignedTagsToggled(bool t)1488 void ItemDescEditTab::slotAssignedTagsToggled(bool t)
1489 {
1490 d->tagCheckView->checkableAlbumFilterModel()->setFilterChecked(t);
1491 d->tagCheckView->checkableAlbumFilterModel()->setFilterPartiallyChecked(t);
1492 d->tagCheckView->checkableAlbumFilterModel()->setFilterBehavior(t ? AlbumFilterModel::StrictFiltering
1493 : AlbumFilterModel::FullFiltering);
1494
1495 if (t)
1496 {
1497 // if we filter by assigned, we should initially clear the normal search.
1498
1499 if (!d->togglingSearchSettings)
1500 {
1501 d->togglingSearchSettings = true;
1502 d->tagsSearchBar->clear();
1503 d->togglingSearchSettings = false;
1504 }
1505
1506 // Only after above change, do this.
1507
1508 d->tagCheckView->expandMatches(d->tagCheckView->rootIndex());
1509 }
1510 }
1511
slotApplyChangesToAllVersions()1512 void ItemDescEditTab::slotApplyChangesToAllVersions()
1513 {
1514 if (!d->modified)
1515 {
1516 return;
1517 }
1518
1519 if (d->currInfos.isEmpty())
1520 {
1521 return;
1522 }
1523
1524 QSet<qlonglong> tmpSet;
1525 QList<QPair<qlonglong, qlonglong> > relations;
1526
1527 foreach (const ItemInfo& info, d->currInfos)
1528 {
1529 // Collect all ids in all image's relations.
1530
1531 relations.append(info.relationCloud());
1532 }
1533
1534 if (relations.isEmpty())
1535 {
1536 slotApplyAllChanges();
1537 return;
1538 }
1539
1540 for (int i = 0 ; i < relations.size() ; ++i)
1541 {
1542 // Use QSet to prevent duplicates.
1543
1544 tmpSet.insert(relations.at(i).first);
1545 tmpSet.insert(relations.at(i).second);
1546 }
1547
1548 FileActionMngr::instance()->applyMetadata(ItemInfoList(tmpSet.values()), d->hub);
1549
1550 d->modified = false;
1551 d->hub.resetChanged();
1552 d->applyBtn->setEnabled(false);
1553 d->revertBtn->setEnabled(false);
1554 d->applyToAllVersionsButton->setEnabled(false);
1555 }
1556
initProgressIndicator()1557 void ItemDescEditTab::initProgressIndicator()
1558 {
1559 if (!ProgressManager::instance()->findItembyId(QLatin1String("ItemDescEditTabProgress")))
1560 {
1561 FileActionProgress* const item = new FileActionProgress(QLatin1String("ItemDescEditTabProgress"));
1562
1563 connect(this, SIGNAL(signalProgressMessageChanged(QString)),
1564 item, SLOT(slotProgressStatus(QString)));
1565
1566 connect(this, SIGNAL(signalProgressValueChanged(float)),
1567 item, SLOT(slotProgressValue(float)));
1568
1569 connect(this, SIGNAL(signalProgressFinished()),
1570 item, SLOT(slotCompleted()));
1571 }
1572 }
1573
getNewTagEdit() const1574 AddTagsLineEdit* ItemDescEditTab::getNewTagEdit() const
1575 {
1576 return d->newTagEdit;
1577 }
1578
1579 } // namespace Digikam
1580