Qt通用属性工具:随心定义,随时可见(一)

传送门:
《Qt通用属性工具:随心定义,随时可见(一)》
《Qt通用属性工具:随心定义,随时可见(二)》
《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好像认为你很闲,一查项目,代码没几行😂 。所以呢,这么好的东西,还是慎用。但是慎用不表示你不需要深层次的掌握它。

四、自定义使用

楼已经太高了,下篇讲吧

  • 0
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
如果 Qt::WindowTaskbarButtonHint 属性在你使用的 Qt 版本中不可用,可以考虑使用 Qt::Tool 属性来实现窗口分开的效果。Qt::Tool 属性将窗口转换为工具栏窗口,可以在任务栏中分别显示每个窗口的图标和标题。 具体实现方法如下: 1. 在需要设置的窗口类的构造函数中,通过 setWindowFlags 函数设置窗口属性。例如,设置为工具栏窗口: ``` c++ setWindowFlags(Qt::Tool | Qt::WindowMinimizeButtonHint | Qt::WindowSystemMenuHint | Qt::WindowCloseButtonHint | Qt::WindowMaximizeButtonHint | Qt::WindowStaysOnTopHint); ``` 2. 如果希望窗口合并到同一任务栏图标下,可以将 Qt::Tool 属性去掉即可。 ``` c++ setWindowFlags(Qt::Window | Qt::WindowMinimizeButtonHint | Qt::WindowSystemMenuHint | Qt::WindowCloseButtonHint | Qt::WindowMaximizeButtonHint | Qt::WindowStaysOnTopHint); ``` 示例代码如下: ``` c++ #include <QApplication> #include <QWidget> int main(int argc, char *argv[]) { QApplication app(argc, argv); QWidget window; window.setWindowFlags(Qt::Tool | Qt::WindowMinimizeButtonHint | Qt::WindowSystemMenuHint | Qt::WindowCloseButtonHint | Qt::WindowMaximizeButtonHint | Qt::WindowStaysOnTopHint); window.show(); QWidget window2; window2.setWindowFlags(Qt::Tool | Qt::WindowMinimizeButtonHint | Qt::WindowSystemMenuHint | Qt::WindowCloseButtonHint | Qt::WindowMaximizeButtonHint | Qt::WindowStaysOnTopHint); window2.show(); return app.exec(); } ``` 注意:使用 Qt::Tool 属性将窗口转换为工具栏窗口,可能会导致窗口的行为和样式与普通窗口不同。例如,工具栏窗口默认不会显示在 Windows 任务栏和 Mac OS Dock 中。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

键盘会跳舞

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值