KDEUI
ktreewidgetsearchline.cpp
Go to the documentation of this file.
00001 /* This file is part of the KDE libraries 00002 Copyright (c) 2003 Scott Wheeler <wheeler@kde.org> 00003 Copyright (c) 2005 Rafal Rzepecki <divide@users.sourceforge.net> 00004 Copyright (c) 2006 Hamish Rodda <rodda@kde.org> 00005 00006 This library is free software; you can redistribute it and/or 00007 modify it under the terms of the GNU Library General Public 00008 License version 2 as published by the Free Software Foundation. 00009 00010 This library is distributed in the hope that it will be useful, 00011 but WITHOUT ANY WARRANTY; without even the implied warranty of 00012 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 00013 Library General Public License for more details. 00014 00015 You should have received a copy of the GNU Library General Public License 00016 along with this library; see the file COPYING.LIB. If not, write to 00017 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 00018 Boston, MA 02110-1301, USA. 00019 */ 00020 00021 #include "ktreewidgetsearchline.h" 00022 00023 #include <QtCore/QList> 00024 #include <QtCore/QTimer> 00025 #include <QtGui/QApplication> 00026 #include <QtGui/QContextMenuEvent> 00027 #include <QtGui/QHBoxLayout> 00028 #include <QtGui/QHeaderView> 00029 #include <QtGui/QLabel> 00030 #include <QtGui/QMenu> 00031 #include <QtGui/QToolButton> 00032 #include <QtGui/QTreeWidget> 00033 00034 #include <kdebug.h> 00035 #include <kiconloader.h> 00036 #include <klocale.h> 00037 #include <ktoolbar.h> 00038 00039 class KTreeWidgetSearchLine::Private 00040 { 00041 public: 00042 Private( KTreeWidgetSearchLine *_q ) 00043 : q( _q ), 00044 caseSensitive( Qt::CaseInsensitive ), 00045 keepParentsVisible( true ), 00046 canChooseColumns( true ), 00047 queuedSearches( 0 ) 00048 { 00049 } 00050 00051 KTreeWidgetSearchLine *q; 00052 QList<QTreeWidget *> treeWidgets; 00053 Qt::CaseSensitivity caseSensitive; 00054 bool keepParentsVisible; 00055 bool canChooseColumns; 00056 QString search; 00057 int queuedSearches; 00058 QList<int> searchColumns; 00059 00060 void _k_rowsInserted(const QModelIndex & parent, int start, int end) const; 00061 void _k_treeWidgetDeleted( QObject *treeWidget ); 00062 void _k_slotColumnActivated(QAction* action); 00063 void _k_slotAllVisibleColumns(); 00064 void _k_queueSearch(const QString&); 00065 void _k_activateSearch(); 00066 00067 void checkColumns(); 00068 void checkItemParentsNotVisible(QTreeWidget *treeWidget); 00069 bool checkItemParentsVisible(QTreeWidgetItem* item); 00070 }; 00071 00073 // private slots 00075 00076 // Hack to make a protected method public 00077 class QTreeWidgetWorkaround : public QTreeWidget 00078 { 00079 public: 00080 QTreeWidgetItem *itemFromIndex( const QModelIndex &index ) const 00081 { 00082 return QTreeWidget::itemFromIndex( index ); 00083 } 00084 }; 00085 00086 void KTreeWidgetSearchLine::Private::_k_rowsInserted( const QModelIndex & parentIndex, int start, int end ) const 00087 { 00088 QAbstractItemModel* model = qobject_cast<QAbstractItemModel*>( q->sender() ); 00089 if ( !model ) 00090 return; 00091 00092 QTreeWidget* widget = 0L; 00093 foreach ( QTreeWidget* tree, treeWidgets ) 00094 if ( tree->model() == model ) { 00095 widget = tree; 00096 break; 00097 } 00098 00099 if ( !widget ) 00100 return; 00101 00102 QTreeWidgetWorkaround* widgetW = static_cast<QTreeWidgetWorkaround *>(widget); 00103 for (int i = start; i <= end; ++i) { 00104 if (QTreeWidgetItem *item = widgetW->itemFromIndex(model->index(i, 0, parentIndex))) { 00105 bool newHidden = !q->itemMatches(item, q->text()); 00106 if (item->isHidden() != newHidden) { 00107 item->setHidden(newHidden); 00108 emit q->hiddenChanged(item, newHidden); 00109 } 00110 } 00111 } 00112 } 00113 00114 void KTreeWidgetSearchLine::Private::_k_treeWidgetDeleted( QObject *object ) 00115 { 00116 treeWidgets.removeAll( static_cast<QTreeWidget *>( object ) ); 00117 q->setEnabled( treeWidgets.isEmpty() ); 00118 } 00119 00120 void KTreeWidgetSearchLine::Private::_k_slotColumnActivated( QAction *action ) 00121 { 00122 if ( !action ) 00123 return; 00124 00125 bool ok; 00126 int column = action->data().toInt( &ok ); 00127 00128 if ( !ok ) 00129 return; 00130 00131 if ( action->isChecked() ) { 00132 if ( !searchColumns.isEmpty() ) { 00133 if ( !searchColumns.contains( column ) ) 00134 searchColumns.append( column ); 00135 00136 if ( searchColumns.count() == treeWidgets.first()->header()->count() - treeWidgets.first()->header()->hiddenSectionCount() ) 00137 searchColumns.clear(); 00138 00139 } else { 00140 searchColumns.append( column ); 00141 } 00142 } else { 00143 if ( searchColumns.isEmpty() ) { 00144 QHeaderView* const header = treeWidgets.first()->header(); 00145 00146 for ( int i = 0; i < header->count(); i++ ) { 00147 if ( i != column && !header->isSectionHidden( i ) ) 00148 searchColumns.append( i ); 00149 } 00150 00151 } else if ( searchColumns.contains( column ) ) { 00152 searchColumns.removeAll( column ); 00153 } 00154 } 00155 00156 q->updateSearch(); 00157 } 00158 00159 void KTreeWidgetSearchLine::Private::_k_slotAllVisibleColumns() 00160 { 00161 if ( searchColumns.isEmpty() ) 00162 searchColumns.append( 0 ); 00163 else 00164 searchColumns.clear(); 00165 00166 q->updateSearch(); 00167 } 00168 00170 // private methods 00172 00173 00174 void KTreeWidgetSearchLine::Private::checkColumns() 00175 { 00176 canChooseColumns = q->canChooseColumnsCheck(); 00177 } 00178 00179 void KTreeWidgetSearchLine::Private::checkItemParentsNotVisible(QTreeWidget *treeWidget) 00180 { 00181 for (QTreeWidgetItemIterator it(treeWidget); *it; ++it) { 00182 QTreeWidgetItem *item = *it; 00183 bool newHidden = !q->itemMatches(item, search); 00184 if (item->isHidden() != newHidden) { 00185 item->setHidden(newHidden); 00186 emit q->hiddenChanged(item, newHidden); 00187 } 00188 } 00189 } 00190 00191 #include <kvbox.h> 00192 00200 bool KTreeWidgetSearchLine::Private::checkItemParentsVisible(QTreeWidgetItem *item) 00201 { 00202 bool childMatch = false; 00203 for (int i = 0; i < item->childCount(); ++i) { 00204 childMatch |= checkItemParentsVisible(item->child(i)); 00205 } 00206 00207 // Should this item be shown? It should if any children should be, or if it matches. 00208 bool newHidden = !childMatch && !q->itemMatches(item, search); 00209 if (item->isHidden() != newHidden) { 00210 item->setHidden(newHidden); 00211 emit q->hiddenChanged(item, newHidden); 00212 } 00213 00214 return !newHidden; 00215 } 00216 00217 00219 // public methods 00221 00222 KTreeWidgetSearchLine::KTreeWidgetSearchLine( QWidget *q, QTreeWidget *treeWidget ) 00223 : KLineEdit( q ), d( new Private( this ) ) 00224 { 00225 connect( this, SIGNAL( textChanged( const QString& ) ), 00226 this, SLOT( _k_queueSearch( const QString& ) ) ); 00227 00228 setClearButtonShown( true ); 00229 setTreeWidget( treeWidget ); 00230 00231 if ( !treeWidget ) { 00232 setEnabled( false ); 00233 } 00234 } 00235 00236 KTreeWidgetSearchLine::KTreeWidgetSearchLine( QWidget *q, 00237 const QList<QTreeWidget *> &treeWidgets ) 00238 : KLineEdit( q ), d( new Private( this ) ) 00239 { 00240 connect( this, SIGNAL( textChanged( const QString& ) ), 00241 this, SLOT( _k_queueSearch( const QString& ) ) ); 00242 00243 setClearButtonShown( true ); 00244 setTreeWidgets( treeWidgets ); 00245 } 00246 00247 KTreeWidgetSearchLine::~KTreeWidgetSearchLine() 00248 { 00249 delete d; 00250 } 00251 00252 Qt::CaseSensitivity KTreeWidgetSearchLine::caseSensitivity() const 00253 { 00254 return d->caseSensitive; 00255 } 00256 00257 QList<int> KTreeWidgetSearchLine::searchColumns() const 00258 { 00259 if ( d->canChooseColumns ) 00260 return d->searchColumns; 00261 else 00262 return QList<int>(); 00263 } 00264 00265 bool KTreeWidgetSearchLine::keepParentsVisible() const 00266 { 00267 return d->keepParentsVisible; 00268 } 00269 00270 QTreeWidget *KTreeWidgetSearchLine::treeWidget() const 00271 { 00272 if ( d->treeWidgets.count() == 1 ) 00273 return d->treeWidgets.first(); 00274 else 00275 return 0; 00276 } 00277 00278 QList<QTreeWidget *> KTreeWidgetSearchLine::treeWidgets() const 00279 { 00280 return d->treeWidgets; 00281 } 00282 00283 00285 // public slots 00287 00288 void KTreeWidgetSearchLine::addTreeWidget( QTreeWidget *treeWidget ) 00289 { 00290 if ( treeWidget ) { 00291 connectTreeWidget( treeWidget ); 00292 00293 d->treeWidgets.append( treeWidget ); 00294 setEnabled( !d->treeWidgets.isEmpty() ); 00295 00296 d->checkColumns(); 00297 } 00298 } 00299 00300 void KTreeWidgetSearchLine::removeTreeWidget( QTreeWidget *treeWidget ) 00301 { 00302 if ( treeWidget ) { 00303 int index = d->treeWidgets.indexOf( treeWidget ); 00304 00305 if ( index != -1 ) { 00306 d->treeWidgets.removeAt( index ); 00307 d->checkColumns(); 00308 00309 disconnectTreeWidget( treeWidget ); 00310 00311 setEnabled( !d->treeWidgets.isEmpty() ); 00312 } 00313 } 00314 } 00315 00316 void KTreeWidgetSearchLine::updateSearch( const QString &pattern ) 00317 { 00318 d->search = pattern.isNull() ? text() : pattern; 00319 00320 foreach ( QTreeWidget* treeWidget, d->treeWidgets ) 00321 updateSearch( treeWidget ); 00322 } 00323 00324 void KTreeWidgetSearchLine::updateSearch( QTreeWidget *treeWidget ) 00325 { 00326 if ( !treeWidget || !treeWidget->topLevelItemCount() ) 00327 return; 00328 00329 00330 // If there's a selected item that is visible, make sure that it's visible 00331 // when the search changes too (assuming that it still matches). 00332 00333 QTreeWidgetItem *currentItem = treeWidget->currentItem(); 00334 00335 if ( d->keepParentsVisible ) 00336 for ( int i = 0; i < treeWidget->topLevelItemCount(); ++i ) 00337 d->checkItemParentsVisible( treeWidget->topLevelItem( i ) ); 00338 else 00339 d->checkItemParentsNotVisible( treeWidget ); 00340 00341 if ( currentItem ) 00342 treeWidget->scrollToItem( currentItem ); 00343 } 00344 00345 void KTreeWidgetSearchLine::setCaseSensitivity( Qt::CaseSensitivity caseSensitive ) 00346 { 00347 if ( d->caseSensitive != caseSensitive ) { 00348 d->caseSensitive = caseSensitive; 00349 updateSearch(); 00350 } 00351 } 00352 00353 void KTreeWidgetSearchLine::setKeepParentsVisible( bool visible ) 00354 { 00355 if ( d->keepParentsVisible != visible ) { 00356 d->keepParentsVisible = visible; 00357 updateSearch(); 00358 } 00359 } 00360 00361 void KTreeWidgetSearchLine::setSearchColumns( const QList<int> &columns ) 00362 { 00363 if ( d->canChooseColumns ) 00364 d->searchColumns = columns; 00365 } 00366 00367 void KTreeWidgetSearchLine::setTreeWidget( QTreeWidget *treeWidget ) 00368 { 00369 setTreeWidgets( QList<QTreeWidget *>() ); 00370 addTreeWidget( treeWidget ); 00371 } 00372 00373 void KTreeWidgetSearchLine::setTreeWidgets( const QList<QTreeWidget *> &treeWidgets ) 00374 { 00375 foreach ( QTreeWidget* treeWidget, d->treeWidgets ) 00376 disconnectTreeWidget( treeWidget ); 00377 00378 d->treeWidgets = treeWidgets; 00379 00380 foreach ( QTreeWidget* treeWidget, d->treeWidgets ) 00381 connectTreeWidget( treeWidget ); 00382 00383 d->checkColumns(); 00384 00385 setEnabled( !d->treeWidgets.isEmpty() ); 00386 } 00387 00389 // protected members 00391 00392 bool KTreeWidgetSearchLine::itemMatches( const QTreeWidgetItem *item, const QString &pattern ) const 00393 { 00394 if ( pattern.isEmpty() ) 00395 return true; 00396 00397 // If the search column list is populated, search just the columns 00398 // specified. If it is empty default to searching all of the columns. 00399 00400 if ( !d->searchColumns.isEmpty() ) { 00401 QList<int>::ConstIterator it = d->searchColumns.constBegin(); 00402 for ( ; it != d->searchColumns.constEnd(); ++it ) { 00403 if ( *it < item->treeWidget()->columnCount() && 00404 item->text( *it ).indexOf( pattern, 0, d->caseSensitive ) >= 0 ) 00405 return true; 00406 } 00407 } else { 00408 for ( int i = 0; i < item->treeWidget()->columnCount(); i++) { 00409 if ( item->treeWidget()->columnWidth(i) > 0 && 00410 item->text( i ).indexOf( pattern, 0, d->caseSensitive ) >= 0 ) 00411 return true; 00412 } 00413 } 00414 00415 return false; 00416 } 00417 00418 void KTreeWidgetSearchLine::contextMenuEvent( QContextMenuEvent *event ) 00419 { 00420 QMenu *popup = KLineEdit::createStandardContextMenu(); 00421 00422 if ( d->canChooseColumns ) { 00423 popup->addSeparator(); 00424 QMenu *subMenu = popup->addMenu( i18n("Search Columns") ); 00425 00426 QAction* allVisibleColumnsAction = subMenu->addAction( i18n("All Visible Columns"), 00427 this, SLOT( _k_slotAllVisibleColumns() ) ); 00428 allVisibleColumnsAction->setCheckable( true ); 00429 allVisibleColumnsAction->setChecked( !d->searchColumns.count() ); 00430 subMenu->addSeparator(); 00431 00432 bool allColumnsAreSearchColumns = true; 00433 00434 QActionGroup* group = new QActionGroup( popup ); 00435 group->setExclusive( false ); 00436 connect( group, SIGNAL( triggered( QAction* ) ), SLOT( _k_slotColumnActivated( QAction* ) ) ); 00437 00438 QHeaderView* const header = d->treeWidgets.first()->header(); 00439 for ( int j = 0; j < header->count(); j++ ) { 00440 int i = header->logicalIndex( j ); 00441 00442 if ( header->isSectionHidden( i ) ) 00443 continue; 00444 00445 QString columnText = d->treeWidgets.first()->headerItem()->text( i ); 00446 QAction* columnAction = subMenu->addAction( d->treeWidgets.first()->headerItem()->icon( i ), columnText ); 00447 columnAction->setCheckable( true ); 00448 columnAction->setChecked( d->searchColumns.isEmpty() || d->searchColumns.contains( i ) ); 00449 columnAction->setData( i ); 00450 columnAction->setActionGroup( group ); 00451 00452 if ( d->searchColumns.isEmpty() || d->searchColumns.indexOf( i ) != -1 ) 00453 columnAction->setChecked( true ); 00454 else 00455 allColumnsAreSearchColumns = false; 00456 } 00457 00458 allVisibleColumnsAction->setChecked( allColumnsAreSearchColumns ); 00459 00460 // searchColumnsMenuActivated() relies on one possible "all" representation 00461 if ( allColumnsAreSearchColumns && !d->searchColumns.isEmpty() ) 00462 d->searchColumns.clear(); 00463 } 00464 00465 popup->exec( event->globalPos() ); 00466 delete popup; 00467 } 00468 00469 void KTreeWidgetSearchLine::connectTreeWidget( QTreeWidget *treeWidget ) 00470 { 00471 connect( treeWidget, SIGNAL( destroyed( QObject* ) ), 00472 this, SLOT( _k_treeWidgetDeleted( QObject* ) ) ); 00473 00474 connect( treeWidget->model(), SIGNAL( rowsInserted( const QModelIndex&, int, int) ), 00475 this, SLOT( _k_rowsInserted( const QModelIndex&, int, int ) ) ); 00476 } 00477 00478 void KTreeWidgetSearchLine::disconnectTreeWidget( QTreeWidget *treeWidget ) 00479 { 00480 disconnect( treeWidget, SIGNAL( destroyed( QObject* ) ), 00481 this, SLOT( _k_treeWidgetDeleted( QObject* ) ) ); 00482 00483 disconnect( treeWidget->model(), SIGNAL( rowsInserted( const QModelIndex&, int, int) ), 00484 this, SLOT( _k_rowsInserted( const QModelIndex&, int, int ) ) ); 00485 } 00486 00487 bool KTreeWidgetSearchLine::canChooseColumnsCheck() 00488 { 00489 // This is true if either of the following is true: 00490 00491 // there are no listviews connected 00492 if ( d->treeWidgets.isEmpty() ) 00493 return false; 00494 00495 const QTreeWidget *first = d->treeWidgets.first(); 00496 00497 const unsigned int numcols = first->columnCount(); 00498 // the listviews have only one column, 00499 if ( numcols < 2 ) 00500 return false; 00501 00502 QStringList headers; 00503 for ( unsigned int i = 0; i < numcols; ++i ) 00504 headers.append( first->headerItem()->text( i ) ); 00505 00506 QList<QTreeWidget *>::ConstIterator it = d->treeWidgets.constBegin(); 00507 for ( ++it /* skip the first one */; it != d->treeWidgets.constEnd(); ++it ) { 00508 // the listviews have different numbers of columns, 00509 if ( (unsigned int) (*it)->columnCount() != numcols ) 00510 return false; 00511 00512 // the listviews differ in column labels. 00513 QStringList::ConstIterator jt; 00514 unsigned int i; 00515 for ( i = 0, jt = headers.constBegin(); i < numcols; ++i, ++jt ) { 00516 Q_ASSERT( jt != headers.constEnd() ); 00517 00518 if ( (*it)->headerItem()->text( i ) != *jt ) 00519 return false; 00520 } 00521 } 00522 00523 return true; 00524 } 00525 00526 bool KTreeWidgetSearchLine::event(QEvent *event) { 00527 00528 if (event->type() == QEvent::KeyPress) { 00529 QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event); 00530 if(keyEvent->matches(QKeySequence::MoveToNextLine) || keyEvent->matches(QKeySequence::SelectNextLine) || 00531 keyEvent->matches(QKeySequence::MoveToPreviousLine) || keyEvent->matches(QKeySequence::SelectPreviousLine) || 00532 keyEvent->matches(QKeySequence::MoveToNextPage) || keyEvent->matches(QKeySequence::SelectNextPage) || 00533 keyEvent->matches(QKeySequence::MoveToPreviousPage) || keyEvent->matches(QKeySequence::SelectPreviousPage) || 00534 keyEvent->key() == Qt::Key_Enter || keyEvent->key() == Qt::Key_Return) 00535 { 00536 QTreeWidget *first = d->treeWidgets.first(); 00537 if(first) { 00538 QApplication::sendEvent(first, event); 00539 return true; 00540 } 00541 } 00542 } 00543 return KLineEdit::event(event); 00544 } 00545 00547 // protected slots 00549 00550 void KTreeWidgetSearchLine::Private::_k_queueSearch( const QString &_search ) 00551 { 00552 queuedSearches++; 00553 search = _search; 00554 00555 QTimer::singleShot( 200, q, SLOT( _k_activateSearch() ) ); 00556 } 00557 00558 void KTreeWidgetSearchLine::Private::_k_activateSearch() 00559 { 00560 --queuedSearches; 00561 00562 if ( queuedSearches == 0 ) 00563 q->updateSearch( search ); 00564 } 00565 00567 // KTreeWidgetSearchLineWidget 00569 00570 class KTreeWidgetSearchLineWidget::Private 00571 { 00572 public: 00573 Private() 00574 : treeWidget( 0 ), 00575 searchLine( 0 ) 00576 { 00577 } 00578 00579 QTreeWidget *treeWidget; 00580 KTreeWidgetSearchLine *searchLine; 00581 }; 00582 00583 KTreeWidgetSearchLineWidget::KTreeWidgetSearchLineWidget( QWidget *parent, QTreeWidget *treeWidget ) 00584 : QWidget( parent ), d( new Private ) 00585 { 00586 d->treeWidget = treeWidget; 00587 00588 // can't call createWidgets directly because it calls virtual functions 00589 // that might not work if called directly from here due to how inheritance works 00590 QMetaObject::invokeMethod(this, "createWidgets", Qt::QueuedConnection); 00591 } 00592 00593 KTreeWidgetSearchLineWidget::~KTreeWidgetSearchLineWidget() 00594 { 00595 delete d; 00596 } 00597 00598 KTreeWidgetSearchLine *KTreeWidgetSearchLineWidget::createSearchLine( QTreeWidget *treeWidget ) const 00599 { 00600 return new KTreeWidgetSearchLine( const_cast<KTreeWidgetSearchLineWidget*>(this), treeWidget ); 00601 } 00602 00603 void KTreeWidgetSearchLineWidget::createWidgets() 00604 { 00605 QLabel *label = new QLabel( i18n("S&earch:"), this ); 00606 00607 searchLine()->show(); 00608 00609 label->setBuddy( d->searchLine ); 00610 label->show(); 00611 00612 QHBoxLayout* layout = new QHBoxLayout( this ); 00613 layout->setMargin( 0 ); 00614 layout->addWidget( label ); 00615 layout->addWidget( d->searchLine ); 00616 setFocusProxy( searchLine() ); 00617 } 00618 00619 KTreeWidgetSearchLine *KTreeWidgetSearchLineWidget::searchLine() const 00620 { 00621 if ( !d->searchLine ) 00622 d->searchLine = createSearchLine( d->treeWidget ); 00623 00624 return d->searchLine; 00625 } 00626 00627 #include "ktreewidgetsearchline.moc"
KDE 4.6 API Reference