File indexing completed on 2024-04-28 05:34:17
0001 // SPDX-FileCopyrightText: 2018 Daniel Vrátil <dvratil@kde.org> 0002 // 0003 // SPDX-License-Identifier: LGPL-2.1-or-later 0004 0005 #include "passwordfiltermodel.h" 0006 #include "abbreviations.h" 0007 #include "passwordsmodel.h" 0008 0009 #include <KDescendantsProxyModel> 0010 0011 #include <QFutureWatcher> 0012 #include <QtConcurrent> 0013 0014 #include <chrono> 0015 #include <iterator> 0016 0017 using namespace PlasmaPass; 0018 0019 namespace 0020 { 0021 constexpr const auto invalidateDelay = std::chrono::milliseconds(100); 0022 constexpr const char *newFilterProperty = "newFilter"; 0023 0024 class ModelIterator 0025 { 0026 public: 0027 using reference = const QModelIndex &; 0028 using pointer = QModelIndex *; 0029 using value_type = QModelIndex; 0030 using difference_type = int; 0031 using iterator_category = std::forward_iterator_tag; 0032 0033 static ModelIterator begin(QAbstractItemModel *model) 0034 { 0035 return ModelIterator{model, model->index(0, 0)}; 0036 } 0037 0038 static ModelIterator end(QAbstractItemModel *model) 0039 { 0040 return ModelIterator{model, {}}; 0041 } 0042 0043 bool operator==(const ModelIterator &other) const 0044 { 0045 return mModel == other.mModel && mIndex == other.mIndex; 0046 } 0047 0048 bool operator!=(const ModelIterator &other) const 0049 { 0050 return !(*this == other); 0051 } 0052 0053 QModelIndex operator*() const 0054 { 0055 return mIndex; 0056 } 0057 0058 const QModelIndex *operator->() const 0059 { 0060 return &mIndex; 0061 } 0062 0063 ModelIterator &operator++() 0064 { 0065 if (mIndex.row() < mModel->rowCount() - 1) { 0066 mIndex = mModel->index(mIndex.row() + 1, mIndex.column()); 0067 } else { 0068 mIndex = {}; 0069 } 0070 return *this; 0071 } 0072 0073 ModelIterator operator++(int) 0074 { 0075 ModelIterator it = *this; 0076 ++*this; 0077 return it; 0078 } 0079 0080 private: 0081 ModelIterator(QAbstractItemModel *model, const QModelIndex &index) 0082 : mModel(model) 0083 , mIndex(index) 0084 { 0085 } 0086 0087 private: 0088 QAbstractItemModel *mModel = nullptr; 0089 QModelIndex mIndex; 0090 }; 0091 0092 } // namespace 0093 0094 PasswordFilterModel::PathFilter::PathFilter(QString filter) 0095 : filter(std::move(filter)) 0096 { 0097 } 0098 0099 PasswordFilterModel::PathFilter::PathFilter(const PathFilter &other) 0100 : filter(other.filter) 0101 { 0102 updateParts(); 0103 } 0104 0105 PasswordFilterModel::PathFilter &PasswordFilterModel::PathFilter::operator=(const PathFilter &other) 0106 { 0107 filter = other.filter; 0108 updateParts(); 0109 return *this; 0110 } 0111 0112 PasswordFilterModel::PathFilter::PathFilter(PathFilter &&other) noexcept 0113 : filter(std::move(other.filter)) 0114 { 0115 updateParts(); 0116 } 0117 0118 PasswordFilterModel::PathFilter &PasswordFilterModel::PathFilter::operator=(PathFilter &&other) noexcept 0119 { 0120 filter = std::move(other.filter); 0121 updateParts(); 0122 return *this; 0123 } 0124 0125 void PasswordFilterModel::PathFilter::updateParts() 0126 { 0127 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) 0128 mParts = filter.splitRef(QLatin1Char('/'), Qt::SkipEmptyParts); 0129 #else 0130 mParts = QStringView(filter).split(QLatin1Char('/'), Qt::SkipEmptyParts); 0131 #endif 0132 } 0133 0134 PasswordFilterModel::PathFilter::result_type PasswordFilterModel::PathFilter::operator()(const QModelIndex &index) const 0135 { 0136 const auto path = index.model()->data(index, PasswordsModel::FullNameRole).toString(); 0137 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) 0138 const auto weight = matchPathFilter(path.splitRef(QLatin1Char('/')), mParts); 0139 #else 0140 const auto weight = matchPathFilter(QStringView(path).split(QLatin1Char('/')), mParts); 0141 #endif 0142 return std::make_pair(index, weight); 0143 } 0144 0145 PasswordFilterModel::PasswordFilterModel(QObject *parent) 0146 : QSortFilterProxyModel(parent) 0147 , mFlatModel(new KDescendantsProxyModel(this)) 0148 { 0149 mFlatModel->setDisplayAncestorData(false); 0150 sort(0); // enable sorting 0151 0152 mUpdateTimer.setSingleShot(true); 0153 connect(&mUpdateTimer, &QTimer::timeout, this, &PasswordFilterModel::delayedUpdateFilter); 0154 connect(&mUpdateTimer, &QTimer::timeout, this, []() { 0155 qDebug() << "Update timer timeout, will calculate results lazily."; 0156 }); 0157 } 0158 0159 void PasswordFilterModel::setSourceModel(QAbstractItemModel *sourceModel) 0160 { 0161 mFlatModel->setSourceModel(sourceModel); 0162 0163 if (this->sourceModel() == nullptr) { 0164 QSortFilterProxyModel::setSourceModel(mFlatModel); 0165 } 0166 } 0167 0168 QString PasswordFilterModel::passwordFilter() const 0169 { 0170 return mFilter.filter; 0171 } 0172 0173 void PasswordFilterModel::setPasswordFilter(const QString &filter) 0174 { 0175 if (mFilter.filter != filter) { 0176 if (mUpdateTimer.isActive()) { 0177 mUpdateTimer.stop(); 0178 } 0179 0180 mUpdateTimer.setProperty(newFilterProperty, filter); 0181 mUpdateTimer.start(invalidateDelay); 0182 0183 if (mFuture.isRunning()) { 0184 mFuture.cancel(); 0185 } 0186 if (!filter.isEmpty()) { 0187 mFuture = QtConcurrent::mappedReduced<QHash<QModelIndex, int>>(ModelIterator::begin(sourceModel()), 0188 ModelIterator::end(sourceModel()), 0189 PathFilter{filter}, 0190 [](QHash<QModelIndex, int> &result, const std::pair<QModelIndex, int> &value) { 0191 result.insert(value.first, value.second); 0192 }); 0193 auto watcher = new QFutureWatcher<QHash<QModelIndex, int>>(); 0194 connect(watcher, &QFutureWatcherBase::finished, this, [this, watcher]() { 0195 mSortingLookup = mFuture.result(); 0196 watcher->deleteLater(); 0197 // If the timer is not active it means we were to slow, so don't invoke 0198 // delayedUpdateFilter() again, just update mSortingLookup with our 0199 // results. 0200 if (mUpdateTimer.isActive()) { 0201 mUpdateTimer.stop(); 0202 delayedUpdateFilter(); 0203 } 0204 }); 0205 connect(watcher, &QFutureWatcherBase::canceled, watcher, &QObject::deleteLater); 0206 watcher->setFuture(mFuture); 0207 } 0208 } 0209 } 0210 0211 void PasswordFilterModel::delayedUpdateFilter() 0212 { 0213 mFilter = PathFilter(mUpdateTimer.property(newFilterProperty).toString()); 0214 Q_EMIT passwordFilterChanged(); 0215 if (mFuture.isRunning()) { 0216 // If the future is still running, clear up the lookup table, we will have to 0217 // calculate the intermediate results ourselves 0218 mSortingLookup.clear(); 0219 } 0220 invalidate(); 0221 } 0222 0223 QVariant PasswordFilterModel::data(const QModelIndex &index, int role) const 0224 { 0225 if (role == Qt::DisplayRole) { 0226 return data(index, PasswordsModel::FullNameRole); 0227 } 0228 0229 return QSortFilterProxyModel::data(index, role); 0230 } 0231 0232 bool PasswordFilterModel::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const 0233 { 0234 const auto src_index = sourceModel()->index(source_row, 0, source_parent); 0235 const auto type = static_cast<PasswordsModel::EntryType>(sourceModel()->data(src_index, PasswordsModel::EntryTypeRole).toInt()); 0236 if (type == PasswordsModel::FolderEntry) { 0237 return false; 0238 } 0239 0240 if (mFilter.filter.isEmpty()) { 0241 return true; 0242 } 0243 0244 // Try to lookup the weight in the lookup table, the worker thread may have put it in there 0245 // while the updateTimer was ticking 0246 auto weight = mSortingLookup.find(src_index); 0247 if (weight == mSortingLookup.end()) { 0248 // It's not there, let's calculate the value now 0249 const auto result = mFilter(src_index); 0250 weight = mSortingLookup.insert(result.first, result.second); 0251 } 0252 0253 return *weight > -1; 0254 } 0255 0256 bool PasswordFilterModel::lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const 0257 { 0258 const auto weightLeft = mSortingLookup.value(source_left, -1); 0259 const auto weightRight = mSortingLookup.value(source_right, -1); 0260 0261 if (weightLeft == weightRight) { 0262 const auto nameLeft = source_left.data(PasswordsModel::FullNameRole).toString(); 0263 const auto nameRight = source_right.data(PasswordsModel::FullNameRole).toString(); 0264 return QString::localeAwareCompare(nameLeft, nameRight) < 0; 0265 } 0266 0267 return weightLeft < weightRight; 0268 } 0269 0270 #include "moc_passwordfiltermodel.cpp"