File indexing completed on 2024-05-05 05:37:09
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 <QActionGroup> 0013 #include <QApplication> 0014 #include <QContextMenuEvent> 0015 #include <QHBoxLayout> 0016 #include <QHeaderView> 0017 #include <QLabel> 0018 #include <QList> 0019 #include <QMenu> 0020 #include <QTimer> 0021 #include <QToolButton> 0022 #include <QTreeView> 0023 0024 #include <QDebug> 0025 #include <klocalizedstring.h> 0026 0027 class KTreeViewSearchLinePrivate 0028 { 0029 public: 0030 KTreeViewSearchLinePrivate(KTreeViewSearchLine *_parent) 0031 : parent(_parent) 0032 , caseSensitive(Qt::CaseInsensitive) 0033 , activeSearch(false) 0034 , keepParentsVisible(true) 0035 , canChooseColumns(true) 0036 , queuedSearches(0) 0037 { 0038 } 0039 0040 KTreeViewSearchLine *parent; 0041 QList<QTreeView *> treeViews; 0042 Qt::CaseSensitivity caseSensitive; 0043 bool activeSearch; 0044 bool keepParentsVisible; 0045 bool canChooseColumns; 0046 QString search; 0047 int queuedSearches; 0048 QList<int> searchColumns; 0049 0050 void rowsInserted(QAbstractItemModel *model, const QModelIndex &parent, int start, int end) const; 0051 void treeViewDeleted(QObject *treeView); 0052 void slotColumnActivated(QAction *action); 0053 void slotAllVisibleColumns(); 0054 0055 void checkColumns(); 0056 void checkItemParentsNotVisible(QTreeView *treeView); 0057 bool checkItemParentsVisible(QTreeView *treeView, const QModelIndex &index); 0058 }; 0059 0060 //////////////////////////////////////////////////////////////////////////////// 0061 // private slots 0062 //////////////////////////////////////////////////////////////////////////////// 0063 void KTreeViewSearchLine::rowsInserted(const QModelIndex &parentIndex, int start, int end) const 0064 { 0065 QAbstractItemModel *model = qobject_cast<QAbstractItemModel *>(sender()); 0066 d->rowsInserted(model, parentIndex, start, end); 0067 } 0068 0069 void KTreeViewSearchLinePrivate::rowsInserted(QAbstractItemModel *model, const QModelIndex &parentIndex, int start, int end) const 0070 { 0071 // QAbstractItemModel* model = qobject_cast<QAbstractItemModel*>( parent->sender() ); 0072 if (!model) { 0073 return; 0074 } 0075 0076 const auto it = std::find_if(treeViews.cbegin(), treeViews.cend(), [=](QTreeView *tree) { 0077 return tree->model() == model; 0078 }); 0079 if (it == treeViews.cend()) { 0080 return; 0081 } 0082 0083 QTreeView *widget = *it; 0084 for (int i = start; i <= end; ++i) { 0085 widget->setRowHidden(i, parentIndex, !parent->itemMatches(parentIndex, i, parent->text())); 0086 } 0087 } 0088 0089 void KTreeViewSearchLinePrivate::treeViewDeleted(QObject *object) 0090 { 0091 treeViews.removeAll(static_cast<QTreeView *>(object)); 0092 parent->setEnabled(treeViews.isEmpty()); 0093 } 0094 0095 void KTreeViewSearchLinePrivate::slotColumnActivated(QAction *action) 0096 { 0097 if (!action) { 0098 return; 0099 } 0100 0101 bool ok; 0102 int column = action->data().toInt(&ok); 0103 0104 if (!ok) { 0105 return; 0106 } 0107 0108 if (action->isChecked()) { 0109 if (!searchColumns.isEmpty()) { 0110 if (!searchColumns.contains(column)) { 0111 searchColumns.append(column); 0112 } 0113 0114 if (searchColumns.count() == treeViews.first()->header()->count() - treeViews.first()->header()->hiddenSectionCount()) { 0115 searchColumns.clear(); 0116 } 0117 0118 } else { 0119 searchColumns.append(column); 0120 } 0121 } else { 0122 if (searchColumns.isEmpty()) { 0123 QHeaderView *const header = treeViews.first()->header(); 0124 0125 for (int i = 0; i < header->count(); i++) { 0126 if (i != column && !header->isSectionHidden(i)) { 0127 searchColumns.append(i); 0128 } 0129 } 0130 0131 } else if (searchColumns.contains(column)) { 0132 searchColumns.removeAll(column); 0133 } 0134 } 0135 0136 parent->updateSearch(); 0137 } 0138 0139 void KTreeViewSearchLinePrivate::slotAllVisibleColumns() 0140 { 0141 if (searchColumns.isEmpty()) { 0142 searchColumns.append(0); 0143 } else { 0144 searchColumns.clear(); 0145 } 0146 0147 parent->updateSearch(); 0148 } 0149 0150 //////////////////////////////////////////////////////////////////////////////// 0151 // private methods 0152 //////////////////////////////////////////////////////////////////////////////// 0153 0154 void KTreeViewSearchLinePrivate::checkColumns() 0155 { 0156 canChooseColumns = parent->canChooseColumnsCheck(); 0157 } 0158 0159 void KTreeViewSearchLinePrivate::checkItemParentsNotVisible(QTreeView *treeView) 0160 { 0161 Q_UNUSED(treeView) 0162 0163 // TODO: PORT ME 0164 #if 0 0165 QTreeWidgetItemIterator it( treeWidget ); 0166 0167 for ( ; *it; ++it ) { 0168 QTreeWidgetItem *item = *it; 0169 item->treeWidget()->setItemHidden( item, !parent->itemMatches( item, search ) ); 0170 } 0171 #endif 0172 } 0173 0174 /** Check whether \p item, its siblings and their descendents should be shown. Show or hide the items as necessary. 0175 * 0176 * \p item The list view item to start showing / hiding items at. Typically, this is the first child of another item, or the 0177 * the first child of the list view. 0178 * \return \c true if an item which should be visible is found, \c false if all items found should be hidden. If this function 0179 * returns true and \p highestHiddenParent was not 0, highestHiddenParent will have been shown. 0180 */ 0181 bool KTreeViewSearchLinePrivate::checkItemParentsVisible(QTreeView *treeView, const QModelIndex &index) 0182 { 0183 bool childMatch = false; 0184 const int rowcount = treeView->model()->rowCount(index); 0185 for (int i = 0; i < rowcount; ++i) { 0186 childMatch |= checkItemParentsVisible(treeView, treeView->model()->index(i, 0, index)); 0187 } 0188 0189 // Should this item be shown? It should if any children should be, or if it matches. 0190 const QModelIndex parentindex = index.parent(); 0191 if (childMatch || parent->itemMatches(parentindex, index.row(), search)) { 0192 treeView->setRowHidden(index.row(), parentindex, false); 0193 return true; 0194 } 0195 0196 treeView->setRowHidden(index.row(), parentindex, true); 0197 0198 return false; 0199 } 0200 0201 //////////////////////////////////////////////////////////////////////////////// 0202 // public methods 0203 //////////////////////////////////////////////////////////////////////////////// 0204 0205 KTreeViewSearchLine::KTreeViewSearchLine(QWidget *parent, QTreeView *treeView) 0206 : KLineEdit(parent) 0207 , d(new KTreeViewSearchLinePrivate(this)) 0208 { 0209 connect(this, &QLineEdit::textChanged, this, &KTreeViewSearchLine::queueSearch); 0210 0211 setClearButtonEnabled(true); 0212 setTreeView(treeView); 0213 0214 if (!treeView) { 0215 setEnabled(false); 0216 } 0217 } 0218 0219 KTreeViewSearchLine::KTreeViewSearchLine(QWidget *parent, const QList<QTreeView *> &treeViews) 0220 : KLineEdit(parent) 0221 , d(new KTreeViewSearchLinePrivate(this)) 0222 { 0223 connect(this, &QLineEdit::textChanged, this, &KTreeViewSearchLine::queueSearch); 0224 0225 setClearButtonEnabled(true); 0226 setTreeViews(treeViews); 0227 } 0228 0229 KTreeViewSearchLine::~KTreeViewSearchLine() 0230 { 0231 delete d; 0232 } 0233 0234 Qt::CaseSensitivity KTreeViewSearchLine::caseSensitivity() const 0235 { 0236 return d->caseSensitive; 0237 } 0238 0239 QList<int> KTreeViewSearchLine::searchColumns() const 0240 { 0241 if (d->canChooseColumns) { 0242 return d->searchColumns; 0243 } else { 0244 return QList<int>(); 0245 } 0246 } 0247 0248 bool KTreeViewSearchLine::keepParentsVisible() const 0249 { 0250 return d->keepParentsVisible; 0251 } 0252 0253 QTreeView *KTreeViewSearchLine::treeView() const 0254 { 0255 if (d->treeViews.count() == 1) { 0256 return d->treeViews.first(); 0257 } else { 0258 return nullptr; 0259 } 0260 } 0261 0262 QList<QTreeView *> KTreeViewSearchLine::treeViews() const 0263 { 0264 return d->treeViews; 0265 } 0266 0267 //////////////////////////////////////////////////////////////////////////////// 0268 // public slots 0269 //////////////////////////////////////////////////////////////////////////////// 0270 0271 void KTreeViewSearchLine::addTreeView(QTreeView *treeView) 0272 { 0273 if (treeView) { 0274 connectTreeView(treeView); 0275 0276 d->treeViews.append(treeView); 0277 setEnabled(!d->treeViews.isEmpty()); 0278 0279 d->checkColumns(); 0280 } 0281 } 0282 0283 void KTreeViewSearchLine::removeTreeView(QTreeView *treeView) 0284 { 0285 if (treeView) { 0286 int index = d->treeViews.indexOf(treeView); 0287 0288 if (index != -1) { 0289 d->treeViews.removeAt(index); 0290 d->checkColumns(); 0291 0292 disconnectTreeView(treeView); 0293 0294 setEnabled(!d->treeViews.isEmpty()); 0295 } 0296 } 0297 } 0298 0299 void KTreeViewSearchLine::updateSearch(const QString &pattern) 0300 { 0301 d->search = pattern.isNull() ? text() : pattern; 0302 0303 for (QTreeView *treeView : std::as_const(d->treeViews)) { 0304 updateSearch(treeView); 0305 } 0306 } 0307 0308 void KTreeViewSearchLine::updateSearch(QTreeView *treeView) 0309 { 0310 if (!treeView || !treeView->model()->rowCount()) { 0311 return; 0312 } 0313 0314 // If there's a selected item that is visible, make sure that it's visible 0315 // when the search changes too (assuming that it still matches). 0316 0317 QModelIndex currentIndex = treeView->currentIndex(); 0318 0319 bool wasUpdateEnabled = treeView->updatesEnabled(); 0320 treeView->setUpdatesEnabled(false); 0321 if (d->keepParentsVisible) { 0322 for (int i = 0; i < treeView->model()->rowCount(); ++i) { 0323 d->checkItemParentsVisible(treeView, treeView->rootIndex()); 0324 } 0325 } else { 0326 d->checkItemParentsNotVisible(treeView); 0327 } 0328 treeView->setUpdatesEnabled(wasUpdateEnabled); 0329 0330 if (currentIndex.isValid()) { 0331 treeView->scrollTo(currentIndex); 0332 } 0333 } 0334 0335 void KTreeViewSearchLine::setCaseSensitivity(Qt::CaseSensitivity caseSensitive) 0336 { 0337 if (d->caseSensitive != caseSensitive) { 0338 d->caseSensitive = caseSensitive; 0339 updateSearch(); 0340 } 0341 } 0342 0343 void KTreeViewSearchLine::setKeepParentsVisible(bool visible) 0344 { 0345 if (d->keepParentsVisible != visible) { 0346 d->keepParentsVisible = visible; 0347 updateSearch(); 0348 } 0349 } 0350 0351 void KTreeViewSearchLine::setSearchColumns(const QList<int> &columns) 0352 { 0353 if (d->canChooseColumns) { 0354 d->searchColumns = columns; 0355 } 0356 } 0357 0358 void KTreeViewSearchLine::setTreeView(QTreeView *treeView) 0359 { 0360 setTreeViews(QList<QTreeView *>()); 0361 addTreeView(treeView); 0362 } 0363 0364 void KTreeViewSearchLine::setTreeViews(const QList<QTreeView *> &treeViews) 0365 { 0366 for (QTreeView *treeView : std::as_const(d->treeViews)) { 0367 disconnectTreeView(treeView); 0368 } 0369 0370 d->treeViews = treeViews; 0371 0372 for (QTreeView *treeView : std::as_const(d->treeViews)) { 0373 connectTreeView(treeView); 0374 } 0375 0376 d->checkColumns(); 0377 0378 setEnabled(!d->treeViews.isEmpty()); 0379 } 0380 0381 //////////////////////////////////////////////////////////////////////////////// 0382 // protected members 0383 //////////////////////////////////////////////////////////////////////////////// 0384 0385 bool KTreeViewSearchLine::itemMatches(const QModelIndex &index, int row, const QString &pattern) const 0386 { 0387 if (pattern.isEmpty()) { 0388 return true; 0389 } 0390 0391 if (!index.isValid()) { 0392 return false; 0393 } 0394 0395 // If the search column list is populated, search just the columns 0396 // specifified. If it is empty default to searching all of the columns. 0397 0398 const int columncount = index.model()->columnCount(index); 0399 if (!d->searchColumns.isEmpty()) { 0400 QList<int>::ConstIterator it = d->searchColumns.constBegin(); 0401 for (; it != d->searchColumns.constEnd(); ++it) { 0402 if (*it < columncount && index.model()->index(row, *it, index).data(Qt::DisplayRole).toString().indexOf(pattern, 0, d->caseSensitive) >= 0) { 0403 return true; 0404 } 0405 } 0406 } else { 0407 for (int i = 0; i < columncount; ++i) { 0408 if (index.model()->index(row, i, index).data(Qt::DisplayRole).toString().indexOf(pattern, 0, d->caseSensitive) >= 0) { 0409 return true; 0410 } 0411 } 0412 } 0413 0414 return false; 0415 } 0416 0417 void KTreeViewSearchLine::contextMenuEvent(QContextMenuEvent *event) 0418 { 0419 QMenu *popup = KLineEdit::createStandardContextMenu(); 0420 0421 if (d->canChooseColumns) { 0422 popup->addSeparator(); 0423 QMenu *subMenu = popup->addMenu(i18n("Search Columns")); 0424 0425 QAction *allVisibleColumnsAction = subMenu->addAction(i18n("All Visible Columns"), this, SLOT(slotAllVisibleColumns())); 0426 allVisibleColumnsAction->setCheckable(true); 0427 allVisibleColumnsAction->setChecked(!d->searchColumns.count()); 0428 subMenu->addSeparator(); 0429 0430 bool allColumnsAreSearchColumns = true; 0431 0432 QActionGroup *group = new QActionGroup(popup); 0433 group->setExclusive(false); 0434 connect(group, SIGNAL(triggered(QAction *)), SLOT(slotColumnActivated(QAction *))); 0435 0436 QHeaderView *const header = d->treeViews.first()->header(); 0437 for (int j = 0; j < header->count(); j++) { 0438 int i = header->logicalIndex(j); 0439 0440 if (header->isSectionHidden(i)) { 0441 continue; 0442 } 0443 0444 QString columnText = header->model()->headerData(i, Qt::Horizontal, Qt::DisplayRole).toString(); 0445 QAction *columnAction = subMenu->addAction(qvariant_cast<QIcon>(header->model()->headerData(i, Qt::Horizontal, Qt::DecorationRole)), columnText); 0446 columnAction->setCheckable(true); 0447 columnAction->setChecked(d->searchColumns.isEmpty() || d->searchColumns.contains(i)); 0448 columnAction->setData(i); 0449 columnAction->setActionGroup(group); 0450 0451 if (d->searchColumns.isEmpty() || d->searchColumns.indexOf(i) != -1) { 0452 columnAction->setChecked(true); 0453 } else { 0454 allColumnsAreSearchColumns = false; 0455 } 0456 } 0457 0458 allVisibleColumnsAction->setChecked(allColumnsAreSearchColumns); 0459 0460 // searchColumnsMenuActivated() relies on one possible "all" representation 0461 if (allColumnsAreSearchColumns && !d->searchColumns.isEmpty()) { 0462 d->searchColumns.clear(); 0463 } 0464 } 0465 0466 popup->exec(event->globalPos()); 0467 delete popup; 0468 } 0469 0470 void KTreeViewSearchLine::connectTreeView(QTreeView *treeView) 0471 { 0472 connect(treeView, SIGNAL(destroyed(QObject *)), this, SLOT(treeViewDeleted(QObject *))); 0473 0474 connect(treeView->model(), &QAbstractItemModel::rowsInserted, this, &KTreeViewSearchLine::rowsInserted); 0475 } 0476 0477 void KTreeViewSearchLine::disconnectTreeView(QTreeView *treeView) 0478 { 0479 disconnect(treeView, SIGNAL(destroyed(QObject *)), this, SLOT(treeViewDeleted(QObject *))); 0480 0481 disconnect(treeView->model(), &QAbstractItemModel::rowsInserted, this, &KTreeViewSearchLine::rowsInserted); 0482 } 0483 0484 bool KTreeViewSearchLine::canChooseColumnsCheck() 0485 { 0486 // This is true if either of the following is true: 0487 0488 // there are no listviews connected 0489 if (d->treeViews.isEmpty()) { 0490 return false; 0491 } 0492 0493 const QTreeView *first = d->treeViews.first(); 0494 0495 const int numcols = first->model()->columnCount(); 0496 // the listviews have only one column, 0497 if (numcols < 2) { 0498 return false; 0499 } 0500 0501 QStringList headers; 0502 for (int i = 0; i < numcols; ++i) { 0503 headers.append(first->header()->model()->headerData(i, Qt::Horizontal, Qt::DisplayRole).toString()); 0504 } 0505 0506 QList<QTreeView *>::ConstIterator it = d->treeViews.constBegin(); 0507 for (++it /* skip the first one */; it != d->treeViews.constEnd(); ++it) { 0508 // the listviews have different numbers of columns, 0509 if ((*it)->model()->columnCount() != numcols) { 0510 return false; 0511 } 0512 0513 // the listviews differ in column labels. 0514 QStringList::ConstIterator jt; 0515 int i; 0516 for (i = 0, jt = headers.constBegin(); i < numcols; ++i, ++jt) { 0517 Q_ASSERT(jt != headers.constEnd()); 0518 0519 if ((*it)->header()->model()->headerData(i, Qt::Horizontal, Qt::DisplayRole).toString() != *jt) { 0520 return false; 0521 } 0522 } 0523 } 0524 0525 return true; 0526 } 0527 0528 //////////////////////////////////////////////////////////////////////////////// 0529 // protected slots 0530 //////////////////////////////////////////////////////////////////////////////// 0531 0532 void KTreeViewSearchLine::queueSearch(const QString &search) 0533 { 0534 d->queuedSearches++; 0535 d->search = search; 0536 0537 QTimer::singleShot(200, this, &KTreeViewSearchLine::activateSearch); 0538 } 0539 0540 void KTreeViewSearchLine::activateSearch() 0541 { 0542 --(d->queuedSearches); 0543 0544 if (d->queuedSearches == 0) { 0545 updateSearch(d->search); 0546 } 0547 } 0548 0549 //////////////////////////////////////////////////////////////////////////////// 0550 // KTreeViewSearchLineWidget 0551 //////////////////////////////////////////////////////////////////////////////// 0552 0553 class KTreeViewSearchLineWidgetPrivate 0554 { 0555 public: 0556 KTreeViewSearchLineWidgetPrivate() 0557 : treeView(nullptr) 0558 , searchLine(nullptr) 0559 { 0560 } 0561 0562 QTreeView *treeView; 0563 KTreeViewSearchLine *searchLine; 0564 }; 0565 0566 KTreeViewSearchLineWidget::KTreeViewSearchLineWidget(QWidget *parent, QTreeView *treeView) 0567 : QWidget(parent) 0568 , d(new KTreeViewSearchLineWidgetPrivate) 0569 { 0570 d->treeView = treeView; 0571 0572 QTimer::singleShot(0, this, &KTreeViewSearchLineWidget::createWidgets); 0573 } 0574 0575 KTreeViewSearchLineWidget::~KTreeViewSearchLineWidget() 0576 { 0577 delete d; 0578 } 0579 0580 KTreeViewSearchLine *KTreeViewSearchLineWidget::createSearchLine(QTreeView *treeView) const 0581 { 0582 return new KTreeViewSearchLine(const_cast<KTreeViewSearchLineWidget *>(this), treeView); 0583 } 0584 0585 void KTreeViewSearchLineWidget::createWidgets() 0586 { 0587 QLabel *label = new QLabel(i18n("S&earch:"), this); 0588 label->setObjectName(QLatin1String("kde toolbar widget")); 0589 0590 searchLine()->show(); 0591 0592 label->setBuddy(d->searchLine); 0593 label->show(); 0594 0595 QHBoxLayout *layout = new QHBoxLayout(this); 0596 layout->setSpacing(5); 0597 layout->setContentsMargins(0, 0, 0, 0); 0598 layout->addWidget(label); 0599 layout->addWidget(d->searchLine); 0600 } 0601 0602 KTreeViewSearchLine *KTreeViewSearchLineWidget::searchLine() const 0603 { 0604 if (!d->searchLine) { 0605 d->searchLine = createSearchLine(d->treeView); 0606 } 0607 0608 return d->searchLine; 0609 } 0610 0611 #include "moc_ktreeviewsearchline.cpp"