Last active
April 2, 2019 05:05
-
-
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// QPropertyModel | |
// - a class for easily turning any QObject-derived subclass with properties into a one-row model | |
// | |
// Copyright 2013 - Harvey Chapman <[email protected]> | |
// 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); | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// QPropertyModel | |
// - a class for easily turning any QObject-derived subclass with properties into a one-row model | |
// | |
// Copyright 2013 - Harvey Chapman <[email protected]> | |
// 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