File indexing completed on 2024-05-05 11:56:21

0001 /*
0002     SPDX-License-Identifier: GPL-2.0-or-later
0003     SPDX-FileCopyrightText: 2012 Martin Kuettler <martin.kuettler@gmail.com>
0004 */
0005 
0006 #include "searchbar.h"
0007 
0008 #include "worksheet.h"
0009 #include "worksheetentry.h"
0010 #include "worksheettextitem.h"
0011 #include "worksheetview.h"
0012 
0013 #include <KLocalizedString>
0014 #include <QIcon>
0015 #include <QMenu>
0016 
0017 SearchBar::SearchBar(QWidget* parent, Worksheet* worksheet) : QWidget(parent),
0018     m_stdUi(new Ui::StandardSearchBar()),
0019     m_worksheet(worksheet),
0020     m_searchFlags{WorksheetEntry::SearchAll}
0021 {
0022     setupStdUi();
0023     setStartCursor(worksheet->worksheetCursor());
0024     setCurrentCursor(m_startCursor);
0025 }
0026 
0027 SearchBar::~SearchBar()
0028 {
0029     if (m_stdUi)
0030         delete m_stdUi;
0031     else
0032         delete m_extUi;
0033     if (m_currentCursor.isValid()) {
0034         worksheet()->worksheetView()->setFocus();
0035         m_currentCursor.entry()->focusEntry();
0036     } else if (m_startCursor.isValid()) {
0037         worksheet()->worksheetView()->setFocus();
0038         m_startCursor.entry()->focusEntry();
0039     }
0040 }
0041 
0042 void SearchBar::showStandard()
0043 {
0044     if (m_stdUi)
0045         return;
0046 
0047     delete m_extUi;
0048     m_extUi = nullptr;
0049     for (auto* child : children())
0050         delete child;
0051 
0052     delete layout();
0053     m_stdUi = new Ui::StandardSearchBar();
0054     setupStdUi();
0055 }
0056 
0057 void SearchBar::showExtended()
0058 {
0059     if (m_extUi)
0060         return;
0061 
0062     delete m_stdUi;
0063     m_stdUi = nullptr;
0064     for (auto* child : children())
0065         delete child;
0066 
0067     delete layout();
0068     m_extUi = new Ui::ExtendedSearchBar();
0069     setupExtUi();
0070 
0071 }
0072 
0073 void SearchBar::next()
0074 {
0075     if (!m_currentCursor.isValid() && !m_currentCursor.entry() && !m_atEnd)
0076         return;
0077     searchForward(true);
0078 }
0079 
0080 void SearchBar::prev()
0081 {
0082     if (!m_currentCursor.isValid() && !m_currentCursor.entry() &&
0083         !m_atBeginning)
0084         return;
0085     searchBackward(true);
0086 }
0087 
0088 void SearchBar::searchBackward(bool skipFirstChar)
0089 {
0090     WorksheetCursor result;
0091     WorksheetEntry* entry;
0092     worksheet()->setWorksheetCursor(WorksheetCursor());
0093     QTextDocument::FindFlags f = m_qtFlags | QTextDocument::FindBackward;
0094     if (m_currentCursor.isValid()) {
0095         bool atBeginningOfEntry = false;
0096         if (skipFirstChar) {
0097             QTextCursor c = m_currentCursor.textCursor();
0098             c.movePosition(QTextCursor::PreviousCharacter);
0099             atBeginningOfEntry = (c == m_currentCursor.textCursor());
0100             setCurrentCursor(WorksheetCursor(m_currentCursor.entry(),
0101                                              m_currentCursor.textItem(), c));
0102         }
0103         if (!atBeginningOfEntry)
0104             result = m_currentCursor.entry()->search(m_pattern, m_searchFlags,
0105                                                  f, m_currentCursor);
0106         entry = m_currentCursor.entry()->previous();
0107     } else if (m_currentCursor.entry() && m_currentCursor.entry()->previous()) {
0108         entry = m_currentCursor.entry()->previous();
0109     } else {
0110         entry = worksheet()->lastEntry();
0111     }
0112     setCurrentCursor(WorksheetCursor());
0113 
0114     while (!result.isValid() && entry) {
0115         result = entry->search(m_pattern, m_searchFlags, f);
0116         entry = entry->previous();
0117     }
0118     if (result.isValid()) {
0119         m_atBeginning = false;
0120         QTextCursor c = result.textCursor();
0121         if (result.textCursor().hasSelection())
0122             c.setPosition(result.textCursor().selectionStart());
0123         setCurrentCursor(WorksheetCursor(result.entry(), result.textItem(), c));
0124         worksheet()->makeVisible(m_currentCursor);
0125         clearStatus();
0126         worksheet()->setWorksheetCursor(result);
0127     } else {
0128         if (m_atBeginning) {
0129             m_notFound = true;
0130             setStatus(i18n("Not found"));
0131         } else {
0132             m_atBeginning = true;
0133             setStatus(i18n("Reached beginning"));
0134         }
0135         worksheet()->setWorksheetCursor(m_startCursor);
0136     }
0137 }
0138 
0139 void SearchBar::searchForward(bool skipFirstChar)
0140 {
0141     WorksheetCursor result;
0142     WorksheetEntry* entry;
0143     worksheet()->setWorksheetCursor(WorksheetCursor());
0144     if (m_currentCursor.isValid()) {
0145         if (skipFirstChar) {
0146             QTextCursor c = m_currentCursor.textCursor();
0147             c.movePosition(QTextCursor::NextCharacter);
0148             setCurrentCursor(WorksheetCursor(m_currentCursor.entry(),
0149                                              m_currentCursor.textItem(), c));
0150         }
0151         result = m_currentCursor.entry()->search(m_pattern, m_searchFlags,
0152                                                  m_qtFlags, m_currentCursor);
0153         entry = m_currentCursor.entry()->next();
0154     } else if (m_currentCursor.entry()) {
0155         entry = m_currentCursor.entry();
0156     } else {
0157         entry = worksheet()->firstEntry();
0158     }
0159     setCurrentCursor(WorksheetCursor());
0160 
0161     while (!result.isValid() && entry) {
0162         result = entry->search(m_pattern, m_searchFlags, m_qtFlags);
0163         entry = entry->next();
0164     }
0165 
0166     if (result.isValid()) {
0167         m_atEnd = false;
0168         QTextCursor c = result.textCursor();
0169         if (result.textCursor().hasSelection())
0170             c.setPosition(result.textCursor().selectionStart());
0171         setCurrentCursor(WorksheetCursor(result.entry(), result.textItem(), c));
0172         worksheet()->makeVisible(m_currentCursor);
0173         clearStatus();
0174         worksheet()->setWorksheetCursor(result);
0175     } else {
0176         if (m_atEnd) {
0177             m_notFound = true;
0178             setStatus(i18n("Not found"));
0179         } else {
0180             m_atEnd = true;
0181             setStatus(i18n("Reached end"));
0182         }
0183         worksheet()->setWorksheetCursor(m_startCursor);
0184     }
0185 }
0186 
0187 void SearchBar::on_close_clicked()
0188 {
0189     deleteLater();
0190 }
0191 
0192 void SearchBar::on_openExtended_clicked()
0193 {
0194     showExtended();
0195 }
0196 
0197 void SearchBar::on_openStandard_clicked()
0198 {
0199     showStandard();
0200 }
0201 
0202 void SearchBar::on_next_clicked()
0203 {
0204     next();
0205 }
0206 
0207 void SearchBar::on_previous_clicked()
0208 {
0209     prev();
0210 }
0211 
0212 void SearchBar::on_replace_clicked()
0213 {
0214     if (!m_currentCursor.isValid())
0215         return;
0216 
0217     QTextCursor cursor = m_currentCursor.textCursor();
0218     cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor,
0219                         m_pattern.length());
0220     cursor.insertText(m_replacement);
0221     next();
0222 }
0223 
0224 void SearchBar::on_replaceAll_clicked()
0225 {
0226     int count = 0;
0227     WorksheetEntry* entry = worksheet()->firstEntry();
0228     WorksheetCursor cursor;
0229     for (; entry; entry = entry->next()) {
0230         cursor = entry->search(m_pattern, m_searchFlags, m_qtFlags);
0231         while (cursor.isValid()) {
0232             cursor.textCursor().insertText(m_replacement);
0233             cursor = entry->search(m_pattern, m_searchFlags, m_qtFlags,
0234                                    cursor);
0235             ++count;
0236         }
0237     }
0238     setStatus(i18np("Replaced %1 instance", "Replaced %1 instances", count));
0239 }
0240 
0241 void SearchBar::on_pattern_textChanged(const QString& p)
0242 {
0243     worksheet()->setWorksheetCursor(WorksheetCursor());
0244     m_atBeginning = m_atEnd = m_notFound = false;
0245     if (!p.startsWith(m_pattern))
0246         setCurrentCursor(m_startCursor);
0247     m_pattern = p;
0248     if (!m_pattern.isEmpty()) {
0249         searchForward();
0250         nextButton()->setEnabled(true);
0251         previousButton()->setEnabled(true);
0252         if (m_extUi) {
0253             m_extUi->replace->setEnabled(true);
0254             m_extUi->replaceAll->setEnabled(true);
0255         }
0256     } else {
0257         worksheet()->setWorksheetCursor(m_startCursor);
0258         nextButton()->setEnabled(false);
0259         previousButton()->setEnabled(false);
0260         if (m_extUi) {
0261             m_extUi->replace->setEnabled(false);
0262             m_extUi->replaceAll->setEnabled(false);
0263         }
0264     }
0265 }
0266 
0267 void SearchBar::on_replacement_textChanged(const QString& r)
0268 {
0269     m_replacement = r;
0270 }
0271 
0272 void SearchBar::on_removeFlag_clicked()
0273 {
0274     QMenu* menu = new QMenu(this);
0275     fillLocationsMenu(menu, m_searchFlags);
0276     connect(menu, SIGNAL("aboutToHide()"), menu, SLOT("deleteLater()"));
0277     menu->exec(mapToGlobal(m_extUi->removeFlag->geometry().topLeft()));
0278 }
0279 
0280 void SearchBar::on_addFlag_clicked()
0281 {
0282     QMenu* menu = new QMenu(this);
0283     fillLocationsMenu(menu, WorksheetEntry::SearchAll ^ m_searchFlags);
0284     connect(menu, SIGNAL("aboutToHide()"), menu, SLOT("deleteLater()"));
0285     menu->exec(mapToGlobal(m_extUi->addFlag->geometry().topLeft()));
0286 }
0287 
0288 void SearchBar::invalidateStartCursor()
0289 {
0290     if (!m_startCursor.isValid())
0291         return;
0292 
0293     WorksheetEntry* entry = m_startCursor.entry()->next();
0294     if (!entry && worksheet()->firstEntry() != m_startCursor.entry())
0295         entry = worksheet()->firstEntry();
0296 
0297     setStartCursor(WorksheetCursor(entry, nullptr, QTextCursor()));
0298 }
0299 
0300 void SearchBar::invalidateCurrentCursor()
0301 {
0302     if (!m_currentCursor.isValid())
0303         return;
0304 
0305     WorksheetEntry* entry = m_currentCursor.entry()->next();
0306     if (!entry)
0307         entry = worksheet()->firstEntry();
0308 
0309     setCurrentCursor(WorksheetCursor(entry, nullptr, QTextCursor()));
0310 }
0311 
0312 void SearchBar::toggleFlag()
0313 {
0314     if (!sender())
0315         return;
0316     int flag = sender()->property("searchFlag").toInt();
0317     m_searchFlags ^= flag;
0318     updateSearchLocations();
0319 }
0320 
0321 void SearchBar::on_matchCase_toggled(bool b)
0322 {
0323     m_qtFlags &= ~QTextDocument::FindCaseSensitively;
0324     if (b)
0325         m_qtFlags |= QTextDocument::FindCaseSensitively;
0326     searchForward();
0327 }
0328 
0329 void SearchBar::updateSearchLocations()
0330 {
0331     static QList<QString> names;
0332     if (names.empty())
0333         names << i18n("Commands") << i18n("Results") << i18n("Errors")
0334               << i18n("Text") << i18n("LaTeX Code");
0335 
0336     QString text = QLatin1String("");
0337     int flag = 1;
0338     for (int i = 0; flag < WorksheetEntry::SearchAll; flag = (1<<(++i))) {
0339         if (m_searchFlags & flag) {
0340             if (!text.isEmpty())
0341                 text += QLatin1String(", ");
0342             text += names.at(i);
0343         }
0344     }
0345     m_extUi->searchFlagsList->setText(text);
0346     if (m_searchFlags == 0) {
0347         m_extUi->removeFlag->setEnabled(false);
0348         m_extUi->addFlag->setEnabled(true);
0349     } else if (m_searchFlags == WorksheetEntry::SearchAll) {
0350         m_extUi->removeFlag->setEnabled(true);
0351         m_extUi->addFlag->setEnabled(false);
0352     } else {
0353         m_extUi->addFlag->setEnabled(true);
0354         m_extUi->removeFlag->setEnabled(true);
0355     }
0356 }
0357 
0358 void SearchBar::fillLocationsMenu(QMenu* menu, int flags)
0359 {
0360     static QList<QString> names;
0361     if (names.empty())
0362         names << i18n("Commands") << i18n("Results") << i18n("Errors")
0363               << i18n("Text") << i18n("LaTeX Code");
0364     int flag = 1;
0365     for (int i = 0; flag < WorksheetEntry::SearchAll; flag = (1<<(++i))) {
0366         if (flags & flag) {
0367             QAction* a = menu->addAction(names.at(i), this, SLOT(toggleFlag()));
0368             a->setProperty("searchFlag", flag);
0369         }
0370     }
0371 }
0372 
0373 void SearchBar::setStartCursor(WorksheetCursor cursor)
0374 {
0375     if (m_startCursor.entry())
0376         disconnect(m_startCursor.entry(), SIGNAL(aboutToBeDeleted()), this,
0377                    SLOT(invalidateStartCursor()));
0378     if (cursor.entry())
0379         connect(cursor.entry(), SIGNAL(aboutToBeDeleted()), this,
0380                 SLOT(invalidateStartCursor()), Qt::DirectConnection);
0381     m_startCursor = cursor;
0382 }
0383 
0384 void SearchBar::setCurrentCursor(WorksheetCursor cursor)
0385 {
0386     if (m_currentCursor.entry())
0387         disconnect(m_currentCursor.entry(), SIGNAL(aboutToBeDeleted()), this,
0388                    SLOT(invalidateCurrentCursor()));
0389     if (cursor.entry())
0390         connect(cursor.entry(), SIGNAL(aboutToBeDeleted()), this,
0391                 SLOT(invalidateCurrentCursor()), Qt::DirectConnection);
0392     m_currentCursor = cursor;
0393 }
0394 
0395 void SearchBar::setStatus(QString message)
0396 {
0397     KSqueezedTextLabel* status;
0398     if (m_stdUi)
0399         status = m_stdUi->status;
0400     else
0401         status = m_extUi->status;
0402 
0403     status->setText(message);
0404 }
0405 
0406 void SearchBar::clearStatus()
0407 {
0408     setStatus(QLatin1String(""));
0409 }
0410 
0411 void SearchBar::setupStdUi()
0412 {
0413     if (!m_stdUi)
0414         return;
0415 
0416     m_stdUi->setupUi(this);
0417     m_stdUi->close->setIcon(QIcon::fromTheme(QLatin1String("dialog-close")));
0418     m_stdUi->openExtended->setIcon(QIcon::fromTheme(QLatin1String("arrow-up-double")));
0419     m_stdUi->pattern->setText(m_pattern);
0420     m_stdUi->matchCase->setChecked(m_qtFlags & QTextDocument::FindCaseSensitively);
0421     m_stdUi->next->setIcon(QIcon::fromTheme(QLatin1String("go-down-search")));
0422     m_stdUi->previous->setIcon(QIcon::fromTheme(QLatin1String("go-up-search")));
0423     if (m_pattern.isEmpty()) {
0424         m_stdUi->next->setEnabled(false);
0425         m_stdUi->previous->setEnabled(false);
0426     }
0427 
0428     m_stdUi->close->setShortcut(Qt::Key_Escape);
0429     setFocusProxy(m_stdUi->pattern);
0430 }
0431 
0432 void SearchBar::setupExtUi()
0433 {
0434     if (!m_extUi)
0435         return;
0436 
0437     m_extUi->setupUi(this);
0438     m_extUi->close->setIcon(QIcon::fromTheme(QLatin1String("dialog-close")));
0439     m_extUi->openStandard->setIcon(QIcon::fromTheme(QLatin1String("arrow-down-double")));
0440     m_extUi->pattern->setText(m_pattern);
0441     m_extUi->replacement->setText(m_replacement);
0442     m_extUi->matchCase->setChecked(m_qtFlags & QTextDocument::FindCaseSensitively);
0443     m_extUi->next->setIcon(QIcon::fromTheme(QLatin1String("go-down-search")));
0444     m_extUi->previous->setIcon(QIcon::fromTheme(QLatin1String("go-up-search")));
0445     if (m_pattern.isEmpty()) {
0446         m_extUi->next->setEnabled(false);
0447         m_extUi->previous->setEnabled(false);
0448         m_extUi->replace->setEnabled(false);
0449         m_extUi->replaceAll->setEnabled(false);
0450     }
0451 
0452     m_extUi->addFlag->setIcon(QIcon::fromTheme(QLatin1String("list-add")));
0453     m_extUi->removeFlag->setIcon(QIcon::fromTheme(QLatin1String("list-remove")));
0454 
0455     m_extUi->close->setShortcut(Qt::Key_Escape);
0456     setFocusProxy(m_extUi->pattern);
0457     updateSearchLocations();
0458 }
0459 
0460 QPushButton* SearchBar::previousButton()
0461 {
0462     if (m_stdUi)
0463         return m_stdUi->previous;
0464     else
0465         return m_extUi->previous;
0466 }
0467 
0468 QPushButton* SearchBar::nextButton()
0469 {
0470     if (m_stdUi)
0471         return m_stdUi->next;
0472     else
0473         return m_extUi->next;
0474 }
0475 
0476 Worksheet* SearchBar::worksheet()
0477 {
0478     return m_worksheet;
0479 }