StefanSalewski / gintro

High level GObject-Introspection based GTK3/GTK4 bindings for Nim language
MIT License
296 stars 20 forks source link

Glade/GtkBuilder doesn't work #34

Open define-private-public opened 5 years ago

define-private-public commented 5 years ago

I noticed that this repo was missing a glade example and I wanted to contribute one; a little dice rolling app. I'm testing this on Linux right now.

Here is my glade file:

<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.22.1 -->
<interface>
  <requires lib="gtk+" version="3.20"/>
  <object class="GtkWindow" id="diceRollWindow">
    <property name="can_focus">False</property>
    <property name="default_width">300</property>
    <property name="default_height">100</property>
    <child>
      <placeholder/>
    </child>
    <child>
      <object class="GtkBox">
        <property name="visible">True</property>
        <property name="can_focus">False</property>
        <child>
          <object class="GtkBox">
            <property name="visible">True</property>
            <property name="can_focus">False</property>
            <property name="orientation">vertical</property>
            <child>
              <object class="GtkRadioButton" id="d4Radio">
                <property name="label" translatable="yes">D4</property>
                <property name="visible">True</property>
                <property name="can_focus">True</property>
                <property name="receives_default">False</property>
                <property name="active">True</property>
                <property name="draw_indicator">True</property>
              </object>
              <packing>
                <property name="expand">False</property>
                <property name="fill">True</property>
                <property name="position">0</property>
              </packing>
            </child>
            <child>
              <object class="GtkRadioButton" id="d6Radio">
                <property name="label" translatable="yes">D6</property>
                <property name="visible">True</property>
                <property name="can_focus">True</property>
                <property name="receives_default">False</property>
                <property name="active">True</property>
                <property name="draw_indicator">True</property>
                <property name="group">d4Radio</property>
              </object>
              <packing>
                <property name="expand">False</property>
                <property name="fill">True</property>
                <property name="position">1</property>
              </packing>
            </child>
            <child>
              <object class="GtkRadioButton" id="d8Radio">
                <property name="label" translatable="yes">D8</property>
                <property name="visible">True</property>
                <property name="can_focus">True</property>
                <property name="receives_default">False</property>
                <property name="active">True</property>
                <property name="draw_indicator">True</property>
                <property name="group">d4Radio</property>
              </object>
              <packing>
                <property name="expand">False</property>
                <property name="fill">True</property>
                <property name="position">2</property>
              </packing>
            </child>
            <child>
              <object class="GtkRadioButton" id="d10Radio">
                <property name="label" translatable="yes">D10</property>
                <property name="visible">True</property>
                <property name="can_focus">True</property>
                <property name="receives_default">False</property>
                <property name="active">True</property>
                <property name="draw_indicator">True</property>
                <property name="group">d4Radio</property>
              </object>
              <packing>
                <property name="expand">False</property>
                <property name="fill">True</property>
                <property name="position">3</property>
              </packing>
            </child>
            <child>
              <object class="GtkRadioButton" id="d12Radio">
                <property name="label" translatable="yes">D12</property>
                <property name="visible">True</property>
                <property name="can_focus">True</property>
                <property name="receives_default">False</property>
                <property name="active">True</property>
                <property name="draw_indicator">True</property>
                <property name="group">d4Radio</property>
              </object>
              <packing>
                <property name="expand">False</property>
                <property name="fill">True</property>
                <property name="position">4</property>
              </packing>
            </child>
            <child>
              <object class="GtkRadioButton" id="d20Radio">
                <property name="label" translatable="yes">D20</property>
                <property name="visible">True</property>
                <property name="can_focus">True</property>
                <property name="receives_default">False</property>
                <property name="active">True</property>
                <property name="draw_indicator">True</property>
                <property name="group">d4Radio</property>
              </object>
              <packing>
                <property name="expand">False</property>
                <property name="fill">True</property>
                <property name="position">5</property>
              </packing>
            </child>
          </object>
          <packing>
            <property name="expand">False</property>
            <property name="fill">True</property>
            <property name="position">0</property>
          </packing>
        </child>
        <child>
          <object class="GtkBox">
            <property name="visible">True</property>
            <property name="can_focus">False</property>
            <property name="orientation">vertical</property>
            <child>
              <object class="GtkLabel" id="resultLabel">
                <property name="visible">True</property>
                <property name="can_focus">False</property>
              </object>
              <packing>
                <property name="expand">True</property>
                <property name="fill">True</property>
                <property name="position">0</property>
              </packing>
            </child>
            <child>
              <object class="GtkButton" id="rollButton">
                <property name="label" translatable="yes">Roll</property>
                <property name="visible">True</property>
                <property name="can_focus">True</property>
                <property name="receives_default">True</property>
              </object>
              <packing>
                <property name="expand">True</property>
                <property name="fill">True</property>
                <property name="position">1</property>
              </packing>
            </child>
          </object>
          <packing>
            <property name="expand">True</property>
            <property name="fill">True</property>
            <property name="position">1</property>
          </packing>
        </child>
      </object>
    </child>
  </object>
</interface>

Before adding in the functionality, I wanted to make sure that the window would display. I have this in python, and the window does show up:

#!/usr/bin/python3

import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk

builder = Gtk.Builder()
builder.add_from_file('DiceRoll.glade')

window = builder.get_object('diceRollWindow')
window.show_all()

Gtk.main()

With this Nim code, the program is exiting in an instant without showing the window at all. The terminal output Display Window does show up though.

import gintro/[gtk, glib, gobject, gio]

proc appActivate (app: Application) =
  let builder = newBuilder()
  let success = builder.addFromFile("DiceRoll.glade") > 0

  if success:
    let window = cast[Window](builder.getObject("diceRollWindow"))
    window.showAll
    echo("Display Window")
  else:
    echo("Error loading glade file")

proc main =
  let app = newApplication("org.gtk.DiceRoll")
  connect(app, "activate", appActivate)
  discard app.run

main()

Glade is an essential part of the Gtk ecosystem. Is it a bug or did I forgot something?

StefanSalewski commented 5 years ago

Thanks for testing.

Yes I know that glade example was missing, I was too lazy to provide one and as number of gintro users was very close to zero in the last years it was no real problem.

I don't like glade too much myself, I used it NEd text editor, but that was with oldgtk3. Using an external xml file should be no problem with gintro I guess, linking it together with the executable may be more demanding I managed that for NEd editor, but it was not easy.

I will fix the wrong dll name in the next days and investigate glade example.

StefanSalewski commented 5 years ago

I had just a short look at the glade problems, and I remembered one reason why I did not provide one example for gintro. For GTK3 programs we can use the app style which you used in your Nim code, or we can use the traditional gtk2 style with gtk_init() and gtk_main(). For the later style I provided a glade example for oldgtk3. Your Python code seems to use that old style, and that style should work with gintro too, you would use t0.nim gintro example as a starting point.

Of course it would be nicer to use app style with glade. I assume that generally that will work, maybe with one exception: Maybe we can not take the main window from glade but have to use the app functions for that. I have to investigate that, have not yet found a nice example, maybe I have to ask at gtk mailing list.

define-private-public commented 5 years ago

Okay, I tested doing it based off of t0.nim, the glade file now shows up.

Would you like an example of the new "app style" or the older one. Is there any benefit to the newer app style?

StefanSalewski commented 5 years ago

The app style is what is generally recommended for GTK3, see

https://developer.gnome.org/gtk3/stable/ch01s04.html#id-1.2.3.12.5

I assume that some of GTK3's functionality may not fully work with legacy style. And of course the visual appearance is different, for example the pulldown menu for rarely used actions on top of the screen (not on window header bar) is provide for apps. (But I heard that may disappear again for upcoming GTK4)

Indeed using app style with glade is not a big problem, see

https://stackoverflow.com/questions/15406533/how-do-i-create-a-gtkapplicationwindow-widget-with-glade

Basically we only have to set the app for the top level window from glade. I have already a minimal working test for that, but the ugly part is

let window = cast[ApplicationWindow](getObject(builder, "window"))

We have this ugly unsecure cast for all widgets provided by glade currently. Ugly is that we have to write the cast at all, and that type check is only done by GTK3 itself at runtime when the widget is actually used. Currently I have the impression that gobject-intropspection does not provide functions for widget type tests at all. In C code these tests are generally done with C macros. I will see what I can do...

This is my current test code:

<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.18.3 -->
<interface>
  <requires lib="gtk+" version="3.12"/>
  <object class="GtkApplicationWindow" id="window">
    <property name="visible">True</property>
    <property name="can_focus">False</property>
    <child>
      <object class="GtkGrid" id="grid">
        <property name="visible">True</property>
        <property name="can_focus">False</property>
        <child>
          <object class="GtkButton" id="button1">
            <property name="label" translatable="yes">Button 1</property>
            <property name="visible">True</property>
            <property name="can_focus">True</property>
            <property name="receives_default">True</property>
          </object>
          <packing>
            <property name="left_attach">0</property>
            <property name="top_attach">0</property>
          </packing>
        </child>
        <child>
          <object class="GtkButton" id="button2">
            <property name="label" translatable="yes">Button 2</property>
            <property name="visible">True</property>
            <property name="can_focus">True</property>
            <property name="receives_default">True</property>
          </object>
          <packing>
            <property name="left_attach">1</property>
            <property name="top_attach">0</property>
          </packing>
        </child>
        <child>
          <object class="GtkButton" id="quit">
            <property name="label" translatable="yes">Quit</property>
            <property name="visible">True</property>
            <property name="can_focus">True</property>
            <property name="receives_default">True</property>
          </object>
          <packing>
            <property name="left_attach">0</property>
            <property name="top_attach">1</property>
            <property name="width">2</property>
          </packing>
        </child>
      </object>
    </child>
  </object>
</interface>
import gintro/[gtk, glib, gobject, gio]

proc appActivate(app: Application) =
  var
    builder: Builder
    value: Value
    button: Button

  builder = newBuilder()
  discard addFromFile(builder, "builder.ui")
  let window = cast[ApplicationWindow](getObject(builder, "window"))

  #discard value.init(typeFromName("GObject"))
  #value.setObject(app)
  #setProperty(window, "application", value)

  window.setApplication(app) # above 3 lines work, but just calling this function is simpler

proc main =
  let app = newApplication("org.gtk.example")
  connect(app, "activate", appActivate)
  discard run(app)

main()
StefanSalewski commented 5 years ago

Well, we can do our own test at runtime with code like

echo toBool(g_type_check_instance_is_a(cast[ptr TypeInstance00](window.impl), typeFromName("GtkWindow")))

Maybe we should for each data type builder could provide add procs like getWindow(), getButton()... to replace casts like

let window = cast[ApplicationWindow](getObject(builder, "window"))
define-private-public commented 5 years ago

That getWindow(), getButton() does seem like a good solution. Alternatively, could it be possible that each class can take a constructor that could convert a GObject* pointer to the type. E.g:

let
  window = Window(getObject(builder, "window"))
  button = Button(getObject(builder, "button"))

But that is more typing.

define-private-public commented 5 years ago

Hey, my old Nim code above doesn't work, but this here does:

import gintro/[gtk, glib, gobject, gio]

proc appActivate (app: Application) =
  let builder = newBuilder()
  discard builder.addFromFile("DiceRoll.glade")
  let window = cast[ApplicationWindow](builder.getObject("diceRollWindow"))

  window.setApplication(app)
  window.showAll

proc main =
  let app = newApplication("org.gtk.DiceRoll")
  app.connect("activate", appActivate)
  discard app.run

main()

If you are interested in merging in this Glade example, I'm willing to finish it up.

StefanSalewski commented 5 years ago

Seems you have missed a lot...

See latest issues

https://github.com/StefanSalewski/gintro/issues/45

and

https://github.com/StefanSalewski/gintro/issues/46

So all works fine, but connecting the signals from within glade/builder is still untested. Note that recently it was reported that gintro may not compile with 0.19.4 and 0.19.6 due to a missing bracket. Compiles fine for me with 0.19.9, so I hesitated to make a new gintro release just for that bracket.

Well, maybe I should add some docs about glade/builder. I should test connecting signals from within builder for this, and also using resource files -- is some work.

StefanSalewski commented 5 years ago

Working glade/builder example is now provided:

https://github.com/StefanSalewski/gintro#gtk-builderuser-interfaces-created-with-the-glade-tool

define-private-public commented 5 years ago

I'm a little confused. Do you still need to manually specify the signal -> handler connections in Nim?

StefanSalewski commented 5 years ago

I don't know, sorry.

I am not even sure if connecting signals from within glade files is possible at all -- from my memory I thought that it may be possible, but that memory is ten years ago, it may be wrong.

In the last months I have seen only glade examples where connecting the handlers to signals was done in the C code, and that works fine for Nim also.

So I guess we have to wait until someone uses gintro and complains that it is not working and points us maybe to an C example, or maybe tell us that it is working?

Currently I am working a bit on all the array parameters in GTK -- I have recently added support for cstringArray, which was needed in one of the latest new examples. But there seems to be many more arrays parameter be used in GTK, supporting all them is hard. Most of these functions I never heard about, so I have to learn how the parameters are used first. Then I have to investigate if we can generate code for support, or if we have to do that manually. The later may indicate adding a few hundred procs manually, which may be a few weeks fulltime work. One example for such a proc is

proc dragDestSet*(self: Widget; flags: DestDefaults; targets: TargetEntry00Array; 
    nTargets: int; actions: gdk.DragAction) = 

with wrong parameter TargetEntry00Array. But there are many of these, and I have still to learn how all these work, so that I can start testing after fixing.

Another issue is emission of custom signals. For default signals we have type save handlers, but for custom signals it may be hard, maybe we can cover it all with closures? I have never used custom signals myself before.

And finally we may investigate using resource files with glade/builder. I did that for the NEd editor, but it was hard and I did not understand it really, so we have to investigate it for gintro.

Oh, and one more: I have still no idea how gintro may work with newruntime.