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 <paul.wicking@qt.io>
This commit is contained in:
Friedemann Kleint 2020-07-10 08:09:56 +02:00
parent 9ac5273d03
commit a83b2c64a9
4 changed files with 228 additions and 108 deletions

View File

@ -57,9 +57,10 @@
#include <QSettings> #include <QSettings>
SettingsTree::SettingsTree(QWidget *parent) 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")}); setHeaderLabels({tr("Setting"), tr("Type"), tr("Value")});
header()->setSectionResizeMode(0, QHeaderView::ResizeToContents); header()->setSectionResizeMode(0, QHeaderView::ResizeToContents);
@ -77,6 +78,8 @@ SettingsTree::SettingsTree(QWidget *parent)
connect(&refreshTimer, &QTimer::timeout, this, &SettingsTree::maybeRefresh); connect(&refreshTimer, &QTimer::timeout, this, &SettingsTree::maybeRefresh);
} }
SettingsTree::~SettingsTree() = default;
void SettingsTree::setSettingsObject(const SettingsPtr &newSettings) void SettingsTree::setSettingsObject(const SettingsPtr &newSettings)
{ {
settings = newSettings; settings = newSettings;
@ -211,6 +214,14 @@ void SettingsTree::updateChildItems(QTreeWidgetItem *parent)
if (value.userType() == QMetaType::UnknownType) { if (value.userType() == QMetaType::UnknownType) {
child->setText(1, "Invalid"); child->setText(1, "Invalid");
} else { } 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(1, value.typeName());
} }
child->setText(2, VariantDelegate::displayText(value)); child->setText(2, VariantDelegate::displayText(value));

View File

@ -60,14 +60,18 @@ QT_BEGIN_NAMESPACE
class QSettings; class QSettings;
QT_END_NAMESPACE QT_END_NAMESPACE
struct TypeChecker;
class SettingsTree : public QTreeWidget class SettingsTree : public QTreeWidget
{ {
Q_OBJECT Q_OBJECT
public: public:
using SettingsPtr = QSharedPointer<QSettings>; using SettingsPtr = QSharedPointer<QSettings>;
using TypeCheckerPtr = QSharedPointer<TypeChecker>;
SettingsTree(QWidget *parent = nullptr); SettingsTree(QWidget *parent = nullptr);
~SettingsTree();
void setSettingsObject(const SettingsPtr &settings); void setSettingsObject(const SettingsPtr &settings);
QSize sizeHint() const override; QSize sizeHint() const override;
@ -94,6 +98,7 @@ private:
void moveItemForward(QTreeWidgetItem *parent, int oldIndex, int newIndex); void moveItemForward(QTreeWidgetItem *parent, int oldIndex, int newIndex);
SettingsPtr settings; SettingsPtr settings;
TypeCheckerPtr m_typeChecker;
QTimer refreshTimer; QTimer refreshTimer;
QIcon groupIcon; QIcon groupIcon;
QIcon keyIcon; QIcon keyIcon;

View File

@ -50,29 +50,83 @@
#include "variantdelegate.h" #include "variantdelegate.h"
#include <QCheckBox>
#include <QDateTime> #include <QDateTime>
#include <QLineEdit> #include <QLineEdit>
#include <QSpinBox>
#include <QRegularExpressionValidator> #include <QRegularExpressionValidator>
#include <QTextStream>
VariantDelegate::VariantDelegate(QObject *parent) #include <algorithm>
: QStyledItemDelegate(parent)
static bool isPrintableChar(char c)
{ {
boolExp.setPattern("true|false"); return uchar(c) >= 32 && uchar(c) < 128;
}
static bool isPrintable(const QByteArray &ba)
{
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); boolExp.setPatternOptions(QRegularExpression::CaseInsensitiveOption);
Q_ASSERT(boolExp.isValid());
byteArrayExp.setPattern("[\\x00-\\xff]*"); byteArrayExp.setPattern(R"RX(^[\x00-\xff]*$)RX");
charExp.setPattern("."); charExp.setPattern("^.$");
colorExp.setPattern("^\\(([0-9]*),([0-9]*),([0-9]*),([0-9]*)\\)$"); Q_ASSERT(charExp.isValid());
colorExp.setPattern(R"RX(^\(([0-9]*),([0-9]*),([0-9]*),([0-9]*)\)$)RX");
Q_ASSERT(colorExp.isValid());
doubleExp.setPattern(""); doubleExp.setPattern("");
pointExp.setPattern("^\\((-?[0-9]*),(-?[0-9]*)\\)$"); pointExp.setPattern(R"RX(^\((-?[0-9]*),(-?[0-9]*)\)$)RX");
rectExp.setPattern("^\\((-?[0-9]*),(-?[0-9]*),(-?[0-9]*),(-?[0-9]*)\\)$"); Q_ASSERT(pointExp.isValid());
signedIntegerExp.setPattern("-?[0-9]*"); 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; 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})"); const QString datePattern = "([0-9]{,4})-([0-9]{,2})-([0-9]{,2})";
timeExp.setPattern("([0-9]{,2}):([0-9]{,2}):([0-9]{,2})"); dateExp.setPattern('^' + datePattern + '$');
dateTimeExp.setPattern(dateExp.pattern() + 'T' + timeExp.pattern()); 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> &typeChecker,
QObject *parent)
: QStyledItemDelegate(parent),
m_typeChecker(typeChecker)
{
} }
void VariantDelegate::paint(QPainter *painter, void VariantDelegate::paint(QPainter *painter,
@ -103,6 +157,26 @@ QWidget *VariantDelegate::createEditor(QWidget *parent,
if (!isSupportedType(originalValue.userType())) if (!isSupportedType(originalValue.userType()))
return nullptr; 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); QLineEdit *lineEdit = new QLineEdit(parent);
lineEdit->setFrame(false); lineEdit->setFrame(false);
@ -110,45 +184,45 @@ QWidget *VariantDelegate::createEditor(QWidget *parent,
switch (originalValue.userType()) { switch (originalValue.userType()) {
case QMetaType::Bool: case QMetaType::Bool:
regExp = boolExp; regExp = m_typeChecker->boolExp;
break; break;
case QMetaType::QByteArray: case QMetaType::QByteArray:
regExp = byteArrayExp; regExp = m_typeChecker->byteArrayExp;
break; break;
case QMetaType::QChar: case QMetaType::QChar:
regExp = charExp; regExp = m_typeChecker->charExp;
break; break;
case QMetaType::QColor: case QMetaType::QColor:
regExp = colorExp; regExp = m_typeChecker->colorExp;
break; break;
case QMetaType::QDate: case QMetaType::QDate:
regExp = dateExp; regExp = m_typeChecker->dateExp;
break; break;
case QMetaType::QDateTime: case QMetaType::QDateTime:
regExp = dateTimeExp; regExp = m_typeChecker->dateTimeExp;
break; break;
case QMetaType::Double: case QMetaType::Double:
regExp = doubleExp; regExp = m_typeChecker->doubleExp;
break; break;
case QMetaType::Int: case QMetaType::Int:
case QMetaType::LongLong: case QMetaType::LongLong:
regExp = signedIntegerExp; regExp = m_typeChecker->signedIntegerExp;
break; break;
case QMetaType::QPoint: case QMetaType::QPoint:
regExp = pointExp; regExp = m_typeChecker->pointExp;
break; break;
case QMetaType::QRect: case QMetaType::QRect:
regExp = rectExp; regExp = m_typeChecker->rectExp;
break; break;
case QMetaType::QSize: case QMetaType::QSize:
regExp = sizeExp; regExp = m_typeChecker->sizeExp;
break; break;
case QMetaType::QTime: case QMetaType::QTime:
regExp = timeExp; regExp = m_typeChecker->timeExp;
break; break;
case QMetaType::UInt: case QMetaType::UInt:
case QMetaType::ULongLong: case QMetaType::ULongLong:
regExp = unsignedIntegerExp; regExp = m_typeChecker->unsignedIntegerExp;
break; break;
default: default:
break; break;
@ -166,14 +240,34 @@ void VariantDelegate::setEditorData(QWidget *editor,
const QModelIndex &index) const const QModelIndex &index) const
{ {
QVariant value = index.model()->data(index, Qt::UserRole); QVariant value = index.model()->data(index, Qt::UserRole);
if (QLineEdit *lineEdit = qobject_cast<QLineEdit *>(editor)) if (auto spinBox = qobject_cast<QSpinBox *>(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<QCheckBox *>(editor)) {
checkBox->setChecked(value.toBool());
} else if (QLineEdit *lineEdit = qobject_cast<QLineEdit *>(editor)) {
if (value.userType() == QMetaType::QByteArray
&& !isPrintable(value.toByteArray())) {
lineEdit->setReadOnly(true);
}
lineEdit->setText(displayText(value)); lineEdit->setText(displayText(value));
}
} }
void VariantDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, void VariantDelegate::setModelData(QWidget *editor, QAbstractItemModel *model,
const QModelIndex &index) const const QModelIndex &index) const
{ {
QLineEdit *lineEdit = qobject_cast<QLineEdit *>(editor); const QVariant originalValue = index.model()->data(index, Qt::UserRole);
QVariant value;
if (auto spinBox = qobject_cast<QSpinBox *>(editor)) {
value.setValue(spinBox->value());
} else if (auto checkBox = qobject_cast<QCheckBox *>(editor)) {
value.setValue(checkBox->isChecked());
} else if (QLineEdit *lineEdit = qobject_cast<QLineEdit *>(editor)) {
if (!lineEdit->isModified()) if (!lineEdit->isModified())
return; return;
@ -185,8 +279,6 @@ void VariantDelegate::setModelData(QWidget *editor, QAbstractItemModel *model,
return; return;
} }
QVariant originalValue = index.model()->data(index, Qt::UserRole);
QVariant value;
QRegularExpressionMatch match; QRegularExpressionMatch match;
switch (originalValue.userType()) { switch (originalValue.userType()) {
@ -194,7 +286,7 @@ void VariantDelegate::setModelData(QWidget *editor, QAbstractItemModel *model,
value = text.at(0); value = text.at(0);
break; break;
case QMetaType::QColor: case QMetaType::QColor:
match = colorExp.match(text); match = m_typeChecker->colorExp.match(text);
value = QColor(qMin(match.captured(1).toInt(), 255), value = QColor(qMin(match.captured(1).toInt(), 255),
qMin(match.captured(2).toInt(), 255), qMin(match.captured(2).toInt(), 255),
qMin(match.captured(3).toInt(), 255), qMin(match.captured(3).toInt(), 255),
@ -217,16 +309,16 @@ void VariantDelegate::setModelData(QWidget *editor, QAbstractItemModel *model,
} }
break; break;
case QMetaType::QPoint: case QMetaType::QPoint:
match = pointExp.match(text); match = m_typeChecker->pointExp.match(text);
value = QPoint(match.captured(1).toInt(), match.captured(2).toInt()); value = QPoint(match.captured(1).toInt(), match.captured(2).toInt());
break; break;
case QMetaType::QRect: case QMetaType::QRect:
match = rectExp.match(text); match = m_typeChecker->rectExp.match(text);
value = QRect(match.captured(1).toInt(), match.captured(2).toInt(), value = QRect(match.captured(1).toInt(), match.captured(2).toInt(),
match.captured(3).toInt(), match.captured(4).toInt()); match.captured(3).toInt(), match.captured(4).toInt());
break; break;
case QMetaType::QSize: case QMetaType::QSize:
match = sizeExp.match(text); match = m_typeChecker->sizeExp.match(text);
value = QSize(match.captured(1).toInt(), match.captured(2).toInt()); value = QSize(match.captured(1).toInt(), match.captured(2).toInt());
break; break;
case QMetaType::QStringList: case QMetaType::QStringList:
@ -244,6 +336,7 @@ void VariantDelegate::setModelData(QWidget *editor, QAbstractItemModel *model,
value = text; value = text;
value.convert(originalValue.userType()); value.convert(originalValue.userType());
} }
}
model->setData(index, displayText(value), Qt::DisplayRole); model->setData(index, displayText(value), Qt::DisplayRole);
model->setData(index, value, Qt::UserRole); model->setData(index, value, Qt::UserRole);
@ -279,7 +372,9 @@ QString VariantDelegate::displayText(const QVariant &value)
{ {
switch (value.userType()) { switch (value.userType()) {
case QMetaType::Bool: case QMetaType::Bool:
return value.toBool() ? "" : "";
case QMetaType::QByteArray: case QMetaType::QByteArray:
return byteArrayToString(value.toByteArray());
case QMetaType::QChar: case QMetaType::QChar:
case QMetaType::Double: case QMetaType::Double:
case QMetaType::Int: case QMetaType::Int:

View File

@ -53,13 +53,34 @@
#include <QStyledItemDelegate> #include <QStyledItemDelegate>
#include <QRegularExpression> #include <QRegularExpression>
#include <QSharedPointer>
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 class VariantDelegate : public QStyledItemDelegate
{ {
Q_OBJECT Q_OBJECT
public: public:
VariantDelegate(QObject *parent = nullptr); explicit VariantDelegate(const QSharedPointer<TypeChecker> &typeChecker,
QObject *parent = nullptr);
void paint(QPainter *painter, const QStyleOptionViewItem &option, void paint(QPainter *painter, const QStyleOptionViewItem &option,
const QModelIndex &index) const override; const QModelIndex &index) const override;
@ -73,19 +94,7 @@ public:
static QString displayText(const QVariant &value); static QString displayText(const QVariant &value);
private: private:
mutable QRegularExpression boolExp; QSharedPointer<TypeChecker> m_typeChecker;
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;
}; };
#endif #endif