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>
#endifnamespace 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_OBJECTpublic: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_OBJECTpublic:// 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_OBJECTpublic: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_OBJECTpublic: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_OBJECTpublic: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;};} // QtPropertyEditorQ_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 neededint 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]);elsereturn QVariant(propertyName);} else if(index.column() == 1) {// Object's objectName or else the property value.if(propertyName.isEmpty())return QVariant(object->objectName());elsereturn 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 optionalQRegularExpression 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 optionalQRegularExpression 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 optionalQRegularExpression 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 optionalQRegularExpression 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 optionalQRegularExpression 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 optionalQRegularExpression 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 hQSize size = value.toSize();return QString::number(size.width()) + QString(" x ") + QString::number(size.height());} else if(value.type() == QVariant::SizeF) {// w x hQSizeF 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 QTableCornerButtonopt.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好像认为你很闲,一查项目,代码没几行😂 。所以呢,这么好的东西,还是慎用。但是慎用不表示你不需要深层次的掌握它。

四、自定义使用

楼已经太高了,下篇讲吧

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/239938.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

pmp到底是什么?

一、PMP是什么 PMP 是项目管理的入门级证书&#xff0c;全称是项目管理专业人士资格认证&#xff0c;由美国项目管理协会&#xff08;PMI&#xff09;举办的&#xff0c;从1999 年到现在已经有20多年发展历史了。 顾名思义&#xff0c;PMP考试就是一场评估应试者是否具备专业…

React学习计划-React16--React基础(五)脚手架创建项目、todoList案例、配置代理、消息订阅与发布

一、使用脚手架create-react-app创建项目 react脚手架 xxx脚手架&#xff1a;用来帮助程序员快速创建一个基于xxx库的模板项目 包含了所有需要的配置&#xff08;语法检查、jsx编译、devServe…&#xff09;下载好了所有相关的依赖可以直接运行一个简单的效果 react提供了一个…

红队打靶练习:DIGITALWORLD.LOCAL: MERCY V2

目录 信息收集 1、arp 2、netdiscover 3、nmap 4、nikto 5、whatweb 6、总结 目录探测 1、gobuster 2、dirsearch WEB enum4linux枚举工具 smbclient工具 knock工具 CMS 文件包含漏洞 Tomcat 提权 系统信息收集 本地提权 get root 信息收集 1、arp ┌──…

【设计模式】命令模式

其他系列文章导航 Java基础合集数据结构与算法合集 设计模式合集 多线程合集 分布式合集 ES合集 文章目录 其他系列文章导航 文章目录 前言 一、什么是命令模式&#xff1f; 二、命令模式的优点和应用场景 三、命令模式的要素和实现 3.1 命令 3.2 具体命令 3.3 接受者 …

Github 2023-12-23 开源项目日报 Top10

根据Github Trendings的统计&#xff0c;今日(2023-12-23统计)共有10个项目上榜。根据开发语言中项目的数量&#xff0c;汇总情况如下&#xff1a; 开发语言项目数量Python项目6C项目2C项目1Jupyter Notebook项目1HTML项目1Go项目1非开发语言项目1 免费API集体清单 创建周期…

VGGNet

目录 一、VGGNet介绍 1、VGG块 2、VGG架构 3、LeNet, AlexNet和VGGNet对比 4、总结 二、代码实现 1、定义VGG卷积块 2、VGG网络 3、训练模型 4、总结 一、VGGNet介绍 VGGNet&#xff08;Visual Geometry Group Network&#xff09;是一种深度卷积神经网络&#xff0c;…

java String转asc码,然后ascII再转四位的16进制数。

理论知识补充&#xff1a; char是Java中的保留字&#xff0c;表示一种数据类型。与别的语言不同的是&#xff0c;char在Java中是16位的&#xff0c;因为Java用的是Unicode编码。不过8位的ASCII码包含在Unicode编码中&#xff0c;其值对应十进制的表示范围是0~127。 char是Java八…

《软件方法(下)》8.2.4 类和属性的命名

DDD领域驱动设计批评文集 做强化自测题获得“软件方法建模师”称号 《软件方法》各章合集 8.2 建模步骤C-1 识别类和属性 8.2.4 类和属性的命名 8.2.4.2 关于DDD话语中的“通用语言” DDD&#xff08;领域驱动设计&#xff09;话语中有“通用语言&#xff08;Ubiquitous L…

2023-12-22 linux C语言pthread_kill函数,pthread_kill(tid, 0)可以用来判断线程是否存在

一、该函数其实不是kill线程&#xff0c;而是向线程发送一个signal&#xff0c;pthread_kill()函数的作用是向某个线程传递一个信号&#xff0c;创建的线程中signal(SIGKILL,sig_handler)函数去处理对应的信号&#xff0c;如果你给一个线程发送了SIGQUIT、SIGKILL&#xff0c;但…

Ubuntu20.04.2-mate上Lazarus安装与测试

简言 Lazarus采用RAD方式界面开发&#xff0c;一套代码可交差编译出windows、ios、android、solaris、BSD等 各平台运行的程序&#xff0c;在unbuntu的repo中有2.2.0版本可用&#xff0c;在sourceforge上有2.2.6版本和3.0.0的Rolling版可下载安装&#xff0c;但感觉上2.2.0和2…

跨境电商独立站深度分析演示网站

对于跨境电商卖家来说&#xff0c;多平台、多站点的布局是非常重要的战略。这样做可以规避”鸡蛋放在同一个篮子里”的风险也能够追求更高的销售额和利润。同时&#xff0c;市场的变化也带来了新的发展机会&#xff0c;因此很多出海企业都希望抓住独立站的新机遇&#xff0c;抢…

【优质】「web开发网页制作」html+css关于动漫主题海贼王网页制作(7页面附源码)

涉及知识 动漫主题网页制作&#xff0c;海贼王网站7页面&#xff0c;动漫divcss&#xff0c;动漫网站成品&#xff0c;CSSDIV布局&#xff0c;期末网页大作业&#xff0c;网页作业成品&#xff0c;web前端源码实例&#xff0c;如何制作网页&#xff0c;网页设计思路&#xff0…

SpringIOC之SimpleTimeZoneAwareLocaleContext

博主介绍:✌全网粉丝5W+,全栈开发工程师,从事多年软件开发,在大厂呆过。持有软件中级、六级等证书。可提供微服务项目搭建与毕业项目实战,博主也曾写过优秀论文,查重率极低,在这方面有丰富的经验✌ 博主作品:《Java项目案例》主要基于SpringBoot+MyBatis/MyBatis-plus+…

IntelliJ IDEA 2020将SpringMVC项目打成war包

一 、打开 Project Structure 进行配置 1. 打开方式 &#xff08;1&#xff09;CtrlAltShiftS &#xff08;2&#xff09;File->Project Structure &#xff08;3&#xff09;点击如下图标&#xff1a; 2. 进入 Project Structure&#xff0c;添加Artifacts Web Applica…

后台留言列表

<!DOCTYPE html> <html> <head> <meta charset"utf-8"> <title>后台管理系统</title> <meta http-equiv"X-UA-Compatible" content"IEedge,chrome1"> <meta http-equiv"Acc…

解决xcode 运行不老iPhone 15 iOS 17.1 设备的问题

问题 最近要查看一下ios 17.1的设备的性能&#xff0c;但是当前版本的Xcode运行不了 解决方法 1、更新Xcode版本到15.1以上 2、更新完成后&#xff0c;大概率出现这个情况 原因&#xff1a;在app Store中更新到Xcode15后,运行不了模拟器和真机.需要下载iOS 17对应的模拟器.&…

Linux 一键部署二进制Gitea

gitea 前言 Gitea 是一个轻量级的 DevOps 平台软件。从开发计划到产品成型的整个软件生命周期,他都能够高效而轻松的帮助团队和开发者。包括 Git 托管、代码审查、团队协作、软件包注册和 CI/CD。它与 GitHub、Bitbucket 和 GitLab 等比较类似。 Gitea 最初是从 Gogs 分支而来…

python报错A value is trying to be set on a copy of a slice

加入.copy()即可避免该报错提示 原代码&#xff1a; df5df4.drop_duplicates() print(df5.shape)df5[班型中文名称]df5[班型名称]-A print(df5.head()) 输出结果&#xff1a; 修改后代码&#xff1a; df5df4.drop_duplicates().copy() print(df5.shape)df5[班型中文名称]df…

通过 Higress Wasm 插件 3 倍性能实现 Spring-cloud-gateway 功能

作者&#xff1a;韦鑫&#xff0c;Higress Committer&#xff0c;来自南京航空航天大学分布式系统实验室 导读&#xff1a;本文将和大家一同回顾 Spring Cloud Gateway 是如何满足 HTTP 请求/响应转换需求场景的&#xff0c;并为大家介绍在这种场景下使用 Higress 云原生网关的…

一个屌丝程序员的分享

目录 闲扯 关于chatgpt的看法 关于学习工作之外的事情 关于提升自我&#xff0c;避免内耗的事情 写在最后 闲扯 现在是2023.11.8日晚上10.37分&#xff0c;刚结束完今天的任务&#xff0c;今天过的很累&#xff0c;今年过的很快&#xff0c;今年基本过的也很不如意&#x…