Search code examples
gtkgtk3pygobject

Gio.Menu items always disabled for boolean actions


I can’t seem to make GMenu items work for boolean state actions.

Here is a MCVE (inspired from LiuLang’s example on how to use GMenu and GAction):

#!/usr/bin/env python3

# Copyright (C) 2013 LiuLang <gsushzhsosgsu@gmail.com>

# Use of this source code is governed by GPLv3 license that can be found
# in http://www.gnu.org/licenses/gpl-3.0.html

from gi.repository import Gio, Gtk, GLib
import sys

menus_str ='''
<?xml version="1.0"?>
<interface>
<menu id="menubar">
<submenu id="appmenu">
  <attribute name="label" translatable="yes">App</attribute>
  <section>
    <item>
      <attribute name="label" translatable="yes">Quit</attribute>
      <attribute name="action">app.quit</attribute>
    </item>
  </section>
  <section>
    <item>
      <attribute name="label" translatable="yes">Toggle broken</attribute>
      <attribute name="action">app.toggle</attribute>
    </item>
  </section>
  <section>
    <item>
      <attribute name="label" translatable="yes">Radios select A</attribute>
      <attribute name="action">app.radio</attribute>
      <attribute name="target">A</attribute>
    </item>
    <item>
      <attribute name="label" translatable="yes">Radios select B</attribute>
      <attribute name="action">app.radio</attribute>
      <attribute name="target">B</attribute>
    </item>
  </section>
</submenu>
</menu>
</interface>
'''

class App:
    def __init__(self):
        self.app = Gtk.Application.new('org.liulang.test', 0)
        self.app.connect('startup', self.on_app_startup)
        self.app.connect('activate', self.on_app_activate)

    def run(self, argv):
        self.app.run(argv)

    def on_app_activate(self, app):
        self.window.show_all()

    def on_app_startup(self, app):
        self.window = Gtk.ApplicationWindow.new(app)
        self.window.set_default_size(640, 480)
        self.window.set_title('Gio Actions Demo')
        self.window.set_border_width(5)

        app.add_window(self.window)

        builder = Gtk.Builder()
        # It is better to load ui from a seperate file
        builder.add_from_string(menus_str)
        builder.connect_signals(self)

        menubar = builder.get_object('menubar')
        app.set_menubar(menubar)

        # No-state no-parameter action 'quit'
        action = Gio.SimpleAction.new('quit', None)
        action.connect('activate', lambda *args: self.app.quit())
        self.app.add_action(action)

        # Boolean state action 'toggle' − should display as a checkbox ?
        action = Gio.SimpleAction.new_stateful('toggle', GLib.VariantType.new('b'), GLib.Variant('b', True))
        action.connect('activate', lambda action, target: print('toggling boolean action to', target.get_boolean()) or action.change_state(target))
        self.app.add_action(action)

        # String state action 'radio' − correctly displays as a radio
        action = Gio.SimpleAction.new_stateful('toggle', GLib.VariantType.new('b'), GLib.Variant('b', True))
        action = Gio.SimpleAction.new_stateful('radio', GLib.VariantType.new('s'), GLib.Variant('s', 'A'))
        action.connect('activate', lambda action, target: print('toggling radio action to', target.get_string()) or action.change_state(target))
        self.app.add_action(action)



if __name__ == '__main__':
    app = App()
    app.run(sys.argv)

I expect the second item in the menu to be a checkbox reflecting the state of the toggled action. Instead, I get an item that is always greyed out, even though the action is active.

The menu item seems well connected to the action, as it is done in the same way as the other actions that work, and adding a shortcut with Gtk.Application.set_accels_for_action shows the accelerators in the menu. Interactive debugging (with GTK_DEBUG=interactive) confirms the action is active.

See the following screenshot of the menu:

menu with toggleable item greyed out

I can’t seem to figure out what I’m doing wrong. Documentation is scarce and only contain examples with simple stateless actions. It’s also very hard to google for this problem as Gtk has a 3 or 4 different paradigms for menus and actions and whatnot that clutter any attempts at a search.

I’ve found the following comment in some source code:

 * ## Boolean State
 * 
 * An action with a boolean state will most typically be used with a "toggle"
 * or "switch" menu item. The state can be set directly, but activating the
 * action (with no parameter) results in the state being toggled.
 * 
 * Selecting a toggle menu item will activate the action. The menu item should
 * be rendered as "checked" when the state is true.

However no attribute listed in the GMenuModel wiki documentation seems to allow to select a toggle item. This is also not the way other actions work, see the radio example whose style is never specified.


Solution

  • It turns out a boolean toggleable action takes no parameter, the state contained inside the action is enough.

    I.e. the code had to be:

    action = Gio.SimpleAction.new_stateful('toggle', GLib.VariantType.new('b'), None)