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
)
167 QTreeWidgetItemIterator
it( treeWidget
);
169 for ( ; *it
; ++it
) {
170 QTreeWidgetItem
*item
= *it
;
171 item
->treeWidget()->setItemHidden( item
, !parent
->itemMatches( item
, search
) );
178 /** Check whether \p item, its siblings and their descendents should be shown. Show or hide the items as necessary.
180 * \p item The list view item to start showing / hiding items at. Typically, this is the first child of another item, or the
181 * the first child of the list view.
182 * \return \c true if an item which should be visible is found, \c false if all items found should be hidden. If this function
183 * returns true and \p highestHiddenParent was not 0, highestHiddenParent will have been shown.
185 bool KTreeViewSearchLine::Private::checkItemParentsVisible( QTreeView
*treeView
, const QModelIndex
&index
)
187 bool childMatch
= false;
188 const int rowcount
= treeView
->model()->rowCount( index
);
189 for ( int i
= 0; i
< rowcount
; ++i
)
190 childMatch
|= checkItemParentsVisible( treeView
, treeView
->model()->index( i
, 0, index
) );
192 // Should this item be shown? It should if any children should be, or if it matches.
193 const QModelIndex parentindex
= index
.parent();
194 if ( childMatch
|| parent
->itemMatches( parentindex
, index
.row(), search
) ) {
195 treeView
->setRowHidden( index
.row(), parentindex
, false );
199 treeView
->setRowHidden( index
.row(), parentindex
, true );
205 ////////////////////////////////////////////////////////////////////////////////
207 ////////////////////////////////////////////////////////////////////////////////
209 KTreeViewSearchLine::KTreeViewSearchLine( QWidget
*parent
, QTreeView
*treeView
)
210 : KLineEdit( parent
), d( new Private( this ) )
212 connect( this, SIGNAL( textChanged( const QString
& ) ),
213 this, SLOT( queueSearch( const QString
& ) ) );
215 setClearButtonShown( true );
216 setTreeView( treeView
);
223 KTreeViewSearchLine::KTreeViewSearchLine( QWidget
*parent
,
224 const QList
<QTreeView
*> &treeViews
)
225 : KLineEdit( parent
), d( new Private( this ) )
227 connect( this, SIGNAL( textChanged( const QString
& ) ),
228 this, SLOT( queueSearch( const QString
& ) ) );
230 setClearButtonShown( true );
231 setTreeViews( treeViews
);
234 KTreeViewSearchLine::~KTreeViewSearchLine()
239 Qt::CaseSensitivity
KTreeViewSearchLine::caseSensitivity() const
241 return d
->caseSensitive
;
244 QList
<int> KTreeViewSearchLine::searchColumns() const
246 if ( d
->canChooseColumns
)
247 return d
->searchColumns
;
252 bool KTreeViewSearchLine::keepParentsVisible() const
254 return d
->keepParentsVisible
;
257 QTreeView
*KTreeViewSearchLine::treeView() const
259 if ( d
->treeViews
.count() == 1 )
260 return d
->treeViews
.first();
265 QList
<QTreeView
*> KTreeViewSearchLine::treeViews() const
271 ////////////////////////////////////////////////////////////////////////////////
273 ////////////////////////////////////////////////////////////////////////////////
275 void KTreeViewSearchLine::addTreeView( QTreeView
*treeView
)
278 connectTreeView( treeView
);
280 d
->treeViews
.append( treeView
);
281 setEnabled( !d
->treeViews
.isEmpty() );
287 void KTreeViewSearchLine::removeTreeView( QTreeView
*treeView
)
290 int index
= d
->treeViews
.indexOf( treeView
);
293 d
->treeViews
.removeAt( index
);
296 disconnectTreeView( treeView
);
298 setEnabled( !d
->treeViews
.isEmpty() );
303 void KTreeViewSearchLine::updateSearch( const QString
&pattern
)
305 d
->search
= pattern
.isNull() ? text() : pattern
;
307 foreach ( QTreeView
* treeView
, d
->treeViews
)
308 updateSearch( treeView
);
311 void KTreeViewSearchLine::updateSearch( QTreeView
*treeView
)
313 if ( !treeView
|| !treeView
->model()->rowCount() )
317 // If there's a selected item that is visible, make sure that it's visible
318 // when the search changes too (assuming that it still matches).
320 QModelIndex currentIndex
= treeView
->currentIndex();
322 bool wasUpdateEnabled
= treeView
->updatesEnabled();
323 treeView
->setUpdatesEnabled( false );
324 if ( d
->keepParentsVisible
)
325 for ( int i
= 0; i
< treeView
->model()->rowCount(); ++i
)
326 d
->checkItemParentsVisible( treeView
, treeView
->rootIndex() );
328 d
->checkItemParentsNotVisible( treeView
);
329 treeView
->setUpdatesEnabled( wasUpdateEnabled
);
331 if ( currentIndex
.isValid() )
332 treeView
->scrollTo( currentIndex
);
335 void KTreeViewSearchLine::setCaseSensitivity( Qt::CaseSensitivity caseSensitive
)
337 if ( d
->caseSensitive
!= caseSensitive
) {
338 d
->caseSensitive
= caseSensitive
;
343 void KTreeViewSearchLine::setKeepParentsVisible( bool visible
)
345 if ( d
->keepParentsVisible
!= visible
) {
346 d
->keepParentsVisible
= visible
;
351 void KTreeViewSearchLine::setSearchColumns( const QList
<int> &columns
)
353 if ( d
->canChooseColumns
)
354 d
->searchColumns
= columns
;
357 void KTreeViewSearchLine::setTreeView( QTreeView
*treeView
)
359 setTreeViews( QList
<QTreeView
*>() );
360 addTreeView( treeView
);
363 void KTreeViewSearchLine::setTreeViews( const QList
<QTreeView
*> &treeViews
)
365 foreach ( QTreeView
* treeView
, d
->treeViews
)
366 disconnectTreeView( treeView
);
368 d
->treeViews
= treeViews
;
370 foreach ( QTreeView
* treeView
, d
->treeViews
)
371 connectTreeView( treeView
);
375 setEnabled( !d
->treeViews
.isEmpty() );
378 ////////////////////////////////////////////////////////////////////////////////
380 ////////////////////////////////////////////////////////////////////////////////
382 bool KTreeViewSearchLine::itemMatches( const QModelIndex
&index
, int row
, const QString
&pattern
) const
384 if ( pattern
.isEmpty() )
387 if ( !index
.isValid() )
390 // If the search column list is populated, search just the columns
391 // specifified. If it is empty default to searching all of the columns.
393 const int columncount
= index
.model()->columnCount( index
);
394 if ( !d
->searchColumns
.isEmpty() ) {
395 QList
<int>::ConstIterator it
= d
->searchColumns
.constBegin();
396 for ( ; it
!= d
->searchColumns
.constEnd(); ++it
) {
397 if ( *it
< columncount
&&
398 index
.child( row
, *it
).data( Qt::DisplayRole
).toString().indexOf( pattern
, 0, d
->caseSensitive
) >= 0 )
402 for ( int i
= 0; i
< columncount
; ++i
) {
403 if ( index
.child( row
, i
).data( Qt::DisplayRole
).toString().indexOf( pattern
, 0, d
->caseSensitive
) >= 0 )
411 void KTreeViewSearchLine::contextMenuEvent( QContextMenuEvent
*event
)
413 QMenu
*popup
= KLineEdit::createStandardContextMenu();
415 if ( d
->canChooseColumns
) {
416 popup
->addSeparator();
417 QMenu
*subMenu
= popup
->addMenu( i18n("Search Columns") );
419 QAction
* allVisibleColumnsAction
= subMenu
->addAction( i18n("All Visible Columns"),
420 this, SLOT( slotAllVisibleColumns() ) );
421 allVisibleColumnsAction
->setCheckable( true );
422 allVisibleColumnsAction
->setChecked( !d
->searchColumns
.count() );
423 subMenu
->addSeparator();
425 bool allColumnsAreSearchColumns
= true;
427 QActionGroup
* group
= new QActionGroup( popup
);
428 group
->setExclusive( false );
429 connect( group
, SIGNAL( triggered( QAction
* ) ), SLOT( slotColumnActivated( QAction
* ) ) );
431 QHeaderView
* const header
= d
->treeViews
.first()->header();
432 for ( int j
= 0; j
< header
->count(); j
++ ) {
433 int i
= header
->logicalIndex( j
);
435 if ( header
->isSectionHidden( i
) )
438 QString columnText
= header
->model()->headerData( i
, Qt::Horizontal
, Qt::DisplayRole
).toString();
439 QAction
* columnAction
= subMenu
->addAction( qvariant_cast
<QIcon
>( header
->model()->headerData( i
, Qt::Horizontal
, Qt::DecorationRole
) ), columnText
);
440 columnAction
->setCheckable( true );
441 columnAction
->setChecked( d
->searchColumns
.isEmpty() || d
->searchColumns
.contains( i
) );
442 columnAction
->setData( i
);
443 columnAction
->setActionGroup( group
);
445 if ( d
->searchColumns
.isEmpty() || d
->searchColumns
.indexOf( i
) != -1 )
446 columnAction
->setChecked( true );
448 allColumnsAreSearchColumns
= false;
451 allVisibleColumnsAction
->setChecked( allColumnsAreSearchColumns
);
453 // searchColumnsMenuActivated() relies on one possible "all" representation
454 if ( allColumnsAreSearchColumns
&& !d
->searchColumns
.isEmpty() )
455 d
->searchColumns
.clear();
458 popup
->exec( event
->globalPos() );
462 void KTreeViewSearchLine::connectTreeView( QTreeView
*treeView
)
464 connect( treeView
, SIGNAL( destroyed( QObject
* ) ),
465 this, SLOT( treeViewDeleted( QObject
* ) ) );
467 connect( treeView
->model(), SIGNAL( rowsInserted( const QModelIndex
&, int, int) ),
468 this, SLOT( rowsInserted( const QModelIndex
&, int, int ) ) );
471 void KTreeViewSearchLine::disconnectTreeView( QTreeView
*treeView
)
473 disconnect( treeView
, SIGNAL( destroyed( QObject
* ) ),
474 this, SLOT( treeViewDeleted( QObject
* ) ) );
476 disconnect( treeView
->model(), SIGNAL( rowsInserted( const QModelIndex
&, int, int) ),
477 this, SLOT( rowsInserted( const QModelIndex
&, int, int ) ) );
480 bool KTreeViewSearchLine::canChooseColumnsCheck()
482 // This is true if either of the following is true:
484 // there are no listviews connected
485 if ( d
->treeViews
.isEmpty() )
488 const QTreeView
*first
= d
->treeViews
.first();
490 const int numcols
= first
->model()->columnCount();
491 // the listviews have only one column,
496 for ( int i
= 0; i
< numcols
; ++i
)
497 headers
.append( first
->header()->model()->headerData( i
, Qt::Horizontal
, Qt::DisplayRole
).toString() );
499 QList
<QTreeView
*>::ConstIterator it
= d
->treeViews
.constBegin();
500 for ( ++it
/* skip the first one */; it
!= d
->treeViews
.constEnd(); ++it
) {
501 // the listviews have different numbers of columns,
502 if ( (*it
)->model()->columnCount() != numcols
)
505 // the listviews differ in column labels.
506 QStringList::ConstIterator jt
;
508 for ( i
= 0, jt
= headers
.constBegin(); i
< numcols
; ++i
, ++jt
) {
509 Q_ASSERT( jt
!= headers
.constEnd() );
511 if ( (*it
)->header()->model()->headerData( i
, Qt::Horizontal
, Qt::DisplayRole
).toString() != *jt
)
519 ////////////////////////////////////////////////////////////////////////////////
521 ////////////////////////////////////////////////////////////////////////////////
523 void KTreeViewSearchLine::queueSearch( const QString
&search
)
528 QTimer::singleShot( 200, this, SLOT( activateSearch() ) );
531 void KTreeViewSearchLine::activateSearch()
533 --(d
->queuedSearches
);
535 if ( d
->queuedSearches
== 0 )
536 updateSearch( d
->search
);
539 ////////////////////////////////////////////////////////////////////////////////
540 // KTreeViewSearchLineWidget
541 ////////////////////////////////////////////////////////////////////////////////
543 class KTreeViewSearchLineWidget::Private
553 KTreeViewSearchLine
*searchLine
;
556 KTreeViewSearchLineWidget::KTreeViewSearchLineWidget( QWidget
*parent
, QTreeView
*treeView
)
557 : QWidget( parent
), d( new Private
)
559 d
->treeView
= treeView
;
561 QTimer::singleShot( 0, this, SLOT( createWidgets() ) );
564 KTreeViewSearchLineWidget::~KTreeViewSearchLineWidget()
569 KTreeViewSearchLine
*KTreeViewSearchLineWidget::createSearchLine( QTreeView
*treeView
) const
571 return new KTreeViewSearchLine( const_cast<KTreeViewSearchLineWidget
*>(this), treeView
);
574 void KTreeViewSearchLineWidget::createWidgets()
576 QLabel
*label
= new QLabel( i18n("S&earch:"), this );
577 label
->setObjectName( QLatin1String("kde toolbar widget") );
579 searchLine()->show();
581 label
->setBuddy( d
->searchLine
);
584 QHBoxLayout
* layout
= new QHBoxLayout( this );
585 layout
->setSpacing( 5 );
586 layout
->setMargin( 0 );
587 layout
->addWidget( label
);
588 layout
->addWidget( d
->searchLine
);
591 KTreeViewSearchLine
*KTreeViewSearchLineWidget::searchLine() const
593 if ( !d
->searchLine
)
594 d
->searchLine
= createSearchLine( d
->treeView
);
596 return d
->searchLine
;
599 #include "ktreeviewsearchline.moc"