Home > Gtk3, PyGObject, python > PyGObject: Gtk.MenuBar

PyGObject: Gtk.MenuBar

13 Aprile 2020

torna all’indice appunti

MenuBar

GTK+ mette a disposizione 2 diversi tipi di menu, Gtk.MenuBar
e Gtk.Toolbar.
Gtk.MenuBar è la barra dei menu standard che contiene una o più istanze di Gtk.MenuItem,
o una delle sue sottoclassi.
Il widget Gtk.Toolbar invece, è la toolbar classica contenente i bottoni che
permettono un accesso rapido alle funzioni più comuni gestite da una applicazione, ad esempio
“new document”, “print page”, “undo”. Contiene una o più istanze di Gtk.ToolItem,
o una delle sue sottoclassi.

Le properties principali sono:

Name Type Flags Short Description
child-pack-direction Gtk.PackDirection r/w/en La direzione di impacchettamento dei child nella menubar
pack-direction Gtk.PackDirection r/w/en La direzione di impacchettamento della menubar

Metodi

Oltre ai soliti getter e setter relativi alle properties dell’oggetto Gtk.MenuBar,
i principali metodi sono:

new()

Crea un nuovo oggetto Gtk.MenuBar

new_from_model(model)

Crea un nuovo oggetto Gtk.MenuBar e lo popola con gli elementi e i sottomenu ricavati dal model.
Gli elementi del menu creati sono connessi alle actions trovate nel Gtk.ApplicationWindow
al quale la menubar appartiene.
Parametri:
model: l’oggetto Gio.MenuModel dal quale estrapoliamo gli elementi
per popolare la menubar;

get_child_pack_direction()

Ritorna l’oggetto Gtk.PackDirection che rappresenta come i widget debbano essere
impacchettati all’interno di un child, nella menubar.

get_pack_direction()

Ritorna l’oggetto Gtk.PackDirection che rappresenta come gli elementi debbano
essere impacchettati nella menubar.

set_child_pack_direction(child_pack_dir)

Setta il modo in cui i widget debbano essere impacchettati all’interno di un child, nella menubar.
Parametri:
child_pack_dir: l’oggetto Gtk.PackDirection che indica il senso
di impacchettamento;

set_pack_direction(pack_dir)

Setta il modo in cui gli elementi debbano essere impacchettati nella menubar.
Parametri:
pack_dir: l’oggetto Gtk.PackDirection che indica il senso
di impacchettamento;

Actions

Le Actions rappresentano le operazioni che un utente può eseguire. Si crea una action creando
un’istanza della classe Gio.SimpleAction,
la si connette al segnale “activate”, cosicchè possa essere invocata una callback di riferimento.
Le actions, per comodità di organizzazione, possono essere mappate in widget
Gio.ActionMap, che permettono di mappare le diverse actions,
appartenenti a diversi gruppi, con l’utilizzo di un prefisso identificativo (ad esempio
“app.action_name” o “win.action_name”).

Come si crea il menu di una applicazione?
Si può effettuare in due modi, o creando un’istanza di Gio.Menu
e aggiungendo ad essa i vari oggetti Gio.MenuItem,
o leggendo da una stringa XML e costruendo il menu tramite Gtk.Builder.

Gio.Menu

Gio.Menu è una semplice implementazione di Gio.MenuModel ovvero un oggetto
rappresenta il contenuto di un menu, con una lista ordinata di elementi, che a loro volta
rappresentano le voci del menu. Ognuno di questi elementi è associato ad una action, che viene
attivata attraverso l’elemento stesso. L’oggetto Gio.Menu viene di fatto popolato da istanze di
Gio.MenuItem (gli elementi della lista).
Lo stesso Gio.Menu mette a disposizione metodi scorciatoia molto comodi che permettono di inserire
direttamente gli elementi, senza prima creare le istanze di Gio.MenuItem.
Ad esempio per inserire un elemento standard, possiamo usare il metodo Gio.Menu.insert(),
per aggiungere una sezione, il metodo Gio.Menu.insert_section(), mentre per
inserire un sottomenu, il metodo Gio.Menu.insert_submenu().

L’iter più esplicito consiste invece nel:

1. Si crea l’istanza Gio.Menu che rappresenta il menu model del sistema di menu;

>>> menu_model = Gio.Menu.new()

2. Si crea l’istanza di Gio.Menu, che rappresenta la voce di menu nella menubar,
cioè “File” (sottomenu del menumodel) e la si aggiunge al menu model;

>>> menu_model = Gio.Menu.new()
>>> menu_model.append_submenu('File', menu_file)

3. Si crea l’istanza di Gio.MenuItem, che rappresenta il primo elemento del menu
“File”, cioè “New” e la si aggiunge al menu “File”;

>>> file_new = Gio.MenuItem.new('New', 'win.FileNew')
>>> menu_file.append_item(file_new)

4. Si crea la Action che corrisponderà alla voce di menu che abbiamo creato, e
che si attiverà cliccando sulla voce stessa;

>>> action_file = Gio.SimpleAction.new(name='FileNew', parameter_type=None)

5. Si aggiunge la action all’istanza di Gtk.ApplicationWindow con il metodo
add_action(action);

6. Si connette la action alla callback desiderata;

Ecco un codeice di esempio:

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


class AppWindow(Gtk.ApplicationWindow):
    def __init__(self):
        super().__init__()
        self.set_default_size(200, 100)
        # actions
        act_file = Gio.SimpleAction.new(name='FileNew', parameter_type=None)
        act_quit = Gio.SimpleAction.new(name='FileQuit', parameter_type=None)
        act_info = Gio.SimpleAction.new(name='AboutInfo', parameter_type=None)
        self.add_action(act_file)
        self.add_action(act_quit)
        self.add_action(act_info)
        # model menu
        menu_model = Gio.Menu.new()
        # 1st menu
        menu_file = Gio.Menu.new()
        file_new = Gio.MenuItem.new('New', 'win.FileNew')
        file_quit = Gio.MenuItem.new('Quit', 'win.FileQuit')
        menu_file.append_item(file_new)
        menu_file.append_item(file_quit)
        # 2nd menu
        menu_about = Gio.Menu.new()
        about_info = Gio.MenuItem.new('Info', 'win.AboutInfo')
        menu_about.append_item(about_info)

        menu_model.append_submenu('File', menu_file)
        menu_model.append_submenu('About', menu_about)

        menu_bar = Gtk.MenuBar.new_from_model(menu_model)

        # layout
        layout = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
        layout.pack_start(menu_bar, False, False, 0)
        self.add(layout)
        # bindings
        act_file.connect('activate', self.on_menu)
        act_quit.connect('activate', self.on_quit)
        act_info.connect('activate', self.on_menu)
        self.connect('destroy', Gtk.main_quit)

    def on_menu(self, action, value):
        print("INFO: menu '%s'" % action.props.name)

    @staticmethod
    def on_quit(action, param):
        Gtk.main_quit()


if __name__ == '__main__':
    win = AppWindow()
    win.connect("destroy", Gtk.main_quit)
    win.show_all()
    Gtk.main()

Gtk.Builder e XML

La stessa cosa si ottiene anche via Gtk.Builder.
Gtk.Builder è un oggetto che legge le descrizioni di una UI da un testo e ne istanzia gli oggetti
descritti in esso.
A grandi linee, all’interno del testo, gli oggetti del menu saranno così identificati:

UI ovvero l’interfaccia utente del menu: sarà compresa tra i tag

<interface></interface>

;

menu_model ovvero l’oggetto menu genitore: sarà compreso tra i tag

<menu></menu>

;

sub_menu ovvero le voci dei menu: saranno comprese tra i tag

<submenu></submenu>

;

item ovvero gli elementi interni alle voci di menu: saranno compresi tra i tag

<item></item>

;

attribute ovvero l’attributo dell’istanza che verrà creata dal buider: sarà
compreso tra i tag

<attribute></attribute>

;

Nel nostro caso precedente il testo che rappresenta il menu della app, sarà:

<interface>
  <menu id='MenuModel'>
    <submenu>
      <attribute name='label'>File</attribute>
      <item>
        <attribute name='label'>New</attribute>
        <attribute name='action'>win.FileNew</attribute>
      </item>
      <item>
        <attribute name='label'>Quit</attribute>
        <attribute name='action'>win.FileQuit</attribute>
      </item>
    </submenu>
    <submenu>
      <attribute name='label'>About</attribute>
      <item>
        <attribute name='label'>Info</attribute>
        <attribute name='action'>win.AboutInfo</attribute>
      </item>
    </submenu>
  </menu>
</interface>

Basterà quindi creare il builder con il class_method Gtk.Builder.new_from_string(string, length):

>>> import gi
... gi.require_version('Gtk', '3.0')
... from gi.repository import Gtk, Gio
>> UI_INFO = """
... <interface>
...
... </interface>
... """
>>> builder = Gtk.Builder.new_from_string(UI_INFO, -1)

Come anticipato, il builder istanzia gli oggetti descritti nella stringa e permette di recuperarli
con il metodo get_object(string):

>>> builder.get_object('MenuModel')
<Gio.Menu object at 0x0000000004cb7580 (GMenu at 0x000000000406f870)>
>>> menu_model = builder.get_object('MenuModel')
>>> menu_model.get_n_items()
2

Una volta creato il menu con il builder, il resto del codice non cambia:

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


UI_INFO = """
<interface>
  <menu id='MenuModel'>
    <submenu>
      <attribute name='label'>File</attribute>
      <item>
        <attribute name='label'>New</attribute>
        <attribute name='action'>win.FileNew</attribute>
      </item>
      <item>
        <attribute name='label'>Quit</attribute>
        <attribute name='action'>win.FileQuit</attribute>
      </item>
    </submenu>
    <submenu>
      <attribute name='label'>About</attribute>
      <item>
        <attribute name='label'>Info</attribute>
        <attribute name='action'>win.AboutInfo</attribute>
      </item>
    </submenu>
  </menu>
</interface>
"""


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

        builder = Gtk.Builder.new_from_string(UI_INFO, -1)

        act_file = Gio.SimpleAction.new(name='FileNew', parameter_type=None)
        act_quit = Gio.SimpleAction.new(name='FileQuit', parameter_type=None)
        act_info = Gio.SimpleAction.new(name='AboutInfo', parameter_type=None)
        self.add_action(act_file)
        self.add_action(act_quit)
        self.add_action(act_info)

        menu_model = builder.get_object('MenuModel')
        menu_bar = Gtk.MenuBar.new_from_model(menu_model)

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

        act_file.connect('activate', self.on_menu)
        act_quit.connect('activate', self.on_quit)
        act_info.connect('activate', self.on_menu)
        self.connect('destroy', Gtk.main_quit)

    def on_menu(self, action, value):
        print("INFO: menu '%s'" % action.props.name)

    @staticmethod
    def on_quit(action, param):
        Gtk.main_quit()


if __name__ == '__main__':
    win = AppWindow()
    win.connect("destroy", Gtk.main_quit)
    win.show_all()
    Gtk.main()

Sottomenu di menu

Con lo stesso principio possiamo creare sottomenu di menu, ovvero elementi di menu, che a loro
volta contengono altri elementi sotto di essi.

import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, Gio


MENUBAR_INFO = """
<interface>
  <menu id='MenuModel'>
    <submenu>
      <attribute name='label'>File</attribute>
      <submenu>
        <attribute name="label">New</attribute>
          <section>
            <item>
              <attribute name="label">Text File</attribute>
              <attribute name="action">win.NewTextFile</attribute>
            </item>
            <item>
              <attribute name="label">Doc File</attribute>
              <attribute name="action">win.NewDocFile</attribute>
            </item>
            <item>
              <attribute name="label">Python File</attribute>
              <attribute name="action">win.NewPyFile</attribute>
            </item>
          </section>
      </submenu>
      <item>
        <attribute name='label'>Quit</attribute>
        <attribute name='action'>win.FileQuit</attribute>
      </item>
    </submenu>
    <submenu>
      <attribute name='label'>About</attribute>
      <item>
        <attribute name='label'>Info</attribute>
        <attribute name='action'>win.AboutInfo</attribute>
      </item>
    </submenu>
  </menu>
</interface>
"""


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

        builder = Gtk.Builder.new_from_string(MENUBAR_INFO, -1)

        act_text = Gio.SimpleAction.new(name='NewTextFile', parameter_type=None)
        act_doc = Gio.SimpleAction.new(name='NewDocFile', parameter_type=None)
        act_py = Gio.SimpleAction.new(name='NewPyFile', parameter_type=None)
        act_quit = Gio.SimpleAction.new(name='FileQuit', parameter_type=None)
        act_info = Gio.SimpleAction.new(name='AboutInfo', parameter_type=None)
        self.add_action(act_text)
        self.add_action(act_doc)
        self.add_action(act_py)
        self.add_action(act_quit)
        self.add_action(act_info)

        menu_model = builder.get_object('MenuModel')
        menu_bar = Gtk.MenuBar.new_from_model(menu_model)

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

        act_text.connect('activate', self.on_file)
        act_doc.connect('activate', self.on_file)
        act_py.connect('activate', self.on_file)
        act_quit.connect('activate', self.on_quit)
        act_info.connect('activate', self.on_menu)
        self.connect('destroy', Gtk.main_quit)

    def on_menu(self, action, value):
        print("INFO: menu '%s'" % action.props.name)

    def on_file(self, action, value):
        print("INFO: menu '%s'" % action.props.name)

    @staticmethod
    def on_quit(action, param):
        Gtk.main_quit()


if __name__ == '__main__':
    win = AppWindow()
    win.connect("destroy", Gtk.main_quit)
    win.show_all()
    Gtk.main()

Menu con RadioButton e ToggleButton

Nelle voci di menu possono essere contemplati anche elementi con stato, come ad esempio i
radiobutton e i togglebutton.

import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, GLib, Gio


MENUBAR_INFO = """
<?xml version="1.0" encoding="UTF-8"?>
<interface>
  <menu id="menumodel">
    <submenu>
      <attribute name="label">Settings</attribute>
      <section>
        <item>
          <attribute name="label">millimeters</attribute>
          <attribute name="action">win.state</attribute>
          <attribute name="target">mm</attribute>
        </item>
        <item>
          <attribute name="label">inches</attribute>
          <attribute name="action">win.state</attribute>
          <attribute name="target">inches</attribute>
        </item>
      </section>
      <section>
        <item>
          <attribute name="label">Convert</attribute>
          <attribute name="action">win.convert</attribute>
        </item>
      </section>
    </submenu>
  </menu>
</interface>
"""


class AppWindow(Gtk.ApplicationWindow):
    def __init__(self):
        super().__init__(title="MenuBar Example")
        self.set_default_size(200, 200)

        act_state = Gio.SimpleAction.new_stateful(
            name="state", parameter_type=GLib.VariantType.new('s'),
            state=GLib.Variant.new_string('inches'))

        act_convert = Gio.SimpleAction.new_stateful(
            name="convert", parameter_type=None,
            state=GLib.Variant.new_boolean(False))
        self.add_action(act_state)
        self.add_action(act_convert)
        # bindings
        act_state.connect("activate", self.on_state)
        act_convert.connect("activate", self.on_convert)

        builder = Gtk.Builder.new_from_string(MENUBAR_INFO, -1)
        menu_model = builder.get_object('menumodel')
        menu_bar = Gtk.MenuBar.new_from_model(menu_model)
        box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
        box.pack_start(menu_bar, False, False, 0)
        self.add(box)

    def on_state(self, action, parameter):
        print("INFO: measure is set to '%s'" % parameter.get_string())
        action.set_state(parameter)

    def on_convert(self, action, parameter):
        action.set_state(GLib.Variant.new_boolean(not action.get_state()))
        result = "enabled" if action.get_state().get_boolean() else "disabled"
        print("INFO: <Convert> is %s" % result)


if __name__ == '__main__':
    win = AppWindow()
    win.connect("destroy", Gtk.main_quit)
    win.show_all()
    Gtk.main()

ToolBar

Con lo stesso principio si crea anche una ToolBar.
Una volta costruita la stringa XML la si dà in pasto al Gtk.Builder e si crea la toolbar:

import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, Gio


TOOLBAR_INFO = """
<?xml version="1.0" encoding="UTF-8"?>
<interface>
  <!-- interface-requires gtk+ 3.0 -->
  <object class="GtkToolbar" id="toolbar">
    <property name="visible">True</property>
    <property name="can_focus">False</property>
    <property name="hexpand">True</property>
    <property name="show_arrow">False</property>
    <child>
      <object class="GtkToolButton" id="new_button">
        <property name="use_action_appearance">False</property>
        <property name="visible">True</property>
        <property name="can_focus">False</property>
        <property name="use_action_appearance">False</property>
        <property name="is_important">True</property>
        <property name="action_name">win.new</property>
        <property name="label" translatable="yes">New</property>
        <property name="use_underline">True</property>
        <property name="stock_id">gtk-new</property>
      </object>
      <packing>
        <property name="expand">False</property>
        <property name="homogeneous">True</property>
      </packing>
    </child>
    <child>
      <object class="GtkToolButton" id="open_button">
        <property name="use_action_appearance">False</property>
        <property name="visible">True</property>
        <property name="can_focus">False</property>
        <property name="use_action_appearance">False</property>
        <property name="is_important">True</property>
        <property name="action_name">win.open</property>
        <property name="label" translatable="yes">Open</property>
        <property name="use_underline">True</property>
        <property name="stock_id">gtk-open</property>
      </object>
      <packing>
        <property name="expand">False</property>
        <property name="homogeneous">True</property>
      </packing>
    </child>
  </object>
</interface>
"""


class AppWindow(Gtk.ApplicationWindow):
    def __init__(self):
        super().__init__(title="Toolbar Example")
        self.set_default_size(250, 100)

        builder = Gtk.Builder.new_from_string(TOOLBAR_INFO, -1)

        act_new = Gio.SimpleAction.new(name='new', parameter_type=None)
        act_open = Gio.SimpleAction.new(name='open', parameter_type=None)
        self.add_action(act_new)
        self.add_action(act_open)

        tool_bar = builder.get_object('toolbar')
        box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
        box.pack_start(tool_bar, False, False, 0)
        self.add(box)

        # bindings
        act_new.connect('activate', self.on_menu)
        act_open.connect('activate', self.on_menu)
        self.connect('destroy', Gtk.main_quit)

    def on_menu(self, action, value):
        print("INFO: menu '%s'" % action.props.name)


if __name__ == '__main__':
    win = AppWindow()
    win.connect("destroy", Gtk.main_quit)
    win.show_all()
    Gtk.main()

Gtk.ApplicationWindow e Gtk.Application

Se invece di utilizzare solo un oggetto Gtk.ApplicationWindow, come negli esempi precedenti,
volessimo strutturare la nostra applicazione, utilizzando, sia una window Gtk.ApplicationWindow,
sia un oggetto Gtk.Application, avremmo la possibità di distinguere le actions, in base al loro
prefisso. Le actions inerenti la window dell’applicazione, come negli esempi precedenti, hanno
prefisso “win.” e vanno create, aggiunte alla lista delle actions e connesse, all’interno di
Gtk.ApplicationWindowe, mentre le altre vengono istanziate e connesse all’interno di
Gtk.Application e avranno prefisso “app.”.

Il codice subirà qualche variante, ad esempio il builder verrà creato dentro Gtk.Application.

import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, Gio
import sys


MENUBAR_INFO = """
<interface>
  <menu id='MenuModel'>
    <submenu>
      <attribute name='label'>File</attribute>
      <item>
        <attribute name='label'>New</attribute>
        <attribute name='action'>app.FileNew</attribute>
      </item>
      <item>
        <attribute name='label'>Quit</attribute>
        <attribute name='action'>app.FileQuit</attribute>
      </item>
    </submenu>
    <submenu>
      <attribute name='label'>About</attribute>
      <item>
        <attribute name='label'>Info</attribute>
        <attribute name='action'>win.AboutInfo</attribute>
      </item>
    </submenu>
  </menu>
</interface>
"""


class AppWindow(Gtk.ApplicationWindow):
    def __init__(self, title, app):
        super().__init__(title=title, application=app)
        self.set_default_size(200, 100)

        act_info = Gio.SimpleAction.new(name='AboutInfo', parameter_type=None)
        self.add_action(act_info)
        act_info.connect('activate', self.on_menu)
        self.connect('destroy', Gtk.main_quit)

    def on_menu(self, action, value):
        print("INFO: menu '%s'" % action.props.name)


class GApplication(Gtk.Application):
    def __init__(self):
        super().__init__()

    def do_activate(self):
        win = AppWindow(title="MenuBar Example", app=self)
        win.show_all()

    def do_startup(self):
        # PRIMA AZIONE DA FARE: invocare do_startup()
        Gtk.Application.do_startup(self)

        builder = Gtk.Builder.new_from_string(MENUBAR_INFO, -1)
        menu_model = builder.get_object('MenuModel')
        self.set_menubar(menu_model)
        act_file = Gio.SimpleAction.new(name='FileNew', parameter_type=None)
        act_quit = Gio.SimpleAction.new(name='FileQuit', parameter_type=None)
        self.add_action(act_file)
        self.add_action(act_quit)
        act_file.connect('activate', self.on_new)
        act_quit.connect('activate', self.on_quit)

    def on_new(self, action, value):
        print("INFO: menu '%s'" % action.props.name)

    def on_quit(self, action, parameter):
        sys.exit()


if __name__ == '__main__':
    app = GApplication()
    exit_status = app.run(sys.argv)
    sys.exit(exit_status)

Accelerator and mnemonic

Le Labels possono contenere mnemonics. Gli Mnemonics sono gli underscores nella
label prima del carattere che vogliamo utilizzare come scorciatoia da tastiera.
Ad esempio “_File”, ci indica che la “F” sarà il carattere mnemonic del menu File.
Premendo ALT, gli mnemonic diventeranno visibili. Premendo Alt+F il menu File, si aprirà.
Gli Accelerators possono essere aggiunti esplicitamente nella stringa XML che
definisce la UI. Per fare questo basta aggiungere un attributo “accel” all’item.
Nota:

&lt;Primary&gt;q

creerà la sequenza Ctrl+Q, dove “Primary” si riferisce al tasto Ctrl.

l’XML dell’item diventerà quindi:

      <item>
        <attribute name='label'>_Quit</attribute>
        <attribute name='action'>app.FileQuit</attribute>
        <attribute name="accel">&lt;Primary&gt;q</attribute>
      </item>

link di riferimento:

torna all’indice degli appunti
Gtk3 MenuBar

Categorie:Gtk3, PyGObject, python Tag: , ,
I commenti sono chiusi.