QAbstractItemModel with QtQuick: Column is always 0 in the index
-
I'm quite confused by QML. Since a few weeks, I try to implement a timeline for annotation stuff in videos with QML and I can't really get it worked, since I'm quite new to QML.
I try to work you through my problem. This is an example, how the timeline should look like: Timeline example I got different tracks in which I store different annotations which simply represents, that from a start to an end point the video contains an annotations of the given track. For example if I annotate all scenes in a video which contain sunny images, every annotation box marks the scenes where the video has sunny images.
I plan on saving and getting this information through XML files for example. A possibly example would be:
<root length="800" filename="tralala.mp4"> <track name="sunny"> <annotation start="20" end="50"/> <annotation start="70" end="120"/> ... </track> <track name="cloudy"> ... </track> </root>
To get the data into a model I could later use, I parse the file with a method like this:
readModelFromXML():
QFile xmlFile(_filename); xmlFile.open(QIODevice::ReadOnly); xml.setDevice(&xmlFile); TrackItem* root; while(!xml.atEnd() && !xml.hasError()) { QXmlStreamReader::TokenType token = xml.readNext(); if(token == QXmlStreamReader::StartDocument) continue; if(token == QXmlStreamReader::StartElement) { if(xml.name() == "root") { QMap<QString, QVariant> itemData; itemData["length"] = xml.attributes().value("length").toInt(); itemData["filename"] = xml.attributes().value("filename").toString(); root = new TrackItem(itemData); } else if(xml.name() == "track") { QMap<QString, QVariant> itemData; itemData["name"] = xml.attributes().value("name").toString(); TrackItem* track = new TrackItem(itemData, root); root->insertChildren(root->childCount(), track); } else if(xml.name() == "annotation") { QMap<QString, QVariant> itemData; itemData["start"] = xml.attributes().value("start").toInt(); itemData["end"] = xml.attributes().value("end").toInt(); TrackItem* parent = root->child(root->childCount() - 1); TrackItem* annotation = new TrackItem(itemData, parent); parent->insertChildren(parent->childCount(), annotation); } } }
Where a TrackItem holds a QList with its children, a QMap with the stored data and possibly a parent form type TrackItem. So my data than looks much like a tree with a root TrackItem object which has no parent, as data it has stored the length and the filename and as its child objects it has the TrackItems for the different tracks. The track TrackItems than have the root object as their parent and only stores the name of the track. Each track than has the annotations with a start and end point stored as itemData as its childs.
TrackItem.h:
public: explicit TrackItem(QMap<QString, QVariant> &data, TrackItem *parent = 0); ~TrackItem(); some functions for getting childs, inserting childs and so on private: QList<TrackItem*> childItems; QMap<QString, QVariant> itemData; TrackItem *parentItem;
So now we're getting closer to my problem. I made my own QAbstractItemModel implementation for the communication to my QtQuick view. My own QAbstractItemModel currently has the following roles.
roleNames():
QHash<int, QByteArray> roles; roles[NameRole] = "name"; roles[StartFrameRole] = "startFrame"; roles[EndFrameRole] = "endFrame"; return roles;
The data functions looks as follows.
data(const QModelIndex &index, int role):
if (!index.isValid()) return QVariant(); TrackItem *item = getItem(index); if (role == NameRole) return item->data("name"); else if (role == StartFrameRole) return item->data("start"); else if (role == EndFrameRole) return item->data("end"); return QVariant();
with getItem(const QModelIndex &index):
if (index.isValid()) { TrackItem *item = static_cast<TrackItem*>(index.internalPointer()); if (item) return item; } return rootItem;
and TrackItem::data(QString key) returning the data stored in the QMap itemData of the TrackItem.
index(int row, int column, const QModelIndex &parent):
if (parent.isValid() && parent.column() != 0) return QModelIndex(); TrackItem *parentItem = getItem(parent); TrackItem *childItem = parentItem->child(row); if (childItem) return createIndex(row, column, childItem); else return QModelIndex();
So in index I try to create the indexes of the TrackItems.
That much to the C++ side. Now I will cover a bit my QML code and follow with my problem. So on the QML side of the programm, I have a QML file called timeline, which I set in the constructor of my own QWidget, which represents the look of the example above.
TimelineWidget Constructor derived from QWidget:
sharedEngine_ = new QQmlEngine(this); quickWidget_ = new QQuickWidget(sharedEngine_, this); QQmlContext *context = quickWidget_->rootContext(); context->setContextProperty("timeline", this); model_ = new TrackModel(":/resources/example.txt", context); context->setContextProperty("trackmodel", model_); quickWidget_->setSource(QUrl::fromLocalFile("qml/timeline.qml")); ui->layout->addWidget(quickWidget_);
As you can see, at this point I also currently create the QAbstractItemModel and set it as a contect property to the QML context, so I could use my model in QML.
So essentially my timeline QML file is a rectangle containing two columns. In the first one, I create the track heads with the name of the track via a repeater over the context property "trackmodel" from above.
Repeater { id: headerRepeater model: trackmodel TrackHead { label: model.name width: headerWidth height: 50 selected: false } }
In the second column I essentially than create each of my tracks in a scrollView:
Item { width: tracksContainer.width + headerWidth height: headers.height + 30 Column { id: tracksContainer Repeater { id: tracksRepeater model: trackDelegateModel } } }
Here I use a DelegateModel in which I try to build the individual tracks.
DelegateModel { id: trackDelegateModel model: trackmodel Track { model: trackmodel trackId: index height: 50 width: timelineLength ... also here are some "slots" } }
So now to the Track QML file. Each track is also simply a rectangle in which I try to create new Items, that should represent the annotations. Here I also try to use a delegate.
Item { Repeater { id: annotationRepeater; model: trackModel } }
with this DelegateModel:
DelegateModel { id: trackModel Annotation { myModel: model trackIndex: trackId height: 15 width: model.endFrame - model.startFrame x: model.startFrame y: 17.5 ... like before here are also some "slots" } }
So at this point I try to grab information from the QAbstractItemModel via the startFrame and endFrame roles to calculate the length of each annotation. The Annotation than simply is another Rectangle with some manipulation possibilities to move them to another frame in the track or to a whole other track, trimming them and some other things.
Now finally to my problem. I could build the timeline like in the example above. The yellow boxes in the example where painted over in gimp. I can't get the annotations to show up, because I don't understand how the QModelIndex is created. Everytime I step into the data function of the QAbstractItemModel, I could only get TrackItems from the layer after the root, so just the track layer. In what way does QtQuick communicate with the QAbstractItemModel? I thought that I get a row and column in the index function and could create unique indexes for each TrackItem, so that I could grab the proper TrackItem with the getItem function in my data function. Somehow column is always 0 in the index function. How do I tell my model on which layer (root, track or annotation) I am, so that in QML I could grab the right data in the delegates? I hope my problem is clear enough. This is my first post here, so I may apologies if it is to long or out of form. I really hope, someone could help me with this problem.
-
I tried to understand by reading your description. I should appreciate your patience to write this whole description.
Coming to you issue, you are using model and view with View has single column. You are using the repeater. It is single column there. This requests for index with row and column=0. This is how it works always. If you are using TableView with appropriate TableModel then it will request for row and column values.