File indexing completed on 2025-01-05 03:35:43

0001 /*
0002     File                 : SearchReplaceWidget.cpp
0003     Project              : LabPlot
0004     Description          : Search&Replace widget for the spreadsheet
0005     --------------------------------------------------------------------
0006     SPDX-FileCopyrightText: 2023 Alexander Semke <alexander.semke@web.de>
0007 
0008     SPDX-License-Identifier: GPL-2.0-or-later
0009 */
0010 
0011 #include "SearchReplaceWidget.h"
0012 #include "backend/core/Settings.h"
0013 #include "backend/spreadsheet/Spreadsheet.h"
0014 #include "backend/spreadsheet/SpreadsheetModel.h"
0015 #include "commonfrontend/spreadsheet/SpreadsheetView.h"
0016 #include "kdefrontend/GuiTools.h"
0017 
0018 #include <KConfigGroup>
0019 #include <KMessageWidget>
0020 
0021 #include <QLineEdit>
0022 #include <QMenu>
0023 #include <QRadioButton>
0024 #include <QStack>
0025 
0026 // TODO: use the current default datetime format or always enforce the same format/behavior?
0027 static const QString defaultDateTimeFormat = QStringLiteral("yyyy-MM-dd hh:mm:ss.zzz");
0028 
0029 SearchReplaceWidget::SearchReplaceWidget(Spreadsheet* spreadsheet, QWidget* parent)
0030     : QWidget(parent)
0031     , m_spreadsheet(spreadsheet) {
0032     m_view = static_cast<SpreadsheetView*>(spreadsheet->view());
0033 
0034     auto* layout = new QVBoxLayout(this);
0035     this->setLayout(layout);
0036     QSizePolicy sizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed);
0037     this->setSizePolicy(sizePolicy);
0038 }
0039 
0040 SearchReplaceWidget::~SearchReplaceWidget() {
0041     // save the current settings,
0042     // save everything except of the patterns, they will be set when the widget is opened again
0043     KConfigGroup conf = Settings::group(QLatin1String("SearchReplaceWidget"));
0044 
0045     if (m_searchWidget) {
0046         conf.writeEntry("SimpleMatchCase", uiSearch.tbMatchCase->isChecked());
0047 
0048         // history for the text value
0049         QStringList items;
0050         for (int i = 0; i < uiSearch.cbFind->count(); ++i)
0051             items << uiSearch.cbFind->itemText(i);
0052 
0053         if (!items.empty())
0054             conf.writeEntry("SimpleValueHistory", items);
0055     }
0056 
0057     if (m_searchReplaceWidget) {
0058         conf.writeEntry("DataType", uiSearchReplace.cbDataType->currentData().toInt());
0059         conf.writeEntry("Order", uiSearchReplace.cbOrder->currentData().toInt());
0060         conf.writeEntry("MatchCase", uiSearchReplace.tbMatchCase->isChecked());
0061         conf.writeEntry("SelectionOnly", uiSearchReplace.tbSelectionOnly->isChecked());
0062         conf.writeEntry("Operator", uiSearchReplace.cbOperator->currentData().toInt());
0063         conf.writeEntry("OperatorText", uiSearchReplace.cbOperatorText->currentData().toInt());
0064         conf.writeEntry("OperatorDateTime", uiSearchReplace.cbOperatorDateTime->currentData().toInt());
0065 
0066         // history for the first numerical value
0067         QStringList items;
0068         for (int i = 0; i < uiSearchReplace.cbValue1->count(); ++i)
0069             items << uiSearchReplace.cbValue1->itemText(i);
0070 
0071         if (!items.empty()) {
0072             conf.writeEntry("Value1History", items);
0073             items.clear();
0074         }
0075 
0076         // history for the second numerical value
0077         for (int i = 0; i < uiSearchReplace.cbValue2->count(); ++i)
0078             items << uiSearchReplace.cbValue2->itemText(i);
0079 
0080         if (!items.empty()) {
0081             conf.writeEntry("Value2History", items);
0082             items.clear();
0083         }
0084 
0085         // history for the text value
0086         for (int i = 0; i < uiSearchReplace.cbValueText->count(); ++i)
0087             items << uiSearchReplace.cbValueText->itemText(i);
0088 
0089         if (!items.empty()) {
0090             conf.writeEntry("ValueTextHistory", items);
0091             items.clear();
0092         }
0093 
0094         // history for replace numeric values
0095         for (int i = 0; i < uiSearchReplace.cbReplace->count(); ++i)
0096             items << uiSearchReplace.cbReplace->itemText(i);
0097 
0098         if (!items.empty()) {
0099             conf.writeEntry("ReplaceHistory", items);
0100             items.clear();
0101         }
0102 
0103         // history for replace numeric values
0104         for (int i = 0; i < uiSearchReplace.cbReplace->count(); ++i)
0105             items << uiSearchReplace.cbReplace->itemText(i);
0106 
0107         if (!items.empty())
0108             conf.writeEntry("ReplaceTextHistory", items);
0109 
0110         // history for replace datetime value
0111         conf.writeEntry("ReplaceDateTimeHistory", uiSearchReplace.dteReplace->dateTime());
0112     }
0113 }
0114 
0115 void SearchReplaceWidget::setReplaceEnabled(bool enabled) {
0116     m_replaceEnabled = !enabled;
0117     switchFindReplace();
0118 }
0119 
0120 void SearchReplaceWidget::setInitialPattern(AbstractColumn::ColumnMode mode, const QString& pattern) {
0121     m_initialColumnMode = mode;
0122 
0123     if (m_searchWidget)
0124         uiSearch.cbFind->setCurrentText(pattern);
0125     else if (m_searchReplaceWidget) {
0126         switch (m_initialColumnMode) {
0127         case AbstractColumn::ColumnMode::Text:
0128             uiSearchReplace.cbDataType->setCurrentIndex(uiSearchReplace.cbDataType->findData((int)DataType::Text));
0129             uiSearchReplace.cbValueText->setCurrentText(pattern);
0130             break;
0131         case AbstractColumn::ColumnMode::Double:
0132         case AbstractColumn::ColumnMode::Integer:
0133         case AbstractColumn::ColumnMode::BigInt:
0134             uiSearchReplace.cbDataType->setCurrentIndex(uiSearchReplace.cbDataType->findData((int)DataType::Numeric));
0135             ;
0136             bool ok;
0137             QLocale().toDouble(pattern, &ok);
0138             if (ok)
0139                 uiSearchReplace.cbValue1->setCurrentText(pattern);
0140             else
0141                 uiSearchReplace.cbValue1->setCurrentText(QString());
0142             break;
0143         case AbstractColumn::ColumnMode::DateTime:
0144         case AbstractColumn::ColumnMode::Day:
0145         case AbstractColumn::ColumnMode::Month:
0146             uiSearchReplace.cbDataType->setCurrentIndex(uiSearchReplace.cbDataType->findData((int)DataType::DateTime));
0147             auto value = QDateTime::fromString(pattern, defaultDateTimeFormat);
0148             if (value.isValid())
0149                 uiSearchReplace.dteValue1->setDateTime(value);
0150             else
0151                 uiSearchReplace.dteValue1->setDateTime(QDateTime::currentDateTime());
0152             break;
0153         }
0154     }
0155 }
0156 
0157 void SearchReplaceWidget::setFocus() {
0158     if (m_replaceEnabled)
0159         uiSearchReplace.cbValueText->setFocus();
0160     else
0161         uiSearch.cbFind->setFocus();
0162 }
0163 
0164 void SearchReplaceWidget::initSearchWidget() {
0165     m_searchWidget = new QWidget(this);
0166     uiSearch.setupUi(m_searchWidget);
0167     uiSearch.cbFind->lineEdit()->setClearButtonEnabled(true);
0168     static_cast<QVBoxLayout*>(layout())->insertWidget(0, m_searchWidget);
0169 
0170     // restore saved settings if available
0171     KConfigGroup conf = Settings::group(QLatin1String("SearchReplaceWidget"));
0172     uiSearch.cbFind->addItems(conf.readEntry(QLatin1String("SimpleValueHistory"), QStringList()));
0173     uiSearch.cbFind->setCurrentText(QString()); // will be set to the initial search pattern later
0174     uiSearch.tbMatchCase->setChecked(conf.readEntry(QLatin1String("SimpleMatchCase"), false));
0175 
0176     // connections
0177     connect(uiSearch.cbFind->lineEdit(), &QLineEdit::returnPressed, this, [=]() {
0178         findNextSimple(true);
0179         addCurrentTextToHistory(uiSearch.cbFind);
0180     });
0181     connect(uiSearch.cbFind->lineEdit(), &QLineEdit::textChanged, this, [=]() {
0182         m_patternFound = false;
0183         findNextSimple(false);
0184     });
0185 
0186     connect(uiSearch.tbFindNext, &QToolButton::clicked, this, [=]() {
0187         findNextSimple(true);
0188         addCurrentTextToHistory(uiSearch.cbFind);
0189     });
0190     connect(uiSearch.tbFindPrev, &QToolButton::clicked, this, [=]() {
0191         findPreviousSimple(true);
0192         addCurrentTextToHistory(uiSearch.cbFind);
0193     });
0194 
0195     connect(uiSearch.tbMatchCase, &QToolButton::toggled, this, [=]() {
0196         findNextSimple(false);
0197     });
0198     connect(uiSearch.tbSwitchFindReplace, &QToolButton::clicked, this, &SearchReplaceWidget::switchFindReplace);
0199     connect(uiSearch.bCancel, &QPushButton::clicked, this, &SearchReplaceWidget::cancel);
0200 }
0201 
0202 void SearchReplaceWidget::initSearchReplaceWidget() {
0203     // init UI
0204     m_searchReplaceWidget = new QWidget(this);
0205     uiSearchReplace.setupUi(m_searchReplaceWidget);
0206     static_cast<QVBoxLayout*>(layout())->insertWidget(1, m_searchReplaceWidget);
0207 
0208     uiSearchReplace.cbDataType->addItem(i18n("Text"), int(DataType::Text));
0209     uiSearchReplace.cbDataType->addItem(i18n("Numeric"), int(DataType::Numeric));
0210     uiSearchReplace.cbDataType->addItem(i18n("Date & Time"), int(DataType::DateTime));
0211 
0212     uiSearchReplace.cbOperatorText->addItem(i18n("Equal To"), int(OperatorText::EqualTo));
0213     uiSearchReplace.cbOperatorText->addItem(i18n("Not Equal To"), int(OperatorText::NotEqualTo));
0214     uiSearchReplace.cbOperatorText->addItem(i18n("Starts With"), int(OperatorText::StartsWith));
0215     uiSearchReplace.cbOperatorText->addItem(i18n("Ends With"), int(OperatorText::EndsWith));
0216     uiSearchReplace.cbOperatorText->addItem(i18n("Contains"), int(OperatorText::Contain));
0217     uiSearchReplace.cbOperatorText->addItem(i18n("Does Not Contain"), int(OperatorText::NotContain));
0218     uiSearchReplace.cbOperatorText->insertSeparator(6);
0219     uiSearchReplace.cbOperatorText->addItem(i18n("Regular Expression"), int(OperatorText::RegEx));
0220 
0221     uiSearchReplace.cbOperator->addItem(i18n("Equal to"), int(Operator::EqualTo));
0222     uiSearchReplace.cbOperator->addItem(i18n("Not Equal to"), int(Operator::NotEqualTo));
0223     uiSearchReplace.cbOperator->addItem(i18n("Between (Incl. End Points)"), int(Operator::BetweenIncl));
0224     uiSearchReplace.cbOperator->addItem(i18n("Between (Excl. End Points)"), int(Operator::BetweenExcl));
0225     uiSearchReplace.cbOperator->addItem(i18n("Greater than"), int(Operator::GreaterThan));
0226     uiSearchReplace.cbOperator->addItem(i18n("Greater than or Equal to"), int(Operator::GreaterThanEqualTo));
0227     uiSearchReplace.cbOperator->addItem(i18n("Less than"), int(Operator::LessThan));
0228     uiSearchReplace.cbOperator->addItem(i18n("Less than or Equal to"), int(Operator::LessThanEqualTo));
0229 
0230     uiSearchReplace.cbOperatorDateTime->addItem(i18n("Equal to"), int(Operator::EqualTo));
0231     uiSearchReplace.cbOperatorDateTime->addItem(i18n("Not Equal to"), int(Operator::NotEqualTo));
0232     uiSearchReplace.cbOperatorDateTime->addItem(i18n("Between (Incl. End Points)"), int(Operator::BetweenIncl));
0233     uiSearchReplace.cbOperatorDateTime->addItem(i18n("Between (Excl. End Points)"), int(Operator::BetweenExcl));
0234     uiSearchReplace.cbOperatorDateTime->addItem(i18n("Greater than"), int(Operator::GreaterThan));
0235     uiSearchReplace.cbOperatorDateTime->addItem(i18n("Greater than or Equal to"), int(Operator::GreaterThanEqualTo));
0236     uiSearchReplace.cbOperatorDateTime->addItem(i18n("Less than"), int(Operator::LessThan));
0237     uiSearchReplace.cbOperatorDateTime->addItem(i18n("Less than or Equal to"), int(Operator::LessThanEqualTo));
0238 
0239     uiSearchReplace.cbValueText->lineEdit()->setClearButtonEnabled(true);
0240     uiSearchReplace.cbValue1->lineEdit()->setClearButtonEnabled(true);
0241     uiSearchReplace.cbValue2->lineEdit()->setClearButtonEnabled(true);
0242 
0243     uiSearchReplace.cbValue1->lineEdit()->setValidator(new QDoubleValidator(uiSearchReplace.cbValue1->lineEdit()));
0244     uiSearchReplace.cbValue2->lineEdit()->setValidator(new QDoubleValidator(uiSearchReplace.cbValue2->lineEdit()));
0245 
0246     uiSearchReplace.cbOrder->addItem(i18n("Column Major"), int(Order::ColumnMajor));
0247     uiSearchReplace.cbOrder->addItem(i18n("Row Major"), int(Order::RowMajor));
0248 
0249     // set meaninungful non-empty initial value for DateTime so the user doesn't need to type
0250     // everything from scratch when switching to DateTime type
0251     auto now = QDateTime::currentDateTime();
0252     uiSearchReplace.dteValue1->setDateTime(now);
0253     uiSearchReplace.dteValue2->setDateTime(now);
0254 
0255     uiSearchReplace.dteValue1->setDisplayFormat(defaultDateTimeFormat);
0256     uiSearchReplace.dteValue2->setDisplayFormat(defaultDateTimeFormat);
0257     uiSearchReplace.dteReplace->setDisplayFormat(defaultDateTimeFormat);
0258 
0259     // restore saved settings if available
0260     KConfigGroup conf = Settings::group(QLatin1String("SearchReplaceWidget"));
0261     uiSearchReplace.cbDataType->setCurrentIndex(uiSearchReplace.cbOperator->findData(conf.readEntry("DataType", 0)));
0262     uiSearchReplace.cbOrder->setCurrentIndex(uiSearchReplace.cbOperator->findData(conf.readEntry("Order", 0)));
0263     uiSearchReplace.tbMatchCase->setChecked(conf.readEntry("MatchCase", false));
0264     uiSearchReplace.tbSelectionOnly->setChecked(conf.readEntry("SelectionOnly", false));
0265     uiSearchReplace.cbOperator->setCurrentIndex(uiSearchReplace.cbOperator->findData(conf.readEntry("Operator", 0)));
0266     uiSearchReplace.cbOperatorText->setCurrentIndex(uiSearchReplace.cbOperatorText->findData(conf.readEntry("OperatorText", 0)));
0267     uiSearchReplace.cbOperatorDateTime->setCurrentIndex(uiSearchReplace.cbOperatorDateTime->findData(conf.readEntry("OperatorDateTime", 0)));
0268 
0269     dataTypeChanged(uiSearchReplace.cbDataType->currentIndex());
0270     operatorChanged(uiSearchReplace.cbOperator->currentIndex());
0271     operatorDateTimeChanged(uiSearchReplace.cbOperatorDateTime->currentIndex());
0272 
0273     // history
0274     uiSearchReplace.cbValue1->addItems(conf.readEntry("Value1History", QStringList()));
0275     uiSearchReplace.cbValue1->setCurrentText(QString()); // will be set to the initial search pattern later
0276     uiSearchReplace.cbValue2->addItems(conf.readEntry("Value2History", QStringList()));
0277     uiSearchReplace.cbValue2->setCurrentText(QString());
0278     uiSearchReplace.cbValueText->addItems(conf.readEntry("ValueTextHistory", QStringList()));
0279     uiSearchReplace.cbValueText->setCurrentText(QString());
0280     uiSearchReplace.cbReplace->addItems(conf.readEntry("ReplaceHistory", QStringList()));
0281     uiSearchReplace.cbReplace->setCurrentText(QString());
0282     uiSearchReplace.cbReplaceText->addItems(conf.readEntry("ReplaceTextHistory", QStringList()));
0283     uiSearchReplace.cbReplaceText->setCurrentText(QString());
0284     uiSearchReplace.dteReplace->setDateTime(conf.readEntry("ReplaceDateTimeHistory", QDateTime::currentDateTime()));
0285 
0286     // connections
0287     connect(uiSearchReplace.cbDataType, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &SearchReplaceWidget::dataTypeChanged);
0288 
0289     connect(uiSearchReplace.cbOperator, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &SearchReplaceWidget::operatorChanged);
0290     connect(uiSearchReplace.cbOperatorText, QOverload<int>::of(&QComboBox::currentIndexChanged), this, [=]() {
0291         findNext(true);
0292     });
0293     connect(uiSearchReplace.cbOperatorDateTime, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &SearchReplaceWidget::operatorDateTimeChanged);
0294 
0295     connect(uiSearchReplace.cbValue1->lineEdit(), &QLineEdit::returnPressed, this, [=]() {
0296         findNext(true);
0297     });
0298     connect(uiSearchReplace.cbValue2->lineEdit(), &QLineEdit::returnPressed, this, [=]() {
0299         findNext(true);
0300     });
0301     connect(uiSearchReplace.cbValueText->lineEdit(), &QLineEdit::returnPressed, this, [=]() {
0302         findNext(true);
0303     });
0304     connect(uiSearchReplace.dteValue1, &QDateTimeEdit::dateTimeChanged, this, [=]() {
0305         m_patternFound = false;
0306         findNext(false);
0307     });
0308     connect(uiSearchReplace.dteValue2, &QDateTimeEdit::dateTimeChanged, this, [=]() {
0309         m_patternFound = false;
0310         findNext(false);
0311     });
0312     connect(uiSearchReplace.tbFindNext, &QToolButton::clicked, this, [=]() {
0313         findNext(true);
0314     });
0315     connect(uiSearchReplace.tbFindPrev, &QToolButton::clicked, this, [=]() {
0316         findPrevious(true);
0317     });
0318     connect(uiSearchReplace.bFindAll, &QPushButton::clicked, this, &SearchReplaceWidget::findAll);
0319 
0320     connect(uiSearchReplace.cbReplace->lineEdit(), &QLineEdit::returnPressed, this, &SearchReplaceWidget::replaceNext);
0321     connect(uiSearchReplace.bReplaceNext, &QPushButton::clicked, this, &SearchReplaceWidget::replaceNext);
0322     connect(uiSearchReplace.bReplaceAll, &QPushButton::clicked, this, &SearchReplaceWidget::replaceAll);
0323     connect(uiSearchReplace.tbMatchCase, &QToolButton::toggled, this, [=]() {
0324         findNext(false);
0325     });
0326 
0327     connect(uiSearchReplace.tbSwitchFindReplace, &QToolButton::clicked, this, &SearchReplaceWidget::switchFindReplace);
0328     connect(uiSearchReplace.bCancel, &QPushButton::clicked, this, &SearchReplaceWidget::cancel);
0329 
0330     // custom context menus for LineEdit in ComboBox
0331     uiSearchReplace.cbValueText->setContextMenuPolicy(Qt::CustomContextMenu);
0332     connect(uiSearchReplace.cbValueText,
0333             &QComboBox::customContextMenuRequested,
0334             this,
0335             QOverload<const QPoint&>::of(&SearchReplaceWidget::findContextMenuRequest));
0336 
0337     uiSearchReplace.cbReplaceText->setContextMenuPolicy(Qt::CustomContextMenu);
0338     connect(uiSearchReplace.cbReplaceText,
0339             &QComboBox::customContextMenuRequested,
0340             this,
0341             QOverload<const QPoint&>::of(&SearchReplaceWidget::replaceContextMenuRequest));
0342 }
0343 
0344 void SearchReplaceWidget::addCurrentTextToHistory(QComboBox* comboBox) const {
0345     const QString& text = comboBox->currentText();
0346     if (text.isEmpty())
0347         return;
0348 
0349     const int index = comboBox->findText(text);
0350 
0351     if (index > 0)
0352         comboBox->removeItem(index);
0353 
0354     if (index != 0) {
0355         comboBox->insertItem(0, text);
0356         comboBox->setCurrentIndex(0);
0357     }
0358 }
0359 
0360 void SearchReplaceWidget::setDataType(DataType dataType) {
0361     uiSearchReplace.cbDataType->setCurrentIndex(uiSearchReplace.cbDataType->findData((int)dataType));
0362 }
0363 
0364 void SearchReplaceWidget::setOrder(Order order) {
0365     uiSearchReplace.cbOrder->setCurrentIndex(uiSearchReplace.cbOrder->findData((int)order));
0366 }
0367 
0368 void SearchReplaceWidget::setTextOperator(OperatorText op) {
0369     uiSearchReplace.cbOperatorText->setCurrentIndex(uiSearchReplace.cbOperatorText->findData((int)op));
0370 }
0371 
0372 void SearchReplaceWidget::setReplaceText(const QString& text) {
0373     switch (m_initialColumnMode) {
0374     case AbstractColumn::ColumnMode::Text:
0375         uiSearchReplace.cbReplaceText->setCurrentText(text);
0376         break;
0377     case AbstractColumn::ColumnMode::Double:
0378     case AbstractColumn::ColumnMode::Integer:
0379     case AbstractColumn::ColumnMode::BigInt:
0380         uiSearchReplace.cbReplace->setCurrentText(text);
0381         break;
0382     case AbstractColumn::ColumnMode::DateTime:
0383     case AbstractColumn::ColumnMode::Day:
0384     case AbstractColumn::ColumnMode::Month:
0385         uiSearchReplace.dteReplace->setDateTime(QDateTime::fromString(text, defaultDateTimeFormat));
0386         break;
0387     }
0388 
0389     uiSearchReplace.cbReplace->setCurrentText(text);
0390 }
0391 
0392 // **********************************************************
0393 // ************************* SLOTs **************************
0394 // **********************************************************
0395 void SearchReplaceWidget::cancel() {
0396     m_spreadsheet->model()->setSearchText(QString()); // clear the global search text that was potentialy set during "find all"
0397     showMessage(QString());
0398     close();
0399 }
0400 
0401 void SearchReplaceWidget::findContextMenuRequest(const QPoint& pos) {
0402     showExtendedContextMenu(false /* replace */, pos);
0403 }
0404 
0405 void SearchReplaceWidget::replaceContextMenuRequest(const QPoint& pos) {
0406     showExtendedContextMenu(true /* replace */, pos);
0407 }
0408 
0409 void SearchReplaceWidget::dataTypeChanged(int index) {
0410     const auto type = static_cast<DataType>(index);
0411     switch (type) {
0412     case DataType::Text: {
0413         // show text
0414         uiSearchReplace.frameText->show();
0415         uiSearchReplace.lReplaceText->show();
0416         uiSearchReplace.cbReplaceText->show();
0417         uiSearchReplace.cbReplaceText->lineEdit()->setValidator(nullptr);
0418         uiSearchReplace.tbMatchCase->show();
0419 
0420         // hide numeric
0421         uiSearchReplace.frameNumeric->hide();
0422         uiSearchReplace.lReplace->hide();
0423         uiSearchReplace.cbReplace->hide();
0424 
0425         // hide datetime
0426         uiSearchReplace.frameDateTime->hide();
0427         uiSearchReplace.lReplaceDateTime->hide();
0428         uiSearchReplace.dteReplace->hide();
0429 
0430         break;
0431     }
0432     case DataType::Numeric: {
0433         // show numeric
0434         uiSearchReplace.frameNumeric->show();
0435         uiSearchReplace.lReplace->show();
0436         uiSearchReplace.cbReplace->show();
0437 
0438         // hide text
0439         uiSearchReplace.frameText->hide();
0440         uiSearchReplace.lReplaceText->hide();
0441         uiSearchReplace.cbReplaceText->hide();
0442         uiSearchReplace.tbMatchCase->hide();
0443 
0444         // hide datetime
0445         uiSearchReplace.frameDateTime->hide();
0446         uiSearchReplace.lReplaceDateTime->hide();
0447         uiSearchReplace.dteReplace->hide();
0448 
0449         // clear the replace pattern if it doesn't represent a numeric value
0450         const auto& pattern = uiSearchReplace.cbReplace->currentText();
0451         bool ok;
0452         QLocale().toDouble(pattern, &ok);
0453         if (!ok)
0454             uiSearchReplace.cbReplace->setCurrentText(QString());
0455         uiSearchReplace.cbReplace->lineEdit()->setValidator(new QDoubleValidator(uiSearchReplace.cbReplace->lineEdit()));
0456         break;
0457     }
0458     case DataType::DateTime: {
0459         // show datetime
0460         uiSearchReplace.frameDateTime->show();
0461         uiSearchReplace.lReplaceDateTime->show();
0462         uiSearchReplace.dteReplace->show();
0463 
0464         // hide text
0465         uiSearchReplace.frameText->hide();
0466         uiSearchReplace.lReplaceText->hide();
0467         uiSearchReplace.cbReplaceText->hide();
0468         uiSearchReplace.tbMatchCase->hide();
0469 
0470         // hide numeric
0471         uiSearchReplace.frameNumeric->hide();
0472         uiSearchReplace.lReplace->hide();
0473         uiSearchReplace.cbReplace->hide();
0474 
0475         // clear the replace pattern if it doesn't represent a datetime value
0476         const auto& pattern = uiSearchReplace.cbReplace->currentText();
0477         QDateTime value = QDateTime::fromString(pattern, defaultDateTimeFormat);
0478         if (!value.isValid())
0479             uiSearchReplace.cbReplace->setCurrentText(QString());
0480 
0481         break;
0482     }
0483     }
0484 }
0485 
0486 void SearchReplaceWidget::operatorChanged(int /* index */) const {
0487     const auto op = static_cast<Operator>(uiSearchReplace.cbOperator->currentData().toInt());
0488     bool visible = (op == Operator::BetweenIncl) || (op == Operator::BetweenExcl);
0489 
0490     uiSearchReplace.lMin->setVisible(visible);
0491     uiSearchReplace.lMax->setVisible(visible);
0492     uiSearchReplace.lAnd->setVisible(visible);
0493     uiSearchReplace.cbValue2->setVisible(visible);
0494 }
0495 
0496 void SearchReplaceWidget::operatorDateTimeChanged(int /* index */) const {
0497     const auto op = static_cast<Operator>(uiSearchReplace.cbOperatorDateTime->currentData().toInt());
0498     bool visible = (op == Operator::BetweenIncl) || (op == Operator::BetweenExcl);
0499 
0500     uiSearchReplace.lMinDateTime->setVisible(visible);
0501     uiSearchReplace.lMaxDateTime->setVisible(visible);
0502     uiSearchReplace.lAndDateTime->setVisible(visible);
0503     uiSearchReplace.dteValue2->setVisible(visible);
0504 }
0505 
0506 // settings
0507 void SearchReplaceWidget::switchFindReplace() {
0508     m_replaceEnabled = !m_replaceEnabled;
0509     if (m_replaceEnabled) { // show the find&replace widget
0510         if (!m_searchReplaceWidget)
0511             initSearchReplaceWidget();
0512 
0513         m_searchReplaceWidget->show();
0514 
0515         // make the first column in the different QFrames having the same width
0516         // TODO: doesn't work on first open
0517         uiSearchReplace.cbDataType->setMinimumWidth(uiSearchReplace.cbOperator->width());
0518         uiSearchReplace.cbOrder->setMinimumWidth(uiSearchReplace.cbOperator->width());
0519         uiSearchReplace.cbOperatorText->setMinimumWidth(uiSearchReplace.cbOperator->width());
0520         uiSearchReplace.cbOperatorDateTime->setMinimumWidth(uiSearchReplace.cbOperator->width());
0521 
0522         if (m_searchWidget) {
0523             // switching from simple to advanced search,
0524             // take over the current search pattern it it's valid for the current data type
0525             const auto type = static_cast<DataType>(uiSearchReplace.cbDataType->currentIndex());
0526             const auto& pattern = uiSearch.cbFind->currentText();
0527             switch (type) {
0528             case DataType::Text: {
0529                 uiSearchReplace.cbValueText->setCurrentText(pattern);
0530                 break;
0531             }
0532             case DataType::Numeric: {
0533                 bool ok;
0534                 QLocale().toDouble(pattern, &ok);
0535                 if (ok)
0536                     uiSearchReplace.cbValue1->setCurrentText(pattern);
0537                 else
0538                     uiSearchReplace.cbValue1->setCurrentText(QString());
0539                 break;
0540             }
0541             case DataType::DateTime: {
0542                 QDateTime value = QDateTime::fromString(pattern, defaultDateTimeFormat);
0543                 if (value.isValid())
0544                     uiSearchReplace.dteValue1->setDateTime(value);
0545                 else
0546                     uiSearchReplace.dteValue1->setDateTime(QDateTime::currentDateTime());
0547                 break;
0548             }
0549             }
0550 
0551             m_searchWidget->hide();
0552         }
0553     } else { // show the find widget
0554         if (!m_searchWidget)
0555             initSearchWidget();
0556 
0557         m_searchWidget->show();
0558 
0559         if (m_searchReplaceWidget) {
0560             // switching from advanced to simple search, show the current search pattern
0561             const auto type = static_cast<DataType>(uiSearchReplace.cbDataType->currentIndex());
0562             switch (type) {
0563             case DataType::Text: {
0564                 uiSearch.cbFind->setCurrentText(uiSearchReplace.cbValueText->currentText());
0565                 break;
0566             }
0567             case DataType::Numeric: {
0568                 uiSearch.cbFind->setCurrentText(uiSearchReplace.cbValue1->currentText());
0569                 break;
0570             }
0571             case DataType::DateTime: {
0572                 uiSearch.cbFind->setCurrentText(uiSearchReplace.dteValue1->text());
0573                 break;
0574             }
0575             }
0576 
0577             m_searchReplaceWidget->hide();
0578         }
0579     }
0580 
0581     showMessage(QString());
0582 }
0583 
0584 // **********************************************************
0585 // **************  simple find functions  *******************
0586 // **********************************************************
0587 /*!
0588  * search the next cell in the column-major order that matches
0589  * to the specified pattern. The search is done ignoring the data type
0590  * and iterpreting everything as text. Used in the "simple search"-mode.
0591  */
0592 bool SearchReplaceWidget::findNextSimple(bool proceed) {
0593     const QString& pattern = uiSearch.cbFind->currentText();
0594     if (pattern.isEmpty()) {
0595         GuiTools::highlight(uiSearch.cbFind->lineEdit(), false);
0596         return true;
0597     }
0598 
0599     const auto cs = uiSearch.tbMatchCase->isChecked() ? Qt::CaseSensitive : Qt::CaseInsensitive;
0600 
0601     // spreadsheet size and the start cell
0602     const int colCount = m_spreadsheet->columnCount();
0603     const int rowCount = m_spreadsheet->rowCount();
0604     int curRow = m_view->firstSelectedRow();
0605     int curCol = m_view->firstSelectedColumn();
0606 
0607     if (proceed) {
0608         if (curRow != rowCount - 1)
0609             ++curRow; // not the last row yet, navigate to the next row
0610         else {
0611             // last row
0612             if (curCol != colCount - 1) {
0613                 // not the last column yet, navigate to the first row in the next column
0614                 ++curCol;
0615                 curRow = 0;
0616             } else {
0617                 // last row in the last column, cannot navigate any further
0618                 GuiTools::highlight(uiSearch.cbFind->lineEdit(), !m_patternFound);
0619                 return false;
0620             }
0621         }
0622     }
0623 
0624     // all settings are determined -> search the next cell matching the specified pattern(s)
0625     const auto& columns = m_spreadsheet->children<Column>();
0626     bool startCol = true;
0627     bool startRow = true;
0628 
0629     // search in the column-major order ignoring the data type
0630     // and iterpreting everything as text
0631     for (int col = 0; col < colCount; ++col) {
0632         if (startCol && col < curCol)
0633             continue;
0634 
0635         auto* column = columns.at(col)->asStringColumn();
0636 
0637         for (int row = 0; row < rowCount; ++row) {
0638             if (startRow && row < curRow)
0639                 continue;
0640 
0641             if (column->textAt(row).contains(pattern, cs)) {
0642                 m_patternFound = true;
0643                 m_view->goToCell(row, col);
0644                 GuiTools::highlight(uiSearch.cbFind->lineEdit(), false);
0645                 return true;
0646             }
0647 
0648             startRow = false;
0649         }
0650 
0651         startCol = false;
0652     }
0653 
0654     GuiTools::highlight(uiSearch.cbFind->lineEdit(), !m_patternFound);
0655     showMessage(QString());
0656     return false;
0657 }
0658 
0659 /*!
0660  * search the previous cell in the column-major order that matches
0661  * to the specified pattern. The search is done ignoring the data type
0662  * and iterpreting everything as text. Used in the "simple search"-mode.
0663  */
0664 bool SearchReplaceWidget::findPreviousSimple(bool proceed) {
0665     const QString& pattern = uiSearch.cbFind->currentText();
0666     if (pattern.isEmpty()) {
0667         GuiTools::highlight(uiSearch.cbFind->lineEdit(), false);
0668         showMessage(QString());
0669         return true;
0670     }
0671 
0672     const auto cs = uiSearch.tbMatchCase->isChecked() ? Qt::CaseSensitive : Qt::CaseInsensitive;
0673 
0674     // spreadsheet size and the start cell
0675     const int colCount = m_spreadsheet->columnCount();
0676     const int rowCount = m_spreadsheet->rowCount();
0677     int curRow = m_view->firstSelectedRow();
0678     int curCol = m_view->firstSelectedColumn();
0679 
0680     if (proceed) {
0681         if (curRow > 0)
0682             --curRow; // not the first row yet, navigate to the previous cell
0683         else {
0684             // first row
0685             if (curCol > 0) {
0686                 // not the first column yet, navigate to the last row in the previous column
0687                 --curCol;
0688                 curRow = rowCount - 1;
0689             } else {
0690                 // first row in the first column, cannot navigate any further
0691                 GuiTools::highlight(uiSearch.cbFind->lineEdit(), !m_patternFound);
0692                 return false;
0693             }
0694         }
0695     }
0696 
0697     // all settings are determined -> search the next cell matching the specified pattern(s)
0698     const auto& columns = m_spreadsheet->children<Column>();
0699     bool startCol = true;
0700     bool startRow = true;
0701 
0702     for (int col = colCount; col >= 0; --col) {
0703         if (startCol && col > curCol)
0704             continue;
0705 
0706         auto* column = columns.at(col)->asStringColumn();
0707 
0708         for (int row = rowCount; row >= 0; --row) {
0709             if (startRow && row > curRow)
0710                 continue;
0711 
0712             if (column->textAt(row).contains(pattern, cs)) {
0713                 m_patternFound = true;
0714                 m_view->goToCell(row, col);
0715                 GuiTools::highlight(uiSearch.cbFind->lineEdit(), false);
0716                 return true;
0717             }
0718 
0719             startRow = false;
0720         }
0721 
0722         startCol = false;
0723     }
0724 
0725     GuiTools::highlight(uiSearch.cbFind->lineEdit(), !m_patternFound);
0726     showMessage(QString());
0727     return false;
0728 }
0729 
0730 // **********************************************************
0731 // ****  advanced and data type specific find functions  ****
0732 // **********************************************************
0733 bool SearchReplaceWidget::findNext(bool proceed, bool findAndReplace) {
0734     // search pattern(s)
0735     const auto type = static_cast<DataType>(uiSearchReplace.cbDataType->currentIndex());
0736     QString pattern1;
0737     QString pattern2;
0738     QString replaceValue;
0739     switch (type) {
0740     case DataType::Text:
0741         pattern1 = uiSearchReplace.cbValueText->currentText();
0742         addCurrentTextToHistory(uiSearchReplace.cbValueText);
0743         if (findAndReplace) {
0744             replaceValue = uiSearchReplace.cbReplaceText->currentText();
0745             addCurrentTextToHistory(uiSearchReplace.cbReplaceText);
0746         }
0747         break;
0748     case DataType::Numeric:
0749         pattern1 = uiSearchReplace.cbValue1->currentText();
0750         pattern2 = uiSearchReplace.cbValue2->currentText();
0751         addCurrentTextToHistory(uiSearchReplace.cbValue1);
0752         addCurrentTextToHistory(uiSearchReplace.cbValue2);
0753         if (findAndReplace) {
0754             replaceValue = uiSearchReplace.cbReplace->currentText();
0755             addCurrentTextToHistory(uiSearchReplace.cbReplace);
0756         }
0757         break;
0758     case DataType::DateTime:
0759         pattern1 = uiSearchReplace.dteValue1->text();
0760         pattern2 = uiSearchReplace.dteValue2->text();
0761         if (findAndReplace)
0762             replaceValue = uiSearchReplace.dteReplace->text();
0763         break;
0764     }
0765 
0766     if (pattern1.isEmpty()) {
0767         highlight(type, false);
0768         return true;
0769     }
0770 
0771     if (findAndReplace && replaceValue.isEmpty())
0772         return false;
0773 
0774     // settings
0775     const auto opText = static_cast<OperatorText>(uiSearchReplace.cbOperatorText->currentData().toInt());
0776     const auto opNumeric = static_cast<Operator>(uiSearchReplace.cbOperator->currentData().toInt());
0777     const auto opDateTime = static_cast<Operator>(uiSearchReplace.cbOperatorDateTime->currentData().toInt());
0778     const auto cs = uiSearchReplace.tbMatchCase->isChecked() ? Qt::CaseSensitive : Qt::CaseInsensitive;
0779     const bool columnMajor = (uiSearchReplace.cbOrder->currentIndex() == 0);
0780 
0781     // spreadsheet size and the start cell
0782     const int colCount = m_spreadsheet->columnCount();
0783     const int rowCount = m_spreadsheet->rowCount();
0784     int curRow = m_view->firstSelectedRow();
0785     int curCol = m_view->firstSelectedColumn();
0786 
0787     if (columnMajor && proceed) {
0788         if (curRow != rowCount - 1)
0789             ++curRow; // not the last row yet, navigate to the next row
0790         else {
0791             // last row
0792             if (curCol != colCount - 1) {
0793                 // not the last column yet, navigate to the first row in the next column
0794                 ++curCol;
0795                 curRow = 0;
0796             } else {
0797                 // last row in the last column, cannot navigate any further
0798                 highlight(type, !m_patternFound);
0799                 return false;
0800             }
0801         }
0802     }
0803 
0804     if (!columnMajor && proceed) {
0805         if (curCol != colCount - 1)
0806             ++curCol; // not the last column yet, navigate to the next column
0807         else {
0808             // last column
0809             if (curRow != rowCount - 1) {
0810                 // not the last row yet, navigate to the next row in the first column
0811                 ++curRow;
0812                 curCol = 0;
0813             } else {
0814                 // last row in the last column, cannot navigate any further
0815                 highlight(type, !m_patternFound);
0816                 return false;
0817             }
0818         }
0819     }
0820 
0821     // all settings are determined -> search the next cell matching the specified pattern(s)
0822     const auto& columns = m_spreadsheet->children<Column>();
0823     bool startCol = true;
0824     bool startRow = true;
0825     bool match = false;
0826 
0827     if (columnMajor) {
0828         for (int col = 0; col < colCount; ++col) {
0829             if (startCol && col < curCol)
0830                 continue;
0831 
0832             auto* column = columns.at(col);
0833             if (!checkColumnType(column, type)) {
0834                 startRow = false;
0835                 continue;
0836             }
0837 
0838             for (int row = 0; row < rowCount; ++row) {
0839                 if (startRow && row < curRow)
0840                     continue;
0841 
0842                 match = checkColumnRow(column, type, row, opText, opNumeric, opDateTime, pattern1, pattern2, cs);
0843                 if (match) {
0844                     m_patternFound = true;
0845                     m_view->goToCell(row, col);
0846                     if (findAndReplace)
0847                         setValue(column, type, row, replaceValue);
0848                     highlight(type, false);
0849                     return true;
0850                 }
0851 
0852                 startRow = false;
0853             }
0854 
0855             startCol = false;
0856         }
0857     } else { // row-major
0858         for (int row = 0; row < rowCount; ++row) {
0859             if (startRow && row < curRow)
0860                 continue;
0861 
0862             for (int col = 0; col < colCount; ++col) {
0863                 if (startCol && col < curCol)
0864                     continue;
0865 
0866                 auto* column = columns.at(col);
0867                 if (!checkColumnType(column, type)) {
0868                     startCol = false;
0869                     continue;
0870                 }
0871 
0872                 match = checkColumnRow(column, type, row, opText, opNumeric, opDateTime, pattern1, pattern2, cs);
0873                 if (match) {
0874                     m_patternFound = true;
0875                     m_view->goToCell(row, col);
0876                     if (findAndReplace)
0877                         setValue(column, type, row, replaceValue);
0878                     highlight(type, false);
0879                     return true;
0880                 }
0881 
0882                 startCol = false;
0883             }
0884 
0885             startRow = false;
0886         }
0887     }
0888 
0889     highlight(type, !m_patternFound);
0890     return false;
0891 }
0892 
0893 bool SearchReplaceWidget::findPrevious(bool proceed) {
0894     // search pattern(s)
0895     const auto type = static_cast<DataType>(uiSearchReplace.cbDataType->currentIndex());
0896     QString pattern1;
0897     QString pattern2;
0898     switch (type) {
0899     case DataType::Text:
0900         pattern1 = uiSearchReplace.cbValueText->currentText();
0901         addCurrentTextToHistory(uiSearchReplace.cbValueText);
0902         break;
0903     case DataType::Numeric:
0904         pattern1 = uiSearchReplace.cbValue1->currentText();
0905         pattern2 = uiSearchReplace.cbValue2->currentText();
0906         addCurrentTextToHistory(uiSearchReplace.cbValue1);
0907         addCurrentTextToHistory(uiSearchReplace.cbValue2);
0908         break;
0909     case DataType::DateTime:
0910         pattern1 = uiSearchReplace.dteValue1->text();
0911         pattern2 = uiSearchReplace.dteValue2->text();
0912         break;
0913     }
0914 
0915     if (pattern1.isEmpty()) {
0916         highlight(type, false);
0917         return true;
0918     }
0919 
0920     // settings
0921     const auto opText = static_cast<OperatorText>(uiSearchReplace.cbOperatorText->currentData().toInt());
0922     const auto opNumeric = static_cast<Operator>(uiSearchReplace.cbOperator->currentData().toInt());
0923     const auto opDateTime = static_cast<Operator>(uiSearchReplace.cbOperatorDateTime->currentData().toInt());
0924     const auto cs = uiSearchReplace.tbMatchCase->isChecked() ? Qt::CaseSensitive : Qt::CaseInsensitive;
0925     const bool columnMajor = (uiSearchReplace.cbOrder->currentIndex() == 0);
0926 
0927     // spreadsheet size and the start cell
0928     const int colCount = m_spreadsheet->columnCount();
0929     const int rowCount = m_spreadsheet->rowCount();
0930     int curRow = m_view->firstSelectedRow();
0931     int curCol = m_view->firstSelectedColumn();
0932 
0933     if (columnMajor && proceed) {
0934         if (curRow > 0)
0935             --curRow; // not the first row yet, navigate to the previous cell
0936         else {
0937             // first row
0938             if (curCol > 0) {
0939                 // not the first column yet, navigate to the last row in the previous column
0940                 --curCol;
0941                 curRow = rowCount - 1;
0942             } else {
0943                 // first row in the first column, cannot navigate any further
0944                 highlight(type, !m_patternFound);
0945                 return false;
0946             }
0947         }
0948     }
0949 
0950     if (!columnMajor && proceed) {
0951         if (curCol > 0)
0952             --curCol; // not the first column yet, navigate to the previous column
0953         else {
0954             // first column
0955             if (curRow > 0) {
0956                 // not the first row yet, navigate to the previous row in the last column
0957                 --curRow;
0958                 curCol = colCount - 1;
0959             } else {
0960                 // first row in the first column, cannot navigate any further
0961                 highlight(type, !m_patternFound);
0962                 return false;
0963             }
0964         }
0965     }
0966 
0967     // all settings are determined -> search the next cell matching the specified pattern(s)
0968     const auto& columns = m_spreadsheet->children<Column>();
0969     bool startCol = true;
0970     bool startRow = true;
0971     bool match = false;
0972 
0973     if (columnMajor) {
0974         for (int col = colCount - 1; col >= 0; --col) {
0975             if (startCol && col > curCol)
0976                 continue;
0977 
0978             auto* column = columns.at(col);
0979             if (!checkColumnType(column, type)) {
0980                 startCol = false;
0981                 continue;
0982             }
0983 
0984             for (int row = rowCount - 1; row >= 0; --row) {
0985                 if (startRow && row > curRow)
0986                     continue;
0987 
0988                 match = checkColumnRow(column, type, row, opText, opNumeric, opDateTime, pattern1, pattern2, cs);
0989                 if (match) {
0990                     m_patternFound = true;
0991                     m_view->goToCell(row, col);
0992                     highlight(type, false);
0993                     return true;
0994                 }
0995 
0996                 startRow = false;
0997             }
0998 
0999             startCol = false;
1000         }
1001     } else { // row-major
1002         for (int row = rowCount - 1; row >= 0; --row) {
1003             if (startRow && row > curRow)
1004                 continue;
1005 
1006             for (int col = colCount - 1; col >= 0; --col) {
1007                 if (startCol && col > curCol)
1008                     continue;
1009 
1010                 auto* column = columns.at(col);
1011                 if (!checkColumnType(column, type)) {
1012                     startCol = false;
1013                     continue;
1014                 }
1015 
1016                 match = checkColumnRow(column, type, row, opText, opNumeric, opDateTime, pattern1, pattern2, cs);
1017                 if (match) {
1018                     m_patternFound = true;
1019                     m_view->goToCell(row, col);
1020                     highlight(type, false);
1021                     return true;
1022                 }
1023 
1024                 startCol = false;
1025             }
1026 
1027             startRow = false;
1028         }
1029     }
1030 
1031     highlight(type, !m_patternFound);
1032     return false;
1033 }
1034 
1035 void SearchReplaceWidget::findAll() {
1036     const auto type = static_cast<DataType>(uiSearchReplace.cbDataType->currentIndex());
1037     QString pattern1;
1038     QString pattern2;
1039     switch (type) {
1040     case DataType::Text:
1041         pattern1 = uiSearchReplace.cbValueText->currentText();
1042         break;
1043     case DataType::Numeric:
1044         pattern1 = uiSearchReplace.cbValue1->currentText();
1045         pattern2 = uiSearchReplace.cbValue2->currentText();
1046         break;
1047     case DataType::DateTime:
1048         pattern1 = uiSearchReplace.dteValue1->text();
1049         pattern1 = uiSearchReplace.dteValue2->text();
1050         break;
1051     }
1052 
1053     if (pattern1.isEmpty()) {
1054         highlight(type, false);
1055         return;
1056     }
1057 
1058     // clear the previous selection
1059     m_view->clearSelection();
1060 
1061     // settings
1062     const auto opText = static_cast<OperatorText>(uiSearchReplace.cbOperatorText->currentData().toInt());
1063     const auto opNumeric = static_cast<Operator>(uiSearchReplace.cbOperator->currentData().toInt());
1064     const auto opDateTime = static_cast<Operator>(uiSearchReplace.cbOperatorDateTime->currentData().toInt());
1065     const auto cs = uiSearchReplace.tbMatchCase->isChecked() ? Qt::CaseSensitive : Qt::CaseInsensitive;
1066     const int colCount = m_spreadsheet->columnCount();
1067     const int rowCount = m_spreadsheet->rowCount();
1068 
1069     // all settings are determined -> select all cells matching the specified pattern(s)
1070     const auto& columns = m_spreadsheet->children<Column>();
1071     bool match = false;
1072     int matchCount = 0;
1073 
1074     for (int col = 0; col < colCount; ++col) {
1075         auto* column = columns.at(col);
1076         if (!checkColumnType(column, type))
1077             continue;
1078 
1079         for (int row = 0; row < rowCount; ++row) {
1080             match = checkColumnRow(column, type, row, opText, opNumeric, opDateTime, pattern1, pattern2, cs);
1081             if (match) {
1082                 m_view->selectCell(row, col);
1083                 ++matchCount;
1084             }
1085         }
1086     }
1087 
1088     if (matchCount > 0)
1089         showMessage(i18np("%1 match found", "%1 matches found", matchCount));
1090     else
1091         showMessage(QString());
1092 }
1093 
1094 void SearchReplaceWidget::replaceNext() {
1095     // check the current cell first (no proceed) whether the value matches and needs to be replaced.
1096     // if it doesn't match, proceed to the next cell.
1097     const bool found = findNext(false /* proceed */, true /* find and replace */);
1098     if (!found)
1099         findNext(true /* proceed */, true /* find and replace */);
1100 }
1101 
1102 void SearchReplaceWidget::replaceAll() {
1103     const auto type = static_cast<DataType>(uiSearchReplace.cbDataType->currentIndex());
1104     QString pattern1;
1105     QString pattern2;
1106     QString replaceValue;
1107     switch (type) {
1108     case DataType::Text:
1109         pattern1 = uiSearchReplace.cbValueText->currentText();
1110         addCurrentTextToHistory(uiSearchReplace.cbReplaceText);
1111         replaceValue = uiSearchReplace.cbReplaceText->currentText();
1112         break;
1113     case DataType::Numeric:
1114         pattern1 = uiSearchReplace.cbValue1->currentText();
1115         pattern2 = uiSearchReplace.cbValue2->currentText();
1116         addCurrentTextToHistory(uiSearchReplace.cbReplace);
1117         replaceValue = uiSearchReplace.cbReplace->currentText();
1118         break;
1119     case DataType::DateTime:
1120         pattern1 = uiSearchReplace.dteValue1->text();
1121         pattern2 = uiSearchReplace.dteValue2->text();
1122         replaceValue = uiSearchReplace.dteReplace->text();
1123         break;
1124     }
1125 
1126     if (pattern1.isEmpty()) {
1127         highlight(type, false);
1128         return;
1129     }
1130 
1131     if (replaceValue.isEmpty())
1132         return;
1133 
1134     // clear the previous selection
1135     m_view->clearSelection();
1136 
1137     // settings
1138     const auto opText = static_cast<OperatorText>(uiSearchReplace.cbOperatorText->currentData().toInt());
1139     const auto opNumeric = static_cast<Operator>(uiSearchReplace.cbOperator->currentData().toInt());
1140     const auto opDateTime = static_cast<Operator>(uiSearchReplace.cbOperatorDateTime->currentData().toInt());
1141     const auto cs = uiSearchReplace.tbMatchCase->isChecked() ? Qt::CaseSensitive : Qt::CaseInsensitive;
1142     const int colCount = m_spreadsheet->columnCount();
1143     const int rowCount = m_spreadsheet->rowCount();
1144 
1145     // all settings are determined -> select all cells matching the specified pattern(s)
1146     const auto& columns = m_spreadsheet->children<Column>();
1147     bool match = false;
1148     int matchCount = 0;
1149     m_spreadsheet->beginMacro(i18n("%1: replace values", m_spreadsheet->name()));
1150 
1151     for (int col = 0; col < colCount; ++col) {
1152         auto* column = columns.at(col);
1153         if (!checkColumnType(column, type))
1154             continue;
1155 
1156         for (int row = 0; row < rowCount; ++row) {
1157             match = checkColumnRow(column, type, row, opText, opNumeric, opDateTime, pattern1, pattern2, cs);
1158             if (match) {
1159                 setValue(column, type, row, replaceValue);
1160                 ++matchCount;
1161             }
1162         }
1163     }
1164 
1165     m_spreadsheet->endMacro();
1166 
1167     if (matchCount > 0)
1168         showMessage(i18np("%1 replacement made", "%1 replacements made", matchCount));
1169     else
1170         showMessage(QString());
1171 }
1172 
1173 // **********************************************************
1174 // ************ find/replace helper functions **************
1175 // **********************************************************
1176 bool SearchReplaceWidget::checkColumnType(Column* column, DataType type) {
1177     bool valid = false;
1178 
1179     switch (type) {
1180     case DataType::Text:
1181         valid = (column->columnMode() == AbstractColumn::ColumnMode::Text);
1182         break;
1183     case DataType::Numeric:
1184         valid = column->isNumeric();
1185         break;
1186     case DataType::DateTime:
1187         valid = (column->columnMode() == AbstractColumn::ColumnMode::DateTime);
1188         break;
1189     }
1190 
1191     return valid;
1192 }
1193 
1194 bool SearchReplaceWidget::checkColumnRow(Column* column,
1195                                          DataType type,
1196                                          int row,
1197                                          OperatorText opText,
1198                                          Operator opNumeric,
1199                                          Operator opDateTime,
1200                                          const QString& pattern1,
1201                                          const QString pattern2,
1202                                          Qt::CaseSensitivity cs) {
1203     bool match = false;
1204     switch (type) {
1205     case DataType::Text:
1206         match = checkCellText(column->textAt(row), pattern1, opText, cs);
1207         break;
1208     case DataType::Numeric:
1209         match = checkCellNumeric(column->valueAt(row), pattern1, pattern2, opNumeric);
1210         break;
1211     case DataType::DateTime:
1212         match = checkCellDateTime(column->dateTimeAt(row), uiSearchReplace.dteValue1->dateTime(), uiSearchReplace.dteValue2->dateTime(), opDateTime);
1213         break;
1214     }
1215 
1216     return match;
1217 }
1218 
1219 bool SearchReplaceWidget::checkCellText(const QString& cellText, const QString& pattern, OperatorText op, Qt::CaseSensitivity cs) {
1220     bool match = false;
1221 
1222     switch (op) {
1223     case OperatorText::EqualTo: {
1224         match = (cellText.compare(pattern, cs) == 0);
1225         break;
1226     }
1227     case OperatorText::NotEqualTo: {
1228         match = (cellText.compare(pattern, cs) != 0);
1229         break;
1230     }
1231     case OperatorText::StartsWith: {
1232         match = cellText.startsWith(pattern, cs);
1233         break;
1234     }
1235     case OperatorText::EndsWith: {
1236         match = cellText.endsWith(pattern, cs);
1237         break;
1238     }
1239     case OperatorText::Contain: {
1240         match = (cellText.indexOf(pattern, cs) != -1);
1241         break;
1242     }
1243     case OperatorText::NotContain: {
1244         match = (cellText.indexOf(pattern, cs) == -1);
1245         break;
1246     }
1247     case OperatorText::RegEx: {
1248         QRegularExpression re(pattern);
1249         if (cs == Qt::CaseInsensitive)
1250             re.setPatternOptions(QRegularExpression::CaseInsensitiveOption);
1251         match = re.match(cellText).hasMatch();
1252         break;
1253     }
1254     }
1255 
1256     return match;
1257 }
1258 
1259 bool SearchReplaceWidget::checkCellNumeric(double cellValue, const QString& pattern1, const QString& pattern2, Operator op) {
1260     if (pattern1.isEmpty())
1261         return false;
1262 
1263     if ((op == Operator::BetweenIncl || op == Operator::BetweenExcl) && pattern2.isEmpty())
1264         return false;
1265 
1266     bool ok;
1267     const auto numberLocale = QLocale();
1268 
1269     const double patternValue1 = numberLocale.toDouble(pattern1, &ok);
1270     if (!ok)
1271         return false;
1272 
1273     double patternValue2 = 0.;
1274     if (op == Operator::BetweenIncl || op == Operator::BetweenExcl) {
1275         patternValue2 = numberLocale.toDouble(pattern2, &ok);
1276         if (!ok)
1277             return false;
1278     }
1279 
1280     bool match = false;
1281 
1282     switch (op) {
1283     case Operator::EqualTo: {
1284         match = (cellValue == patternValue1);
1285         break;
1286     }
1287     case Operator::NotEqualTo: {
1288         match = (cellValue != patternValue1);
1289         break;
1290     }
1291     case Operator::BetweenIncl: {
1292         match = (cellValue >= patternValue1 && cellValue <= patternValue2);
1293         break;
1294     }
1295     case Operator::BetweenExcl: {
1296         match = (cellValue > patternValue1 && cellValue < patternValue2);
1297         break;
1298     }
1299     case Operator::GreaterThan: {
1300         match = (cellValue > patternValue1);
1301         break;
1302     }
1303     case Operator::GreaterThanEqualTo: {
1304         match = (cellValue >= patternValue1);
1305         break;
1306     }
1307     case Operator::LessThan: {
1308         match = (cellValue < patternValue1);
1309         break;
1310     }
1311     case Operator::LessThanEqualTo: {
1312         match = (cellValue <= patternValue1);
1313         break;
1314     }
1315     }
1316 
1317     return match;
1318 }
1319 
1320 bool SearchReplaceWidget::checkCellDateTime(const QDateTime& cellValueDateTime,
1321                                             const QDateTime& patternDateTimeValue1,
1322                                             const QDateTime& patternDateTimeValue2,
1323                                             Operator op) {
1324     if (!patternDateTimeValue1.isValid())
1325         return false;
1326 
1327     if ((op == Operator::BetweenIncl || op == Operator::BetweenExcl) && !patternDateTimeValue2.isValid())
1328         return false;
1329 
1330     const double cellValue = cellValueDateTime.toMSecsSinceEpoch();
1331     const double patternValue1 = patternDateTimeValue1.toMSecsSinceEpoch();
1332     const double patternValue2 = patternDateTimeValue2.toMSecsSinceEpoch();
1333     bool match = false;
1334 
1335     switch (op) {
1336     case Operator::EqualTo: {
1337         match = (cellValue == patternValue1);
1338         break;
1339     }
1340     case Operator::NotEqualTo: {
1341         match = (cellValue != patternValue1);
1342         break;
1343     }
1344     case Operator::BetweenIncl: {
1345         match = (cellValue >= patternValue1 && cellValue <= patternValue2);
1346         break;
1347     }
1348     case Operator::BetweenExcl: {
1349         match = (cellValue > patternValue1 && cellValue < patternValue2);
1350         break;
1351     }
1352     case Operator::GreaterThan: {
1353         match = (cellValue > patternValue1);
1354         break;
1355     }
1356     case Operator::GreaterThanEqualTo: {
1357         match = (cellValue >= patternValue1);
1358         break;
1359     }
1360     case Operator::LessThan: {
1361         match = (cellValue < patternValue1);
1362         break;
1363     }
1364     case Operator::LessThanEqualTo: {
1365         match = (cellValue <= patternValue1);
1366         break;
1367     }
1368     }
1369 
1370     return match;
1371 }
1372 
1373 void SearchReplaceWidget::setValue(Column* column, DataType type, int row, const QString& replaceValue) {
1374     switch (type) {
1375     case DataType::Text:
1376         column->setTextAt(row, replaceValue);
1377         break;
1378     case DataType::Numeric: {
1379         bool ok;
1380         const auto mode = column->columnMode();
1381         if (mode == AbstractColumn::ColumnMode::Double) {
1382             const double value = QLocale().toDouble(replaceValue, &ok);
1383             if (ok)
1384                 column->setValueAt(row, value);
1385         } else if (mode == AbstractColumn::ColumnMode::Integer) {
1386             const int value = QLocale().toInt(replaceValue, &ok);
1387             if (ok)
1388                 column->setIntegerAt(row, value);
1389         } else if (mode == AbstractColumn::ColumnMode::BigInt) {
1390             const qint64 value = QLocale().toLongLong(replaceValue, &ok);
1391             if (ok)
1392                 column->setBigIntAt(row, value);
1393         }
1394         break;
1395     }
1396     case DataType::DateTime:
1397         const auto& value = uiSearchReplace.dteReplace->dateTime();
1398         if (value.isValid())
1399             column->setDateTimeAt(row, value);
1400         break;
1401     }
1402 }
1403 
1404 void SearchReplaceWidget::highlight(DataType type, bool invalid) {
1405     switch (type) {
1406     case DataType::Text:
1407         GuiTools::highlight(uiSearchReplace.cbValueText, invalid);
1408         break;
1409     case DataType::Numeric:
1410         GuiTools::highlight(uiSearchReplace.cbValue1, invalid);
1411         GuiTools::highlight(uiSearchReplace.cbValue2, invalid);
1412         break;
1413     case DataType::DateTime:
1414         GuiTools::highlight(uiSearchReplace.dteValue1, invalid);
1415         GuiTools::highlight(uiSearchReplace.dteValue2, invalid);
1416         break;
1417     }
1418 
1419     showMessage(QString());
1420 }
1421 
1422 void SearchReplaceWidget::showMessage(const QString& message) {
1423     if (message.isEmpty()) {
1424         if (m_messageWidget && m_messageWidget->isVisible())
1425             m_messageWidget->close();
1426     } else {
1427         if (!m_messageWidget) {
1428             m_messageWidget = new KMessageWidget(this);
1429             // m_messageWidget->setCloseButtonVisible(false);
1430             m_messageWidget->setMessageType(KMessageWidget::Information);
1431             auto* vBoxLayout = static_cast<QVBoxLayout*>(layout());
1432             vBoxLayout->insertWidget(0, m_messageWidget);
1433         }
1434         m_messageWidget->setText(message);
1435         // m_messageWidget->move(300, 500);
1436         m_messageWidget->animatedShow();
1437     }
1438 }
1439 
1440 // **********************************************************
1441 // **** context menu related helper classes and functions ***
1442 // **********************************************************
1443 // the code below is taken from src/ktexteditor/katesearchbar.cpp from the ktexteditor repository.
1444 // idially, the same i18n is done for the strings below as in ktexteditor.
1445 
1446 class AddMenuManager {
1447 private:
1448     QVector<QString> m_insertBefore;
1449     QVector<QString> m_insertAfter;
1450     QSet<QAction*> m_actionPointers;
1451     uint m_indexWalker{0};
1452     QMenu* m_menu{nullptr};
1453 
1454 public:
1455     AddMenuManager(QMenu* parent, int expectedItemCount)
1456         : m_insertBefore(QVector<QString>(expectedItemCount))
1457         , m_insertAfter(QVector<QString>(expectedItemCount)) {
1458         Q_ASSERT(parent != nullptr);
1459 
1460         m_menu = parent->addMenu(i18n("Add..."));
1461         if (!m_menu)
1462             return;
1463 
1464         m_menu->setIcon(QIcon::fromTheme(QStringLiteral("list-add")));
1465     }
1466 
1467     void enableMenu(bool enabled) {
1468         if (m_menu == nullptr)
1469             return;
1470 
1471         m_menu->setEnabled(enabled);
1472     }
1473 
1474     void addEntry(const QString& before,
1475                   const QString& after,
1476                   const QString& description,
1477                   const QString& realBefore = QString(),
1478                   const QString& realAfter = QString()) {
1479         if (!m_menu)
1480             return;
1481 
1482         auto* const action = m_menu->addAction(before + after + QLatin1Char('\t') + description);
1483         m_insertBefore[m_indexWalker] = QString(realBefore.isEmpty() ? before : realBefore);
1484         m_insertAfter[m_indexWalker] = QString(realAfter.isEmpty() ? after : realAfter);
1485         action->setData(QVariant(m_indexWalker++));
1486         m_actionPointers.insert(action);
1487     }
1488 
1489     void addSeparator() {
1490         if (!m_menu)
1491             return;
1492 
1493         m_menu->addSeparator();
1494     }
1495 
1496     void handle(QAction* action, QLineEdit* lineEdit) {
1497         if (!m_actionPointers.contains(action))
1498             return;
1499 
1500         const int cursorPos = lineEdit->cursorPosition();
1501         const int index = action->data().toUInt();
1502         const QString& before = m_insertBefore[index];
1503         const QString& after = m_insertAfter[index];
1504         lineEdit->insert(before + after);
1505         lineEdit->setCursorPosition(cursorPos + before.count());
1506         lineEdit->setFocus();
1507     }
1508 };
1509 
1510 struct ParInfo {
1511     int openIndex;
1512     bool capturing;
1513     int captureNumber; // 1..9
1514 };
1515 
1516 void SearchReplaceWidget::showExtendedContextMenu(bool replace, const QPoint& pos) {
1517     // create the original menu
1518     QLineEdit* lineEdit;
1519     if (replace)
1520         lineEdit = uiSearchReplace.cbReplaceText->lineEdit();
1521     else
1522         lineEdit = uiSearchReplace.cbValueText->lineEdit();
1523 
1524     auto* const contextMenu = lineEdit->createStandardContextMenu();
1525     if (!contextMenu)
1526         return;
1527 
1528     // the extension should only be available for regex
1529     const auto opText = static_cast<OperatorText>(uiSearchReplace.cbOperatorText->currentData().toInt());
1530     if (opText != OperatorText::RegEx) {
1531         contextMenu->exec(lineEdit->mapToGlobal(pos));
1532         return;
1533     }
1534 
1535     bool extendMenu = true;
1536     bool regexMode = true;
1537 
1538     AddMenuManager addMenuManager(contextMenu, 37);
1539     if (!extendMenu)
1540         addMenuManager.enableMenu(extendMenu);
1541     else {
1542         // Build menu
1543         if (!replace) {
1544             if (regexMode) {
1545                 addMenuManager.addEntry(QStringLiteral("^"), QString(), i18n("Beginning of line"));
1546                 addMenuManager.addEntry(QStringLiteral("$"), QString(), i18n("End of line"));
1547                 addMenuManager.addSeparator();
1548                 addMenuManager.addEntry(QStringLiteral("."), QString(), i18n("Match any character excluding new line (by default)"));
1549                 addMenuManager.addEntry(QStringLiteral("+"), QString(), i18n("One or more occurrences"));
1550                 addMenuManager.addEntry(QStringLiteral("*"), QString(), i18n("Zero or more occurrences"));
1551                 addMenuManager.addEntry(QStringLiteral("?"), QString(), i18n("Zero or one occurrences"));
1552                 addMenuManager.addEntry(QStringLiteral("{a"),
1553                                         QStringLiteral(",b}"),
1554                                         i18n("<a> through <b> occurrences"),
1555                                         QStringLiteral("{"),
1556                                         QStringLiteral(",}"));
1557 
1558                 addMenuManager.addSeparator();
1559                 addMenuManager.addSeparator();
1560                 addMenuManager.addEntry(QStringLiteral("("), QStringLiteral(")"), i18n("Group, capturing"));
1561                 addMenuManager.addEntry(QStringLiteral("|"), QString(), i18n("Or"));
1562                 addMenuManager.addEntry(QStringLiteral("["), QStringLiteral("]"), i18n("Set of characters"));
1563                 addMenuManager.addEntry(QStringLiteral("[^"), QStringLiteral("]"), i18n("Negative set of characters"));
1564                 addMenuManager.addSeparator();
1565             }
1566         } else {
1567             addMenuManager.addEntry(QStringLiteral("\\0"), QString(), i18n("Whole match reference"));
1568             addMenuManager.addSeparator();
1569             if (regexMode) {
1570                 const QString pattern = uiSearchReplace.cbReplace->currentText();
1571                 const QVector<QString> capturePatterns = this->capturePatterns(pattern);
1572 
1573                 const int captureCount = capturePatterns.count();
1574                 for (int i = 1; i <= 9; i++) {
1575                     const QString number = QString::number(i);
1576                     const QString& captureDetails =
1577                         (i <= captureCount) ? QLatin1String(" = (") + QStringView(capturePatterns[i - 1]).left(30) + QLatin1Char(')') : QString();
1578                     addMenuManager.addEntry(QLatin1String("\\") + number, QString(), i18n("Reference") + QLatin1Char(' ') + number + captureDetails);
1579                 }
1580 
1581                 addMenuManager.addSeparator();
1582             }
1583         }
1584 
1585         addMenuManager.addEntry(QStringLiteral("\\n"), QString(), i18n("Line break"));
1586         addMenuManager.addEntry(QStringLiteral("\\t"), QString(), i18n("Tab"));
1587 
1588         if (!replace && regexMode) {
1589             addMenuManager.addEntry(QStringLiteral("\\b"), QString(), i18n("Word boundary"));
1590             addMenuManager.addEntry(QStringLiteral("\\B"), QString(), i18n("Not word boundary"));
1591             addMenuManager.addEntry(QStringLiteral("\\d"), QString(), i18n("Digit"));
1592             addMenuManager.addEntry(QStringLiteral("\\D"), QString(), i18n("Non-digit"));
1593             addMenuManager.addEntry(QStringLiteral("\\s"), QString(), i18n("Whitespace (excluding line breaks)"));
1594             addMenuManager.addEntry(QStringLiteral("\\S"), QString(), i18n("Non-whitespace"));
1595             addMenuManager.addEntry(QStringLiteral("\\w"), QString(), i18n("Word character (alphanumerics plus '_')"));
1596             addMenuManager.addEntry(QStringLiteral("\\W"), QString(), i18n("Non-word character"));
1597         }
1598 
1599         addMenuManager.addEntry(QStringLiteral("\\0???"), QString(), i18n("Octal character 000 to 377 (2^8-1)"), QStringLiteral("\\0"));
1600         addMenuManager.addEntry(QStringLiteral("\\x{????}"), QString(), i18n("Hex character 0000 to FFFF (2^16-1)"), QStringLiteral("\\x{....}"));
1601         addMenuManager.addEntry(QStringLiteral("\\\\"), QString(), i18n("Backslash"));
1602 
1603         if (!replace && regexMode) {
1604             addMenuManager.addSeparator();
1605             addMenuManager.addEntry(QStringLiteral("(?:E"), QStringLiteral(")"), i18n("Group, non-capturing"), QStringLiteral("(?:"));
1606             addMenuManager.addEntry(QStringLiteral("(?=E"), QStringLiteral(")"), i18n("Positive Lookahead"), QStringLiteral("(?="));
1607             addMenuManager.addEntry(QStringLiteral("(?!E"), QStringLiteral(")"), i18n("Negative lookahead"), QStringLiteral("(?!"));
1608             // variable length positive/negative lookbehind is an experimental feature in Perl 5.30
1609             // see: https://perldoc.perl.org/perlre.html
1610             // currently QRegularExpression only supports fixed-length positive/negative lookbehind (2020-03-01)
1611             addMenuManager.addEntry(QStringLiteral("(?<=E"), QStringLiteral(")"), i18n("Fixed-length positive lookbehind"), QStringLiteral("(?<="));
1612             addMenuManager.addEntry(QStringLiteral("(?<!E"), QStringLiteral(")"), i18n("Fixed-length negative lookbehind"), QStringLiteral("(?<!"));
1613         }
1614 
1615         // TODO: support case conversion line in Kate later?
1616         /*
1617         if (replace) {
1618             addMenuManager.addSeparator();
1619             addMenuManager.addEntry(QStringLiteral("\\L"), QString(), i18n("Begin lowercase conversion"));
1620             addMenuManager.addEntry(QStringLiteral("\\U"), QString(), i18n("Begin uppercase conversion"));
1621             addMenuManager.addEntry(QStringLiteral("\\E"), QString(), i18n("End case conversion"));
1622             addMenuManager.addEntry(QStringLiteral("\\l"), QString(), i18n("Lowercase first character conversion"));
1623             addMenuManager.addEntry(QStringLiteral("\\u"), QString(), i18n("Uppercase first character conversion"));
1624             addMenuManager.addEntry(QStringLiteral("\\#[#..]"), QString(), i18n("Replacement counter (for Replace All)"), QStringLiteral("\\#"));
1625         }
1626         */
1627     }
1628 
1629     // Show menu
1630     auto* const result = contextMenu->exec(lineEdit->mapToGlobal(pos));
1631     if (result)
1632         addMenuManager.handle(result, lineEdit);
1633 }
1634 
1635 QVector<QString> SearchReplaceWidget::capturePatterns(const QString& pattern) const {
1636     QVector<QString> capturePatterns;
1637     capturePatterns.reserve(9);
1638     QStack<ParInfo> parInfos;
1639 
1640     const int inputLen = pattern.length();
1641     int input = 0; // walker index
1642     bool insideClass = false;
1643     int captureCount = 0;
1644 
1645     while (input < inputLen) {
1646         if (insideClass) {
1647             // Wait for closing, unescaped ']'
1648             if (pattern[input].unicode() == L']')
1649                 insideClass = false;
1650 
1651             input++;
1652         } else {
1653             switch (pattern[input].unicode()) {
1654             case L'\\':
1655                 // Skip this and any next character
1656                 input += 2;
1657                 break;
1658 
1659             case L'(':
1660                 ParInfo curInfo;
1661                 curInfo.openIndex = input;
1662                 curInfo.capturing = (input + 1 >= inputLen) || (pattern[input + 1].unicode() != '?');
1663                 if (curInfo.capturing) {
1664                     captureCount++;
1665                 }
1666                 curInfo.captureNumber = captureCount;
1667                 parInfos.push(curInfo);
1668 
1669                 input++;
1670                 break;
1671 
1672             case L')':
1673                 if (!parInfos.empty()) {
1674                     ParInfo& top = parInfos.top();
1675                     if (top.capturing && (top.captureNumber <= 9)) {
1676                         const int start = top.openIndex + 1;
1677                         const int len = input - start;
1678                         if (capturePatterns.size() < top.captureNumber) {
1679                             capturePatterns.resize(top.captureNumber);
1680                         }
1681                         capturePatterns[top.captureNumber - 1] = pattern.mid(start, len);
1682                     }
1683                     parInfos.pop();
1684                 }
1685 
1686                 input++;
1687                 break;
1688 
1689             case L'[':
1690                 input++;
1691                 insideClass = true;
1692                 break;
1693 
1694             default:
1695                 input++;
1696                 break;
1697             }
1698         }
1699     }
1700 
1701     return capturePatterns;
1702 }