// Copyright (C) 2016 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author Giuseppe D'Angelo // Copyright (C) 2016 Samuel Gaist // Copyright (C) 2016 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause #include "regularexpressiondialog.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include Q_DECLARE_METATYPE(QRegularExpression::MatchType) static QString rawStringLiteral(QString pattern) { pattern.prepend(QLatin1String("R\"RX(")); pattern.append(QLatin1String(")RX\"")); return pattern; } static QString patternToCode(QString pattern) { pattern.replace(QLatin1String("\\"), QLatin1String("\\\\")); pattern.replace(QLatin1String("\""), QLatin1String("\\\"")); pattern.prepend(QLatin1Char('"')); pattern.append(QLatin1Char('"')); return pattern; } static QString codeToPattern(QString code) { for (int i = 0; i < code.size(); ++i) { if (code.at(i) == QLatin1Char('\\')) code.remove(i, 1); } if (code.startsWith(QLatin1Char('"')) && code.endsWith(QLatin1Char('"'))) { code.chop(1); code.remove(0, 1); } return code; } static QFrame *createHorizontalSeparator() { auto *result = new QFrame; result->setFrameStyle(QFrame::HLine | QFrame::Sunken); return result; } static QFrame *createVerticalSeparator() { auto *result = new QFrame; result->setFrameStyle(QFrame::VLine | QFrame::Sunken); return result; } class PatternLineEdit : public QLineEdit { Q_OBJECT public: explicit PatternLineEdit(QWidget *parent = nullptr); private slots: void copyToCode(); void pasteFromCode(); void escapeSelection(); protected: void contextMenuEvent(QContextMenuEvent *event) override; private: QAction *escapeSelectionAction; QAction *copyToCodeAction; QAction *pasteFromCodeAction; }; PatternLineEdit::PatternLineEdit(QWidget *parent) : QLineEdit(parent), escapeSelectionAction(new QAction(tr("Escape Selection"), this)), copyToCodeAction(new QAction(tr("Copy to Code"), this)), pasteFromCodeAction(new QAction(tr("Paste from Code"), this)) { setClearButtonEnabled(true); connect(escapeSelectionAction, &QAction::triggered, this, &PatternLineEdit::escapeSelection); connect(copyToCodeAction, &QAction::triggered, this, &PatternLineEdit::copyToCode); connect(pasteFromCodeAction, &QAction::triggered, this, &PatternLineEdit::pasteFromCode); #if !QT_CONFIG(clipboard) copyToCodeAction->setEnabled(false); pasteFromCodeAction->setEnabled(false); #endif } void PatternLineEdit::escapeSelection() { const QString selection = selectedText(); const QString escapedSelection = QRegularExpression::escape(selection); if (escapedSelection != selection) { QString t = text(); t.replace(selectionStart(), selection.size(), escapedSelection); setText(t); } } void PatternLineEdit::copyToCode() { #if QT_CONFIG(clipboard) QGuiApplication::clipboard()->setText(patternToCode(text())); #endif } void PatternLineEdit::pasteFromCode() { #if QT_CONFIG(clipboard) setText(codeToPattern(QGuiApplication::clipboard()->text())); #endif } void PatternLineEdit::contextMenuEvent(QContextMenuEvent *event) { QMenu *menu = createStandardContextMenu(); menu->setAttribute(Qt::WA_DeleteOnClose); menu->addSeparator(); escapeSelectionAction->setEnabled(hasSelectedText()); menu->addAction(escapeSelectionAction); menu->addSeparator(); menu->addAction(copyToCodeAction); menu->addAction(pasteFromCodeAction); menu->popup(event->globalPos()); } class DisplayLineEdit : public QLineEdit { public: explicit DisplayLineEdit(QWidget *parent = nullptr); }; DisplayLineEdit::DisplayLineEdit(QWidget *parent) : QLineEdit(parent) { setReadOnly(true); QPalette disabledPalette = palette(); disabledPalette.setBrush(QPalette::Base, disabledPalette.brush(QPalette::Disabled, QPalette::Base)); setPalette(disabledPalette); #if QT_CONFIG(clipboard) QAction *copyAction = new QAction(this); copyAction->setText(RegularExpressionDialog::tr("Copy to clipboard")); copyAction->setIcon(QIcon(QStringLiteral(":/images/copy.png"))); connect(copyAction, &QAction::triggered, this, [this] () { QGuiApplication::clipboard()->setText(text()); }); addAction(copyAction, QLineEdit::TrailingPosition); #endif } RegularExpressionDialog::RegularExpressionDialog(QWidget *parent) : QDialog(parent) { setupUi(); setWindowTitle(tr("QRegularExpression Example")); connect(patternLineEdit, &QLineEdit::textChanged, this, &RegularExpressionDialog::refresh); connect(subjectTextEdit, &QPlainTextEdit::textChanged, this, &RegularExpressionDialog::refresh); connect(caseInsensitiveOptionCheckBox, &QCheckBox::toggled, this, &RegularExpressionDialog::refresh); connect(dotMatchesEverythingOptionCheckBox, &QCheckBox::toggled, this, &RegularExpressionDialog::refresh); connect(multilineOptionCheckBox, &QCheckBox::toggled, this, &RegularExpressionDialog::refresh); connect(extendedPatternSyntaxOptionCheckBox, &QCheckBox::toggled, this, &RegularExpressionDialog::refresh); connect(invertedGreedinessOptionCheckBox, &QCheckBox::toggled, this, &RegularExpressionDialog::refresh); connect(dontCaptureOptionCheckBox, &QCheckBox::toggled, this, &RegularExpressionDialog::refresh); connect(useUnicodePropertiesOptionCheckBox, &QCheckBox::toggled, this, &RegularExpressionDialog::refresh); connect(offsetSpinBox, &QSpinBox::valueChanged, this, &RegularExpressionDialog::refresh); connect(matchTypeComboBox, &QComboBox::currentIndexChanged, this, &RegularExpressionDialog::refresh); connect(anchoredMatchOptionCheckBox, &QCheckBox::toggled, this, &RegularExpressionDialog::refresh); connect(dontCheckSubjectStringMatchOptionCheckBox, &QCheckBox::toggled, this, &RegularExpressionDialog::refresh); patternLineEdit->setText(tr("(\\+?\\d+)-(?\\d+)-(?\\w+)")); subjectTextEdit->setPlainText(tr("My office number is +43-152-0123456, my mobile is 001-41-255512 instead.")); refresh(); } void RegularExpressionDialog::setResultUiEnabled(bool enabled) { matchDetailsTreeWidget->setEnabled(enabled); namedGroupsTreeWidget->setEnabled(enabled); replacementTextEdit->setEnabled(enabled); } static void setTextColor(QWidget *widget, const QColor &color) { QPalette palette = widget->palette(); palette.setColor(QPalette::Text, color); widget->setPalette(palette); } void RegularExpressionDialog::refresh() { setUpdatesEnabled(false); const QString pattern = patternLineEdit->text(); const QString text = subjectTextEdit->toPlainText(); offsetSpinBox->setMaximum(qMax(0, text.length() - 1)); escapedPatternLineEdit->setText(patternToCode(pattern)); rawStringLiteralLineEdit->setText(rawStringLiteral(pattern)); setTextColor(patternLineEdit, subjectTextEdit->palette().color(QPalette::Text)); matchDetailsTreeWidget->clear(); namedGroupsTreeWidget->clear(); regexpStatusLabel->setText(QString()); replacementTextEdit->clear(); if (pattern.isEmpty()) { setResultUiEnabled(false); setUpdatesEnabled(true); return; } regularExpression.setPattern(pattern); if (!regularExpression.isValid()) { setTextColor(patternLineEdit, Qt::red); regexpStatusLabel->setText(tr("Invalid: syntax error at position %1 (%2)") .arg(regularExpression.patternErrorOffset()) .arg(regularExpression.errorString())); setResultUiEnabled(false); setUpdatesEnabled(true); return; } setResultUiEnabled(true); QRegularExpression::MatchType matchType = qvariant_cast(matchTypeComboBox->currentData()); QRegularExpression::PatternOptions patternOptions = QRegularExpression::NoPatternOption; QRegularExpression::MatchOptions matchOptions = QRegularExpression::NoMatchOption; if (anchoredMatchOptionCheckBox->isChecked()) matchOptions |= QRegularExpression::AnchorAtOffsetMatchOption; if (dontCheckSubjectStringMatchOptionCheckBox->isChecked()) matchOptions |= QRegularExpression::DontCheckSubjectStringMatchOption; if (caseInsensitiveOptionCheckBox->isChecked()) patternOptions |= QRegularExpression::CaseInsensitiveOption; if (dotMatchesEverythingOptionCheckBox->isChecked()) patternOptions |= QRegularExpression::DotMatchesEverythingOption; if (multilineOptionCheckBox->isChecked()) patternOptions |= QRegularExpression::MultilineOption; if (extendedPatternSyntaxOptionCheckBox->isChecked()) patternOptions |= QRegularExpression::ExtendedPatternSyntaxOption; if (invertedGreedinessOptionCheckBox->isChecked()) patternOptions |= QRegularExpression::InvertedGreedinessOption; if (dontCaptureOptionCheckBox->isChecked()) patternOptions |= QRegularExpression::DontCaptureOption; if (useUnicodePropertiesOptionCheckBox->isChecked()) patternOptions |= QRegularExpression::UseUnicodePropertiesOption; regularExpression.setPatternOptions(patternOptions); const int capturingGroupsCount = regularExpression.captureCount() + 1; const int offset = offsetSpinBox->value(); QRegularExpressionMatchIterator iterator = regularExpression.globalMatch(text, offset, matchType, matchOptions); int i = 0; while (iterator.hasNext()) { QRegularExpressionMatch match = iterator.next(); QTreeWidgetItem *matchDetailTopItem = new QTreeWidgetItem(matchDetailsTreeWidget); matchDetailTopItem->setText(0, QString::number(i)); for (int captureGroupIndex = 0; captureGroupIndex < capturingGroupsCount; ++captureGroupIndex) { QTreeWidgetItem *matchDetailItem = new QTreeWidgetItem(matchDetailTopItem); matchDetailItem->setText(1, QString::number(captureGroupIndex)); matchDetailItem->setText(2, match.captured(captureGroupIndex)); } ++i; } matchDetailsTreeWidget->expandAll(); regexpStatusLabel->setText(tr("Valid")); const QStringList namedCaptureGroups = regularExpression.namedCaptureGroups(); for (int i = 0; i < namedCaptureGroups.size(); ++i) { const QString currentNamedCaptureGroup = namedCaptureGroups.at(i); QTreeWidgetItem *namedGroupItem = new QTreeWidgetItem(namedGroupsTreeWidget); namedGroupItem->setText(0, QString::number(i)); namedGroupItem->setText(1, currentNamedCaptureGroup.isNull() ? tr("") : currentNamedCaptureGroup); } updateReplacement(); setUpdatesEnabled(true); } void RegularExpressionDialog::updateReplacement() { replacementTextEdit->clear(); const QString &replacement = replacementLineEdit->text(); if (!regularExpression.isValid() || replacement.isEmpty()) return; QString replaced = subjectTextEdit->toPlainText(); replaced.replace(regularExpression, replacement); replacementTextEdit->setPlainText(replaced); } void RegularExpressionDialog::setupUi() { auto *mainLayout = new QVBoxLayout(this); mainLayout->addWidget(setupTextUi()); mainLayout->addWidget(createHorizontalSeparator()); auto *horizontalLayout = new QHBoxLayout(); mainLayout->addLayout(horizontalLayout); horizontalLayout->addWidget(setupOptionsUi()); horizontalLayout->addWidget(createVerticalSeparator()); horizontalLayout->addWidget(setupInfoUi()); auto font = QFontDatabase::systemFont(QFontDatabase::FixedFont); patternLineEdit->setFont(font); rawStringLiteralLineEdit->setFont(font); escapedPatternLineEdit->setFont(font); replacementLineEdit->setFont(font); subjectTextEdit->setFont(font); replacementTextEdit->setFont(font); } QWidget *RegularExpressionDialog::setupTextUi() { QWidget *container = new QWidget; QFormLayout *layout = new QFormLayout(container); layout->setFieldGrowthPolicy(QFormLayout::AllNonFixedFieldsGrow); layout->setContentsMargins(QMargins()); QLabel *regexpAndSubjectLabel = new QLabel(tr("

Regular expression and text input

")); layout->addRow(regexpAndSubjectLabel); patternLineEdit = new PatternLineEdit; patternLineEdit->setClearButtonEnabled(true); layout->addRow(tr("&Pattern:"), patternLineEdit); rawStringLiteralLineEdit = new DisplayLineEdit; layout->addRow(tr("&Raw string literal:"), rawStringLiteralLineEdit); escapedPatternLineEdit = new DisplayLineEdit; layout->addRow(tr("&Escaped pattern:"), escapedPatternLineEdit); subjectTextEdit = new QPlainTextEdit; layout->addRow(tr("&Subject text:"), subjectTextEdit); layout->addRow(createHorizontalSeparator()); QLabel *replaceLabel = new QLabel(tr("

Replacement")); layout->addRow(replaceLabel); replacementLineEdit = new QLineEdit; replacementLineEdit->setClearButtonEnabled(true); connect(replacementLineEdit, &QLineEdit::textChanged, this, &RegularExpressionDialog::updateReplacement); layout->addRow(tr("&Replace by:"), replacementLineEdit); replacementLineEdit->setToolTip(tr("Use \\1, \\2... as placeholders for the captured groups.")); replacementTextEdit = new QPlainTextEdit; replacementTextEdit->setReadOnly(true); layout->addRow(tr("Result:"), replacementTextEdit); return container; } QWidget *RegularExpressionDialog::setupOptionsUi() { QWidget *container = new QWidget; QFormLayout *layout = new QFormLayout(container); layout->setFieldGrowthPolicy(QFormLayout::AllNonFixedFieldsGrow); layout->setContentsMargins(QMargins()); layout->addRow(new QLabel(tr("

Options

"))); caseInsensitiveOptionCheckBox = new QCheckBox(tr("Case insensitive (/i)")); dotMatchesEverythingOptionCheckBox = new QCheckBox(tr("Dot matches everything (/s)")); multilineOptionCheckBox = new QCheckBox(tr("Multiline (/m)")); extendedPatternSyntaxOptionCheckBox = new QCheckBox(tr("Extended pattern (/x)")); invertedGreedinessOptionCheckBox = new QCheckBox(tr("Inverted greediness")); dontCaptureOptionCheckBox = new QCheckBox(tr("Don't capture")); useUnicodePropertiesOptionCheckBox = new QCheckBox(tr("Use unicode properties (/u)")); QGridLayout *patternOptionsCheckBoxLayout = new QGridLayout; int gridRow = 0; patternOptionsCheckBoxLayout->addWidget(caseInsensitiveOptionCheckBox, gridRow, 1); patternOptionsCheckBoxLayout->addWidget(dotMatchesEverythingOptionCheckBox, gridRow, 2); ++gridRow; patternOptionsCheckBoxLayout->addWidget(multilineOptionCheckBox, gridRow, 1); patternOptionsCheckBoxLayout->addWidget(extendedPatternSyntaxOptionCheckBox, gridRow, 2); ++gridRow; patternOptionsCheckBoxLayout->addWidget(invertedGreedinessOptionCheckBox, gridRow, 1); patternOptionsCheckBoxLayout->addWidget(dontCaptureOptionCheckBox, gridRow, 2); ++gridRow; patternOptionsCheckBoxLayout->addWidget(useUnicodePropertiesOptionCheckBox, gridRow, 1); layout->addRow(tr("Pattern options:"), patternOptionsCheckBoxLayout); offsetSpinBox = new QSpinBox; layout->addRow(tr("Match &offset:"), offsetSpinBox); matchTypeComboBox = new QComboBox; matchTypeComboBox->addItem(tr("Normal"), QVariant::fromValue(QRegularExpression::NormalMatch)); matchTypeComboBox->addItem(tr("Partial prefer complete"), QVariant::fromValue(QRegularExpression::PartialPreferCompleteMatch)); matchTypeComboBox->addItem(tr("Partial prefer first"), QVariant::fromValue(QRegularExpression::PartialPreferFirstMatch)); matchTypeComboBox->addItem(tr("No match"), QVariant::fromValue(QRegularExpression::NoMatch)); layout->addRow(tr("Match &type:"), matchTypeComboBox); dontCheckSubjectStringMatchOptionCheckBox = new QCheckBox(tr("Don't check subject string")); anchoredMatchOptionCheckBox = new QCheckBox(tr("Anchored match")); QGridLayout *matchOptionsCheckBoxLayout = new QGridLayout; matchOptionsCheckBoxLayout->addWidget(dontCheckSubjectStringMatchOptionCheckBox, 0, 0); matchOptionsCheckBoxLayout->addWidget(anchoredMatchOptionCheckBox, 0, 1); layout->addRow(tr("Match options:"), matchOptionsCheckBoxLayout); return container; } QWidget *RegularExpressionDialog::setupInfoUi() { QWidget *container = new QWidget; QFormLayout *layout = new QFormLayout(container); layout->setFieldGrowthPolicy(QFormLayout::AllNonFixedFieldsGrow); layout->setContentsMargins(QMargins()); QLabel *matchInfoLabel = new QLabel(tr("

Match information

")); layout->addRow(matchInfoLabel); matchDetailsTreeWidget = new QTreeWidget; matchDetailsTreeWidget->setHeaderLabels(QStringList() << tr("Match index") << tr("Group index") << tr("Captured string")); matchDetailsTreeWidget->setSizeAdjustPolicy(QTreeWidget::AdjustToContents); layout->addRow(tr("Match details:"), matchDetailsTreeWidget); layout->addRow(createHorizontalSeparator()); QLabel *regexpInfoLabel = new QLabel(tr("

Regular expression information

")); layout->addRow(regexpInfoLabel); regexpStatusLabel = new QLabel(tr("Valid")); regexpStatusLabel->setWordWrap(true); layout->addRow(tr("Pattern status:"), regexpStatusLabel); namedGroupsTreeWidget = new QTreeWidget; namedGroupsTreeWidget->setHeaderLabels(QStringList() << tr("Index") << tr("Named group")); namedGroupsTreeWidget->setSizeAdjustPolicy(QTreeWidget::AdjustToContents); namedGroupsTreeWidget->setRootIsDecorated(false); layout->addRow(tr("Named groups:"), namedGroupsTreeWidget); return container; } #include "regularexpressiondialog.moc"