File indexing completed on 2024-12-01 03:41:41

0001 /*
0002     This file is part of the KDE libraries
0003     SPDX-FileCopyrightText: 2003 Scott Wheeler <wheeler@kde.org>
0004     SPDX-FileCopyrightText: 2004 Gustavo Sverzut Barbieri <gsbarbieri@users.sourceforge.net>
0005 
0006     SPDX-License-Identifier: LGPL-2.0-or-later
0007 */
0008 
0009 #include "klistwidgetsearchline.h"
0010 
0011 #include <QApplication>
0012 #include <QEvent>
0013 #include <QKeyEvent>
0014 #include <QListWidget>
0015 #include <QTimer>
0016 
0017 class KListWidgetSearchLinePrivate
0018 {
0019 public:
0020     KListWidgetSearchLinePrivate(KListWidgetSearchLine *parent)
0021         : q(parent)
0022     {
0023     }
0024 
0025     void _k_listWidgetDeleted();
0026     void _k_queueSearch(const QString &);
0027     void _k_activateSearch();
0028     void _k_rowsInserted(const QModelIndex &, int, int);
0029     void _k_dataChanged(const QModelIndex &, const QModelIndex &);
0030 
0031     void init(QListWidget *listWidget = nullptr);
0032     void updateHiddenState(int start, int end);
0033 
0034     KListWidgetSearchLine *const q;
0035     QListWidget *listWidget = nullptr;
0036     Qt::CaseSensitivity caseSensitivity = Qt::CaseInsensitive;
0037     bool activeSearch = false;
0038     QString search;
0039     int queuedSearches = 0;
0040 };
0041 
0042 /******************************************************************************
0043  * Public Methods                                                             *
0044  *****************************************************************************/
0045 KListWidgetSearchLine::KListWidgetSearchLine(QWidget *parent, QListWidget *listWidget)
0046     : QLineEdit(parent)
0047     , d(new KListWidgetSearchLinePrivate(this))
0048 
0049 {
0050     d->init(listWidget);
0051 }
0052 
0053 KListWidgetSearchLine::~KListWidgetSearchLine()
0054 {
0055     clear(); // returning items back to listWidget
0056 }
0057 
0058 Qt::CaseSensitivity KListWidgetSearchLine::caseSensitive() const
0059 {
0060     return d->caseSensitivity;
0061 }
0062 
0063 QListWidget *KListWidgetSearchLine::listWidget() const
0064 {
0065     return d->listWidget;
0066 }
0067 
0068 /******************************************************************************
0069  * Public Slots                                                               *
0070  *****************************************************************************/
0071 void KListWidgetSearchLine::updateSearch(const QString &s)
0072 {
0073     d->search = s.isNull() ? text() : s;
0074     if (d->listWidget) {
0075         d->updateHiddenState(0, d->listWidget->count() - 1);
0076     }
0077 }
0078 
0079 void KListWidgetSearchLine::clear()
0080 {
0081     // Show items back to QListWidget
0082     if (d->listWidget != nullptr) {
0083         for (int i = 0; i < d->listWidget->count(); ++i) {
0084             d->listWidget->item(i)->setHidden(false);
0085         }
0086     }
0087 
0088     d->search = QString();
0089     d->queuedSearches = 0;
0090     QLineEdit::clear();
0091 }
0092 
0093 void KListWidgetSearchLine::setCaseSensitivity(Qt::CaseSensitivity cs)
0094 {
0095     d->caseSensitivity = cs;
0096 }
0097 
0098 void KListWidgetSearchLine::setListWidget(QListWidget *lw)
0099 {
0100     if (d->listWidget != nullptr) {
0101         disconnect(d->listWidget, SIGNAL(destroyed()), this, SLOT(_k_listWidgetDeleted()));
0102         d->listWidget->model()->disconnect(this);
0103     }
0104 
0105     d->listWidget = lw;
0106 
0107     if (lw != nullptr) {
0108         // clang-format off
0109         connect(d->listWidget, SIGNAL(destroyed()), this, SLOT(_k_listWidgetDeleted()));
0110         connect(d->listWidget->model(), SIGNAL(rowsInserted(QModelIndex,int,int)), this, SLOT(_k_rowsInserted(QModelIndex,int,int)));
0111         connect(d->listWidget->model(), SIGNAL(dataChanged(QModelIndex,QModelIndex)), this, SLOT(_k_dataChanged(QModelIndex,QModelIndex)));
0112         // clang-format on
0113         setEnabled(true);
0114     } else {
0115         setEnabled(false);
0116     }
0117 }
0118 
0119 /******************************************************************************
0120  * Protected Methods                                                          *
0121  *****************************************************************************/
0122 bool KListWidgetSearchLine::itemMatches(const QListWidgetItem *item, const QString &s) const
0123 {
0124     if (s.isEmpty()) {
0125         return true;
0126     }
0127 
0128     if (item == nullptr) {
0129         return false;
0130     }
0131 
0132     return (item->text().indexOf(s, 0, caseSensitive() ? Qt::CaseSensitive : Qt::CaseInsensitive) >= 0);
0133 }
0134 
0135 void KListWidgetSearchLinePrivate::init(QListWidget *_listWidget)
0136 {
0137     listWidget = _listWidget;
0138 
0139     QObject::connect(q, SIGNAL(textChanged(QString)), q, SLOT(_k_queueSearch(QString)));
0140 
0141     if (listWidget != nullptr) {
0142         // clang-format off
0143         QObject::connect(listWidget, SIGNAL(destroyed()), q, SLOT(_k_listWidgetDeleted()));
0144         QObject::connect(listWidget->model(), SIGNAL(rowsInserted(QModelIndex,int,int)), q, SLOT(_k_rowsInserted(QModelIndex,int,int)));
0145         QObject::connect(listWidget->model(), SIGNAL(dataChanged(QModelIndex,QModelIndex)), q, SLOT(_k_dataChanged(QModelIndex,QModelIndex)));
0146         // clang-format on
0147         q->setEnabled(true);
0148     } else {
0149         q->setEnabled(false);
0150     }
0151     q->setClearButtonEnabled(true);
0152 }
0153 
0154 void KListWidgetSearchLinePrivate::updateHiddenState(int start, int end)
0155 {
0156     if (!listWidget) {
0157         return;
0158     }
0159 
0160     QListWidgetItem *currentItem = listWidget->currentItem();
0161 
0162     // Remove Non-Matching items
0163     for (int index = start; index <= end; ++index) {
0164         QListWidgetItem *item = listWidget->item(index);
0165         if (!q->itemMatches(item, search)) {
0166             item->setHidden(true);
0167 
0168             if (item == currentItem) {
0169                 currentItem = nullptr; // It's not in listWidget anymore.
0170             }
0171         } else if (item->isHidden()) {
0172             item->setHidden(false);
0173         }
0174     }
0175 
0176     if (listWidget->isSortingEnabled()) {
0177         listWidget->sortItems();
0178     }
0179 
0180     if (currentItem != nullptr) {
0181         listWidget->scrollToItem(currentItem);
0182     }
0183 }
0184 
0185 bool KListWidgetSearchLine::event(QEvent *event)
0186 {
0187     if (event->type() == QEvent::KeyPress) {
0188         QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
0189         if (keyEvent->matches(QKeySequence::MoveToNextLine) || keyEvent->matches(QKeySequence::SelectNextLine)
0190             || keyEvent->matches(QKeySequence::MoveToPreviousLine) || keyEvent->matches(QKeySequence::SelectPreviousLine)
0191             || keyEvent->matches(QKeySequence::MoveToNextPage) || keyEvent->matches(QKeySequence::SelectNextPage)
0192             || keyEvent->matches(QKeySequence::MoveToPreviousPage) || keyEvent->matches(QKeySequence::SelectPreviousPage)) {
0193             if (d->listWidget) {
0194                 QApplication::sendEvent(d->listWidget, event);
0195                 return true;
0196             }
0197         } else if (keyEvent->key() == Qt::Key_Enter || keyEvent->key() == Qt::Key_Return) {
0198             if (d->listWidget) {
0199                 QApplication::sendEvent(d->listWidget, event);
0200                 return true;
0201             }
0202         }
0203     }
0204     return QLineEdit::event(event);
0205 }
0206 /******************************************************************************
0207  * Protected Slots                                                            *
0208  *****************************************************************************/
0209 void KListWidgetSearchLinePrivate::_k_queueSearch(const QString &s)
0210 {
0211     queuedSearches++;
0212     search = s;
0213     QTimer::singleShot(200, q, SLOT(_k_activateSearch()));
0214 }
0215 
0216 void KListWidgetSearchLinePrivate::_k_activateSearch()
0217 {
0218     queuedSearches--;
0219 
0220     if (queuedSearches <= 0) {
0221         q->updateSearch(search);
0222         queuedSearches = 0;
0223     }
0224 }
0225 
0226 /******************************************************************************
0227  * KListWidgetSearchLinePrivate Slots                                                              *
0228  *****************************************************************************/
0229 void KListWidgetSearchLinePrivate::_k_listWidgetDeleted()
0230 {
0231     listWidget = nullptr;
0232     q->setEnabled(false);
0233 }
0234 
0235 void KListWidgetSearchLinePrivate::_k_rowsInserted(const QModelIndex &parent, int start, int end)
0236 {
0237     if (parent.isValid()) {
0238         return;
0239     }
0240 
0241     updateHiddenState(start, end);
0242 }
0243 
0244 void KListWidgetSearchLinePrivate::_k_dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight)
0245 {
0246     if (topLeft.parent().isValid()) {
0247         return;
0248     }
0249 
0250     updateHiddenState(topLeft.row(), bottomRight.row());
0251 }
0252 
0253 #include "moc_klistwidgetsearchline.cpp"