File indexing completed on 2025-04-27 03:57:26
0001 /* ============================================================ 0002 * 0003 * This file is a part of digiKam project 0004 * https://www.digikam.org 0005 * 0006 * Date : 2003-03-09 0007 * Description : Captions, Tags, and Rating properties editor 0008 * 0009 * SPDX-FileCopyrightText: 2003-2005 by Renchi Raju <renchi dot raju at gmail dot com> 0010 * SPDX-FileCopyrightText: 2003-2024 by Gilles Caulier <caulier dot gilles at gmail dot com> 0011 * SPDX-FileCopyrightText: 2006-2011 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de> 0012 * SPDX-FileCopyrightText: 2009-2011 by Andi Clemens <andi dot clemens at gmail dot com> 0013 * SPDX-FileCopyrightText: 2009-2011 by Johannes Wienke <languitar at semipol dot de> 0014 * SPDX-FileCopyrightText: 2015 by Veaceslav Munteanu <veaceslav dot munteanu90 at gmail dot com> 0015 * 0016 * SPDX-License-Identifier: GPL-2.0-or-later 0017 * 0018 * ============================================================ */ 0019 0020 #include "itemdescedittab_p.h" 0021 0022 namespace Digikam 0023 { 0024 0025 ItemDescEditTab::ItemDescEditTab(QWidget* const parent) 0026 : DVBox(parent), 0027 d (new Private(this)) 0028 { 0029 d->hub = new DisjointMetadata; 0030 0031 setContentsMargins(QMargins()); 0032 setSpacing(d->spacing); 0033 0034 d->tabWidget = new QTabWidget(this); 0035 0036 d->metadataChangeTimer = new QTimer(this); 0037 d->metadataChangeTimer->setSingleShot(true); 0038 d->metadataChangeTimer->setInterval(250); 0039 0040 // Captions/Date/Rating view ----------------------------------- 0041 0042 initDescriptionView(); 0043 0044 // Tags view --------------------------------------------------- 0045 0046 initTagsView(); 0047 0048 // Information Management View --------------------------------- 0049 0050 initInformationView(); 0051 0052 // Initialize -------------------------------------------------- 0053 0054 d->setupEventFilters(); 0055 0056 updateRecentTags(); 0057 0058 // Setup signals/slots connctions ------------------------------- 0059 0060 d->setupConnections(); 0061 } 0062 0063 ItemDescEditTab::~ItemDescEditTab() 0064 { 0065 delete d->hub; 0066 delete d; 0067 } 0068 0069 void ItemDescEditTab::setCurrentTab(int index) 0070 { 0071 d->tabWidget->setCurrentIndex(index); 0072 } 0073 0074 int ItemDescEditTab::currentTab() const 0075 { 0076 return d->tabWidget->currentIndex(); 0077 } 0078 0079 void ItemDescEditTab::readSettings(KConfigGroup& group) 0080 { 0081 setCurrentTab(group.readEntry(QLatin1String("ItemDescEdit Tab"), (int)DESCRIPTIONS)); 0082 d->titleEdit->setCurrentLanguageCode(group.readEntry(QLatin1String("ItemDescEditTab TitleLang"), QString())); 0083 d->captionsEdit->setCurrentLanguageCode(group.readEntry(QLatin1String("ItemDescEditTab CaptionsLang"), QString())); 0084 0085 d->templateViewer->readSettings(group); 0086 0087 d->tagCheckView->setConfigGroup(group); 0088 d->tagCheckView->setEntryPrefix(QLatin1String("ItemDescEditTab TagCheckView")); 0089 d->tagCheckView->loadState(); 0090 d->tagsSearchBar->setConfigGroup(group); 0091 d->tagsSearchBar->setEntryPrefix(QLatin1String("ItemDescEditTab SearchBar")); 0092 d->tagsSearchBar->loadState(); 0093 } 0094 0095 void ItemDescEditTab::writeSettings(KConfigGroup& group) 0096 { 0097 group.writeEntry(QLatin1String("ItemDescEdit Tab"), currentTab()); 0098 group.writeEntry(QLatin1String("ItemDescEditTab TitleLang"), d->titleEdit->currentLanguageCode()); 0099 group.writeEntry(QLatin1String("ItemDescEditTab CaptionsLang"), d->captionsEdit->currentLanguageCode()); 0100 0101 d->templateViewer->writeSettings(group); 0102 0103 d->tagCheckView->saveState(); 0104 d->tagsSearchBar->saveState(); 0105 } 0106 0107 void ItemDescEditTab::setItem(const ItemInfo& info) 0108 { 0109 slotChangingItems(); 0110 ItemInfoList list; 0111 0112 if (!info.isNull()) 0113 { 0114 list << info; 0115 } 0116 0117 d->setInfos(list); 0118 } 0119 0120 void ItemDescEditTab::setItems(const ItemInfoList& infos) 0121 { 0122 slotChangingItems(); 0123 d->setInfos(infos); 0124 } 0125 0126 bool ItemDescEditTab::eventFilter(QObject* o, QEvent* e) 0127 { 0128 if (e->type() == QEvent::KeyPress) 0129 { 0130 QKeyEvent* const k = static_cast<QKeyEvent*>(e); 0131 0132 if ((k->key() == Qt::Key_Enter) || (k->key() == Qt::Key_Return)) 0133 { 0134 if (k->modifiers() == Qt::ControlModifier) 0135 { 0136 d->lastSelectedWidget = qobject_cast<QWidget*>(o); 0137 0138 Q_EMIT signalNextItem(); 0139 0140 return true; 0141 } 0142 else if (k->modifiers() == Qt::ShiftModifier) 0143 { 0144 d->lastSelectedWidget = qobject_cast<QWidget*>(o); 0145 0146 Q_EMIT signalPrevItem(); 0147 0148 return true; 0149 } 0150 } 0151 0152 if (k->key() == Qt::Key_PageUp) 0153 { 0154 d->lastSelectedWidget = qobject_cast<QWidget*>(o); 0155 0156 Q_EMIT signalPrevItem(); 0157 0158 return true; 0159 } 0160 0161 if (k->key() == Qt::Key_PageDown) 0162 { 0163 d->lastSelectedWidget = qobject_cast<QWidget*>(o); 0164 0165 Q_EMIT signalNextItem(); 0166 0167 return true; 0168 } 0169 0170 if ((d->newTagEdit == o) && 0171 !d->newTagEdit->completer()->popup()->isVisible()) 0172 { 0173 if (k->key() == Qt::Key_Up) 0174 { 0175 d->lastSelectedWidget = qobject_cast<QWidget*>(o); 0176 0177 Q_EMIT signalPrevItem(); 0178 0179 return true; 0180 } 0181 0182 if (k->key() == Qt::Key_Down) 0183 { 0184 d->lastSelectedWidget = qobject_cast<QWidget*>(o); 0185 0186 Q_EMIT signalNextItem(); 0187 0188 return true; 0189 } 0190 } 0191 } 0192 0193 return DVBox::eventFilter(o, e); 0194 } 0195 0196 bool ItemDescEditTab::isModified() const 0197 { 0198 return d->modified; 0199 } 0200 0201 void ItemDescEditTab::slotChangingItems() 0202 { 0203 if (!d->modified) 0204 { 0205 return; 0206 } 0207 0208 if (d->currInfos.isEmpty()) 0209 { 0210 return; 0211 } 0212 0213 if (!ApplicationSettings::instance()->getApplySidebarChangesDirectly()) 0214 { 0215 // Open dialog via queued connection out-of-scope, see bug 302311 0216 0217 DisjointMetadata* const hub2 = new DisjointMetadata(); 0218 hub2->setDataFields(d->hub->dataFields()); 0219 0220 Q_EMIT signalAskToApplyChanges(d->currInfos, hub2); 0221 0222 d->reset(); 0223 } 0224 else 0225 { 0226 slotApplyAllChanges(); 0227 } 0228 } 0229 0230 void ItemDescEditTab::slotAskToApplyChanges(const QList<ItemInfo>& infos, DisjointMetadata* hub) 0231 { 0232 int changedFields = 0; 0233 0234 if (hub->titlesChanged()) 0235 { 0236 ++changedFields; 0237 } 0238 0239 if (hub->commentsChanged()) 0240 { 0241 ++changedFields; 0242 } 0243 0244 if (hub->dateTimeChanged()) 0245 { 0246 ++changedFields; 0247 } 0248 0249 if (hub->ratingChanged()) 0250 { 0251 ++changedFields; 0252 } 0253 0254 if (hub->pickLabelChanged()) 0255 { 0256 ++changedFields; 0257 } 0258 0259 if (hub->colorLabelChanged()) 0260 { 0261 ++changedFields; 0262 } 0263 0264 if (hub->tagsChanged()) 0265 { 0266 ++changedFields; 0267 } 0268 0269 QString text; 0270 0271 if (changedFields == 1) 0272 { 0273 if (hub->commentsChanged()) 0274 { 0275 text = i18ncp("@info", "You have edited the image caption. ", 0276 "You have edited the captions of %1 images. ", 0277 infos.count()); 0278 } 0279 else if (hub->titlesChanged()) 0280 { 0281 text = i18ncp("@info", "You have edited the image title. ", 0282 "You have edited the titles of %1 images. ", 0283 infos.count()); 0284 } 0285 else if (hub->dateTimeChanged()) 0286 { 0287 text = i18ncp("@info", "You have edited the date of the image. ", 0288 "You have edited the date of %1 images. ", 0289 infos.count()); 0290 } 0291 else if (hub->pickLabelChanged()) 0292 { 0293 text = i18ncp("@info", "You have edited the pick label of the image. ", 0294 "You have edited the pick label of %1 images. ", 0295 infos.count()); 0296 } 0297 else if (hub->colorLabelChanged()) 0298 { 0299 text = i18ncp("@info", "You have edited the color label of the image. ", 0300 "You have edited the color label of %1 images. ", 0301 infos.count()); 0302 } 0303 else if (hub->ratingChanged()) 0304 { 0305 text = i18ncp("@info", "You have edited the rating of the image. ", 0306 "You have edited the rating of %1 images. ", 0307 infos.count()); 0308 } 0309 else if (hub->tagsChanged()) 0310 { 0311 text = i18ncp("@info", "You have edited the tags of the image. ", 0312 "You have edited the tags of %1 images. ", 0313 infos.count()); 0314 } 0315 0316 text += i18nc("@info", "Do you want to apply your changes?"); 0317 } 0318 else 0319 { 0320 text = i18ncp("@info", "<p>You have edited the metadata of the image: </p>", 0321 "<p>You have edited the metadata of %1 images: </p>", 0322 infos.count()); 0323 0324 text += QLatin1String("<p><ul>"); 0325 0326 if (hub->titlesChanged()) 0327 { 0328 text += i18nc("@info", "<li>title</li>"); 0329 } 0330 0331 if (hub->commentsChanged()) 0332 { 0333 text += i18nc("@info", "<li>caption</li>"); 0334 } 0335 0336 if (hub->dateTimeChanged()) 0337 { 0338 text += i18nc("@info", "<li>date</li>"); 0339 } 0340 0341 if (hub->pickLabelChanged()) 0342 { 0343 text += i18nc("@info", "<li>pick label</li>"); 0344 } 0345 0346 if (hub->colorLabelChanged()) 0347 { 0348 text += i18nc("@info", "<li>color label</li>"); 0349 } 0350 0351 if (hub->ratingChanged()) 0352 { 0353 text += i18nc("@info", "<li>rating</li>"); 0354 } 0355 0356 if (hub->tagsChanged()) 0357 { 0358 text += i18nc("@info", "<li>tags</li>"); 0359 } 0360 0361 text += QLatin1String("</ul></p>"); 0362 0363 text += i18nc("@info", "<p>Do you want to apply your changes?</p>"); 0364 } 0365 0366 QCheckBox* const alwaysCBox = new QCheckBox(i18nc("@action", "Always apply changes without confirmation")); 0367 0368 QPointer<QMessageBox> msgBox = new QMessageBox(QMessageBox::Information, 0369 i18nc("@title:window", "Apply Changes?"), 0370 text, 0371 QMessageBox::Yes | QMessageBox::No, 0372 qApp->activeWindow()); 0373 msgBox->setCheckBox(alwaysCBox); 0374 msgBox->setDefaultButton(QMessageBox::No); 0375 msgBox->setEscapeButton(QMessageBox::No); 0376 0377 // Pop-up a message in desktop notification manager 0378 0379 DNotificationWrapper(QString(), i18nc("@info", "Apply changes?"), 0380 DigikamApp::instance(), DigikamApp::instance()->windowTitle()); 0381 0382 int returnCode = msgBox->exec(); 0383 bool alwaysApply = msgBox->checkBox()->isChecked(); 0384 delete msgBox; 0385 0386 if (alwaysApply) 0387 { 0388 ApplicationSettings::instance()->setApplySidebarChangesDirectly(true); 0389 } 0390 0391 if (returnCode == QMessageBox::No) 0392 { 0393 delete hub; 0394 0395 return; 0396 } 0397 0398 // otherwise apply: 0399 0400 FileActionMngr::instance()->applyMetadata(infos, hub); 0401 } 0402 0403 void ItemDescEditTab::slotApplyAllChanges() 0404 { 0405 if (!d->modified) 0406 { 0407 return; 0408 } 0409 0410 if (d->currInfos.isEmpty()) 0411 { 0412 return; 0413 } 0414 0415 FileActionMngr::instance()->applyMetadata(d->currInfos, *d->hub); 0416 d->reset(); 0417 } 0418 0419 void ItemDescEditTab::slotRevertAllChanges() 0420 { 0421 if (!d->modified) 0422 { 0423 return; 0424 } 0425 0426 if (d->currInfos.isEmpty()) 0427 { 0428 return; 0429 } 0430 0431 d->setInfos(d->currInfos); 0432 } 0433 0434 void ItemDescEditTab::slotReadFromFileMetadataToDatabase() 0435 { 0436 d->initProgressIndicator(); 0437 0438 Q_EMIT signalProgressMessageChanged(i18nc("@info", "Reading metadata from files. Please wait...")); 0439 0440 d->ignoreItemAttributesWatch = true; 0441 int i = 0; 0442 0443 ScanController::instance()->suspendCollectionScan(); 0444 0445 CollectionScanner scanner; 0446 0447 Q_FOREACH (const ItemInfo& info, d->currInfos) 0448 { 0449 scanner.scanFile(info, CollectionScanner::CleanScan); 0450 0451 Q_EMIT signalProgressValueChanged(i++ / (float)d->currInfos.count()); 0452 0453 qApp->processEvents(); 0454 } 0455 0456 ScanController::instance()->resumeCollectionScan(); 0457 d->ignoreItemAttributesWatch = false; 0458 0459 Q_EMIT signalProgressFinished(); 0460 0461 // reload everything 0462 0463 d->setInfos(d->currInfos); 0464 } 0465 0466 void ItemDescEditTab::slotWriteToFileMetadataFromDatabase() 0467 { 0468 d->initProgressIndicator(); 0469 0470 Q_EMIT signalProgressMessageChanged(i18nc("@info", "Writing metadata to files. Please wait...")); 0471 0472 int i = 0; 0473 0474 Q_FOREACH (const ItemInfo& info, d->currInfos) 0475 { 0476 MetadataHub hub; 0477 0478 // read in from database 0479 0480 hub.load(info); 0481 0482 // write out to file DMetadata 0483 0484 ScanController::FileMetadataWrite writeScope(info); 0485 writeScope.changed(hub.writeToMetadata(info, MetadataHub::WRITE_ALL)); 0486 0487 Q_EMIT signalProgressValueChanged(i++ / (float)d->currInfos.count()); 0488 0489 qApp->processEvents(); 0490 } 0491 0492 Q_EMIT signalProgressFinished(); 0493 } 0494 0495 void ItemDescEditTab::slotModified() 0496 { 0497 d->modified = true; 0498 d->applyBtn->setEnabled(true); 0499 d->revertBtn->setEnabled(true); 0500 0501 if (d->currInfos.size() == 1) 0502 { 0503 d->applyToAllVersionsButton->setEnabled(true); 0504 } 0505 } 0506 0507 void ItemDescEditTab::slotMoreMenu() 0508 { 0509 d->moreMenu->clear(); 0510 0511 if (d->singleSelection()) 0512 { 0513 d->moreMenu->addAction(i18nc("@action", "Read metadata from file to database"), 0514 this, SLOT(slotReadFromFileMetadataToDatabase())); 0515 QAction* const writeAction = d->moreMenu->addAction(i18nc("@action", "Write metadata to each file"), 0516 this, SLOT(slotWriteToFileMetadataFromDatabase())); 0517 0518 // we do not need a "Write to file" action here because the apply button will do just that 0519 // if selection is a single file. 0520 // Adding the option will confuse users: Does the apply button not write to file? 0521 // Removing the option will confuse users: There is not option to write to file! (not visible in single selection) 0522 // Disabling will confuse users: Why is it disabled? 0523 0524 writeAction->setEnabled(false); 0525 } 0526 else 0527 { 0528 // We need to make clear that this action is different from the Apply button, 0529 // which saves the same changes to all files. These batch operations operate on each single file. 0530 0531 d->moreMenu->addAction(i18nc("@action", "Read metadata from each file to database"), 0532 this, SLOT(slotReadFromFileMetadataToDatabase())); 0533 0534 d->moreMenu->addAction(i18nc("@action", "Write metadata to each file"), 0535 this, SLOT(slotWriteToFileMetadataFromDatabase())); 0536 0537 d->moreMenu->addSeparator(); 0538 0539 d->moreMenu->addAction(i18nc("@action", "Unify tags of selected items"), 0540 this, SLOT(slotUnifyPartiallyTags())); 0541 } 0542 } 0543 0544 void ItemDescEditTab::slotImagesChanged(int albumId) 0545 { 0546 if (d->ignoreItemAttributesWatch || d->modified) 0547 { 0548 return; 0549 } 0550 0551 Album* const a = AlbumManager::instance()->findAlbum(albumId); 0552 0553 if (d->currInfos.isEmpty() || !a || a->isRoot() || (a->type() != Album::TAG)) 0554 { 0555 return; 0556 } 0557 0558 d->setInfos(d->currInfos); 0559 } 0560 0561 void ItemDescEditTab::slotReloadForMetadataChange() 0562 { 0563 // NOTE: What to do if d->modified? Reloading is no option. 0564 // It may be a little change the user wants to ignore, or a large conflict. 0565 0566 if (d->currInfos.isEmpty() || d->modified) 0567 { 0568 d->resetMetadataChangeInfo(); 0569 0570 return; 0571 } 0572 0573 if (d->singleSelection()) 0574 { 0575 if (d->metadataChangeIds.contains(d->currInfos.first().id())) 0576 { 0577 d->setInfos(d->currInfos); 0578 } 0579 } 0580 else 0581 { 0582 // if image id is in our list, update 0583 0584 Q_FOREACH (const ItemInfo& info, d->currInfos) 0585 { 0586 if (d->metadataChangeIds.contains(info.id())) 0587 { // cppcheck-suppress useStlAlgorithm 0588 d->setInfos(d->currInfos); 0589 break; 0590 } 0591 } 0592 } 0593 } 0594 0595 void ItemDescEditTab::slotApplyChangesToAllVersions() 0596 { 0597 if (!d->modified) 0598 { 0599 return; 0600 } 0601 0602 if (d->currInfos.isEmpty()) 0603 { 0604 return; 0605 } 0606 0607 QSet<qlonglong> tmpSet; 0608 QList<QPair<qlonglong, qlonglong> > relations; 0609 0610 Q_FOREACH (const ItemInfo& info, d->currInfos) 0611 { 0612 // Collect all ids in all image's relations. 0613 0614 relations.append(info.relationCloud()); 0615 } 0616 0617 if (relations.isEmpty()) 0618 { 0619 slotApplyAllChanges(); 0620 return; 0621 } 0622 0623 for (int i = 0 ; i < relations.size() ; ++i) 0624 { 0625 // Use QSet to prevent duplicates. 0626 0627 tmpSet.insert(relations.at(i).first); 0628 tmpSet.insert(relations.at(i).second); 0629 } 0630 0631 FileActionMngr::instance()->applyMetadata(ItemInfoList(tmpSet.values()), *d->hub); 0632 0633 d->modified = false; 0634 d->hub->resetChanged(); 0635 d->applyBtn->setEnabled(false); 0636 d->revertBtn->setEnabled(false); 0637 d->applyToAllVersionsButton->setEnabled(false); 0638 } 0639 0640 } // namespace Digikam 0641 0642 #include "moc_itemdescedittab.cpp"