File indexing completed on 2024-04-28 15:51:45
0001 /* 0002 SPDX-FileCopyrightText: 2003 Scott Wheeler <wheeler@kde.org> 0003 SPDX-FileCopyrightText: 2005 Rafal Rzepecki <divide@users.sourceforge.net> 0004 SPDX-FileCopyrightText: 2006 Hamish Rodda <rodda@kde.org> 0005 SPDX-FileCopyrightText: 2007 Pino Toscano <pino@kde.org> 0006 0007 SPDX-License-Identifier: LGPL-2.0-only 0008 */ 0009 0010 #include "ktreeviewsearchline.h" 0011 0012 #include <QApplication> 0013 #include <QContextMenuEvent> 0014 #include <QHBoxLayout> 0015 #include <QHeaderView> 0016 #include <QLabel> 0017 #include <QList> 0018 #include <QMenu> 0019 #include <QRegularExpression> 0020 #include <QTimer> 0021 #include <QToolButton> 0022 #include <QTreeView> 0023 0024 #include <KLocalizedString> 0025 #include <KToolBar> 0026 #include <QDebug> 0027 0028 class KTreeViewSearchLine::Private 0029 { 0030 public: 0031 explicit Private(KTreeViewSearchLine *_parent) 0032 : parent(_parent) 0033 , treeView(nullptr) 0034 , caseSensitive(Qt::CaseInsensitive) 0035 , regularExpression(false) 0036 , activeSearch(false) 0037 , queuedSearches(0) 0038 { 0039 } 0040 0041 KTreeViewSearchLine *parent; 0042 QTreeView *treeView; 0043 Qt::CaseSensitivity caseSensitive; 0044 bool regularExpression; 0045 bool activeSearch; 0046 QString search; 0047 int queuedSearches; 0048 0049 void rowsInserted(const QModelIndex &parent, int start, int end) const; 0050 void treeViewDeleted(QObject *object); 0051 void slotCaseSensitive(); 0052 void slotRegularExpression(); 0053 0054 void checkItemParentsNotVisible(QTreeView *treeView); 0055 bool filterItems(QTreeView *treeView, const QModelIndex &index); 0056 }; 0057 0058 //////////////////////////////////////////////////////////////////////////////// 0059 // private slots 0060 //////////////////////////////////////////////////////////////////////////////// 0061 0062 void KTreeViewSearchLine::Private::rowsInserted(const QModelIndex &parentIndex, int start, int end) const 0063 { 0064 QAbstractItemModel *model = qobject_cast<QAbstractItemModel *>(parent->sender()); 0065 if (!model) { 0066 return; 0067 } 0068 0069 QTreeView *widget = nullptr; 0070 if (treeView->model() == model) { 0071 widget = treeView; 0072 } 0073 0074 if (!widget) { 0075 return; 0076 } 0077 0078 for (int i = start; i <= end; ++i) { 0079 widget->setRowHidden(i, parentIndex, !parent->itemMatches(parentIndex, i, parent->text())); 0080 } 0081 } 0082 0083 void KTreeViewSearchLine::Private::treeViewDeleted(QObject *object) 0084 { 0085 if (object == treeView) { 0086 treeView = nullptr; 0087 parent->setEnabled(false); 0088 } 0089 } 0090 0091 void KTreeViewSearchLine::Private::slotCaseSensitive() 0092 { 0093 if (caseSensitive == Qt::CaseSensitive) { 0094 parent->setCaseSensitivity(Qt::CaseInsensitive); 0095 } else { 0096 parent->setCaseSensitivity(Qt::CaseSensitive); 0097 } 0098 0099 parent->updateSearch(); 0100 } 0101 0102 void KTreeViewSearchLine::Private::slotRegularExpression() 0103 { 0104 if (regularExpression) { 0105 parent->setRegularExpression(false); 0106 } else { 0107 parent->setRegularExpression(true); 0108 } 0109 0110 parent->updateSearch(); 0111 } 0112 0113 //////////////////////////////////////////////////////////////////////////////// 0114 // private methods 0115 //////////////////////////////////////////////////////////////////////////////// 0116 0117 /** Check whether \p item, its siblings and their descendants should be shown. Show or hide the items as necessary. 0118 * 0119 * \p item The list view item to start showing / hiding items at. Typically, this is the first child of another item, or the 0120 * the first child of the list view. 0121 * \return \c true if an item which should be visible is found, \c false if all items found should be hidden. If this function 0122 * returns true and \p highestHiddenParent was not 0, highestHiddenParent will have been shown. 0123 */ 0124 bool KTreeViewSearchLine::Private::filterItems(QTreeView *treeView, const QModelIndex &index) 0125 { 0126 bool childMatch = false; 0127 const int rowcount = treeView->model()->rowCount(index); 0128 for (int i = 0; i < rowcount; ++i) { 0129 childMatch |= filterItems(treeView, treeView->model()->index(i, 0, index)); 0130 } 0131 0132 // Should this item be shown? It should if any children should be, or if it matches. 0133 const QModelIndex parentindex = index.parent(); 0134 if (childMatch || parent->itemMatches(parentindex, index.row(), search)) { 0135 treeView->setRowHidden(index.row(), parentindex, false); 0136 return true; 0137 } 0138 0139 treeView->setRowHidden(index.row(), parentindex, true); 0140 0141 return false; 0142 } 0143 0144 //////////////////////////////////////////////////////////////////////////////// 0145 // public methods 0146 //////////////////////////////////////////////////////////////////////////////// 0147 0148 KTreeViewSearchLine::KTreeViewSearchLine(QWidget *parent, QTreeView *treeView) 0149 : KLineEdit(parent) 0150 , d(new Private(this)) 0151 { 0152 connect(this, &KTreeViewSearchLine::textChanged, this, &KTreeViewSearchLine::queueSearch); 0153 0154 setClearButtonEnabled(true); 0155 setTreeView(treeView); 0156 0157 if (!treeView) { 0158 setEnabled(false); 0159 } 0160 } 0161 0162 KTreeViewSearchLine::~KTreeViewSearchLine() 0163 { 0164 delete d; 0165 } 0166 0167 Qt::CaseSensitivity KTreeViewSearchLine::caseSensitivity() const 0168 { 0169 return d->caseSensitive; 0170 } 0171 0172 bool KTreeViewSearchLine::regularExpression() const 0173 { 0174 return d->regularExpression; 0175 } 0176 0177 QTreeView *KTreeViewSearchLine::treeView() const 0178 { 0179 return d->treeView; 0180 } 0181 0182 //////////////////////////////////////////////////////////////////////////////// 0183 // public slots 0184 //////////////////////////////////////////////////////////////////////////////// 0185 0186 void KTreeViewSearchLine::updateSearch(const QString &pattern) 0187 { 0188 d->search = pattern.isNull() ? text() : pattern; 0189 0190 updateSearch(d->treeView); 0191 } 0192 0193 void KTreeViewSearchLine::updateSearch(QTreeView *treeView) 0194 { 0195 if (!treeView || !treeView->model()->rowCount()) { 0196 return; 0197 } 0198 0199 // If there's a selected item that is visible, make sure that it's visible 0200 // when the search changes too (assuming that it still matches). 0201 0202 QModelIndex currentIndex = treeView->currentIndex(); 0203 0204 bool wasUpdateEnabled = treeView->updatesEnabled(); 0205 treeView->setUpdatesEnabled(false); 0206 d->filterItems(treeView, treeView->rootIndex()); 0207 treeView->setUpdatesEnabled(wasUpdateEnabled); 0208 0209 if (currentIndex.isValid()) { 0210 treeView->scrollTo(currentIndex); 0211 } 0212 } 0213 0214 void KTreeViewSearchLine::setCaseSensitivity(Qt::CaseSensitivity caseSensitivity) 0215 { 0216 if (d->caseSensitive != caseSensitivity) { 0217 d->caseSensitive = caseSensitivity; 0218 updateSearch(); 0219 Q_EMIT searchOptionsChanged(); 0220 } 0221 } 0222 0223 void KTreeViewSearchLine::setRegularExpression(bool value) 0224 { 0225 if (d->regularExpression != value) { 0226 d->regularExpression = value; 0227 updateSearch(); 0228 Q_EMIT searchOptionsChanged(); 0229 } 0230 } 0231 0232 void KTreeViewSearchLine::setTreeView(QTreeView *treeView) 0233 { 0234 disconnectTreeView(d->treeView); 0235 d->treeView = treeView; 0236 connectTreeView(treeView); 0237 0238 setEnabled(treeView != nullptr); 0239 } 0240 0241 //////////////////////////////////////////////////////////////////////////////// 0242 // protected members 0243 //////////////////////////////////////////////////////////////////////////////// 0244 0245 bool KTreeViewSearchLine::itemMatches(const QModelIndex &parentIndex, int row, const QString &pattern) const 0246 { 0247 if (pattern.isEmpty()) { 0248 return true; 0249 } 0250 0251 if (!parentIndex.isValid() && parentIndex != d->treeView->rootIndex()) { 0252 return false; 0253 } 0254 0255 // Construct a regular expression object with the right options. 0256 QRegularExpression re; 0257 if (d->regularExpression) { 0258 re.setPattern(pattern); 0259 re.setPatternOptions(d->caseSensitive ? QRegularExpression::NoPatternOption : QRegularExpression::CaseInsensitiveOption); 0260 } 0261 0262 // If the search column list is populated, search just the columns 0263 // specified. If it is empty default to searching all of the columns. 0264 QAbstractItemModel *model = d->treeView->model(); 0265 const int columncount = model->columnCount(parentIndex); 0266 for (int i = 0; i < columncount; ++i) { 0267 const QString str = model->data(model->index(row, i, parentIndex), Qt::DisplayRole).toString(); 0268 if (d->regularExpression) { 0269 return str.contains(re); 0270 } else { 0271 return str.contains(pattern, d->caseSensitive ? Qt::CaseSensitive : Qt::CaseInsensitive); 0272 } 0273 } 0274 0275 return false; 0276 } 0277 0278 void KTreeViewSearchLine::contextMenuEvent(QContextMenuEvent *event) 0279 { 0280 QMenu *popup = KLineEdit::createStandardContextMenu(); 0281 0282 popup->addSeparator(); 0283 QMenu *optionsSubMenu = popup->addMenu(i18n("Search Options")); 0284 QAction *caseSensitiveAction = optionsSubMenu->addAction(i18nc("Enable case sensitive search in the side navigation panels", "Case Sensitive"), this, [this] { d->slotCaseSensitive(); }); 0285 caseSensitiveAction->setCheckable(true); 0286 caseSensitiveAction->setChecked(d->caseSensitive); 0287 QAction *regularExpressionAction = optionsSubMenu->addAction(i18nc("Enable regular expression search in the side navigation panels", "Regular Expression"), this, [this] { d->slotRegularExpression(); }); 0288 regularExpressionAction->setCheckable(true); 0289 regularExpressionAction->setChecked(d->regularExpression); 0290 0291 popup->exec(event->globalPos()); 0292 delete popup; 0293 } 0294 0295 void KTreeViewSearchLine::connectTreeView(QTreeView *treeView) 0296 { 0297 if (treeView) { 0298 connect(treeView, &QTreeView::destroyed, this, &KTreeViewSearchLine::treeViewDeleted); 0299 0300 connect(treeView->model(), &QAbstractItemModel::rowsInserted, this, &KTreeViewSearchLine::rowsInserted); 0301 } 0302 } 0303 0304 void KTreeViewSearchLine::disconnectTreeView(QTreeView *treeView) 0305 { 0306 if (treeView) { 0307 disconnect(treeView, &QTreeView::destroyed, this, &KTreeViewSearchLine::treeViewDeleted); 0308 0309 disconnect(treeView->model(), &QAbstractItemModel::rowsInserted, this, &KTreeViewSearchLine::rowsInserted); 0310 } 0311 } 0312 0313 //////////////////////////////////////////////////////////////////////////////// 0314 // protected slots 0315 //////////////////////////////////////////////////////////////////////////////// 0316 0317 void KTreeViewSearchLine::queueSearch(const QString &search) 0318 { 0319 d->queuedSearches++; 0320 d->search = search; 0321 0322 QTimer::singleShot(200, this, &KTreeViewSearchLine::activateSearch); 0323 } 0324 0325 void KTreeViewSearchLine::activateSearch() 0326 { 0327 --(d->queuedSearches); 0328 0329 if (d->queuedSearches == 0) { 0330 updateSearch(d->search); 0331 } 0332 } 0333 0334 //////////////////////////////////////////////////////////////////////////////// 0335 // private functions 0336 //////////////////////////////////////////////////////////////////////////////// 0337 0338 void KTreeViewSearchLine::rowsInserted(const QModelIndex &parent, int start, int end) const 0339 { 0340 d->rowsInserted(parent, start, end); 0341 } 0342 0343 void KTreeViewSearchLine::treeViewDeleted(QObject *treeView) 0344 { 0345 d->treeViewDeleted(treeView); 0346 } 0347 0348 //////////////////////////////////////////////////////////////////////////////// 0349 // KTreeViewSearchLineWidget 0350 //////////////////////////////////////////////////////////////////////////////// 0351 0352 class KTreeViewSearchLineWidget::Private 0353 { 0354 public: 0355 Private() 0356 : treeView(nullptr) 0357 , searchLine(nullptr) 0358 { 0359 } 0360 0361 QTreeView *treeView; 0362 KTreeViewSearchLine *searchLine; 0363 }; 0364 0365 KTreeViewSearchLineWidget::KTreeViewSearchLineWidget(QWidget *parent, QTreeView *treeView) 0366 : QWidget(parent) 0367 , d(new Private) 0368 { 0369 d->treeView = treeView; 0370 0371 QTimer::singleShot(0, this, &KTreeViewSearchLineWidget::createWidgets); 0372 } 0373 0374 KTreeViewSearchLineWidget::~KTreeViewSearchLineWidget() 0375 { 0376 delete d; 0377 } 0378 0379 KTreeViewSearchLine *KTreeViewSearchLineWidget::createSearchLine(QTreeView *treeView) const 0380 { 0381 return new KTreeViewSearchLine(const_cast<KTreeViewSearchLineWidget *>(this), treeView); 0382 } 0383 0384 void KTreeViewSearchLineWidget::createWidgets() 0385 { 0386 QLabel *label = new QLabel(i18n("S&earch:"), this); 0387 label->setObjectName(QStringLiteral("kde toolbar widget")); 0388 0389 searchLine()->show(); 0390 0391 label->setBuddy(d->searchLine); 0392 label->show(); 0393 0394 QHBoxLayout *layout = new QHBoxLayout(this); 0395 layout->setSpacing(5); 0396 layout->setContentsMargins(0, 0, 0, 0); 0397 layout->addWidget(label); 0398 layout->addWidget(d->searchLine); 0399 } 0400 0401 KTreeViewSearchLine *KTreeViewSearchLineWidget::searchLine() const 0402 { 0403 if (!d->searchLine) { 0404 d->searchLine = createSearchLine(d->treeView); 0405 } 0406 0407 return d->searchLine; 0408 } 0409 0410 #include "moc_ktreeviewsearchline.cpp"