Qt通用属性工具:随心定义,随时可见(一)
一、开胃菜,没图我说个DIAO
先不BB,给大家上个效果图展示下:
上图我们也没干啥,几行代码:
#include "widget.h"
#include <QApplication>
#include <QObject>
#include "QtPropertyEditor.h"
#include <QHBoxLayout>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Widget w;
QtPropertyEditor::QtPropertyTreeEditor tree_editor(&w);
QHBoxLayout* hlayout = new QHBoxLayout;
hlayout->addWidget(&tree_editor);
w.setLayout(hlayout);
tree_editor.treeModel.propertyNames = QtPropertyEditor::getPropertyNames(&w);
tree_editor.treeModel.setObject(&w);
tree_editor.resizeColumnsToContents();
w.show();
return a.exec();
}
我们创建了一个最基本的QWidget对象,并将此对象作为属性展示对象传给了我们的通用属性编辑器。程序运行,帅气的属性编辑器展示出来了。当我们改变窗口时,属性编辑器中对应的数据也实时更新显示。很显然,MVC模式的运用跑不了了。属性编辑器啊属性编辑器,我们说了千万遍的Qt属性系统也是必然使用了的。准确的来说,这个属性编辑器就是基于属性系统实现的。对于 Qt属性系统
还不过关的朋友,可以去这篇《Qt 属性系统(The Property System )》 先做点准备功课。
二、核心代码
/* --------------------------------------------------------------------------------
* QObject property editor UI.
*
* Author: Marcel Paz Goldschen-Ohm /// 尊重原作者,即使自己做了修改,也别偷偷摸摸抹除原作者
* Email: marcel.goldschen@gmail.com
* -------------------------------------------------------------------------------- */
#ifndef __QtPropertyEditor_H__
#define __QtPropertyEditor_H__
#include <functional>
#include <QAbstractItemModel>
#include <QAction>
#include <QByteArray>
#include <QDialog>
#include <QDialogButtonBox>
#include <QHash>
#include <QList>
#include <QMetaProperty>
#include <QObject>
#include <QString>
#include <QStringList>
#include <QStyledItemDelegate>
#include <QTableView>
#include <QTreeView>
#include <QVariant>
#include <QVBoxLayout>
#ifdef DEBUG
#include <iostream>
#include <QDebug>
#endif
namespace QtPropertyEditor
{
// List all object property names.
QList<QByteArray> getPropertyNames(QObject *object);
QList<QByteArray> getMetaPropertyNames(const QMetaObject &metaObject);
QList<QByteArray> getNoninheritedPropertyNames(QObject *object);
// Handle descendant properties such as "child.grandchild.property".
QObject* descendant(QObject *object, const QByteArray &pathToDescendantObject);
// Get the size of a QTableView widget.
QSize getTableSize(const QTableView *table);
/* --------------------------------------------------------------------------------
* Things that all QObject property models should be able to do.
* -------------------------------------------------------------------------------- */
class QtAbstractPropertyModel : public QAbstractItemModel
{
Q_OBJECT
public:
QtAbstractPropertyModel(QObject *parent = 0) : QAbstractItemModel(parent) {}
QList<QByteArray> propertyNames;
QHash<QByteArray, QString> propertyHeaders;
void setProperties(const QString &str);
void addProperty(const QString &str);
virtual QObject* objectAtIndex(const QModelIndex &index) const = 0;
virtual QByteArray propertyNameAtIndex(const QModelIndex &index) const = 0;
const QMetaProperty metaPropertyAtIndex(const QModelIndex &index) const;
virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const;
virtual bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole);
virtual Qt::ItemFlags flags(const QModelIndex &index) const;
};
/* --------------------------------------------------------------------------------
* Property tree model for a QObject tree.
* Max tree depth can be specified (i.e. depth = 0 --> single object only).
* -------------------------------------------------------------------------------- */
class QtPropertyTreeModel : public QtAbstractPropertyModel
{
Q_OBJECT
public:
// Internal tree node.
struct Node
{
// Node traversal.
Node *parent = NULL;
QList<Node*> children;
// Node data.
QObject *object = NULL;
QByteArray propertyName;
Node(Node *parent = NULL) : parent(parent) {}
~Node() { qDeleteAll(children); }
void setObject(QObject *object, int maxChildDepth = -1, const QList<QByteArray> &propertyNames = QList<QByteArray>());
};
QtPropertyTreeModel(QObject *parent = NULL) : QtAbstractPropertyModel(parent) {}
// Getters.
QObject* object() const { return _root.object; }
int maxDepth() const { return _maxTreeDepth; }
// Setters.
void setObject(QObject *object) { beginResetModel(); _root.setObject(object, _maxTreeDepth, propertyNames); endResetModel(); }
void setMaxDepth(int i) { beginResetModel(); _maxTreeDepth = i; reset(); endResetModel(); }
void setProperties(const QString &str) { beginResetModel(); QtAbstractPropertyModel::setProperties(str); reset(); endResetModel(); }
void addProperty(const QString &str) { beginResetModel(); QtAbstractPropertyModel::addProperty(str); reset(); endResetModel(); }
// Model interface.
Node* nodeAtIndex(const QModelIndex &index) const;
QObject* objectAtIndex(const QModelIndex &index) const;
QByteArray propertyNameAtIndex(const QModelIndex &index) const;
QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const;
QModelIndex parent(const QModelIndex &index) const;
int rowCount(const QModelIndex &parent = QModelIndex()) const;
int columnCount(const QModelIndex &parent = QModelIndex()) const;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const;
bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole);
Qt::ItemFlags flags(const QModelIndex &index) const;
QVariant headerData(int section, Qt::Orientation orientation, int role) const;
public slots:
void reset() { setObject(object()); }
protected:
Node _root;
int _maxTreeDepth = -1;
};
/* --------------------------------------------------------------------------------
* Property table model for a list of QObjects (rows are objects, columns are properties).
* -------------------------------------------------------------------------------- */
class QtPropertyTableModel : public QtAbstractPropertyModel
{
Q_OBJECT
public:
typedef std::function<QObject*()> ObjectCreatorFunction;
QtPropertyTableModel(QObject *parent = NULL) : QtAbstractPropertyModel(parent) {}
// Getters.
QObjectList objects() const { return _objects; }
ObjectCreatorFunction objectCreator() const { return _objectCreator; }
// Setters.
void setObjects(const QObjectList &objects) { beginResetModel(); _objects = objects; endResetModel(); }
template <class T>
void setObjects(const QList<T*> &objects);
template <class T>
void setChildObjects(QObject *parent);
void setObjectCreator(ObjectCreatorFunction creator) { _objectCreator = creator; }
void setProperties(const QString &str) { beginResetModel(); QtAbstractPropertyModel::setProperties(str); endResetModel(); }
void addProperty(const QString &str) { beginResetModel(); QtAbstractPropertyModel::addProperty(str); endResetModel(); }
// Model interface.
QObject* objectAtIndex(const QModelIndex &index) const;
QByteArray propertyNameAtIndex(const QModelIndex &index) const;
QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const;
QModelIndex parent(const QModelIndex &index) const;
int rowCount(const QModelIndex &parent = QModelIndex()) const;
int columnCount(const QModelIndex &parent = QModelIndex()) const;
QVariant headerData(int section, Qt::Orientation orientation, int role) const;
bool insertRows(int row, int count, const QModelIndex &parent = QModelIndex());
bool removeRows(int row, int count, const QModelIndex &parent = QModelIndex());
bool moveRows(const QModelIndex &sourceParent, int sourceRow, int count, const QModelIndex &destinationParent, int destinationRow);
void reorderChildObjectsToMatchRowOrder(int firstRow = 0);
// Default creator functions for convenience.
// Requires template class T to implement a default constructor T().
template <class T>
static QObject* defaultCreator() { return new T(); }
template <class T>
static QObject* defaultChildCreator(QObject *parent) { T *object = new T(); object->setParent(parent); return object; }
signals:
void rowCountChanged();
void rowOrderChanged();
protected:
QObjectList _objects;
ObjectCreatorFunction _objectCreator = NULL;
};
template <class T>
void QtPropertyTableModel::setObjects(const QList<T*> &objects)
{
beginResetModel();
_objects.clear();
foreach(T *object, objects) {
if(QObject *obj = qobject_cast<QObject*>(object))
_objects.append(obj);
}
endResetModel();
}
template <class T>
void QtPropertyTableModel::setChildObjects(QObject *parent)
{
beginResetModel();
_objects.clear();
foreach(T *derivedObject, parent->findChildren<T*>(QString(), Qt::FindDirectChildrenOnly)) {
if(QObject *object = qobject_cast<QObject*>(derivedObject))
_objects.append(object);
}
_objectCreator = std::bind(&QtPropertyTableModel::defaultChildCreator<T>, parent);
endResetModel();
}
/* --------------------------------------------------------------------------------
* Property editor delegate.
* -------------------------------------------------------------------------------- */
class QtPropertyDelegate: public QStyledItemDelegate
{
public:
QtPropertyDelegate(QWidget *parent = 0) : QStyledItemDelegate(parent) {}
QWidget* createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const Q_DECL_OVERRIDE;
void setEditorData(QWidget *editor, const QModelIndex &index) const Q_DECL_OVERRIDE;
void setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const Q_DECL_OVERRIDE;
QString displayText(const QVariant &value, const QLocale &locale) const Q_DECL_OVERRIDE;
void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const Q_DECL_OVERRIDE;
protected:
bool editorEvent(QEvent *event, QAbstractItemModel *model, const QStyleOptionViewItem &option, const QModelIndex &index) Q_DECL_OVERRIDE;
};
/* --------------------------------------------------------------------------------
* User types for QVariant that will be handled by QtPropertyDelegate.
* User types need to be declared via Q_DECLARE_METATYPE (see below outside of namespace)
* and also registered via qRegisterMetaType (see static instantiation in .cpp file)
* -------------------------------------------------------------------------------- */
// For static registration of user types (see static instantiation in QtPropertyEditor.cpp).
template <typename Type> class MetaTypeRegistration
{
public:
inline MetaTypeRegistration()
{
qRegisterMetaType<Type>();
}
};
// For push buttons.
// See Q_DECLARE_METATYPE below and qRegisterMetaType in .cpp file.
class QtPushButtonActionWrapper
{
public:
QtPushButtonActionWrapper(QAction *action = NULL) : action(action) {}
QtPushButtonActionWrapper(const QtPushButtonActionWrapper &other) { action = other.action; }
~QtPushButtonActionWrapper() {}
QAction *action = NULL;
};
/* --------------------------------------------------------------------------------
* Tree editor for properties in a QObject tree.
* -------------------------------------------------------------------------------- */
class QtPropertyTreeEditor : public QTreeView
{
Q_OBJECT
public:
QtPropertyTreeEditor(QWidget *parent = NULL);
// Owns its own tree model for convenience. This means model will be deleted along with editor.
// However, you're not forced to use this model.
QtPropertyTreeModel treeModel;
public slots:
void resizeColumnsToContents();
protected:
QtPropertyDelegate _delegate;
};
/* --------------------------------------------------------------------------------
* Table editor for properties in a list of QObjects.
* -------------------------------------------------------------------------------- */
class QtPropertyTableEditor : public QTableView
{
Q_OBJECT
public:
QtPropertyTableEditor(QWidget *parent = NULL);
// Owns its own table model for convenience. This means model will be deleted along with editor.
// However, you're not forced to use this model.
QtPropertyTableModel tableModel;
bool isDynamic() const { return _isDynamic; }
void setIsDynamic(bool b);
QSize sizeHint() const Q_DECL_OVERRIDE { return getTableSize(this); }
public slots:
void horizontalHeaderContextMenu(QPoint pos);
void verticalHeaderContextMenu(QPoint pos);
void appendRow();
void insertSelectedRows();
void removeSelectedRows();
void handleSectionMove(int logicalIndex, int oldVisualIndex, int newVisualIndex);
protected:
QtPropertyDelegate _delegate;
bool _isDynamic = true;
void keyPressEvent(QKeyEvent *event) Q_DECL_OVERRIDE;
bool eventFilter(QObject* o, QEvent* e) Q_DECL_OVERRIDE;
};
} // QtPropertyEditor
Q_DECLARE_METATYPE(QtPropertyEditor::QtPushButtonActionWrapper);
#endif // __QtPropertyEditor_H__
/* --------------------------------------------------------------------------------
* Author: Marcel Paz Goldschen-Ohm
* Email: marcel.goldschen@gmail.com
* -------------------------------------------------------------------------------- */
#include "QtPropertyEditor.h"
#include <QAbstractButton>
#include <QApplication>
#include <QComboBox>
#include <QEvent>
#include <QHeaderView>
#include <QLineEdit>
#include <QMenu>
#include <QMessageBox>
#include <QMetaObject>
#include <QMetaType>
#include <QMouseEvent>
#include <QPushButton>
#include <QRegularExpression>
#include <QScrollBar>
#include <QStylePainter>
#include <QToolButton>
#include <QDebug>
namespace QtPropertyEditor
{
static MetaTypeRegistration<QtPushButtonActionWrapper> thisInstantiationRegistersQtPushButtonActionWrapperWithQt;
QList<QByteArray> getPropertyNames(QObject *object)
{
QList<QByteArray> propertyNames = getMetaPropertyNames(*object->metaObject());
foreach(const QByteArray &dynamicPropertyName, object->dynamicPropertyNames()) {
propertyNames << dynamicPropertyName;
}
return propertyNames;
}
QList<QByteArray> getMetaPropertyNames(const QMetaObject &metaObject)
{
QList<QByteArray> propertyNames;
int numProperties = metaObject.propertyCount();
for(int i = 0; i < numProperties; ++i) {
const QMetaProperty metaProperty = metaObject.property(i);
propertyNames << QByteArray(metaProperty.name());
}
return propertyNames;
}
QList<QByteArray> getNoninheritedPropertyNames(QObject *object)
{
QList<QByteArray> propertyNames = getPropertyNames(object);
QList<QByteArray> superPropertyNames = getMetaPropertyNames(*object->metaObject()->superClass());
foreach(const QByteArray &superPropertyName, superPropertyNames) {
propertyNames.removeOne(superPropertyName);
}
return propertyNames;
}
QObject* descendant(QObject *object, const QByteArray &pathToDescendantObject)
{
// Get descendent object specified by "path.to.descendant", where "path", "to" and "descendant"
// are the object names of objects with the parent->child relationship object->path->to->descendant.
if(!object || pathToDescendantObject.isEmpty())
return 0;
if(pathToDescendantObject.contains('.')) {
QList<QByteArray> descendantObjectNames = pathToDescendantObject.split('.');
foreach(QByteArray name, descendantObjectNames) {
object = object->findChild<QObject*>(QString(name));
if(!object)
return 0; // Invalid path to descendant object.
}
return object;
}
return object->findChild<QObject*>(QString(pathToDescendantObject));
}
QSize getTableSize(const QTableView *table)
{
int w = table->verticalHeader()->width() + 4; // +4 seems to be needed
int h = table->horizontalHeader()->height() + 4;
for(int i = 0; i < table->model()->columnCount(); i++)
w += table->columnWidth(i);
for(int i = 0; i < table->model()->rowCount(); i++)
h += table->rowHeight(i);
return QSize(w, h);
}
void QtAbstractPropertyModel::setProperties(const QString &str)
{
// str = "name0: header0, name1, name2, name3: header3 ..."
propertyNames.clear();
propertyHeaders.clear();
QStringList fields = str.split(",", QString::SkipEmptyParts);
foreach(const QString &field, fields) {
if(!field.trimmed().isEmpty())
addProperty(field);
}
}
void QtAbstractPropertyModel::addProperty(const QString &str)
{
// "name" OR "name: header"
if(str.contains(":")) {
int pos = str.indexOf(":");
QByteArray propertyName = str.left(pos).trimmed().toUtf8();
QString propertyHeader = str.mid(pos+1).trimmed();
propertyNames.push_back(propertyName);
propertyHeaders[propertyName] = propertyHeader;
} else {
QByteArray propertyName = str.trimmed().toUtf8();
propertyNames.push_back(propertyName);
}
}
const QMetaProperty QtAbstractPropertyModel::metaPropertyAtIndex(const QModelIndex &index) const
{
QObject *object = objectAtIndex(index);
if(!object)
return QMetaProperty();
QByteArray propertyName = propertyNameAtIndex(index);
if(propertyName.isEmpty())
return QMetaProperty();
// Return metaObject with same name.
const QMetaObject *metaObject = object->metaObject();
int numProperties = metaObject->propertyCount();
for(int i = 0; i < numProperties; ++i) {
const QMetaProperty metaProperty = metaObject->property(i);
if(QByteArray(metaProperty.name()) == propertyName)
return metaProperty;
}
return QMetaProperty();
}
QVariant QtAbstractPropertyModel::data(const QModelIndex &index, int role) const
{
if(!index.isValid())
return QVariant();
if(role == Qt::DisplayRole || role == Qt::EditRole) {
QObject *object = objectAtIndex(index);
if(!object)
return QVariant();
QByteArray propertyName = propertyNameAtIndex(index);
if(propertyName.isEmpty())
return QVariant();
return object->property(propertyName.constData());
}
return QVariant();
}
bool QtAbstractPropertyModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
if(!index.isValid())
return false;
if(role == Qt::EditRole) {
QObject *object = objectAtIndex(index);
if(!object)
return false;
QByteArray propertyName = propertyNameAtIndex(index);
if(propertyName.isEmpty())
return false;
bool result = object->setProperty(propertyName.constData(), value);
// Result will be FALSE for dynamic properties, which causes the tree view to lag.
// So make sure we still return TRUE in this case.
if(!result && object->dynamicPropertyNames().contains(propertyName))
return true;
return result;
}
return false;
}
Qt::ItemFlags QtAbstractPropertyModel::flags(const QModelIndex &index) const
{
Qt::ItemFlags flags = QAbstractItemModel::flags(index);
if(!index.isValid())
return flags;
QObject *object = objectAtIndex(index);
if(!object)
return flags;
flags |= Qt::ItemIsEnabled;
flags |= Qt::ItemIsSelectable;
QByteArray propertyName = propertyNameAtIndex(index);
const QMetaProperty metaProperty = metaPropertyAtIndex(index);
if(metaProperty.isWritable() || object->dynamicPropertyNames().contains(propertyName))
flags |= Qt::ItemIsEditable;
return flags;
}
void QtPropertyTreeModel::Node::setObject(QObject *object, int maxChildDepth, const QList<QByteArray> &propertyNames)
{
this->object = object;
propertyName.clear();
qDeleteAll(children);
children.clear();
if(!object) return;
// Compiled properties (but exclude objectName as this is displayed for the object node itself).
const QMetaObject *metaObject = object->metaObject();
int numProperties = metaObject->propertyCount();
for(int i = 0; i < numProperties; ++i) {
const QMetaProperty metaProperty = metaObject->property(i);
QByteArray propertyName = QByteArray(metaProperty.name());
if(propertyNames.isEmpty() || propertyNames.contains(propertyName)) {
Node *node = new Node(this);
node->propertyName = propertyName;
children.append(node);
}
}
// Dynamic properties.
QList<QByteArray> dynamicPropertyNames = object->dynamicPropertyNames();
foreach(const QByteArray &propertyName, dynamicPropertyNames) {
if(propertyNames.isEmpty() || propertyNames.contains(propertyName)) {
Node *node = new Node(this);
node->propertyName = propertyName;
children.append(node);
}
}
// Child objects.
if(maxChildDepth > 0 || maxChildDepth == -1) {
if(maxChildDepth > 0)
--maxChildDepth;
QMap<QByteArray, QObjectList> childMap;
foreach(QObject *child, object->children()) {
childMap[QByteArray(child->metaObject()->className())].append(child);
}
for(auto it = childMap.begin(); it != childMap.end(); ++it) {
foreach(QObject *child, it.value()) {
Node *node = new Node(this);
node->setObject(child, maxChildDepth, propertyNames);
children.append(node);
}
}
}
}
QtPropertyTreeModel::Node* QtPropertyTreeModel::nodeAtIndex(const QModelIndex &index) const
{
try {
return static_cast<Node*>(index.internalPointer());
} catch(...) {
return NULL;
}
}
QObject* QtPropertyTreeModel::objectAtIndex(const QModelIndex &index) const
{
// If node is an object, return the node's object.
// Else if node is a property, return the parent node's object.
Node *node = nodeAtIndex(index);
if(!node) return NULL;
if(node->object) return node->object;
if(node->parent) return node->parent->object;
return NULL;
}
QByteArray QtPropertyTreeModel::propertyNameAtIndex(const QModelIndex &index) const
{
// If node is a property, return the node's property name.
// Else if node is an object, return "objectName".
Node *node = nodeAtIndex(index);
if(!node) return QByteArray();
if(!node->propertyName.isEmpty()) return node->propertyName;
return QByteArray();
}
QModelIndex QtPropertyTreeModel::index(int row, int column, const QModelIndex &parent) const
{
// Return a model index whose internal pointer references the appropriate tree node.
if(column < 0 || column >= 2 || !hasIndex(row, column, parent))
return QModelIndex();
const Node *parentNode = parent.isValid() ? nodeAtIndex(parent) : &_root;
if(!parentNode || row < 0 || row >= parentNode->children.size())
return QModelIndex();
Node *node = parentNode->children.at(row);
return node ? createIndex(row, column, node) : QModelIndex();
}
QModelIndex QtPropertyTreeModel::parent(const QModelIndex &index) const
{
// Return a model index for parent node (column must be 0).
if(!index.isValid())
return QModelIndex();
Node *node = nodeAtIndex(index);
if(!node)
return QModelIndex();
Node *parentNode = node->parent;
if(!parentNode || parentNode == &_root)
return QModelIndex();
int row = 0;
Node *grandparentNode = parentNode->parent;
if(grandparentNode)
row = grandparentNode->children.indexOf(parentNode);
return createIndex(row, 0, parentNode);
}
int QtPropertyTreeModel::rowCount(const QModelIndex &parent) const
{
// Return number of child nodes.
const Node *parentNode = parent.isValid() ? nodeAtIndex(parent) : &_root;
return parentNode ? parentNode->children.size() : 0;
}
int QtPropertyTreeModel::columnCount(const QModelIndex &parent) const
{
// Return 2 for name/value columns.
const Node *parentNode = parent.isValid() ? nodeAtIndex(parent) : &_root;
return (parentNode ? 2 : 0);
}
QVariant QtPropertyTreeModel::data(const QModelIndex &index, int role) const
{
if(!index.isValid())
return QVariant();
if(role == Qt::DisplayRole || role == Qt::EditRole) {
QObject *object = objectAtIndex(index);
if(!object)
return QVariant();
QByteArray propertyName = propertyNameAtIndex(index);
if(index.column() == 0) {
// Object's class name or else the property name.
if(propertyName.isEmpty())
return QVariant(object->metaObject()->className());
else if(propertyHeaders.contains(propertyName))
return QVariant(propertyHeaders[propertyName]);
else
return QVariant(propertyName);
} else if(index.column() == 1) {
// Object's objectName or else the property value.
if(propertyName.isEmpty())
return QVariant(object->objectName());
else
return object->property(propertyName.constData());
}
}
return QVariant();
}
bool QtPropertyTreeModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
if(!index.isValid())
return false;
if(role == Qt::EditRole) {
QObject *object = objectAtIndex(index);
if(!object)
return false;
QByteArray propertyName = propertyNameAtIndex(index);
if(index.column() == 0) {
// Object class name or property name.
return false;
} else if(index.column() == 1) {
// Object's objectName or else the property value.
if(propertyName.isEmpty()) {
object->setObjectName(value.toString());
return true;
} else {
bool result = object->setProperty(propertyName.constData(), value);
// Result will be FALSE for dynamic properties, which causes the tree view to lag.
// So make sure we still return TRUE in this case.
if(!result && object->dynamicPropertyNames().contains(propertyName))
return true;
return result;
}
}
}
return false;
}
Qt::ItemFlags QtPropertyTreeModel::flags(const QModelIndex &index) const
{
Qt::ItemFlags flags = QAbstractItemModel::flags(index);
if(!index.isValid())
return flags;
QObject *object = objectAtIndex(index);
if(!object)
return flags;
flags |= Qt::ItemIsEnabled;
flags |= Qt::ItemIsSelectable;
if(index.column() == 1) {
QByteArray propertyName = propertyNameAtIndex(index);
const QMetaProperty metaProperty = metaPropertyAtIndex(index);
if(metaProperty.isWritable() || object->dynamicPropertyNames().contains(propertyName) || objectAtIndex(index))
flags |= Qt::ItemIsEditable;
}
return flags;
}
QVariant QtPropertyTreeModel::headerData(int section, Qt::Orientation orientation, int role) const
{
if(role == Qt::DisplayRole) {
if(orientation == Qt::Horizontal) {
if(section == 0)
return QVariant("Name");
else if(section == 1)
return QVariant("Value");
else if(section == 3){
return QVariant("type");
}
}
}
return QVariant();
}
QObject* QtPropertyTableModel::objectAtIndex(const QModelIndex &index) const
{
if(_objects.size() <= index.row())
return 0;
QObject *object = _objects.at(index.row());
// If property names are specified, check if name at column is a path to a child object property.
if(!propertyNames.isEmpty()) {
if(propertyNames.size() > index.column()) {
QByteArray propertyName = propertyNames.at(index.column());
if(propertyName.contains('.')) {
int pos = propertyName.lastIndexOf('.');
return descendant(object, propertyName.left(pos));
}
}
}
return object;
}
QByteArray QtPropertyTableModel::propertyNameAtIndex(const QModelIndex &index) const
{
// If property names are specified, return the name at column.
if(!propertyNames.isEmpty()) {
if(propertyNames.size() > index.column()) {
QByteArray propertyName = propertyNames.at(index.column());
if(propertyName.contains('.')) {
int pos = propertyName.lastIndexOf('.');
return propertyName.mid(pos + 1);
}
return propertyName;
}
return QByteArray();
}
// If property names are NOT specified, return the metaObject's property name at column.
QObject *object = objectAtIndex(index);
if(!object)
return QByteArray();
const QMetaObject *metaObject = object->metaObject();
int numProperties = metaObject->propertyCount();
if(numProperties > index.column())
return QByteArray(metaObject->property(index.column()).name());
// If column is greater than the number of metaObject properties, check for dynamic properties.
const QList<QByteArray> &dynamicPropertyNames = object->dynamicPropertyNames();
if(numProperties + dynamicPropertyNames.size() > index.column())
return dynamicPropertyNames.at(index.column() - numProperties);
return QByteArray();
}
QModelIndex QtPropertyTableModel::index(int row, int column, const QModelIndex &/* parent */) const
{
return createIndex(row, column);
}
QModelIndex QtPropertyTableModel::parent(const QModelIndex &/* index */) const
{
return QModelIndex();
}
int QtPropertyTableModel::rowCount(const QModelIndex &/* parent */) const
{
return _objects.size();
}
int QtPropertyTableModel::columnCount(const QModelIndex &/* parent */) const
{
// Number of properties.
if(!propertyNames.isEmpty())
return propertyNames.size();
if(_objects.isEmpty())
return 0;
QObject *object = _objects.at(0);
const QMetaObject *metaObject = object->metaObject();
return metaObject->propertyCount() + object->dynamicPropertyNames().size();
}
QVariant QtPropertyTableModel::headerData(int section, Qt::Orientation orientation, int role) const
{
if(role == Qt::DisplayRole) {
if(orientation == Qt::Vertical) {
return QVariant(section);
} else if(orientation == Qt::Horizontal) {
QByteArray propertyName = propertyNameAtIndex(createIndex(0, section));
QByteArray childPath;
if(propertyNames.size() > section) {
QByteArray pathToPropertyName = propertyNames.at(section);
if(pathToPropertyName.contains('.')) {
int pos = pathToPropertyName.lastIndexOf('.');
childPath = pathToPropertyName.left(pos + 1);
}
}
if(propertyHeaders.contains(propertyName))
return QVariant(childPath + propertyHeaders.value(propertyName));
return QVariant(childPath + propertyName);
}
}
return QVariant();
}
bool QtPropertyTableModel::insertRows(int row, int count, const QModelIndex &parent)
{
// Only valid if we have an object creator method.
if(!_objectCreator)
return false;
bool columnCountWillAlsoChange = _objects.isEmpty() && propertyNames.isEmpty();
beginInsertRows(parent, row, row + count - 1);
for(int i = row; i < row + count; ++i) {
QObject *object = _objectCreator();
_objects.insert(i, object);
}
endInsertRows();
if(row + count < _objects.size())
reorderChildObjectsToMatchRowOrder(row + count);
if(columnCountWillAlsoChange) {
beginResetModel();
endResetModel();
}
emit rowCountChanged();
return true;
}
bool QtPropertyTableModel::removeRows(int row, int count, const QModelIndex &parent)
{
beginRemoveRows(parent, row, row + count - 1);
for(int i = row; i < row + count; ++i)
delete _objects.at(i);
QObjectList::iterator begin = _objects.begin() + row;
_objects.erase(begin, begin + count);
endRemoveRows();
emit rowCountChanged();
return true;
}
bool QtPropertyTableModel::moveRows(const QModelIndex &/*sourceParent*/, int sourceRow, int count, const QModelIndex &/*destinationParent*/, int destinationRow)
{
beginResetModel();
QObjectList objectsToMove;
for(int i = sourceRow; i < sourceRow + count; ++i)
objectsToMove.append(_objects.takeAt(sourceRow));
for(int i = 0; i < objectsToMove.size(); ++i) {
if(destinationRow + i >= _objects.size())
_objects.append(objectsToMove.at(i));
else
_objects.insert(destinationRow + i, objectsToMove.at(i));
}
endResetModel();
reorderChildObjectsToMatchRowOrder(sourceRow <= destinationRow ? sourceRow : destinationRow);
emit rowOrderChanged();
return true;
}
void QtPropertyTableModel::reorderChildObjectsToMatchRowOrder(int firstRow)
{
for(int i = firstRow; i < rowCount(); ++i) {
QObject *object = objectAtIndex(createIndex(i, 0));
if(object) {
QObject *parent = object->parent();
if(parent) {
object->setParent(NULL);
object->setParent(parent);
}
}
}
}
QWidget* QtPropertyDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
QVariant value = index.data(Qt::DisplayRole);
if(value.isValid()) {
if(value.type() == QVariant::Bool) {
// We want a check box, but instead of creating an editor widget we'll just directly
// draw the check box in paint() and handle mouse clicks in editorEvent().
// Here, we'll just return NULL to make sure that no editor is created when this cell is double clicked.
return NULL;
} else if(value.type() == QVariant::Double) {
// Return a QLineEdit to enter double values with arbitrary precision and scientific notation.
QLineEdit *editor = new QLineEdit(parent);
editor->setText(value.toString());
return editor;
} else if(value.type() == QVariant::Int) {
// We don't need to do anything special for an integer, we'll just use the default QSpinBox.
// However, we do need to check if it is an enum. If so, we'll use a QComboBox editor.
const QtAbstractPropertyModel *propertyModel = qobject_cast<const QtAbstractPropertyModel*>(index.model());
if(propertyModel) {
const QMetaProperty metaProperty = propertyModel->metaPropertyAtIndex(index);
if(metaProperty.isValid() && metaProperty.isEnumType()) {
const QMetaEnum metaEnum = metaProperty.enumerator();
int numKeys = metaEnum.keyCount();
if(numKeys > 0) {
QComboBox *editor = new QComboBox(parent);
for(int j = 0; j < numKeys; ++j) {
QByteArray key = QByteArray(metaEnum.key(j));
editor->addItem(QString(key));
}
QByteArray currentKey = QByteArray(metaEnum.valueToKey(value.toInt()));
editor->setCurrentText(QString(currentKey));
return editor;
}
}
}
} else if(value.type() == QVariant::Size || value.type() == QVariant::SizeF ||
value.type() == QVariant::Point || value.type() == QVariant::PointF ||
value.type() == QVariant::Rect || value.type() == QVariant::RectF) {
// Return a QLineEdit. Parsing will be done in displayText() and setEditorData().
QLineEdit *editor = new QLineEdit(parent);
editor->setText(displayText(value, QLocale()));
return editor;
} else if(value.type() == QVariant::UserType) {
if(value.canConvert<QtPushButtonActionWrapper>()) {
// We want a push button, but instead of creating an editor widget we'll just directly
// draw the button in paint() and handle mouse clicks in editorEvent().
// Here, we'll just return NULL to make sure that no editor is created when this cell is double clicked.
return NULL;
}
}
}
return QStyledItemDelegate::createEditor(parent, option, index);
}
void QtPropertyDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const
{
QStyledItemDelegate::setEditorData(editor, index);
}
void QtPropertyDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const
{
QVariant value = index.data(Qt::DisplayRole);
if(value.isValid()) {
if(value.type() == QVariant::Double) {
// Set model's double value data to numeric representation in QLineEdit editor.
// Conversion from text to number handled by QVariant.
QLineEdit *lineEditor = qobject_cast<QLineEdit*>(editor);
if(lineEditor) {
QVariant value = QVariant(lineEditor->text());
bool ok;
double dval = value.toDouble(&ok);
if(ok)
model->setData(index, QVariant(dval), Qt::EditRole);
return;
}
} else if(value.type() == QVariant::Int) {
// We don't need to do anything special for an integer.
// However, if it's an enum we'll set the data based on the QComboBox editor.
QComboBox *comboBoxEditor = qobject_cast<QComboBox*>(editor);
if(comboBoxEditor) {
QString selectedKey = comboBoxEditor->currentText();
const QtAbstractPropertyModel *propertyModel = qobject_cast<const QtAbstractPropertyModel*>(model);
if(propertyModel) {
const QMetaProperty metaProperty = propertyModel->metaPropertyAtIndex(index);
if(metaProperty.isValid() && metaProperty.isEnumType()) {
const QMetaEnum metaEnum = metaProperty.enumerator();
bool ok;
int selectedValue = metaEnum.keyToValue(selectedKey.toLatin1().constData(), &ok);
if(ok)
model->setData(index, QVariant(selectedValue), Qt::EditRole);
return;
}
}
// If we got here, we have a QComboBox editor but the property at index is not an enum.
}
} else if(value.type() == QVariant::Size) {
QLineEdit *lineEditor = qobject_cast<QLineEdit*>(editor);
if(lineEditor) {
// Parse formats: (w x h) or (w,h) or (w h) <== () are optional
QRegularExpression regex("\\s*\\(?\\s*(\\d+)\\s*[x,\\s]\\s*(\\d+)\\s*\\)?\\s*");
QRegularExpressionMatch match = regex.match(lineEditor->text().trimmed());
if(match.hasMatch() && match.capturedTexts().size() == 3) {
bool wok, hok;
int w = match.captured(1).toInt(&wok);
int h = match.captured(2).toInt(&hok);
if(wok && hok)
model->setData(index, QVariant(QSize(w, h)), Qt::EditRole);
}
}
} else if(value.type() == QVariant::SizeF) {
QLineEdit *lineEditor = qobject_cast<QLineEdit*>(editor);
if(lineEditor) {
// Parse formats: (w x h) or (w,h) or (w h) <== () are optional
QRegularExpression regex("\\s*\\(?\\s*([0-9\\+\\-\\.eE]+)\\s*[x,\\s]\\s*([0-9\\+\\-\\.eE]+)\\s*\\)?\\s*");
QRegularExpressionMatch match = regex.match(lineEditor->text().trimmed());
if(match.hasMatch() && match.capturedTexts().size() == 3) {
bool wok, hok;
double w = match.captured(1).toDouble(&wok);
double h = match.captured(2).toDouble(&hok);
if(wok && hok)
model->setData(index, QVariant(QSizeF(w, h)), Qt::EditRole);
}
}
} else if(value.type() == QVariant::Point) {
QLineEdit *lineEditor = qobject_cast<QLineEdit*>(editor);
if(lineEditor) {
// Parse formats: (x,y) or (x y) <== () are optional
QRegularExpression regex("\\s*\\(?\\s*(\\d+)\\s*[x,\\s]\\s*(\\d+)\\s*\\)?\\s*");
QRegularExpressionMatch match = regex.match(lineEditor->text().trimmed());
if(match.hasMatch() && match.capturedTexts().size() == 3) {
bool xok, yok;
int x = match.captured(1).toInt(&xok);
int y = match.captured(2).toInt(&yok);
if(xok && yok)
model->setData(index, QVariant(QPoint(x, y)), Qt::EditRole);
}
}
} else if(value.type() == QVariant::PointF) {
QLineEdit *lineEditor = qobject_cast<QLineEdit*>(editor);
if(lineEditor) {
// Parse formats: (x,y) or (x y) <== () are optional
QRegularExpression regex("\\s*\\(?\\s*([0-9\\+\\-\\.eE]+)\\s*[x,\\s]\\s*([0-9\\+\\-\\.eE]+)\\s*\\)?\\s*");
QRegularExpressionMatch match = regex.match(lineEditor->text().trimmed());
if(match.hasMatch() && match.capturedTexts().size() == 3) {
bool xok, yok;
double x = match.captured(1).toDouble(&xok);
double y = match.captured(2).toDouble(&yok);
if(xok && yok)
model->setData(index, QVariant(QPointF(x, y)), Qt::EditRole);
}
}
} else if(value.type() == QVariant::Rect) {
QLineEdit *lineEditor = qobject_cast<QLineEdit*>(editor);
if(lineEditor) {
// Parse formats: [Point,Size] or [Point Size] <== [] are optional
// Point formats: (x,y) or (x y) <== () are optional
// Size formats: (w x h) or (w,h) or (w h) <== () are optional
QRegularExpression regex("\\s*\\[?"
"\\s*\\(?\\s*(\\d+)\\s*[,\\s]\\s*(\\d+)\\s*\\)?\\s*"
"[,\\s]"
"\\s*\\(?\\s*(\\d+)\\s*[x,\\s]\\s*(\\d+)\\s*\\)?\\s*"
"\\]?\\s*");
QRegularExpressionMatch match = regex.match(lineEditor->text().trimmed());
if(match.hasMatch() && match.capturedTexts().size() == 5) {
bool xok, yok, wok, hok;
int x = match.captured(1).toInt(&xok);
int y = match.captured(2).toInt(&yok);
int w = match.captured(3).toInt(&wok);
int h = match.captured(4).toInt(&hok);
if(xok && yok && wok && hok)
model->setData(index, QVariant(QRect(x, y, w, h)), Qt::EditRole);
}
}
} else if(value.type() == QVariant::RectF) {
QLineEdit *lineEditor = qobject_cast<QLineEdit*>(editor);
if(lineEditor) {
// Parse formats: [Point,Size] or [Point Size] <== [] are optional
// Point formats: (x,y) or (x y) <== () are optional
// Size formats: (w x h) or (w,h) or (w h) <== () are optional
QRegularExpression regex("\\s*\\[?"
"\\s*\\(?\\s*([0-9\\+\\-\\.eE]+)\\s*[,\\s]\\s*([0-9\\+\\-\\.eE]+)\\s*\\)?\\s*"
"[,\\s]"
"\\s*\\(?\\s*([0-9\\+\\-\\.eE]+)\\s*[x,\\s]\\s*([0-9\\+\\-\\.eE]+)\\s*\\)?\\s*"
"\\]?\\s*");
QRegularExpressionMatch match = regex.match(lineEditor->text().trimmed());
if(match.hasMatch() && match.capturedTexts().size() == 5) {
bool xok, yok, wok, hok;
double x = match.captured(1).toDouble(&xok);
double y = match.captured(2).toDouble(&yok);
double w = match.captured(3).toDouble(&wok);
double h = match.captured(4).toDouble(&hok);
if(xok && yok && wok && hok)
model->setData(index, QVariant(QRectF(x, y, w, h)), Qt::EditRole);
}
}
// } else if(value.type() == QVariant::Color) {
// QLineEdit *lineEditor = qobject_cast<QLineEdit*>(editor);
// if(lineEditor) {
// // Parse formats: (r,g,b) or (r g b) or (r,g,b,a) or (r g b a) <== () are optional
// QRegularExpression regex("\\s*\\(?"
// "\\s*(\\d+)\\s*"
// "[,\\s]\\s*(\\d+)\\s*"
// "[,\\s]\\s*(\\d+)\\s*"
// "([,\\s]\\s*(\\d+)\\s*)?"
// "\\)?\\s*");
// QRegularExpressionMatch match = regex.match(lineEditor->text().trimmed());
// if(match.hasMatch() && (match.capturedTexts().size() == 4 || match.capturedTexts().size() == 5)) {
// bool rok, gok, bok, aok;
// int r = match.captured(1).toInt(&rok);
// int g = match.captured(2).toInt(&gok);
// int b = match.captured(3).toInt(&bok);
// if(match.capturedTexts().size() == 4) {
// if(rok && gok && bok)
// model->setData(index, QColor(r, g, b), Qt::EditRole);
// } else if(match.capturedTexts().size() == 5) {
// int a = match.captured(4).toInt(&aok);
// if(rok && gok && bok && aok)
// model->setData(index, QColor(r, g, b, a), Qt::EditRole);
// }
// }
// }
}
}
QStyledItemDelegate::setModelData(editor, model, index);
}
QString QtPropertyDelegate::displayText(const QVariant &value, const QLocale &locale) const
{
if(value.isValid()) {
if(value.type() == QVariant::Size) {
// w x h
QSize size = value.toSize();
return QString::number(size.width()) + QString(" x ") + QString::number(size.height());
} else if(value.type() == QVariant::SizeF) {
// w x h
QSizeF size = value.toSizeF();
return QString::number(size.width()) + QString(" x ") + QString::number(size.height());
} else if(value.type() == QVariant::Point) {
// (x, y)
QPoint point = value.toPoint();
return QString("(")
+ QString::number(point.x()) + QString(", ") + QString::number(point.y())
+ QString(")");
} else if(value.type() == QVariant::PointF) {
// (x, y)
QPointF point = value.toPointF();
return QString("(")
+ QString::number(point.x()) + QString(", ") + QString::number(point.y())
+ QString(")");
} else if(value.type() == QVariant::Rect) {
// [(x, y), w x h]
QRect rect = value.toRect();
return QString("[(")
+ QString::number(rect.x()) + QString(", ") + QString::number(rect.y())
+ QString("), ")
+ QString::number(rect.width()) + QString(" x ") + QString::number(rect.height())
+ QString("]");
} else if(value.type() == QVariant::RectF) {
// [(x, y), w x h]
QRectF rect = value.toRectF();
return QString("[(")
+ QString::number(rect.x()) + QString(", ") + QString::number(rect.y())
+ QString("), ")
+ QString::number(rect.width()) + QString(" x ") + QString::number(rect.height())
+ QString("]");
// } else if(value.type() == QVariant::Color) {
// // (r, g, b, a)
// QColor color = value.value<QColor>();
// return QString("(")
// + QString::number(color.red()) + QString(", ") + QString::number(color.green()) + QString(", ")
// + QString::number(color.blue()) + QString(", ") + QString::number(color.alpha())
// + QString(")");
}
}
return QStyledItemDelegate::displayText(value, locale);
}
void QtPropertyDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
QVariant value = index.data(Qt::DisplayRole);
if(value.isValid()) {
if(value.type() == QVariant::Bool) {
bool checked = value.toBool();
QStyleOptionButton buttonOption;
buttonOption.state |= QStyle::State_Active; // Required!
buttonOption.state |= ((index.flags() & Qt::ItemIsEditable) ? QStyle::State_Enabled : QStyle::State_ReadOnly);
buttonOption.state |= (checked ? QStyle::State_On : QStyle::State_Off);
QRect checkBoxRect = QApplication::style()->subElementRect(QStyle::SE_CheckBoxIndicator, &buttonOption); // Only used to get size of native checkbox widget.
buttonOption.rect = QStyle::alignedRect(option.direction, Qt::AlignLeft, checkBoxRect.size(), option.rect); // Our checkbox rect.
QApplication::style()->drawControl(QStyle::CE_CheckBox, &buttonOption, painter);
return;
} else if(value.type() == QVariant::Int) {
// We don't need to do anything special for an integer.
// However, if it's an enum want to render the key name instead of the value.
// This cannot be done in displayText() because we need the model index to get the key name.
const QtAbstractPropertyModel *propertyModel = qobject_cast<const QtAbstractPropertyModel*>(index.model());
if(propertyModel) {
const QMetaProperty metaProperty = propertyModel->metaPropertyAtIndex(index);
if(metaProperty.isValid() && metaProperty.isEnumType()) {
const QMetaEnum metaEnum = metaProperty.enumerator();
QByteArray currentKey = QByteArray(metaEnum.valueToKey(value.toInt()));
QStyleOptionViewItem itemOption(option);
initStyleOption(&itemOption, index);
itemOption.text = QString(currentKey);
QApplication::style()->drawControl(QStyle::CE_ItemViewItem, &itemOption, painter);
return;
}
}
} else if(value.type() == QVariant::UserType) {
if(value.canConvert<QtPushButtonActionWrapper>()) {
QAction *action = value.value<QtPushButtonActionWrapper>().action;
QStyleOptionButton buttonOption;
buttonOption.state = QStyle::State_Active | QStyle::State_Raised;
//buttonOption.features = QStyleOptionButton::DefaultButton;
if(action) buttonOption.text = action->text();
buttonOption.rect = option.rect;
//buttonOption.rect = QRect(option.rect.x() + 5, option.rect.y() + 5, option.rect.width() - 10, option.rect.height() - 10);
QApplication::style()->drawControl(QStyle::CE_PushButton, &buttonOption, painter);
return;
}
}
}
QStyledItemDelegate::paint(painter, option, index);
}
bool QtPropertyDelegate::editorEvent(QEvent *event, QAbstractItemModel *model, const QStyleOptionViewItem &option, const QModelIndex &index)
{
QVariant value = index.data(Qt::DisplayRole);
if(value.isValid()) {
if(value.type() == QVariant::Bool) {
if(event->type() == QEvent::MouseButtonDblClick)
return false;
if(event->type() != QEvent::MouseButtonRelease)
return false;
QMouseEvent *mouseEvent = static_cast<QMouseEvent*>(event);
if(mouseEvent->button() != Qt::LeftButton)
return false;
//QStyleOptionButton buttonOption;
//QRect checkBoxRect = QApplication::style()->subElementRect(QStyle::SE_CheckBoxIndicator, &buttonOption); // Only used to get size of native checkbox widget.
//buttonOption.rect = QStyle::alignedRect(option.direction, Qt::AlignLeft, checkBoxRect.size(), option.rect); // Our checkbox rect.
// option.rect ==> cell
// buttonOption.rect ==> check box
// Here, we choose to allow clicks anywhere in the cell to toggle the checkbox.
if(!option.rect.contains(mouseEvent->pos()))
return false;
bool checked = value.toBool();
QVariant newValue(!checked); // Toggle model's bool value.
bool success = model->setData(index, newValue, Qt::EditRole);
// Update entire table row just in case some other cell also refers to the same bool value.
// Otherwise, that other cell will not reflect the current state of the bool set via this cell.
if(success)
model->dataChanged(index.sibling(index.row(), 0), index.sibling(index.row(), model->columnCount()));
return success;
} else if(value.type() == QVariant::UserType) {
if(value.canConvert<QtPushButtonActionWrapper>()) {
QMouseEvent *mouseEvent = static_cast<QMouseEvent*>(event);
if(mouseEvent->button() != Qt::LeftButton)
return false;
if(!option.rect.contains(mouseEvent->pos()))
return false;
QAction *action = value.value<QtPushButtonActionWrapper>().action;
if(action) action->trigger();
return true;
}
}
}
return QStyledItemDelegate::editorEvent(event, model, option, index);
}
QtPropertyTreeEditor::QtPropertyTreeEditor(QWidget *parent) : QTreeView(parent)
{
setItemDelegate(&_delegate);
setAlternatingRowColors(true);
setModel(&treeModel);
}
void QtPropertyTreeEditor::resizeColumnsToContents()
{
resizeColumnToContents(0);
resizeColumnToContents(1);
}
QtPropertyTableEditor::QtPropertyTableEditor(QWidget *parent) : QTableView(parent)
{
setItemDelegate(&_delegate);
setAlternatingRowColors(true);
setModel(&tableModel);
verticalHeader()->setSectionResizeMode(QHeaderView::ResizeToContents);
setIsDynamic(_isDynamic);
// Draggable rows.
verticalHeader()->setSectionsMovable(_isDynamic);
connect(verticalHeader(), SIGNAL(sectionMoved(int, int, int)), this, SLOT(handleSectionMove(int, int, int)));
// Header context menus.
horizontalHeader()->setContextMenuPolicy(Qt::CustomContextMenu);
verticalHeader()->setContextMenuPolicy(Qt::CustomContextMenu);
connect(horizontalHeader(), SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(horizontalHeaderContextMenu(QPoint)));
connect(verticalHeader(), SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(verticalHeaderContextMenu(QPoint)));
// Custom corner button.
if(QAbstractButton *cornerButton = findChild<QAbstractButton*>()) {
cornerButton->installEventFilter(this);
}
}
void QtPropertyTableEditor::setIsDynamic(bool b)
{
_isDynamic = b;
// Dragging rows.
verticalHeader()->setSectionsMovable(_isDynamic);
// Corner button.
if(QAbstractButton *cornerButton = findChild<QAbstractButton*>()) {
if(_isDynamic) {
cornerButton->disconnect(SIGNAL(clicked()));
connect(cornerButton, SIGNAL(clicked()), this, SLOT(appendRow()));
cornerButton->setText("+");
cornerButton->setToolTip("Append row");
} else {
cornerButton->disconnect(SIGNAL(clicked()));
connect(cornerButton, SIGNAL(clicked()), this, SLOT(selectAll()));
cornerButton->setText("");
cornerButton->setToolTip("Select all");
}
// adjust the width of the vertical header to match the preferred corner button width
// (unfortunately QAbstractButton doesn't implement any size hinting functionality)
QStyleOptionHeader opt;
opt.text = cornerButton->text();
//opt.icon = cornerButton->icon();
QSize s = (cornerButton->style()->sizeFromContents(QStyle::CT_HeaderSection, &opt, QSize(), cornerButton).expandedTo(QApplication::globalStrut()));
if(s.isValid()) {
verticalHeader()->setMinimumWidth(s.width());
}
}
}
void QtPropertyTableEditor::horizontalHeaderContextMenu(QPoint pos)
{
QModelIndexList indexes = selectionModel()->selectedColumns();
QMenu *menu = new QMenu;
menu->addAction("Resize Columns To Contents", this, SLOT(resizeColumnsToContents()));
menu->popup(horizontalHeader()->viewport()->mapToGlobal(pos));
}
void QtPropertyTableEditor::verticalHeaderContextMenu(QPoint pos)
{
QModelIndexList indexes = selectionModel()->selectedRows();
QMenu *menu = new QMenu;
if(_isDynamic) {
QtPropertyTableModel *propertyTableModel = qobject_cast<QtPropertyTableModel*>(model());
if(propertyTableModel->objectCreator()) {
menu->addAction("Append Row", this, SLOT(appendRow()));
}
if(indexes.size()) {
if(propertyTableModel->objectCreator()) {
menu->addSeparator();
menu->addAction("Insert Rows", this, SLOT(insertSelectedRows()));
menu->addSeparator();
}
menu->addAction("Delete Rows", this, SLOT(removeSelectedRows()));
}
}
menu->popup(verticalHeader()->viewport()->mapToGlobal(pos));
}
void QtPropertyTableEditor::appendRow()
{
if(!_isDynamic)
return;
QtPropertyTableModel *propertyTableModel = qobject_cast<QtPropertyTableModel*>(model());
if(!propertyTableModel || !propertyTableModel->objectCreator())
return;
model()->insertRows(model()->rowCount(), 1);
}
void QtPropertyTableEditor::insertSelectedRows()
{
if(!_isDynamic)
return;
QtPropertyTableModel *propertyTableModel = qobject_cast<QtPropertyTableModel*>(model());
if(!propertyTableModel || !propertyTableModel->objectCreator())
return;
QModelIndexList indexes = selectionModel()->selectedRows();
if(indexes.size() == 0)
return;
QList<int> rows;
foreach(const QModelIndex &index, indexes) {
rows.append(index.row());
}
qSort(rows);
model()->insertRows(rows.at(0), rows.size());
}
void QtPropertyTableEditor::removeSelectedRows()
{
if(!_isDynamic)
return;
QModelIndexList indexes = selectionModel()->selectedRows();
if(indexes.size() == 0)
return;
QList<int> rows;
foreach(const QModelIndex &index, indexes) {
rows.append(index.row());
}
qSort(rows);
for(int i = rows.size() - 1; i >= 0; --i) {
model()->removeRows(rows.at(i), 1);
}
}
void QtPropertyTableEditor::handleSectionMove(int /* logicalIndex */, int oldVisualIndex, int newVisualIndex)
{
if(!_isDynamic)
return;
QtPropertyTableModel *propertyTableModel = qobject_cast<QtPropertyTableModel*>(model());
if(!propertyTableModel)
return;
// Move objects in the model, and then move the sections back to maintain logicalIndex order.
propertyTableModel->moveRows(QModelIndex(), oldVisualIndex, 1, QModelIndex(), newVisualIndex);
disconnect(verticalHeader(), SIGNAL(sectionMoved(int, int, int)), this, SLOT(handleSectionMove(int, int, int)));
verticalHeader()->moveSection(newVisualIndex, oldVisualIndex);
connect(verticalHeader(), SIGNAL(sectionMoved(int, int, int)), this, SLOT(handleSectionMove(int, int, int)));
}
void QtPropertyTableEditor::keyPressEvent(QKeyEvent *event)
{
switch(event->key()) {
case Qt::Key_Backspace:
case Qt::Key_Delete:
if(_isDynamic && QMessageBox::question(this, "Delete Rows?", "Delete selected rows?", QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) {
removeSelectedRows();
}
break;
case Qt::Key_Plus:
appendRow();
break;
default:
break;
}
}
bool QtPropertyTableEditor::eventFilter(QObject* o, QEvent* e)
{
if (e->type() == QEvent::Paint) {
if(QAbstractButton *btn = qobject_cast<QAbstractButton*>(o)) {
// paint by hand (borrowed from QTableCornerButton)
QStyleOptionHeader opt;
opt.init(btn);
QStyle::State styleState = QStyle::State_None;
if (btn->isEnabled())
styleState |= QStyle::State_Enabled;
if (btn->isActiveWindow())
styleState |= QStyle::State_Active;
if (btn->isDown())
styleState |= QStyle::State_Sunken;
opt.state = styleState;
opt.rect = btn->rect();
opt.text = btn->text(); // this line is the only difference to QTableCornerButton
//opt.icon = btn->icon(); // this line is the only difference to QTableCornerButton
opt.position = QStyleOptionHeader::OnlyOneSection;
QStylePainter painter(btn);
painter.drawControl(QStyle::CE_Header, opt);
return true; // eat event
}
}
return false;
}
} // QtPropertyEditor
三 、说说用途
这些年来,大家肯定听多了什么 组态
、 虚幻引擎
、低代码平台
、 拖拽式编程
啊,听起来都好DIAO啊,本质是啥呢,说到底还是一堆的属性配置,与大量的相关业务逻辑做的映射。以HMI
为例,目前说得上名的企业基本都是使用组态
这一套思想,例如威纶通、凡易、WINCC,亿维自动化,matlab也提供组态,甚至于我们所说的大型绘图软件,如cad、solidworks 也有组态的使用,不过人家叫做块,或者说是标准件、模板,大家都是对对象支持了属性编辑
,都是可以模块化的复用和自定义,在这点上都是好兄弟。
既然每个自定义的对象或者组件都有大量的属性需要展示、或者暴漏给用户进行交互,是为每一个控件写一个窗口去支持属性的修改,还是使用一套统一的属性系统,使用通用的模板去完成这一重要的功能模块,这不难得出结论。
没有用Qt的,我可以放心的告诉你,基本都自己实现了一套属性系统,内部使用了大量的反射机制;用了Qt的,当然也有一部分没有使用Qt原生的属性系统,而是苦哈哈的维护这陈年旧代码,随着组件的增加,不断的写对象,写对象属性编辑对话框,子子孙孙,无穷无尽,这有一点好处,提供了长期的需求和就业岗位,也算是造福程序员啦。那么,用了Qt属性系统的那些项目呢,代码清清爽爽,赏心悦目,当然缺点就是你boss好像认为你很闲,一查项目,代码没几行😂 。所以呢,这么好的东西,还是慎用。但是慎用不表示你不需要深层次的掌握它。
四、自定义使用
楼已经太高了,下篇讲吧
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!