romgrk / node-gtk

GTK+ bindings for NodeJS (via GObject introspection)
MIT License
494 stars 41 forks source link

GTK4 doesn't have Gtk.Builder.connectSignals #308

Open rexkogitans opened 3 years ago

rexkogitans commented 3 years ago

Sorry, I cannot reopen issue #305.

Your example uses GTK 3, however the GTK documentation clearly states that this function no longer exists:

https://docs.gtk.org/gtk4/migrating-3to4.html

Somehow, I have the feeling that there should be a language-specific implementation of Gtk.BuilderScope. Since Gtk.BuilderCScope is the C/C++ implementation, naming it Gtk.BuilderJSScope would make sense.

Originally posted by @rexkogitans in https://github.com/romgrk/node-gtk/issues/305#issuecomment-897662735

romgrk commented 3 years ago

If you want to provide a replacement for the deprecated function, PR:s are welcome. I don't have enough time to add an implementation myself. The first thing I would recommend though would be to carefully read the GTK documentation to see what their proposed migration solution is.

rexkogitans commented 3 years ago

I will try to create an implementation of what is GtkBuilderScopeInterface in C during the next weeks. Either I can write it in node or in C, maybe I will need some help. The resulting code should allow the creation of a class like what I have posted in issue #305, but with another name, something like:

class MyClass extends Gtk.BuilderJSScope { ... }
romgrk commented 3 years ago

Ok. Don't have time to look into this in details at the moment, but whatever you do make sure to check what PyGObject and GJS are doing, I try to stay as close to them as possible.

rexkogitans commented 2 years ago

I am sorry to say that I do not make any progress with it. Obviously, I need to register the callback functions somehow. I tried to override the setScope method and started out with this:

  Gtk.Builder.prototype.setScope = function (scope) {
      const props = [];
      props.push(...Object.getOwnPropertyNames(scope));
      props.push(...Object.getOwnPropertyNames(Object.getPrototypeOf(scope)));
      for (let prop of props) {
        if (typeof scope[prop] == "function") console.log(prop)
      }
    }

This outputs all the functions of scope. So, as far as I understand, setScope needs to call some internal function or add them to an array to register the callbacks, so that addFromString can use it.

The intended usage looks like this:

    const builder = Gtk.Builder.new()
    builder.setScope(this)
    builder.addFromString(ui, -1)
rexkogitans commented 2 years ago

@romgrk After reading tons of source code of the GTK project, a came to the conclusion that Gtk.Builder.setScope and Gtk.Builder.addCallbackSymbol should be used for language bindings where symbols (i.e. function names) are not in necessarily in global scope. So, I came up with this approach:

#!/usr/bin/env node

const gi = require('node-gtk')
const GLib = gi.require('GLib', '2.0')
const Gtk = gi.require('Gtk', '4.0')
const GObject = gi.require('GObject', '4.0')

class MyTestApp {

  constructor() {
    console.log("constructor")
  }

  _onButtonClicked() {
    console.log("Yippie!");
  }

}

const loop = GLib.MainLoop.new(null, false)
const app = new Gtk.Application('rexkogitans.nodegtk.mwe-4', 0)
app.on('activate', onActivate)

const status = app.run([])

gi.startLoop()
loop.run()

console.log('Finished with status:', status)

function onActivate() {

  const window = new Gtk.ApplicationWindow(app)
  window.setTitle('Window')
  window.setDefaultSize(200, 200)

  const ui = `
    <?xml version="1.0" encoding="UTF-8"?>
    <interface>
      <requires lib="gtk" version="4.0"/>
        <object class="GtkBox" id="root">
          <property name="orientation">vertical</property>
          <child>
            <object class="GtkLabel" id="helloLabel">
              <property name="vexpand">1</property>
              <property name="label">Hello World!</property>
            </object>
          </child>
          <child>
            <object class="GtkButton" id="actionButton">
              <property name="label" translatable="yes">Action</property>
              <property name="receives_default">1</property>
              <signal name="clicked"
                      handler="_onButtonClicked"
                      swapped="no"
                      />
            </object>
          </child>
          <child>
            <object class="GtkButton" id="closeButton">
              <property name="label" translatable="yes">Close</property>
              <property name="receives_default">1</property>
            </object>
          </child>
        </object>
    </interface>
  `

  const builder = new Gtk.Builder()
  const myapp = new MyTestApp()
  const scope = new Gtk.BuilderCScope()
  builder.setScope(scope)
  scope.addCallbackSymbol("_onButtonClicked", myapp._onButtonClicked.bind(myapp))
  builder.addFromString(ui, -1)
  const root = builder.getObject('root')

  const closeButton = builder.getObject('closeButton')
  closeButton.on('clicked', () => window.close())

  window.setChild(root)
  window.show()
  window.present()

}

function onQuit() {
  loop.quit()
  app.quit()
  return false
}

This looks quite promising. When clicking the action button, it writes Yippie! to the console, as expected. However, when clicking the button a second time, the application crashes (core dump). I did not go into it with Valgrind or any kind of debugging tool, since I know that my understanding is too low here. I hope it helps in pointing out that there may be a bug which should be fixed.

TheDogHusky commented 2 months ago

Hey! News about this? I've been trying to use (import) GTK 4.0 and Adw 1 but I got an error that seems related to this issue:

    const handlerID = this.connect(event, callback, after)
                           ^

TypeError: Signal callback is not a function