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(ba.cbegin(), ba.cend(), isPrintableChar);
70}
71
72static QString byteArrayToString(const QByteArray &ba)
73{
74 if (isPrintable(ba))
75 return QString::fromLatin1(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, 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, Qt::UserRole);
138 if (!isSupportedType(value.userType())) {
139 QStyleOptionViewItem myOption = option;
140 myOption.state &= ~QStyle::State_Enabled;
141 QStyledItemDelegate::paint(painter, 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, Qt::UserRole);
157 if (!isSupportedType(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(-32767, 32767);
168 return spinBox;
169 }
170 case QMetaType::UInt:
171 case QMetaType::ULongLong: {
172 auto spinBox = new QSpinBox(parent);
173 spinBox->setRange(0, 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, Qt::UserRole);
243 if (auto spinBox = qobject_cast<QSpinBox *>(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 *>(editor)) {
250 checkBox->setChecked(value.toBool());
251 } else if (QLineEdit *lineEdit = qobject_cast<QLineEdit *>(editor)) {
252 if (value.userType() == QMetaType::QByteArray
253 && !isPrintable(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, Qt::UserRole);
264 QVariant value;
265
266 if (auto spinBox = qobject_cast<QSpinBox *>(editor)) {
267 value.setValue(spinBox->value());
268 } else if (auto checkBox = qobject_cast<QCheckBox *>(editor)) {
269 value.setValue(checkBox->isChecked());
270 } else if (QLineEdit *lineEdit = qobject_cast<QLineEdit *>(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(0);
287 break;
288 case QMetaType::QColor:
289 match = m_typeChecker->colorExp.match(text);
290 value = QColor(qMin(match.captured(1).toInt(), 255),
291 qMin(match.captured(2).toInt(), 255),
292 qMin(match.captured(3).toInt(), 255),
293 qMin(match.captured(4).toInt(), 255));
294 break;
295 case QMetaType::QDate:
296 {
297 QDate date = QDate::fromString(text, Qt::ISODate);
298 if (!date.isValid())
299 return;
300 value = date;
301 }
302 break;
303 case QMetaType::QDateTime:
304 {
305 QDateTime dateTime = QDateTime::fromString(text, Qt::ISODate);
306 if (!dateTime.isValid())
307 return;
308 value = dateTime;
309 }
310 break;
311 case QMetaType::QPoint:
312 match = m_typeChecker->pointExp.match(text);
313 value = QPoint(match.captured(1).toInt(), match.captured(2).toInt());
314 break;
315 case QMetaType::QRect:
316 match = m_typeChecker->rectExp.match(text);
317 value = QRect(match.captured(1).toInt(), match.captured(2).toInt(),
318 match.captured(3).toInt(), match.captured(4).toInt());
319 break;
320 case QMetaType::QSize:
321 match = m_typeChecker->sizeExp.match(text);
322 value = QSize(match.captured(1).toInt(), match.captured(2).toInt());
323 break;
324 case QMetaType::QStringList:
325 value = text.split(',');
326 break;
327 case QMetaType::QTime:
328 {
329 QTime time = QTime::fromString(text, Qt::ISODate);
330 if (!time.isValid())
331 return;
332 value = time;
333 }
334 break;
335 default:
336 value = text;
337 value.convert(originalValue.metaType());
338 }
339 }
340
341 model->setData(index, displayText(value), Qt::DisplayRole);
342 model->setData(index, value, 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 switch (value.userType()) {
374 case QMetaType::Bool:
375 return value.toBool() ? "✓" : "☐";
376 case QMetaType::QByteArray:
377 return byteArrayToString(value.toByteArray());
378 case QMetaType::QChar:
379 case QMetaType::Double:
380 case QMetaType::Int:
381 case QMetaType::LongLong:
382 case QMetaType::QString:
383 case QMetaType::UInt:
384 case QMetaType::ULongLong:
385 return value.toString();
386 case QMetaType::QColor:
387 {
388 QColor color = qvariant_cast<QColor>(value);
389 return QString("(%1,%2,%3,%4)")
390 .arg(color.red()).arg(color.green())
391 .arg(color.blue()).arg(color.alpha());
392 }
393 case QMetaType::QDate:
394 return value.toDate().toString(Qt::ISODate);
395 case QMetaType::QDateTime:
396 return value.toDateTime().toString(Qt::ISODate);
397 case QMetaType::UnknownType:
398 return "<Invalid>";
399 case QMetaType::QPoint:
400 {
401 QPoint point = value.toPoint();
402 return QString("(%1,%2)").arg(point.x()).arg(point.y());
403 }
404 case QMetaType::QRect:
405 {
406 QRect rect = value.toRect();
407 return QString("(%1,%2,%3,%4)")
408 .arg(rect.x()).arg(rect.y())
409 .arg(rect.width()).arg(rect.height());
410 }
411 case QMetaType::QSize:
412 {
413 QSize size = value.toSize();
414 return QString("(%1,%2)").arg(size.width()).arg(size.height());
415 }
416 case QMetaType::QStringList:
417 return value.toStringList().join(',');
418 case QMetaType::QTime:
419 return value.toTime().toString(Qt::ISODate);
420 default:
421 break;
422 }
423 return QString("<%1>").arg(value.typeName());
424}
425