From a83b2c64a9828a1d22347eaf31fd251b2ef647ee Mon Sep 17 00:00:00 2001 From: Friedemann Kleint Date: Fri, 10 Jul 2020 08:09:56 +0200 Subject: Polish the settingseditor example The example is meant to show an item delegate with a line edit with QRegularExpression-based validation depending on type. Unfortunately, this does not work since QSettings mostly return QString types. Fix it to a partially working state by - Making the expressions match from beginning to end which was overlooked in the QRegExp->QRegularExpression change. - Use QCheckBox, QSpinBox for bool/int since it is silly to have a user edit a bool value by typing 'true'/'false'. - Move the expressions out to a separate struct to be able to do some guessing of the type when reading the QSettings, implement for bool and int. - Use a fancy Unicode checkmark for displaying bools. - Fix the garbled display of QByteArray with binary data by displaying them with hex characters and setting them read-only. Change-Id: Iba22dfafc3b813b3fd3d2915ef5210d661049382 Reviewed-by: Paul Wicking --- .../widgets/tools/settingseditor/settingstree.cpp | 15 +- .../widgets/tools/settingseditor/settingstree.h | 5 + .../tools/settingseditor/variantdelegate.cpp | 277 ++++++++++++++------- .../widgets/tools/settingseditor/variantdelegate.h | 37 +-- 4 files changed, 227 insertions(+), 107 deletions(-) (limited to 'examples/widgets') diff --git a/examples/widgets/tools/settingseditor/settingstree.cpp b/examples/widgets/tools/settingseditor/settingstree.cpp index 9132368e4a..04af8ce3b9 100644 --- a/examples/widgets/tools/settingseditor/settingstree.cpp +++ b/examples/widgets/tools/settingseditor/settingstree.cpp @@ -57,9 +57,10 @@ #include SettingsTree::SettingsTree(QWidget *parent) - : QTreeWidget(parent) + : QTreeWidget(parent), + m_typeChecker(new TypeChecker) { - setItemDelegate(new VariantDelegate(this)); + setItemDelegate(new VariantDelegate(m_typeChecker, this)); setHeaderLabels({tr("Setting"), tr("Type"), tr("Value")}); header()->setSectionResizeMode(0, QHeaderView::ResizeToContents); @@ -77,6 +78,8 @@ SettingsTree::SettingsTree(QWidget *parent) connect(&refreshTimer, &QTimer::timeout, this, &SettingsTree::maybeRefresh); } +SettingsTree::~SettingsTree() = default; + void SettingsTree::setSettingsObject(const SettingsPtr &newSettings) { settings = newSettings; @@ -211,6 +214,14 @@ void SettingsTree::updateChildItems(QTreeWidgetItem *parent) if (value.userType() == QMetaType::UnknownType) { child->setText(1, "Invalid"); } else { + if (value.type() == QVariant::String) { + const QString stringValue = value.toString(); + if (m_typeChecker->boolExp.match(stringValue).hasMatch()) { + value.setValue(stringValue.compare("true", Qt::CaseInsensitive) == 0); + } else if (m_typeChecker->signedIntegerExp.match(stringValue).hasMatch()) + value.setValue(stringValue.toInt()); + } + child->setText(1, value.typeName()); } child->setText(2, VariantDelegate::displayText(value)); diff --git a/examples/widgets/tools/settingseditor/settingstree.h b/examples/widgets/tools/settingseditor/settingstree.h index 3e9e9658ce..54b1ce6157 100644 --- a/examples/widgets/tools/settingseditor/settingstree.h +++ b/examples/widgets/tools/settingseditor/settingstree.h @@ -60,14 +60,18 @@ QT_BEGIN_NAMESPACE class QSettings; QT_END_NAMESPACE +struct TypeChecker; + class SettingsTree : public QTreeWidget { Q_OBJECT public: using SettingsPtr = QSharedPointer; + using TypeCheckerPtr = QSharedPointer; SettingsTree(QWidget *parent = nullptr); + ~SettingsTree(); void setSettingsObject(const SettingsPtr &settings); QSize sizeHint() const override; @@ -94,6 +98,7 @@ private: void moveItemForward(QTreeWidgetItem *parent, int oldIndex, int newIndex); SettingsPtr settings; + TypeCheckerPtr m_typeChecker; QTimer refreshTimer; QIcon groupIcon; QIcon keyIcon; diff --git a/examples/widgets/tools/settingseditor/variantdelegate.cpp b/examples/widgets/tools/settingseditor/variantdelegate.cpp index eb822f0dc2..255f6135be 100644 --- a/examples/widgets/tools/settingseditor/variantdelegate.cpp +++ b/examples/widgets/tools/settingseditor/variantdelegate.cpp @@ -50,29 +50,83 @@ #include "variantdelegate.h" +#include #include #include +#include #include +#include -VariantDelegate::VariantDelegate(QObject *parent) - : QStyledItemDelegate(parent) +#include + +static bool isPrintableChar(char c) +{ + return uchar(c) >= 32 && uchar(c) < 128; +} + +static bool isPrintable(const QByteArray &ba) { - boolExp.setPattern("true|false"); + return std::all_of(ba.cbegin(), ba.cend(), isPrintableChar); +} + +static QString byteArrayToString(const QByteArray &ba) +{ + if (isPrintable(ba)) + return QString::fromLatin1(ba); + QString result; + for (char c : ba) { + if (isPrintableChar(c)) { + if (c == '\\') + result += QLatin1Char(c); + result += QLatin1Char(c); + } else { + const uint uc = uchar(c); + result += "\\x"; + if (uc < 16) + result += '0'; + result += QString::number(uc, 16); + } + } + return result; +} + +TypeChecker::TypeChecker() +{ + boolExp.setPattern("^(true)|(false)$"); boolExp.setPatternOptions(QRegularExpression::CaseInsensitiveOption); + Q_ASSERT(boolExp.isValid()); - byteArrayExp.setPattern("[\\x00-\\xff]*"); - charExp.setPattern("."); - colorExp.setPattern("^\\(([0-9]*),([0-9]*),([0-9]*),([0-9]*)\\)$"); + byteArrayExp.setPattern(R"RX(^[\x00-\xff]*$)RX"); + charExp.setPattern("^.$"); + Q_ASSERT(charExp.isValid()); + colorExp.setPattern(R"RX(^\(([0-9]*),([0-9]*),([0-9]*),([0-9]*)\)$)RX"); + Q_ASSERT(colorExp.isValid()); doubleExp.setPattern(""); - pointExp.setPattern("^\\((-?[0-9]*),(-?[0-9]*)\\)$"); - rectExp.setPattern("^\\((-?[0-9]*),(-?[0-9]*),(-?[0-9]*),(-?[0-9]*)\\)$"); - signedIntegerExp.setPattern("-?[0-9]*"); + pointExp.setPattern(R"RX(^\((-?[0-9]*),(-?[0-9]*)\)$)RX"); + Q_ASSERT(pointExp.isValid()); + rectExp.setPattern(R"RX(^\((-?[0-9]*),(-?[0-9]*),(-?[0-9]*),(-?[0-9]*)\)$)RX"); + Q_ASSERT(rectExp.isValid()); + signedIntegerExp.setPattern("^-?[0-9]*$"); + Q_ASSERT(signedIntegerExp.isValid()); sizeExp = pointExp; - unsignedIntegerExp.setPattern("[0-9]*"); + unsignedIntegerExp.setPattern("^[0-9]+$"); + Q_ASSERT(unsignedIntegerExp.isValid()); - dateExp.setPattern("([0-9]{,4})-([0-9]{,2})-([0-9]{,2})"); - timeExp.setPattern("([0-9]{,2}):([0-9]{,2}):([0-9]{,2})"); - dateTimeExp.setPattern(dateExp.pattern() + 'T' + timeExp.pattern()); + const QString datePattern = "([0-9]{,4})-([0-9]{,2})-([0-9]{,2})"; + dateExp.setPattern('^' + datePattern + '$'); + Q_ASSERT(dateExp.isValid()); + const QString timePattern = "([0-9]{,2}):([0-9]{,2}):([0-9]{,2})"; + timeExp.setPattern('^' + timePattern + '$'); + Q_ASSERT(timeExp.isValid()); + dateTimeExp.setPattern('^' + datePattern + 'T' + timePattern + '$'); + Q_ASSERT(dateTimeExp.isValid()); +} + +VariantDelegate::VariantDelegate(const QSharedPointer &typeChecker, + QObject *parent) + : QStyledItemDelegate(parent), + m_typeChecker(typeChecker) +{ } void VariantDelegate::paint(QPainter *painter, @@ -103,6 +157,26 @@ QWidget *VariantDelegate::createEditor(QWidget *parent, if (!isSupportedType(originalValue.userType())) return nullptr; + switch (originalValue.userType()) { + case QMetaType::Bool: + return new QCheckBox(parent); + break; + case QMetaType::Int: + case QMetaType::LongLong: { + auto spinBox = new QSpinBox(parent); + spinBox->setRange(-32767, 32767); + return spinBox; + } + case QMetaType::UInt: + case QMetaType::ULongLong: { + auto spinBox = new QSpinBox(parent); + spinBox->setRange(0, 63335); + return spinBox; + } + default: + break; + } + QLineEdit *lineEdit = new QLineEdit(parent); lineEdit->setFrame(false); @@ -110,45 +184,45 @@ QWidget *VariantDelegate::createEditor(QWidget *parent, switch (originalValue.userType()) { case QMetaType::Bool: - regExp = boolExp; + regExp = m_typeChecker->boolExp; break; case QMetaType::QByteArray: - regExp = byteArrayExp; + regExp = m_typeChecker->byteArrayExp; break; case QMetaType::QChar: - regExp = charExp; + regExp = m_typeChecker->charExp; break; case QMetaType::QColor: - regExp = colorExp; + regExp = m_typeChecker->colorExp; break; case QMetaType::QDate: - regExp = dateExp; + regExp = m_typeChecker->dateExp; break; case QMetaType::QDateTime: - regExp = dateTimeExp; + regExp = m_typeChecker->dateTimeExp; break; case QMetaType::Double: - regExp = doubleExp; + regExp = m_typeChecker->doubleExp; break; case QMetaType::Int: case QMetaType::LongLong: - regExp = signedIntegerExp; + regExp = m_typeChecker->signedIntegerExp; break; case QMetaType::QPoint: - regExp = pointExp; + regExp = m_typeChecker->pointExp; break; case QMetaType::QRect: - regExp = rectExp; + regExp = m_typeChecker->rectExp; break; case QMetaType::QSize: - regExp = sizeExp; + regExp = m_typeChecker->sizeExp; break; case QMetaType::QTime: - regExp = timeExp; + regExp = m_typeChecker->timeExp; break; case QMetaType::UInt: case QMetaType::ULongLong: - regExp = unsignedIntegerExp; + regExp = m_typeChecker->unsignedIntegerExp; break; default: break; @@ -166,83 +240,102 @@ void VariantDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const { QVariant value = index.model()->data(index, Qt::UserRole); - if (QLineEdit *lineEdit = qobject_cast(editor)) + if (auto spinBox = qobject_cast(editor)) { + const auto userType = value.userType(); + if (userType == QMetaType::UInt || userType == QMetaType::ULongLong) + spinBox->setValue(value.toUInt()); + else + spinBox->setValue(value.toInt()); + } else if (auto checkBox = qobject_cast(editor)) { + checkBox->setChecked(value.toBool()); + } else if (QLineEdit *lineEdit = qobject_cast(editor)) { + if (value.userType() == QMetaType::QByteArray + && !isPrintable(value.toByteArray())) { + lineEdit->setReadOnly(true); + } lineEdit->setText(displayText(value)); + } } void VariantDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const { - QLineEdit *lineEdit = qobject_cast(editor); - if (!lineEdit->isModified()) - return; + const QVariant originalValue = index.model()->data(index, Qt::UserRole); + QVariant value; - QString text = lineEdit->text(); - const QValidator *validator = lineEdit->validator(); - if (validator) { - int pos; - if (validator->validate(text, pos) != QValidator::Acceptable) + if (auto spinBox = qobject_cast(editor)) { + value.setValue(spinBox->value()); + } else if (auto checkBox = qobject_cast(editor)) { + value.setValue(checkBox->isChecked()); + } else if (QLineEdit *lineEdit = qobject_cast(editor)) { + if (!lineEdit->isModified()) return; - } - - QVariant originalValue = index.model()->data(index, Qt::UserRole); - QVariant value; - QRegularExpressionMatch match; - switch (originalValue.userType()) { - case QMetaType::QChar: - value = text.at(0); - break; - case QMetaType::QColor: - match = colorExp.match(text); - value = QColor(qMin(match.captured(1).toInt(), 255), - qMin(match.captured(2).toInt(), 255), - qMin(match.captured(3).toInt(), 255), - qMin(match.captured(4).toInt(), 255)); - break; - case QMetaType::QDate: - { - QDate date = QDate::fromString(text, Qt::ISODate); - if (!date.isValid()) + QString text = lineEdit->text(); + const QValidator *validator = lineEdit->validator(); + if (validator) { + int pos; + if (validator->validate(text, pos) != QValidator::Acceptable) return; - value = date; } - break; - case QMetaType::QDateTime: - { - QDateTime dateTime = QDateTime::fromString(text, Qt::ISODate); - if (!dateTime.isValid()) - return; - value = dateTime; - } - break; - case QMetaType::QPoint: - match = pointExp.match(text); - value = QPoint(match.captured(1).toInt(), match.captured(2).toInt()); - break; - case QMetaType::QRect: - match = rectExp.match(text); - value = QRect(match.captured(1).toInt(), match.captured(2).toInt(), - match.captured(3).toInt(), match.captured(4).toInt()); - break; - case QMetaType::QSize: - match = sizeExp.match(text); - value = QSize(match.captured(1).toInt(), match.captured(2).toInt()); - break; - case QMetaType::QStringList: - value = text.split(','); - break; - case QMetaType::QTime: - { - QTime time = QTime::fromString(text, Qt::ISODate); - if (!time.isValid()) - return; - value = time; + + QRegularExpressionMatch match; + + switch (originalValue.userType()) { + case QMetaType::QChar: + value = text.at(0); + break; + case QMetaType::QColor: + match = m_typeChecker->colorExp.match(text); + value = QColor(qMin(match.captured(1).toInt(), 255), + qMin(match.captured(2).toInt(), 255), + qMin(match.captured(3).toInt(), 255), + qMin(match.captured(4).toInt(), 255)); + break; + case QMetaType::QDate: + { + QDate date = QDate::fromString(text, Qt::ISODate); + if (!date.isValid()) + return; + value = date; + } + break; + case QMetaType::QDateTime: + { + QDateTime dateTime = QDateTime::fromString(text, Qt::ISODate); + if (!dateTime.isValid()) + return; + value = dateTime; + } + break; + case QMetaType::QPoint: + match = m_typeChecker->pointExp.match(text); + value = QPoint(match.captured(1).toInt(), match.captured(2).toInt()); + break; + case QMetaType::QRect: + match = m_typeChecker->rectExp.match(text); + value = QRect(match.captured(1).toInt(), match.captured(2).toInt(), + match.captured(3).toInt(), match.captured(4).toInt()); + break; + case QMetaType::QSize: + match = m_typeChecker->sizeExp.match(text); + value = QSize(match.captured(1).toInt(), match.captured(2).toInt()); + break; + case QMetaType::QStringList: + value = text.split(','); + break; + case QMetaType::QTime: + { + QTime time = QTime::fromString(text, Qt::ISODate); + if (!time.isValid()) + return; + value = time; + } + break; + default: + value = text; + value.convert(originalValue.userType()); } - break; - default: - value = text; - value.convert(originalValue.userType()); } model->setData(index, displayText(value), Qt::DisplayRole); @@ -279,7 +372,9 @@ QString VariantDelegate::displayText(const QVariant &value) { switch (value.userType()) { case QMetaType::Bool: + return value.toBool() ? "✓" : "☐"; case QMetaType::QByteArray: + return byteArrayToString(value.toByteArray()); case QMetaType::QChar: case QMetaType::Double: case QMetaType::Int: diff --git a/examples/widgets/tools/settingseditor/variantdelegate.h b/examples/widgets/tools/settingseditor/variantdelegate.h index 96e44fd181..9d46aa77a5 100644 --- a/examples/widgets/tools/settingseditor/variantdelegate.h +++ b/examples/widgets/tools/settingseditor/variantdelegate.h @@ -53,13 +53,34 @@ #include #include +#include + +struct TypeChecker +{ + TypeChecker(); + + QRegularExpression boolExp; + QRegularExpression byteArrayExp; + QRegularExpression charExp; + QRegularExpression colorExp; + QRegularExpression dateExp; + QRegularExpression dateTimeExp; + QRegularExpression doubleExp; + QRegularExpression pointExp; + QRegularExpression rectExp; + QRegularExpression signedIntegerExp; + QRegularExpression sizeExp; + QRegularExpression timeExp; + QRegularExpression unsignedIntegerExp; +}; class VariantDelegate : public QStyledItemDelegate { Q_OBJECT public: - VariantDelegate(QObject *parent = nullptr); + explicit VariantDelegate(const QSharedPointer &typeChecker, + QObject *parent = nullptr); void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override; @@ -73,19 +94,7 @@ public: static QString displayText(const QVariant &value); private: - mutable QRegularExpression boolExp; - mutable QRegularExpression byteArrayExp; - mutable QRegularExpression charExp; - mutable QRegularExpression colorExp; - mutable QRegularExpression dateExp; - mutable QRegularExpression dateTimeExp; - mutable QRegularExpression doubleExp; - mutable QRegularExpression pointExp; - mutable QRegularExpression rectExp; - mutable QRegularExpression signedIntegerExp; - mutable QRegularExpression sizeExp; - mutable QRegularExpression timeExp; - mutable QRegularExpression unsignedIntegerExp; + QSharedPointer m_typeChecker; }; #endif -- cgit v1.2.3