dolphin/keditbookmarks/kebsearchline.cpp
Stephen Kelly ed2872074e Simplify some Qt module style includes.
This is the easiest way to transition to Qt5.
2012-04-10 00:20:22 +02:00

698 lines
21 KiB
C++

/* This file is part of the KDE project
Copyright (C) 2005 Daniel Teske <teske@squorn.de>
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public
License version 2 or at your option version 3 as published by
the Free Software Foundation.
This program 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
General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; see the file COPYING. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#include "kebsearchline.h"
#include <kdebug.h>
#include <QTimer>
#include <QTreeView>
#include <QListView>
#include <QLabel>
#include <kicon.h>
#include <klocale.h>
#include <QHeaderView>
#include <QBoxLayout>
#include <QKeyEvent>
#include <QMenu>
////////////////////////////////////////////////////////////////////////////////
// public methods
////////////////////////////////////////////////////////////////////////////////
class KViewSearchLine::KViewSearchLinePrivate
{
public:
KViewSearchLinePrivate() :
listView(0),
treeView(0),
caseSensitive(false),
activeSearch(false),
keepParentsVisible(true),
queuedSearches(0) {}
QListView * listView;
QTreeView * treeView;
bool caseSensitive;
bool activeSearch;
bool keepParentsVisible;
QString search;
int queuedSearches;
QLinkedList<int> searchColumns;
};
KViewSearchLine::KViewSearchLine(QWidget *parent, QAbstractItemView *v) :
KLineEdit(parent)
{
d = new KViewSearchLinePrivate;
setClearButtonShown(true);
d->treeView = dynamic_cast<QTreeView *>(v);
d->listView = dynamic_cast<QListView *>(v);
connect(this, SIGNAL(textChanged(QString)),
this, SLOT(queueSearch(QString)));
if(view()) {
connect(view(), SIGNAL(destroyed()),
this, SLOT(listViewDeleted()));
connect(model(), SIGNAL(dataChanged(QModelIndex,QModelIndex)),
this, SLOT(slotDataChanged(QModelIndex,QModelIndex)));
connect(model(), SIGNAL(rowsInserted(QModelIndex,int,int)),
this, SLOT(slotRowsInserted(QModelIndex,int,int)));
connect(model(), SIGNAL(rowsRemoved(QModelIndex,int,int)),
this, SLOT(slotRowsRemoved(QModelIndex,int,int)));
connect(model(), SIGNAL(columnsInserted(QModelIndex,int,int)),
this, SLOT(slotColumnsInserted(QModelIndex,int,int)));
connect(model(), SIGNAL(columnsRemoved(QModelIndex,int,int)),
this, SLOT(slotColumnsRemoved(QModelIndex,int,int)));
connect(model(), SIGNAL(modelReset()), this, SLOT(slotModelReset()));
}
else
setEnabled(false);
}
KViewSearchLine::KViewSearchLine(QWidget *parent) :
KLineEdit(parent)
{
d = new KViewSearchLinePrivate;
setClearButtonShown(true);
d->treeView = 0;
d->listView = 0;
connect(this, SIGNAL(textChanged(QString)),
this, SLOT(queueSearch(QString)));
setEnabled(false);
}
KViewSearchLine::~KViewSearchLine()
{
delete d;
}
QAbstractItemView * KViewSearchLine::view() const
{
if(d->treeView)
return d->treeView;
else
return d->listView;
}
bool KViewSearchLine::caseSensitive() const
{
return d->caseSensitive;
}
bool KViewSearchLine::keepParentsVisible() const
{
return d->keepParentsVisible;
}
////////////////////////////////////////////////////////////////////////////////
// public slots
////////////////////////////////////////////////////////////////////////////////
void KViewSearchLine::updateSearch(const QString &s)
{
if(!view())
return;
d->search = s.isNull() ? text() : s;
// 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).
//FIXME reimplement
if(d->keepParentsVisible)
checkItemParentsVisible(model()->index(0,0, QModelIndex()));
else
checkItemParentsNotVisible();
}
void KViewSearchLine::setCaseSensitive(bool cs)
{
d->caseSensitive = cs;
}
void KViewSearchLine::setKeepParentsVisible(bool v)
{
d->keepParentsVisible = v;
}
void KViewSearchLine::setSearchColumns(const QLinkedList<int> &columns)
{
d->searchColumns = columns;
}
void KViewSearchLine::setView(QAbstractItemView *v)
{
if(view()) {
disconnect(view(), SIGNAL(destroyed()),
this, SLOT(listViewDeleted()));
disconnect(model(), SIGNAL(dataChanged(QModelIndex,QModelIndex)),
this, SLOT(slotDataChanged(QModelIndex,QModelIndex)));
disconnect(model(), SIGNAL(rowsInserted(QModelIndex,int,int)),
this, SLOT(slotRowsInserted(QModelIndex,int,int)));
disconnect(model(), SIGNAL(rowsRemoved(QModelIndex,int,int)),
this, SLOT(slotRowsRemoved(QModelIndex,int,int)));
disconnect(model(), SIGNAL(columnsInserted(QModelIndex,int,int)),
this, SLOT(slotColumnsInserted(QModelIndex,int,int)));
disconnect(model(), SIGNAL(columnsRemoved(QModelIndex,int,int)),
this, SLOT(slotColumnsRemoved(QModelIndex,int,int)));
disconnect(model(), SIGNAL(modelReset()), this, SLOT(slotModelReset()));
}
d->treeView = dynamic_cast<QTreeView *>(v);
d->listView = dynamic_cast<QListView *>(v);
if(view()) {
connect(view(), SIGNAL(destroyed()),
this, SLOT(listViewDeleted()));
connect(model(), SIGNAL(dataChanged(QModelIndex,QModelIndex)),
this, SLOT(slotDataChanged(QModelIndex,QModelIndex)));
connect(model(), SIGNAL(rowsInserted(QModelIndex,int,int)),
this, SLOT(slotRowsInserted(QModelIndex,int,int)));
connect(model(), SIGNAL(rowsRemoved(QModelIndex,int,int)),
this, SLOT(slotRowsRemoved(QModelIndex,int,int)));
connect(model(), SIGNAL(columnsInserted(QModelIndex,int,int)),
this, SLOT(slotColumnsInserted(QModelIndex,int,int)));
connect(model(), SIGNAL(columnsRemoved(QModelIndex,int,int)),
this, SLOT(slotColumnsRemoved(QModelIndex,int,int)));
connect(model(), SIGNAL(modelReset()), this, SLOT(slotModelReset()));
}
setEnabled(bool(view()));
}
////////////////////////////////////////////////////////////////////////////////
// protected members
////////////////////////////////////////////////////////////////////////////////
bool KViewSearchLine::itemMatches(const QModelIndex & item, const QString &s) const
{
if(s.isEmpty())
return true;
// If the search column list is populated, search just the columns
// specifified. If it is empty default to searching all of the columns.
if(d->treeView)
{
int columnCount = d->treeView->header()->count();
int row = item.row();
QModelIndex parent = item.parent();
if(!d->searchColumns.isEmpty()) {
QLinkedList<int>::const_iterator it, end;
end = d->searchColumns.constEnd();
for(it = d->searchColumns.constBegin(); it != end; ++it)
{
if(*it < columnCount)
{
const QString & text = model()->data(parent.child(row, *it)).toString();
if(text.indexOf(s, 0, d->caseSensitive ? Qt::CaseSensitive : Qt::CaseInsensitive) >= 0)
return true;
}
}
}
else {
for(int i = 0; i < columnCount; i++)
{
if(d->treeView->isColumnHidden(i) == false)
{
const QString & text = model()->data(parent.child(row, i)).toString();
if(text.indexOf(s, 0, d->caseSensitive ? Qt::CaseSensitive : Qt::CaseInsensitive) >= 0)
return true;
}
}
}
return false;
}
else
{
QString text = model()->data(item).toString();
if(text.indexOf(s, 0, d->caseSensitive ? Qt::CaseSensitive : Qt::CaseInsensitive) >= 0)
return true;
else
return false;
}
}
void KViewSearchLine::contextMenuEvent( QContextMenuEvent*e )
{
qDeleteAll(actions);
QMenu *popup = KLineEdit::createStandardContextMenu();
if(d->treeView)
{
int columnCount = d->treeView->header()->count();
actions.resize(columnCount + 1);
if(columnCount)
{
QMenu *submenu = new QMenu(i18n("Search Columns"), popup);
popup->addMenu(submenu);
bool allVisibleColumsCheked = true;
QAction * allVisibleAct = new QAction(i18n("All Visible Columns"), 0);
allVisibleAct->setCheckable(true);
submenu->addAction(allVisibleAct);
submenu->addSeparator();
for(int i=0; i<columnCount; ++i)
{
int logicalIndex = d->treeView->header()->logicalIndex(i);
QString columnText = model()->headerData(logicalIndex, Qt::Horizontal).toString();
if(columnText.isEmpty())
columnText = i18nc("Column number %1","Column No. %1", i);
QAction * act = new QAction(columnText, 0);
act->setCheckable(true);
if( d->searchColumns.isEmpty() || d->searchColumns.contains(logicalIndex) )
act->setChecked(true);
actions[logicalIndex] = act;
if( !d->treeView || (d->treeView->isColumnHidden(i) == false) )
{
submenu->addAction(act);
allVisibleColumsCheked = allVisibleColumsCheked && act->isChecked();
}
}
actions[columnCount] = allVisibleAct;
if(d->searchColumns.isEmpty() || allVisibleColumsCheked)
{
allVisibleAct->setChecked(true);
d->searchColumns.clear();
}
connect(submenu, SIGNAL(triggered(QAction*)), this, SLOT(searchColumnsMenuActivated(QAction*)));
}
}
popup->exec( e->globalPos() );
delete popup;
}
////////////////////////////////////////////////////////////////////////////////
// protected slots
////////////////////////////////////////////////////////////////////////////////
void KViewSearchLine::queueSearch(const QString &search)
{
d->queuedSearches++;
d->search = search;
QTimer::singleShot(200, this, SLOT(activateSearch()));
}
void KViewSearchLine::activateSearch()
{
--(d->queuedSearches);
if(d->queuedSearches == 0)
updateSearch(d->search);
}
////////////////////////////////////////////////////////////////////////////////
// private slots
////////////////////////////////////////////////////////////////////////////////
void KViewSearchLine::listViewDeleted()
{
d->treeView = 0;
d->listView = 0;
setEnabled(false);
}
void KViewSearchLine::searchColumnsMenuActivated(QAction * action)
{
int index = 0;
int count = actions.count();
for(int i=0; i<count; ++i)
{
if(action == actions[i])
{
index = i;
break;
}
}
int columnCount = d->treeView->header()->count();
if(index == columnCount)
{
if(d->searchColumns.isEmpty()) //all columns was checked
d->searchColumns.append(0);
else
d->searchColumns.clear();
}
else
{
if(d->searchColumns.contains(index))
d->searchColumns.removeAll(index);
else
{
if(d->searchColumns.isEmpty()) //all columns was checked
{
for(int i=0; i<columnCount; ++i)
if(i != index)
d->searchColumns.append(i);
}
else
d->searchColumns.append(index);
}
}
updateSearch();
}
void KViewSearchLine::slotRowsRemoved(const QModelIndex &parent, int, int)
{
if(!d->keepParentsVisible)
return;
QModelIndex p = parent;
while(p.isValid())
{
int count = model()->rowCount(p);
if(count && anyVisible( model()->index(0,0, p), model()->index( count-1, 0, p)))
return;
if(itemMatches(p, d->search))
return;
setVisible(p, false);
p = p.parent();
}
}
void KViewSearchLine::slotColumnsInserted(const QModelIndex &, int, int )
{
updateSearch();
}
void KViewSearchLine::slotColumnsRemoved(const QModelIndex &, int first, int last)
{
if(d->treeView)
updateSearch();
else
{
if(d->listView->modelColumn() >= first && d->listView->modelColumn()<= last)
{
if(d->listView->modelColumn()>last)
kFatal()<<"Columns were removed, the modelColumn() doesn't exist anymore. "
"K4listViewSearchLine can't cope with that."<<endl;
updateSearch();
}
}
}
void KViewSearchLine::slotModelReset()
{
//FIXME Is there a way to ensure that the view
// has already responded to the reset signal?
updateSearch();
}
void KViewSearchLine::slotDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight)
{
QModelIndex parent = topLeft.parent();
int column = 0;
if(d->listView)
column = d->listView->modelColumn();
bool match = recheck( model()->index(topLeft.row(), column, parent), model()->index(bottomRight.row(), column, parent));
if(!d->keepParentsVisible)
return;
if(!parent.isValid()) // includes listview
return;
if(match)
{
QModelIndex p = parent;
while(p.isValid())
{
setVisible(p, true);
p = p.parent();
}
}
else //no match => might need to hide parents (this is ugly)
{
if(isVisible(parent) == false) // parent is already hidden
return;
//parent is visible => implies all parents visible
// first check if all of the unchanged rows are hidden
match = false;
if(topLeft.row() >= 1)
match = match || anyVisible( model()->index(0,0, parent), model()->index(topLeft.row()-1, 0, parent));
int rowCount = model()->rowCount(parent);
if(bottomRight.row() + 1 <= rowCount - 1)
match = match || anyVisible( model()->index(bottomRight.row()+1, 0, parent), model()->index(rowCount-1, 0, parent));
if(!match) //all child rows hidden
{
if(itemMatches(parent, d->search))
return;
// and parent didn't match, hide it
setVisible(parent, false);
// need to check all the way up to root
QModelIndex p = parent.parent();
while(p.isValid())
{
//hide p if no children of p isVisible and it doesn't match
int count = model()->rowCount(p);
if(anyVisible( model()->index(0, 0, p), model()->index(count-1, 0, p)))
return;
if(itemMatches(p, d->search))
return;
setVisible(p, false);
p = p.parent();
}
}
}
}
////////////////////////////////////////////////////////////////////////////////
// private methods
////////////////////////////////////////////////////////////////////////////////
QAbstractItemModel * KViewSearchLine::model() const
{
if(d->treeView)
return d->treeView->model();
else
return d->listView->model();
}
bool KViewSearchLine::anyVisible(const QModelIndex & first, const QModelIndex & last)
{
Q_ASSERT(d->treeView);
QModelIndex parent = first.parent();
QModelIndex index = first;
while(true)
{
if( isVisible(index))
return true;
if(index == last)
break;
index = nextRow(index);
}
return false;
}
bool KViewSearchLine::isVisible(const QModelIndex & index)
{
if(d->treeView)
return !d->treeView->isRowHidden(index.row(), index.parent());
else
return d->listView->isRowHidden(index.row());
}
QModelIndex KViewSearchLine::nextRow(const QModelIndex & index)
{
return model()->index(index.row()+1, index.column(), index.parent());
}
bool KViewSearchLine::recheck(const QModelIndex & first, const QModelIndex & last)
{
bool visible = false;
QModelIndex index = first;
while(true)
{
int rowCount = model()->rowCount(index);
if(d->keepParentsVisible && rowCount && anyVisible( index.child(0,0), index.child( rowCount-1, 0)))
{
visible = true;
}
else // no children visible
{
bool match = itemMatches(index, d->search);
setVisible(index, match);
visible = visible || match;
}
if(index == last)
break;
index = nextRow(index);
}
return visible;
}
void KViewSearchLine::slotRowsInserted(const QModelIndex &parent, int first, int last)
{
bool visible = false;
int column = 0;
if(d->listView)
column = d->listView->modelColumn();
QModelIndex index = model()->index(first, column, parent);
QModelIndex end = model()->index(last, column, parent);
while(true)
{
if(itemMatches(index, d->search))
{
visible = true;
setVisible(index, true);
}
else
setVisible(index, false);
if(index == end)
break;
index = nextRow(index);
}
if(!d->keepParentsVisible)
return;
if(visible)
{
QModelIndex p = parent;
while(p.isValid())
{
setVisible(p, true);
p = p.parent();
}
}
}
void KViewSearchLine::setVisible(QModelIndex index, bool v)
{
if(d->treeView)
d->treeView->setRowHidden(index.row(), index.parent(), !v);
else
d->listView->setRowHidden(index.row(), !v);
}
void KViewSearchLine::checkItemParentsNotVisible()
{
int rowCount = model()->rowCount( QModelIndex() );
int column = 0;
if(d->listView)
column = d->listView->modelColumn();
for(int i = 0; i < rowCount; ++i)
{
QModelIndex it = model()->index(i, column, QModelIndex());
if(itemMatches(it, d->search))
setVisible(it, true);
else
setVisible(it, false);
}
}
/** Check whether \p index, its siblings and their descendents should be shown. Show or hide the items as necessary.
*
* \p index The list view item to start showing / hiding items at. Typically, this is the first child of another item, or the
* 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.
*/
bool KViewSearchLine::checkItemParentsVisible(QModelIndex index)
{
bool visible = false;
int rowCount = model()->rowCount(index.parent());
int column = 0;
if(d->listView)
column = d->listView->modelColumn();
for(int i = 0; i<rowCount; ++i)
{
index = model()->index(i, column, index.parent());
if((model()->rowCount(index) && checkItemParentsVisible(index.child(0,column)))
|| itemMatches(index, d->search))
{
visible = true;
setVisible(index, true);
}
else
setVisible(index, false);
}
return visible;
}
////////////////////////////////////////////////////////////////////////////////
// KViewSearchLineWidget
////////////////////////////////////////////////////////////////////////////////
class KViewSearchLineWidget::KViewSearchLineWidgetPrivate
{
public:
KViewSearchLineWidgetPrivate() : view(0), searchLine(0), layout(0) {}
QAbstractItemView *view;
KViewSearchLine *searchLine;
QHBoxLayout *layout;
};
KViewSearchLineWidget::KViewSearchLineWidget(QAbstractItemView *view,
QWidget *parent) :
QWidget(parent)
{
d = new KViewSearchLineWidgetPrivate;
d->view = view;
QTimer::singleShot(0, this, SLOT(createWidgets()));
}
KViewSearchLineWidget::~KViewSearchLineWidget()
{
delete d->layout;
delete d;
}
KViewSearchLine *KViewSearchLineWidget::createSearchLine(QAbstractItemView *view)
{
if(!d->searchLine)
d->searchLine = new KViewSearchLine(0, view);
return d->searchLine;
}
void KViewSearchLineWidget::createWidgets()
{
d->layout = new QHBoxLayout(this);
d->layout->setMargin(0);
QLabel *label = new QLabel(i18n("S&earch:"));
label->setObjectName( QLatin1String("kde toolbar widget" ));
d->layout->addWidget(label);
d->searchLine = createSearchLine(d->view);
d->layout->addWidget(d->searchLine);
d->searchLine->show();
label->setBuddy(d->searchLine);
label->show();
}
KViewSearchLine *KViewSearchLineWidget::searchLine() const
{
return d->searchLine;
}
#include "kebsearchline.moc"