go-qml / qml

QML support for the Go language
Other
1.96k stars 187 forks source link

Need support for model interface to set on views #9

Open eivindro opened 10 years ago

eivindro commented 10 years ago

model-view objects like TableView has a model property which needs to be set. Now to feed the view from a proper dynamic model this should probably be a go interface that mimics the virtual functions in C++ class QAbstractItemModel.

Since all other model classes inherits from this C++ class, that's all that needs to be implemented. Convenience interfaces for specialized view like listview and tableview may be implemented purely in Go to make life it easier for the Go user.

For an introduction to the C++ model classes have a look here: http://qt-project.org/doc/qt-5.1/qtquick/qtquick-modelviewsdata-cppmodels.html

Now, the implementation of this is essential to be able to support GUIs with larger amounts of non static data. And it also gives a very clean cut between data and gui as the same data model could be visualized in many different ways without having to make any changes to the data model itself.

niemeyer commented 10 years ago

For the record, working with model views is already possible and convenient today via the indexed mechanism.

Here is a good example:

Let's keep this bug open, though, as there might be advantages in supporting an implementation of QAbstractItemModel.

michael-k commented 10 years ago

Regarding TableViews, I had the problem, that role did not work. (At least I could not find out how to get it to work. So I use this workaround:

TableView {
    model: contactList.len

    TableViewColumn {
        title: qsTr("Name")
        delegate: Item {
            Text {
                anchors.verticalCenter: parent.verticalCenter
                color: styleData.textColor
                elide: styleData.elideMode
                text: contactList.name(styleData.row)
            }
        }
    }
}
quarnster commented 9 years ago

Is there an efficient way to remove/add a single item out of 1000 with the index model?

Just setting model = array.length will re-create all 999/1001 elements rather than just tweak the single one that was changed.

quarnster commented 9 years ago

To answer myself; yes there is. For example:

    ListModel {
        id: myModel
    }
...
    myModel.append({"itemId":rs.item(i).id});

itemId will then exist as a variable in the delagate component that is created as a result. myModel can be located from go so that its functions can be called from there if one wants to.

NSkelsey commented 9 years ago

@quarnster I am trying to figure out what you did here. Is there any chance you could elaborate?

quarnster commented 9 years ago

Sure, here's a quick freestanding example:

package main

import (
    "gopkg.in/qml.v1"
    "log"
)

var qmlFile = `
import QtQuick 2.3
import QtQuick.Controls 1.2
import QtQuick.Layouts 1.0

ApplicationWindow {
    id: main
    width: 800
    height: 600

    function validate() {
        for (var i = 0; i < gomodel.len(); i++) {
            var godata = gomodel.get(i);
            var qmldata = lmodel.get(i).goitem;
            // Note that "godata" and "qmldata" are not
            // pointing to the same address though...
            if (godata.text != qmldata.text) {
                console.log(godata.text, qmldata.text);
            }
        }
    }
    function insert(i, data) {
        lmodel.insert(i, {"goitem": data});
        validate();
    }
    function removed(i) {
        lmodel.remove(i);
        validate();
    }
    ListModel {
        id: lmodel
    }
    ColumnLayout {
        Button {
            text: "add"
            onClicked: {
                console.log("adding!!");
                gomodel.add("Item " + Math.floor(Math.random()*1024));
            }
        }
        Button {
            text: "remove"
            onClicked: {
                console.log("removing!!");
                gomodel.remove(lv.currentIndex);
            }
        }

        ScrollView {
            Layout.fillHeight: true
            ListView {
                id: lv
                model: lmodel
                height: contentHeight
                delegate: Rectangle {
                    id: item
                    color: lv.currentIndex == index ? Qt.rgba(0.9,0.9,0.9,1.0) : "white"
                    width: Math.max(t.width, parent.width)
                    height: t.height
                    clip: true
                    Text {
                        id: t
                        text: goitem.text
                    }
                    MouseArea {
                        anchors.fill: parent
                        onClicked: lv.currentIndex = index
                    }
                }
            }
        }
    }
}
`

var window *qml.Window

type (
    Item struct {
        Text string
    }
    Model struct {
        items []*Item
    }
)

func (m *Model) Add(text string) {
    i := len(m.items)
    m.items = append(m.items, &Item{text})
    window.Call("insert", i, m.items[i])
}

func (m *Model) Remove(i int) {
    copy(m.items[i:], m.items[i+1:])
    m.items = m.items[:len(m.items)-1]
    window.Call("removed", i)
}

func (m *Model) Len() int {
    return len(m.items)
}

func (m *Model) Get(i int) *Item {
    return m.items[i]
}

func main() {
    model := &Model{}
    log.Println(qml.Run(func() error {
        engine := qml.NewEngine()
        defer engine.Destroy()
        engine.Context().SetVar("gomodel", model)
        component, err := engine.LoadString("main.qml", qmlFile)
        if err != nil {
            return err
        }
        window = component.CreateWindow(nil)
        window.Show()
        window.Wait()
        return nil
    }))
}
NSkelsey commented 9 years ago

@quarnster Thanks for the response. That window.Call in Add is very clever! I would not have thought of that.

This should be added to the delegate dir of the examples.

pjoe commented 9 years ago

Though putting things through a QML ListModel is workable, in my experience that won't perform anything near the QAbstractItemModel, that can efficiently signal changes in the model directly to the QML ListView, see e.g. beginInsertRows/endInsertRows. This is especially true if you are doing insertion/removal of mutliple items at once. So having a way to implement this QAbstractItemlModel interface in go would be really cool :D

pjoe commented 9 years ago

Ohh I just saw that there is already a list-model branch with some work in progress for this, anything we can do to help?

niemeyer commented 9 years ago

I'm not sure. The main blocker is researching in depth how to implement these interfaces in a sane way, and the reason it's taking so long is because the interface seems extensive and non-trivial on the C++ side. Whatever we do needs to be straightforward and sensible, and I haven't managed to work on it enough to get to that point.

filcuc commented 9 years ago

Niemeyer we should get in contact. I'm the author of the DOtherSide project that bring Qml bindings for Nim and D. I worked on this topic maybe i can share my implementation and see if it works for you too.

pjoe commented 9 years ago

Also lemme know if there is anything i can do to help. I have been using QAbstractItemList for interfacing C++ and QML for several years by now (since sometime from Qt 4.7beta). I'm on IRC freenode (as pjoe or pjoe_dj) during daytime CET