PDA

View Full Version : Generic-enough QAbstractItemModel.



hickscorp
5th November 2010, 04:55
Hello Folk :)

i'm trying to achieve something very interesting, and the solution to this pattern turns up to be more complex than i thought. It may be very interesting to think about it, even if you're an experienced Qt programmer.

i'm trying here to first enumerate the key constraints of my project.

1) The core of my project is composed of various objects, all having a list of properties. A GUI is made to allow the user to edit those properties. A property is a very simple struct, composed of a direction (I/O enum) and a QVariant.

2) Since the GUI shall offer the user a very large panel of editors to allow variable edition, i'm using custom types as follows:
class IntList : public QList<qint32> {}; i also have UIntList, StringList, RGBList, etc. You may ask "Why didn't you simply use a typedef YourType QList<qint32>?" and the answer is very simple, when later using Q_REGISTER_METATYPE on my list types, if they are typedefs, then the type ID is the same for those types pointing to the same alias, and the GUI can't make the distinction between, for example a UIDList and a IntList, which are in fact the same objects, but needs to be edited differently on screen. You have to note that i also have similar custom types for QHashes.

3) i have subclassed QItemEditorFactory to allow various custom types to have their own widget for edition. Then in my code, when the GUI gets an object selected in a graphical area, a custom editor is created by my factory and presented to the user. For basic types, everything is fine.

4) i wanted a generic GUI editor for my List / Hash types. First, i strugled with the class template forbiden by Qt when inheriting a QObject. In summary you can't create a QWidget based on a class template to edit a QList<T>. It's forbidden, that's all. i presume i understand why, probably because MOC would then need to create as many classes for this class as there are of used types at compile time... Okay. i realised that instead of writing a perfect QList<T> editor, i would have to write a "model editor".
So, i made a custom model which is a template class. The model looks like this:
template <class L, typename T>
class ListModel : public QAbstractItemModel {
public:
// Constructor / Destructor.
ListModel (QObject *parent=0);
virtual ~ListModel ();

// Get model's header.
virtual QVariant headerData (int section, Qt::Orientation orientation, int role=Qt::DisplayRole) const;
// Get row / column count.
virtual int columnCount (QModelIndex const &parent=QModelIndex()) const;
virtual int rowCount (QModelIndex const &parent=QModelIndex()) const;
virtual bool hasChildren (QModelIndex const &parent=QModelIndex()) const;
// Get single item information (Index, flags, parent, data).
virtual QModelIndex index (int row, int column, QModelIndex const &parent=QModelIndex()) const;
virtual Qt::ItemFlags flags (QModelIndex const &index) const;
virtual QModelIndex parent (QModelIndex const &index) const;
virtual QVariant data (QModelIndex const &index, int role) const;
virtual bool setData (QModelIndex const &index, QVariant const &value, int role=Qt::EditRole);
// Model write methods.
virtual void reset (L &list);
virtual bool insertRow (int position, QModelIndex const &parent=QModelIndex());
virtual bool insertRow (int position, QList<QVariant> const &data, QModelIndex const &parent=QModelIndex());
virtual bool insertRows (int position, int count, QModelIndex const &parent=QModelIndex());
virtual bool insertRows (int position, int count, QList<QVariant> const &data, QModelIndex const &parent=QModelIndex());
virtual bool removeRows (int position, int count, QModelIndex const &index=QModelIndex());

// Get value of modified list.
virtual L const& value () { return _list; };

private:
// Invalid objects.
static QVariant _invalidVariant;
static QModelIndex _invalidIndex;
// Internal data.
L _list;
};
The instanciation / setup of this model is done as follows:
ListModel<UIntList, quint32> *tMdl = new ListModel<UIntList, quint32>(this);tMdl->reset(aListOfUInts);
i'm not very happy of this, because it requires to specify the custom type to work on, AND the subtype the container has, because with my custom types i loose the information of the QList template argument... And my model needs to know this contained type because it needs to instantiate QVariants based on that. If someone has a clean solution for this, that's the first (And probably easier) question i had :)

i went back to the initial problem (The generic QList<T> editor for my factory...), and realised it won't actually edit QLists, because it would be impossible for it to be a class template... So instead of that, it's actually a model editor. It just accepts a setModel.

This is horribly ugly, because instead of using a nice Q_PROPERTY as i can do with my other editors, i have to go thru a very complicated chain of tests on the qMetaTypeId of the variable to be able to instantiate the right ListModel<?,?> like this:
#include "WgtVariableEditor.h"
#include "REATypes.h"
#include "WgtWFNodeUidEditor.h"
#include "WgtListTypeEditor.h"
#include "ListModel.hpp"
#include <QItemEditorFactory>
#include <QHBoxLayout>
#include <QSizePolicy>
#include <QVariant>
#include <QDebug>

using namespace REA;

WgtVariableEditor::WgtVariableEditor (QWidget *parent, Variable const &var)
: QWidget(parent, 0), _widget(0), _variable(var) {
[... Much blabla ...]
// Create the widget and store it's metaobject.
_widget = f->createEditor((QVariant::Type)_variable.value.userT ype(), this);
lyt->addWidget(_widget);
// List type, the editor isn't regular.
int ut = var.value.userType();
if (ut==qMetaTypeId<REA::IntList>() || ut==qMetaTypeId<REA::UIntList>() || ut==qMetaTypeId<REA::RgbList>() || ut==qMetaTypeId<REA::StringList>() || ut==qMetaTypeId<REA::CharList>()) {
QAbstractItemModel *mdl = 0;
if (ut==qMetaTypeId<REA::StringList>()) {
ListModel<StringList, QString> *tMdl = new ListModel<StringList, QString>(this);
tMdl->reset(var.value.value<REA::StringList>());
mdl = tMdl;
}
else if (/*Do this for all my custom types... Ouchy.*/) {
[... Much blabla ...]
}
((WgtListTypeEditor*)_widget)->setModel(mdl);
connect(_widget, SIGNAL(valueChanged()), this, SLOT(onModeledValueEdition()));
}
// Regular editor.
else {
// Retrieve metaobject.
QMetaObject const *mo = _widget->metaObject();
// Retrieve value property name.
QByteArray vpName = f->valuePropertyName((QVariant::Type)_variable.value. userType());
// Set widget value.
_widget->setProperty(vpName, _variable.value);
// Get property index.
int pIndex = mo->indexOfProperty(vpName);
// Retrieve metamethod for notification signal.
QMetaMethod signal = mo->property(pIndex).notifySignal();
// Finally generate signal name.
QString sName = QString("2%1").arg(signal.signature());
connect(_widget, sName.toLatin1(), this, SLOT(onValueEdition()));
}
}
void WgtVariableEditor::onValueEdition () {
// Invalid parameter?
if (!_variable.value.isValid())
return;
// Retrieve editor widget object.
QItemEditorFactory const *f = QItemEditorFactory::defaultFactory();
// Retrieve value property name.
QByteArray vpName = f->valuePropertyName(_variable.value.type());
QVariant value = _widget->property(vpName);
// Invalid variant...
if (!value.isValid())
return;
// Same types...
if (value.userType()!=_variable.value.userType()) {
qDebug() << __FUNCTION__ << "Typing mismatch. Original is of" << _variable.value.type() << "- Emited is of" << value.type();
return;
}
_variable.value = value;
emit(valueEdited(_variable));
}
// i even had to create a custom signal... More tests on the metatypeID =/
void WgtVariableEditor::onModeledValueEdition () {
// Invalid parameter?
if (!_variable.value.isValid())
return;
if (_variable.value.userType()==qMetaTypeId<REA::StringList>()) {
ListModel<StringList, QString> *mdl = (ListModel<StringList,QString>*)((WgtListTypeEditor*)_widget)->model();
_variable.value.setValue(*((REA::StringList*)&mdl->value()));
emit(valueEdited(_variable));
}
}

i think for begining on the subject, you get the big picture. My main question is: How can a very generic List / Hash editor can be achieved using Qt? With Objective-C / Ruby / Python, no problem... The typing is weak, it's so easy with late static binding... With C++ and the lack of class template when inheriting QObject, i'm struggling. i have a solution as you can see, but it's horrible to maintain, and very difficult to explain to coworkers who just have "java" skills...

Anyone can help please?
Thanks a lot :)
Pierre.