00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
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
00075
00076
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
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
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
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
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
00331
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
00391
00392 bool KTreeWidgetSearchLine::itemMatches( const QTreeWidgetItem *item, const QString &pattern ) const
00393 {
00394 if ( pattern.isEmpty() )
00395 return true;
00396
00397
00398
00399
00400 if ( !d->searchColumns.isEmpty() ) {
00401 QList<int>::ConstIterator it = d->searchColumns.begin();
00402 for ( ; it != d->searchColumns.end(); ++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
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
00490
00491
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
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 ; it != d->treeWidgets.constEnd(); ++it ) {
00508
00509 if ( (unsigned int) (*it)->columnCount() != numcols )
00510 return false;
00511
00512
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
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
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
00589
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->setSpacing( 5 );
00614 layout->setMargin( 0 );
00615 layout->addWidget( label );
00616 layout->addWidget( d->searchLine );
00617 setFocusProxy( searchLine() );
00618 }
00619
00620 KTreeWidgetSearchLine *KTreeWidgetSearchLineWidget::searchLine() const
00621 {
00622 if ( !d->searchLine )
00623 d->searchLine = createSearchLine( d->treeWidget );
00624
00625 return d->searchLine;
00626 }
00627
00628 #include "ktreewidgetsearchline.moc"