File indexing completed on 2025-04-27 03:58:35
0001 /* ============================================================ 0002 * 0003 * This file is a part of digiKam project 0004 * https://www.digikam.org 0005 * 0006 * Date : 2006-02-22 0007 * Description : a generic widget to display metadata 0008 * 0009 * SPDX-FileCopyrightText: 2006-2024 by Gilles Caulier <caulier dot gilles at gmail dot com> 0010 * 0011 * SPDX-License-Identifier: GPL-2.0-or-later 0012 * 0013 * ============================================================ */ 0014 0015 #include "metadatawidget.h" 0016 0017 // Qt includes 0018 0019 #include <QButtonGroup> 0020 #include <QClipboard> 0021 #include <QDataStream> 0022 #include <QFile> 0023 #include <QFrame> 0024 #include <QGridLayout> 0025 #include <QLabel> 0026 #include <QMap> 0027 #include <QMimeData> 0028 #include <QPaintDevice> 0029 #include <QPainter> 0030 #include <QPointer> 0031 #include <QPrintDialog> 0032 #include <QPrinter> 0033 #include <QPushButton> 0034 #include <QTextDocument> 0035 #include <QToolButton> 0036 #include <QVBoxLayout> 0037 #include <QActionGroup> 0038 #include <QStandardPaths> 0039 #include <QMenu> 0040 #include <QApplication> 0041 #include <QStyle> 0042 #include <QElapsedTimer> 0043 0044 // KDE includes 0045 0046 #include <klocalizedstring.h> 0047 0048 // Local includes 0049 0050 #include "digikam_debug.h" 0051 #include "metadatalistview.h" 0052 #include "metadatalistviewitem.h" 0053 #include "mdkeylistviewitem.h" 0054 #include "searchtextbar.h" 0055 #include "setup.h" 0056 #include "dfiledialog.h" 0057 0058 namespace Digikam 0059 { 0060 0061 class Q_DECL_HIDDEN MetadataWidget::Private 0062 { 0063 0064 public: 0065 0066 explicit Private() 0067 : noneAction (nullptr), 0068 photoAction (nullptr), 0069 customAction (nullptr), 0070 settingsAction (nullptr), 0071 mainLayout (nullptr), 0072 filterBtn (nullptr), 0073 toolBtn (nullptr), 0074 saveMetadata (nullptr), 0075 printMetadata (nullptr), 0076 copy2ClipBoard (nullptr), 0077 optionsMenu (nullptr), 0078 view (nullptr), 0079 searchBar (nullptr), 0080 metadata (nullptr) 0081 { 0082 } 0083 0084 QAction* noneAction; 0085 QAction* photoAction; 0086 QAction* customAction; 0087 QAction* settingsAction; 0088 0089 QGridLayout* mainLayout; 0090 0091 QToolButton* filterBtn; 0092 QToolButton* toolBtn; 0093 0094 QString fileName; 0095 0096 QStringList tagsFilter; 0097 0098 QAction* saveMetadata; 0099 QAction* printMetadata; 0100 QAction* copy2ClipBoard; 0101 0102 QMenu* optionsMenu; 0103 0104 MetadataListView* view; 0105 0106 SearchTextBar* searchBar; 0107 0108 DMetadata* metadata; 0109 DMetadata::MetaDataMap metaDataMap; 0110 }; 0111 0112 MetadataWidget::MetadataWidget(QWidget* const parent, const QString& name) 0113 : QWidget(parent), 0114 d (new Private) 0115 { 0116 setObjectName(name); 0117 0118 const int spacing = qMin(QApplication::style()->pixelMetric(QStyle::PM_LayoutHorizontalSpacing), 0119 QApplication::style()->pixelMetric(QStyle::PM_LayoutVerticalSpacing)); 0120 d->mainLayout = new QGridLayout(this); 0121 0122 // ----------------------------------------------------------------- 0123 0124 d->filterBtn = new QToolButton(this); 0125 d->filterBtn->setToolTip(i18nc("@info: metadata view", "Tags filter options")); 0126 d->filterBtn->setIcon(QIcon::fromTheme(QLatin1String("view-filter"))); 0127 d->filterBtn->setPopupMode(QToolButton::InstantPopup); 0128 d->filterBtn->setWhatsThis(i18nc("@info: metadata view", "Apply tags filter over metadata.")); 0129 0130 d->optionsMenu = new QMenu(d->filterBtn); 0131 QActionGroup* const filterGroup = new QActionGroup(this); 0132 0133 d->noneAction = d->optionsMenu->addAction(i18nc("@action: metadata view", "No filter")); 0134 d->noneAction->setCheckable(true); 0135 filterGroup->addAction(d->noneAction); 0136 d->photoAction = d->optionsMenu->addAction(i18nc("@action: metadata view", "Photograph")); 0137 d->photoAction->setCheckable(true); 0138 filterGroup->addAction(d->photoAction); 0139 d->customAction = d->optionsMenu->addAction(i18nc("@action: metadata view", "Custom")); 0140 d->customAction->setCheckable(true); 0141 filterGroup->addAction(d->customAction); 0142 d->optionsMenu->addSeparator(); 0143 d->settingsAction = d->optionsMenu->addAction(i18nc("@action: metadata view", "Settings")); 0144 d->settingsAction->setCheckable(false); 0145 0146 filterGroup->setExclusive(true); 0147 d->filterBtn->setMenu(d->optionsMenu); 0148 0149 // ----------------------------------------------------------------- 0150 0151 d->toolBtn = new QToolButton(this); 0152 d->toolBtn->setToolTip(i18nc("@info: metadata view", "Tools")); 0153 d->toolBtn->setIcon(QIcon::fromTheme(QLatin1String("system-run"))); 0154 d->toolBtn->setPopupMode(QToolButton::InstantPopup); 0155 d->toolBtn->setWhatsThis(i18nc("@info: metadata view", "Run tool over metadata tags.")); 0156 0157 QMenu* const toolMenu = new QMenu(d->toolBtn); 0158 d->saveMetadata = toolMenu->addAction(i18nc("@action:inmenu", "Save in file")); 0159 d->printMetadata = toolMenu->addAction(i18nc("@action:inmenu", "Print")); 0160 d->copy2ClipBoard = toolMenu->addAction(i18nc("@action:inmenu", "Copy to Clipboard")); 0161 d->toolBtn->setMenu(toolMenu); 0162 0163 d->view = new MetadataListView(this); 0164 QString barName = name + QLatin1String("SearchBar"); 0165 d->searchBar = new SearchTextBar(this, barName); 0166 0167 // ----------------------------------------------------------------- 0168 0169 d->mainLayout->addWidget(d->filterBtn, 0, 0, 1, 1); 0170 d->mainLayout->addWidget(d->searchBar, 0, 1, 1, 3); 0171 d->mainLayout->addWidget(d->toolBtn, 0, 4, 1, 1); 0172 d->mainLayout->addWidget(d->view, 1, 0, 1, 5); 0173 d->mainLayout->setColumnStretch(2, 10); 0174 d->mainLayout->setRowStretch(1, 10); 0175 d->mainLayout->setContentsMargins(spacing, spacing, spacing, spacing); 0176 d->mainLayout->setSpacing(0); 0177 } 0178 0179 MetadataWidget::~MetadataWidget() 0180 { 0181 delete d->metadata; 0182 delete d; 0183 } 0184 0185 void MetadataWidget::setup() 0186 { 0187 connect(d->optionsMenu, SIGNAL(triggered(QAction*)), 0188 this, SLOT(slotFilterChanged(QAction*))); 0189 0190 connect(d->copy2ClipBoard, SIGNAL(triggered(bool)), 0191 this, SLOT(slotCopy2Clipboard())); 0192 0193 connect(d->printMetadata, SIGNAL(triggered(bool)), 0194 this, SLOT(slotPrintMetadata())); 0195 0196 connect(d->saveMetadata, SIGNAL(triggered(bool)), 0197 this, SLOT(slotSaveMetadataToFile())); 0198 0199 connect(d->searchBar, SIGNAL(signalSearchTextSettings(SearchTextSettings)), 0200 d->view, SLOT(slotSearchTextChanged(SearchTextSettings))); 0201 0202 connect(d->view, SIGNAL(signalTextFilterMatch(bool)), 0203 d->searchBar, SLOT(slotSearchResult(bool))); 0204 } 0205 0206 void MetadataWidget::slotFilterChanged(QAction* action) 0207 { 0208 if (action == d->settingsAction) 0209 { 0210 Q_EMIT signalSetupMetadataFilters(); 0211 } 0212 else if ((action == d->noneAction) || 0213 (action == d->photoAction) || 0214 (action == d->customAction)) 0215 { 0216 buildView(); 0217 } 0218 } 0219 0220 QStringList MetadataWidget::getTagsFilter() const 0221 { 0222 return d->tagsFilter; 0223 } 0224 0225 void MetadataWidget::setTagsFilter(const QStringList& list) 0226 { 0227 d->tagsFilter = list; 0228 0229 if (d->tagsFilter.isEmpty()) 0230 { 0231 d->customAction->setEnabled(false); 0232 0233 if (getMode() == CUSTOM) 0234 { 0235 d->noneAction->setChecked(true); 0236 } 0237 } 0238 else 0239 { 0240 d->customAction->setEnabled(true); 0241 } 0242 0243 buildView(); 0244 } 0245 0246 MetadataListView* MetadataWidget::view() const 0247 { 0248 return d->view; 0249 } 0250 0251 void MetadataWidget::enabledToolButtons(bool b) 0252 { 0253 d->toolBtn->setEnabled(b); 0254 } 0255 0256 bool MetadataWidget::setMetadata(const DMetadata& data) 0257 { 0258 if (d->metadata) 0259 { 0260 delete d->metadata; 0261 } 0262 0263 d->metadata = new DMetadata(data.data()); 0264 0265 // Cleanup all metadata contents. 0266 0267 setMetadataMap(); 0268 0269 if (d->metadata->isEmpty()) 0270 { 0271 setMetadataEmpty(); 0272 return false; 0273 } 0274 0275 // Try to decode current metadata. 0276 0277 QElapsedTimer execTimer; 0278 execTimer.start(); 0279 0280 bool error = !decodeMetadata(); 0281 0282 qCDebug(DIGIKAM_GENERAL_LOG) << getMetadataTitle() << "decoding took" 0283 << execTimer.elapsed() << "ms (" << error << ")"; 0284 0285 enabledToolButtons(!error); 0286 0287 // Refresh view using decoded metadata. 0288 0289 buildView(); 0290 0291 return true; 0292 } 0293 0294 void MetadataWidget::setMetadataEmpty() 0295 { 0296 d->view->clear(); 0297 enabledToolButtons(false); 0298 } 0299 0300 DMetadata* MetadataWidget::getMetadata() const 0301 { 0302 return d->metadata; 0303 } 0304 0305 bool MetadataWidget::storeMetadataToFile(const QUrl& url, const QByteArray& metaData) 0306 { 0307 if (url.isEmpty()) 0308 { 0309 return false; 0310 } 0311 0312 QFile file(url.toLocalFile()); 0313 0314 if (!file.open(QIODevice::WriteOnly)) 0315 { 0316 return false; 0317 } 0318 0319 QDataStream stream(&file); 0320 stream.writeRawData(metaData.data(), metaData.size()); 0321 file.close(); 0322 0323 return true; 0324 } 0325 0326 void MetadataWidget::setMetadataMap(const DMetadata::MetaDataMap& data) 0327 { 0328 d->metaDataMap = data; 0329 } 0330 0331 const DMetadata::MetaDataMap& MetadataWidget::getMetadataMap() 0332 { 0333 return d->metaDataMap; 0334 } 0335 0336 void MetadataWidget::setIfdList(const DMetadata::MetaDataMap& ifds, const QStringList& tagsFilter) 0337 { 0338 d->view->setIfdList(ifds, tagsFilter); 0339 } 0340 0341 void MetadataWidget::setIfdList(const DMetadata::MetaDataMap& ifds, const QStringList& keysFilter, 0342 const QStringList& tagsFilter) 0343 { 0344 d->view->setIfdList(ifds, keysFilter, tagsFilter); 0345 } 0346 0347 QString MetadataWidget::metadataToText() const 0348 { 0349 QString textmetadata = i18nc("@info: metadata clipboard", "File name: %1 (%2)", d->fileName, getMetadataTitle()); 0350 int i = 0; 0351 QTreeWidgetItem* item = nullptr; 0352 0353 do 0354 { 0355 item = d->view->topLevelItem(i); 0356 MdKeyListViewItem* const lvItem = dynamic_cast<MdKeyListViewItem*>(item); 0357 0358 if (lvItem) 0359 { 0360 textmetadata.append(QLatin1String("\n\n>>> ")); 0361 textmetadata.append(lvItem->getDecryptedKey()); 0362 textmetadata.append(QLatin1String(" <<<\n\n")); 0363 0364 int j = 0; 0365 QTreeWidgetItem* item2 = nullptr; 0366 0367 do 0368 { 0369 item2 = dynamic_cast<QTreeWidgetItem*>(lvItem); 0370 item2 = item2 ? item2->child(j) : nullptr; 0371 0372 if (item2) 0373 { 0374 MetadataListViewItem* const lvItem2 = dynamic_cast<MetadataListViewItem*>(item2); 0375 0376 if (lvItem2) 0377 { 0378 textmetadata.append(lvItem2->text(0)); 0379 textmetadata.append(QLatin1String(" : ")); 0380 textmetadata.append(lvItem2->text(1)); 0381 textmetadata.append(QLatin1Char('\n')); 0382 } 0383 } 0384 0385 ++j; 0386 } 0387 while (item2); 0388 } 0389 0390 ++i; 0391 } 0392 while (item); 0393 0394 return textmetadata; 0395 } 0396 0397 void MetadataWidget::slotCopy2Clipboard() 0398 { 0399 QMimeData* const mimeData = new QMimeData(); 0400 mimeData->setText(metadataToText()); 0401 QApplication::clipboard()->setMimeData(mimeData, QClipboard::Clipboard); 0402 } 0403 0404 void MetadataWidget::slotPrintMetadata() 0405 { 0406 QPrinter printer; 0407 printer.setFullPage(true); 0408 0409 QPointer<QPrintDialog> dialog = new QPrintDialog(&printer, qApp->activeWindow()); 0410 0411 if (dialog->exec()) 0412 { 0413 QTextDocument doc; 0414 doc.setPlainText(metadataToText()); 0415 QFont font(QApplication::font()); 0416 font.setPointSize(10); // we define 10pt to be a nice base size for printing. 0417 doc.setDefaultFont(font); 0418 doc.print(&printer); 0419 } 0420 0421 delete dialog; 0422 } 0423 0424 QUrl MetadataWidget::saveMetadataToFile(const QString& caption, const QString& fileFilter) 0425 { 0426 QPointer<DFileDialog> fileSaveDialog = new DFileDialog(this, caption, 0427 QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation)); 0428 fileSaveDialog->setAcceptMode(QFileDialog::AcceptSave); 0429 fileSaveDialog->setFileMode(QFileDialog::AnyFile); 0430 fileSaveDialog->selectFile(d->fileName); 0431 fileSaveDialog->setNameFilter(fileFilter); 0432 fileSaveDialog->exec(); 0433 0434 // Check for cancel. 0435 0436 if (!fileSaveDialog->hasAcceptedUrls()) 0437 { 0438 delete fileSaveDialog; 0439 0440 return QUrl(); 0441 } 0442 0443 QUrl url = fileSaveDialog->selectedUrls().first(); 0444 delete fileSaveDialog; 0445 0446 return url; 0447 } 0448 0449 void MetadataWidget::setMode(int mode) 0450 { 0451 if (mode == NONE) 0452 { 0453 d->noneAction->setChecked(true); 0454 } 0455 else if (mode == PHOTO) 0456 { 0457 d->photoAction->setChecked(true); 0458 } 0459 else 0460 { 0461 d->customAction->setChecked(true); 0462 } 0463 0464 buildView(); 0465 } 0466 0467 int MetadataWidget::getMode() const 0468 { 0469 if (d->noneAction->isChecked()) 0470 { 0471 return NONE; 0472 } 0473 else if (d->photoAction->isChecked()) 0474 { 0475 return PHOTO; 0476 } 0477 0478 return CUSTOM; 0479 } 0480 0481 QString MetadataWidget::getCurrentItemKey() const 0482 { 0483 return d->view->getCurrentItemKey(); 0484 } 0485 0486 void MetadataWidget::setCurrentItemByKey(const QString& itemKey) 0487 { 0488 d->view->setCurrentItemByKey(itemKey); 0489 } 0490 0491 bool MetadataWidget::loadFromData(const QString& fileName, const DMetadata& data) 0492 { 0493 setFileName(fileName); 0494 0495 return (setMetadata(data)); 0496 } 0497 0498 QString MetadataWidget::getTagTitle(const QString&) 0499 { 0500 return QString(); 0501 } 0502 0503 QString MetadataWidget::getTagDescription(const QString&) 0504 { 0505 return QString(); 0506 } 0507 0508 void MetadataWidget::setFileName(const QString& fileName) 0509 { 0510 d->fileName = fileName; 0511 } 0512 0513 void MetadataWidget::setUserAreaWidget(QWidget* const w) 0514 { 0515 QVBoxLayout* const vLayout = new QVBoxLayout(); 0516 vLayout->setSpacing(qMin(QApplication::style()->pixelMetric(QStyle::PM_LayoutHorizontalSpacing), 0517 QApplication::style()->pixelMetric(QStyle::PM_LayoutVerticalSpacing))); 0518 vLayout->addWidget(w); 0519 vLayout->addStretch(); 0520 d->mainLayout->addLayout(vLayout, 3, 0, 1, 5); 0521 } 0522 0523 void MetadataWidget::buildView() 0524 { 0525 d->view->slotSearchTextChanged(d->searchBar->searchTextSettings()); 0526 } 0527 0528 } // namespace Digikam 0529 0530 #include "moc_metadatawidget.cpp"