therecipe / qt

Qt binding for Go (Golang) with support for Windows / macOS / Linux / FreeBSD / Android / iOS / Sailfish OS / Raspberry Pi / AsteroidOS / Ubuntu Touch / JavaScript / WebAssembly
GNU Lesser General Public License v3.0
10.49k stars 748 forks source link

QTabWidget example #70

Closed omac777 closed 7 years ago

omac777 commented 8 years ago

The following is a bit of code for a QTabWidget example created dynamically without using qml files.

mkdir qtabwidget1
cd qtabwidget1
rm -rf deploy/
qtdeploy build desktop
cd deploy/linux_minimal/
./qtabwidget1.sh 

//Here is the main.go to be placed in qtabwidget1
package main

import (
    "os"

    "github.com/therecipe/qt/core"
    "github.com/therecipe/qt/widgets"
)

func main() {
    //everything important in the main is invoked from within qt/widgets
    widgets.NewQApplication(len(os.Args), os.Args)

    //create a button and connect the clicked signal
    var button = widgets.NewQPushButton2("Click me!", nil)
    button.ConnectClicked(func(flag bool) {
        widgets.QMessageBox_Information(nil, "OK", "You clicked me!", widgets.QMessageBox__Ok, widgets.QMessageBox__Ok)
    })

    var myTabWidget *widgets.QTabWidget
    myTabWidget = widgets.NewQTabWidget(nil)

    //Create the entire first tab page
    var myFirstTabPage *widgets.QWidget
    myFirstTabPage = widgets.NewQWidget(nil, 0)
    var layoutFirstTabPage = widgets.NewQVBoxLayout()
    var buttonFirstTabPage = widgets.NewQPushButton2("Click me! first page", nil)
    layoutFirstTabPage.AddWidget(buttonFirstTabPage, 0, core.Qt__AlignCenter)
    myFirstTabPage.SetLayout(layoutFirstTabPage)

    //Create the entire second tab page
    var mySecondTabPage *widgets.QWidget
    mySecondTabPage = widgets.NewQWidget(nil, 0)
    var layoutSecondTabPage = widgets.NewQVBoxLayout()
    var buttonSecondTabPage = widgets.NewQPushButton2("Click me! second page", nil)
    layoutSecondTabPage.AddWidget(buttonSecondTabPage, 0, core.Qt__AlignCenter)
    mySecondTabPage.SetLayout(layoutSecondTabPage)

    //Create the entire third tab page
    var myThirdTabPage *widgets.QWidget
    myThirdTabPage = widgets.NewQWidget(nil, 0)
    var layoutThirdTabPage = widgets.NewQVBoxLayout()
    var buttonThirdTabPage = widgets.NewQPushButton2("Click me! third page", nil)
    layoutThirdTabPage.AddWidget(buttonThirdTabPage, 0, core.Qt__AlignCenter)
    myThirdTabPage.SetLayout(layoutThirdTabPage)

    //Now add the tab pages to the tab widget itself
    myTabWidget.AddTab(myFirstTabPage, "first")
    myTabWidget.AddTab(mySecondTabPage, "second")
    myTabWidget.AddTab(myThirdTabPage, "third")

    //create a main window layout
    var mainWindowLayout = widgets.NewQVBoxLayout()

    //now add stuff to the mainWindowLayout
    mainWindowLayout.AddWidget(myTabWidget, 0, core.Qt__AlignCenter)
    mainWindowLayout.AddWidget(button, 0, core.Qt__AlignCenter)

    //create the main window,
    var window = widgets.NewQMainWindow(nil, 0)
    window.SetWindowTitle("Hello World Example")
    window.SetMinimumSize2(640, 480)

    //attach the mainWindowLayout to the main window itself
    window.Layout().DestroyQObject()
    window.SetLayout(mainWindowLayout)

    //show the main window
    window.Show()

    //run the main application main driver
    widgets.QApplication_Exec()
}
therecipe commented 8 years ago

Hey

Nice, I'm always looking for new examples. Can I use this?

omac777 commented 8 years ago

On 10/20/2016 08:49 AM, therecipe wrote:

Hey

Nice, I'm always looking for new examples. Can I use this?

I'm happy it's useful. By all means, go right ahead.

I want to make it a bit more thorough with some real controls in each tab page along with a few signals/slots for each of those controls. I'll try to send something after the weekend.

Cheers

therecipe commented 8 years ago

Okay, I will happily merge it and integrate it into the setup :)

omac777 commented 8 years ago

On 10/26/2016 12:12 PM, therecipe wrote:

Okay, I will happily merge it and integrate it into the setup :)

I was trying to do something similar with the qmlengine passing values without any actual .qml files, but it seems not possible. Unfortunately a QMainWindow is not a QmlView. I cannot set: 1)window.RootContext().SetContextProperty("QmlBridge", qmlBridge) I find great value in this qmlbridge.

2)window.SetSource(core.NewQUrl3("qrc:///qml/bridge2.qml", 0)) I understand this is now necessary, but I was hoping to find a way to remove the dependency of having any .qml file at all in the scenario where we want to build gui's dynamically within the code, BUT still wishing to have the qml bridge to pass values between the qt gui fields and golang in duplex fashion.

I was getting compile errors with the following. The point of this exercise was having signals that pass interesting information from the gui to the golang for processing, then when done, the processed information would need to be passed back. There are two ways: 1)native just qt's c++ way with no qml which I would love to see within your infrastructure(no example yet) and 2)qml with qmlengine way(qtbridge/qtbridge2).

package main

import ( "fmt" "github.com/therecipe/qt/core" "github.com/therecipe/qt/gui" "github.com/therecipe/qt/widgets" "os" "time" )

//go:generate qtmoc type QmlBridge struct { core.QObject

_ func(data string)        `signal:sendToQml`
_ func(data string) string `slot:sendToGo`

}

func main() { //everything important in the main is invoked from within qt/widgets widgets.NewQApplication(len(os.Args), os.Args)

var myTabWidget *widgets.QTabWidget
myTabWidget = widgets.NewQTabWidget(nil)

//Create the entire first tab page
var myFirstTabPage *widgets.QWidget
myFirstTabPage = widgets.NewQWidget(nil, 0)
var layoutFirstTabPage = widgets.NewQVBoxLayout()
var buttonFirstTabPage = widgets.NewQPushButton2("Click me! first

page", nil)

buttonFirstTabPage.ConnectClicked(func(flag bool) {
    //widgets.QMessageBox_Information(nil, "OK", "You clicked me!",

widgets.QMessageBoxOk, widgets.QMessageBoxOk) fmt.Printf("buttonFirstTabPage.ConnectClicked()...\n") })

layoutFirstTabPage.AddWidget(buttonFirstTabPage, 0, core.Qt__AlignCenter)
myFirstTabPage.SetLayout(layoutFirstTabPage)

//Create the entire second tab page
var mySecondTabPage *widgets.QWidget
mySecondTabPage = widgets.NewQWidget(nil, 0)
var layoutSecondTabPage = widgets.NewQVBoxLayout()
var buttonSecondTabPage = widgets.NewQPushButton2("Click me! second

page", nil)

buttonSecondTabPage.ConnectClicked(func(flag bool) {
    //widgets.QMessageBox_Information(nil, "OK", "You clicked me!",

widgets.QMessageBoxOk, widgets.QMessageBoxOk) fmt.Printf("buttonSecondTabPage.ConnectClicked()...\n") })

layoutSecondTabPage.AddWidget(buttonSecondTabPage, 0, core.Qt__AlignCenter)
mySecondTabPage.SetLayout(layoutSecondTabPage)

//Create the entire third tab page
var myThirdTabPage *widgets.QWidget
myThirdTabPage = widgets.NewQWidget(nil, 0)
var layoutThirdTabPage = widgets.NewQVBoxLayout()
var buttonThirdTabPage = widgets.NewQPushButton2("Click me! third

page", nil)

buttonThirdTabPage.ConnectClicked(func(flag bool) {
    //widgets.QMessageBox_Information(nil, "OK", "You clicked me!",

widgets.QMessageBoxOk, widgets.QMessageBoxOk) fmt.Printf("buttonThirdTabPage.ConnectClicked()...\n") })

layoutThirdTabPage.AddWidget(buttonThirdTabPage, 0, core.Qt__AlignCenter)
myThirdTabPage.SetLayout(layoutThirdTabPage)

//Now add the tab pages to the tab widget itself
myTabWidget.AddTab(myFirstTabPage, "first")
myTabWidget.AddTab(mySecondTabPage, "second")
myTabWidget.AddTab(myThirdTabPage, "third")

//create a main window button
var myMainWindowButton = widgets.NewQPushButton2("Click me!", nil)

//connect clicked signal to the main window button
myMainWindowButton.ConnectClicked(func(flag bool) {
    //widgets.QMessageBox_Information(nil, "OK", "You clicked me!",

widgets.QMessageBoxOk, widgets.QMessageBoxOk) fmt.Printf("myMainWindowButton.ConnectClicked()...\n") })

//create a main window layout
var mainWindowLayout = widgets.NewQVBoxLayout()

//now add stuff to the mainWindowLayout
mainWindowLayout.AddWidget(myTabWidget, 0, core.Qt__AlignCenter)
mainWindowLayout.AddWidget(myMainWindowButton, 0, core.Qt__AlignCenter)

//create the main window,
var window = widgets.NewQMainWindow(nil, 0)
window.SetWindowTitle("Hello World Example")
window.SetMinimumSize2(640, 480)

//attach the mainWindowLayout to the main window itself
window.Layout().DestroyQObject()
window.SetLayout(mainWindowLayout)

var qmlBridge = NewQmlBridge(nil)
qmlBridge.ConnectSendToGo(func(data string) string {
    fmt.Println("go:", data)
    return "hello from go"
})

window.RootContext().SetContextProperty("QmlBridge", qmlBridge)
//view.RootContext().SetContextProperty("QmlBridge", qmlBridge)
//view.SetSource(core.NewQUrl3("qrc:///qml/bridge2.qml", 0))

go func() {
    for t := range time.NewTicker(time.Second * 1).C {
        qmlBridge.SendToQml(t.Format(time.ANSIC))
    }
}()

//show the main window
window.Show()

//run the main application main driver
widgets.QApplication_Exec()

}

therecipe commented 8 years ago

Hey

I hope that I understood you correctly.

You are interested in an quick/bridge2 equivalent in plain c++ widgets, am I right? (the signal/slot part of it)

Because if you would want to omit the projectFolder/qml/*.qml "asset" files (which are compiled into the application and not needed afterwards), you could use the QQmlEngine class and the QQmlComponent::setData function to create QML components at runtime (for example with inlined or dynamicaly created QML code). But it's generally not recommended to create/manage QML components directly from C++ code and that's the reason why the communication between QML and C++ is made with signals/slots in the examples.

Here is an example of how to use signals and slots with C++ widgets. (I ported over the quick/bridge2 example) I intentionaly didn't change the "QmlBridge" and "sendToQml" and "sendToGo" names to make it easier to compare both examples.

package main

import (
    "fmt"
    "os"
    "time"

    "github.com/therecipe/qt/core"
    "github.com/therecipe/qt/gui"
    "github.com/therecipe/qt/widgets"
)

//go:generate qtmoc
type QmlBridge struct {
    core.QObject

    _ func(data string)        `signal:sendToQml`
    _ func(data string) string `slot:sendToGo` //only slots can return something
}

func main() {
    var qmlBridge *QmlBridge

    widgets.NewQApplication(len(os.Args), os.Args)

    //create a label, which is also later showed (instead of a QMainWindow)
    var label = widgets.NewQLabel(nil, 0)
    label.SetMinimumSize2(320, 240)
    label.SetStyleSheet("QLabel { background-color: black; color: white; font-size: 16px }")
    label.SetAlignment(core.Qt__AlignCenter)

    //used this because QLabel got no clicked signal
    label.ConnectMousePressEvent(func(ev *gui.QMouseEvent) {
        //in main thread

        println(qmlBridge.SendToGo("hello from qml"))
    })

    //create a instance of QmlBridge and connect the slot and signal
    qmlBridge = NewQmlBridge(nil)
    qmlBridge.ConnectSendToGo(func(data string) string {
        //in main thread

        fmt.Println("go:", data)
        return "hello from go"
    })
    qmlBridge.ConnectSendToQml(func(data string) {
        //in main thread

        label.SetText(data)
    })

    //timer in another goroutine (and thread) that emits the signal to update the label
    //you need to use slots or signals if you want to change visual Qt elements from another thread
    go func() {
        //some other thread

        for t := range time.NewTicker(time.Second * 1).C {
            qmlBridge.SendToQml(t.Format(time.ANSIC))
        }
    }()

    //show label (which will act as a window)
    label.Show()

    widgets.QApplication_Exec()
}

Maybe I should also explain that you can subclass any Qt class that is derived from QObject and add your own signals and slots to it. To subclass for example the QMainWindow class, you could create a struct like this:

type MyCustomMainWindow struct {
  widgets.QMainWindow //it's important that you don't use a pointer for the parent class, or it won't work

  _ func(someStringArray []string, someQVariant *core.QVariant)        `signal:myCustomSignal` //names need to start with a lower case for both signals and slots
  _ func(someString string, someInteger int, someQObject *core.QObject) string `slot:myCustomSlot` //almost everthing is support, but you can return only one parameter
  _ func() `slot:myCustomSlot2`
}

Then run qtmoc in the same folder and you should get a bunch of moc_* files. (qtdeploy also does that for you before compiling)

Now you should be able to create a new instance of MyCustomMainWindow with NewMyCustomMainWindow() and connect the slots and signals as usuall with *.ConnectMyCustomSignal(), *.ConnectMyCustomSlot(), .... And you can also call the signals/slots with *.MyCustomSignal(), *.MyCustomSlot(), ...

Also here is another example where I needed to wrap a function with a custom slot, to show a notification in the main thread. https://github.com/therecipe/qt/issues/83#issuecomment-256374229

omac777 commented 8 years ago

Thank you clarifying the qmlbridge usage without static qml files/assets and providing extra information about custom types and custom slots. It was very helpful.

therecipe commented 8 years ago

You are welcome :)

I probably should put some of this info into the readme or wiki.

omac777 commented 8 years ago

I noticed an odd difference in windows vs linux for qml tableview. linux doesn't require application window within the qml file when running it through standalone qmlscene -I ./qml blahtableview.qml

http://doc.qt.io/qt-5/qml-qtquick-controls-tableview.html import QtQuick 2.2 import QtQuick.Layouts 1.1 import QtQuick.Controls 1.4

ListModel { id: libraryModel ListElement { title: "A Masterpiece" author: "Gabriel" } ListElement { title: "Brilliance" author: "Jens" } ListElement { title: "Outstanding" author: "Frederik" } } TableView { TableViewColumn { role: "title" title: "Title" width: 100 } TableViewColumn { role: "author" title: "Author" width: 200 } model: libraryModel }

In windows, you need to wrap it with something like: import QtQuick 2.2 import QtQuick.Layouts 1.1 import QtQuick.Controls 1.4

ApplicationWindow { id: window visible: true

//above tableview stuff goes here

}

It's nice to make it appear in qmlscene, but I wasn't able to get it to appear with qtdeploy build desktop cd deploy/linux_minimal/ ./qtableview1.sh

Perhaps it has to do with missing stuff within the deploy/linux_minimal/ missing contents found within the qt's internal qml/ directory holding the tableview.qml and associated .so/.dll's.

It would be nice to see table view as part of the examples using some bridge stuff to modify different contents within the listmodel and updating the view, then modifying the view contents consequently updating the list model and triggering a bridge event to notify the golang side of the change in the listmodel and act accordingly.

therecipe commented 8 years ago

Thats strange, qmlscene should behave the same on all systems.

Usually you have to use ApplicationWindow, if you use qml.NewQQmlApplicationEngine and can replace it with something like Rectangle if you use quick.NewQQuickView.

Unlike QQuickView, QQmlApplicationEngine does not automatically create a root window. If you are using visual items from Qt Quick, you will need to place them inside of a Window.

From http://doc.qt.io/qt-5/qqmlapplicationengine.html

But its importent to always have only 1 root item per *.qml file. That's probably why the example didn't worked. If I wrap it with a Rectangle or Item it works for me on Linux and macOS.

I will try to create small example that does what you described above.

therecipe commented 8 years ago

Added a small example: https://github.com/therecipe/qt/tree/master/internal/examples/quick/tableview

But this is just one way to do this, you could for example also use a C++ core.QAbstractItemModel instead of a QML ListModel.

omac777 commented 8 years ago

That example is well-appreciated. I just updated from github and ran qtsetup again. I must admit, the rebuild of therecipe/qt for 15mins is definitely ok.

Where I feel the pain point again, being used to golang builds without header files and rebuild alls, building with "qtdeploy build desktop" on every iteration and having to re-run the qtmoc and associated tools certainly eats up the rapid golang build cycle I am used to.

So here is a suggestion to reflect upon. Is there any way to circumvent having to use all the qtbuild tools on a regular basis? How about building a generic qmlbridge that passes stuff to qml gui's and receives generic ui events for which we just override if we want to use them? That way we don't have to use any qtbuild tools and reuse one golang qmlbridge package that has already been built and we just import that without having to rebuild it all the time as we currently do with qtdeploy build desktop.

omac777 commented 8 years ago

Everything is good on Debian Sid. the Tableview example displayed. therecipe/qt, you are EXTRAORDINARY! I wish your project a long life. It deserves more than the 800+ stars it has now.

export QT_DIR=/opt/Qt5.7.0 cd /home/loongson/Code/src/github.com mv therecipe theoldrecipe go get -u github.com/therecipe/qt go get -v github.com/therecipe/qt/cmd/... cd /home/loongson/Code/src/github.com/therecipe/qt $GOPATH/bin/qtsetup INFO[0000] running setup/prep
INFO[0000] running setup/check desktop
INFO[0000] GOOS: linux
INFO[0000] GOVERSION: go1.7.3
INFO[0000] GOROOT: /home/loongson/go1.7.3
INFO[0000] GOPATH: /home/loongson/Code
INFO[0000] HASH: 295dde0726c58a2348818b327d0d1a2138ae360b INFO[0000] QT_DIR: /opt/Qt5.7.0
INFO[0000] QT_STUB: false
INFO[0000] Distro: ubuntu
INFO[0000] QT_PKG_CONFIG:
INFO[0000] UsePkgConfig: false
INFO[0000] running setup/generate
INFO[0007] generating full qt/core
INFO[0019] generating full qt/androidextras
INFO[0020] generating full qt/gui
INFO[0030] generating full qt/network
INFO[0033] generating full qt/xml
INFO[0034] generating full qt/dbus
INFO[0035] generating full qt/nfc
INFO[0035] generating full qt/script
INFO[0036] generating full qt/sensors
INFO[0038] generating full qt/positioning
INFO[0039] generating full qt/widgets
INFO[0078] generating full qt/sql
INFO[0079] generating full qt/qml
INFO[0081] generating full qt/websockets
INFO[0081] generating full qt/xmlpatterns
INFO[0081] generating full qt/bluetooth
INFO[0083] generating full qt/webchannel
INFO[0083] generating full qt/svg
INFO[0084] generating full qt/multimedia
INFO[0091] generating full qt/quick
INFO[0093] generating full qt/help
INFO[0096] generating full qt/location
INFO[0096] generating full qt/scripttools
INFO[0096] generating full qt/uitools
INFO[0096] generating full qt/x11extras
INFO[0096] generating full qt/webengine
INFO[0098] generating full qt/testlib
INFO[0098] generating full qt/serialport
INFO[0098] generating full qt/serialbus
INFO[0099] generating full qt/printsupport
INFO[0100] generating full qt/designer
INFO[0103] generating full qt/scxml
INFO[0103] generating full qt/gamepad
INFO[0104] generating full qt/purchasing
INFO[0104] generating full qt/sailfish
INFO[0104] running setup/install desktop
INFO[0104] starting to install modules (~15min)
INFO[0104] installing full qt/core
INFO[0150] installing full qt/androidextras
INFO[0151] installing full qt/gui
INFO[0192] installing full qt/network
INFO[0211] installing full qt/xml
INFO[0217] installing full qt/dbus
INFO[0223] installing full qt/nfc
INFO[0227] installing full qt/script
INFO[0231] installing full qt/sensors
INFO[0244] installing full qt/positioning
INFO[0249] installing full qt/widgets
INFO[0629] installing full qt/sql
INFO[0640] installing full qt/qml
INFO[0646] installing full qt/websockets
INFO[0650] installing full qt/xmlpatterns
INFO[0654] installing full qt/bluetooth
INFO[0662] installing full qt/webchannel
INFO[0664] installing full qt/svg
INFO[0670] installing full qt/multimedia
INFO[0713] installing full qt/quick
INFO[0729] installing full qt/help
INFO[0745] installing full qt/location
INFO[0749] installing full qt/scripttools
INFO[0751] installing full qt/uitools
INFO[0755] installing full qt/x11extras
INFO[0756] installing full qt/webengine
INFO[0765] installing full qt/testlib
INFO[0769] installing full qt/serialport
INFO[0772] installing full qt/serialbus
INFO[0779] installing full qt/printsupport
INFO[0790] installing full qt/designer
INFO[0805] installing full qt/scxml
INFO[0809] installing full qt/gamepad
INFO[0812] installing full qt/purchasing
INFO[0815] installing full qt/sailfish
INFO[0815] running setup/test desktop (~10min)
INFO[0815] testing widgets/line_edits
INFO[0836] testing widgets/video_player
INFO[0862] testing widgets/graphicsscene
INFO[0877] testing widgets/dropsite
INFO[0920] testing widgets/table
INFO[0957] testing widgets/treeview/treeview_dual
INFO[0978] testing widgets/treeview/treeview_filelist
INFO[0995] testing widgets/bridge2
INFO[1020] testing widgets/systray
INFO[1043] testing quick/bridge
INFO[1070] testing quick/bridge2
INFO[1094] testing quick/calc
INFO[1111] testing quick/dialog
INFO[1128] testing quick/translate
INFO[1145] testing quick/view
INFO[1161] testing quick/tableview
INFO[1188] testing qml/application
INFO[1200] testing qml/material
INFO[1210] testing qml/prop
INFO[1222] testing uitools/calculator

therecipe commented 8 years ago

Thank you :)

Yes, qtdeploy is pretty slow compared to the standard go build/run

For example building the quick/tableview example with qtdeploy build desktop takes 30 sec for me. <1 sec is spend with qtrcc 5 sec are spend with qtmoc 8 sec are spend with qtminimal 12 sec are spend with go build and the missing 5 sec are probably spend with copying dependencies

You can bring that down to 12 sec if use the qt/cmd tools manually. To do this, run qtmoc only after you made changes to your "moc/qmlbridge" struct and use the "cached" moc files when possible. You may also want to remove the need to invoke qtrcc during development and use a normal filepath instead of a path with qrc:// prefix to load your qml files. (That doesn't save much time, but this way you don't have to invoke qtrcc everytime you make changes to your qml files) And finally build you project with the raw go build command.

How about building a generic qmlbridge that passes stuff to qml gui's and receives generic ui events

You can already do this, just create a new package with your "qmlbridge" structs and run qtmoc in that folder. You can even subclass you own "moc" structs and add more signals/slots to them.

But the general plan for this repo is to map the Qt API as unaltered as possible and that prevents me from adding any "custom" Qt/QML code. I might create a new repo at some point, where I could collect such packages. But it's not planned yet.

omac777 commented 8 years ago

CentOS el7 tableview.sh out of the box was giving me problems This was working and the contents are copied locally within linux_minimal qmlscene -I /opt/Qt5.7.0/5.7/gcc_64/qml tableview.qml

...tweaking tableview.sh with the following works: export LD_LIBRARY_PATH=$dirname:$dirname/qml:$dirname/qml/QtQuick:$dirname/qml/QtQuick/Controls export QT_PLUGIN_PATH=$dirname:$dirname/qml:$dirname/qml/QtQuick:$dirname/qml/QtQuick/Controls export QML_IMPORT_PATH=$dirname/qml:$dirname/qml:$dirname/qml/QtQuick:$dirname/qml/QtQuick/Controls export QML2_IMPORT_PATH=$dirname/qml:$dirname/qml:$dirname/qml/QtQuick:$dirname/qml/QtQuick/Controls

therecipe commented 8 years ago

I will look into this.

therecipe commented 8 years ago

The *.sh files should work again with https://github.com/therecipe/qt/commit/48d3171bdd17341e382835408097fcf577dd7cc7

I made some changes when I added support for pkg-config on linux and that caused the issue.