5

There is no full documentation about how to use Gtk.Builder in PyGObject to create a menubar.

I don't use that Gtk.UIManager because it is deprecated. The example code below is based on my experience with Gtk.UIManager.

In the example should appear a menubar with Foo as a top menu group having an clickable item Bar.

#!/usr/bin/env python3
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
from gi.repository import Gio

class Window(Gtk.ApplicationWindow):
    def __init__(self):
        Gtk.Window.__init__(self)
        self.set_default_size(200, 100)

        #
        self.interface_info = """
        <interface>
          <menu id='TheMenu'>
            <section>
              <attribute name='foo'>Foo</attribute>
              <item>
                <attribute name='bar'>Bar</attribute>
              </item>
            </section>
          </menu>
        </interface>
        """

        builder = Gtk.Builder.new_from_string(self.interface_info, -1)

        action_bar = Gio.SimpleAction.new('bar', None)
        action_bar.connect('activate', self.on_menu)
        self.add_action(action_bar)

        menubar = builder.get_object('TheMenu')

        # layout
        self.layout = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
        self.layout.pack_start(menubar, True, True, 0)
        self.add(self.layout)

        self.connect('destroy', Gtk.main_quit)
        self.show_all()

    def on_menu(self, widget):
        print(widget)

if __name__ == '__main__':
    win = Window()
    Gtk.main()

The current error is

Traceback (most recent call last):
  File "./_menubar.py", line 46, in <module>
    win = Window()
  File "./_menubar.py", line 36, in __init__
    self.layout.pack_start(menubar, True, True, 0)
TypeError: argument child: Expected Gtk.Widget, but got gi.repository.Gio.Menu

I am unsure about

  • How to create the XML string.
  • How to get the menubar-widget.
  • How to create Actions/Click-handlers for menu items.

Of course the question could be extended to toolbars but I wouldn't made it to complexe.

btw: I don't want to use Gtk.Application.set_menubar(). Because there is no Gtk.Application.set_toolbar() and currently I see no advantage on having a Gtk-based application object.

EDIT: I also tried this variant (without any success):

gio_menu = builder.get_object('TheMenu')
menubar = Gtk.Menubar.new_from_model(gio_menu)
buhtz
  • 10,774
  • 18
  • 76
  • 149
  • You may wan tot give a look to the `amtk` library, wich aims at creating traditionnal UIs without using deprecated GTK+ components: https://blogs.gnome.org/swilmet/2018/04/24/expanding-amtk-to-support-guis-with-headerbar/ – liberforce Apr 26 '18 at 09:23
  • @liberforce Amtk looks like C? Can you give a code exampel - there is none in the documentation. Is it so hard to create a menubar and a toolbar with PyGObject that you need another library?! – buhtz Apr 26 '18 at 15:18
  • Ah, right, maybe it hasn't python bindings. Don't forget GObject and GTK+ are in C too, Amtk is just another GObject-based library. – liberforce Apr 26 '18 at 15:44
  • Maybe this example of [toolbar created using Glade and GtkBuilder](https://developer.gnome.org/gnome-devel-demos/stable/toolbar_builder.py.html.en) is what you're looking for? – liberforce Apr 26 '18 at 15:51
  • And here's for the [menubar and GtkBuilder](https://developer.gnome.org/gnome-devel-demos/stable/menubar.py.html.en). – liberforce Apr 26 '18 at 15:53

1 Answers1

5

My answer is based on a foreign answer on the gtk-dev-app mailinglist.

I prefere Variant 3.

Variant 1: with XML-String

Please be aware of the different naming of the action between the XML-string (win.bar) and the Gio.SimpleAction(bar).

#!/usr/bin/env python3
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
from gi.repository import Gio

class Window(Gtk.ApplicationWindow):
    def __init__(self):
        Gtk.Window.__init__(self)
        self.set_default_size(200, 100)

        #
        self.interface_info = """
        <interface>
          <menu id='TheMenu'>
            <submenu>
              <attribute name='label'>Foo</attribute>
              <item>
                <attribute name='label'>Bar</attribute>
                <attribute name='action'>win.bar</attribute>
              </item>
            </submenu>
          </menu>
        </interface>
        """

        builder = Gtk.Builder.new_from_string(self.interface_info, -1)

        action_bar = Gio.SimpleAction.new('bar', None)
        action_bar.connect('activate', self.on_menu)
        self.add_action(action_bar)

        menumodel = builder.get_object('TheMenu')
        menubar = Gtk.MenuBar.new_from_model(menumodel)

        # layout
        self.layout = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
        self.layout.pack_start(menubar, False, False, 0)
        self.add(self.layout)

        self.connect('destroy', Gtk.main_quit)
        self.show_all()

    def on_menu(self, action, value):
        print('Action: {}\nValue: {}'.format(action, value))

if __name__ == '__main__':
    win = Window()
    Gtk.main()

Variant 2: without XML but with Actions

I prefere this variant because it doesn't use (human unreadable XML) and Gtk.Builder. Here you create the structure of your menu as a data structure based on Gio.Menu and connect a Action (which itself is connected to an event handler) to it's items. Out of that informations the widget for the menubar is kind of generated.

#!/usr/bin/env python3
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
from gi.repository import Gio

class Window(Gtk.ApplicationWindow):
    def __init__(self):
        Gtk.Window.__init__(self)
        self.set_default_size(200, 100)

        action_bar = Gio.SimpleAction.new('bar', None)
        action_bar.connect('activate', self.on_menu)
        self.add_action(action_bar)

        # root of the menu
        menu_model = Gio.Menu.new()

        # menu item "Bar"
        menu_item = Gio.MenuItem.new('Bar', 'win.bar')

        # sub-menu "Foo" with item "Bar"
        menu_foo = Gio.Menu.new()
        menu_foo.append_item(menu_item)
        menu_model.append_submenu('Foo', menu_foo)

        # create menubar widget from the model
        menubar = Gtk.MenuBar.new_from_model(menu_model)

        # layout
        self.layout = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
        self.layout.pack_start(menubar, False, False, 0)
        self.add(self.layout)

        self.connect('destroy', Gtk.main_quit)
        self.show_all()

    def on_menu(self, action, value):
        print('Action: {}\nValue: {}'.format(action, value))

if __name__ == '__main__':
    win = Window()
    Gtk.main()

Variant 3: Old-school, easy without XML, Actions or Gio layer

This variant works kind of "old school" because you simply build your menu widgets together and connect signalls directly to them. This works without using a underlying and abstract data structure (e. g. Gio.MenuModel or an XML-string) and without a Application class.

#!/usr/bin/env python3
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk

class Window(Gtk.Window):
    def __init__(self):
        Gtk.Window.__init__(self)
        self.set_default_size(200, 100)

        # create menubar
        menubar = self._create_menubar()

        # create a toolbar
        toolbar = self._create_toolbar()

        # layout
        self.layout = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
        self.layout.pack_start(menubar, False, False, 0)
        self.layout.pack_start(toolbar, False, False, 0)
        self.add(self.layout)

        self.connect('destroy', Gtk.main_quit)
        self.show_all()

    def _create_menubar(self):
        # menu item 'Bar'
        item_bar = Gtk.MenuItem.new_with_label('Bar')
        item_bar.connect('activate', self.on_menu)

        # sub menu for 'Bar'
        menu_foo = Gtk.Menu.new()
        menu_foo.append(item_bar)

        # main menu 'Foo' with attached sub menu
        item_foo = Gtk.MenuItem.new_with_label('Foo')
        item_foo.set_submenu(menu_foo)

        # the menubar itself
        menubar = Gtk.MenuBar.new()
        menubar.append(item_foo)

        return menubar

    def _create_toolbar(self):
        toolbar = Gtk.Toolbar.new()

        # button with label
        bar_item = Gtk.ToolButton.new(None, 'Bar')
        bar_item.connect('clicked', self.on_menu)
        toolbar.insert(bar_item, -1)

        # button with icon
        bar_item = Gtk.ToolButton.new_from_stock(Gtk.STOCK_OK)
        bar_item.connect('clicked', self.on_menu)
        toolbar.insert(bar_item, -1)

        return toolbar

    def on_menu(self, caller):
        print(caller)

if __name__ == '__main__':
    win = Window()
    Gtk.main()
buhtz
  • 10,774
  • 18
  • 76
  • 149