ADTF Qt5
Loading...
Searching...
No Matches
search_text_widget.h
Go to the documentation of this file.
1
7
8/*
9 * This file depends on Qt which is licensed under LGPLv3.
10 * See ADTF_DIR/3rdparty/qt5 and doc/license for detailed information.
11 */
12
13#pragma once
14
15#include <adtf_utils.h>
16
17#include <QActionGroup>
18#include <QLabel>
19#include <QLineEdit>
20#include <QMetaType>
21#include <QObject>
22#include <QPushButton>
23#include <QSortFilterProxyModel>
24#include <QSpinBox>
25#include <QStandardItemModel>
26#include <QString>
27#include <QThreadPool>
28#include <QHash>
29#include <QSet>
30#include <QStringListModel>
31#include <QCompleter>
32#include <QEvent>
33#include <adtfui/themed_icon.h>
34
35#include <optional>
36
37namespace std
38{
46Q_DECL_CONST_FUNCTION Q_DECL_CONSTEXPR inline uint qHash(const ::std::tuple<quintptr, int>& i, uint seed = 0) noexcept
47{
48 return ::qHash(std::get<0>(i), seed) + ::qHash(std::get<1>(i), seed);
49}
50} // namespace std
51
52namespace adtf
53{
54namespace ui
55{
56namespace widget
57{
58namespace detail
59{
61enum class FilterType
62{
63 normal,
64 wildcard,
65 regex,
66 fuzzy
67};
68
69class cSearchTextWidget;
70
71// cFilterModel
73class IFilterModel : public QSortFilterProxyModel
74{
75 Q_OBJECT
76
77public:
78 using QSortFilterProxyModel::QSortFilterProxyModel;
79 virtual void SetFilterColumn(int64_t nFilterColumn) = 0;
80 virtual void ClearMatchingData() = 0;
81 virtual void SetMatchingTexts(QSet<QString> oMatchingTexts) = 0;
82
83protected:
84 virtual void ClearMappingCache() = 0;
85};
86
87template<template<class, class> class MAP = QHash, template<class> class SET = QSet>
88class filter_model final : public IFilterModel
89{
90public:
91 filter_model(QObject* parent = nullptr): IFilterModel(parent)
92 {
93 setDynamicSortFilter(false);
94 }
95
96 QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override
97 {
98 if (role == Qt::DecorationRole)
99 {
100 if (section == m_nFilterColumn)
101 {
102 static const auto oFilterIcon = create_themed_icon(":/Filter.svg");
103 return oFilterIcon;
104 }
105 }
106 return QSortFilterProxyModel::headerData(section, orientation, role);
107 }
108 void setSourceModel(QAbstractItemModel* pNewSourceModel) override
109 {
110 filter_model::ClearMappingCache();
111 const auto pOldSourceModel = sourceModel();
112 if (pOldSourceModel)
113 {
114 disconnect(pOldSourceModel, &QAbstractItemModel::rowsAboutToBeInserted, this,
115 &filter_model::ClearMappingCache);
116 disconnect(pOldSourceModel, &QAbstractItemModel::rowsAboutToBeMoved, this,
117 &filter_model::ClearMappingCache);
118 disconnect(pOldSourceModel, &QAbstractItemModel::rowsAboutToBeRemoved, this,
119 &filter_model::ClearMappingCache);
120 disconnect(pOldSourceModel, &QAbstractItemModel::columnsAboutToBeInserted, this,
121 &filter_model::ClearMappingCache);
122 disconnect(pOldSourceModel, &QAbstractItemModel::columnsAboutToBeMoved, this,
123 &filter_model::ClearMappingCache);
124 disconnect(pOldSourceModel, &QAbstractItemModel::columnsAboutToBeRemoved, this,
125 &filter_model::ClearMappingCache);
126 disconnect(pOldSourceModel, &QAbstractItemModel::modelAboutToBeReset, this,
127 &filter_model::ClearMappingCache);
128 }
129 if (pNewSourceModel)
130 {
131 connect(pNewSourceModel, &QAbstractItemModel::rowsAboutToBeInserted, this,
132 &filter_model::ClearMappingCache);
133 connect(pNewSourceModel, &QAbstractItemModel::rowsAboutToBeMoved, this, &filter_model::ClearMappingCache);
134 connect(pNewSourceModel, &QAbstractItemModel::rowsAboutToBeRemoved, this, &filter_model::ClearMappingCache);
135 connect(pNewSourceModel, &QAbstractItemModel::columnsAboutToBeInserted, this,
136 &filter_model::ClearMappingCache);
137 connect(pNewSourceModel, &QAbstractItemModel::columnsAboutToBeMoved, this,
138 &filter_model::ClearMappingCache);
139 connect(pNewSourceModel, &QAbstractItemModel::columnsAboutToBeRemoved, this,
140 &filter_model::ClearMappingCache);
141 connect(pNewSourceModel, &QAbstractItemModel::modelAboutToBeReset, this, &filter_model::ClearMappingCache);
142 }
143
144 QSortFilterProxyModel::setSourceModel(pNewSourceModel);
145 }
146
147 void SetFilterColumn(int64_t nFilterColumn) override
148 {
149 m_nFilterColumn = nFilterColumn;
150 ClearMatchingData();
151 emit headerDataChanged(Qt::Horizontal, 0, columnCount() - 1);
152 }
153 void ClearMatchingData() override
154 {
155 if (m_setMatchingTexts)
156 {
157 m_setMatchingTexts.reset();
158 ClearMappingCache();
159 invalidate();
160 }
161 }
162 void SetMatchingTexts(QSet<QString> oMatchingTexts) override
163 {
164 m_setMatchingTexts = SET<QString>{oMatchingTexts.cbegin(), oMatchingTexts.cend()};
165 ClearMappingCache();
166 invalidate();
167 }
168
169protected:
170 bool filterAcceptsRow(int source_row, const QModelIndex& source_parent) const override
171 {
172 if (!m_setMatchingTexts || m_nFilterColumn < 0)
173 {
174 return true;
175 }
176
177 return filterAcceptsRowInternal(sourceModel(), source_row, source_parent);
178 }
179
180private:
181 bool filterAcceptsRowInternal(const QAbstractItemModel* pSourceModel,
182 int source_row,
183 const QModelIndex& source_parent) const
184 {
185 // Build a custom index that avoids model queries for early rejection.
186 const auto oHashKey = std::tuple{source_parent.internalId(), source_row};
187
188 if (const auto it = static_cast<const decltype(m_oAcceptedItems)&>(m_oAcceptedItems).find(oHashKey);
189 it != m_oAcceptedItems.cend())
190 {
191 // Cached success is always a success.
192 // Cached failure can still skip any further backtracking.
193
194 // Some minor difference between Qt and STL alike containers...
195 if constexpr (std::is_same_v<MAP<std::tuple<quintptr, int>, bool>, QHash<std::tuple<quintptr, int>, bool>>)
196 {
197 return it.value();
198 }
199 else
200 {
201 return it->second;
202 }
203 }
204
205 bool result = false;
206
207 // Accept all children of accepted items. (if source_parent is not already root)
208 if (source_parent.isValid())
209 {
210 auto oGrandParent = pSourceModel->parent(source_parent);
211 if (filterAcceptsRowInternal(pSourceModel, source_parent.row(), oGrandParent))
212 {
213 // This will inevitably also apply to any future child node.
214 result = true;
215 }
216 }
217
218 const auto oRowIndex = pSourceModel->index(source_row, 0, source_parent);
219 if (!result)
220 {
221 const auto oElementIndex =
222 m_nFilterColumn != 0 ? pSourceModel->sibling(source_row, m_nFilterColumn, oRowIndex) : oRowIndex;
223 // When backtracking and there wasn't a match / cached result, start string comparison.
224 if ((*m_setMatchingTexts).contains(pSourceModel->data(oElementIndex).toString()))
225 {
226 result = true;
227 }
228 }
229
230 if (!pSourceModel->flags(oRowIndex).testFlag(Qt::ItemNeverHasChildren))
231 {
232 // Cache both successes and failures. Don't bother with leaf nodes though, they will be queried only
233 // once.
234 if constexpr (std::is_same_v<MAP<std::tuple<quintptr, int>, bool>, QHash<std::tuple<quintptr, int>, bool>>)
235 {
236 m_oAcceptedItems.insert(oHashKey, result);
237 }
238 else
239 {
240 m_oAcceptedItems.emplace(oHashKey, result);
241 }
242 }
243 return result;
244 }
245
246 void ClearMappingCache() override
247 {
248 m_oAcceptedItems.clear();
249 }
250
251 int64_t m_nFilterColumn = -1;
252
253 std::optional<SET<QString>> m_setMatchingTexts;
254
255 // Mapping of NON-PERSISTENT source indices to cached acceptance state.
256 // Only valid until next structural change to source model.
257 mutable MAP<std::tuple<quintptr, int>, bool> m_oAcceptedItems;
258};
260
266class cSearchTextWidget : public QFrame
267{
268 Q_OBJECT
269
270signals:
275
276public:
281 cSearchTextWidget(QWidget* pParent = nullptr);
286
287public:
292 void SetFilterModel(IFilterModel& oFilterModel);
293
298 uint64_t GetSelectedFilterColumn() const;
303 void SetFilterColumnNames(const std::vector<QString>& vecFilterColumnNames);
308 void SetFilterColumnNamesFromModel(const QAbstractItemModel& oModel);
309
314 void SetSearchText(const QString& strSearchText);
318 void Clear();
319
329 void SelectFilterType(FilterType oFilterType);
330
332 bool IsCaseSensitive() const;
337 void SetCaseSensitive(bool bCaseSensitive);
338
339private:
341 void UpdateUi();
342
343 void HandleChangedSearchParameters();
344
345 void SetCachedText(QSet<QString> oCachedText, bool reset = false, bool schedule = true);
346
347 void SearchNormalString();
348 void SearchWildcard();
349 void SearchRegexString();
350 void SearchFuzzy();
351
352 void SetMatchingTexts(QSet<QString> oMatchingTexts);
353
354 QSet<QString> ComputeItemTexts(const QModelIndex& oModelIndex, const uint64_t nFilterColumn);
355 void ComputeTreeModelIndexs(const QModelIndex& oModelIndex,
356 const uint64_t nFilterColumn,
357 const std::function<void(const QModelIndex& oIndex)>& fnCallback);
358
359protected:
360 bool eventFilter(QObject* obj, QEvent* event) override;
361
362private:
363 IFilterModel* m_pFilterModel = nullptr;
364 QAbstractItemModel* m_pSourceModel = nullptr;
365
366 QLineEdit* m_pSearchTextLineEdit = nullptr;
367 QLabel* m_pErrorLabel = nullptr;
368 QToolButton* m_pClearButton = nullptr;
369 QMenu* m_pFilterMenu = nullptr;
370 QActionGroup* m_pFilterActionGroup = nullptr;
371 QAction* m_pFuzzyFilterAction = nullptr;
372 QAction* m_pCaseSensitiveAction = nullptr;
373 QPushButton* m_pFilterButton = nullptr;
374 QMenu* m_pFilterColumnMenu = nullptr;
375 QActionGroup* m_pFilterColumnActionGroup = nullptr;
376 QWidget* m_pFuzzyParametersWidget = nullptr;
377 QSpinBox* m_pFuzzyScaledScoreThresholdSpinBox = nullptr;
378 QStringListModel* m_pAutoCompletionModel = nullptr;
379 QCompleter* m_pAutoCompleter = nullptr;
380 QTimer* m_pPendingSearch = nullptr;
381
382 bool m_bIsInInitialization = false;
383
384 struct cSearchIndexState
385 {
386 uint64_t nSelectedFilterColumn = std::numeric_limits<uint64_t>::max();
387 QSet<QString> setItemTextCache;
388 std::optional<QSet<QString>> setMatchingItemText;
389 };
390 cSearchIndexState m_oSearchIndex;
391
392 QThreadPool m_oSearchThreadPool;
393 std::atomic<bool> m_bSearchCanceled = false;
395};
396
397} // namespace detail
398
399} // namespace widget
400} // namespace ui
401} // namespace adtf
402Q_DECLARE_METATYPE(adtf::ui::widget::detail::FilterType)
void searchParametersChanged()
This signal is emitted when the search parameters changed.
cSearchTextWidget(QWidget *pParent=nullptr)
void SetCaseSensitive(bool bCaseSensitive)
void selectedFilterColumnChanged()
This signal is emitted when the selected filter column changed.
void SetFilterColumnNames(const std::vector< QString > &vecFilterColumnNames)
void SelectFilterType(FilterType oFilterType)
void SetSearchText(const QString &strSearchText)
void SetFilterColumnNamesFromModel(const QAbstractItemModel &oModel)
bool IsCaseSensitive() const
Returns whether 'case sensitive' is selected.
void SetFilterModel(IFilterModel &oFilterModel)
Namespace for all provided widget functionality helper.
Definition filter_model_treeview.h:25
Namespace for the ADTF UI SDK.
Definition adtfui_pkg.h:13
Namespace for all functionality provided by ADTF and its SDKs.
Definition adtfui_pkg.h:13
FilterType
Filter type.
Definition search_text_widget.h:62
Q_DECL_CONST_FUNCTION Q_DECL_CONSTEXPR uint qHash(const ::std::tuple< quintptr, int > &i, uint seed=0) noexcept
qHash for tuple of (quintptr, int)
Definition search_text_widget.h:46