1/****************************************************************************
2**
3** Copyright (C) 2016 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author Giuseppe D'Angelo <giuseppe.dangelo@kdab.com>
4** Copyright (C) 2016 Samuel Gaist <samuel.gaist@edeltech.ch>
5** Copyright (C) 2016 The Qt Company Ltd.
6** Contact: https://www.qt.io/licensing/
7**
8** This file is part of the examples of the Qt Toolkit.
9**
10** $QT_BEGIN_LICENSE:BSD$
11** Commercial License Usage
12** Licensees holding valid commercial Qt licenses may use this file in
13** accordance with the commercial license agreement provided with the
14** Software or, alternatively, in accordance with the terms contained in
15** a written agreement between you and The Qt Company. For licensing terms
16** and conditions see https://www.qt.io/terms-conditions. For further
17** information use the contact form at https://www.qt.io/contact-us.
18**
19** BSD License Usage
20** Alternatively, you may use this file under the terms of the BSD license
21** as follows:
22**
23** "Redistribution and use in source and binary forms, with or without
24** modification, are permitted provided that the following conditions are
25** met:
26** * Redistributions of source code must retain the above copyright
27** notice, this list of conditions and the following disclaimer.
28** * Redistributions in binary form must reproduce the above copyright
29** notice, this list of conditions and the following disclaimer in
30** the documentation and/or other materials provided with the
31** distribution.
32** * Neither the name of The Qt Company Ltd nor the names of its
33** contributors may be used to endorse or promote products derived
34** from this software without specific prior written permission.
35**
36**
37** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
38** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
39** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
40** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
41** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
42** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
43** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
44** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
45** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
46** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
47** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
48**
49** $QT_END_LICENSE$
50**
51****************************************************************************/
52
53#include "regularexpressiondialog.h"
54
55#include <QApplication>
56
57#include <QCheckBox>
58#include <QComboBox>
59#include <QLabel>
60#include <QLineEdit>
61#include <QMenu>
62#include <QSpinBox>
63#include <QPlainTextEdit>
64#include <QTreeWidget>
65
66#include <QAction>
67#include <QClipboard>
68#include <QContextMenuEvent>
69
70#include <QHBoxLayout>
71#include <QGridLayout>
72#include <QFormLayout>
73
74#include <QRegularExpression>
75#include <QRegularExpressionMatch>
76#include <QRegularExpressionMatchIterator>
77
78Q_DECLARE_METATYPE(QRegularExpression::MatchType)
79
80static QString rawStringLiteral(QString pattern)
81{
82 pattern.prepend(QLatin1String("R\"RX("));
83 pattern.append(QLatin1String(")RX\""));
84 return pattern;
85}
86
87static QString patternToCode(QString pattern)
88{
89 pattern.replace(QLatin1String("\\"), QLatin1String("\\\\"));
90 pattern.replace(QLatin1String("\""), QLatin1String("\\\""));
91 pattern.prepend(QLatin1Char('"'));
92 pattern.append(QLatin1Char('"'));
93 return pattern;
94}
95
96static QString codeToPattern(QString code)
97{
98 for (int i = 0; i < code.size(); ++i) {
99 if (code.at(i) == QLatin1Char('\\'))
100 code.remove(i, 1);
101 }
102 if (code.startsWith(QLatin1Char('"')) && code.endsWith(QLatin1Char('"'))) {
103 code.chop(1);
104 code.remove(0, 1);
105 }
106 return code;
107}
108
109class PatternLineEdit : public QLineEdit
110{
111 Q_OBJECT
112public:
113 explicit PatternLineEdit(QWidget *parent = nullptr);
114
115private slots:
116 void copyToCode();
117 void pasteFromCode();
118 void escapeSelection();
119
120protected:
121 void contextMenuEvent(QContextMenuEvent *event) override;
122
123private:
124 QAction *escapeSelectionAction;
125 QAction *copyToCodeAction;
126 QAction *pasteFromCodeAction;
127};
128
129PatternLineEdit::PatternLineEdit(QWidget *parent) :
130 QLineEdit(parent),
131 escapeSelectionAction(new QAction(tr("Escape Selection"), this)),
132 copyToCodeAction(new QAction(tr("Copy to Code"), this)),
133 pasteFromCodeAction(new QAction(tr("Paste from Code"), this))
134{
135 setClearButtonEnabled(true);
136 connect(escapeSelectionAction, &QAction::triggered, this, &PatternLineEdit::escapeSelection);
137 connect(copyToCodeAction, &QAction::triggered, this, &PatternLineEdit::copyToCode);
138 connect(pasteFromCodeAction, &QAction::triggered, this, &PatternLineEdit::pasteFromCode);
139#if !QT_CONFIG(clipboard)
140 copyToCodeAction->setEnabled(false);
141 pasteFromCodeAction->setEnabled(false);
142#endif
143}
144
145void PatternLineEdit::escapeSelection()
146{
147 const QString selection = selectedText();
148 const QString escapedSelection = QRegularExpression::escape(selection);
149 if (escapedSelection != selection) {
150 QString t = text();
151 t.replace(selectionStart(), selection.size(), escapedSelection);
152 setText(t);
153 }
154}
155
156void PatternLineEdit::copyToCode()
157{
158#if QT_CONFIG(clipboard)
159 QGuiApplication::clipboard()->setText(patternToCode(text()));
160#endif
161}
162
163void PatternLineEdit::pasteFromCode()
164{
165#if QT_CONFIG(clipboard)
166 setText(codeToPattern(QGuiApplication::clipboard()->text()));
167#endif
168}
169
170void PatternLineEdit::contextMenuEvent(QContextMenuEvent *event)
171{
172 QMenu *menu = createStandardContextMenu();
173 menu->setAttribute(Qt::WA_DeleteOnClose);
174 menu->addSeparator();
175 escapeSelectionAction->setEnabled(hasSelectedText());
176 menu->addAction(escapeSelectionAction);
177 menu->addSeparator();
178 menu->addAction(copyToCodeAction);
179 menu->addAction(pasteFromCodeAction);
180 menu->popup(event->globalPos());
181}
182
183class DisplayLineEdit : public QLineEdit
184{
185public:
186 explicit DisplayLineEdit(QWidget *parent = nullptr);
187};
188
189DisplayLineEdit::DisplayLineEdit(QWidget *parent) : QLineEdit(parent)
190{
191 setReadOnly(true);
192 QPalette disabledPalette = palette();
193 disabledPalette.setBrush(QPalette::Base, disabledPalette.brush(QPalette::Disabled, QPalette::Base));
194 setPalette(disabledPalette);
195
196#if QT_CONFIG(clipboard)
197 QAction *copyAction = new QAction(this);
198 copyAction->setText(RegularExpressionDialog::tr("Copy to clipboard"));
199 copyAction->setIcon(QIcon(QStringLiteral(":/images/copy.png")));
200 connect(copyAction, &QAction::triggered, this,
201 [this] () { QGuiApplication::clipboard()->setText(text()); });
202 addAction(copyAction, QLineEdit::TrailingPosition);
203#endif
204}
205
206RegularExpressionDialog::RegularExpressionDialog(QWidget *parent)
207 : QDialog(parent)
208{
209 setupUi();
210 setWindowTitle(tr("QRegularExpression Example"));
211
212 connect(patternLineEdit, &QLineEdit::textChanged, this, &RegularExpressionDialog::refresh);
213 connect(subjectTextEdit, &QPlainTextEdit::textChanged, this, &RegularExpressionDialog::refresh);
214
215 connect(caseInsensitiveOptionCheckBox, &QCheckBox::toggled, this, &RegularExpressionDialog::refresh);
216 connect(dotMatchesEverythingOptionCheckBox, &QCheckBox::toggled, this, &RegularExpressionDialog::refresh);
217 connect(multilineOptionCheckBox, &QCheckBox::toggled, this, &RegularExpressionDialog::refresh);
218 connect(extendedPatternSyntaxOptionCheckBox, &QCheckBox::toggled, this, &RegularExpressionDialog::refresh);
219 connect(invertedGreedinessOptionCheckBox, &QCheckBox::toggled, this, &RegularExpressionDialog::refresh);
220 connect(dontCaptureOptionCheckBox, &QCheckBox::toggled, this, &RegularExpressionDialog::refresh);
221 connect(useUnicodePropertiesOptionCheckBox, &QCheckBox::toggled, this, &RegularExpressionDialog::refresh);
222
223 connect(offsetSpinBox, &QSpinBox::valueChanged,
224 this, &RegularExpressionDialog::refresh);
225
226 connect(matchTypeComboBox, &QComboBox::currentIndexChanged,
227 this, &RegularExpressionDialog::refresh);
228
229 connect(anchoredMatchOptionCheckBox, &QCheckBox::toggled, this, &RegularExpressionDialog::refresh);
230 connect(dontCheckSubjectStringMatchOptionCheckBox, &QCheckBox::toggled, this, &RegularExpressionDialog::refresh);
231
232 patternLineEdit->setText(tr("(\\+?\\d+)-(?<prefix>\\d+)-(?<number>\\w+)"));
233 subjectTextEdit->setPlainText(tr("My office number is +43-152-0123456, my mobile is 001-41-255512 instead."));
234
235 refresh();
236}
237
238void RegularExpressionDialog::setResultUiEnabled(bool enabled)
239{
240 matchDetailsTreeWidget->setEnabled(enabled);
241 namedGroupsTreeWidget->setEnabled(enabled);
242}
243
244static void setTextColor(QWidget *widget, const QColor &color)
245{
246 QPalette palette = widget->palette();
247 palette.setColor(QPalette::Text, color);
248 widget->setPalette(palette);
249}
250
251void RegularExpressionDialog::refresh()
252{
253 setUpdatesEnabled(false);
254
255 const QString pattern = patternLineEdit->text();
256 const QString text = subjectTextEdit->toPlainText();
257
258 offsetSpinBox->setMaximum(qMax(0, text.length() - 1));
259
260 escapedPatternLineEdit->setText(patternToCode(pattern));
261 rawStringLiteralLineEdit->setText(rawStringLiteral(pattern));
262
263 setTextColor(patternLineEdit, subjectTextEdit->palette().color(QPalette::Text));
264 matchDetailsTreeWidget->clear();
265 namedGroupsTreeWidget->clear();
266 regexpStatusLabel->setText(QString());
267
268 if (pattern.isEmpty()) {
269 setResultUiEnabled(false);
270 setUpdatesEnabled(true);
271 return;
272 }
273
274 QRegularExpression rx(pattern);
275 if (!rx.isValid()) {
276 setTextColor(patternLineEdit, Qt::red);
277 regexpStatusLabel->setText(tr("Invalid: syntax error at position %1 (%2)")
278 .arg(rx.patternErrorOffset())
279 .arg(rx.errorString()));
280 setResultUiEnabled(false);
281 setUpdatesEnabled(true);
282 return;
283 }
284
285 setResultUiEnabled(true);
286
287 QRegularExpression::MatchType matchType = qvariant_cast<QRegularExpression::MatchType>(matchTypeComboBox->currentData());
288 QRegularExpression::PatternOptions patternOptions = QRegularExpression::NoPatternOption;
289 QRegularExpression::MatchOptions matchOptions = QRegularExpression::NoMatchOption;
290
291 if (anchoredMatchOptionCheckBox->isChecked())
292 matchOptions |= QRegularExpression::AnchorAtOffsetMatchOption;
293 if (dontCheckSubjectStringMatchOptionCheckBox->isChecked())
294 matchOptions |= QRegularExpression::DontCheckSubjectStringMatchOption;
295
296 if (caseInsensitiveOptionCheckBox->isChecked())
297 patternOptions |= QRegularExpression::CaseInsensitiveOption;
298 if (dotMatchesEverythingOptionCheckBox->isChecked())
299 patternOptions |= QRegularExpression::DotMatchesEverythingOption;
300 if (multilineOptionCheckBox->isChecked())
301 patternOptions |= QRegularExpression::MultilineOption;
302 if (extendedPatternSyntaxOptionCheckBox->isChecked())
303 patternOptions |= QRegularExpression::ExtendedPatternSyntaxOption;
304 if (invertedGreedinessOptionCheckBox->isChecked())
305 patternOptions |= QRegularExpression::InvertedGreedinessOption;
306 if (dontCaptureOptionCheckBox->isChecked())
307 patternOptions |= QRegularExpression::DontCaptureOption;
308 if (useUnicodePropertiesOptionCheckBox->isChecked())
309 patternOptions |= QRegularExpression::UseUnicodePropertiesOption;
310
311 rx.setPatternOptions(patternOptions);
312
313 const int capturingGroupsCount = rx.captureCount() + 1;
314
315 QRegularExpressionMatchIterator iterator = rx.globalMatch(text, offsetSpinBox->value(), matchType, matchOptions);
316 int i = 0;
317
318 while (iterator.hasNext()) {
319 QRegularExpressionMatch match = iterator.next();
320
321 QTreeWidgetItem *matchDetailTopItem = new QTreeWidgetItem(matchDetailsTreeWidget);
322 matchDetailTopItem->setText(0, QString::number(i));
323
324 for (int captureGroupIndex = 0; captureGroupIndex < capturingGroupsCount; ++captureGroupIndex) {
325 QTreeWidgetItem *matchDetailItem = new QTreeWidgetItem(matchDetailTopItem);
326 matchDetailItem->setText(1, QString::number(captureGroupIndex));
327 matchDetailItem->setText(2, match.captured(captureGroupIndex));
328 }
329
330 ++i;
331 }
332
333 matchDetailsTreeWidget->expandAll();
334
335 regexpStatusLabel->setText(tr("Valid"));
336
337 const QStringList namedCaptureGroups = rx.namedCaptureGroups();
338 for (int i = 0; i < namedCaptureGroups.size(); ++i) {
339 const QString currentNamedCaptureGroup = namedCaptureGroups.at(i);
340
341 QTreeWidgetItem *namedGroupItem = new QTreeWidgetItem(namedGroupsTreeWidget);
342 namedGroupItem->setText(0, QString::number(i));
343 namedGroupItem->setText(1, currentNamedCaptureGroup.isNull() ? tr("<no name>") : currentNamedCaptureGroup);
344 }
345
346
347 setUpdatesEnabled(true);
348}
349
350void RegularExpressionDialog::setupUi()
351{
352 QWidget *leftHalfContainer = setupLeftUi();
353
354 QFrame *verticalSeparator = new QFrame;
355 verticalSeparator->setFrameStyle(QFrame::VLine | QFrame::Sunken);
356
357 QWidget *rightHalfContainer = setupRightUi();
358
359 QHBoxLayout *mainLayout = new QHBoxLayout;
360 mainLayout->addWidget(leftHalfContainer);
361 mainLayout->addWidget(verticalSeparator);
362 mainLayout->addWidget(rightHalfContainer);
363
364 setLayout(mainLayout);
365}
366
367QWidget *RegularExpressionDialog::setupLeftUi()
368{
369 QWidget *container = new QWidget;
370
371 QFormLayout *layout = new QFormLayout(container);
372 layout->setFieldGrowthPolicy(QFormLayout::AllNonFixedFieldsGrow);
373 layout->setContentsMargins(QMargins());
374
375 QLabel *regexpAndSubjectLabel = new QLabel(tr("<h3>Regular expression and text input</h3>"));
376 layout->addRow(regexpAndSubjectLabel);
377
378 patternLineEdit = new PatternLineEdit;
379 patternLineEdit->setClearButtonEnabled(true);
380 layout->addRow(tr("&Pattern:"), patternLineEdit);
381
382 rawStringLiteralLineEdit = new DisplayLineEdit;
383 layout->addRow(tr("&Raw string literal:"), rawStringLiteralLineEdit);
384 escapedPatternLineEdit = new DisplayLineEdit;
385 layout->addRow(tr("&Escaped pattern:"), escapedPatternLineEdit);
386
387 subjectTextEdit = new QPlainTextEdit;
388 layout->addRow(tr("&Subject text:"), subjectTextEdit);
389
390 caseInsensitiveOptionCheckBox = new QCheckBox(tr("Case insensitive (/i)"));
391 dotMatchesEverythingOptionCheckBox = new QCheckBox(tr("Dot matches everything (/s)"));
392 multilineOptionCheckBox = new QCheckBox(tr("Multiline (/m)"));
393 extendedPatternSyntaxOptionCheckBox = new QCheckBox(tr("Extended pattern (/x)"));
394 invertedGreedinessOptionCheckBox = new QCheckBox(tr("Inverted greediness"));
395 dontCaptureOptionCheckBox = new QCheckBox(tr("Don't capture"));
396 useUnicodePropertiesOptionCheckBox = new QCheckBox(tr("Use unicode properties (/u)"));
397
398 QGridLayout *patternOptionsCheckBoxLayout = new QGridLayout;
399 int gridRow = 0;
400 patternOptionsCheckBoxLayout->addWidget(caseInsensitiveOptionCheckBox, gridRow, 1);
401 patternOptionsCheckBoxLayout->addWidget(dotMatchesEverythingOptionCheckBox, gridRow, 2);
402 ++gridRow;
403 patternOptionsCheckBoxLayout->addWidget(multilineOptionCheckBox, gridRow, 1);
404 patternOptionsCheckBoxLayout->addWidget(extendedPatternSyntaxOptionCheckBox, gridRow, 2);
405 ++gridRow;
406 patternOptionsCheckBoxLayout->addWidget(invertedGreedinessOptionCheckBox, gridRow, 1);
407 patternOptionsCheckBoxLayout->addWidget(dontCaptureOptionCheckBox, gridRow, 2);
408 ++gridRow;
409 patternOptionsCheckBoxLayout->addWidget(useUnicodePropertiesOptionCheckBox, gridRow, 1);
410
411 layout->addRow(tr("Pattern options:"), patternOptionsCheckBoxLayout);
412
413 offsetSpinBox = new QSpinBox;
414 layout->addRow(tr("Match &offset:"), offsetSpinBox);
415
416 matchTypeComboBox = new QComboBox;
417 matchTypeComboBox->addItem(tr("Normal"), QVariant::fromValue(QRegularExpression::NormalMatch));
418 matchTypeComboBox->addItem(tr("Partial prefer complete"), QVariant::fromValue(QRegularExpression::PartialPreferCompleteMatch));
419 matchTypeComboBox->addItem(tr("Partial prefer first"), QVariant::fromValue(QRegularExpression::PartialPreferFirstMatch));
420 matchTypeComboBox->addItem(tr("No match"), QVariant::fromValue(QRegularExpression::NoMatch));
421 layout->addRow(tr("Match &type:"), matchTypeComboBox);
422
423 dontCheckSubjectStringMatchOptionCheckBox = new QCheckBox(tr("Don't check subject string"));
424 anchoredMatchOptionCheckBox = new QCheckBox(tr("Anchored match"));
425
426 QGridLayout *matchOptionsCheckBoxLayout = new QGridLayout;
427 matchOptionsCheckBoxLayout->addWidget(dontCheckSubjectStringMatchOptionCheckBox, 0, 0);
428 matchOptionsCheckBoxLayout->addWidget(anchoredMatchOptionCheckBox, 0, 1);
429 layout->addRow(tr("Match options:"), matchOptionsCheckBoxLayout);
430
431 return container;
432}
433
434QWidget *RegularExpressionDialog::setupRightUi()
435{
436 QWidget *container = new QWidget;
437
438 QFormLayout *layout = new QFormLayout(container);
439 layout->setFieldGrowthPolicy(QFormLayout::AllNonFixedFieldsGrow);
440 layout->setContentsMargins(QMargins());
441
442 QLabel *matchInfoLabel = new QLabel(tr("<h3>Match information</h3>"));
443 layout->addRow(matchInfoLabel);
444
445 matchDetailsTreeWidget = new QTreeWidget;
446 matchDetailsTreeWidget->setHeaderLabels(QStringList() << tr("Match index") << tr("Group index") << tr("Captured string"));
447 matchDetailsTreeWidget->setSizeAdjustPolicy(QTreeWidget::AdjustToContents);
448 layout->addRow(tr("Match details:"), matchDetailsTreeWidget);
449
450 QFrame *horizontalSeparator = new QFrame;
451 horizontalSeparator->setFrameStyle(QFrame::HLine | QFrame::Sunken);
452 layout->addRow(horizontalSeparator);
453
454 QLabel *regexpInfoLabel = new QLabel(tr("<h3>Regular expression information</h3>"));
455 layout->addRow(regexpInfoLabel);
456
457 regexpStatusLabel = new QLabel(tr("Valid"));
458 regexpStatusLabel->setWordWrap(true);
459 layout->addRow(tr("Pattern status:"), regexpStatusLabel);
460
461 namedGroupsTreeWidget = new QTreeWidget;
462 namedGroupsTreeWidget->setHeaderLabels(QStringList() << tr("Index") << tr("Named group"));
463 namedGroupsTreeWidget->setSizeAdjustPolicy(QTreeWidget::AdjustToContents);
464 namedGroupsTreeWidget->setRootIsDecorated(false);
465 layout->addRow(tr("Named groups:"), namedGroupsTreeWidget);
466
467 return container;
468}
469
470#include "regularexpressiondialog.moc"
471