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