1/*
2 This file is part of the KDE libraries
3 SPDX-FileCopyrightText: 2003 Scott Wheeler <wheeler@kde.org>
4 SPDX-FileCopyrightText: 2004 Gustavo Sverzut Barbieri <gsbarbieri@users.sourceforge.net>
5
6 SPDX-License-Identifier: LGPL-2.0-or-later
7*/
8
9#include "klistwidgetsearchline.h"
10
11#include <QApplication>
12#include <QEvent>
13#include <QKeyEvent>
14#include <QListWidget>
15#include <QTimer>
16
17class KListWidgetSearchLinePrivate
18{
19public:
20 KListWidgetSearchLinePrivate(KListWidgetSearchLine *parent)
21 : q(parent)
22 {
23 }
24
25 void _k_listWidgetDeleted();
26 void _k_queueSearch(const QString &);
27 void _k_activateSearch();
28 void _k_rowsInserted(const QModelIndex &, int, int);
29 void _k_dataChanged(const QModelIndex &, const QModelIndex &);
30
31 void init(QListWidget *listWidget = nullptr);
32 void updateHiddenState(int start, int end);
33
34 KListWidgetSearchLine *const q;
35 QListWidget *listWidget = nullptr;
36 Qt::CaseSensitivity caseSensitivity = Qt::CaseInsensitive;
37 bool activeSearch = false;
38 QString search;
39 int queuedSearches = 0;
40};
41
42/******************************************************************************
43 * Public Methods *
44 *****************************************************************************/
45KListWidgetSearchLine::KListWidgetSearchLine(QWidget *parent, QListWidget *listWidget)
46 : QLineEdit(parent)
47 , d(new KListWidgetSearchLinePrivate(this))
48
49{
50 d->init(listWidget);
51}
52
53KListWidgetSearchLine::~KListWidgetSearchLine()
54{
55 clear(); // returning items back to listWidget
56}
57
58Qt::CaseSensitivity KListWidgetSearchLine::caseSensitive() const
59{
60 return d->caseSensitivity;
61}
62
63QListWidget *KListWidgetSearchLine::listWidget() const
64{
65 return d->listWidget;
66}
67
68/******************************************************************************
69 * Public Slots *
70 *****************************************************************************/
71void KListWidgetSearchLine::updateSearch(const QString &s)
72{
73 d->search = s.isNull() ? text() : s;
74 if (d->listWidget) {
75 d->updateHiddenState(start: 0, end: d->listWidget->count() - 1);
76 }
77}
78
79void KListWidgetSearchLine::clear()
80{
81 // Show items back to QListWidget
82 if (d->listWidget != nullptr) {
83 for (int i = 0; i < d->listWidget->count(); ++i) {
84 d->listWidget->item(row: i)->setHidden(false);
85 }
86 }
87
88 d->search = QString();
89 d->queuedSearches = 0;
90 QLineEdit::clear();
91}
92
93void KListWidgetSearchLine::setCaseSensitivity(Qt::CaseSensitivity cs)
94{
95 d->caseSensitivity = cs;
96}
97
98void KListWidgetSearchLine::setListWidget(QListWidget *lw)
99{
100 if (d->listWidget != nullptr) {
101 disconnect(sender: d->listWidget, SIGNAL(destroyed()), receiver: this, SLOT(_k_listWidgetDeleted()));
102 d->listWidget->model()->disconnect(receiver: this);
103 }
104
105 d->listWidget = lw;
106
107 if (lw != nullptr) {
108 // clang-format off
109 connect(sender: d->listWidget, SIGNAL(destroyed()), receiver: this, SLOT(_k_listWidgetDeleted()));
110 connect(sender: d->listWidget->model(), SIGNAL(rowsInserted(QModelIndex,int,int)), receiver: this, SLOT(_k_rowsInserted(QModelIndex,int,int)));
111 connect(sender: d->listWidget->model(), SIGNAL(dataChanged(QModelIndex,QModelIndex)), receiver: this, SLOT(_k_dataChanged(QModelIndex,QModelIndex)));
112 // clang-format on
113 setEnabled(true);
114 } else {
115 setEnabled(false);
116 }
117}
118
119/******************************************************************************
120 * Protected Methods *
121 *****************************************************************************/
122bool KListWidgetSearchLine::itemMatches(const QListWidgetItem *item, const QString &s) const
123{
124 if (s.isEmpty()) {
125 return true;
126 }
127
128 if (item == nullptr) {
129 return false;
130 }
131
132 return (item->text().indexOf(s, from: 0, cs: caseSensitive() ? Qt::CaseSensitive : Qt::CaseInsensitive) >= 0);
133}
134
135void KListWidgetSearchLinePrivate::init(QListWidget *_listWidget)
136{
137 listWidget = _listWidget;
138
139 QObject::connect(sender: q, SIGNAL(textChanged(QString)), receiver: q, SLOT(_k_queueSearch(QString)));
140
141 if (listWidget != nullptr) {
142 // clang-format off
143 QObject::connect(sender: listWidget, SIGNAL(destroyed()), receiver: q, SLOT(_k_listWidgetDeleted()));
144 QObject::connect(sender: listWidget->model(), SIGNAL(rowsInserted(QModelIndex,int,int)), receiver: q, SLOT(_k_rowsInserted(QModelIndex,int,int)));
145 QObject::connect(sender: listWidget->model(), SIGNAL(dataChanged(QModelIndex,QModelIndex)), receiver: q, SLOT(_k_dataChanged(QModelIndex,QModelIndex)));
146 // clang-format on
147 q->setEnabled(true);
148 } else {
149 q->setEnabled(false);
150 }
151 q->setClearButtonEnabled(true);
152}
153
154void KListWidgetSearchLinePrivate::updateHiddenState(int start, int end)
155{
156 if (!listWidget) {
157 return;
158 }
159
160 QListWidgetItem *currentItem = listWidget->currentItem();
161
162 // Remove Non-Matching items
163 for (int index = start; index <= end; ++index) {
164 QListWidgetItem *item = listWidget->item(row: index);
165 if (!q->itemMatches(item, s: search)) {
166 item->setHidden(true);
167
168 if (item == currentItem) {
169 currentItem = nullptr; // It's not in listWidget anymore.
170 }
171 } else if (item->isHidden()) {
172 item->setHidden(false);
173 }
174 }
175
176 if (listWidget->isSortingEnabled()) {
177 listWidget->sortItems();
178 }
179
180 if (currentItem != nullptr) {
181 listWidget->scrollToItem(item: currentItem);
182 }
183}
184
185bool KListWidgetSearchLine::event(QEvent *event)
186{
187 if (event->type() == QEvent::KeyPress) {
188 QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
189 if (keyEvent->matches(key: QKeySequence::MoveToNextLine) || keyEvent->matches(key: QKeySequence::SelectNextLine)
190 || keyEvent->matches(key: QKeySequence::MoveToPreviousLine) || keyEvent->matches(key: QKeySequence::SelectPreviousLine)
191 || keyEvent->matches(key: QKeySequence::MoveToNextPage) || keyEvent->matches(key: QKeySequence::SelectNextPage)
192 || keyEvent->matches(key: QKeySequence::MoveToPreviousPage) || keyEvent->matches(key: QKeySequence::SelectPreviousPage)) {
193 if (d->listWidget) {
194 QApplication::sendEvent(receiver: d->listWidget, event);
195 return true;
196 }
197 } else if (keyEvent->key() == Qt::Key_Enter || keyEvent->key() == Qt::Key_Return) {
198 if (d->listWidget) {
199 QApplication::sendEvent(receiver: d->listWidget, event);
200 return true;
201 }
202 }
203 }
204 return QLineEdit::event(event);
205}
206/******************************************************************************
207 * Protected Slots *
208 *****************************************************************************/
209void KListWidgetSearchLinePrivate::_k_queueSearch(const QString &s)
210{
211 queuedSearches++;
212 search = s;
213 QTimer::singleShot(msec: 200, receiver: q, SLOT(_k_activateSearch()));
214}
215
216void KListWidgetSearchLinePrivate::_k_activateSearch()
217{
218 queuedSearches--;
219
220 if (queuedSearches <= 0) {
221 q->updateSearch(s: search);
222 queuedSearches = 0;
223 }
224}
225
226/******************************************************************************
227 * KListWidgetSearchLinePrivate Slots *
228 *****************************************************************************/
229void KListWidgetSearchLinePrivate::_k_listWidgetDeleted()
230{
231 listWidget = nullptr;
232 q->setEnabled(false);
233}
234
235void KListWidgetSearchLinePrivate::_k_rowsInserted(const QModelIndex &parent, int start, int end)
236{
237 if (parent.isValid()) {
238 return;
239 }
240
241 updateHiddenState(start, end);
242}
243
244void KListWidgetSearchLinePrivate::_k_dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight)
245{
246 if (topLeft.parent().isValid()) {
247 return;
248 }
249
250 updateHiddenState(start: topLeft.row(), end: bottomRight.row());
251}
252
253#include "moc_klistwidgetsearchline.cpp"
254

source code of kitemviews/src/klistwidgetsearchline.cpp