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"