node-gtk icon indicating copy to clipboard operation
node-gtk copied to clipboard

GTK4 doesn't have Gtk.Builder.connectSignals

Open rexkogitans opened this issue 4 years ago • 6 comments

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

rexkogitans avatar Aug 12 '21 14:08 rexkogitans

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.

romgrk avatar Aug 12 '21 17:08 romgrk

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 { ... }

rexkogitans avatar Aug 15 '21 09:08 rexkogitans

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.

romgrk avatar Aug 16 '21 00:08 romgrk

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 avatar Oct 01 '21 20:10 rexkogitans

@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.

rexkogitans avatar Oct 18 '21 18:10 rexkogitans