Skip to content

Instantly share code, notes, and snippets.

@sr105
Last active April 2, 2019 05:05
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save sr105/7955969 to your computer and use it in GitHub Desktop.
Save sr105/7955969 to your computer and use it in GitHub Desktop.
QPropertyModel class for easily turning any QObject-derived subclass with properties into a one-row model.
// QPropertyModel
// - a class for easily turning any QObject-derived subclass with properties into a one-row model
//
// Copyright 2013 - Harvey Chapman <hchapman@3gfp.com>
// Source: https://gist.github.com/sr105/7955969
// License:
// This work is licensed under the Creative Commons Attribution-ShareAlike
// 4.0 International License. To view a copy of this license, visit
// http://creativecommons.org/licenses/by-sa/4.0/deed.en_US.
//
// It's not required, but I'd appreciate it if any improvements were e-mailed
// back to me so I can share them with others. This code is specifically not
// GPL-like so you can use it commercially without worrying about it tainting
// the rest of your proprietary code.
// -- Harvey
#include "qpropertymodel.h"
#include <QDataWidgetMapper>
#include <QMetaProperty>
#include <QSignalMapper>
#include <QDebug>
QPropertyModel::QPropertyModel(QObject *source, QObject *parent) :
QAbstractItemModel(parent),
_source(source)
{
connectToPropertyNotifySignals();
}
QPropertyDataWidgetMapper *QPropertyModel::newMapper(QObject *source, QObject *parent)
{
QPropertyDataWidgetMapper *_mapper = new QPropertyDataWidgetMapper(parent);
_mapper->setModel(new QPropertyModel(source, parent));
return _mapper;
}
QPropertyDataWidgetMapper *QPropertyModel::newMapper()
{
QPropertyDataWidgetMapper *_mapper = new QPropertyDataWidgetMapper(this);
_mapper->setModel(this);
return _mapper;
}
QStringList QPropertyModel::propertyNames() const
{
static QStringList names;
if (names.size())
return names;
foreach (const QMetaProperty &p, properties().values())
names << QString::fromLatin1(p.name());
return names;
}
int QPropertyModel::columnForProperty(QString name) const
{
QMap<int, QMetaProperty> props = properties();
foreach (int index, props.keys())
if (props[index].name() == name)
return index;
qDebug("No property \"%s\" found!", qPrintable(name));
return -1;
}
QMap<int, QMetaProperty> QPropertyModel::properties() const
{
// TODO: should we filter out properties with no NOTIFY? Otherwise, how will
// we know when they change? Maybe it doesn't matter. Just let the user
// any QObject they want and deal with the consequences of the features
// that their QObject properties support.
static QMap<int, QMetaProperty> properties;
if (properties.size())
return properties;
// Save a map of properties
const QMetaObject* metaObject = _source->metaObject();
// Start at 0 to get all inherited properties, too. Start at the offset for just this
// subclass.
// for(int i = metaObject->propertyOffset(); i < metaObject->propertyCount(); ++i)
for(int i = 0; i < metaObject->propertyCount(); ++i)
properties.insert(i, metaObject->property(i));
return properties;
}
void QPropertyModel::connectToPropertyNotifySignals()
{
QMap<int, QMetaProperty> props = properties();
QSignalMapper *mapper = new QSignalMapper(this);
foreach (int index, props.keys()) {
if (!props[index].hasNotifySignal())
continue;
// It's difficult to map all signals from a single object to
// a single slot with an identifiable piece of information.
// Ideas:
// - http://stackoverflow.com/questions/10805174/qobject-generic-signal-handler
// - a dummy SignalForwarder class instance for each signal for QSignalMapper.
SignalForwarder *sf = new SignalForwarder(this);
connect(_source, QByteArray("2") + props[index].notifySignal().signature(), sf, SIGNAL(forward()));
connect(sf, SIGNAL(forward()), mapper, SLOT(map()));
mapper->setMapping(sf, index);
}
connect(mapper, SIGNAL(mapped(int)), this, SLOT(columnChanged(int)));
}
void QPropertyModel::columnChanged(int column)
{
//qDebug("columnChanged(%d)", column);
QModelIndex index = createIndex(0, column);
emit dataChanged(index, index);
}
QVariant QPropertyModel::data(const QModelIndex& index, int role) const {
//qDebug() << "data(" << index << "," << role << ")";
if (!hasIndex(index))
return QVariant();
return _source->property(properties().value(index.column()).name());
}
bool QPropertyModel::setData(const QModelIndex &index, const QVariant &value, int role) {
//qDebug() << "setData(" << index << "," << value << "," << role << ")";
if (!hasIndex(index) || role != Qt::EditRole)
return QAbstractItemModel::setData(index, value, role);
QMetaProperty mp = properties().value(index.column());
bool rc = _source->setProperty(mp.name(), value);
if (rc && !mp.hasNotifySignal())
// property doesn't support NOTIFY, emit dataChanged()
emit dataChanged(index, index);
return rc;
}
int QPropertyModel::columnCount(const QModelIndex &/*parent*/) const {
return properties().size();
}
int QPropertyModel::rowCount(const QModelIndex &/*parent*/) const {
return 1;
}
Qt::ItemFlags QPropertyModel::flags(const QModelIndex &index) const {
QMetaProperty mp = properties().value(index.column());
return QAbstractItemModel::flags(index)
| Qt::ItemIsSelectable
| Qt::ItemIsEnabled
| mp.isWritable() ? Qt::ItemIsEditable : Qt::NoItemFlags;
}
QModelIndex QPropertyModel::parent(const QModelIndex &/*child*/) const {
return QModelIndex();
}
QModelIndex QPropertyModel::index(int row, int column, const QModelIndex &parent) const {
if (QAbstractItemModel::hasIndex(row, column, parent))
return createIndex(row, column);
return QModelIndex();
}
bool QPropertyModel::hasIndex(const QModelIndex &index) const {
return QAbstractItemModel::hasIndex(index.row(), index.column(), index.parent());
}
QPropertyModel *QPropertyDataWidgetMapper::model() const
{
return qobject_cast<QPropertyModel*>(QDataWidgetMapper::model());
}
void QPropertyDataWidgetMapper::addMapping(QWidget *widget, QString property)
{
if (model() && model()->columnForProperty(property) >= 0)
QDataWidgetMapper::addMapping(widget, model()->columnForProperty(property));
}
void QPropertyDataWidgetMapper::addMapping(QWidget *widget, QString property, const QByteArray &propertyName)
{
if (model() && model()->columnForProperty(property) >= 0)
QDataWidgetMapper::addMapping(widget, model()->columnForProperty(property), propertyName);
}
void QPropertyDataWidgetMapper::addMapping(QWidget *widget, int section)
{
QDataWidgetMapper::addMapping(widget, section);
}
void QPropertyDataWidgetMapper::addMapping(QWidget *widget, int section, const QByteArray &propertyName)
{
QDataWidgetMapper::addMapping(widget, section, propertyName);
}
// QPropertyModel
// - a class for easily turning any QObject-derived subclass with properties into a one-row model
//
// Copyright 2013 - Harvey Chapman <hchapman@3gfp.com>
// Source: https://gist.github.com/sr105/7955969
// License:
// This work is licensed under the Creative Commons Attribution-ShareAlike
// 4.0 International License. To view a copy of this license, visit
// http://creativecommons.org/licenses/by-sa/4.0/deed.en_US.
//
// It's not required, but I'd appreciate it if any improvements were e-mailed
// back to me so I can share them with others. This code is specifically not
// GPL-like so you can use it commercially without worrying about it tainting
// the rest of your proprietary code.
// -- Harvey
#ifndef QPROPERTYMODEL_H
#define QPROPERTYMODEL_H
#include <QAbstractItemModel>
#include <QStringList>
#include <QDataWidgetMapper>
class QPropertyModel;
// Convenience class that exposes the public methods of QPropertyModel
// without requiring casting.
class QPropertyDataWidgetMapper : public QDataWidgetMapper
{
Q_OBJECT
public:
QPropertyDataWidgetMapper(QObject *parent = 0) : QDataWidgetMapper(parent) {}
// QDataWidgetMapper::model() re-written to return QPropertyDataWidgetMapper
QPropertyModel *model() const;
// For convenience, these automatically convert "property" into column numbers
void addMapping(QWidget *widget, QString property);
void addMapping(QWidget *widget, QString property, const QByteArray &propertyName);
// Pass-thru methods to QDataWidgetMapper
void addMapping(QWidget *widget, int section);
void addMapping(QWidget *widget, int section, const QByteArray &propertyName);
};
// QPropertyModel creates a single row data model consisting of columns mapping
// to properties in a QObject. The column list can be retrieved as a QStringList,
// and a method exists to convert the property names to column numbers.
class QPropertyModel : public QAbstractItemModel
{
Q_OBJECT
public:
explicit QPropertyModel(QObject *source, QObject *parent = 0);
// Return a QPropertyDataWidgetMapper wrapping a new instance of this class.
static QPropertyDataWidgetMapper *newMapper(QObject *source, QObject *parent = 0);
// Return a QPropertyDataWidgetMapper wrapping this existing instance
QPropertyDataWidgetMapper *newMapper();
QStringList propertyNames() const;
int columnForProperty(QString name) const;
QMap<int, QMetaProperty> properties() const;
protected:
void connectToPropertyNotifySignals();
// Pointer to our data source
QObject *_source;
protected slots:
void columnChanged(int column);
// Required virtual function implementations. They mostly map
// directly to the (getItem/setItem/itemChanged) methods above.
public:
// read & write data
virtual QVariant data(const QModelIndex &index, int role) const;
virtual bool setData(const QModelIndex &index, const QVariant &value, int role);
// returns the number of properties in _source
virtual int columnCount(const QModelIndex &parent) const;
// all hard-coded simple implementations
virtual int rowCount(const QModelIndex &parent = QModelIndex()) const;
virtual Qt::ItemFlags flags(const QModelIndex &index) const;
virtual QModelIndex parent(const QModelIndex &child) const;
virtual QModelIndex index(int row, int column, const QModelIndex &) const;
// Helper method to make virtual methods easier to code
virtual bool hasIndex(const QModelIndex &index) const;
};
// Until we can come up with something more clever, this little class allows
// us to connect each signal in a single QObject to a single slot using
// QSignalMapper to pass information to us about which signal was sent.
// QSignalMapper maps Objects to data. All of our signals come from the same
// object, so that won't work. However, if we create a SignalObject as a
// forwareder for each signal, now we have a unique object for each signal
// that QSignalMapper can work with.
class SignalForwarder: public QObject
{
Q_OBJECT
public:
SignalForwarder(QObject *parent = 0) : QObject(parent) {}
signals:
void forward();
};
#endif // QPROPERTYMODEL_H
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment