File indexing completed on 2024-04-28 03:53:09

0001 /*
0002     This file is part of the KDE libraries
0003     SPDX-FileCopyrightText: 1999 Carsten Pfeiffer <pfeiffer@kde.org>
0004 
0005     SPDX-License-Identifier: LGPL-2.0-or-later
0006 */
0007 
0008 #ifndef KCOMPLETIONMATCHESWRAPPER_P_H
0009 #define KCOMPLETIONMATCHESWRAPPER_P_H
0010 
0011 #include "kcompletion.h"
0012 #include "kcomptreenode_p.h"
0013 
0014 #include <kcompletionmatches.h>
0015 
0016 class KCOMPLETION_EXPORT KCompletionMatchesWrapper
0017 {
0018 public:
0019     explicit KCompletionMatchesWrapper(KCompletion::SorterFunction const &sorterFunction, KCompletion::CompOrder compOrder = KCompletion::Insertion)
0020         : m_sortedListPtr(compOrder == KCompletion::Weighted ? new KCompletionMatchesList : nullptr)
0021         , m_dirty(false)
0022         , m_compOrder(compOrder)
0023         , m_sorterFunction(sorterFunction)
0024     {
0025     }
0026 
0027     KCompletionMatchesWrapper(const KCompletionMatchesWrapper &) = delete;
0028     KCompletionMatchesWrapper &operator=(const KCompletionMatchesWrapper &) = delete;
0029 
0030     void setSorting(KCompletion::CompOrder compOrder)
0031     {
0032         if (compOrder == KCompletion::Weighted && !m_sortedListPtr) {
0033             m_sortedListPtr = std::make_unique<KCompletionMatchesList>();
0034         } else if (compOrder != KCompletion::Weighted) {
0035             m_sortedListPtr.reset();
0036         }
0037         m_compOrder = compOrder;
0038         m_stringList.clear();
0039         m_dirty = false;
0040     }
0041 
0042     KCompletion::CompOrder sorting() const
0043     {
0044         return m_compOrder;
0045     }
0046 
0047     void append(int i, const QString &string)
0048     {
0049         if (m_sortedListPtr) {
0050             m_sortedListPtr->insert(i, string);
0051         } else {
0052             m_stringList.append(string);
0053         }
0054         m_dirty = true;
0055     }
0056 
0057     void clear()
0058     {
0059         if (m_sortedListPtr) {
0060             m_sortedListPtr->clear();
0061         }
0062         m_stringList.clear();
0063         m_dirty = false;
0064     }
0065 
0066     uint size() const
0067     {
0068         if (m_sortedListPtr) {
0069             return m_sortedListPtr->size();
0070         }
0071         return m_stringList.size();
0072     }
0073 
0074     bool isEmpty() const
0075     {
0076         return size() == 0;
0077     }
0078 
0079     QString first() const
0080     {
0081         return list().constFirst();
0082     }
0083 
0084     QString last() const
0085     {
0086         return list().constLast();
0087     }
0088 
0089     inline QStringList list() const;
0090 
0091     inline void findAllCompletions(const KCompTreeNode *, const QString &, bool ignoreCase, bool &hasMultipleMatches);
0092 
0093     inline void extractStringsFromNode(const KCompTreeNode *, const QString &beginning, bool addWeight = false);
0094 
0095     inline void extractStringsFromNodeCI(const KCompTreeNode *, const QString &beginning, const QString &restString);
0096 
0097     mutable QStringList m_stringList;
0098     std::unique_ptr<KCompletionMatchesList> m_sortedListPtr;
0099     mutable bool m_dirty;
0100     KCompletion::CompOrder m_compOrder;
0101     KCompletion::SorterFunction const &m_sorterFunction;
0102 };
0103 
0104 void KCompletionMatchesWrapper::findAllCompletions(const KCompTreeNode *treeRoot, const QString &string, bool ignoreCase, bool &hasMultipleMatches)
0105 {
0106     // qDebug() << "*** finding all completions for " << string;
0107 
0108     if (string.isEmpty()) {
0109         return;
0110     }
0111 
0112     if (ignoreCase) { // case insensitive completion
0113         extractStringsFromNodeCI(treeRoot, QString(), string);
0114         hasMultipleMatches = (size() > 1);
0115         return;
0116     }
0117 
0118     QString completion;
0119     const KCompTreeNode *node = treeRoot;
0120 
0121     // start at the tree-root and try to find the search-string
0122     for (const QChar ch : string) {
0123         node = node->find(ch);
0124 
0125         if (node) {
0126             completion += ch;
0127         } else {
0128             return; // no completion -> return empty list
0129         }
0130     }
0131 
0132     // Now we have the last node of the to be completed string.
0133     // Follow it as long as it has exactly one child (= longest possible
0134     // completion)
0135 
0136     while (node->childrenCount() == 1) {
0137         node = node->firstChild();
0138         if (!node->isNull()) {
0139             completion += *node;
0140         }
0141         // qDebug() << completion << node->latin1();
0142     }
0143 
0144     // there is just one single match)
0145     if (node->childrenCount() == 0) {
0146         append(node->weight(), completion);
0147     }
0148 
0149     else {
0150         // node has more than one child
0151         // -> recursively find all remaining completions
0152         hasMultipleMatches = true;
0153         extractStringsFromNode(node, completion);
0154     }
0155 }
0156 
0157 QStringList KCompletionMatchesWrapper::list() const
0158 {
0159     if (m_sortedListPtr && m_dirty) {
0160         m_sortedListPtr->sort();
0161         m_dirty = false;
0162 
0163         m_stringList.clear();
0164         m_stringList.reserve(m_sortedListPtr->size());
0165         // high weight == sorted last -> reverse the sorting here
0166         std::transform(m_sortedListPtr->crbegin(), m_sortedListPtr->crend(), std::back_inserter(m_stringList), [](const KSortableItem<QString> &item) {
0167             return item.value();
0168         });
0169     } else if (m_compOrder == KCompletion::Sorted) {
0170         m_sorterFunction(m_stringList);
0171     }
0172 
0173     return m_stringList;
0174 }
0175 
0176 void KCompletionMatchesWrapper::extractStringsFromNode(const KCompTreeNode *node, const QString &beginning, bool addWeight)
0177 {
0178     if (!node) {
0179         return;
0180     }
0181 
0182     // qDebug() << "Beginning: " << beginning;
0183     const KCompTreeChildren *list = node->children();
0184     QString string;
0185     QString w;
0186 
0187     // loop thru all children
0188     for (KCompTreeNode *cur = list->begin(); cur; cur = cur->m_next) {
0189         string = beginning;
0190         node = cur;
0191         if (!node->isNull()) {
0192             string += *node;
0193         }
0194 
0195         while (node && node->childrenCount() == 1) {
0196             node = node->firstChild();
0197             if (node->isNull()) {
0198                 break;
0199             }
0200             string += *node;
0201         }
0202 
0203         if (node && node->isNull()) { // we found a leaf
0204             if (addWeight) {
0205                 // add ":num" to the string to store the weighting
0206                 string += QLatin1Char(':');
0207                 w.setNum(node->weight());
0208                 string.append(w);
0209             }
0210             append(node->weight(), string);
0211         }
0212 
0213         // recursively find all other strings.
0214         if (node && node->childrenCount() > 1) {
0215             extractStringsFromNode(node, string, addWeight);
0216         }
0217     }
0218 }
0219 
0220 void KCompletionMatchesWrapper::extractStringsFromNodeCI(const KCompTreeNode *node, const QString &beginning, const QString &restString)
0221 {
0222     if (restString.isEmpty()) {
0223         extractStringsFromNode(node, beginning, false /*noweight*/);
0224         return;
0225     }
0226 
0227     QChar ch1 = restString.at(0);
0228     QString newRest = restString.mid(1);
0229     KCompTreeNode *child1;
0230     KCompTreeNode *child2;
0231 
0232     child1 = node->find(ch1); // the correct match
0233     if (child1) {
0234         extractStringsFromNodeCI(child1, beginning + QChar(*child1), newRest);
0235     }
0236 
0237     // append the case insensitive matches, if available
0238     if (ch1.isLetter()) {
0239         // find out if we have to lower or upper it. Is there a better way?
0240         QChar ch2 = ch1.toLower();
0241         if (ch1 == ch2) {
0242             ch2 = ch1.toUpper();
0243         }
0244         if (ch1 != ch2) {
0245             child2 = node->find(ch2);
0246             if (child2) {
0247                 extractStringsFromNodeCI(child2, beginning + QChar(*child2), newRest);
0248             }
0249         }
0250     }
0251 }
0252 
0253 #endif // KCOMPLETIONMATCHESWRAPPER_P_H