401 lines
11 KiB
C++
401 lines
11 KiB
C++
#include "qgpropertywidget.h"
|
|
|
|
#include <QTreeView>
|
|
#include <QTreeWidgetItem>
|
|
#include <QStandardItemModel>
|
|
#include <QStandardItem>
|
|
|
|
#include <QWidget>
|
|
#include <QVBoxLayout>
|
|
#include <QLabel>
|
|
|
|
#include <QMetaObject>
|
|
#include <QMetaProperty>
|
|
#include <QMetaEnum>
|
|
|
|
#include <QStyledItemDelegate>
|
|
#include <QItemDelegate>
|
|
|
|
#include <QLineEdit>
|
|
#include <QCheckBox>
|
|
#include <QComboBox>
|
|
#include <QDialogButtonBox>
|
|
|
|
#include <QDebug>
|
|
|
|
class QgPropertyWidget::Implementation
|
|
{
|
|
public:
|
|
QWidget* w;
|
|
QLabel* lbl;
|
|
QVBoxLayout* ly;
|
|
QTreeView* t;
|
|
QStandardItemModel* m;
|
|
|
|
QObject* obj;
|
|
|
|
QDialogButtonBox* bb;
|
|
};
|
|
|
|
|
|
enum DelegateType
|
|
{
|
|
DT_Enum=QVariant::UserType+1,
|
|
DT_Checkbox,
|
|
DT_Combo
|
|
};
|
|
|
|
const int MyTypeRole = Qt::UserRole + 1;
|
|
|
|
class PropertyItem: public QStandardItem
|
|
{
|
|
public:
|
|
QObject& obj;
|
|
const QMetaObject& mt;
|
|
QMetaProperty mp;
|
|
int index;
|
|
QStandardItem& value;
|
|
QStandardItem& label;
|
|
|
|
virtual ~PropertyItem()
|
|
{
|
|
//qDebug()<<"PropertyItem::~PropertyItem()";
|
|
}
|
|
|
|
PropertyItem(QObject& o, int i):
|
|
obj(o),
|
|
mt(*o.metaObject()),
|
|
mp(mt.property(i)),
|
|
index(i),
|
|
value(*this),
|
|
label(*new QStandardItem)
|
|
{
|
|
label.setData(mp.name(), Qt::DisplayRole);
|
|
label.setEditable(false);
|
|
|
|
QString description; //=mt.classInfo(mt.indexOfClassInfo("description")).value();
|
|
description+=QString(" (")+QString(mp.typeName())+QString(")");
|
|
label.setToolTip(description);
|
|
label.setData(MyTypeRole, reinterpret_cast<const unsigned long long>(&obj));
|
|
|
|
value.setData(reinterpret_cast<const unsigned long long>(this), MyTypeRole);
|
|
value.setEnabled(mp.isValid() && mp.isReadable());
|
|
|
|
value.setData("(invalid)", Qt::DisplayRole);
|
|
value.setEditable(false);
|
|
|
|
propertyInit();
|
|
}
|
|
|
|
virtual void propertyInit()
|
|
{
|
|
value.setEditable(mp.isWritable());
|
|
value.setData(mp.read(&obj), Qt::DisplayRole);
|
|
}
|
|
virtual QString displayText() const
|
|
{
|
|
return mp.read(&obj).toString();
|
|
}
|
|
virtual QWidget* createEditor(QWidget */*parent*/, const QStyleOptionViewItem &/*option*/, const QModelIndex &/*index*/) const
|
|
{
|
|
return 0;
|
|
}
|
|
virtual bool updateEditorGeometry(QWidget */*editor*/, const QStyleOptionViewItem &/*option*/, const QModelIndex &/*index*/) const
|
|
{
|
|
return false;
|
|
}
|
|
|
|
virtual bool onSetModelData(QWidget* /*editor*/, QAbstractItemModel */*model*/, const QModelIndex& /*index*/) const
|
|
{
|
|
obj.setProperty(mp.name(), value.data(Qt::DisplayRole));
|
|
return false;
|
|
}
|
|
|
|
virtual bool setModelData(QWidget* editor, QAbstractItemModel *model, const QModelIndex& index)
|
|
{
|
|
label.setCheckable(true);
|
|
label.setCheckState(Qt::Checked);
|
|
return onSetModelData(editor, model, index);
|
|
}
|
|
virtual bool setEditorData(QWidget */*editor*/, const QModelIndex &/*index*/) const
|
|
{
|
|
return false;
|
|
}
|
|
};
|
|
|
|
class EnumPropertyItem: public PropertyItem
|
|
{
|
|
public:
|
|
EnumPropertyItem(QObject& o, int i):
|
|
PropertyItem(o, i)
|
|
{
|
|
}
|
|
|
|
virtual void propertyInit() override
|
|
{
|
|
QMetaEnum e=mp.enumerator();
|
|
auto v=mp.read(&obj).toInt();
|
|
auto key=e.valueToKey(v);
|
|
|
|
//value.setData(v);
|
|
|
|
if (!key)
|
|
value.setData(QString("%1 (invalid)").arg(v), Qt::DisplayRole);
|
|
else
|
|
{
|
|
value.setData(key, Qt::DisplayRole);
|
|
value.setToolTip(QString("%1 (%2)").arg(key).arg(v));
|
|
}
|
|
}
|
|
|
|
virtual QString displayText() const
|
|
{
|
|
return mp.read(&obj).toString();
|
|
}
|
|
|
|
QWidget* createEditor(QWidget *parent, const QStyleOptionViewItem &/*option*/, const QModelIndex &/*index*/) const override
|
|
{
|
|
QMetaEnum e=mp.enumerator();
|
|
auto v=mp.read(&obj).toInt();
|
|
|
|
auto* cb=new QComboBox(parent);
|
|
int selected=0;
|
|
for(int i=0; i<e.keyCount(); ++i)
|
|
{
|
|
//qDebug()<<"ENUM:"<<i<<e.key(i)<<e.value(i);
|
|
|
|
cb->addItem(QString("%1 (%2)").arg(e.key(i)).arg(e.value(i)), e.value(i));
|
|
if (e.value(i)==v)
|
|
selected=i;
|
|
}
|
|
cb->setCurrentIndex(selected);
|
|
return cb;
|
|
}
|
|
virtual bool onSetModelData(QWidget* editor, QAbstractItemModel */*model*/, const QModelIndex& /*index*/) const override
|
|
{
|
|
QComboBox* cb=qobject_cast<QComboBox*>(editor);
|
|
int v=cb->currentData().toInt();
|
|
mp.write(&obj, v);
|
|
value.setData(cb->currentText(), Qt::DisplayRole);
|
|
qDebug()<<"property="<<v;
|
|
return true;
|
|
}
|
|
virtual bool setEditorData(QWidget */*editor*/, const QModelIndex &/*index*/) const override
|
|
{
|
|
return false;
|
|
}
|
|
};
|
|
|
|
class MyDelegate: public QStyledItemDelegate
|
|
{
|
|
QWidget* createEditor(QWidget *parent,
|
|
const QStyleOptionViewItem &option,
|
|
const QModelIndex &index) const override
|
|
{
|
|
auto mt_opaque = index.data(MyTypeRole).toULongLong(); //MyTypeRole).toInt();
|
|
if (mt_opaque)
|
|
{
|
|
auto p=reinterpret_cast<PropertyItem*>(mt_opaque);
|
|
auto editor=p->createEditor(parent, option, index);
|
|
if (editor)
|
|
return editor;
|
|
}
|
|
return QStyledItemDelegate::createEditor(parent, option, index);
|
|
}
|
|
virtual QString displayText(const QVariant &value, const QLocale &locale) const override
|
|
{
|
|
return QStyledItemDelegate::displayText(value, locale);
|
|
}
|
|
virtual void updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index) const
|
|
{
|
|
auto mt_opaque = index.data(MyTypeRole).toULongLong(); //MyTypeRole).toInt();
|
|
if (mt_opaque)
|
|
{
|
|
auto p=reinterpret_cast<PropertyItem*>(mt_opaque);
|
|
if (p->updateEditorGeometry(editor, option, index))
|
|
return;
|
|
}
|
|
QStyledItemDelegate::updateEditorGeometry(editor, option, index);
|
|
}
|
|
virtual void setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const override
|
|
{
|
|
QStyledItemDelegate::setModelData(editor, model, index);
|
|
|
|
auto mt_opaque = index.data(MyTypeRole).toULongLong(); //MyTypeRole).toInt();
|
|
if (mt_opaque)
|
|
{
|
|
auto p=reinterpret_cast<PropertyItem*>(mt_opaque);
|
|
if (p->setModelData(editor, model, index))
|
|
return;
|
|
}
|
|
//QStyledItemDelegate::setModelData(editor, model, index);
|
|
}
|
|
virtual void setEditorData(QWidget *editor, const QModelIndex &index) const override
|
|
{
|
|
auto mt_opaque = index.data(MyTypeRole).toULongLong(); //MyTypeRole).toInt();
|
|
if (mt_opaque)
|
|
{
|
|
auto p=reinterpret_cast<PropertyItem*>(mt_opaque);
|
|
if (p->setEditorData(editor, index))
|
|
return;
|
|
}
|
|
QStyledItemDelegate::setEditorData(editor, index);
|
|
}
|
|
};
|
|
|
|
static void populateTreeR(QObject* obj, const QMetaObject* mt, int start, int stop, QStandardItem* root)
|
|
{
|
|
if (mt==0)
|
|
mt=obj->metaObject();
|
|
if (start<0)
|
|
start=mt->propertyOffset();
|
|
if (stop<0)
|
|
stop=mt->propertyCount();
|
|
|
|
for(int i=start; i<stop; ++i)
|
|
{
|
|
PropertyItem* pi=0;
|
|
auto mt=obj->metaObject();
|
|
auto mp=mt->property(i);
|
|
if (mp.isEnumType() && !mp.isFlagType())
|
|
pi=new EnumPropertyItem(*obj, i);
|
|
else if (mp.type()==(QVariant::Type)QMetaType::QObjectStar)
|
|
{
|
|
auto item=new QStandardItem(mp.name());
|
|
item->setEditable(false);
|
|
//auto i2=new QStandardItem("FAKE");
|
|
//item->appendRow(i2);
|
|
//i2->setEditable(false);
|
|
//QObject* o=((QObject*)mp.read(obj).toULongLong());
|
|
QObject* o=reinterpret_cast<QObject*>(mp.read(obj).toLongLong());
|
|
if (o)
|
|
{
|
|
populateTreeR(o, 0, -1, -1, item);
|
|
root->appendRow(item);
|
|
}
|
|
//pi=new PropertyItem(*obj, i);
|
|
}
|
|
else
|
|
pi=new PropertyItem(*obj, i);
|
|
|
|
pi->propertyInit();
|
|
root->appendRow(QList<QStandardItem*>()<<&pi->label<<pi);
|
|
}
|
|
auto sc=mt->superClass();
|
|
if (sc)
|
|
{
|
|
auto item=new QStandardItem(sc->className());
|
|
item->setEditable(false);
|
|
//auto i2=new QStandardItem("FAKE");
|
|
//item->appendRow(i2);
|
|
//i2->setEditable(false);
|
|
populateTreeR(obj, sc, sc->propertyOffset(), -1, item);
|
|
root->appendRow(item);
|
|
}
|
|
}
|
|
|
|
static void populateTree(QObject* obj, QStandardItem* root)
|
|
{
|
|
populateTreeR(obj, 0, -1, -1, root);
|
|
}
|
|
|
|
QgPropertyWidget::QgPropertyWidget(QObject *obj, QWidget *parent):
|
|
QWidget(parent), //QScrollArea(parent),
|
|
p_(*new QgPropertyWidget::Implementation)
|
|
{
|
|
p_.obj=obj;
|
|
p_.w=this;
|
|
p_.ly=new QVBoxLayout;
|
|
p_.lbl=new QLabel(obj->objectName());
|
|
p_.ly->addWidget(p_.lbl);
|
|
|
|
p_.t=new QTreeView(parent);
|
|
p_.t->setUniformRowHeights(true);
|
|
|
|
p_.t->setItemDelegate(new MyDelegate);
|
|
|
|
p_.m=new QStandardItemModel;
|
|
auto root=p_.m->invisibleRootItem();
|
|
p_.m->setHeaderData(0, Qt::Horizontal, "Property", Qt::DisplayRole);
|
|
p_.m->setHeaderData(1, Qt::Horizontal, "Value", Qt::DisplayRole);
|
|
|
|
populateTree(obj, root);
|
|
|
|
connect(p_.t, &QTreeView::expanded,
|
|
[=](const QModelIndex&)
|
|
{
|
|
qDebug()<<"expanded!";
|
|
});
|
|
|
|
/*
|
|
connect(p_.m, &QStandardItemModel::itemChanged,
|
|
[=](QStandardItem* item)
|
|
{
|
|
item->setCheckState(Qt::Checked);
|
|
}
|
|
);
|
|
*/
|
|
p_.t->setModel(p_.m);
|
|
p_.t->resizeColumnToContents(1);
|
|
p_.t->setAlternatingRowColors(true);
|
|
|
|
p_.ly->addWidget(p_.t);
|
|
|
|
//p_.bb=new QDialogButtonBox(QDialogButtonBox::Ok|QDialogButtonBox::Cancel);
|
|
//p_.ly->addWidget(p_.bb);
|
|
|
|
//p_.w->setLayout(p_.ly);
|
|
//this->setWidget(p_.w);
|
|
setLayout(p_.ly);
|
|
|
|
//connect(p_.bb, &QDialogButtonBox::accepted, this, QDialog::accept);
|
|
//connect(p_.bb, &QDialogButtonBox::rejected, this, QDialog::reject);
|
|
}
|
|
|
|
QgPropertyWidget::~QgPropertyWidget()
|
|
{
|
|
//qDebug()<<"QgPropertyWidget::~QgPropertyWidget()";
|
|
//qDebug()<<p_.obj;
|
|
p_.obj=0;
|
|
p_.t->setModel(0);
|
|
delete p_.m;
|
|
delete &p_;
|
|
}
|
|
|
|
|
|
|
|
class QgPropertyDialog::Implementation
|
|
{
|
|
public:
|
|
QgPropertyWidget* w;
|
|
QVBoxLayout* ly;
|
|
QDialogButtonBox* bb;
|
|
};
|
|
|
|
QgPropertyDialog::QgPropertyDialog(QObject* obj, QWidget* parent):
|
|
QDialog(parent),
|
|
p_(*new QgPropertyDialog::Implementation)
|
|
{
|
|
p_.w=new QgPropertyWidget(obj, this);
|
|
|
|
p_.ly=new QVBoxLayout;
|
|
|
|
p_.ly->addWidget(p_.w);
|
|
|
|
p_.bb=new QDialogButtonBox(QDialogButtonBox::Ok|QDialogButtonBox::Cancel);
|
|
|
|
p_.ly->addWidget(p_.bb);
|
|
|
|
setLayout(p_.ly);
|
|
|
|
connect(p_.bb, &QDialogButtonBox::accepted, this, QDialog::accept);
|
|
connect(p_.bb, &QDialogButtonBox::rejected, this, QDialog::reject);
|
|
}
|
|
|
|
QgPropertyDialog::~QgPropertyDialog()
|
|
{
|
|
//setLayout(0);
|
|
delete p_.ly;
|
|
delete &p_;
|
|
}
|