File indexing completed on 2025-04-27 03:58:33

0001 /* ============================================================
0002  *
0003  * This file is a part of digiKam project
0004  * https://www.digikam.org
0005  *
0006  * Date        : 2009-07-16
0007  * Description : metadata selector.
0008  *
0009  * SPDX-FileCopyrightText: 2009-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 "metadataselector.h"
0016 
0017 // Qt includes
0018 
0019 #include <QHeaderView>
0020 #include <QGridLayout>
0021 #include <QApplication>
0022 #include <QPushButton>
0023 #include <QStyle>
0024 
0025 // KDE includes
0026 
0027 #include <klocalizedstring.h>
0028 
0029 // Local includes
0030 
0031 #include "ditemtooltip.h"
0032 #include "mdkeylistviewitem.h"
0033 
0034 namespace Digikam
0035 {
0036 
0037 MetadataSelectorItem::MetadataSelectorItem(MdKeyListViewItem* const parent,
0038                                            const QString& key,
0039                                            const QString& title,
0040                                            const QString& desc)
0041     : QTreeWidgetItem(parent),
0042       m_key          (key),
0043       m_parent       (parent)
0044 {
0045     setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsUserCheckable);
0046     setCheckState(0, Qt::Unchecked);
0047     setChildIndicatorPolicy(QTreeWidgetItem::DontShowIndicator);
0048 
0049     setText(0, title);
0050     setToolTip(0, key);
0051 
0052     QString descVal = desc.simplified();
0053 
0054     if (descVal.length() > 512)
0055     {
0056         descVal.truncate(512);
0057         descVal.append(QLatin1String("..."));
0058     }
0059 
0060     setText(1, descVal);
0061 
0062     DToolTipStyleSheet cnt;
0063     setToolTip(1, QLatin1String
0064                ("<qt><p>") + cnt.breakString(descVal) + QLatin1String("</p></qt>"));
0065 }
0066 
0067 MetadataSelectorItem::~MetadataSelectorItem()
0068 {
0069 }
0070 
0071 QString MetadataSelectorItem::key() const
0072 {
0073     return m_key;
0074 }
0075 
0076 QString MetadataSelectorItem::mdKeyTitle() const
0077 {
0078     return (m_parent ? m_parent->text(0) : QString());
0079 }
0080 
0081 // ------------------------------------------------------------------------------------
0082 
0083 MetadataSelector::MetadataSelector(MetadataSelectorView* const parent)
0084     : QTreeWidget(parent),
0085       m_parent   (parent)
0086 {
0087     setSelectionMode(QAbstractItemView::SingleSelection);
0088     setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
0089     setAllColumnsShowFocus(true);
0090     setUniformRowHeights(true);
0091     setColumnCount(2);
0092 
0093     QStringList labels;
0094     labels.append(i18nc("@title: metadata properties", "Name"));
0095     labels.append(i18nc("@title: metadata properties", "Description"));
0096     setHeaderLabels(labels);
0097     header()->setSectionResizeMode(0, QHeaderView::ResizeToContents);
0098     header()->setSectionResizeMode(1, QHeaderView::Stretch);
0099 
0100     setSortingEnabled(true);
0101     sortByColumn(0, Qt::AscendingOrder);
0102 }
0103 
0104 MetadataSelector::~MetadataSelector()
0105 {
0106 }
0107 
0108 void MetadataSelector::setTagsMap(const DMetadata::TagsMap& map)
0109 {
0110     clear();
0111 
0112     uint                    subItems      = 0;
0113     QString                 ifDItemName, currentIfDName;
0114     MdKeyListViewItem*      parentifDItem = nullptr;
0115     QList<QTreeWidgetItem*> toplevelItems;
0116 
0117     for (DMetadata::TagsMap::const_iterator it = map.constBegin();
0118          it != map.constEnd(); ++it)
0119     {
0120         // Check if we have changed group.
0121 
0122         if (m_parent->backend() == MetadataSelectorView::Exiv2Backend)
0123         {
0124             currentIfDName = it.key().section(QLatin1Char('.'), 1, 1);
0125         }
0126         else
0127         {
0128             currentIfDName = it.key().section(QLatin1Char('.'), 0, 0);
0129         }
0130 
0131         if (currentIfDName != ifDItemName)
0132         {
0133             ifDItemName = currentIfDName;
0134 
0135             // Remove the group header if it has no items.
0136 
0137             if ((subItems == 0) && parentifDItem)
0138             {
0139                 delete parentifDItem;
0140             }
0141 
0142             parentifDItem = new MdKeyListViewItem(nullptr, currentIfDName);
0143             toplevelItems << parentifDItem;
0144             subItems      = 0;
0145         }
0146 
0147         // Ignore all unknown Exif tags.
0148 
0149         if (!it.key().section(QLatin1Char('.'), 2, 2).startsWith(QLatin1String("0x")))
0150         {
0151             new MetadataSelectorItem(parentifDItem, it.key(),
0152                                      it.value().at(0),          // Name
0153                                      it.value().at(2));         // Description
0154             ++subItems;
0155         }
0156     }
0157 
0158     addTopLevelItems(toplevelItems);
0159 
0160     // We need to call setFirstColumnSpanned() in here again because the
0161     // widgets were added parentless and therefore no layout information was
0162     // present at construction time. Now that all items have a parent, we need
0163     // to trigger the method again.
0164 
0165     for (QList<QTreeWidgetItem*>::const_iterator it = toplevelItems.constBegin() ;
0166          it != toplevelItems.constEnd() ; ++it)
0167     {
0168         if (*it)
0169         {
0170             (*it)->setFirstColumnSpanned(true);
0171         }
0172     }
0173 
0174     expandAll();
0175 }
0176 
0177 void MetadataSelector::setcheckedTagsList(const QStringList& list)
0178 {
0179     QTreeWidgetItemIterator it(this);
0180 
0181     while (*it)
0182     {
0183         MetadataSelectorItem* const item = dynamic_cast<MetadataSelectorItem*>(*it);
0184 
0185         if (item && list.contains(item->key()))
0186         {
0187             item->setCheckState(0, Qt::Checked);
0188         }
0189 
0190         ++it;
0191     }
0192 }
0193 
0194 QStringList MetadataSelector::checkedTagsList()
0195 {
0196     QStringList list;
0197     QTreeWidgetItemIterator it(this, QTreeWidgetItemIterator::Checked);
0198 
0199     while (*it)
0200     {
0201         MetadataSelectorItem* const item = dynamic_cast<MetadataSelectorItem*>(*it);
0202 
0203         if (item)
0204         {
0205             list.append(item->key());
0206         }
0207 
0208         ++it;
0209     }
0210 
0211     return list;
0212 }
0213 
0214 void MetadataSelector::clearSelection()
0215 {
0216     collapseAll();
0217 
0218     QTreeWidgetItemIterator it(this, QTreeWidgetItemIterator::Checked);
0219 
0220     while (*it)
0221     {
0222         MetadataSelectorItem* const item = dynamic_cast<MetadataSelectorItem*>(*it);
0223 
0224         if (item)
0225         {
0226             item->setCheckState(0, Qt::Unchecked);
0227         }
0228 
0229         ++it;
0230     }
0231 
0232     expandAll();
0233 }
0234 
0235 void MetadataSelector::selectAll()
0236 {
0237     collapseAll();
0238 
0239     QTreeWidgetItemIterator it(this, QTreeWidgetItemIterator::NotChecked);
0240 
0241     while (*it)
0242     {
0243         MetadataSelectorItem* const item = dynamic_cast<MetadataSelectorItem*>(*it);
0244 
0245         if (item)
0246         {
0247             item->setCheckState(0, Qt::Checked);
0248         }
0249 
0250         ++it;
0251     }
0252 
0253     expandAll();
0254 }
0255 
0256 // ------------------------------------------------------------------------------------
0257 
0258 class Q_DECL_HIDDEN MetadataSelectorView::Private
0259 {
0260 public:
0261 
0262     explicit Private()
0263       : selectAllBtn       (nullptr),
0264         clearSelectionBtn  (nullptr),
0265         defaultSelectionBtn(nullptr),
0266         selector           (nullptr),
0267         searchBar          (nullptr),
0268         backend            (Exiv2Backend)
0269     {
0270     }
0271 
0272     QStringList       defaultFilter;
0273 
0274     QPushButton*      selectAllBtn;
0275     QPushButton*      clearSelectionBtn;
0276     QPushButton*      defaultSelectionBtn;
0277 
0278     MetadataSelector* selector;
0279 
0280     SearchTextBar*    searchBar;
0281     Backend           backend;
0282 };
0283 
0284 MetadataSelectorView::MetadataSelectorView(QWidget* const parent, Backend be)
0285     : QWidget(parent),
0286       d      (new Private)
0287 {
0288     d->backend              = be;
0289     const int spacing       = qMin(QApplication::style()->pixelMetric(QStyle::PM_LayoutHorizontalSpacing),
0290                              QApplication::style()->pixelMetric(QStyle::PM_LayoutVerticalSpacing));
0291 
0292     QGridLayout* const grid = new QGridLayout(this);
0293     d->selector             = new MetadataSelector(this);
0294     d->searchBar            = new SearchTextBar(this, QLatin1String("MetadataSelectorView"));
0295     d->selectAllBtn         = new QPushButton(i18nc("@action: metadata selector", "Select All"),this);
0296     d->clearSelectionBtn    = new QPushButton(i18nc("@action: metadata selector", "Clear"),this);
0297     d->defaultSelectionBtn  = new QPushButton(i18nc("@action: metadata selector", "Default"),this);
0298 
0299     grid->addWidget(d->selector,            0, 0, 1, 5);
0300     grid->addWidget(d->searchBar,           1, 0, 1, 1);
0301     grid->addWidget(d->selectAllBtn,        1, 2, 1, 1);
0302     grid->addWidget(d->clearSelectionBtn,   1, 3, 1, 1);
0303     grid->addWidget(d->defaultSelectionBtn, 1, 4, 1, 1);
0304     grid->setColumnStretch(0, 10);
0305     grid->setRowStretch(0, 10);
0306     grid->setContentsMargins(spacing, spacing, spacing, spacing);
0307     grid->setSpacing(spacing);
0308 
0309     setControlElements(SearchBar | SelectAllBtn | DefaultBtn | ClearBtn);
0310 
0311     connect(d->searchBar, SIGNAL(signalSearchTextSettings(SearchTextSettings)),
0312             this, SLOT(slotSearchTextChanged(SearchTextSettings)));
0313 
0314     connect(d->selectAllBtn, SIGNAL(clicked()),
0315             this, SLOT(slotSelectAll()));
0316 
0317     connect(d->defaultSelectionBtn, SIGNAL(clicked()),
0318             this, SLOT(slotDeflautSelection()));
0319 
0320     connect(d->clearSelectionBtn, SIGNAL(clicked()),
0321             this, SLOT(slotClearSelection()));
0322 }
0323 
0324 MetadataSelectorView::~MetadataSelectorView()
0325 {
0326     delete d;
0327 }
0328 
0329 void MetadataSelectorView::setTagsMap(const DMetadata::TagsMap& map)
0330 {
0331     d->selector->setTagsMap(map);
0332 }
0333 
0334 void MetadataSelectorView::setcheckedTagsList(const QStringList& list)
0335 {
0336     d->selector->setcheckedTagsList(list);
0337 }
0338 
0339 void MetadataSelectorView::setDefaultFilter(const QStringList& list)
0340 {
0341     d->defaultFilter = list;
0342 }
0343 
0344 QStringList MetadataSelectorView::defaultFilter() const
0345 {
0346     return d->defaultFilter;
0347 }
0348 
0349 int MetadataSelectorView::itemsCount() const
0350 {
0351     return d->selector->model()->rowCount();
0352 }
0353 
0354 MetadataSelectorView::Backend MetadataSelectorView::backend() const
0355 {
0356     return d->backend;
0357 }
0358 
0359 QStringList MetadataSelectorView::checkedTagsList() const
0360 {
0361     d->searchBar->clear();
0362     return d->selector->checkedTagsList();
0363 }
0364 
0365 void MetadataSelectorView::slotSearchTextChanged(const SearchTextSettings& settings)
0366 {
0367     QString search       = settings.text;
0368     bool atleastOneMatch = false;
0369 
0370     // Restore all MdKey items.
0371 
0372     QTreeWidgetItemIterator it2(d->selector);
0373 
0374     while (*it2)
0375     {
0376         MdKeyListViewItem* const item = dynamic_cast<MdKeyListViewItem*>(*it2);
0377 
0378         if (item)
0379         {
0380             item->setHidden(false);
0381         }
0382 
0383         ++it2;
0384     }
0385 
0386     QTreeWidgetItemIterator it(d->selector);
0387 
0388     while (*it)
0389     {
0390         MetadataSelectorItem* const item = dynamic_cast<MetadataSelectorItem*>(*it);
0391 
0392         if (item)
0393         {
0394             bool match = item->text(0).contains(search, settings.caseSensitive) ||
0395                          item->mdKeyTitle().contains(search, settings.caseSensitive);
0396 
0397             if (match)
0398             {
0399                 atleastOneMatch = true;
0400                 item->setHidden(false);
0401             }
0402             else
0403             {
0404                 item->setHidden(true);
0405             }
0406         }
0407 
0408         ++it;
0409     }
0410 
0411     // If we found MdKey items alone, we hide it...
0412 
0413     cleanUpMdKeyItem();
0414 
0415     d->searchBar->slotSearchResult(atleastOneMatch);
0416 }
0417 
0418 void MetadataSelectorView::cleanUpMdKeyItem()
0419 {
0420     QTreeWidgetItemIterator it(d->selector);
0421 
0422     while (*it)
0423     {
0424         MdKeyListViewItem* const item = dynamic_cast<MdKeyListViewItem*>(*it);
0425 
0426         if (item)
0427         {
0428             int children = item->childCount();
0429             int visibles = 0;
0430 
0431             for (int i = 0 ; i < children ; ++i)
0432             {
0433                 QTreeWidgetItem* const citem = (*it)->child(i);
0434 
0435                 if (!citem->isHidden())
0436                 {
0437                     ++visibles;
0438                 }
0439             }
0440 
0441             if (!children || !visibles)
0442             {
0443                 item->setHidden(true);
0444             }
0445         }
0446 
0447         ++it;
0448     }
0449 }
0450 
0451 void MetadataSelectorView::slotDeflautSelection()
0452 {
0453     slotClearSelection();
0454 
0455     QApplication::setOverrideCursor(Qt::WaitCursor);
0456     d->selector->collapseAll();
0457 
0458     QTreeWidgetItemIterator it(d->selector);
0459 
0460     while (*it)
0461     {
0462         MetadataSelectorItem* const item = dynamic_cast<MetadataSelectorItem*>(*it);
0463 
0464         if (item)
0465         {
0466             if (d->defaultFilter.contains(item->text(0)))
0467             {
0468                 item->setCheckState(0, Qt::Checked);
0469             }
0470         }
0471 
0472         ++it;
0473     }
0474 
0475     d->selector->expandAll();
0476     QApplication::restoreOverrideCursor();
0477 }
0478 
0479 void MetadataSelectorView::slotSelectAll()
0480 {
0481     QApplication::setOverrideCursor(Qt::WaitCursor);
0482     d->selector->selectAll();
0483     QApplication::restoreOverrideCursor();
0484 }
0485 
0486 void MetadataSelectorView::slotClearSelection()
0487 {
0488     QApplication::setOverrideCursor(Qt::WaitCursor);
0489     d->selector->clearSelection();
0490     QApplication::restoreOverrideCursor();
0491 }
0492 
0493 void MetadataSelectorView::setControlElements(ControlElements controllerMask)
0494 {
0495     d->searchBar->setVisible(controllerMask & SearchBar);
0496     d->selectAllBtn->setVisible(controllerMask & SelectAllBtn);
0497     d->clearSelectionBtn->setVisible(controllerMask & ClearBtn);
0498     d->defaultSelectionBtn->setVisible(controllerMask & DefaultBtn);
0499 }
0500 
0501 void MetadataSelectorView::clearSelection()
0502 {
0503     slotClearSelection();
0504 }
0505 
0506 void MetadataSelectorView::selectAll()
0507 {
0508     slotSelectAll();
0509 }
0510 
0511 void MetadataSelectorView::selectDefault()
0512 {
0513     slotDeflautSelection();
0514 }
0515 
0516 } // namespace Digikam
0517 
0518 #include "moc_metadataselector.cpp"