File indexing completed on 2025-12-07 04:08:32

0001 /* ============================================================
0002  *
0003  * This file is a part of digiKam project
0004  * https://www.digikam.org
0005  *
0006  * Date        : 2021-04-18
0007  * Description : ExifTool metadata list view.
0008  *
0009  * SPDX-FileCopyrightText: 2021-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 "exiftoollistview.h"
0016 
0017 // Qt includes
0018 
0019 #include <QApplication>
0020 #include <QHeaderView>
0021 #include <QStyle>
0022 
0023 // KDE includes
0024 
0025 #include <klocalizedstring.h>
0026 
0027 // Local includes
0028 
0029 #include "exiftoollistviewgroup.h"
0030 #include "exiftoollistviewitem.h"
0031 #include "digikam_debug.h"
0032 
0033 namespace Digikam
0034 {
0035 
0036 class Q_DECL_HIDDEN ExifToolListView::Private
0037 {
0038 
0039 public:
0040 
0041     explicit Private()
0042       : parser(nullptr)
0043     {
0044     }
0045 
0046     QString                      lastError;
0047     QString                      selectedItemKey;
0048     QStringList                  simplifiedTagsList;
0049 
0050     ExifToolParser*              parser;
0051     ExifToolParser::ExifToolData map;
0052 };
0053 
0054 ExifToolListView::ExifToolListView(QWidget* const parent)
0055     : QTreeWidget(parent),
0056       d          (new Private)
0057 {
0058     setColumnCount(2);
0059     setHeaderHidden(true);
0060     setSortingEnabled(true);
0061     setUniformRowHeights(true);
0062     setAllColumnsShowFocus(true);
0063     sortByColumn(0, Qt::AscendingOrder);
0064     setSelectionMode(QAbstractItemView::SingleSelection);
0065     header()->setSectionResizeMode(QHeaderView::Stretch);
0066     setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
0067 
0068     d->parser = new ExifToolParser(this, true);
0069 
0070     connect(d->parser, SIGNAL(signalExifToolAsyncData(ExifToolParser::ExifToolData)),
0071             this, SLOT(slotExifToolAsyncData(ExifToolParser::ExifToolData)));
0072 
0073     connect(this, SIGNAL(itemClicked(QTreeWidgetItem*,int)),
0074             this, SLOT(slotSelectionChanged(QTreeWidgetItem*,int)));
0075 }
0076 
0077 ExifToolListView::~ExifToolListView()
0078 {
0079     delete d;
0080 }
0081 
0082 void ExifToolListView::loadFromUrl(const QUrl& url)
0083 {
0084     clear();
0085     d->map.clear();
0086 
0087     if (!url.isValid())
0088     {
0089         Q_EMIT signalLoadingResult(true);
0090 
0091         return;
0092     }
0093 
0094     if (!d->parser->load(url.toLocalFile()))
0095     {
0096         d->lastError = d->parser->currentErrorString();
0097 
0098         Q_EMIT signalLoadingResult(false);
0099 
0100         return;
0101     }
0102 
0103     d->lastError.clear();
0104 }
0105 
0106 QString ExifToolListView::errorString() const
0107 {
0108     return d->lastError;
0109 }
0110 
0111 void ExifToolListView::slotExifToolAsyncData(const ExifToolParser::ExifToolData& map)
0112 {
0113     d->map       = map;
0114     d->lastError = d->parser->currentErrorString();
0115 
0116     Q_EMIT signalLoadingResult(d->lastError.isEmpty());
0117 }
0118 
0119 ExifToolListViewGroup* ExifToolListView::findGroup(const QString& group)
0120 {
0121     QTreeWidgetItemIterator it(this);
0122 
0123     while (*it)
0124     {
0125         ExifToolListViewGroup* const item = dynamic_cast<ExifToolListViewGroup*>(*it);
0126 
0127         if (item && (item->text(0) == group))
0128         {
0129             return item;
0130         }
0131 
0132         ++it;
0133     }
0134 
0135     return nullptr;
0136 }
0137 
0138 void ExifToolListView::slotSearchTextChanged(const SearchTextSettings& settings)
0139 {
0140     bool query     = false;
0141     QString search = settings.text;
0142 
0143     // Restore all Group items.
0144 
0145     QTreeWidgetItemIterator it2(this);
0146 
0147     while (*it2)
0148     {
0149         ExifToolListViewGroup* const item = dynamic_cast<ExifToolListViewGroup*>(*it2);
0150 
0151         if (item)
0152         {
0153             item->setHidden(false);
0154         }
0155 
0156         ++it2;
0157     }
0158 
0159     QTreeWidgetItemIterator it(this);
0160 
0161     while (*it)
0162     {
0163         ExifToolListViewItem* const item = dynamic_cast<ExifToolListViewItem*>(*it);
0164 
0165         if (item)
0166         {
0167             if (item->text(0).contains(search, settings.caseSensitive) ||
0168                 item->text(1).contains(search, settings.caseSensitive))
0169             {
0170                 query = true;
0171                 item->setHidden(false);
0172             }
0173             else
0174             {
0175                 item->setHidden(true);
0176             }
0177         }
0178 
0179         ++it;
0180     }
0181 
0182     Q_EMIT signalTextFilterMatch(query);
0183 }
0184 
0185 QString ExifToolListView::getCurrentItemKey() const
0186 {
0187     if (currentItem() && (currentItem()->flags() & Qt::ItemIsSelectable))
0188     {
0189         ExifToolListViewItem* const item = static_cast<ExifToolListViewItem*>(currentItem());
0190 
0191         return item->getKey();
0192     }
0193 
0194     return QString();
0195 }
0196 
0197 void ExifToolListView::setCurrentItemByKey(const QString& itemKey)
0198 {
0199     if (itemKey.isNull())
0200     {
0201         return;
0202     }
0203 
0204     QTreeWidgetItemIterator it(this);
0205 
0206     while (*it)
0207     {
0208         ExifToolListViewItem* const item = dynamic_cast<ExifToolListViewItem*>(*it);
0209 
0210         if (item)
0211         {
0212             if (item->getKey() == itemKey)
0213             {
0214                 setCurrentItem(item);
0215                 scrollToItem(item);
0216                 d->selectedItemKey = itemKey;
0217 
0218                 return;
0219             }
0220         }
0221 
0222         ++it;
0223     }
0224 }
0225 
0226 void ExifToolListView::slotSelectionChanged(QTreeWidgetItem* item, int)
0227 {
0228     ExifToolListViewItem* const viewItem = dynamic_cast<ExifToolListViewItem*>(item);
0229 
0230     if (!viewItem)
0231     {
0232         return;
0233     }
0234 
0235     d->selectedItemKey                   = viewItem->getKey();
0236     QString tagValue                     = viewItem->getValue();
0237     QString tagTitle                     = viewItem->getTitle();
0238     QString tagDesc                      = viewItem->getDescription();
0239 
0240     if (tagValue.length() > 128)
0241     {
0242         tagValue.truncate(128);
0243         tagValue.append(QLatin1String("..."));
0244     }
0245 
0246     this->setWhatsThis(i18n("<b>Title: </b><p>%1</p>"
0247                             "<b>Value: </b><p>%2</p>"
0248                             "<b>Description: </b><p>%3</p>",
0249                             tagTitle, tagValue, tagDesc));
0250 }
0251 
0252 void ExifToolListView::setGroupList(const QStringList& tagsFilter, const QStringList& keysFilter)
0253 {
0254     clear();
0255     d->simplifiedTagsList.clear();
0256     QString simplifiedTag;
0257 
0258     QStringList filters = tagsFilter;
0259     QString groupItemName;
0260 
0261     /** Key is formatted like this:
0262      *
0263      * EXIF.ExifIFD.Image.ExposureCompensation
0264      * File.File.Other.FileType
0265      * Composite.Composite.Time.SubSecModifyDate
0266      * File.System.Time.FileInodeChangeDate
0267      * File.System.Other.FileSize
0268      * EXIF.GPS.Location.GPSLongitude
0269      * ICC_Profile.ICC-header.Image.ProfileCreator
0270      * EXIF.IFD1.Image.ThumbnailOffset
0271      * JFIF.JFIF.Image.YResolution
0272      * ICC_Profile.ICC_Profile.Image.GreenMatrixColumn
0273      */
0274     for (ExifToolParser::ExifToolData::const_iterator it = d->map.constBegin() ;
0275          it != d->map.constEnd() ; ++it)
0276     {
0277         // We checking if we have changed of GroupName
0278 
0279         QString currentGroupName = it.key().section(QLatin1Char('.'), 0, 0)
0280                                            .replace(QLatin1Char('_'), QLatin1Char(' '));
0281 
0282         if (currentGroupName == QLatin1String("ExifTool"))
0283         {
0284             // Always ignore ExifTool errors or warnings.
0285 
0286             continue;
0287         }
0288 
0289         if (!keysFilter.isEmpty() && !keysFilter.contains(currentGroupName))
0290         {
0291             // If group filters, always ignore not found entries.
0292 
0293             continue;
0294         }
0295 
0296         ExifToolListViewGroup* parentGroupItem = findGroup(currentGroupName);
0297         simplifiedTag                          = currentGroupName + QLatin1Char('.') + it.key().section(QLatin1Char('.'), -1);
0298 
0299         if (!d->simplifiedTagsList.contains(simplifiedTag))
0300         {
0301             d->simplifiedTagsList.append(simplifiedTag);
0302 
0303             if (!parentGroupItem)
0304             {
0305                 parentGroupItem = new ExifToolListViewGroup(this, currentGroupName);
0306             }
0307 
0308             if      (tagsFilter.isEmpty())
0309             {
0310                 new ExifToolListViewItem(parentGroupItem, it.key(), it.value()[0].toString(), it.value()[2].toString());
0311             }
0312             else
0313             {
0314                 // We ignore all unknown tags if necessary.
0315 
0316                 if      (filters.contains(QLatin1String("FULL")))
0317                 {
0318                     // We don't filter the output (Photo Mode)
0319 
0320                     new ExifToolListViewItem(parentGroupItem, it.key(), it.value()[0].toString(), it.value()[2].toString());
0321                 }
0322                 else if (!filters.isEmpty())
0323                 {
0324                     // We using the filter to make a more user friendly output (Custom Mode)
0325 
0326                     // Filter is not a list of complete tag keys
0327 
0328                     if      (!filters.at(0).contains(QLatin1Char('.')) && filters.contains(it.key().section(QLatin1Char('.'), -1)))
0329                     {
0330                         new ExifToolListViewItem(parentGroupItem, it.key(), it.value()[0].toString(), it.value()[2].toString());
0331                         filters.removeAll(it.key());
0332                     }
0333                     else if (filters.contains(it.key()))
0334                     {
0335                         new ExifToolListViewItem(parentGroupItem, it.key(), it.value()[0].toString(), it.value()[2].toString());
0336                         filters.removeAll(it.key());
0337                     }
0338                 }
0339             }
0340         }
0341     }
0342 
0343     // Add not found tags from filter as grey items.
0344 
0345     d->simplifiedTagsList.clear();
0346 
0347     if (!filters.isEmpty()                       &&
0348         (filters.at(0) != QLatin1String("FULL")) &&
0349         filters.at(0).contains(QLatin1Char('.')))
0350     {
0351         Q_FOREACH (const QString& key, filters)
0352         {
0353             QString grp                  = key.section(QLatin1Char('.'), 0, 0)
0354                                               .replace(QLatin1Char('_'), QLatin1Char(' '));
0355             simplifiedTag                = grp + QLatin1Char('.') + key.section(QLatin1Char('.'), -1);
0356             ExifToolListViewGroup* pitem = findGroup(grp);
0357 
0358             if (!d->simplifiedTagsList.contains(simplifiedTag))
0359             {
0360                 d->simplifiedTagsList.append(simplifiedTag);
0361 
0362                 if (!pitem)
0363                 {
0364                     pitem = new ExifToolListViewGroup(this, grp);
0365                 }
0366 
0367                 new ExifToolListViewItem(pitem, key);
0368             }
0369         }
0370 
0371         // Remove groups with no children.
0372 
0373         QTreeWidgetItemIterator it(this);
0374 
0375         while (*it)
0376         {
0377             ExifToolListViewGroup* const item = dynamic_cast<ExifToolListViewGroup*>(*it);
0378 
0379             if (item && !item->childCount())
0380             {
0381                 delete item;
0382             }
0383 
0384             ++it;
0385         }
0386     }
0387 
0388     setCurrentItemByKey(d->selectedItemKey);
0389     update();
0390 }
0391 
0392 } // namespace Digikam
0393 
0394 #include "moc_exiftoollistview.cpp"