1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
3
4#include "textpropertyeditor_p.h"
5#include "propertylineedit_p.h"
6#include "stylesheeteditor_p.h"
7
8#include <QtWidgets/qlineedit.h>
9#include <QtGui/qvalidator.h>
10#include <QtGui/qevent.h>
11#include <QtWidgets/qcompleter.h>
12#include <QtWidgets/qabstractitemview.h>
13#include <QtCore/qregularexpression.h>
14#include <QtCore/qurl.h>
15#include <QtCore/qfile.h>
16#include <QtCore/qdebug.h>
17
18QT_BEGIN_NAMESPACE
19
20using namespace Qt::StringLiterals;
21
22namespace {
23 const QChar NewLineChar(u'\n');
24 const auto EscapedNewLine = "\\n"_L1;
25
26 // A validator that replaces offending strings
27 class ReplacementValidator : public QValidator {
28 public:
29 ReplacementValidator (QObject * parent,
30 const QString &offending,
31 const QString &replacement);
32 void fixup ( QString & input ) const override;
33 State validate ( QString & input, int &pos) const override;
34 private:
35 const QString m_offending;
36 const QString m_replacement;
37 };
38
39 ReplacementValidator::ReplacementValidator (QObject * parent,
40 const QString &offending,
41 const QString &replacement) :
42 QValidator(parent ),
43 m_offending(offending),
44 m_replacement(replacement)
45 {
46 }
47
48 void ReplacementValidator::fixup ( QString & input ) const {
49 input.replace(before: m_offending, after: m_replacement);
50 }
51
52 QValidator::State ReplacementValidator::validate ( QString & input, int &/* pos */) const {
53 fixup (input);
54 return Acceptable;
55 }
56
57 // A validator for style sheets. Does newline handling and validates sheets.
58 class StyleSheetValidator : public ReplacementValidator {
59 public:
60 StyleSheetValidator (QObject * parent);
61 State validate(QString & input, int &pos) const override;
62 };
63
64 StyleSheetValidator::StyleSheetValidator (QObject * parent) :
65 ReplacementValidator(parent, NewLineChar, EscapedNewLine)
66 {
67 }
68
69 QValidator::State StyleSheetValidator::validate ( QString & input, int &pos) const
70 {
71 // base class
72 const State state = ReplacementValidator:: validate(input, pos);
73 if (state != Acceptable)
74 return state;
75 // now check style sheet, create string with newlines
76 const QString styleSheet = qdesigner_internal::TextPropertyEditor::editorStringToString(s: input, validationMode: qdesigner_internal::ValidationStyleSheet);
77 const bool valid = qdesigner_internal::StyleSheetEditorDialog::isStyleSheetValid(styleSheet);
78 return valid ? Acceptable : Intermediate;
79 }
80
81 // A validator for URLs based on QUrl. Enforces complete protocol
82 // specification with a completer (adds a trailing slash)
83 class UrlValidator : public QValidator {
84 public:
85 UrlValidator(QCompleter *completer, QObject *parent);
86
87 State validate(QString &input, int &pos) const override;
88 void fixup(QString &input) const override;
89 private:
90 QUrl guessUrlFromString(const QString &string) const;
91 QCompleter *m_completer;
92 };
93
94 UrlValidator::UrlValidator(QCompleter *completer, QObject *parent) :
95 QValidator(parent),
96 m_completer(completer)
97 {
98 }
99
100 QValidator::State UrlValidator::validate(QString &input, int &pos) const
101 {
102 Q_UNUSED(pos);
103
104 if (input.isEmpty())
105 return Acceptable;
106
107 const QUrl url(input, QUrl::StrictMode);
108
109 if (!url.isValid() || url.isEmpty())
110 return Intermediate;
111
112 if (url.scheme().isEmpty())
113 return Intermediate;
114
115 if (url.host().isEmpty() && url.path().isEmpty())
116 return Intermediate;
117
118 return Acceptable;
119 }
120
121 void UrlValidator::fixup(QString &input) const
122 {
123 // Don't try to fixup if the user is busy selecting a completion proposal
124 if (const QAbstractItemView *iv = m_completer->popup()) {
125 if (iv->isVisible())
126 return;
127 }
128
129 input = guessUrlFromString(string: input).toString();
130 }
131
132 QUrl UrlValidator::guessUrlFromString(const QString &string) const
133 {
134 const QString urlStr = string.trimmed();
135 const QRegularExpression qualifiedUrl(u"^[a-zA-Z]+\\:.*$"_s);
136 Q_ASSERT(qualifiedUrl.isValid());
137
138 // Check if it looks like a qualified URL. Try parsing it and see.
139 const bool hasSchema = qualifiedUrl.match(subject: urlStr).hasMatch();
140 if (hasSchema) {
141 const QUrl url(urlStr, QUrl::TolerantMode);
142 if (url.isValid())
143 return url;
144 }
145
146 // Might be a Qt resource
147 if (string.startsWith(s: ":/"_L1))
148 return QUrl("qrc"_L1 + string);
149
150 // Might be a file.
151 if (QFile::exists(fileName: urlStr))
152 return QUrl::fromLocalFile(localfile: urlStr);
153
154 // Might be a short url - try to detect the schema.
155 if (!hasSchema) {
156 const int dotIndex = urlStr.indexOf(c: u'.');
157 if (dotIndex != -1) {
158 const QString prefix = urlStr.left(n: dotIndex).toLower();
159 QString urlString;
160 if (prefix == "ftp"_L1)
161 urlString += prefix;
162 else
163 urlString += "http"_L1;
164 urlString += "://"_L1;
165 urlString += urlStr;
166 const QUrl url(urlString, QUrl::TolerantMode);
167 if (url.isValid())
168 return url;
169 }
170 }
171
172 // Fall back to QUrl's own tolerant parser.
173 return QUrl(string, QUrl::TolerantMode);
174 }
175}
176
177namespace qdesigner_internal {
178 // TextPropertyEditor
179 TextPropertyEditor::TextPropertyEditor(QWidget *parent,
180 EmbeddingMode embeddingMode,
181 TextPropertyValidationMode validationMode) :
182 QWidget(parent),
183 m_lineEdit(new PropertyLineEdit(this))
184 {
185 switch (embeddingMode) {
186 case EmbeddingNone:
187 break;
188 case EmbeddingTreeView:
189 m_lineEdit->setFrame(false);
190 break;
191 case EmbeddingInPlace:
192 m_lineEdit->setFrame(false);
193 Q_ASSERT(parent);
194 m_lineEdit->setBackgroundRole(parent->backgroundRole());
195 break;
196 }
197
198 setFocusProxy(m_lineEdit);
199
200 connect(sender: m_lineEdit,signal: &QLineEdit::editingFinished, context: this, slot: &TextPropertyEditor::editingFinished);
201 connect(sender: m_lineEdit,signal: &QLineEdit::returnPressed, context: this, slot: &TextPropertyEditor::slotEditingFinished);
202 connect(sender: m_lineEdit,signal: &QLineEdit::textChanged, context: this, slot: &TextPropertyEditor::slotTextChanged);
203 connect(sender: m_lineEdit,signal: &QLineEdit::textEdited, context: this, slot: &TextPropertyEditor::slotTextEdited);
204
205 setTextPropertyValidationMode(validationMode);
206 }
207
208 void TextPropertyEditor::setTextPropertyValidationMode(TextPropertyValidationMode vm) {
209 m_validationMode = vm;
210 m_lineEdit->setWantNewLine(multiLine(validationMode: m_validationMode));
211 switch (m_validationMode) {
212 case ValidationStyleSheet:
213 m_lineEdit->setValidator(new StyleSheetValidator(m_lineEdit));
214 m_lineEdit->setCompleter(nullptr);
215 break;
216 case ValidationMultiLine:
217 case ValidationRichText:
218 // Set a validator that replaces newline characters by literal "\\n".
219 // While it is not possible to actually type a newline characters,
220 // it can be pasted into the line edit.
221 m_lineEdit->setValidator(new ReplacementValidator(m_lineEdit, NewLineChar, EscapedNewLine));
222 m_lineEdit->setCompleter(nullptr);
223 break;
224 case ValidationSingleLine:
225 // Set a validator that replaces newline characters by a blank.
226 m_lineEdit->setValidator(new ReplacementValidator(m_lineEdit, NewLineChar, QString(u' ')));
227 m_lineEdit->setCompleter(nullptr);
228 break;
229 case ValidationObjectName:
230 setRegularExpressionValidator(u"^[_a-zA-Z][_a-zA-Z0-9]{1,1023}$"_s);
231 m_lineEdit->setCompleter(nullptr);
232 break;
233 case ValidationObjectNameScope:
234 setRegularExpressionValidator(u"^[_a-zA-Z:][_a-zA-Z0-9:]{1,1023}$"_s);
235 m_lineEdit->setCompleter(nullptr);
236 break;
237 case ValidationURL: {
238 static const QStringList urlCompletions = {
239 u"about:blank"_s,
240 u"http://"_s,
241 u"http://www."_s,
242 u"http://qt.io"_s,
243 u"file://"_s,
244 u"ftp://"_s,
245 u"data:"_s,
246 u"data:text/html,"_s,
247 u"qrc:/"_s,
248 };
249 QCompleter *completer = new QCompleter(urlCompletions, m_lineEdit);
250 m_lineEdit->setCompleter(completer);
251 m_lineEdit->setValidator(new UrlValidator(completer, m_lineEdit));
252 }
253 break;
254 }
255
256 setFocusProxy(m_lineEdit);
257 setText(m_cachedText);
258 markIntermediateState();
259 }
260
261 void TextPropertyEditor::setRegularExpressionValidator(const QString &pattern)
262 {
263 QRegularExpression regExp(pattern);
264 Q_ASSERT(regExp.isValid());
265 m_lineEdit->setValidator(new QRegularExpressionValidator(regExp, m_lineEdit));
266 }
267
268 QString TextPropertyEditor::text() const
269 {
270 return m_cachedText;
271 }
272
273 void TextPropertyEditor::markIntermediateState()
274 {
275 if (m_lineEdit->hasAcceptableInput()) {
276 m_lineEdit->setPalette(QPalette());
277 } else {
278 QPalette palette = m_lineEdit->palette();
279 palette.setColor(acg: QPalette::Active, acr: QPalette::Text, acolor: Qt::red);
280 m_lineEdit->setPalette(palette);
281 }
282
283 }
284
285 void TextPropertyEditor::setText(const QString &text)
286 {
287 m_cachedText = text;
288 m_lineEdit->setText(stringToEditorString(s: text, validationMode: m_validationMode));
289 markIntermediateState();
290 m_textEdited = false;
291 }
292
293 void TextPropertyEditor::slotTextEdited()
294 {
295 m_textEdited = true;
296 }
297
298 void TextPropertyEditor::slotTextChanged(const QString &text) {
299 m_cachedText = editorStringToString(s: text, validationMode: m_validationMode);
300 markIntermediateState();
301 if (m_updateMode == UpdateAsYouType)
302 emit textChanged(text: m_cachedText);
303 }
304
305 void TextPropertyEditor::slotEditingFinished()
306 {
307 if (m_updateMode == UpdateOnFinished && m_textEdited) {
308 emit textChanged(text: m_cachedText);
309 m_textEdited = false;
310 }
311 }
312
313 void TextPropertyEditor::selectAll() {
314 m_lineEdit->selectAll();
315 }
316
317 void TextPropertyEditor::clear() {
318 m_lineEdit->clear();
319 }
320
321 void TextPropertyEditor::setAlignment(Qt::Alignment alignment) {
322 m_lineEdit->setAlignment(alignment);
323 }
324
325 void TextPropertyEditor::installEventFilter(QObject *filterObject)
326 {
327 if (m_lineEdit)
328 m_lineEdit->installEventFilter(filterObj: filterObject);
329 }
330
331 void TextPropertyEditor::resizeEvent ( QResizeEvent * event ) {
332 m_lineEdit->resize( event->size());
333 }
334
335 QSize TextPropertyEditor::sizeHint () const {
336 return m_lineEdit->sizeHint ();
337 }
338
339 QSize TextPropertyEditor::minimumSizeHint () const {
340 return m_lineEdit->minimumSizeHint ();
341 }
342
343 // Returns whether newline characters are valid in validationMode.
344 bool TextPropertyEditor::multiLine(TextPropertyValidationMode validationMode) {
345 return validationMode == ValidationMultiLine || validationMode == ValidationStyleSheet || validationMode == ValidationRichText;
346 }
347
348 // Replace newline characters literal "\n" for inline editing in mode ValidationMultiLine
349 QString TextPropertyEditor::stringToEditorString(const QString &s, TextPropertyValidationMode validationMode) {
350 if (s.isEmpty() || !multiLine(validationMode))
351 return s;
352
353 QString rc(s);
354 // protect backslashes
355 rc.replace(c: '\\'_L1, after: "\\\\"_L1);
356 // escape newlines
357 rc.replace(c: u'\n', after: EscapedNewLine);
358 return rc;
359
360 }
361
362 // Replace literal "\n" by actual new lines for inline editing in mode ValidationMultiLine
363 // Note: As the properties are updated while the user types, it is important
364 // that trailing slashes ('bla\') are not deleted nor ignored, else this will
365 // cause jumping of the cursor
366 QString TextPropertyEditor::editorStringToString(const QString &s, TextPropertyValidationMode validationMode) {
367 if (s.isEmpty() || !multiLine(validationMode))
368 return s;
369
370 QString rc(s);
371 for (qsizetype pos = 0; (pos = rc.indexOf(c: u'\\', from: pos)) >= 0 ; ) {
372 // found an escaped character. If not a newline or at end of string, leave as is, else insert '\n'
373 const qsizetype nextpos = pos + 1;
374 if (nextpos >= rc.size()) // trailing '\\'
375 break;
376 // Escaped NewLine
377 if (rc.at(i: nextpos) == u'n')
378 rc[nextpos] = u'\n';
379 // Remove escape, go past escaped
380 rc.remove(i: pos,len: 1);
381 pos++;
382 }
383 return rc;
384 }
385
386 bool TextPropertyEditor::hasAcceptableInput() const {
387 return m_lineEdit->hasAcceptableInput();
388 }
389}
390
391QT_END_NAMESPACE
392

source code of qttools/src/designer/src/lib/shared/textpropertyeditor.cpp