GtkWidget.get_action_group does not find action group
The documentation of GtkWidget.get_action_group(widget, prefix)
states that the function returns a
" GActionGroup
which may have been registered to the widget
or to any
GtkWidget
in its ancestry." My understanding of this sentence is that the
function is not only meant for accessing the action groups registered on
widget
but also for searching the widget tree towards its root for a matching
action group.
When I used the function to that effect in my program it turned out, though, that the search through the widget ancestry is unreliable and fails after -- seemingly -- unrelated changes.
The following example (in Python for brevity) demonstrates the problem. It
creates a simple widget hierarchy with a GtkWindow
containing a GtkPaned
which has a GtkButton
in each pane. Additionally, an action group named
"mygroup" is registered on the window:
import gi
gi.require_version("Gtk", "3.0")
from gi.repository import Gtk, Gio
class MyWindow(Gtk.Window):
def __init__(self):
super().__init__()
paned = Gtk.Paned()
button1 = Gtk.Button(label="Button 1")
button2 = Gtk.Button(label="Button 2")
paned.pack1(button1)
paned.pack2(button2)
self.add(paned)
self.insert_action_group("mygroup", Gio.SimpleActionGroup())
#####
## Uncomment any of the following lines to see the problem:
#####
#button2.set_action_name("win.triggerbug")
#paned.insert_action_group("triggerbug", Gio.SimpleActionGroup())
#button1.insert_action_group("triggerbug", Gio.SimpleActionGroup())
#button2.insert_action_group("triggerbug", Gio.SimpleActionGroup())
action_group = button1.get_action_group("mygroup")
if action_group is not None:
print("Action group 'mygroup' was found")
else:
print("Action group 'mygroup'mygroup was not found")
win = MyWindow()
win.connect("destroy", Gtk.main_quit)
win.show_all()
Gtk.main()
This code works as expected and finds "mygroup" when calling button1.get_action_group
.
However, if an action name is set on button2
(see first commented line. It does not matter
that this action does not exist) then button1.get_action_group
does no longer return the
action group. The error can also be triggered by adding another action group to
paned
or any of the two buttons (see comment in the example).
When investigating the problem, I found out that get_action_group
does not
really search the widget tree for an action group with prefix
but it simply
walks the tree upwards until it finds the first widget that has an instance of
GTK's private class GtkActionMuxer
. It then calls action_muxer_lookup
on
this action muxer. This method only looks up action groups directly registered
on the action muxer.
This means that get_action_group
works only as long as the first action muxer
encountered happens to be the one with the action group being searched for. In
the example above this is only the case if none the commented code lines is
executed. As soon as one of the commands from the comments is added to the
code it causes all action muxers in the ancestry of button2
to be created.
When button1.get_action_group
is then called, the action muxer attached to
paned
is the first one found. However, this is not the one to which "mygroup"
was registered to. This results in get_action_group
returning NULL instead of
the action group as expected.
Suprisingly, actions triggered by GTK's own widgets that implemented
GtkActionable
are not affected by this problem. A close look at the sources
showed that GTK uses different mechanism to locate and trigger actions. This
mechanism relies on the private helper class GtkActionHelper
which
has direct access to the action muxers of the widgets. This allows it to call
GtkActionMuxer.activate_action
which searches the widget hierarchy correctly.
This mechanismis not available to users of GTK, though. This point seem
to have also been addressed in issue #639 (closed). However, there was no solution
offered.
Suggested solution
get_action_group
should be restricted to return only action groups registered
to the current widget. This allows for a reliable behaviour. The current
behaviour is very confusing and likely to break programs.
As suggested in issue #639 (closed) GtkActionHelper (or a similar functionality) should
be available to users of GTK, too. This saves GTk users from having to write
code traversing the ancestry and locating an action when they want to use an
action outside of an GtkActionable
.