File indexing completed on 2024-05-12 15:59:51

0001 /*
0002  *  SPDX-FileCopyrightText: 2019 Agata Cacko <cacko.azh@gmail.com>
0003  *
0004  *  SPDX-License-Identifier: GPL-2.0-or-later
0005  */
0006 
0007 #include "KisResourceSearchBoxFilter.h"
0008 
0009 
0010 #include <QRegExp>
0011 #include <QRegularExpression>
0012 #include <QList>
0013 #include <QSet>
0014 #include <kis_debug.h>
0015 
0016 class Q_DECL_HIDDEN KisResourceSearchBoxFilter::Private
0017 {
0018 public:
0019     Private()
0020         : searchTokenizer("\\s*,+\\s*")
0021     {}
0022 
0023     QRegularExpression searchTokenizer;
0024 
0025     QChar excludeBegin {'!'};
0026     QChar tagBegin {'#'};
0027     QChar exactMatchBeginEnd {'"'};
0028 
0029     QSet<QString> tagExactMatchesIncluded;
0030     QSet<QString> tagExactMatchesExcluded;
0031     QSet<QString> resourceExactMatchesIncluded;
0032     QSet<QString> resourceExactMatchesExcluded;
0033 
0034     QList<QString> resourceNamesPartialIncluded;
0035     QList<QString> resourceNamesPartialExcluded;
0036     QList<QString> tagsPartialIncluded;
0037     QList<QString> tagsPartialExcluded;
0038 
0039     QString filter;
0040 };
0041 
0042 
0043 KisResourceSearchBoxFilter::KisResourceSearchBoxFilter()
0044     : m_d(new Private())
0045 {
0046 }
0047 
0048 KisResourceSearchBoxFilter::~KisResourceSearchBoxFilter()
0049 {
0050 
0051 }
0052 
0053 bool checkDelimetersAndCut(const QChar& begin, const QChar& end, QString& token) {
0054     if (token.startsWith(begin) && token.endsWith(end)) {
0055         token.remove(0, 1);
0056         token = token.left(token.length() - 1);
0057         return true;
0058     } else {
0059         return false;
0060     }
0061 }
0062 
0063 bool checkDelimetersAndCut(const QChar& beginEnd, QString& token) {
0064     return checkDelimetersAndCut(beginEnd, beginEnd, token);
0065 }
0066 
0067 bool checkPrefixAndCut(QChar& prefix, QString& token) {
0068     if (token.startsWith(prefix)) {
0069         token.remove(0, 1);
0070         return true;
0071     } else {
0072         return false;
0073     }
0074 }
0075 
0076 void KisResourceSearchBoxFilter::setFilter(const QString& filter)
0077 {
0078     m_d->filter = QString(filter);
0079     initializeFilterData();
0080 }
0081 
0082 
0083 bool KisResourceSearchBoxFilter::matchesResource(const QString &_resourceName, const QStringList &tagList)
0084 {
0085     // exact matches
0086     QString resourceName = _resourceName.toLower();
0087 
0088     if (m_d->resourceExactMatchesIncluded.count() > 0
0089             && !m_d->resourceExactMatchesIncluded.contains(resourceName)) {
0090         return false;
0091     }
0092     if (m_d->resourceExactMatchesExcluded.contains(resourceName)) {
0093         return false;
0094     }
0095 
0096     // partial name matches
0097     if (m_d->resourceNamesPartialIncluded.count() > 0) {
0098         Q_FOREACH(const QString& partialName, m_d->resourceNamesPartialIncluded) {
0099             if (!resourceName.contains(partialName) && tagList.filter(partialName, Qt::CaseInsensitive).isEmpty()) {
0100                 return false;
0101             }
0102         }
0103     }
0104 
0105     Q_FOREACH(const QString& partialName, m_d->resourceNamesPartialExcluded) {
0106         if (resourceName.contains(partialName) || tagList.filter(partialName, Qt::CaseInsensitive).size() > 0) {
0107             return false;
0108         }
0109     }
0110 
0111     // Tag partial matches
0112     if (m_d->tagsPartialIncluded.count() > 0 ) {
0113         Q_FOREACH(const QString& partialTag, m_d->tagsPartialIncluded) {
0114             if (tagList.filter(partialTag, Qt::CaseInsensitive).isEmpty()) {
0115                 return false;
0116             }
0117         }
0118     }
0119 
0120     if (m_d->tagsPartialExcluded.count() > 0) {
0121         Q_FOREACH(const QString& partialTag, m_d->tagsPartialExcluded) {
0122             if (tagList.filter(partialTag, Qt::CaseInsensitive).size() > 0) {
0123                 return false;
0124             }
0125         }
0126     }
0127 
0128     // Tag exact matches
0129     if (m_d->tagExactMatchesIncluded.count() > 0) {
0130         Q_FOREACH(const QString& tagName, m_d->tagExactMatchesIncluded) {
0131             if (!tagList.contains(tagName, Qt::CaseInsensitive)) {
0132                 return false;
0133             }
0134         }
0135     }
0136 
0137     if (m_d->tagExactMatchesExcluded.count() > 0) {
0138         Q_FOREACH(const QString excludedTag, m_d->tagExactMatchesExcluded) {
0139             if (tagList.contains(excludedTag, Qt::CaseInsensitive)) {
0140                 return false;
0141             }
0142         }
0143     }
0144 
0145     return true;
0146 }
0147 
0148 bool KisResourceSearchBoxFilter::isEmpty()
0149 {
0150     return m_d->filter.isEmpty();
0151 }
0152 
0153 void KisResourceSearchBoxFilter::clearFilterData()
0154 {
0155     m_d->resourceExactMatchesIncluded.clear();
0156     m_d->resourceExactMatchesExcluded.clear();
0157     m_d->tagExactMatchesIncluded.clear();
0158     m_d->tagExactMatchesExcluded.clear();
0159 
0160     m_d->resourceNamesPartialIncluded.clear();
0161     m_d->resourceNamesPartialExcluded.clear();
0162     m_d->tagsPartialIncluded.clear();
0163     m_d->tagsPartialExcluded.clear();
0164 }
0165 
0166 void KisResourceSearchBoxFilter::initializeFilterData()
0167 {
0168     clearFilterData();
0169 
0170     QString tempFilter(m_d->filter);
0171 
0172     QStringList tokens = tempFilter.split(m_d->searchTokenizer, QString::SkipEmptyParts);
0173     Q_FOREACH(const QString& token, tokens) {
0174         QString workingToken(token.toLower());
0175         const bool included = !checkPrefixAndCut(m_d->excludeBegin, workingToken);
0176 
0177         if (checkPrefixAndCut(m_d->tagBegin, workingToken)) {
0178             if (checkDelimetersAndCut(m_d->exactMatchBeginEnd, workingToken)) {
0179                 if (included) {
0180 
0181                     m_d->tagExactMatchesIncluded.insert(workingToken);
0182                 } else {
0183 
0184                     m_d->tagExactMatchesExcluded.insert(workingToken);
0185                 }
0186             } else {
0187                 if (included) {
0188 
0189                     m_d->tagsPartialIncluded.append(workingToken);
0190                 } else {
0191 
0192                     m_d->tagsPartialExcluded.append(workingToken);
0193                 }
0194             }
0195         } else if (checkDelimetersAndCut(m_d->exactMatchBeginEnd, workingToken)) {
0196             if (included) {
0197 
0198                 m_d->resourceExactMatchesIncluded.insert(workingToken);
0199             } else {
0200 
0201                 m_d->resourceExactMatchesExcluded.insert(workingToken);
0202             }
0203 
0204         } else {
0205             if (included) {
0206 
0207                 m_d->resourceNamesPartialIncluded.append(workingToken);
0208             } else {
0209 
0210                 m_d->resourceNamesPartialExcluded.append(workingToken);
0211             }
0212         }
0213     }
0214 }