File indexing completed on 2024-05-19 04:23:36
0001 // SPDX-FileCopyrightText: 2003-2013 Jesper K. Pedersen <jesper.pedersen@kdab.com> 0002 // SPDX-FileCopyrightText: 2005-2007 Dirk Mueller <mueller@kde.org> 0003 // SPDX-FileCopyrightText: 2006-2008 Tuomas Suutari <tuomas@nepnep.net> 0004 // SPDX-FileCopyrightText: 2007-2010 Jan Kundrát <jkt@flaska.net> 0005 // SPDX-FileCopyrightText: 2007-2008 Laurent Montel <montel@kde.org> 0006 // SPDX-FileCopyrightText: 2008 Henner Zeller <h.zeller@acm.org> 0007 // SPDX-FileCopyrightText: 2009 Hassan Ibraheem <hasan.ibraheem@gmail.com> 0008 // SPDX-FileCopyrightText: 2011-2012 Miika Turkia <miika.turkia@gmail.com> 0009 // SPDX-FileCopyrightText: 2012-2016, 2018-2023 Johannes Zarl-Zierl <johannes@zarl-zierl.at> 0010 // SPDX-FileCopyrightText: 2014-2020 Tobias Leupold <tl@stonemx.de> 0011 // SPDX-FileCopyrightText: 2017-2020 Robert Krawitz <rlk@alum.mit.edu> 0012 // 0013 // SPDX-License-Identifier: GPL-2.0-or-later 0014 0015 #include "ImageSearchInfo.h" 0016 0017 #include "AndCategoryMatcher.h" 0018 #include "CategoryMatcher.h" 0019 #include "ContainerCategoryMatcher.h" 0020 #include "ExactCategoryMatcher.h" 0021 #include "NegationCategoryMatcher.h" 0022 #include "NoTagCategoryMatcher.h" 0023 #include "OrCategoryMatcher.h" 0024 #include "ValueCategoryMatcher.h" 0025 #include "WildcardCategoryMatcher.h" 0026 0027 #include <DB/ImageDB.h> 0028 #include <ImageManager/RawImageDecoder.h> 0029 #include <kpabase/FileExtensions.h> 0030 #include <kpabase/Logging.h> 0031 #include <kpabase/SettingsData.h> 0032 0033 #include <KConfigGroup> 0034 #include <KLocalizedString> 0035 #include <KSharedConfig> 0036 #include <QApplication> 0037 #include <QRegExp> 0038 #include <QRegularExpression> 0039 0040 using namespace DB; 0041 0042 static QAtomicInt s_matchGeneration; 0043 static int nextGeneration() 0044 { 0045 return s_matchGeneration++; 0046 } 0047 0048 ImageSearchInfo::ImageSearchInfo() 0049 : m_matchGeneration(nextGeneration()) 0050 { 0051 } 0052 0053 ImageSearchInfo::ImageSearchInfo(const ImageDate &date, 0054 const QString &label, const QString &description) 0055 : m_date(date) 0056 , m_label(label) 0057 , m_description(description) 0058 , m_isNull(false) 0059 , m_matchGeneration(nextGeneration()) 0060 { 0061 } 0062 0063 ImageSearchInfo::ImageSearchInfo(const ImageDate &date, 0064 const QString &label, const QString &description, 0065 const QString &fnPattern) 0066 : m_date(date) 0067 , m_label(label) 0068 , m_description(description) 0069 , m_fnPattern(fnPattern) 0070 , m_isNull(false) 0071 , m_matchGeneration(nextGeneration()) 0072 { 0073 } 0074 0075 QString ImageSearchInfo::label() const 0076 { 0077 return m_label; 0078 } 0079 0080 QRegExp ImageSearchInfo::fnPattern() const 0081 { 0082 return m_fnPattern; 0083 } 0084 0085 QString ImageSearchInfo::description() const 0086 { 0087 return m_description; 0088 } 0089 0090 void ImageSearchInfo::checkIfNull() 0091 { 0092 if (m_compiled.valid || isNull()) 0093 return; 0094 if (m_date.isNull() && m_label.isEmpty() && m_description.isEmpty() 0095 && m_rating == -1 && m_megapixel == 0 && m_exifSearchInfo.isEmpty() 0096 && m_categoryMatchText.isEmpty() 0097 && freeformMatchText().isEmpty() 0098 #ifdef HAVE_KGEOMAP 0099 && !m_regionSelection.first.hasCoordinates() && !m_regionSelection.second.hasCoordinates() 0100 #endif 0101 ) { 0102 m_isNull = true; 0103 } 0104 } 0105 0106 bool ImageSearchInfo::isNull() const 0107 { 0108 return m_isNull; 0109 } 0110 0111 bool ImageSearchInfo::isCacheable() const 0112 { 0113 return m_isCacheable; 0114 } 0115 0116 void ImageSearchInfo::setCacheable(bool cacheable) 0117 { 0118 m_isCacheable = cacheable; 0119 } 0120 0121 bool ImageSearchInfo::match(ImageInfoPtr info) const 0122 { 0123 if (m_isNull) 0124 return true; 0125 0126 if (m_isCacheable && info->matchGeneration() == m_matchGeneration) 0127 return info->isMatched(); 0128 0129 bool ok = doMatch(info); 0130 0131 if (m_isCacheable) { 0132 info->setMatchGeneration(m_matchGeneration); 0133 info->setIsMatched(ok); 0134 } 0135 return ok; 0136 } 0137 0138 bool ImageSearchInfo::doMatch(ImageInfoPtr info) const 0139 { 0140 if (!m_compiled.valid) 0141 compile(); 0142 0143 // -------------------------------------------------- Rating 0144 0145 // ok = ok && (_rating == -1 ) || ( _rating == info->rating() ); 0146 if (m_rating != -1) { 0147 switch (m_ratingSearchMode) { 0148 case 1: 0149 // Image rating at least selected 0150 if (m_rating > info->rating()) 0151 return false; 0152 break; 0153 case 2: 0154 // Image rating less than selected 0155 if (m_rating < info->rating()) 0156 return false; 0157 break; 0158 case 3: 0159 // Image rating not equal 0160 if (m_rating == info->rating()) 0161 return false; 0162 break; 0163 default: 0164 if (m_rating != info->rating()) 0165 return false; 0166 break; 0167 } 0168 } 0169 0170 // -------------------------------------------------- Resolution 0171 if (m_megapixel && (m_megapixel * 1000000 > info->size().width() * info->size().height())) 0172 return false; 0173 0174 if (m_max_megapixel && m_max_megapixel < m_megapixel && (m_max_megapixel * 1000000 < info->size().width() * info->size().height())) 0175 return false; 0176 0177 // -------------------------------------------------- Date 0178 Utilities::FastDateTime actualStart = info->date().start(); 0179 Utilities::FastDateTime actualEnd = info->date().end(); 0180 0181 if (m_date.start().isValid()) { 0182 if (actualEnd < m_date.start() || (m_date.end().isValid() && actualStart > m_date.end())) 0183 return false; 0184 } else if (m_date.end().isValid() && actualStart > m_date.end()) { 0185 return false; 0186 } 0187 0188 // -------------------------------------------------- Label 0189 if (!m_label.isEmpty() && info->label().indexOf(m_label) == -1) 0190 return false; 0191 0192 // -------------------------------------------------- RAW 0193 if (m_searchRAW && !KPABase::isUsableRawImage(info->fileName())) 0194 return false; 0195 0196 #ifdef HAVE_MARBLE 0197 // Search for GPS Position 0198 if (!m_regionSelection.isNull()) { 0199 if (!info->coordinates().hasCoordinates()) 0200 return false; 0201 0202 if (!m_regionSelection.contains(info->coordinates())) 0203 return false; 0204 } 0205 #endif 0206 0207 // -------------------------------------------------- File name pattern 0208 if (!m_fnPattern.isEmpty() && m_fnPattern.indexIn(info->fileName().relative()) == -1) 0209 return false; 0210 0211 // -------------------------------------------------- Options 0212 // alreadyMatched map is used to make it possible to search for 0213 // Jesper & None 0214 QMap<QString, StringSet> alreadyMatched; 0215 for (CategoryMatcher *optionMatcher : m_compiled.categoryMatchers) { 0216 if (!optionMatcher->eval(info, alreadyMatched)) 0217 return false; 0218 } 0219 0220 // -------------------------------------------------- Text 0221 if (!m_description.isEmpty()) { 0222 const QString &txt(info->description()); 0223 const QStringList list = m_description.split(QChar::fromLatin1(' '), Qt::SkipEmptyParts); 0224 for (const QString &word : list) { 0225 if (txt.indexOf(word, 0, Qt::CaseInsensitive) == -1) 0226 return false; 0227 } 0228 } 0229 0230 // -------------------------------------------------- EXIF 0231 if (!m_exifSearchInfo.matches(info->fileName())) 0232 return false; 0233 0234 // -------------------------------------------------- Freeform 0235 if (!freeformMatchText().isEmpty()) { 0236 const auto re = m_freeformMatcher.regularExpression(); 0237 if (!re.match(info->label()).hasMatch() 0238 && !re.match(info->fileName().relative()).hasMatch() 0239 && !re.match(info->description()).hasMatch() 0240 && !m_freeformMatcher.eval(info)) { 0241 return false; 0242 } 0243 } 0244 0245 return true; 0246 } 0247 0248 QString ImageSearchInfo::categoryMatchText(const QString &name) const 0249 { 0250 return m_categoryMatchText[name]; 0251 } 0252 0253 void ImageSearchInfo::setCategoryMatchText(const QString &name, const QString &value) 0254 { 0255 if (value.isEmpty()) { 0256 m_categoryMatchText.remove(name); 0257 } else { 0258 m_categoryMatchText[name] = value; 0259 } 0260 m_isNull = false; 0261 m_compiled.valid = false; 0262 m_matchGeneration = nextGeneration(); 0263 } 0264 0265 void ImageSearchInfo::addAnd(const QString &category, const QString &value) 0266 { 0267 // Escape literal "&"s in value by doubling it 0268 QString escapedValue = value; 0269 escapedValue.replace(QString::fromUtf8("&"), QString::fromUtf8("&&")); 0270 0271 QString val = categoryMatchText(category); 0272 if (!val.isEmpty()) 0273 val += QString::fromLatin1(" & ") + escapedValue; 0274 else 0275 val = escapedValue; 0276 0277 setCategoryMatchText(category, val); 0278 m_isNull = false; 0279 m_compiled.valid = false; 0280 m_matchGeneration = nextGeneration(); 0281 } 0282 0283 void ImageSearchInfo::setRating(short rating) 0284 { 0285 m_rating = rating; 0286 m_isNull = false; 0287 m_matchGeneration = nextGeneration(); 0288 // compiled data is not affected 0289 } 0290 0291 void ImageSearchInfo::setMegaPixel(short megapixel) 0292 { 0293 m_megapixel = megapixel; 0294 m_matchGeneration = nextGeneration(); 0295 } 0296 0297 void ImageSearchInfo::setMaxMegaPixel(short max_megapixel) 0298 { 0299 m_max_megapixel = max_megapixel; 0300 m_matchGeneration = nextGeneration(); 0301 } 0302 0303 void ImageSearchInfo::setSearchMode(int index) 0304 { 0305 m_ratingSearchMode = index; 0306 m_matchGeneration = nextGeneration(); 0307 } 0308 0309 void ImageSearchInfo::setSearchRAW(bool searchRAW) 0310 { 0311 m_searchRAW = searchRAW; 0312 m_matchGeneration = nextGeneration(); 0313 } 0314 0315 QString ImageSearchInfo::toString() const 0316 { 0317 QString res; 0318 bool first = true; 0319 for (QMap<QString, QString>::ConstIterator it = m_categoryMatchText.begin(); it != m_categoryMatchText.end(); ++it) { 0320 if (!it.value().isEmpty()) { 0321 if (first) 0322 first = false; 0323 else 0324 res += QString::fromLatin1(" / "); 0325 0326 QString txt = it.value(); 0327 if (txt == ImageDB::NONE()) 0328 txt = i18nc("As in No persons, no locations etc. I do realize that translators may have problem with this, " 0329 "but I need some how to indicate the category, and users may create their own categories, so this is " 0330 "the best I can do - Jesper.", 0331 "No %1", it.key()); 0332 0333 if (txt.contains(QString::fromLatin1("|"))) 0334 txt.replace(QString::fromLatin1("&"), QString::fromLatin1(" %1 ").arg(i18n("and"))); 0335 0336 else 0337 txt.replace(QString::fromLatin1("&"), QString::fromLatin1(" / ")); 0338 0339 txt.replace(QString::fromLatin1("|"), QString::fromLatin1(" %1 ").arg(i18n("or"))); 0340 txt.replace(QString::fromLatin1("!"), QString::fromLatin1(" %1 ").arg(i18n("not"))); 0341 txt.replace(ImageDB::NONE(), i18nc("As in no other persons, or no other locations. " 0342 "I do realize that translators may have problem with this, " 0343 "but I need some how to indicate the category, and users may create their own categories, so this is " 0344 "the best I can do - Jesper.", 0345 "No other %1", it.key())); 0346 0347 res += txt.simplified(); 0348 } 0349 } 0350 return res; 0351 } 0352 0353 void ImageSearchInfo::debug() 0354 { 0355 for (QMap<QString, QString>::Iterator it = m_categoryMatchText.begin(); it != m_categoryMatchText.end(); ++it) { 0356 qCDebug(DBCategoryMatcherLog) << it.key() << ", " << it.value(); 0357 } 0358 } 0359 0360 QVariantMap ImageSearchInfo::getLockData() const 0361 { 0362 QVariantMap pairs; 0363 pairs[QString::fromLatin1("label")] = m_label; 0364 pairs[QString::fromLatin1("description")] = m_description; 0365 pairs[QString::fromLatin1("categories")] = QVariant(m_categoryMatchText.keys()); 0366 for (QMap<QString, QString>::ConstIterator it = m_categoryMatchText.begin(); it != m_categoryMatchText.end(); ++it) { 0367 pairs[it.key()] = it.value(); 0368 } 0369 return pairs; 0370 } 0371 0372 ImageSearchInfo ImageSearchInfo::loadLock(const QMap<QString, QVariant> &keyValuePairs) 0373 { 0374 ImageSearchInfo info; 0375 info.m_label = keyValuePairs.value(QString::fromLatin1("label"), {}).toString(); 0376 info.m_description = keyValuePairs.value(QString::fromLatin1("description"), {}).toString(); 0377 QStringList categories = keyValuePairs.value(QString::fromLatin1("categories"), {}).toStringList(); 0378 for (QStringList::ConstIterator it = categories.constBegin(); it != categories.constEnd(); ++it) { 0379 info.setCategoryMatchText(*it, keyValuePairs.value(*it, {}).toString()); 0380 } 0381 return info; 0382 } 0383 0384 void ImageSearchInfo::compile() const 0385 { 0386 qCDebug(DBCategoryMatcherLog) << "Compiling search info..."; 0387 m_exifSearchInfo.search(); 0388 0389 CompiledDataPrivate compiledData; 0390 0391 for (QMap<QString, QString>::ConstIterator it = m_categoryMatchText.begin(); it != m_categoryMatchText.end(); ++it) { 0392 const QString category = it.key(); 0393 const QString matchText = it.value(); 0394 0395 const QStringList orParts = matchText.split(QString::fromLatin1("|"), Qt::SkipEmptyParts); 0396 DB::ContainerCategoryMatcher *orMatcher = new DB::OrCategoryMatcher; 0397 0398 for (QString orPart : orParts) { 0399 // Split by " & ", not only by "&", so that the doubled "&"s won't be used as a split point 0400 const QStringList andParts = orPart.split(QString::fromLatin1(" & "), Qt::SkipEmptyParts); 0401 0402 DB::ContainerCategoryMatcher *andMatcher; 0403 bool exactMatch = false; 0404 bool negate = false; 0405 andMatcher = new DB::AndCategoryMatcher; 0406 0407 for (QString str : andParts) { 0408 static const QRegExp regexp(QString::fromLatin1("^\\s*!\\s*(.*)$")); 0409 if (regexp.exactMatch(str)) { // str is preceded with NOT 0410 negate = true; 0411 str = regexp.cap(1); 0412 } 0413 str = str.trimmed(); 0414 CategoryMatcher *valueMatcher; 0415 if (str == ImageDB::NONE()) { // mark AND-group as containing a "No other" condition 0416 exactMatch = true; 0417 continue; 0418 } else { 0419 valueMatcher = new DB::ValueCategoryMatcher(category, str); 0420 if (negate) { 0421 valueMatcher = new DB::NegationCategoryMatcher(valueMatcher); 0422 negate = false; 0423 } 0424 } 0425 andMatcher->addElement(valueMatcher); 0426 } 0427 if (exactMatch) { 0428 DB::CategoryMatcher *exactMatcher = nullptr; 0429 // if andMatcher has exactMatch set, but no CategoryMatchers, then 0430 // matching "category / None" is what we want: 0431 if (andMatcher->mp_elements.count() == 0) { 0432 delete andMatcher; 0433 exactMatcher = new DB::NoTagCategoryMatcher(category); 0434 } else { 0435 ExactCategoryMatcher *noOtherMatcher = new ExactCategoryMatcher(category); 0436 if (andMatcher->mp_elements.count() == 1) 0437 noOtherMatcher->setMatcher(andMatcher->mp_elements[0]); 0438 else 0439 noOtherMatcher->setMatcher(andMatcher); 0440 exactMatcher = noOtherMatcher; 0441 } 0442 if (negate) 0443 exactMatcher = new DB::NegationCategoryMatcher(exactMatcher); 0444 orMatcher->addElement(exactMatcher); 0445 } else if (andMatcher->mp_elements.count() == 1) 0446 orMatcher->addElement(andMatcher->mp_elements[0]); 0447 else if (andMatcher->mp_elements.count() > 1) 0448 orMatcher->addElement(andMatcher); 0449 else 0450 delete andMatcher; 0451 } 0452 CategoryMatcher *matcher = nullptr; 0453 if (orMatcher->mp_elements.count() == 1) 0454 matcher = orMatcher->mp_elements[0]; 0455 else if (orMatcher->mp_elements.count() > 1) 0456 matcher = orMatcher; 0457 else 0458 delete orMatcher; 0459 0460 if (matcher) { 0461 compiledData.categoryMatchers.append(matcher); 0462 if (DBCategoryMatcherLog().isDebugEnabled()) { 0463 qCDebug(DBCategoryMatcherLog) << "Matching text '" << matchText << "' in category " << category << ":"; 0464 matcher->debug(0); 0465 qCDebug(DBCategoryMatcherLog) << "."; 0466 } 0467 } 0468 } 0469 compiledData.valid = true; 0470 std::swap(m_compiled, compiledData); 0471 } 0472 0473 void ImageSearchInfo::debugMatcher() const 0474 { 0475 if (!m_compiled.valid) 0476 compile(); 0477 0478 qCDebug(DBCategoryMatcherLog, "And:"); 0479 for (CategoryMatcher *optionMatcher : m_compiled.categoryMatchers) { 0480 optionMatcher->debug(1); 0481 } 0482 } 0483 0484 QList<QList<SimpleCategoryMatcher *>> ImageSearchInfo::query() const 0485 { 0486 if (!m_compiled.valid) 0487 compile(); 0488 0489 // Combine _optionMachers to one list of lists in Disjunctive 0490 // Normal Form and return it. 0491 0492 QList<CategoryMatcher *>::Iterator it = m_compiled.categoryMatchers.begin(); 0493 QList<QList<SimpleCategoryMatcher *>> result; 0494 if (it == m_compiled.categoryMatchers.end()) 0495 return result; 0496 0497 result = convertMatcher(*it); 0498 ++it; 0499 0500 for (; it != m_compiled.categoryMatchers.end(); ++it) { 0501 QList<QList<SimpleCategoryMatcher *>> current = convertMatcher(*it); 0502 QList<QList<SimpleCategoryMatcher *>> oldResult = result; 0503 result.clear(); 0504 0505 for (QList<SimpleCategoryMatcher *> resultIt : oldResult) { 0506 for (QList<SimpleCategoryMatcher *> currentIt : current) { 0507 QList<SimpleCategoryMatcher *> tmp; 0508 tmp += resultIt; 0509 tmp += currentIt; 0510 result.append(tmp); 0511 } 0512 } 0513 } 0514 return result; 0515 } 0516 0517 Utilities::StringSet ImageSearchInfo::findAlreadyMatched(const QString &group) const 0518 { 0519 Utilities::StringSet result; 0520 const QString str = categoryMatchText(group); 0521 if (str.contains(QString::fromLatin1("|"))) { 0522 return result; 0523 } 0524 0525 const QStringList list = str.split(QString::fromLatin1("&"), Qt::SkipEmptyParts); 0526 for (const QString &part : list) { 0527 const QString nm = part.trimmed(); 0528 if (!nm.contains(QString::fromLatin1("!"))) 0529 result.insert(nm); 0530 } 0531 return result; 0532 } 0533 0534 QList<SimpleCategoryMatcher *> ImageSearchInfo::extractAndMatcher(CategoryMatcher *matcher) const 0535 { 0536 QList<SimpleCategoryMatcher *> result; 0537 0538 AndCategoryMatcher *andMatcher; 0539 SimpleCategoryMatcher *simpleMatcher; 0540 0541 if ((andMatcher = dynamic_cast<AndCategoryMatcher *>(matcher))) { 0542 for (CategoryMatcher *child : andMatcher->mp_elements) { 0543 SimpleCategoryMatcher *simpleMatcher = dynamic_cast<SimpleCategoryMatcher *>(child); 0544 Q_ASSERT(simpleMatcher); 0545 result.append(simpleMatcher); 0546 } 0547 } else if ((simpleMatcher = dynamic_cast<SimpleCategoryMatcher *>(matcher))) 0548 result.append(simpleMatcher); 0549 else 0550 Q_ASSERT(false); 0551 0552 return result; 0553 } 0554 0555 /** Convert matcher to Disjunctive Normal Form. 0556 * 0557 * @return OR-list of AND-lists. (e.g. OR(AND(a,b),AND(c,d))) 0558 */ 0559 QList<QList<SimpleCategoryMatcher *>> ImageSearchInfo::convertMatcher(CategoryMatcher *item) const 0560 { 0561 QList<QList<SimpleCategoryMatcher *>> result; 0562 OrCategoryMatcher *orMatcher; 0563 0564 if ((orMatcher = dynamic_cast<OrCategoryMatcher *>(item))) { 0565 for (CategoryMatcher *child : orMatcher->mp_elements) { 0566 result.append(extractAndMatcher(child)); 0567 } 0568 } else 0569 result.append(extractAndMatcher(item)); 0570 return result; 0571 } 0572 0573 QString ImageSearchInfo::freeformMatchText() const 0574 { 0575 return m_freeformMatcher.regularExpression().pattern(); 0576 } 0577 0578 void ImageSearchInfo::setFreeformMatchText(const QString &freeformMatchText) 0579 { 0580 setCacheable(false); 0581 QRegularExpression re { freeformMatchText, QRegularExpression::CaseInsensitiveOption }; 0582 m_freeformMatcher.setRegularExpression(re); 0583 m_isNull = m_isNull && freeformMatchText.isEmpty(); 0584 } 0585 0586 short ImageSearchInfo::rating() const 0587 { 0588 return m_rating; 0589 } 0590 0591 ImageDate ImageSearchInfo::date() const 0592 { 0593 return m_date; 0594 } 0595 0596 void ImageSearchInfo::addExifSearchInfo(const Exif::SearchInfo info) 0597 { 0598 m_exifSearchInfo = info; 0599 m_isNull = false; 0600 m_matchGeneration = nextGeneration(); 0601 } 0602 0603 void DB::ImageSearchInfo::renameCategory(const QString &oldName, const QString &newName) 0604 { 0605 m_categoryMatchText[newName] = m_categoryMatchText[oldName]; 0606 m_categoryMatchText.remove(oldName); 0607 m_compiled.valid = false; 0608 m_matchGeneration = nextGeneration(); 0609 } 0610 0611 #ifdef HAVE_MARBLE 0612 Map::GeoCoordinates::LatLonBox ImageSearchInfo::regionSelection() const 0613 { 0614 return m_regionSelection; 0615 } 0616 0617 void ImageSearchInfo::setRegionSelection(const Map::GeoCoordinates::LatLonBox &actRegionSelection) 0618 { 0619 m_regionSelection = actRegionSelection; 0620 if (!m_regionSelection.isNull()) { 0621 m_isNull = false; 0622 } 0623 m_matchGeneration = nextGeneration(); 0624 // compiled data is not affected 0625 } 0626 #endif 0627 0628 ImageSearchInfo::CompiledDataPrivate::CompiledDataPrivate(const ImageSearchInfo::CompiledDataPrivate &) 0629 { 0630 // copying invalidates the compiled data 0631 valid = false; 0632 } 0633 0634 ImageSearchInfo::CompiledDataPrivate::~CompiledDataPrivate() 0635 { 0636 qDeleteAll(categoryMatchers); 0637 categoryMatchers.clear(); 0638 } 0639 0640 ImageSearchInfo::CompiledDataPrivate &ImageSearchInfo::CompiledDataPrivate::operator=(const ImageSearchInfo::CompiledDataPrivate &) 0641 { 0642 // copying invalidates the compiled data 0643 valid = false; 0644 return *this; 0645 } 0646 0647 // vi:expandtab:tabstop=4 shiftwidth=4: