kdelibs/kdeui/itemviews/ktreewidgetsearchline.cpp

623 lines
18 KiB
C++

/* This file is part of the KDE libraries
Copyright (c) 2003 Scott Wheeler <wheeler@kde.org>
Copyright (c) 2005 Rafal Rzepecki <divide@users.sourceforge.net>
Copyright (c) 2006 Hamish Rodda <rodda@kde.org>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License version 2 as published by the Free Software Foundation.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#include "ktreewidgetsearchline.h"
#include <QtCore/QList>
#include <QtCore/QTimer>
#include <QtGui/QApplication>
#include <QtGui/qevent.h>
#include <QtGui/QHBoxLayout>
#include <QtGui/QHeaderView>
#include <QtGui/QLabel>
#include <QtGui/QMenu>
#include <QtGui/QToolButton>
#include <QtGui/QTreeWidget>
#include <kdebug.h>
#include <klocale.h>
class KTreeWidgetSearchLine::Private
{
public:
Private( KTreeWidgetSearchLine *_q )
: q( _q ),
caseSensitive( Qt::CaseInsensitive ),
keepParentsVisible( true ),
canChooseColumns( true ),
queuedSearches( 0 )
{
}
KTreeWidgetSearchLine *q;
QList<QTreeWidget *> treeWidgets;
Qt::CaseSensitivity caseSensitive;
bool keepParentsVisible;
bool canChooseColumns;
QString search;
int queuedSearches;
QList<int> searchColumns;
void _k_rowsInserted(const QModelIndex & parent, int start, int end) const;
void _k_treeWidgetDeleted( QObject *treeWidget );
void _k_slotColumnActivated(QAction* action);
void _k_slotAllVisibleColumns();
void _k_queueSearch(const QString&);
void _k_activateSearch();
void checkColumns();
void checkItemParentsNotVisible(QTreeWidget *treeWidget);
bool checkItemParentsVisible(QTreeWidgetItem* item);
};
////////////////////////////////////////////////////////////////////////////////
// private slots
////////////////////////////////////////////////////////////////////////////////
// Hack to make a protected method public
class QTreeWidgetWorkaround : public QTreeWidget
{
public:
QTreeWidgetItem *itemFromIndex( const QModelIndex &index ) const
{
return QTreeWidget::itemFromIndex( index );
}
};
void KTreeWidgetSearchLine::Private::_k_rowsInserted( const QModelIndex & parentIndex, int start, int end ) const
{
QAbstractItemModel* model = qobject_cast<QAbstractItemModel*>( q->sender() );
if ( !model )
return;
QTreeWidget* widget = 0L;
foreach ( QTreeWidget* tree, treeWidgets )
if ( tree->model() == model ) {
widget = tree;
break;
}
if ( !widget )
return;
QTreeWidgetWorkaround* widgetW = static_cast<QTreeWidgetWorkaround *>(widget);
for (int i = start; i <= end; ++i) {
if (QTreeWidgetItem *item = widgetW->itemFromIndex(model->index(i, 0, parentIndex))) {
bool newHidden = !q->itemMatches(item, q->text());
if (item->isHidden() != newHidden) {
item->setHidden(newHidden);
emit q->hiddenChanged(item, newHidden);
}
}
}
}
void KTreeWidgetSearchLine::Private::_k_treeWidgetDeleted( QObject *object )
{
treeWidgets.removeAll( static_cast<QTreeWidget *>( object ) );
q->setEnabled( treeWidgets.isEmpty() );
}
void KTreeWidgetSearchLine::Private::_k_slotColumnActivated( QAction *action )
{
if ( !action )
return;
bool ok;
int column = action->data().toInt( &ok );
if ( !ok )
return;
if ( action->isChecked() ) {
if ( !searchColumns.isEmpty() ) {
if ( !searchColumns.contains( column ) )
searchColumns.append( column );
if ( searchColumns.count() == treeWidgets.first()->header()->count() - treeWidgets.first()->header()->hiddenSectionCount() )
searchColumns.clear();
} else {
searchColumns.append( column );
}
} else {
if ( searchColumns.isEmpty() ) {
QHeaderView* const header = treeWidgets.first()->header();
for ( int i = 0; i < header->count(); i++ ) {
if ( i != column && !header->isSectionHidden( i ) )
searchColumns.append( i );
}
} else if ( searchColumns.contains( column ) ) {
searchColumns.removeAll( column );
}
}
q->updateSearch();
}
void KTreeWidgetSearchLine::Private::_k_slotAllVisibleColumns()
{
if ( searchColumns.isEmpty() )
searchColumns.append( 0 );
else
searchColumns.clear();
q->updateSearch();
}
////////////////////////////////////////////////////////////////////////////////
// private methods
////////////////////////////////////////////////////////////////////////////////
void KTreeWidgetSearchLine::Private::checkColumns()
{
canChooseColumns = q->canChooseColumnsCheck();
}
void KTreeWidgetSearchLine::Private::checkItemParentsNotVisible(QTreeWidget *treeWidget)
{
for (QTreeWidgetItemIterator it(treeWidget); *it; ++it) {
QTreeWidgetItem *item = *it;
bool newHidden = !q->itemMatches(item, search);
if (item->isHidden() != newHidden) {
item->setHidden(newHidden);
emit q->hiddenChanged(item, newHidden);
}
}
}
/** Check whether \p item, its siblings and their descendants should be shown. Show or hide the items as necessary.
*
* \p item The list view item to start showing / hiding items at. Typically, this is the first child of another item, or
* the first child of the list view.
* \return \c true if an item which should be visible is found, \c false if all items found should be hidden. If this function
* returns true and \p highestHiddenParent was not 0, highestHiddenParent will have been shown.
*/
bool KTreeWidgetSearchLine::Private::checkItemParentsVisible(QTreeWidgetItem *item)
{
bool childMatch = false;
for (int i = 0; i < item->childCount(); ++i) {
childMatch |= checkItemParentsVisible(item->child(i));
}
// Should this item be shown? It should if any children should be, or if it matches.
bool newHidden = !childMatch && !q->itemMatches(item, search);
if (item->isHidden() != newHidden) {
item->setHidden(newHidden);
emit q->hiddenChanged(item, newHidden);
}
return !newHidden;
}
////////////////////////////////////////////////////////////////////////////////
// public methods
////////////////////////////////////////////////////////////////////////////////
KTreeWidgetSearchLine::KTreeWidgetSearchLine( QWidget *q, QTreeWidget *treeWidget )
: KLineEdit( q ), d( new Private( this ) )
{
connect( this, SIGNAL(textChanged(QString)),
this, SLOT(_k_queueSearch(QString)) );
setClearButtonShown( true );
setTreeWidget( treeWidget );
if ( !treeWidget ) {
setEnabled( false );
}
}
KTreeWidgetSearchLine::KTreeWidgetSearchLine( QWidget *q,
const QList<QTreeWidget *> &treeWidgets )
: KLineEdit( q ), d( new Private( this ) )
{
connect( this, SIGNAL(textChanged(QString)),
this, SLOT(_k_queueSearch(QString)) );
setClearButtonShown( true );
setTreeWidgets( treeWidgets );
}
KTreeWidgetSearchLine::~KTreeWidgetSearchLine()
{
delete d;
}
Qt::CaseSensitivity KTreeWidgetSearchLine::caseSensitivity() const
{
return d->caseSensitive;
}
QList<int> KTreeWidgetSearchLine::searchColumns() const
{
if ( d->canChooseColumns )
return d->searchColumns;
else
return QList<int>();
}
bool KTreeWidgetSearchLine::keepParentsVisible() const
{
return d->keepParentsVisible;
}
QTreeWidget *KTreeWidgetSearchLine::treeWidget() const
{
if ( d->treeWidgets.count() == 1 )
return d->treeWidgets.first();
else
return 0;
}
QList<QTreeWidget *> KTreeWidgetSearchLine::treeWidgets() const
{
return d->treeWidgets;
}
////////////////////////////////////////////////////////////////////////////////
// public slots
////////////////////////////////////////////////////////////////////////////////
void KTreeWidgetSearchLine::addTreeWidget( QTreeWidget *treeWidget )
{
if ( treeWidget ) {
connectTreeWidget( treeWidget );
d->treeWidgets.append( treeWidget );
setEnabled( !d->treeWidgets.isEmpty() );
d->checkColumns();
}
}
void KTreeWidgetSearchLine::removeTreeWidget( QTreeWidget *treeWidget )
{
if ( treeWidget ) {
int index = d->treeWidgets.indexOf( treeWidget );
if ( index != -1 ) {
d->treeWidgets.removeAt( index );
d->checkColumns();
disconnectTreeWidget( treeWidget );
setEnabled( !d->treeWidgets.isEmpty() );
}
}
}
void KTreeWidgetSearchLine::updateSearch( const QString &pattern )
{
d->search = pattern.isNull() ? text() : pattern;
foreach ( QTreeWidget* treeWidget, d->treeWidgets )
updateSearch( treeWidget );
}
void KTreeWidgetSearchLine::updateSearch( QTreeWidget *treeWidget )
{
if ( !treeWidget || !treeWidget->topLevelItemCount() )
return;
// If there's a selected item that is visible, make sure that it's visible
// when the search changes too (assuming that it still matches).
QTreeWidgetItem *currentItem = treeWidget->currentItem();
if ( d->keepParentsVisible )
for ( int i = 0; i < treeWidget->topLevelItemCount(); ++i )
d->checkItemParentsVisible( treeWidget->topLevelItem( i ) );
else
d->checkItemParentsNotVisible( treeWidget );
if ( currentItem )
treeWidget->scrollToItem( currentItem );
}
void KTreeWidgetSearchLine::setCaseSensitivity( Qt::CaseSensitivity caseSensitive )
{
if ( d->caseSensitive != caseSensitive ) {
d->caseSensitive = caseSensitive;
updateSearch();
}
}
void KTreeWidgetSearchLine::setKeepParentsVisible( bool visible )
{
if ( d->keepParentsVisible != visible ) {
d->keepParentsVisible = visible;
updateSearch();
}
}
void KTreeWidgetSearchLine::setSearchColumns( const QList<int> &columns )
{
if ( d->canChooseColumns )
d->searchColumns = columns;
}
void KTreeWidgetSearchLine::setTreeWidget( QTreeWidget *treeWidget )
{
setTreeWidgets( QList<QTreeWidget *>() );
addTreeWidget( treeWidget );
}
void KTreeWidgetSearchLine::setTreeWidgets( const QList<QTreeWidget *> &treeWidgets )
{
foreach ( QTreeWidget* treeWidget, d->treeWidgets )
disconnectTreeWidget( treeWidget );
d->treeWidgets = treeWidgets;
foreach ( QTreeWidget* treeWidget, d->treeWidgets )
connectTreeWidget( treeWidget );
d->checkColumns();
setEnabled( !d->treeWidgets.isEmpty() );
}
////////////////////////////////////////////////////////////////////////////////
// protected members
////////////////////////////////////////////////////////////////////////////////
bool KTreeWidgetSearchLine::itemMatches( const QTreeWidgetItem *item, const QString &pattern ) const
{
if ( pattern.isEmpty() )
return true;
// If the search column list is populated, search just the columns
// specified. If it is empty default to searching all of the columns.
if ( !d->searchColumns.isEmpty() ) {
QList<int>::ConstIterator it = d->searchColumns.constBegin();
for ( ; it != d->searchColumns.constEnd(); ++it ) {
if ( *it < item->treeWidget()->columnCount() &&
item->text( *it ).indexOf( pattern, 0, d->caseSensitive ) >= 0 )
return true;
}
} else {
for ( int i = 0; i < item->treeWidget()->columnCount(); i++) {
if ( item->treeWidget()->columnWidth(i) > 0 &&
item->text( i ).indexOf( pattern, 0, d->caseSensitive ) >= 0 )
return true;
}
}
return false;
}
void KTreeWidgetSearchLine::contextMenuEvent( QContextMenuEvent *event )
{
QMenu *popup = KLineEdit::createStandardContextMenu();
if ( d->canChooseColumns ) {
popup->addSeparator();
QMenu *subMenu = popup->addMenu( i18n("Search Columns") );
QAction* allVisibleColumnsAction = subMenu->addAction( i18n("All Visible Columns"),
this, SLOT(_k_slotAllVisibleColumns()) );
allVisibleColumnsAction->setCheckable( true );
allVisibleColumnsAction->setChecked( !d->searchColumns.count() );
subMenu->addSeparator();
bool allColumnsAreSearchColumns = true;
QActionGroup* group = new QActionGroup( popup );
group->setExclusive( false );
connect( group, SIGNAL(triggered(QAction*)), SLOT(_k_slotColumnActivated(QAction*)) );
QHeaderView* const header = d->treeWidgets.first()->header();
for ( int j = 0; j < header->count(); j++ ) {
int i = header->logicalIndex( j );
if ( header->isSectionHidden( i ) )
continue;
QString columnText = d->treeWidgets.first()->headerItem()->text( i );
QAction* columnAction = subMenu->addAction( d->treeWidgets.first()->headerItem()->icon( i ), columnText );
columnAction->setCheckable( true );
columnAction->setChecked( d->searchColumns.isEmpty() || d->searchColumns.contains( i ) );
columnAction->setData( i );
columnAction->setActionGroup( group );
if ( d->searchColumns.isEmpty() || d->searchColumns.indexOf( i ) != -1 )
columnAction->setChecked( true );
else
allColumnsAreSearchColumns = false;
}
allVisibleColumnsAction->setChecked( allColumnsAreSearchColumns );
// searchColumnsMenuActivated() relies on one possible "all" representation
if ( allColumnsAreSearchColumns && !d->searchColumns.isEmpty() )
d->searchColumns.clear();
}
popup->exec( event->globalPos() );
delete popup;
}
void KTreeWidgetSearchLine::connectTreeWidget( QTreeWidget *treeWidget )
{
connect( treeWidget, SIGNAL(destroyed(QObject*)),
this, SLOT(_k_treeWidgetDeleted(QObject*)) );
connect( treeWidget->model(), SIGNAL(rowsInserted(QModelIndex,int,int)),
this, SLOT(_k_rowsInserted(QModelIndex,int,int)) );
}
void KTreeWidgetSearchLine::disconnectTreeWidget( QTreeWidget *treeWidget )
{
disconnect( treeWidget, SIGNAL(destroyed(QObject*)),
this, SLOT(_k_treeWidgetDeleted(QObject*)) );
disconnect( treeWidget->model(), SIGNAL(rowsInserted(QModelIndex,int,int)),
this, SLOT(_k_rowsInserted(QModelIndex,int,int)) );
}
bool KTreeWidgetSearchLine::canChooseColumnsCheck()
{
// This is true if either of the following is true:
// there are no listviews connected
if ( d->treeWidgets.isEmpty() )
return false;
const QTreeWidget *first = d->treeWidgets.first();
const unsigned int numcols = first->columnCount();
// the listviews have only one column,
if ( numcols < 2 )
return false;
QStringList headers;
for ( unsigned int i = 0; i < numcols; ++i )
headers.append( first->headerItem()->text( i ) );
QList<QTreeWidget *>::ConstIterator it = d->treeWidgets.constBegin();
for ( ++it /* skip the first one */; it != d->treeWidgets.constEnd(); ++it ) {
// the listviews have different numbers of columns,
if ( (unsigned int) (*it)->columnCount() != numcols )
return false;
// the listviews differ in column labels.
QStringList::ConstIterator jt;
unsigned int i;
for ( i = 0, jt = headers.constBegin(); i < numcols; ++i, ++jt ) {
Q_ASSERT( jt != headers.constEnd() );
if ( (*it)->headerItem()->text( i ) != *jt )
return false;
}
}
return true;
}
bool KTreeWidgetSearchLine::event(QEvent *event) {
if (event->type() == QEvent::KeyPress) {
QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
if(keyEvent->matches(QKeySequence::MoveToNextLine) || keyEvent->matches(QKeySequence::SelectNextLine) ||
keyEvent->matches(QKeySequence::MoveToPreviousLine) || keyEvent->matches(QKeySequence::SelectPreviousLine) ||
keyEvent->matches(QKeySequence::MoveToNextPage) || keyEvent->matches(QKeySequence::SelectNextPage) ||
keyEvent->matches(QKeySequence::MoveToPreviousPage) || keyEvent->matches(QKeySequence::SelectPreviousPage) ||
keyEvent->key() == Qt::Key_Enter || keyEvent->key() == Qt::Key_Return)
{
QTreeWidget *first = d->treeWidgets.first();
if(first) {
QApplication::sendEvent(first, event);
return true;
}
}
}
return KLineEdit::event(event);
}
////////////////////////////////////////////////////////////////////////////////
// protected slots
////////////////////////////////////////////////////////////////////////////////
void KTreeWidgetSearchLine::Private::_k_queueSearch( const QString &_search )
{
queuedSearches++;
search = _search;
QTimer::singleShot( 200, q, SLOT(_k_activateSearch()) );
}
void KTreeWidgetSearchLine::Private::_k_activateSearch()
{
--queuedSearches;
if ( queuedSearches == 0 )
q->updateSearch( search );
}
////////////////////////////////////////////////////////////////////////////////
// KTreeWidgetSearchLineWidget
////////////////////////////////////////////////////////////////////////////////
class KTreeWidgetSearchLineWidget::Private
{
public:
Private()
: treeWidget( 0 ),
searchLine( 0 )
{
}
QTreeWidget *treeWidget;
KTreeWidgetSearchLine *searchLine;
};
KTreeWidgetSearchLineWidget::KTreeWidgetSearchLineWidget( QWidget *parent, QTreeWidget *treeWidget )
: QWidget( parent ), d( new Private )
{
d->treeWidget = treeWidget;
// can't call createWidgets directly because it calls virtual functions
// that might not work if called directly from here due to how inheritance works
QMetaObject::invokeMethod(this, "createWidgets", Qt::QueuedConnection);
}
KTreeWidgetSearchLineWidget::~KTreeWidgetSearchLineWidget()
{
delete d;
}
KTreeWidgetSearchLine *KTreeWidgetSearchLineWidget::createSearchLine( QTreeWidget *treeWidget ) const
{
return new KTreeWidgetSearchLine( const_cast<KTreeWidgetSearchLineWidget*>(this), treeWidget );
}
void KTreeWidgetSearchLineWidget::createWidgets()
{
QLabel *label = new QLabel( i18n("S&earch:"), this );
searchLine()->show();
label->setBuddy( d->searchLine );
label->show();
QHBoxLayout* layout = new QHBoxLayout( this );
layout->setMargin( 0 );
layout->addWidget( label );
layout->addWidget( d->searchLine );
setFocusProxy( searchLine() );
}
KTreeWidgetSearchLine *KTreeWidgetSearchLineWidget::searchLine() const
{
if ( !d->searchLine )
d->searchLine = createSearchLine( d->treeWidget );
return d->searchLine;
}
#include "moc_ktreewidgetsearchline.cpp"