File indexing completed on 2023-10-01 09:34:58
0001 /* This file is part of the KDE project 0002 Copyright (C) 2005 Daniel Teske <teske@squorn.de> 0003 0004 This program is free software; you can redistribute it and/or 0005 modify it under the terms of the GNU General Public 0006 License version 2 or at your option version 3 as published by 0007 the Free Software Foundation. 0008 0009 This program is distributed in the hope that it will be useful, 0010 but WITHOUT ANY WARRANTY; without even the implied warranty of 0011 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 0012 General Public License for more details. 0013 0014 You should have received a copy of the GNU General Public License 0015 along with this program; see the file COPYING. If not, write to 0016 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 0017 Boston, MA 02110-1301, USA. 0018 */ 0019 0020 #include "kebsearchline.h" 0021 #include "keditbookmarks_debug.h" 0022 #include <QContextMenuEvent> 0023 #include <QHBoxLayout> 0024 #include <QLabel> 0025 #include <QListView> 0026 #include <QTimer> 0027 #include <QTreeView> 0028 0029 #include <KLocalizedString> 0030 0031 #include <QHeaderView> 0032 #include <QMenu> 0033 0034 //////////////////////////////////////////////////////////////////////////////// 0035 // public methods 0036 //////////////////////////////////////////////////////////////////////////////// 0037 class KViewSearchLine::KViewSearchLinePrivate 0038 { 0039 public: 0040 KViewSearchLinePrivate() 0041 : listView(nullptr) 0042 , treeView(nullptr) 0043 , caseSensitive(false) 0044 , activeSearch(false) 0045 , keepParentsVisible(true) 0046 , queuedSearches(0) 0047 { 0048 } 0049 0050 QListView *listView; 0051 QTreeView *treeView; 0052 bool caseSensitive; 0053 bool activeSearch; 0054 bool keepParentsVisible; 0055 QString search; 0056 int queuedSearches; 0057 QVector<int> searchColumns; 0058 }; 0059 0060 KViewSearchLine::KViewSearchLine(QWidget *parent, QAbstractItemView *v) 0061 : KLineEdit(parent) 0062 { 0063 d = new KViewSearchLinePrivate; 0064 0065 setClearButtonEnabled(true); 0066 0067 d->treeView = dynamic_cast<QTreeView *>(v); 0068 d->listView = dynamic_cast<QListView *>(v); 0069 0070 connect(this, &KViewSearchLine::textChanged, this, &KViewSearchLine::queueSearch); 0071 0072 if (view()) { 0073 connect(view(), &QObject::destroyed, this, &KViewSearchLine::listViewDeleted); 0074 connect(model(), &QAbstractItemModel::dataChanged, this, &KViewSearchLine::slotDataChanged); 0075 connect(model(), &QAbstractItemModel::rowsInserted, this, &KViewSearchLine::slotRowsInserted); 0076 connect(model(), &QAbstractItemModel::rowsRemoved, this, &KViewSearchLine::slotRowsRemoved); 0077 connect(model(), &QAbstractItemModel::columnsInserted, this, &KViewSearchLine::slotColumnsInserted); 0078 connect(model(), &QAbstractItemModel::columnsRemoved, this, &KViewSearchLine::slotColumnsRemoved); 0079 connect(model(), &QAbstractItemModel::modelReset, this, &KViewSearchLine::slotModelReset); 0080 } else 0081 setEnabled(false); 0082 } 0083 0084 KViewSearchLine::KViewSearchLine(QWidget *parent) 0085 : KLineEdit(parent) 0086 { 0087 d = new KViewSearchLinePrivate; 0088 0089 setClearButtonEnabled(true); 0090 0091 d->treeView = nullptr; 0092 d->listView = nullptr; 0093 0094 connect(this, &KViewSearchLine::textChanged, this, &KViewSearchLine::queueSearch); 0095 0096 setEnabled(false); 0097 } 0098 0099 KViewSearchLine::~KViewSearchLine() 0100 { 0101 delete d; 0102 } 0103 0104 QAbstractItemView *KViewSearchLine::view() const 0105 { 0106 if (d->treeView) 0107 return d->treeView; 0108 else 0109 return d->listView; 0110 } 0111 0112 bool KViewSearchLine::caseSensitive() const 0113 { 0114 return d->caseSensitive; 0115 } 0116 0117 bool KViewSearchLine::keepParentsVisible() const 0118 { 0119 return d->keepParentsVisible; 0120 } 0121 0122 //////////////////////////////////////////////////////////////////////////////// 0123 // public slots 0124 //////////////////////////////////////////////////////////////////////////////// 0125 0126 void KViewSearchLine::updateSearch(const QString &s) 0127 { 0128 if (!view()) 0129 return; 0130 0131 d->search = s.isNull() ? text() : s; 0132 0133 // If there's a selected item that is visible, make sure that it's visible 0134 // when the search changes too (assuming that it still matches). 0135 // FIXME reimplement 0136 0137 if (d->keepParentsVisible) 0138 checkItemParentsVisible(model()->index(0, 0, QModelIndex())); 0139 else 0140 checkItemParentsNotVisible(); 0141 } 0142 0143 void KViewSearchLine::setCaseSensitive(bool cs) 0144 { 0145 d->caseSensitive = cs; 0146 } 0147 0148 void KViewSearchLine::setKeepParentsVisible(bool v) 0149 { 0150 d->keepParentsVisible = v; 0151 } 0152 0153 void KViewSearchLine::setSearchColumns(const QVector<int> &columns) 0154 { 0155 d->searchColumns = columns; 0156 } 0157 0158 void KViewSearchLine::setView(QAbstractItemView *v) 0159 { 0160 if (view()) { 0161 disconnect(view(), &QObject::destroyed, this, &KViewSearchLine::listViewDeleted); 0162 disconnect(model(), &QAbstractItemModel::dataChanged, this, &KViewSearchLine::slotDataChanged); 0163 disconnect(model(), &QAbstractItemModel::rowsInserted, this, &KViewSearchLine::slotRowsInserted); 0164 disconnect(model(), &QAbstractItemModel::rowsRemoved, this, &KViewSearchLine::slotRowsRemoved); 0165 disconnect(model(), &QAbstractItemModel::columnsInserted, this, &KViewSearchLine::slotColumnsInserted); 0166 disconnect(model(), &QAbstractItemModel::columnsRemoved, this, &KViewSearchLine::slotColumnsRemoved); 0167 disconnect(model(), &QAbstractItemModel::modelReset, this, &KViewSearchLine::slotModelReset); 0168 } 0169 0170 d->treeView = dynamic_cast<QTreeView *>(v); 0171 d->listView = dynamic_cast<QListView *>(v); 0172 0173 if (view()) { 0174 connect(view(), &QObject::destroyed, this, &KViewSearchLine::listViewDeleted); 0175 0176 connect(model(), &QAbstractItemModel::dataChanged, this, &KViewSearchLine::slotDataChanged); 0177 connect(model(), &QAbstractItemModel::rowsInserted, this, &KViewSearchLine::slotRowsInserted); 0178 connect(model(), &QAbstractItemModel::rowsRemoved, this, &KViewSearchLine::slotRowsRemoved); 0179 connect(model(), &QAbstractItemModel::columnsInserted, this, &KViewSearchLine::slotColumnsInserted); 0180 connect(model(), &QAbstractItemModel::columnsRemoved, this, &KViewSearchLine::slotColumnsRemoved); 0181 connect(model(), &QAbstractItemModel::modelReset, this, &KViewSearchLine::slotModelReset); 0182 } 0183 0184 setEnabled(bool(view())); 0185 } 0186 0187 //////////////////////////////////////////////////////////////////////////////// 0188 // protected members 0189 //////////////////////////////////////////////////////////////////////////////// 0190 0191 bool KViewSearchLine::itemMatches(const QModelIndex &item, const QString &s) const 0192 { 0193 if (s.isEmpty()) 0194 return true; 0195 0196 // If the search column list is populated, search just the columns 0197 // specified. If it is empty default to searching all of the columns. 0198 if (d->treeView) { 0199 int columnCount = d->treeView->header()->count(); 0200 int row = item.row(); 0201 QModelIndex parent = item.parent(); 0202 if (!d->searchColumns.isEmpty()) { 0203 QVector<int>::const_iterator it, end; 0204 end = d->searchColumns.constEnd(); 0205 for (it = d->searchColumns.constBegin(); it != end; ++it) { 0206 if (*it < columnCount) { 0207 const QString &text = model()->data(model()->index(row, *it, parent)).toString(); 0208 if (text.indexOf(s, 0, d->caseSensitive ? Qt::CaseSensitive : Qt::CaseInsensitive) >= 0) 0209 return true; 0210 } 0211 } 0212 } else { 0213 for (int i = 0; i < columnCount; i++) { 0214 if (d->treeView->isColumnHidden(i) == false) { 0215 const QString &text = model()->data(model()->index(row, i, parent)).toString(); 0216 if (text.indexOf(s, 0, d->caseSensitive ? Qt::CaseSensitive : Qt::CaseInsensitive) >= 0) 0217 return true; 0218 } 0219 } 0220 } 0221 return false; 0222 } else { 0223 QString text = model()->data(item).toString(); 0224 if (text.indexOf(s, 0, d->caseSensitive ? Qt::CaseSensitive : Qt::CaseInsensitive) >= 0) 0225 return true; 0226 else 0227 return false; 0228 } 0229 } 0230 0231 void KViewSearchLine::contextMenuEvent(QContextMenuEvent *e) 0232 { 0233 qDeleteAll(actions); 0234 QMenu *popup = KLineEdit::createStandardContextMenu(); 0235 if (d->treeView) { 0236 int columnCount = d->treeView->header()->count(); 0237 actions.resize(columnCount + 1); 0238 if (columnCount) { 0239 QMenu *submenu = new QMenu(i18n("Search Columns"), popup); 0240 popup->addMenu(submenu); 0241 bool allVisibleColumsCheked = true; 0242 QAction *allVisibleAct = new QAction(i18n("All Visible Columns"), nullptr); 0243 allVisibleAct->setCheckable(true); 0244 submenu->addAction(allVisibleAct); 0245 submenu->addSeparator(); 0246 for (int i = 0; i < columnCount; ++i) { 0247 int logicalIndex = d->treeView->header()->logicalIndex(i); 0248 QString columnText = model()->headerData(logicalIndex, Qt::Horizontal).toString(); 0249 if (columnText.isEmpty()) 0250 columnText = i18nc("Column number %1", "Column No. %1", i); 0251 QAction *act = new QAction(columnText, nullptr); 0252 act->setCheckable(true); 0253 if (d->searchColumns.isEmpty() || d->searchColumns.contains(logicalIndex)) 0254 act->setChecked(true); 0255 0256 actions[logicalIndex] = act; 0257 if (!d->treeView || (d->treeView->isColumnHidden(i) == false)) { 0258 submenu->addAction(act); 0259 allVisibleColumsCheked = allVisibleColumsCheked && act->isChecked(); 0260 } 0261 } 0262 actions[columnCount] = allVisibleAct; 0263 if (d->searchColumns.isEmpty() || allVisibleColumsCheked) { 0264 allVisibleAct->setChecked(true); 0265 d->searchColumns.clear(); 0266 } 0267 connect(submenu, &QMenu::triggered, this, &KViewSearchLine::searchColumnsMenuActivated); 0268 } 0269 } 0270 popup->exec(e->globalPos()); 0271 delete popup; 0272 } 0273 0274 //////////////////////////////////////////////////////////////////////////////// 0275 // protected slots 0276 //////////////////////////////////////////////////////////////////////////////// 0277 0278 void KViewSearchLine::queueSearch(const QString &search) 0279 { 0280 d->queuedSearches++; 0281 d->search = search; 0282 QTimer::singleShot(200, this, &KViewSearchLine::activateSearch); 0283 } 0284 0285 void KViewSearchLine::activateSearch() 0286 { 0287 --(d->queuedSearches); 0288 0289 if (d->queuedSearches == 0) 0290 updateSearch(d->search); 0291 } 0292 0293 //////////////////////////////////////////////////////////////////////////////// 0294 // private slots 0295 //////////////////////////////////////////////////////////////////////////////// 0296 0297 void KViewSearchLine::listViewDeleted() 0298 { 0299 d->treeView = nullptr; 0300 d->listView = nullptr; 0301 setEnabled(false); 0302 } 0303 0304 void KViewSearchLine::searchColumnsMenuActivated(QAction *action) 0305 { 0306 int index = 0; 0307 int count = actions.count(); 0308 for (int i = 0; i < count; ++i) { 0309 if (action == actions[i]) { 0310 index = i; 0311 break; 0312 } 0313 } 0314 int columnCount = d->treeView->header()->count(); 0315 if (index == columnCount) { 0316 if (d->searchColumns.isEmpty()) // all columns was checked 0317 d->searchColumns.append(0); 0318 else 0319 d->searchColumns.clear(); 0320 } else { 0321 if (d->searchColumns.contains(index)) 0322 d->searchColumns.removeAll(index); 0323 else { 0324 if (d->searchColumns.isEmpty()) // all columns was checked 0325 { 0326 for (int i = 0; i < columnCount; ++i) 0327 if (i != index) 0328 d->searchColumns.append(i); 0329 } else 0330 d->searchColumns.append(index); 0331 } 0332 } 0333 updateSearch(); 0334 } 0335 0336 void KViewSearchLine::slotRowsRemoved(const QModelIndex &parent, int, int) 0337 { 0338 if (!d->keepParentsVisible) 0339 return; 0340 0341 QModelIndex p = parent; 0342 while (p.isValid()) { 0343 int count = model()->rowCount(p); 0344 if (count && anyVisible(model()->index(0, 0, p), model()->index(count - 1, 0, p))) 0345 return; 0346 if (itemMatches(p, d->search)) 0347 return; 0348 setVisible(p, false); 0349 p = p.parent(); 0350 } 0351 } 0352 0353 void KViewSearchLine::slotColumnsInserted(const QModelIndex &, int, int) 0354 { 0355 updateSearch(); 0356 } 0357 0358 void KViewSearchLine::slotColumnsRemoved(const QModelIndex &, int first, int last) 0359 { 0360 if (d->treeView) 0361 updateSearch(); 0362 else { 0363 if (d->listView->modelColumn() >= first && d->listView->modelColumn() <= last) { 0364 if (d->listView->modelColumn() > last) 0365 qCCritical(KEDITBOOKMARKS_LOG) << "Columns were removed, the modelColumn() doesn't exist anymore. " 0366 "K4listViewSearchLine can't cope with that."; 0367 updateSearch(); 0368 } 0369 } 0370 } 0371 0372 void KViewSearchLine::slotModelReset() 0373 { 0374 // FIXME Is there a way to ensure that the view 0375 // has already responded to the reset signal? 0376 updateSearch(); 0377 } 0378 0379 void KViewSearchLine::slotDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight) 0380 { 0381 QModelIndex parent = topLeft.parent(); 0382 int column = 0; 0383 if (d->listView) 0384 column = d->listView->modelColumn(); 0385 bool match = recheck(model()->index(topLeft.row(), column, parent), model()->index(bottomRight.row(), column, parent)); 0386 if (!d->keepParentsVisible) 0387 return; 0388 if (!parent.isValid()) // includes listview 0389 return; 0390 if (match) { 0391 QModelIndex p = parent; 0392 while (p.isValid()) { 0393 setVisible(p, true); 0394 p = p.parent(); 0395 } 0396 } else // no match => might need to hide parents (this is ugly) 0397 { 0398 if (isVisible(parent) == false) // parent is already hidden 0399 return; 0400 // parent is visible => implies all parents visible 0401 0402 // first check if all of the unchanged rows are hidden 0403 match = false; 0404 if (topLeft.row() >= 1) 0405 match = match || anyVisible(model()->index(0, 0, parent), model()->index(topLeft.row() - 1, 0, parent)); 0406 int rowCount = model()->rowCount(parent); 0407 if (bottomRight.row() + 1 <= rowCount - 1) 0408 match = match || anyVisible(model()->index(bottomRight.row() + 1, 0, parent), model()->index(rowCount - 1, 0, parent)); 0409 if (!match) // all child rows hidden 0410 { 0411 if (itemMatches(parent, d->search)) 0412 return; 0413 // and parent didn't match, hide it 0414 setVisible(parent, false); 0415 0416 // need to check all the way up to root 0417 QModelIndex p = parent.parent(); 0418 while (p.isValid()) { 0419 // hide p if no children of p isVisible and it doesn't match 0420 int count = model()->rowCount(p); 0421 if (anyVisible(model()->index(0, 0, p), model()->index(count - 1, 0, p))) 0422 return; 0423 0424 if (itemMatches(p, d->search)) 0425 return; 0426 setVisible(p, false); 0427 p = p.parent(); 0428 } 0429 } 0430 } 0431 } 0432 0433 //////////////////////////////////////////////////////////////////////////////// 0434 // private methods 0435 //////////////////////////////////////////////////////////////////////////////// 0436 QAbstractItemModel *KViewSearchLine::model() const 0437 { 0438 if (d->treeView) 0439 return d->treeView->model(); 0440 else 0441 return d->listView->model(); 0442 } 0443 0444 bool KViewSearchLine::anyVisible(const QModelIndex &first, const QModelIndex &last) 0445 { 0446 Q_ASSERT(d->treeView); 0447 QModelIndex index = first; 0448 while (true) { 0449 if (isVisible(index)) 0450 return true; 0451 if (index == last) 0452 break; 0453 index = nextRow(index); 0454 } 0455 return false; 0456 } 0457 0458 bool KViewSearchLine::isVisible(const QModelIndex &index) 0459 { 0460 if (d->treeView) 0461 return !d->treeView->isRowHidden(index.row(), index.parent()); 0462 else 0463 return d->listView->isRowHidden(index.row()); 0464 } 0465 0466 QModelIndex KViewSearchLine::nextRow(const QModelIndex &index) 0467 { 0468 return model()->index(index.row() + 1, index.column(), index.parent()); 0469 } 0470 0471 bool KViewSearchLine::recheck(const QModelIndex &first, const QModelIndex &last) 0472 { 0473 bool visible = false; 0474 QModelIndex index = first; 0475 while (true) { 0476 int rowCount = model()->rowCount(index); 0477 if (d->keepParentsVisible && rowCount && anyVisible(model()->index(0, 0, index), model()->index(rowCount - 1, 0, index))) { 0478 visible = true; 0479 } else // no children visible 0480 { 0481 bool match = itemMatches(index, d->search); 0482 setVisible(index, match); 0483 visible = visible || match; 0484 } 0485 if (index == last) 0486 break; 0487 index = nextRow(index); 0488 } 0489 return visible; 0490 } 0491 0492 void KViewSearchLine::slotRowsInserted(const QModelIndex &parent, int first, int last) 0493 { 0494 bool visible = false; 0495 int column = 0; 0496 if (d->listView) 0497 column = d->listView->modelColumn(); 0498 0499 QModelIndex index = model()->index(first, column, parent); 0500 QModelIndex end = model()->index(last, column, parent); 0501 while (true) { 0502 if (itemMatches(index, d->search)) { 0503 visible = true; 0504 setVisible(index, true); 0505 } else 0506 setVisible(index, false); 0507 if (index == end) 0508 break; 0509 index = nextRow(index); 0510 } 0511 0512 if (!d->keepParentsVisible) 0513 return; 0514 if (visible) { 0515 QModelIndex p = parent; 0516 while (p.isValid()) { 0517 setVisible(p, true); 0518 p = p.parent(); 0519 } 0520 } 0521 } 0522 0523 void KViewSearchLine::setVisible(const QModelIndex &index, bool v) 0524 { 0525 if (d->treeView) 0526 d->treeView->setRowHidden(index.row(), index.parent(), !v); 0527 else 0528 d->listView->setRowHidden(index.row(), !v); 0529 } 0530 0531 void KViewSearchLine::checkItemParentsNotVisible() 0532 { 0533 int rowCount = model()->rowCount(QModelIndex()); 0534 int column = 0; 0535 if (d->listView) 0536 column = d->listView->modelColumn(); 0537 for (int i = 0; i < rowCount; ++i) { 0538 QModelIndex it = model()->index(i, column, QModelIndex()); 0539 if (itemMatches(it, d->search)) 0540 setVisible(it, true); 0541 else 0542 setVisible(it, false); 0543 } 0544 } 0545 0546 /** Check whether \p index, its siblings and their descendants should be shown. Show or hide the items as necessary. 0547 * 0548 * \p index The list view item to start showing / hiding items at. Typically, this is the first child of another item, or the 0549 * the first child of the list view. 0550 * \return \c true if an item which should be visible is found, \c false if all items found should be hidden. 0551 */ 0552 bool KViewSearchLine::checkItemParentsVisible(QModelIndex index) 0553 { 0554 bool visible = false; 0555 int rowCount = model()->rowCount(index.parent()); 0556 int column = 0; 0557 if (d->listView) 0558 column = d->listView->modelColumn(); 0559 for (int i = 0; i < rowCount; ++i) { 0560 index = model()->index(i, column, index.parent()); 0561 if ((model()->rowCount(index) && checkItemParentsVisible(model()->index(0, column, index))) || itemMatches(index, d->search)) { 0562 visible = true; 0563 setVisible(index, true); 0564 } else 0565 setVisible(index, false); 0566 } 0567 return visible; 0568 } 0569 0570 //////////////////////////////////////////////////////////////////////////////// 0571 // KViewSearchLineWidget 0572 //////////////////////////////////////////////////////////////////////////////// 0573 0574 class KViewSearchLineWidget::KViewSearchLineWidgetPrivate 0575 { 0576 public: 0577 KViewSearchLineWidgetPrivate() 0578 : view(nullptr) 0579 , searchLine(nullptr) 0580 , layout(nullptr) 0581 { 0582 } 0583 QAbstractItemView *view; 0584 KViewSearchLine *searchLine; 0585 QHBoxLayout *layout; 0586 }; 0587 0588 KViewSearchLineWidget::KViewSearchLineWidget(QAbstractItemView *view, QWidget *parent) 0589 : QWidget(parent) 0590 { 0591 d = new KViewSearchLineWidgetPrivate; 0592 d->view = view; 0593 0594 QTimer::singleShot(0, this, &KViewSearchLineWidget::createWidgets); 0595 } 0596 0597 KViewSearchLineWidget::~KViewSearchLineWidget() 0598 { 0599 delete d->layout; 0600 delete d; 0601 } 0602 0603 KViewSearchLine *KViewSearchLineWidget::createSearchLine(QAbstractItemView *view) 0604 { 0605 if (!d->searchLine) 0606 d->searchLine = new KViewSearchLine(nullptr, view); 0607 return d->searchLine; 0608 } 0609 0610 void KViewSearchLineWidget::createWidgets() 0611 { 0612 d->layout = new QHBoxLayout(this); 0613 d->layout->setContentsMargins(0, 0, 0, 0); 0614 0615 QLabel *label = new QLabel(i18n("S&earch:")); 0616 label->setObjectName(QStringLiteral("kde toolbar widget")); 0617 d->layout->addWidget(label); 0618 0619 d->searchLine = createSearchLine(d->view); 0620 d->layout->addWidget(d->searchLine); 0621 d->searchLine->show(); 0622 0623 label->setBuddy(d->searchLine); 0624 label->show(); 0625 } 0626 0627 KViewSearchLine *KViewSearchLineWidget::searchLine() const 0628 { 0629 return d->searchLine; 0630 } 0631 0632 #include "moc_kebsearchline.cpp"