Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to create PyGObject application with a menubar using Gtk.Builder?

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)
like image 808
buhtz Avatar asked Oct 17 '22 19:10

buhtz


1 Answers

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()
like image 183
buhtz Avatar answered Oct 21 '22 01:10

buhtz