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

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