/* This file is part of the KDE project Copyright (C) 2005 Daniel Teske 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 #include #include #include #include #include #include #include #include #include #include //////////////////////////////////////////////////////////////////////////////// // 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 searchColumns; }; KViewSearchLine::KViewSearchLine(QWidget *parent, QAbstractItemView *v) : KLineEdit(parent) { d = new KViewSearchLinePrivate; setClearButtonShown(true); d->treeView = dynamic_cast(v); d->listView = dynamic_cast(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 &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(v); d->listView = dynamic_cast(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::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; itreeView->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; itreeView->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; isearchColumns.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."<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; iindex(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"