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 examples of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:BSD$
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** BSD License Usage
18** Alternatively, you may use this file under the terms of the BSD license
19** as follows:
20**
21** "Redistribution and use in source and binary forms, with or without
22** modification, are permitted provided that the following conditions are
23** met:
24** * Redistributions of source code must retain the above copyright
25** notice, this list of conditions and the following disclaimer.
26** * Redistributions in binary form must reproduce the above copyright
27** notice, this list of conditions and the following disclaimer in
28** the documentation and/or other materials provided with the
29** distribution.
30** * Neither the name of The Qt Company Ltd nor the names of its
31** contributors may be used to endorse or promote products derived
32** from this software without specific prior written permission.
33**
34**
35** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
36** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
37** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
38** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
39** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
40** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
41** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
42** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
43** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
44** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
45** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
46**
47** $QT_END_LICENSE$
48**
49****************************************************************************/
50
51#include "variantdelegate.h"
52
53#include <QCheckBox>
54#include <QDateTime>
55#include <QLineEdit>
56#include <QSpinBox>
57#include <QRegularExpressionValidator>
58#include <QTextStream>
59
60#include <algorithm>
61
62static bool isPrintableChar(char c)
63{
64 return uchar(c) >= 32 && uchar(c) < 128;
65}
66
67static bool isPrintable(const QByteArray &ba)
68{
69 return std::all_of(first: ba.cbegin(), last: ba.cend(), pred: isPrintableChar);
70}
71
72static QString byteArrayToString(const QByteArray &ba)
73{
74 if (isPrintable(ba))
75 return QString::fromLatin1(str: ba);
76 QString result;
77 for (char c : ba) {
78 if (isPrintableChar(c)) {
79 if (c == '\\')
80 result += QLatin1Char(c);
81 result += QLatin1Char(c);
82 } else {
83 const uint uc = uchar(c);
84 result += "\\x";
85 if (uc < 16)
86 result += '0';
87 result += QString::number(uc, base: 16);
88 }
89 }
90 return result;
91}
92
93TypeChecker::TypeChecker()
94{
95 boolExp.setPattern("^(true)|(false)$");
96 boolExp.setPatternOptions(QRegularExpression::CaseInsensitiveOption);
97 Q_ASSERT(boolExp.isValid());
98
99 byteArrayExp.setPattern(R"RX(^[\x00-\xff]*$)RX");
100 charExp.setPattern("^.$");
101 Q_ASSERT(charExp.isValid());
102 colorExp.setPattern(R"RX(^\(([0-9]*),([0-9]*),([0-9]*),([0-9]*)\)$)RX");
103 Q_ASSERT(colorExp.isValid());
104 doubleExp.setPattern("");
105 pointExp.setPattern(R"RX(^\((-?[0-9]*),(-?[0-9]*)\)$)RX");
106 Q_ASSERT(pointExp.isValid());
107 rectExp.setPattern(R"RX(^\((-?[0-9]*),(-?[0-9]*),(-?[0-9]*),(-?[0-9]*)\)$)RX");
108 Q_ASSERT(rectExp.isValid());
109 signedIntegerExp.setPattern("^-?[0-9]*$");
110 Q_ASSERT(signedIntegerExp.isValid());
111 sizeExp = pointExp;
112 unsignedIntegerExp.setPattern("^[0-9]+$");
113 Q_ASSERT(unsignedIntegerExp.isValid());
114
115 const QString datePattern = "([0-9]{,4})-([0-9]{,2})-([0-9]{,2})";
116 dateExp.setPattern('^' + datePattern + '$');
117 Q_ASSERT(dateExp.isValid());
118 const QString timePattern = "([0-9]{,2}):([0-9]{,2}):([0-9]{,2})";
119 timeExp.setPattern('^' + timePattern + '$');
120 Q_ASSERT(timeExp.isValid());
121 dateTimeExp.setPattern('^' + datePattern + 'T' + timePattern + '$');
122 Q_ASSERT(dateTimeExp.isValid());
123}
124
125VariantDelegate::VariantDelegate(const QSharedPointer<TypeChecker> &typeChecker,
126 QObject *parent)
127 : QStyledItemDelegate(parent),
128 m_typeChecker(typeChecker)
129{
130}
131
132void VariantDelegate::paint(QPainter *painter,
133 const QStyleOptionViewItem &option,
134 const QModelIndex &index) const
135{
136 if (index.column() == 2) {
137 QVariant value = index.model()->data(index, role: Qt::UserRole);
138 if (!isSupportedType(type: value.userType())) {
139 QStyleOptionViewItem myOption = option;
140 myOption.state &= ~QStyle::State_Enabled;
141 QStyledItemDelegate::paint(painter, option: myOption, index);
142 return;
143 }
144 }
145
146 QStyledItemDelegate::paint(painter, option, index);
147}
148
149QWidget *VariantDelegate::createEditor(QWidget *parent,
150 const QStyleOptionViewItem & /* option */,
151 const QModelIndex &index) const
152{
153 if (index.column() != 2)
154 return nullptr;
155
156 QVariant originalValue = index.model()->data(index, role: Qt::UserRole);
157 if (!isSupportedType(type: originalValue.userType()))
158 return nullptr;
159
160 switch (originalValue.userType()) {
161 case QMetaType::Bool:
162 return new QCheckBox(parent);
163 break;
164 case QMetaType::Int:
165 case QMetaType::LongLong: {
166 auto spinBox = new QSpinBox(parent);
167 spinBox->setRange(min: -32767, max: 32767);
168 return spinBox;
169 }
170 case QMetaType::UInt:
171 case QMetaType::ULongLong: {
172 auto spinBox = new QSpinBox(parent);
173 spinBox->setRange(min: 0, max: 63335);
174 return spinBox;
175 }
176 default:
177 break;
178 }
179
180 QLineEdit *lineEdit = new QLineEdit(parent);
181 lineEdit->setFrame(false);
182
183 QRegularExpression regExp;
184
185 switch (originalValue.userType()) {
186 case QMetaType::Bool:
187 regExp = m_typeChecker->boolExp;
188 break;
189 case QMetaType::QByteArray:
190 regExp = m_typeChecker->byteArrayExp;
191 break;
192 case QMetaType::QChar:
193 regExp = m_typeChecker->charExp;
194 break;
195 case QMetaType::QColor:
196 regExp = m_typeChecker->colorExp;
197 break;
198 case QMetaType::QDate:
199 regExp = m_typeChecker->dateExp;
200 break;
201 case QMetaType::QDateTime:
202 regExp = m_typeChecker->dateTimeExp;
203 break;
204 case QMetaType::Double:
205 regExp = m_typeChecker->doubleExp;
206 break;
207 case QMetaType::Int:
208 case QMetaType::LongLong:
209 regExp = m_typeChecker->signedIntegerExp;
210 break;
211 case QMetaType::QPoint:
212 regExp = m_typeChecker->pointExp;
213 break;
214 case QMetaType::QRect:
215 regExp = m_typeChecker->rectExp;
216 break;
217 case QMetaType::QSize:
218 regExp = m_typeChecker->sizeExp;
219 break;
220 case QMetaType::QTime:
221 regExp = m_typeChecker->timeExp;
222 break;
223 case QMetaType::UInt:
224 case QMetaType::ULongLong:
225 regExp = m_typeChecker->unsignedIntegerExp;
226 break;
227 default:
228 break;
229 }
230
231 if (regExp.isValid()) {
232 QValidator *validator = new QRegularExpressionValidator(regExp, lineEdit);
233 lineEdit->setValidator(validator);
234 }
235
236 return lineEdit;
237}
238
239void VariantDelegate::setEditorData(QWidget *editor,
240 const QModelIndex &index) const
241{
242 QVariant value = index.model()->data(index, role: Qt::UserRole);
243 if (auto spinBox = qobject_cast<QSpinBox *>(object: editor)) {
244 const auto userType = value.userType();
245 if (userType == QMetaType::UInt || userType == QMetaType::ULongLong)
246 spinBox->setValue(value.toUInt());
247 else
248 spinBox->setValue(value.toInt());
249 } else if (auto checkBox = qobject_cast<QCheckBox *>(object: editor)) {
250 checkBox->setChecked(value.toBool());
251 } else if (QLineEdit *lineEdit = qobject_cast<QLineEdit *>(object: editor)) {
252 if (value.userType() == QMetaType::QByteArray
253 && !isPrintable(ba: value.toByteArray())) {
254 lineEdit->setReadOnly(true);
255 }
256 lineEdit->setText(displayText(value));
257 }
258}
259
260void VariantDelegate::setModelData(QWidget *editor, QAbstractItemModel *model,
261 const QModelIndex &index) const
262{
263 const QVariant originalValue = index.model()->data(index, role: Qt::UserRole);
264 QVariant value;
265
266 if (auto spinBox = qobject_cast<QSpinBox *>(object: editor)) {
267 value.setValue(spinBox->value());
268 } else if (auto checkBox = qobject_cast<QCheckBox *>(object: editor)) {
269 value.setValue(checkBox->isChecked());
270 } else if (QLineEdit *lineEdit = qobject_cast<QLineEdit *>(object: editor)) {
271 if (!lineEdit->isModified())
272 return;
273
274 QString text = lineEdit->text();
275 const QValidator *validator = lineEdit->validator();
276 if (validator) {
277 int pos;
278 if (validator->validate(text, pos) != QValidator::Acceptable)
279 return;
280 }
281
282 QRegularExpressionMatch match;
283
284 switch (originalValue.userType()) {
285 case QMetaType::QChar:
286 value = text.at(i: 0);
287 break;
288 case QMetaType::QColor:
289 match = m_typeChecker->colorExp.match(subject: text);
290 value = QColor(qMin(a: match.captured(nth: 1).toInt(), b: 255),
291 qMin(a: match.captured(nth: 2).toInt(), b: 255),
292 qMin(a: match.captured(nth: 3).toInt(), b: 255),
293 qMin(a: match.captured(nth: 4).toInt(), b: 255));
294 break;
295 case QMetaType::QDate:
296 {
297 QDate date = QDate::fromString(s: text, f: Qt::ISODate);
298 if (!date.isValid())
299 return;
300 value = date;
301 }
302 break;
303 case QMetaType::QDateTime:
304 {
305 QDateTime dateTime = QDateTime::fromString(s: text, f: Qt::ISODate);
306 if (!dateTime.isValid())
307 return;
308 value = dateTime;
309 }
310 break;
311 case QMetaType::QPoint:
312 match = m_typeChecker->pointExp.match(subject: text);
313 value = QPoint(match.captured(nth: 1).toInt(), match.captured(nth: 2).toInt());
314 break;
315 case QMetaType::QRect:
316 match = m_typeChecker->rectExp.match(subject: text);
317 value = QRect(match.captured(nth: 1).toInt(), match.captured(nth: 2).toInt(),
318 match.captured(nth: 3).toInt(), match.captured(nth: 4).toInt());
319 break;
320 case QMetaType::QSize:
321 match = m_typeChecker->sizeExp.match(subject: text);
322 value = QSize(match.captured(nth: 1).toInt(), match.captured(nth: 2).toInt());
323 break;
324 case QMetaType::QStringList:
325 value = text.split(sep: ',');
326 break;
327 case QMetaType::QTime:
328 {
329 QTime time = QTime::fromString(s: text, f: Qt::ISODate);
330 if (!time.isValid())
331 return;
332 value = time;
333 }
334 break;
335 default:
336 value = text;
337 value.convert(targetTypeId: originalValue.userType());
338 }
339 }
340
341 model->setData(index, value: displayText(value), role: Qt::DisplayRole);
342 model->setData(index, value, role: Qt::UserRole);
343}
344
345bool VariantDelegate::isSupportedType(int type)
346{
347 switch (type) {
348 case QMetaType::Bool:
349 case QMetaType::QByteArray:
350 case QMetaType::QChar:
351 case QMetaType::QColor:
352 case QMetaType::QDate:
353 case QMetaType::QDateTime:
354 case QMetaType::Double:
355 case QMetaType::Int:
356 case QMetaType::LongLong:
357 case QMetaType::QPoint:
358 case QMetaType::QRect:
359 case QMetaType::QSize:
360 case QMetaType::QString:
361 case QMetaType::QStringList:
362 case QMetaType::QTime:
363 case QMetaType::UInt:
364 case QMetaType::ULongLong:
365 return true;
366 default:
367 return false;
368 }
369}
370
371QString VariantDelegate::displayText(const QVariant &value)
372{
373 static const ushort unicodeBallotBox = 0x2610;
374 static const ushort unicodeCheckmark = 0x2713;
375
376 switch (value.userType()) {
377 case QMetaType::Bool:
378 return value.toBool()
379 ? QString(QChar(unicodeCheckmark))
380 : QString(QChar(unicodeBallotBox));
381 case QMetaType::QByteArray:
382 return byteArrayToString(ba: value.toByteArray());
383 case QMetaType::QChar:
384 case QMetaType::Double:
385 case QMetaType::Int:
386 case QMetaType::LongLong:
387 case QMetaType::QString:
388 case QMetaType::UInt:
389 case QMetaType::ULongLong:
390 return value.toString();
391 case QMetaType::QColor:
392 {
393 QColor color = qvariant_cast<QColor>(v: value);
394 return QString("(%1,%2,%3,%4)")
395 .arg(a: color.red()).arg(a: color.green())
396 .arg(a: color.blue()).arg(a: color.alpha());
397 }
398 case QMetaType::QDate:
399 return value.toDate().toString(format: Qt::ISODate);
400 case QMetaType::QDateTime:
401 return value.toDateTime().toString(format: Qt::ISODate);
402 case QMetaType::UnknownType:
403 return "<Invalid>";
404 case QMetaType::QPoint:
405 {
406 QPoint point = value.toPoint();
407 return QString("(%1,%2)").arg(a: point.x()).arg(a: point.y());
408 }
409 case QMetaType::QRect:
410 {
411 QRect rect = value.toRect();
412 return QString("(%1,%2,%3,%4)")
413 .arg(a: rect.x()).arg(a: rect.y())
414 .arg(a: rect.width()).arg(a: rect.height());
415 }
416 case QMetaType::QSize:
417 {
418 QSize size = value.toSize();
419 return QString("(%1,%2)").arg(a: size.width()).arg(a: size.height());
420 }
421 case QMetaType::QStringList:
422 return value.toStringList().join(sep: ',');
423 case QMetaType::QTime:
424 return value.toTime().toString(f: Qt::ISODate);
425 default:
426 break;
427 }
428 return QString("<%1>").arg(a: value.typeName());
429}
430

source code of qtbase/examples/widgets/tools/settingseditor/variantdelegate.cpp