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 
43 QT_BEGIN_NAMESPACE
44 
45 namespace {
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 
ReplacementValidator(QObject * parent,const QString & offending,const QString & replacement)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 
fixup(QString & input) const71     void ReplacementValidator::fixup ( QString & input ) const {
72         input.replace(m_offending, m_replacement);
73     }
74 
validate(QString & input,int &) const75     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 
StyleSheetValidator(QObject * parent)87     StyleSheetValidator::StyleSheetValidator (QObject * parent) :
88        ReplacementValidator(parent, NewLineChar, EscapedNewLine)
89     {
90     }
91 
validate(QString & input,int & pos) const92     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(input, 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 
UrlValidator(QCompleter * completer,QObject * parent)117     UrlValidator::UrlValidator(QCompleter *completer, QObject *parent) :
118         QValidator(parent),
119         m_completer(completer)
120     {
121     }
122 
validate(QString & input,int & pos) const123     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 
fixup(QString & input) const144     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(input).toString();
153     }
154 
guessUrlFromString(const QString & string) const155     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(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(urlStr))
175             return QUrl::fromLocalFile(urlStr);
176 
177         // Might be a short url - try to detect the schema.
178         if (!hasSchema) {
179             const int dotIndex = urlStr.indexOf(QLatin1Char('.'));
180             if (dotIndex != -1) {
181                 const QString prefix = urlStr.left(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 
200 namespace qdesigner_internal {
201     // TextPropertyEditor
TextPropertyEditor(QWidget * parent,EmbeddingMode embeddingMode,TextPropertyValidationMode validationMode)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(m_lineEdit,&QLineEdit::editingFinished, this, &TextPropertyEditor::editingFinished);
224         connect(m_lineEdit,&QLineEdit::returnPressed, this, &TextPropertyEditor::slotEditingFinished);
225         connect(m_lineEdit,&QLineEdit::textChanged, this, &TextPropertyEditor::slotTextChanged);
226         connect(m_lineEdit,&QLineEdit::textEdited, this, &TextPropertyEditor::slotTextEdited);
227 
228         setTextPropertyValidationMode(validationMode);
229     }
230 
setTextPropertyValidationMode(TextPropertyValidationMode vm)231     void TextPropertyEditor::setTextPropertyValidationMode(TextPropertyValidationMode vm) {
232         m_validationMode = vm;
233         m_lineEdit->setWantNewLine(multiLine(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 
setRegularExpressionValidator(const QString & pattern)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 
text() const292     QString TextPropertyEditor::text() const
293     {
294         return m_cachedText;
295     }
296 
markIntermediateState()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(QPalette::Active, QPalette::Text, Qt::red);
304             m_lineEdit->setPalette(palette);
305         }
306 
307     }
308 
setText(const QString & text)309     void TextPropertyEditor::setText(const QString &text)
310     {
311         m_cachedText = text;
312         m_lineEdit->setText(stringToEditorString(text, m_validationMode));
313         markIntermediateState();
314         m_textEdited = false;
315     }
316 
slotTextEdited()317     void TextPropertyEditor::slotTextEdited()
318     {
319         m_textEdited = true;
320     }
321 
slotTextChanged(const QString & text)322     void  TextPropertyEditor::slotTextChanged(const QString &text) {
323         m_cachedText = editorStringToString(text, m_validationMode);
324         markIntermediateState();
325         if (m_updateMode == UpdateAsYouType)
326             emit textChanged(m_cachedText);
327     }
328 
slotEditingFinished()329     void TextPropertyEditor::slotEditingFinished()
330     {
331         if (m_updateMode == UpdateOnFinished && m_textEdited) {
332             emit textChanged(m_cachedText);
333             m_textEdited = false;
334         }
335     }
336 
selectAll()337     void TextPropertyEditor::selectAll() {
338         m_lineEdit->selectAll();
339     }
340 
clear()341     void TextPropertyEditor::clear() {
342         m_lineEdit->clear();
343     }
344 
setAlignment(Qt::Alignment alignment)345     void TextPropertyEditor::setAlignment(Qt::Alignment alignment) {
346         m_lineEdit->setAlignment(alignment);
347     }
348 
installEventFilter(QObject * filterObject)349     void TextPropertyEditor::installEventFilter(QObject *filterObject)
350     {
351         if (m_lineEdit)
352             m_lineEdit->installEventFilter(filterObject);
353     }
354 
resizeEvent(QResizeEvent * event)355     void TextPropertyEditor::resizeEvent ( QResizeEvent * event ) {
356         m_lineEdit->resize( event->size());
357     }
358 
sizeHint() const359     QSize TextPropertyEditor::sizeHint () const {
360         return  m_lineEdit->sizeHint ();
361     }
362 
minimumSizeHint() const363     QSize TextPropertyEditor::minimumSizeHint () const {
364         return  m_lineEdit->minimumSizeHint ();
365     }
366 
367     // Returns whether newline characters are valid in validationMode.
multiLine(TextPropertyValidationMode 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
stringToEditorString(const QString & s,TextPropertyValidationMode validationMode)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(QLatin1Char('\\'), QStringLiteral("\\\\"));
380         // escape newlines
381         rc.replace(NewLineChar, 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
editorStringToString(const QString & s,TextPropertyValidationMode validationMode)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(QLatin1Char('\\'),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(nextpos) ==  QChar(QLatin1Char('n')))
402                  rc[nextpos] =  NewLineChar;
403             // Remove escape, go past escaped
404             rc.remove(pos,1);
405             pos++;
406         }
407         return rc;
408     }
409 
hasAcceptableInput() const410     bool TextPropertyEditor::hasAcceptableInput() const {
411         return m_lineEdit->hasAcceptableInput();
412     }
413 }
414 
415 QT_END_NAMESPACE
416