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