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(s: QLatin1String("R\"RX("));
83 pattern.append(s: QLatin1String(")RX\""));
84 return pattern;
85}
86
87static QString patternToCode(QString pattern)
88{
89 pattern.replace(before: QLatin1String("\\"), after: QLatin1String("\\\\"));
90 pattern.replace(before: QLatin1String("\""), after: QLatin1String("\\\""));
91 pattern.prepend(c: QLatin1Char('"'));
92 pattern.append(c: 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, len: 1);
101 }
102 if (code.startsWith(c: QLatin1Char('"')) && code.endsWith(c: QLatin1Char('"'))) {
103 code.chop(n: 1);
104 code.remove(i: 0, len: 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(s: "Escape Selection"), this)),
132 copyToCodeAction(new QAction(tr(s: "Copy to Code"), this)),
133 pasteFromCodeAction(new QAction(tr(s: "Paste from Code"), this))
134{
135 setClearButtonEnabled(true);
136 connect(sender: escapeSelectionAction, signal: &QAction::triggered, receiver: this, slot: &PatternLineEdit::escapeSelection);
137 connect(sender: copyToCodeAction, signal: &QAction::triggered, receiver: this, slot: &PatternLineEdit::copyToCode);
138 connect(sender: pasteFromCodeAction, signal: &QAction::triggered, receiver: this, slot: &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(str: selection);
149 if (escapedSelection != selection) {
150 QString t = text();
151 t.replace(i: selectionStart(), len: selection.size(), after: escapedSelection);
152 setText(t);
153 }
154}
155
156void PatternLineEdit::copyToCode()
157{
158#if QT_CONFIG(clipboard)
159 QGuiApplication::clipboard()->setText(patternToCode(pattern: text()));
160#endif
161}
162
163void PatternLineEdit::pasteFromCode()
164{
165#if QT_CONFIG(clipboard)
166 setText(codeToPattern(code: 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(action: escapeSelectionAction);
177 menu->addSeparator();
178 menu->addAction(action: copyToCodeAction);
179 menu->addAction(action: pasteFromCodeAction);
180 menu->popup(pos: 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(acr: QPalette::Base, abrush: disabledPalette.brush(cg: QPalette::Disabled, cr: QPalette::Base));
194 setPalette(disabledPalette);
195
196#if QT_CONFIG(clipboard)
197 QAction *copyAction = new QAction(this);
198 copyAction->setText(RegularExpressionDialog::tr(s: "Copy to clipboard"));
199 copyAction->setIcon(QIcon(QStringLiteral(":/images/copy.png")));
200 connect(sender: copyAction, signal: &QAction::triggered, context: this,
201 slot: [this] () { QGuiApplication::clipboard()->setText(text()); });
202 addAction(action: copyAction, position: QLineEdit::TrailingPosition);
203#endif
204}
205
206RegularExpressionDialog::RegularExpressionDialog(QWidget *parent)
207 : QDialog(parent)
208{
209 setupUi();
210 setWindowTitle(tr(s: "QRegularExpression Example"));
211
212 connect(sender: patternLineEdit, signal: &QLineEdit::textChanged, receiver: this, slot: &RegularExpressionDialog::refresh);
213 connect(sender: subjectTextEdit, signal: &QPlainTextEdit::textChanged, receiver: this, slot: &RegularExpressionDialog::refresh);
214
215 connect(sender: caseInsensitiveOptionCheckBox, signal: &QCheckBox::toggled, receiver: this, slot: &RegularExpressionDialog::refresh);
216 connect(sender: dotMatchesEverythingOptionCheckBox, signal: &QCheckBox::toggled, receiver: this, slot: &RegularExpressionDialog::refresh);
217 connect(sender: multilineOptionCheckBox, signal: &QCheckBox::toggled, receiver: this, slot: &RegularExpressionDialog::refresh);
218 connect(sender: extendedPatternSyntaxOptionCheckBox, signal: &QCheckBox::toggled, receiver: this, slot: &RegularExpressionDialog::refresh);
219 connect(sender: invertedGreedinessOptionCheckBox, signal: &QCheckBox::toggled, receiver: this, slot: &RegularExpressionDialog::refresh);
220 connect(sender: dontCaptureOptionCheckBox, signal: &QCheckBox::toggled, receiver: this, slot: &RegularExpressionDialog::refresh);
221 connect(sender: useUnicodePropertiesOptionCheckBox, signal: &QCheckBox::toggled, receiver: this, slot: &RegularExpressionDialog::refresh);
222
223 connect(sender: offsetSpinBox, signal: QOverload<int>::of(ptr: &QSpinBox::valueChanged),
224 receiver: this, slot: &RegularExpressionDialog::refresh);
225
226 connect(sender: matchTypeComboBox, signal: QOverload<int>::of(ptr: &QComboBox::currentIndexChanged),
227 receiver: this, slot: &RegularExpressionDialog::refresh);
228
229 connect(sender: anchoredMatchOptionCheckBox, signal: &QCheckBox::toggled, receiver: this, slot: &RegularExpressionDialog::refresh);
230 connect(sender: dontCheckSubjectStringMatchOptionCheckBox, signal: &QCheckBox::toggled, receiver: this, slot: &RegularExpressionDialog::refresh);
231
232 patternLineEdit->setText(tr(s: "(\\+?\\d+)-(?<prefix>\\d+)-(?<number>\\w+)"));
233 subjectTextEdit->setPlainText(tr(s: "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(acr: QPalette::Text, acolor: 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(a: 0, b: text.length() - 1));
259
260 escapedPatternLineEdit->setText(patternToCode(pattern));
261 rawStringLiteralLineEdit->setText(rawStringLiteral(pattern));
262
263 setTextColor(widget: patternLineEdit, color: subjectTextEdit->palette().color(cr: 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(widget: patternLineEdit, color: Qt::red);
277 regexpStatusLabel->setText(tr(s: "Invalid: syntax error at position %1 (%2)")
278 .arg(a: rx.patternErrorOffset())
279 .arg(a: rx.errorString()));
280 setResultUiEnabled(false);
281 setUpdatesEnabled(true);
282 return;
283 }
284
285 setResultUiEnabled(true);
286
287 QRegularExpression::MatchType matchType = qvariant_cast<QRegularExpression::MatchType>(v: matchTypeComboBox->currentData());
288 QRegularExpression::PatternOptions patternOptions = QRegularExpression::NoPatternOption;
289 QRegularExpression::MatchOptions matchOptions = QRegularExpression::NoMatchOption;
290
291 if (anchoredMatchOptionCheckBox->isChecked())
292 matchOptions |= QRegularExpression::AnchoredMatchOption;
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(subject: text, offset: 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(column: 0, atext: QString::number(i));
323
324 for (int captureGroupIndex = 0; captureGroupIndex < capturingGroupsCount; ++captureGroupIndex) {
325 QTreeWidgetItem *matchDetailItem = new QTreeWidgetItem(matchDetailTopItem);
326 matchDetailItem->setText(column: 1, atext: QString::number(captureGroupIndex));
327 matchDetailItem->setText(column: 2, atext: match.captured(nth: captureGroupIndex));
328 }
329
330 ++i;
331 }
332
333 matchDetailsTreeWidget->expandAll();
334
335 regexpStatusLabel->setText(tr(s: "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(column: 0, atext: QString::number(i));
343 namedGroupItem->setText(column: 1, atext: currentNamedCaptureGroup.isNull() ? tr(s: "<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(s: "<h3>Regular expression and text input</h3>"));
376 layout->addRow(widget: regexpAndSubjectLabel);
377
378 patternLineEdit = new PatternLineEdit;
379 patternLineEdit->setClearButtonEnabled(true);
380 layout->addRow(labelText: tr(s: "&Pattern:"), field: patternLineEdit);
381
382 rawStringLiteralLineEdit = new DisplayLineEdit;
383 layout->addRow(labelText: tr(s: "&Raw string literal:"), field: rawStringLiteralLineEdit);
384 escapedPatternLineEdit = new DisplayLineEdit;
385 layout->addRow(labelText: tr(s: "&Escaped pattern:"), field: escapedPatternLineEdit);
386
387 subjectTextEdit = new QPlainTextEdit;
388 layout->addRow(labelText: tr(s: "&Subject text:"), field: subjectTextEdit);
389
390 caseInsensitiveOptionCheckBox = new QCheckBox(tr(s: "Case insensitive (/i)"));
391 dotMatchesEverythingOptionCheckBox = new QCheckBox(tr(s: "Dot matches everything (/s)"));
392 multilineOptionCheckBox = new QCheckBox(tr(s: "Multiline (/m)"));
393 extendedPatternSyntaxOptionCheckBox = new QCheckBox(tr(s: "Extended pattern (/x)"));
394 invertedGreedinessOptionCheckBox = new QCheckBox(tr(s: "Inverted greediness"));
395 dontCaptureOptionCheckBox = new QCheckBox(tr(s: "Don't capture"));
396 useUnicodePropertiesOptionCheckBox = new QCheckBox(tr(s: "Use unicode properties (/u)"));
397
398 QGridLayout *patternOptionsCheckBoxLayout = new QGridLayout;
399 int gridRow = 0;
400 patternOptionsCheckBoxLayout->addWidget(caseInsensitiveOptionCheckBox, row: gridRow, column: 1);
401 patternOptionsCheckBoxLayout->addWidget(dotMatchesEverythingOptionCheckBox, row: gridRow, column: 2);
402 ++gridRow;
403 patternOptionsCheckBoxLayout->addWidget(multilineOptionCheckBox, row: gridRow, column: 1);
404 patternOptionsCheckBoxLayout->addWidget(extendedPatternSyntaxOptionCheckBox, row: gridRow, column: 2);
405 ++gridRow;
406 patternOptionsCheckBoxLayout->addWidget(invertedGreedinessOptionCheckBox, row: gridRow, column: 1);
407 patternOptionsCheckBoxLayout->addWidget(dontCaptureOptionCheckBox, row: gridRow, column: 2);
408 ++gridRow;
409 patternOptionsCheckBoxLayout->addWidget(useUnicodePropertiesOptionCheckBox, row: gridRow, column: 1);
410
411 layout->addRow(labelText: tr(s: "Pattern options:"), field: patternOptionsCheckBoxLayout);
412
413 offsetSpinBox = new QSpinBox;
414 layout->addRow(labelText: tr(s: "Match &offset:"), field: offsetSpinBox);
415
416 matchTypeComboBox = new QComboBox;
417 matchTypeComboBox->addItem(atext: tr(s: "Normal"), auserData: QVariant::fromValue(value: QRegularExpression::NormalMatch));
418 matchTypeComboBox->addItem(atext: tr(s: "Partial prefer complete"), auserData: QVariant::fromValue(value: QRegularExpression::PartialPreferCompleteMatch));
419 matchTypeComboBox->addItem(atext: tr(s: "Partial prefer first"), auserData: QVariant::fromValue(value: QRegularExpression::PartialPreferFirstMatch));
420 matchTypeComboBox->addItem(atext: tr(s: "No match"), auserData: QVariant::fromValue(value: QRegularExpression::NoMatch));
421 layout->addRow(labelText: tr(s: "Match &type:"), field: matchTypeComboBox);
422
423 dontCheckSubjectStringMatchOptionCheckBox = new QCheckBox(tr(s: "Don't check subject string"));
424 anchoredMatchOptionCheckBox = new QCheckBox(tr(s: "Anchored match"));
425
426 QGridLayout *matchOptionsCheckBoxLayout = new QGridLayout;
427 matchOptionsCheckBoxLayout->addWidget(dontCheckSubjectStringMatchOptionCheckBox, row: 0, column: 0);
428 matchOptionsCheckBoxLayout->addWidget(anchoredMatchOptionCheckBox, row: 0, column: 1);
429 layout->addRow(labelText: tr(s: "Match options:"), field: 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(s: "<h3>Match information</h3>"));
443 layout->addRow(widget: matchInfoLabel);
444
445 matchDetailsTreeWidget = new QTreeWidget;
446 matchDetailsTreeWidget->setHeaderLabels(QStringList() << tr(s: "Match index") << tr(s: "Group index") << tr(s: "Captured string"));
447 matchDetailsTreeWidget->setSizeAdjustPolicy(QTreeWidget::AdjustToContents);
448 layout->addRow(labelText: tr(s: "Match details:"), field: matchDetailsTreeWidget);
449
450 QFrame *horizontalSeparator = new QFrame;
451 horizontalSeparator->setFrameStyle(QFrame::HLine | QFrame::Sunken);
452 layout->addRow(widget: horizontalSeparator);
453
454 QLabel *regexpInfoLabel = new QLabel(tr(s: "<h3>Regular expression information</h3>"));
455 layout->addRow(widget: regexpInfoLabel);
456
457 regexpStatusLabel = new QLabel(tr(s: "Valid"));
458 regexpStatusLabel->setWordWrap(true);
459 layout->addRow(labelText: tr(s: "Pattern status:"), field: regexpStatusLabel);
460
461 namedGroupsTreeWidget = new QTreeWidget;
462 namedGroupsTreeWidget->setHeaderLabels(QStringList() << tr(s: "Index") << tr(s: "Named group"));
463 namedGroupsTreeWidget->setSizeAdjustPolicy(QTreeWidget::AdjustToContents);
464 namedGroupsTreeWidget->setRootIsDecorated(false);
465 layout->addRow(labelText: tr(s: "Named groups:"), field: namedGroupsTreeWidget);
466
467 return container;
468}
469
470#include "regularexpressiondialog.moc"
471

source code of qtbase/examples/widgets/tools/regularexpression/regularexpressiondialog.cpp