mirror of
https://invent.kde.org/graphics/okular
synced 2024-11-05 18:34:53 +00:00
399 lines
12 KiB
C++
399 lines
12 KiB
C++
/*
|
|
SPDX-FileCopyrightText: 2003 Scott Wheeler <wheeler@kde.org>
|
|
SPDX-FileCopyrightText: 2005 Rafal Rzepecki <divide@users.sourceforge.net>
|
|
SPDX-FileCopyrightText: 2006 Hamish Rodda <rodda@kde.org>
|
|
SPDX-FileCopyrightText: 2007 Pino Toscano <pino@kde.org>
|
|
|
|
SPDX-License-Identifier: LGPL-2.0-only
|
|
*/
|
|
|
|
#include "ktreeviewsearchline.h"
|
|
|
|
#include <QApplication>
|
|
#include <QContextMenuEvent>
|
|
#include <QHBoxLayout>
|
|
#include <QHeaderView>
|
|
#include <QLabel>
|
|
#include <QList>
|
|
#include <QMenu>
|
|
#include <QRegularExpression>
|
|
#include <QTimer>
|
|
#include <QToolButton>
|
|
#include <QTreeView>
|
|
|
|
#include <KLocalizedString>
|
|
#include <KToolBar>
|
|
#include <QDebug>
|
|
|
|
class KTreeViewSearchLine::Private
|
|
{
|
|
public:
|
|
explicit Private(KTreeViewSearchLine *_parent)
|
|
: parent(_parent)
|
|
, treeView(nullptr)
|
|
, caseSensitive(Qt::CaseInsensitive)
|
|
, regularExpression(false)
|
|
, activeSearch(false)
|
|
, queuedSearches(0)
|
|
{
|
|
}
|
|
|
|
KTreeViewSearchLine *parent;
|
|
QTreeView *treeView;
|
|
Qt::CaseSensitivity caseSensitive;
|
|
bool regularExpression;
|
|
bool activeSearch;
|
|
QString search;
|
|
int queuedSearches;
|
|
|
|
void rowsInserted(const QModelIndex &parent, int start, int end) const;
|
|
void treeViewDeleted(QObject *object);
|
|
void slotCaseSensitive();
|
|
void slotRegularExpression();
|
|
|
|
void checkItemParentsNotVisible(QTreeView *treeView);
|
|
bool filterItems(QTreeView *treeView, const QModelIndex &index);
|
|
};
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
// private slots
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void KTreeViewSearchLine::Private::rowsInserted(const QModelIndex &parentIndex, int start, int end) const
|
|
{
|
|
QAbstractItemModel *model = qobject_cast<QAbstractItemModel *>(parent->sender());
|
|
if (!model)
|
|
return;
|
|
|
|
QTreeView *widget = nullptr;
|
|
if (treeView->model() == model) {
|
|
widget = treeView;
|
|
}
|
|
|
|
if (!widget)
|
|
return;
|
|
|
|
for (int i = start; i <= end; ++i) {
|
|
widget->setRowHidden(i, parentIndex, !parent->itemMatches(parentIndex, i, parent->text()));
|
|
}
|
|
}
|
|
|
|
void KTreeViewSearchLine::Private::treeViewDeleted(QObject *object)
|
|
{
|
|
if (object == treeView) {
|
|
treeView = nullptr;
|
|
parent->setEnabled(false);
|
|
}
|
|
}
|
|
|
|
void KTreeViewSearchLine::Private::slotCaseSensitive()
|
|
{
|
|
if (caseSensitive == Qt::CaseSensitive)
|
|
parent->setCaseSensitivity(Qt::CaseInsensitive);
|
|
else
|
|
parent->setCaseSensitivity(Qt::CaseSensitive);
|
|
|
|
parent->updateSearch();
|
|
}
|
|
|
|
void KTreeViewSearchLine::Private::slotRegularExpression()
|
|
{
|
|
if (regularExpression)
|
|
parent->setRegularExpression(false);
|
|
else
|
|
parent->setRegularExpression(true);
|
|
|
|
parent->updateSearch();
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
// private methods
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
/** 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
|
|
* 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 KTreeViewSearchLine::Private::filterItems(QTreeView *treeView, const QModelIndex &index)
|
|
{
|
|
bool childMatch = false;
|
|
const int rowcount = treeView->model()->rowCount(index);
|
|
for (int i = 0; i < rowcount; ++i)
|
|
childMatch |= filterItems(treeView, treeView->model()->index(i, 0, index));
|
|
|
|
// Should this item be shown? It should if any children should be, or if it matches.
|
|
const QModelIndex parentindex = index.parent();
|
|
if (childMatch || parent->itemMatches(parentindex, index.row(), search)) {
|
|
treeView->setRowHidden(index.row(), parentindex, false);
|
|
return true;
|
|
}
|
|
|
|
treeView->setRowHidden(index.row(), parentindex, true);
|
|
|
|
return false;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
// public methods
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
KTreeViewSearchLine::KTreeViewSearchLine(QWidget *parent, QTreeView *treeView)
|
|
: KLineEdit(parent)
|
|
, d(new Private(this))
|
|
{
|
|
connect(this, &KTreeViewSearchLine::textChanged, this, &KTreeViewSearchLine::queueSearch);
|
|
|
|
setClearButtonEnabled(true);
|
|
setTreeView(treeView);
|
|
|
|
if (!treeView) {
|
|
setEnabled(false);
|
|
}
|
|
}
|
|
|
|
KTreeViewSearchLine::~KTreeViewSearchLine()
|
|
{
|
|
delete d;
|
|
}
|
|
|
|
Qt::CaseSensitivity KTreeViewSearchLine::caseSensitivity() const
|
|
{
|
|
return d->caseSensitive;
|
|
}
|
|
|
|
bool KTreeViewSearchLine::regularExpression() const
|
|
{
|
|
return d->regularExpression;
|
|
}
|
|
|
|
QTreeView *KTreeViewSearchLine::treeView() const
|
|
{
|
|
return d->treeView;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
// public slots
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void KTreeViewSearchLine::updateSearch(const QString &pattern)
|
|
{
|
|
d->search = pattern.isNull() ? text() : pattern;
|
|
|
|
updateSearch(d->treeView);
|
|
}
|
|
|
|
void KTreeViewSearchLine::updateSearch(QTreeView *treeView)
|
|
{
|
|
if (!treeView || !treeView->model()->rowCount())
|
|
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).
|
|
|
|
QModelIndex currentIndex = treeView->currentIndex();
|
|
|
|
bool wasUpdateEnabled = treeView->updatesEnabled();
|
|
treeView->setUpdatesEnabled(false);
|
|
d->filterItems(treeView, treeView->rootIndex());
|
|
treeView->setUpdatesEnabled(wasUpdateEnabled);
|
|
|
|
if (currentIndex.isValid())
|
|
treeView->scrollTo(currentIndex);
|
|
}
|
|
|
|
void KTreeViewSearchLine::setCaseSensitivity(Qt::CaseSensitivity caseSensitivity)
|
|
{
|
|
if (d->caseSensitive != caseSensitivity) {
|
|
d->caseSensitive = caseSensitivity;
|
|
updateSearch();
|
|
emit searchOptionsChanged();
|
|
}
|
|
}
|
|
|
|
void KTreeViewSearchLine::setRegularExpression(bool value)
|
|
{
|
|
if (d->regularExpression != value) {
|
|
d->regularExpression = value;
|
|
updateSearch();
|
|
emit searchOptionsChanged();
|
|
}
|
|
}
|
|
|
|
void KTreeViewSearchLine::setTreeView(QTreeView *treeView)
|
|
{
|
|
disconnectTreeView(d->treeView);
|
|
d->treeView = treeView;
|
|
connectTreeView(treeView);
|
|
|
|
setEnabled(treeView != nullptr);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
// protected members
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
bool KTreeViewSearchLine::itemMatches(const QModelIndex &parentIndex, int row, const QString &pattern) const
|
|
{
|
|
if (pattern.isEmpty())
|
|
return true;
|
|
|
|
if (!parentIndex.isValid() && parentIndex != d->treeView->rootIndex())
|
|
return false;
|
|
|
|
// Construct a regular expression object with the right options.
|
|
QRegularExpression re;
|
|
if (d->regularExpression) {
|
|
re.setPattern(pattern);
|
|
re.setPatternOptions(d->caseSensitive ? QRegularExpression::NoPatternOption : QRegularExpression::CaseInsensitiveOption);
|
|
}
|
|
|
|
// If the search column list is populated, search just the columns
|
|
// specified. If it is empty default to searching all of the columns.
|
|
QAbstractItemModel *model = d->treeView->model();
|
|
const int columncount = model->columnCount(parentIndex);
|
|
for (int i = 0; i < columncount; ++i) {
|
|
const QString str = model->data(model->index(row, i, parentIndex), Qt::DisplayRole).toString();
|
|
if (d->regularExpression) {
|
|
return str.contains(re);
|
|
} else {
|
|
return str.contains(pattern, d->caseSensitive ? Qt::CaseSensitive : Qt::CaseInsensitive);
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void KTreeViewSearchLine::contextMenuEvent(QContextMenuEvent *event)
|
|
{
|
|
QMenu *popup = KLineEdit::createStandardContextMenu();
|
|
|
|
popup->addSeparator();
|
|
QMenu *optionsSubMenu = popup->addMenu(i18n("Search Options"));
|
|
QAction *caseSensitiveAction = optionsSubMenu->addAction(i18nc("Enable case sensitive search in the side navigation panels", "Case Sensitive"), this, [this] { d->slotCaseSensitive(); });
|
|
caseSensitiveAction->setCheckable(true);
|
|
caseSensitiveAction->setChecked(d->caseSensitive);
|
|
QAction *regularExpressionAction = optionsSubMenu->addAction(i18nc("Enable regular expression search in the side navigation panels", "Regular Expression"), this, [this] { d->slotRegularExpression(); });
|
|
regularExpressionAction->setCheckable(true);
|
|
regularExpressionAction->setChecked(d->regularExpression);
|
|
|
|
popup->exec(event->globalPos());
|
|
delete popup;
|
|
}
|
|
|
|
void KTreeViewSearchLine::connectTreeView(QTreeView *treeView)
|
|
{
|
|
if (treeView) {
|
|
connect(treeView, &QTreeView::destroyed, this, &KTreeViewSearchLine::treeViewDeleted);
|
|
|
|
connect(treeView->model(), &QAbstractItemModel::rowsInserted, this, &KTreeViewSearchLine::rowsInserted);
|
|
}
|
|
}
|
|
|
|
void KTreeViewSearchLine::disconnectTreeView(QTreeView *treeView)
|
|
{
|
|
if (treeView) {
|
|
disconnect(treeView, &QTreeView::destroyed, this, &KTreeViewSearchLine::treeViewDeleted);
|
|
|
|
disconnect(treeView->model(), &QAbstractItemModel::rowsInserted, this, &KTreeViewSearchLine::rowsInserted);
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
// protected slots
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void KTreeViewSearchLine::queueSearch(const QString &search)
|
|
{
|
|
d->queuedSearches++;
|
|
d->search = search;
|
|
|
|
QTimer::singleShot(200, this, &KTreeViewSearchLine::activateSearch);
|
|
}
|
|
|
|
void KTreeViewSearchLine::activateSearch()
|
|
{
|
|
--(d->queuedSearches);
|
|
|
|
if (d->queuedSearches == 0)
|
|
updateSearch(d->search);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
// private functions
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void KTreeViewSearchLine::rowsInserted(const QModelIndex &parent, int start, int end) const
|
|
{
|
|
d->rowsInserted(parent, start, end);
|
|
}
|
|
|
|
void KTreeViewSearchLine::treeViewDeleted(QObject *treeView)
|
|
{
|
|
d->treeViewDeleted(treeView);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
// KTreeViewSearchLineWidget
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
class KTreeViewSearchLineWidget::Private
|
|
{
|
|
public:
|
|
Private()
|
|
: treeView(nullptr)
|
|
, searchLine(nullptr)
|
|
{
|
|
}
|
|
|
|
QTreeView *treeView;
|
|
KTreeViewSearchLine *searchLine;
|
|
};
|
|
|
|
KTreeViewSearchLineWidget::KTreeViewSearchLineWidget(QWidget *parent, QTreeView *treeView)
|
|
: QWidget(parent)
|
|
, d(new Private)
|
|
{
|
|
d->treeView = treeView;
|
|
|
|
QTimer::singleShot(0, this, &KTreeViewSearchLineWidget::createWidgets);
|
|
}
|
|
|
|
KTreeViewSearchLineWidget::~KTreeViewSearchLineWidget()
|
|
{
|
|
delete d;
|
|
}
|
|
|
|
KTreeViewSearchLine *KTreeViewSearchLineWidget::createSearchLine(QTreeView *treeView) const
|
|
{
|
|
return new KTreeViewSearchLine(const_cast<KTreeViewSearchLineWidget *>(this), treeView);
|
|
}
|
|
|
|
void KTreeViewSearchLineWidget::createWidgets()
|
|
{
|
|
QLabel *label = new QLabel(i18n("S&earch:"), this);
|
|
label->setObjectName(QStringLiteral("kde toolbar widget"));
|
|
|
|
searchLine()->show();
|
|
|
|
label->setBuddy(d->searchLine);
|
|
label->show();
|
|
|
|
QHBoxLayout *layout = new QHBoxLayout(this);
|
|
layout->setSpacing(5);
|
|
layout->setContentsMargins(0, 0, 0, 0);
|
|
layout->addWidget(label);
|
|
layout->addWidget(d->searchLine);
|
|
}
|
|
|
|
KTreeViewSearchLine *KTreeViewSearchLineWidget::searchLine() const
|
|
{
|
|
if (!d->searchLine)
|
|
d->searchLine = createSearchLine(d->treeView);
|
|
|
|
return d->searchLine;
|
|
}
|
|
|
|
#include "moc_ktreeviewsearchline.cpp"
|