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"