2 Copyright (c) 2003 Scott Wheeler <wheeler@kde.org>
3 Copyright (c) 2005 Rafal Rzepecki <divide@users.sourceforge.net>
4 Copyright (c) 2006 Hamish Rodda <rodda@kde.org>
5 Copyright 2007 Pino Toscano <pino@kde.org>
7 This library is free software; you can redistribute it and/or
8 modify it under the terms of the GNU Library General Public
9 License version 2 as published by the Free Software Foundation.
11 This library is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 Library General Public License for more details.
16 You should have received a copy of the GNU Library General Public License
17 along with this library; see the file COPYING.LIB. If not, write to
18 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
19 Boston, MA 02110-1301, USA.
22 #include "ktreeviewsearchline.h"
24 #include <QtCore/QList>
25 #include <QtCore/QTimer>
26 #include <QtGui/QApplication>
27 #include <QtGui/QContextMenuEvent>
28 #include <QtGui/QHBoxLayout>
29 #include <QtGui/QHeaderView>
30 #include <QtGui/QLabel>
31 #include <QtGui/QMenu>
32 #include <QtGui/QToolButton>
33 #include <QtGui/QTreeView>
36 #include <kiconloader.h>
40 class KTreeViewSearchLine::Private
43 Private( KTreeViewSearchLine
*_parent
)
45 caseSensitive( Qt::CaseInsensitive
),
46 activeSearch( false ),
47 keepParentsVisible( true ),
48 canChooseColumns( true ),
53 KTreeViewSearchLine
*parent
;
54 QList
<QTreeView
*> treeViews
;
55 Qt::CaseSensitivity caseSensitive
;
57 bool keepParentsVisible
;
58 bool canChooseColumns
;
61 QList
<int> searchColumns
;
63 void rowsInserted(const QModelIndex
& parent
, int start
, int end
) const;
64 void treeViewDeleted( QObject
*treeView
);
65 void slotColumnActivated(QAction
* action
);
66 void slotAllVisibleColumns();
69 void checkItemParentsNotVisible(QTreeView
*treeView
);
70 bool checkItemParentsVisible(QTreeView
*treeView
, const QModelIndex
&index
);
73 ////////////////////////////////////////////////////////////////////////////////
75 ////////////////////////////////////////////////////////////////////////////////
77 void KTreeViewSearchLine::Private::rowsInserted( const QModelIndex
& parentIndex
, int start
, int end
) const
79 QAbstractItemModel
* model
= qobject_cast
<QAbstractItemModel
*>( parent
->sender() );
83 QTreeView
* widget
= 0L;
84 foreach ( QTreeView
* tree
, treeViews
)
85 if ( tree
->model() == model
) {
93 for ( int i
= start
; i
<= end
; ++i
) {
94 widget
->setRowHidden( i
, parentIndex
, !parent
->itemMatches( parentIndex
, i
, parent
->text() ) );
98 void KTreeViewSearchLine::Private::treeViewDeleted( QObject
*object
)
100 treeViews
.removeAll( static_cast<QTreeView
*>( object
) );
101 parent
->setEnabled( treeViews
.isEmpty() );
104 void KTreeViewSearchLine::Private::slotColumnActivated( QAction
*action
)
110 int column
= action
->data().toInt( &ok
);
115 if ( action
->isChecked() ) {
116 if ( !searchColumns
.isEmpty() ) {
117 if ( !searchColumns
.contains( column
) )
118 searchColumns
.append( column
);
120 if ( searchColumns
.count() == treeViews
.first()->header()->count() - treeViews
.first()->header()->hiddenSectionCount() )
121 searchColumns
.clear();
124 searchColumns
.append( column
);
127 if ( searchColumns
.isEmpty() ) {
128 QHeaderView
* const header
= treeViews
.first()->header();
130 for ( int i
= 0; i
< header
->count(); i
++ ) {
131 if ( i
!= column
&& !header
->isSectionHidden( i
) )
132 searchColumns
.append( i
);
135 } else if ( searchColumns
.contains( column
) ) {
136 searchColumns
.removeAll( column
);
140 parent
->updateSearch();
143 void KTreeViewSearchLine::Private::slotAllVisibleColumns()
145 if ( searchColumns
.isEmpty() )
146 searchColumns
.append( 0 );
148 searchColumns
.clear();
150 parent
->updateSearch();
153 ////////////////////////////////////////////////////////////////////////////////
155 ////////////////////////////////////////////////////////////////////////////////
158 void KTreeViewSearchLine::Private::checkColumns()
160 canChooseColumns
= parent
->canChooseColumnsCheck();
163 void KTreeViewSearchLine::Private::checkItemParentsNotVisible( QTreeView
*treeView
)
169 QTreeWidgetItemIterator
it( treeWidget
);
171 for ( ; *it
; ++it
) {
172 QTreeWidgetItem
*item
= *it
;
173 item
->treeWidget()->setItemHidden( item
, !parent
->itemMatches( item
, search
) );
180 /** Check whether \p item, its siblings and their descendents should be shown. Show or hide the items as necessary.
182 * \p item The list view item to start showing / hiding items at. Typically, this is the first child of another item, or the
183 * the first child of the list view.
184 * \return \c true if an item which should be visible is found, \c false if all items found should be hidden. If this function
185 * returns true and \p highestHiddenParent was not 0, highestHiddenParent will have been shown.
187 bool KTreeViewSearchLine::Private::checkItemParentsVisible( QTreeView
*treeView
, const QModelIndex
&index
)
189 bool childMatch
= false;
190 const int rowcount
= treeView
->model()->rowCount( index
);
191 for ( int i
= 0; i
< rowcount
; ++i
)
192 childMatch
|= checkItemParentsVisible( treeView
, treeView
->model()->index( i
, 0, index
) );
194 // Should this item be shown? It should if any children should be, or if it matches.
195 const QModelIndex parentindex
= index
.parent();
196 if ( childMatch
|| parent
->itemMatches( parentindex
, index
.row(), search
) ) {
197 treeView
->setRowHidden( index
.row(), parentindex
, false );
201 treeView
->setRowHidden( index
.row(), parentindex
, true );
207 ////////////////////////////////////////////////////////////////////////////////
209 ////////////////////////////////////////////////////////////////////////////////
211 KTreeViewSearchLine::KTreeViewSearchLine( QWidget
*parent
, QTreeView
*treeView
)
212 : KLineEdit( parent
), d( new Private( this ) )
214 connect( this, SIGNAL( textChanged( const QString
& ) ),
215 this, SLOT( queueSearch( const QString
& ) ) );
217 setClearButtonShown( true );
218 setTreeView( treeView
);
225 KTreeViewSearchLine::KTreeViewSearchLine( QWidget
*parent
,
226 const QList
<QTreeView
*> &treeViews
)
227 : KLineEdit( parent
), d( new Private( this ) )
229 connect( this, SIGNAL( textChanged( const QString
& ) ),
230 this, SLOT( queueSearch( const QString
& ) ) );
232 setClearButtonShown( true );
233 setTreeViews( treeViews
);
236 KTreeViewSearchLine::~KTreeViewSearchLine()
241 Qt::CaseSensitivity
KTreeViewSearchLine::caseSensitivity() const
243 return d
->caseSensitive
;
246 QList
<int> KTreeViewSearchLine::searchColumns() const
248 if ( d
->canChooseColumns
)
249 return d
->searchColumns
;
254 bool KTreeViewSearchLine::keepParentsVisible() const
256 return d
->keepParentsVisible
;
259 QTreeView
*KTreeViewSearchLine::treeView() const
261 if ( d
->treeViews
.count() == 1 )
262 return d
->treeViews
.first();
267 QList
<QTreeView
*> KTreeViewSearchLine::treeViews() const
273 ////////////////////////////////////////////////////////////////////////////////
275 ////////////////////////////////////////////////////////////////////////////////
277 void KTreeViewSearchLine::addTreeView( QTreeView
*treeView
)
280 connectTreeView( treeView
);
282 d
->treeViews
.append( treeView
);
283 setEnabled( !d
->treeViews
.isEmpty() );
289 void KTreeViewSearchLine::removeTreeView( QTreeView
*treeView
)
292 int index
= d
->treeViews
.indexOf( treeView
);
295 d
->treeViews
.removeAt( index
);
298 disconnectTreeView( treeView
);
300 setEnabled( !d
->treeViews
.isEmpty() );
305 void KTreeViewSearchLine::updateSearch( const QString
&pattern
)
307 d
->search
= pattern
.isNull() ? text() : pattern
;
309 foreach ( QTreeView
* treeView
, d
->treeViews
)
310 updateSearch( treeView
);
313 void KTreeViewSearchLine::updateSearch( QTreeView
*treeView
)
315 if ( !treeView
|| !treeView
->model()->rowCount() )
319 // If there's a selected item that is visible, make sure that it's visible
320 // when the search changes too (assuming that it still matches).
322 QModelIndex currentIndex
= treeView
->currentIndex();
324 bool wasUpdateEnabled
= treeView
->updatesEnabled();
325 treeView
->setUpdatesEnabled( false );
326 if ( d
->keepParentsVisible
)
327 for ( int i
= 0; i
< treeView
->model()->rowCount(); ++i
)
328 d
->checkItemParentsVisible( treeView
, treeView
->rootIndex() );
330 d
->checkItemParentsNotVisible( treeView
);
331 treeView
->setUpdatesEnabled( wasUpdateEnabled
);
333 if ( currentIndex
.isValid() )
334 treeView
->scrollTo( currentIndex
);
337 void KTreeViewSearchLine::setCaseSensitivity( Qt::CaseSensitivity caseSensitive
)
339 if ( d
->caseSensitive
!= caseSensitive
) {
340 d
->caseSensitive
= caseSensitive
;
345 void KTreeViewSearchLine::setKeepParentsVisible( bool visible
)
347 if ( d
->keepParentsVisible
!= visible
) {
348 d
->keepParentsVisible
= visible
;
353 void KTreeViewSearchLine::setSearchColumns( const QList
<int> &columns
)
355 if ( d
->canChooseColumns
)
356 d
->searchColumns
= columns
;
359 void KTreeViewSearchLine::setTreeView( QTreeView
*treeView
)
361 setTreeViews( QList
<QTreeView
*>() );
362 addTreeView( treeView
);
365 void KTreeViewSearchLine::setTreeViews( const QList
<QTreeView
*> &treeViews
)
367 foreach ( QTreeView
* treeView
, d
->treeViews
)
368 disconnectTreeView( treeView
);
370 d
->treeViews
= treeViews
;
372 foreach ( QTreeView
* treeView
, d
->treeViews
)
373 connectTreeView( treeView
);
377 setEnabled( !d
->treeViews
.isEmpty() );
380 ////////////////////////////////////////////////////////////////////////////////
382 ////////////////////////////////////////////////////////////////////////////////
384 bool KTreeViewSearchLine::itemMatches( const QModelIndex
&index
, int row
, const QString
&pattern
) const
386 if ( pattern
.isEmpty() )
389 if ( !index
.isValid() )
392 // If the search column list is populated, search just the columns
393 // specifified. If it is empty default to searching all of the columns.
395 const int columncount
= index
.model()->columnCount( index
);
396 if ( !d
->searchColumns
.isEmpty() ) {
397 QList
<int>::ConstIterator it
= d
->searchColumns
.constBegin();
398 for ( ; it
!= d
->searchColumns
.constEnd(); ++it
) {
399 if ( *it
< columncount
&&
400 index
.child( row
, *it
).data( Qt::DisplayRole
).toString().indexOf( pattern
, 0, d
->caseSensitive
) >= 0 )
404 for ( int i
= 0; i
< columncount
; ++i
) {
405 if ( index
.child( row
, i
).data( Qt::DisplayRole
).toString().indexOf( pattern
, 0, d
->caseSensitive
) >= 0 )
413 void KTreeViewSearchLine::contextMenuEvent( QContextMenuEvent
*event
)
415 QMenu
*popup
= KLineEdit::createStandardContextMenu();
417 if ( d
->canChooseColumns
) {
418 popup
->addSeparator();
419 QMenu
*subMenu
= popup
->addMenu( i18n("Search Columns") );
421 QAction
* allVisibleColumnsAction
= subMenu
->addAction( i18n("All Visible Columns"),
422 this, SLOT( slotAllVisibleColumns() ) );
423 allVisibleColumnsAction
->setCheckable( true );
424 allVisibleColumnsAction
->setChecked( !d
->searchColumns
.count() );
425 subMenu
->addSeparator();
427 bool allColumnsAreSearchColumns
= true;
429 QActionGroup
* group
= new QActionGroup( popup
);
430 group
->setExclusive( false );
431 connect( group
, SIGNAL( triggered( QAction
* ) ), SLOT( slotColumnActivated( QAction
* ) ) );
433 QHeaderView
* const header
= d
->treeViews
.first()->header();
434 for ( int j
= 0; j
< header
->count(); j
++ ) {
435 int i
= header
->logicalIndex( j
);
437 if ( header
->isSectionHidden( i
) )
440 QString columnText
= header
->model()->headerData( i
, Qt::Horizontal
, Qt::DisplayRole
).toString();
441 QAction
* columnAction
= subMenu
->addAction( qvariant_cast
<QIcon
>( header
->model()->headerData( i
, Qt::Horizontal
, Qt::DecorationRole
) ), columnText
);
442 columnAction
->setCheckable( true );
443 columnAction
->setChecked( d
->searchColumns
.isEmpty() || d
->searchColumns
.contains( i
) );
444 columnAction
->setData( i
);
445 columnAction
->setActionGroup( group
);
447 if ( d
->searchColumns
.isEmpty() || d
->searchColumns
.indexOf( i
) != -1 )
448 columnAction
->setChecked( true );
450 allColumnsAreSearchColumns
= false;
453 allVisibleColumnsAction
->setChecked( allColumnsAreSearchColumns
);
455 // searchColumnsMenuActivated() relies on one possible "all" representation
456 if ( allColumnsAreSearchColumns
&& !d
->searchColumns
.isEmpty() )
457 d
->searchColumns
.clear();
460 popup
->exec( event
->globalPos() );
464 void KTreeViewSearchLine::connectTreeView( QTreeView
*treeView
)
466 connect( treeView
, SIGNAL( destroyed( QObject
* ) ),
467 this, SLOT( treeViewDeleted( QObject
* ) ) );
469 connect( treeView
->model(), SIGNAL( rowsInserted( const QModelIndex
&, int, int) ),
470 this, SLOT( rowsInserted( const QModelIndex
&, int, int ) ) );
473 void KTreeViewSearchLine::disconnectTreeView( QTreeView
*treeView
)
475 disconnect( treeView
, SIGNAL( destroyed( QObject
* ) ),
476 this, SLOT( treeViewDeleted( QObject
* ) ) );
478 disconnect( treeView
->model(), SIGNAL( rowsInserted( const QModelIndex
&, int, int) ),
479 this, SLOT( rowsInserted( const QModelIndex
&, int, int ) ) );
482 bool KTreeViewSearchLine::canChooseColumnsCheck()
484 // This is true if either of the following is true:
486 // there are no listviews connected
487 if ( d
->treeViews
.isEmpty() )
490 const QTreeView
*first
= d
->treeViews
.first();
492 const int numcols
= first
->model()->columnCount();
493 // the listviews have only one column,
498 for ( int i
= 0; i
< numcols
; ++i
)
499 headers
.append( first
->header()->model()->headerData( i
, Qt::Horizontal
, Qt::DisplayRole
).toString() );
501 QList
<QTreeView
*>::ConstIterator it
= d
->treeViews
.constBegin();
502 for ( ++it
/* skip the first one */; it
!= d
->treeViews
.constEnd(); ++it
) {
503 // the listviews have different numbers of columns,
504 if ( (*it
)->model()->columnCount() != numcols
)
507 // the listviews differ in column labels.
508 QStringList::ConstIterator jt
;
510 for ( i
= 0, jt
= headers
.constBegin(); i
< numcols
; ++i
, ++jt
) {
511 Q_ASSERT( jt
!= headers
.constEnd() );
513 if ( (*it
)->header()->model()->headerData( i
, Qt::Horizontal
, Qt::DisplayRole
).toString() != *jt
)
521 ////////////////////////////////////////////////////////////////////////////////
523 ////////////////////////////////////////////////////////////////////////////////
525 void KTreeViewSearchLine::queueSearch( const QString
&search
)
530 QTimer::singleShot( 200, this, SLOT( activateSearch() ) );
533 void KTreeViewSearchLine::activateSearch()
535 --(d
->queuedSearches
);
537 if ( d
->queuedSearches
== 0 )
538 updateSearch( d
->search
);
541 ////////////////////////////////////////////////////////////////////////////////
542 // KTreeViewSearchLineWidget
543 ////////////////////////////////////////////////////////////////////////////////
545 class KTreeViewSearchLineWidget::Private
555 KTreeViewSearchLine
*searchLine
;
558 KTreeViewSearchLineWidget::KTreeViewSearchLineWidget( QWidget
*parent
, QTreeView
*treeView
)
559 : QWidget( parent
), d( new Private
)
561 d
->treeView
= treeView
;
563 QTimer::singleShot( 0, this, SLOT( createWidgets() ) );
566 KTreeViewSearchLineWidget::~KTreeViewSearchLineWidget()
571 KTreeViewSearchLine
*KTreeViewSearchLineWidget::createSearchLine( QTreeView
*treeView
) const
573 return new KTreeViewSearchLine( const_cast<KTreeViewSearchLineWidget
*>(this), treeView
);
576 void KTreeViewSearchLineWidget::createWidgets()
578 QLabel
*label
= new QLabel( i18n("S&earch:"), this );
579 label
->setObjectName( QLatin1String("kde toolbar widget") );
581 searchLine()->show();
583 label
->setBuddy( d
->searchLine
);
586 QHBoxLayout
* layout
= new QHBoxLayout( this );
587 layout
->setSpacing( 5 );
588 layout
->setMargin( 0 );
589 layout
->addWidget( label
);
590 layout
->addWidget( d
->searchLine
);
593 KTreeViewSearchLine
*KTreeViewSearchLineWidget::searchLine() const
595 if ( !d
->searchLine
)
596 d
->searchLine
= createSearchLine( d
->treeView
);
598 return d
->searchLine
;
601 #include "ktreeviewsearchline.moc"