File indexing completed on 2024-04-28 15:51:57

0001 /*
0002     SPDX-FileCopyrightText: 2004 Enrico Ros <eros.kde@email.it>
0003     SPDX-FileCopyrightText: 2007, 2009-2010 Pino Toscano <pino@kde.org>
0004 
0005     SPDX-License-Identifier: GPL-2.0-or-later
0006 */
0007 
0008 #include "searchlineedit.h"
0009 
0010 // local includes
0011 
0012 // qt/kde includes
0013 #include <KBusyIndicatorWidget>
0014 #include <KColorScheme>
0015 #include <KLocalizedString>
0016 #include <KMessageBox>
0017 #include <QApplication>
0018 #include <QLayout>
0019 #include <QTimer>
0020 
0021 SearchLineEdit::SearchLineEdit(QWidget *parent, Okular::Document *document)
0022     : KLineEdit(parent)
0023     , m_document(document)
0024     , m_minLength(0)
0025     , m_caseSensitivity(Qt::CaseInsensitive)
0026     , m_searchType(Okular::Document::AllDocument)
0027     , m_id(-1)
0028     , m_moveViewport(false)
0029     , m_changed(false)
0030     , m_fromStart(true)
0031     , m_findAsYouType(true)
0032     , m_searchRunning(false)
0033 {
0034     setObjectName(QStringLiteral("SearchLineEdit"));
0035     setClearButtonEnabled(true);
0036 
0037     // a timer to ensure that we don't flood the document with requests to search
0038     m_inputDelayTimer = new QTimer(this);
0039     m_inputDelayTimer->setSingleShot(true);
0040     connect(m_inputDelayTimer, &QTimer::timeout, this, &SearchLineEdit::startSearch);
0041 
0042     connect(this, &SearchLineEdit::textChanged, this, &SearchLineEdit::slotTextChanged);
0043     connect(document, &Okular::Document::searchFinished, this, &SearchLineEdit::searchFinished);
0044 }
0045 
0046 void SearchLineEdit::clearText()
0047 {
0048     clear();
0049 }
0050 
0051 void SearchLineEdit::setSearchCaseSensitivity(Qt::CaseSensitivity cs)
0052 {
0053     m_caseSensitivity = cs;
0054     m_changed = true;
0055 }
0056 
0057 void SearchLineEdit::setSearchMinimumLength(int length)
0058 {
0059     m_minLength = length;
0060     m_changed = true;
0061 }
0062 
0063 void SearchLineEdit::setSearchType(Okular::Document::SearchType type)
0064 {
0065     if (type == m_searchType) {
0066         return;
0067     }
0068 
0069     disconnect(this, &SearchLineEdit::returnKeyPressed, this, &SearchLineEdit::slotReturnPressed);
0070 
0071     m_searchType = type;
0072 
0073     // Only connect Enter for next/prev searches, the rest of searches are document global so
0074     // next/prev search does not make sense for them
0075     if (m_searchType == Okular::Document::NextMatch || m_searchType == Okular::Document::PreviousMatch) {
0076         connect(this, &SearchLineEdit::returnKeyPressed, this, &SearchLineEdit::slotReturnPressed);
0077     }
0078 
0079     if (!m_changed) {
0080         m_changed = (m_searchType != Okular::Document::NextMatch && m_searchType != Okular::Document::PreviousMatch);
0081     }
0082 }
0083 
0084 void SearchLineEdit::setSearchId(int id)
0085 {
0086     m_id = id;
0087     m_changed = true;
0088 }
0089 
0090 void SearchLineEdit::setSearchColor(const QColor &color)
0091 {
0092     m_color = color;
0093     m_changed = true;
0094 }
0095 
0096 void SearchLineEdit::setSearchMoveViewport(bool move)
0097 {
0098     m_moveViewport = move;
0099 }
0100 
0101 void SearchLineEdit::setSearchFromStart(bool fromStart)
0102 {
0103     m_fromStart = fromStart;
0104 }
0105 
0106 void SearchLineEdit::setFindAsYouType(bool findAsYouType)
0107 {
0108     m_findAsYouType = findAsYouType;
0109 }
0110 
0111 void SearchLineEdit::resetSearch()
0112 {
0113     // Stop the currently running search, if any
0114     stopSearch();
0115 
0116     // Clear highlights
0117     if (m_id != -1) {
0118         m_document->resetSearch(m_id);
0119     }
0120 
0121     // Make sure that the search will be reset at the next one
0122     m_changed = true;
0123 
0124     // Reset input box color
0125     prepareLineEditForSearch();
0126 }
0127 
0128 bool SearchLineEdit::isSearchRunning() const
0129 {
0130     return m_searchRunning;
0131 }
0132 
0133 void SearchLineEdit::restartSearch()
0134 {
0135     m_inputDelayTimer->stop();
0136     m_inputDelayTimer->start(700);
0137     m_changed = true;
0138 }
0139 
0140 void SearchLineEdit::stopSearch()
0141 {
0142     if (m_id == -1 || !m_searchRunning) {
0143         return;
0144     }
0145 
0146     m_inputDelayTimer->stop();
0147     // ### this should just cancel the search with id m_id, not all of them
0148     m_document->cancelSearch();
0149     // flagging as "changed" so the search will be reset at the next one
0150     m_changed = true;
0151 }
0152 
0153 void SearchLineEdit::findNext()
0154 {
0155     if (m_id == -1 || m_searchType != Okular::Document::NextMatch) {
0156         return;
0157     }
0158 
0159     if (!m_changed) {
0160         Q_EMIT searchStarted();
0161         m_searchRunning = true;
0162         m_document->continueSearch(m_id, m_searchType);
0163     } else {
0164         startSearch();
0165     }
0166 }
0167 
0168 void SearchLineEdit::findPrev()
0169 {
0170     if (m_id == -1 || m_searchType != Okular::Document::PreviousMatch) {
0171         return;
0172     }
0173 
0174     if (!m_changed) {
0175         Q_EMIT searchStarted();
0176         m_searchRunning = true;
0177         m_document->continueSearch(m_id, m_searchType);
0178     } else {
0179         startSearch();
0180     }
0181 }
0182 
0183 void SearchLineEdit::slotTextChanged(const QString &text)
0184 {
0185     Q_UNUSED(text);
0186 
0187     prepareLineEditForSearch();
0188 
0189     if (m_findAsYouType) {
0190         restartSearch();
0191     } else {
0192         m_changed = true;
0193     }
0194 }
0195 
0196 void SearchLineEdit::prepareLineEditForSearch()
0197 {
0198     QPalette pal = palette();
0199     const int textLength = text().length();
0200     if (textLength > 0 && textLength < m_minLength) {
0201         const KColorScheme scheme(QPalette::Active, KColorScheme::View);
0202         pal.setBrush(QPalette::Base, scheme.background(KColorScheme::NegativeBackground));
0203         pal.setBrush(QPalette::Text, scheme.foreground(KColorScheme::NegativeText));
0204     } else {
0205         const QPalette qAppPalette = QApplication::palette();
0206         pal.setColor(QPalette::Base, qAppPalette.color(QPalette::Base));
0207         pal.setColor(QPalette::Text, qAppPalette.color(QPalette::Text));
0208     }
0209     setPalette(pal);
0210 }
0211 
0212 void SearchLineEdit::slotReturnPressed(const QString &text)
0213 {
0214     Q_UNUSED(text);
0215 
0216     m_inputDelayTimer->stop();
0217     prepareLineEditForSearch();
0218     if (QApplication::keyboardModifiers() == Qt::ShiftModifier) {
0219         m_searchType = Okular::Document::PreviousMatch;
0220         findPrev();
0221     } else {
0222         m_searchType = Okular::Document::NextMatch;
0223         findNext();
0224     }
0225 }
0226 
0227 void SearchLineEdit::startSearch()
0228 {
0229     if (m_id == -1 || !m_color.isValid()) {
0230         return;
0231     }
0232 
0233     if (m_changed && (m_searchType == Okular::Document::NextMatch || m_searchType == Okular::Document::PreviousMatch)) {
0234         m_document->resetSearch(m_id);
0235     }
0236     m_changed = false;
0237     // search text if have more than 3 chars or else clear search
0238     QString thistext = text();
0239     if (thistext.length() >= qMax(m_minLength, 1)) {
0240         Q_EMIT searchStarted();
0241         m_searchRunning = true;
0242         m_document->searchText(m_id, thistext, m_fromStart, m_caseSensitivity, m_searchType, m_moveViewport, m_color);
0243     } else {
0244         m_document->resetSearch(m_id);
0245     }
0246 }
0247 
0248 void SearchLineEdit::searchFinished(int id, Okular::Document::SearchStatus endStatus)
0249 {
0250     // ignore the searches not started by this search edit
0251     if (id != m_id) {
0252         return;
0253     }
0254 
0255     // if not found, use warning colors
0256     if (endStatus == Okular::Document::NoMatchFound) {
0257         QPalette pal = palette();
0258         const KColorScheme scheme(QPalette::Active, KColorScheme::View);
0259         pal.setBrush(QPalette::Base, scheme.background(KColorScheme::NegativeBackground));
0260         pal.setBrush(QPalette::Text, scheme.foreground(KColorScheme::NegativeText));
0261         setPalette(pal);
0262     } else {
0263         QPalette pal = palette();
0264         const QPalette qAppPalette = QApplication::palette();
0265         pal.setColor(QPalette::Base, qAppPalette.color(QPalette::Base));
0266         pal.setColor(QPalette::Text, qAppPalette.color(QPalette::Text));
0267         setPalette(pal);
0268     }
0269 
0270     m_searchRunning = false;
0271     Q_EMIT searchStopped();
0272 }
0273 
0274 SearchLineWidget::SearchLineWidget(QWidget *parent, Okular::Document *document)
0275     : QWidget(parent)
0276 {
0277     QHBoxLayout *layout = new QHBoxLayout(this);
0278     layout->setContentsMargins(0, 0, 0, 0);
0279 
0280     m_edit = new SearchLineEdit(this, document);
0281     layout->addWidget(m_edit);
0282 
0283     m_anim = new KBusyIndicatorWidget(this);
0284     m_anim->setFixedSize(22, 22);
0285     layout->addWidget(m_anim);
0286     m_anim->hide();
0287 
0288     m_timer = new QTimer(this);
0289     m_timer->setSingleShot(true);
0290     connect(m_timer, &QTimer::timeout, this, &SearchLineWidget::slotTimedout);
0291 
0292     connect(m_edit, &SearchLineEdit::searchStarted, this, &SearchLineWidget::slotSearchStarted);
0293     connect(m_edit, &SearchLineEdit::searchStopped, this, &SearchLineWidget::slotSearchStopped);
0294 }
0295 
0296 SearchLineEdit *SearchLineWidget::lineEdit() const
0297 {
0298     return m_edit;
0299 }
0300 
0301 void SearchLineWidget::slotSearchStarted()
0302 {
0303     m_timer->start(100);
0304 }
0305 
0306 void SearchLineWidget::slotSearchStopped()
0307 {
0308     m_timer->stop();
0309     m_anim->hide();
0310 }
0311 
0312 void SearchLineWidget::slotTimedout()
0313 {
0314     m_anim->show();
0315 }
0316 
0317 #include "moc_searchlineedit.cpp"