diff --git a/demo.lua b/demo.lua new file mode 100644 index 00000000..ffd8ca1b --- /dev/null +++ b/demo.lua @@ -0,0 +1,56 @@ +-- Demo modified from https://github.com/lgi-devs/lgi/issues/333 - the stated issues should be resolved. + +-- hack to require lgi from this directory before /usr/ or wherever windows installs it. +local sep = package.path:match("[/\\]") +package.path = ("./?.lua;./?/init.lua;"):gsub("/", sep) .. package.path + +local lgi, lgi_path = require("lgi") + +-- check the hack worked +assert(lgi_path:match("%.[/\\]lgi.lua"), "Loaded wrong LGI!") + +local Gtk = lgi.require("Gtk", "3.0") + +local button_1 = Gtk.Button.new_with_label("I can be clicked multiple times, and now handlers are overwritten") + +---@diagnostic disable-next-line:duplicate-set-field +button_1.on_clicked = function() + print("Button 2 Click handler 1!") +end +---@diagnostic disable-next-line:duplicate-set-field +button_1.on_clicked = function() + print("Button 2 Click handler 2!") +end + +-- This button demonstrates the difficulty in disconnecting a signal: +local button_2 = Gtk.Button.new_with_label("I can be clicked once!") + +local handler_id +-- using widget.:connect from https://github.com/lgi-devs/lgi/blob/master/docs/guide.md#341-connecting-signals +handler_id = button_2.on_clicked:connect(function(widget) + print("Button 1 Clicked!") + widget:set_label("I can no longer be clicked!") + + -- Now I can disconnect nicely! + button_2.on_clicked:disconnect(handler_id) +end) + +button_2.on_notify["has-focus"]:connect(function (...) + print("has-focus", ...) +end) + +local window = Gtk.Window { + title = "GitHub PR demo", + default_width = 400, + default_height = 300, + on_destroy = Gtk.main_quit, + child = Gtk.VBox { + button_1, + button_2 + }, +} + +window:present() +window:show_all() + +Gtk.main() diff --git a/lgi/class.lua b/lgi/class.lua index b03e2538..ea73f2e1 100644 --- a/lgi/class.lua +++ b/lgi/class.lua @@ -257,6 +257,7 @@ function class.load_class(namespace, info) class._method = component.get_category(info.methods, load_method) class._signal = component.get_category( info.signals, nil, load_signal_name, load_signal_name_reverse) + class._signal_handler = {} -- initialized empty in order to memory manage signal handlers created via assignment operator class._constant = component.get_category(info.constants, core.constant) class._field = component.get_category(info.fields) local type_struct = info.type_struct diff --git a/lgi/override/GObject-Object.lua b/lgi/override/GObject-Object.lua index 8bda1e17..64991b0d 100644 --- a/lgi/override/GObject-Object.lua +++ b/lgi/override/GObject-Object.lua @@ -259,7 +259,7 @@ local function marshal_property(obj, name, flags, gtype, marshaller, ...) local mode = select('#', ...) > 0 and 'WRITABLE' or 'READABLE' if not flags[mode] then error(("%s: `%s' not %s"):format(core.object.query(obj, 'repo')._name, - name, core.downcase(mode))) + name, core.downcase(mode))) end local value = Value(gtype) if mode == 'WRITABLE' then @@ -292,6 +292,7 @@ end local quark_from_string = repo.GLib.quark_from_string local signal_lookup = repo.GObject.signal_lookup local signal_connect_closure_by_id = repo.GObject.signal_connect_closure_by_id +local signal_handler_disconnect = repo.GObject.signal_handler_disconnect local signal_emitv = repo.GObject.signal_emitv -- Connects signal to specified object instance. local function connect_signal(obj, gtype, name, closure, detail, after) @@ -319,9 +320,19 @@ end -- Signal accessor. function Object:_access_signal(object, info, ...) local gtype = self._gtype + + -- faster, as every branch indexes info.name more than once + local name = info.name + if select('#', ...) > 0 then + local existing_handler = self._signal_handler[name] + + if existing_handler then + signal_handler_disconnect(object, existing_handler) + end + -- Assignment means 'connect signal without detail'. - connect_signal(object, gtype, info.name, Closure((...), info)) + self._signal_handler[name] = connect_signal(object, gtype, name, Closure((...), info)) else -- Reading yields table with signal operations. local mt = {} @@ -330,6 +341,11 @@ function Object:_access_signal(object, info, ...) return connect_signal(object, gtype, info.name, Closure(target, info), detail, after) end + + function pad:disconnect(handler_id) + return signal_handler_disconnect(object, handler_id) + end + function pad:emit(...) return emit_signal(object, gtype, info, nil, ...) end @@ -345,8 +361,27 @@ function Object:_access_signal(object, info, ...) return emit_signal(object, gtype, info, detail, ...) end function mt:__newindex(detail, target) - connect_signal(object, gtype, info.name, Closure(target, info), - detail) + local name_with_detail = name .. "::" .. detail + local existing_handler = self._signal_handler[name_with_detail] + if existing_handler then + signal_handler_disconnect(object, existing_handler) + end + + self._signal_handler[name_with_detail] = connect_signal(object, gtype, name, Closure(target, info), + detail) + end + + function mt.__index(_, detail) + local sub_pad = {} + + function sub_pad:connect(target, after) + return connect_signal(object, gtype, name, + Closure(target, info), detail, after) + end + + sub_pad.disconnect = pad.disconnect + + return sub_pad end end