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-21
0007  * Description : a generic list view widget to
0008  *               display metadata
0009  *
0010  * SPDX-FileCopyrightText: 2006-2024 by Gilles Caulier <caulier dot gilles at gmail dot com>
0011  *
0012  * SPDX-License-Identifier: GPL-2.0-or-later
0013  *
0014  * ============================================================ */
0015 
0016 #include "metadatalistview.h"
0017 
0018 // Qt includes
0019 
0020 #include <QHeaderView>
0021 #include <QTimer>
0022 #include <QPalette>
0023 #include <QApplication>
0024 #include <QStyle>
0025 
0026 // KDE includes
0027 
0028 #include <klocalizedstring.h>
0029 
0030 // Local includes
0031 
0032 #include "mdkeylistviewitem.h"
0033 #include "metadatalistviewitem.h"
0034 
0035 namespace Digikam
0036 {
0037 
0038 MetadataListView::MetadataListView(QWidget* const parent)
0039     : QTreeWidget(parent)
0040 {
0041     setColumnCount(2);
0042     setRootIsDecorated(false);
0043     setUniformRowHeights(true);
0044     setAllColumnsShowFocus(true);
0045     setSelectionMode(QAbstractItemView::SingleSelection);
0046     setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
0047     setIndentation(qMin(QApplication::style()->pixelMetric(QStyle::PM_LayoutHorizontalSpacing),
0048                         QApplication::style()->pixelMetric(QStyle::PM_LayoutVerticalSpacing)));
0049     header()->setSectionResizeMode(QHeaderView::Stretch);
0050     header()->hide();
0051 
0052     QStringList labels;
0053     labels.append(QLatin1String("Name"));        // no i18n here: hidden header
0054     labels.append(QLatin1String("Value"));       // no i18n here: hidden header
0055     setHeaderLabels(labels);
0056 
0057     setSortingEnabled(true);
0058     sortByColumn(0, Qt::AscendingOrder);
0059 
0060     m_parent = dynamic_cast<MetadataWidget*>(parent);
0061 
0062     connect(this, SIGNAL(itemClicked(QTreeWidgetItem*,int)),
0063             this, SLOT(slotSelectionChanged(QTreeWidgetItem*,int)));
0064 }
0065 
0066 MetadataListView::~MetadataListView()
0067 {
0068 }
0069 
0070 QString MetadataListView::getCurrentItemKey() const
0071 {
0072     if (currentItem() && (currentItem()->flags() & Qt::ItemIsSelectable))
0073     {
0074         MetadataListViewItem* const item = static_cast<MetadataListViewItem*>(currentItem());
0075         return item->getKey();
0076     }
0077 
0078     return QString();
0079 }
0080 
0081 void MetadataListView::setCurrentItemByKey(const QString& itemKey)
0082 {
0083     if (itemKey.isNull())
0084     {
0085         return;
0086     }
0087 
0088     int i                 = 0;
0089     QTreeWidgetItem* item = nullptr;
0090 
0091     do
0092     {
0093         item = topLevelItem(i);
0094 
0095         if (item && (item->flags() & Qt::ItemIsSelectable))
0096         {
0097             MetadataListViewItem* const lvItem = dynamic_cast<MetadataListViewItem*>(item);
0098 
0099             if (lvItem)
0100             {
0101                 if (lvItem->getKey() == itemKey)
0102                 {
0103                     setCurrentItem(item);
0104                     scrollToItem(item);
0105                     m_selectedItemKey = itemKey;
0106 
0107                     return;
0108                 }
0109             }
0110         }
0111 
0112         ++i;
0113     }
0114     while (item);
0115 }
0116 
0117 void MetadataListView::slotSelectionChanged(QTreeWidgetItem* item, int)
0118 {
0119 
0120     MetadataListViewItem* const viewItem = dynamic_cast<MetadataListViewItem*>(item);
0121 
0122     if (!viewItem)
0123     {
0124         return;
0125     }
0126 
0127     m_selectedItemKey                    = viewItem->getKey();
0128     QString tagValue                     = viewItem->getValue().simplified();
0129     QString tagTitle                     = m_parent->getTagTitle(m_selectedItemKey);
0130     QString tagDesc                      = m_parent->getTagDescription(m_selectedItemKey);
0131 
0132     if (tagValue.length() > 128)
0133     {
0134         tagValue.truncate(128);
0135         tagValue.append(QLatin1String("..."));
0136     }
0137 
0138     this->setWhatsThis(i18n("<b>Title: </b><p>%1</p>"
0139                             "<b>Value: </b><p>%2</p>"
0140                             "<b>Description: </b><p>%3</p>",
0141                             tagTitle, tagValue, tagDesc));
0142 }
0143 
0144 void MetadataListView::setIfdList(const DMetadata::MetaDataMap& ifds, const QStringList& tagsFilter)
0145 {
0146     clear();
0147 
0148     uint               subItems      = 0;
0149     MdKeyListViewItem* parentifDItem = nullptr;
0150     QStringList        filters       = tagsFilter;
0151     QString            ifDItemName;
0152 
0153     for (DMetadata::MetaDataMap::const_iterator it = ifds.constBegin() ; it != ifds.constEnd() ; ++it)
0154     {
0155         // We checking if we have changed of ifDName
0156 
0157         QString currentIfDName = it.key().section(QLatin1Char('.'), 1, 1);
0158 
0159         if (currentIfDName != ifDItemName)
0160         {
0161             ifDItemName = currentIfDName;
0162 
0163             // Check if the current IfD have any items. If no remove it before to toggle to the next IfD.
0164 
0165             if ((subItems == 0) && parentifDItem)
0166             {
0167                 delete parentifDItem;
0168             }
0169 
0170             parentifDItem = new MdKeyListViewItem(this, currentIfDName);
0171             subItems      = 0;
0172         }
0173 
0174         if      (tagsFilter.isEmpty())
0175         {
0176             QString tagTitle = m_parent->getTagTitle(it.key());
0177             new MetadataListViewItem(parentifDItem, it.key(), tagTitle, it.value());
0178             ++subItems;
0179         }
0180         else if (!it.key().section(QLatin1Char('.'), 2, 2).startsWith(QLatin1String("0x")))
0181         {
0182             // We ignore all unknown tags if necessary.
0183 
0184             if      (filters.contains(QLatin1String("FULL")))
0185             {
0186                 // We don't filter the output (Photo Mode)
0187 
0188                 QString tagTitle = m_parent->getTagTitle(it.key());
0189                 new MetadataListViewItem(parentifDItem, it.key(), tagTitle, it.value());
0190                 ++subItems;
0191             }
0192             else if (!filters.isEmpty())
0193             {
0194                 // We using the filter to make a more user friendly output (Custom Mode)
0195 
0196                 // Filter is not a list of complete tag keys
0197 
0198                 if      (!filters.at(0).contains(QLatin1Char('.')) && filters.contains(it.key().section(QLatin1Char('.'), 2, 2)))
0199                 {
0200                     QString tagTitle = m_parent->getTagTitle(it.key());
0201                     new MetadataListViewItem(parentifDItem, it.key(), tagTitle, it.value());
0202                     ++subItems;
0203                     filters.removeAll(it.key());
0204                 }
0205                 else if (filters.contains(it.key()))
0206                 {
0207                     QString tagTitle = m_parent->getTagTitle(it.key());
0208                     new MetadataListViewItem(parentifDItem, it.key(), tagTitle, it.value());
0209                     ++subItems;
0210                     filters.removeAll(it.key());
0211                 }
0212             }
0213         }
0214     }
0215 
0216     // To check if the last IfD have any items...
0217 
0218     if ((subItems == 0) && parentifDItem)
0219     {
0220         delete parentifDItem;
0221     }
0222 
0223     // Add not found tags from filter as grey items.
0224 
0225     if (!filters.isEmpty()                       &&
0226         (filters.at(0) != QLatin1String("FULL")) &&
0227         filters.at(0).contains(QLatin1Char('.')))
0228     {
0229         Q_FOREACH (const QString& key, filters)
0230         {
0231             MdKeyListViewItem* pitem = findMdKeyItem(key);
0232 
0233             if (!pitem)
0234             {
0235                 pitem = new MdKeyListViewItem(this, key.section(QLatin1Char('.'), 1, 1));
0236             }
0237 
0238             QString tagTitle = m_parent->getTagTitle(key);
0239             new MetadataListViewItem(pitem, key, tagTitle);
0240         }
0241     }
0242 
0243     setCurrentItemByKey(m_selectedItemKey);
0244     update();
0245 }
0246 
0247 void MetadataListView::setIfdList(const DMetadata::MetaDataMap& ifds, const QStringList& keysFilter,
0248                                   const QStringList& tagsFilter)
0249 {
0250     clear();
0251 
0252     QStringList        filters       = tagsFilter;
0253     uint               subItems      = 0;
0254     MdKeyListViewItem* parentifDItem = nullptr;
0255 
0256     if (ifds.count() == 0)
0257     {
0258         return;
0259     }
0260 
0261     for (QStringList::const_iterator itKeysFilter = keysFilter.constBegin() ;
0262          itKeysFilter != keysFilter.constEnd() ; ++itKeysFilter)
0263     {
0264         subItems      = 0;
0265         parentifDItem = new MdKeyListViewItem(this, *itKeysFilter);
0266 
0267         DMetadata::MetaDataMap::const_iterator it = ifds.constEnd();
0268 
0269         while (it != ifds.constBegin())
0270         {
0271             --it;
0272 
0273             if (*itKeysFilter == it.key().section(QLatin1Char('.'), 1, 1))
0274             {
0275                 if      (tagsFilter.isEmpty())
0276                 {
0277                     QString tagTitle = m_parent->getTagTitle(it.key());
0278                     new MetadataListViewItem(parentifDItem, it.key(), tagTitle, it.value());
0279                     ++subItems;
0280                 }
0281                 else if (!it.key().section(QLatin1Char('.'), 2, 2).startsWith(QLatin1String("0x")))
0282                 {
0283                     // We ignore all unknown tags if necessary.
0284 
0285                     if (filters.contains(QLatin1String("FULL")))
0286                     {
0287                         // We don't filter the output (Photo Mode)
0288 
0289                         QString tagTitle = m_parent->getTagTitle(it.key());
0290                         new MetadataListViewItem(parentifDItem, it.key(), tagTitle, it.value());
0291                         ++subItems;
0292                     }
0293                     else if (!filters.isEmpty())
0294                     {
0295                         // We using the filter to make a more user friendly output (Custom Mode)
0296 
0297                         // Filter is not a list of complete tag keys
0298 
0299                         if      (!filters.at(0).contains(QLatin1Char('.')) &&
0300                                  filters.contains(it.key().section(QLatin1Char('.'), 2, 2)))
0301                         {
0302                             QString tagTitle = m_parent->getTagTitle(it.key());
0303                             new MetadataListViewItem(parentifDItem, it.key(), tagTitle, it.value());
0304                             ++subItems;
0305                             filters.removeAll(it.key());
0306                         }
0307                         else if (filters.contains(it.key()))
0308                         {
0309                             QString tagTitle = m_parent->getTagTitle(it.key());
0310                             new MetadataListViewItem(parentifDItem, it.key(), tagTitle, it.value());
0311                             ++subItems;
0312                             filters.removeAll(it.key());
0313                         }
0314                         else if (it.key().contains(QLatin1String("]/")))
0315                         {
0316                             // Special case to filter metadata tags in bag containers
0317 
0318                             int propIndex = it.key().lastIndexOf(QLatin1Char(':'));
0319                             int nameIndex = it.key().lastIndexOf(QLatin1Char('.'));
0320 
0321                             if ((propIndex != -1) && (nameIndex != -1))
0322                             {
0323                                 QString property  = it.key().mid(propIndex + 1);
0324                                 QString nameSpace = it.key().left(nameIndex + 1);
0325 
0326                                 if (filters.contains(nameSpace + property))
0327                                 {
0328                                     QString tagTitle = m_parent->getTagTitle(it.key());
0329                                     new MetadataListViewItem(parentifDItem, it.key(), tagTitle, it.value());
0330                                     ++subItems;
0331 
0332                                     if (it.key().contains(QLatin1String("[1]")))
0333                                     {
0334                                         filters.removeAll(nameSpace + property);
0335                                     }
0336                                 }
0337                             }
0338                         }
0339                     }
0340                 }
0341             }
0342         }
0343 
0344         // We checking if the last IfD have any items. If no, we remove it.
0345 
0346         if ((subItems == 0) && parentifDItem)
0347         {
0348             delete parentifDItem;
0349         }
0350     }
0351 
0352     // Add not found tags from filter as grey items.
0353 
0354     if (!filters.isEmpty() &&
0355         (filters.at(0) != QLatin1String("FULL")) &&
0356         filters.at(0).contains(QLatin1Char('.')))
0357     {
0358         Q_FOREACH (const QString& key, filters)
0359         {
0360             MdKeyListViewItem* pitem = findMdKeyItem(key);
0361 
0362             if (!pitem)
0363             {
0364                 pitem = new MdKeyListViewItem(this, key.section(QLatin1Char('.'), 1, 1));
0365             }
0366 
0367             QString tagTitle = m_parent->getTagTitle(key);
0368             new MetadataListViewItem(pitem, key, tagTitle);
0369         }
0370     }
0371 
0372     setCurrentItemByKey(m_selectedItemKey);
0373     update();
0374 }
0375 
0376 void MetadataListView::slotSearchTextChanged(const SearchTextSettings& settings)
0377 {
0378     bool query     = false;
0379     QString search = settings.text;
0380 
0381     // Restore all MdKey items.
0382 
0383     QTreeWidgetItemIterator it2(this);
0384 
0385     while (*it2)
0386     {
0387         MdKeyListViewItem* const item = dynamic_cast<MdKeyListViewItem*>(*it2);
0388 
0389         if (item)
0390         {
0391             item->setHidden(false);
0392         }
0393 
0394         ++it2;
0395     }
0396 
0397     QTreeWidgetItemIterator it(this);
0398 
0399     while (*it)
0400     {
0401         MetadataListViewItem* const item = dynamic_cast<MetadataListViewItem*>(*it);
0402 
0403         if (item)
0404         {
0405             if (item->text(0).contains(search, settings.caseSensitive) ||
0406                 item->text(1).contains(search, settings.caseSensitive))
0407             {
0408                 query = true;
0409                 item->setHidden(false);
0410             }
0411             else
0412             {
0413                 item->setHidden(true);
0414             }
0415         }
0416 
0417         ++it;
0418     }
0419 
0420     // If we found MdKey items alone, we hide it...
0421 
0422     cleanUpMdKeyItem();
0423 
0424     Q_EMIT signalTextFilterMatch(query);
0425 }
0426 
0427 void MetadataListView::cleanUpMdKeyItem()
0428 {
0429     QTreeWidgetItemIterator it(this);
0430 
0431     while (*it)
0432     {
0433         MdKeyListViewItem* const item = dynamic_cast<MdKeyListViewItem*>(*it);
0434 
0435         if (item)
0436         {
0437             int children = item->childCount();
0438             int visibles = 0;
0439 
0440             for (int i = 0 ; i < children ; ++i)
0441             {
0442                 QTreeWidgetItem* const citem = (*it)->child(i);
0443 
0444                 if (!citem->isHidden())
0445                 {
0446                     ++visibles;
0447                 }
0448             }
0449 
0450             if (!children || !visibles)
0451             {
0452                 item->setHidden(true);
0453             }
0454         }
0455 
0456         ++it;
0457     }
0458 }
0459 
0460 MdKeyListViewItem* MetadataListView::findMdKeyItem(const QString& key)
0461 {
0462     QTreeWidgetItemIterator it(this);
0463 
0464     while (*it)
0465     {
0466         MdKeyListViewItem* const item = dynamic_cast<MdKeyListViewItem*>(*it);
0467 
0468         if (item)
0469         {
0470             if (key.section(QLatin1Char('.'), 1, 1) == item->getKey())
0471             {
0472                 return item;
0473             }
0474         }
0475 
0476         ++it;
0477     }
0478 
0479     return nullptr;
0480 }
0481 
0482 } // namespace Digikam
0483 
0484 #include "moc_metadatalistview.cpp"