/* Copyright (c) 2003 Scott Wheeler Copyright (c) 2005 Rafal Rzepecki Copyright (c) 2006 Hamish Rodda Copyright 2007 Pino Toscano This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "ktreeviewsearchline.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include class KTreeViewSearchLine::Private { public: Private( KTreeViewSearchLine *_parent ) : parent( _parent ), treeView( nullptr ), caseSensitive( Qt::CaseInsensitive ), regularExpression( false ), activeSearch( false ), queuedSearches( 0 ) { } KTreeViewSearchLine *parent; QTreeView * treeView; Qt::CaseSensitivity caseSensitive; bool regularExpression; bool activeSearch; QString search; int queuedSearches; void rowsInserted(const QModelIndex & parent, int start, int end) const; void treeViewDeleted( QObject *object ); void slotCaseSensitive(); void slotRegularExpression(); void checkItemParentsNotVisible(QTreeView *treeView); bool filterItems(QTreeView *treeView, const QModelIndex &index); }; //////////////////////////////////////////////////////////////////////////////// // private slots //////////////////////////////////////////////////////////////////////////////// void KTreeViewSearchLine::Private::rowsInserted( const QModelIndex & parentIndex, int start, int end ) const { QAbstractItemModel* model = qobject_cast( parent->sender() ); if ( !model ) return; QTreeView* widget = nullptr; if ( treeView->model() == model ) { widget = treeView; } if ( !widget ) return; for ( int i = start; i <= end; ++i ) { widget->setRowHidden( i, parentIndex, !parent->itemMatches( parentIndex, i, parent->text() ) ); } } void KTreeViewSearchLine::Private::treeViewDeleted( QObject *object ) { if ( object == treeView ) { treeView = nullptr; parent->setEnabled( false ); } } void KTreeViewSearchLine::Private::slotCaseSensitive() { if ( caseSensitive == Qt::CaseSensitive) parent->setCaseSensitivity( Qt::CaseInsensitive ); else parent->setCaseSensitivity( Qt::CaseSensitive ); parent->updateSearch(); } void KTreeViewSearchLine::Private::slotRegularExpression() { if ( regularExpression ) parent->setRegularExpression( false ); else parent->setRegularExpression( true ); parent->updateSearch(); } //////////////////////////////////////////////////////////////////////////////// // private methods //////////////////////////////////////////////////////////////////////////////// /** Check whether \p item, its siblings and their descendants should be shown. Show or hide the items as necessary. * * \p item The list view item to start showing / hiding items at. Typically, this is the first child of another item, or the * the first child of the list view. * \return \c true if an item which should be visible is found, \c false if all items found should be hidden. If this function * returns true and \p highestHiddenParent was not 0, highestHiddenParent will have been shown. */ bool KTreeViewSearchLine::Private::filterItems( QTreeView *treeView, const QModelIndex &index ) { bool childMatch = false; const int rowcount = treeView->model()->rowCount( index ); for ( int i = 0; i < rowcount; ++i ) childMatch |= filterItems( treeView, treeView->model()->index( i, 0, index ) ); // Should this item be shown? It should if any children should be, or if it matches. const QModelIndex parentindex = index.parent(); if ( childMatch || parent->itemMatches( parentindex, index.row(), search ) ) { treeView->setRowHidden( index.row(), parentindex, false ); return true; } treeView->setRowHidden( index.row(), parentindex, true ); return false; } //////////////////////////////////////////////////////////////////////////////// // public methods //////////////////////////////////////////////////////////////////////////////// KTreeViewSearchLine::KTreeViewSearchLine( QWidget *parent, QTreeView *treeView ) : KLineEdit( parent ), d( new Private( this ) ) { connect(this, &KTreeViewSearchLine::textChanged, this, &KTreeViewSearchLine::queueSearch); setClearButtonEnabled( true ); setTreeView( treeView ); if ( !treeView ) { setEnabled( false ); } } KTreeViewSearchLine::~KTreeViewSearchLine() { delete d; } Qt::CaseSensitivity KTreeViewSearchLine::caseSensitivity() const { return d->caseSensitive; } bool KTreeViewSearchLine::regularExpression() const { return d->regularExpression; } QTreeView *KTreeViewSearchLine::treeView() const { return d->treeView; } //////////////////////////////////////////////////////////////////////////////// // public slots //////////////////////////////////////////////////////////////////////////////// void KTreeViewSearchLine::updateSearch( const QString &pattern ) { d->search = pattern.isNull() ? text() : pattern; updateSearch( d->treeView ); } void KTreeViewSearchLine::updateSearch( QTreeView *treeView ) { if ( !treeView || !treeView->model()->rowCount() ) return; // If there's a selected item that is visible, make sure that it's visible // when the search changes too (assuming that it still matches). QModelIndex currentIndex = treeView->currentIndex(); bool wasUpdateEnabled = treeView->updatesEnabled(); treeView->setUpdatesEnabled( false ); d->filterItems( treeView, treeView->rootIndex() ); treeView->setUpdatesEnabled( wasUpdateEnabled ); if ( currentIndex.isValid() ) treeView->scrollTo( currentIndex ); } void KTreeViewSearchLine::setCaseSensitivity( Qt::CaseSensitivity caseSensitivity ) { if ( d->caseSensitive != caseSensitivity ) { d->caseSensitive = caseSensitivity; updateSearch(); emit searchOptionsChanged(); } } void KTreeViewSearchLine::setRegularExpression( bool value ) { if ( d->regularExpression != value ) { d->regularExpression = value; updateSearch(); emit searchOptionsChanged(); } } void KTreeViewSearchLine::setTreeView( QTreeView *treeView ) { disconnectTreeView( d->treeView ); d->treeView = treeView; connectTreeView( treeView ); setEnabled( treeView != nullptr ); } //////////////////////////////////////////////////////////////////////////////// // protected members //////////////////////////////////////////////////////////////////////////////// bool KTreeViewSearchLine::itemMatches( const QModelIndex &parentIndex, int row, const QString &pattern ) const { if ( pattern.isEmpty() ) return true; if ( !parentIndex.isValid() && parentIndex != d->treeView->rootIndex() ) return false; // Construct a regular expression object with the right options. QRegularExpression re; if (d->regularExpression) { re.setPattern(pattern); re.setPatternOptions(d->caseSensitive ? QRegularExpression::NoPatternOption : QRegularExpression::CaseInsensitiveOption); } // If the search column list is populated, search just the columns // specified. If it is empty default to searching all of the columns. QAbstractItemModel *model = d->treeView->model(); const int columncount = model->columnCount( parentIndex ); for ( int i = 0; i < columncount; ++i) { const QString str = model->data( model->index( row, i, parentIndex ), Qt::DisplayRole ).toString(); if (d->regularExpression) { return str.contains(re); } else { return str.contains(pattern, d->caseSensitive? Qt::CaseSensitive : Qt::CaseInsensitive); } } return false; } void KTreeViewSearchLine::contextMenuEvent( QContextMenuEvent *event ) { QMenu *popup = KLineEdit::createStandardContextMenu(); popup->addSeparator(); QMenu *optionsSubMenu = popup->addMenu( i18n("Search Options") ); QAction* caseSensitiveAction = optionsSubMenu->addAction( i18nc("Enable case sensitive search in the side navigation panels", "Case Sensitive"), this, [this] { d->slotCaseSensitive(); } ); caseSensitiveAction->setCheckable( true ); caseSensitiveAction->setChecked( d->caseSensitive ); QAction* regularExpressionAction = optionsSubMenu->addAction( i18nc("Enable regular expression search in the side navigation panels", "Regular Expression"), this, [this] { d->slotRegularExpression(); } ); regularExpressionAction->setCheckable( true ); regularExpressionAction->setChecked( d->regularExpression ); popup->exec( event->globalPos() ); delete popup; } void KTreeViewSearchLine::connectTreeView( QTreeView *treeView ) { if ( treeView ) { connect( treeView, &QTreeView::destroyed, this, &KTreeViewSearchLine::treeViewDeleted ); connect( treeView->model(), &QAbstractItemModel::rowsInserted, this, &KTreeViewSearchLine::rowsInserted ); } } void KTreeViewSearchLine::disconnectTreeView( QTreeView *treeView ) { if ( treeView ) { disconnect( treeView, &QTreeView::destroyed, this, &KTreeViewSearchLine::treeViewDeleted ); disconnect( treeView->model(), &QAbstractItemModel::rowsInserted, this, &KTreeViewSearchLine::rowsInserted ); } } //////////////////////////////////////////////////////////////////////////////// // protected slots //////////////////////////////////////////////////////////////////////////////// void KTreeViewSearchLine::queueSearch( const QString &search ) { d->queuedSearches++; d->search = search; QTimer::singleShot( 200, this, &KTreeViewSearchLine::activateSearch ); } void KTreeViewSearchLine::activateSearch() { --(d->queuedSearches); if ( d->queuedSearches == 0 ) updateSearch( d->search ); } //////////////////////////////////////////////////////////////////////////////// // private functions //////////////////////////////////////////////////////////////////////////////// void KTreeViewSearchLine::rowsInserted(const QModelIndex & parent, int start, int end) const { d->rowsInserted(parent, start, end); } void KTreeViewSearchLine::treeViewDeleted( QObject *treeView ) { d->treeViewDeleted( treeView ); } //////////////////////////////////////////////////////////////////////////////// // KTreeViewSearchLineWidget //////////////////////////////////////////////////////////////////////////////// class KTreeViewSearchLineWidget::Private { public: Private() : treeView( nullptr ), searchLine( nullptr ) { } QTreeView *treeView; KTreeViewSearchLine *searchLine; }; KTreeViewSearchLineWidget::KTreeViewSearchLineWidget( QWidget *parent, QTreeView *treeView ) : QWidget( parent ), d( new Private ) { d->treeView = treeView; QTimer::singleShot( 0, this, &KTreeViewSearchLineWidget::createWidgets ); } KTreeViewSearchLineWidget::~KTreeViewSearchLineWidget() { delete d; } KTreeViewSearchLine *KTreeViewSearchLineWidget::createSearchLine( QTreeView *treeView ) const { return new KTreeViewSearchLine( const_cast(this), treeView ); } void KTreeViewSearchLineWidget::createWidgets() { QLabel *label = new QLabel( i18n("S&earch:"), this ); label->setObjectName( QStringLiteral("kde toolbar widget") ); searchLine()->show(); label->setBuddy( d->searchLine ); label->show(); QHBoxLayout* layout = new QHBoxLayout( this ); layout->setSpacing( 5 ); layout->setContentsMargins( 0, 0, 0, 0 ); layout->addWidget( label ); layout->addWidget( d->searchLine ); } KTreeViewSearchLine *KTreeViewSearchLineWidget::searchLine() const { if ( !d->searchLine ) d->searchLine = createSearchLine( d->treeView ); return d->searchLine; } #include "moc_ktreeviewsearchline.cpp"