Paepoi

Paepoi » PyGObject Tips » Gtk(PyGObject) Tips | メニュー

Gtk(PyGObject) Tips | メニュー

# 最終更新日 2019.09.01

2019 年現在の仕様に追記と書き換え。

GtkMenuButton
GtkMenuButton は Nautilus 3.6 から採用されているボタン式メニュー。
popover と popup のプロパティがあり GtkPopoverMenu と GtkMenu どちらを使うかを選べます。
GtkPopoverMenu は下で解説するのでここでは従来のメニューを出す方法を。
作成するとデフォルトは矢印表示のボタンになります。
#!/usr/bin/env python3

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

class Win(Gtk.ApplicationWindow):
    '''
        こっちを使っている GNOME アプリが存在しないけど
    '''
    def __init__(self, app):
        Gtk.ApplicationWindow.__init__(self, application=app, title='Py')
        # AccelGroup
        accelgroup = Gtk.AccelGroup()
        self.add_accel_group(accelgroup)
        # MenuIten
        item_open = Gtk.MenuItem(label='_Open', use_underline=True, visible=True)
        item_open.connect('activate', self.on_open)
        item_quit = Gtk.MenuItem(label='_Quit', use_underline=True, visible=True)
        item_quit.connect('activate', self.on_quit)
        # Keyboard Shortcut
        item_open.add_accelerator('activate', accelgroup, Gdk.KEY_o,
            Gdk.ModifierType.CONTROL_MASK, Gtk.AccelFlags.VISIBLE)
        item_quit.add_accelerator('activate', accelgroup, Gdk.KEY_q,
            Gdk.ModifierType.CONTROL_MASK, Gtk.AccelFlags.VISIBLE)
        # Menu
        menu = Gtk.Menu()
        menu.append(item_open)
        menu.append(Gtk.SeparatorMenuItem(visible=True))
        menu.append(item_quit)
        # GtkMenuButton
        menubutton = Gtk.MenuButton()
        menubutton.set_popup(menu)
        # これで矢印の向きを変更できる
        #menubutton.set_direction(Gtk.ArrowType.UP)
        # ヘッダーバーに置く
        hbar = Gtk.HeaderBar(show_close_button=True)
        hbar.pack_end(menubutton)
        # GtkTextView
        self.textview = Gtk.TextView()
        sw = Gtk.ScrolledWindow(child=self.textview)
        # self
        self.set_titlebar(hbar)
        self.add(sw)
        self.resize(200, 100)
        self.show_all()

    def on_open(self, action):
        dlg = Gtk.FileChooserNative(
            title='開く',
            transient_for=self,
            action=Gtk.FileChooserAction.OPEN)
        if dlg.run() == Gtk.ResponseType.ACCEPT:
            f = dlg.get_filename()
            with open(f) as o:
                s = o.read()
                self.textview.props.buffer.props.text = s
        dlg.destroy()

    def on_quit(self, action):
        self.close()

    def on_button_press_event(self, widget, event):
        print(event.button)
        return False

class App(Gtk.Application):
    def __init__(self):
        Gtk.Application.__init__(self)

    def do_startup(self):
        Gtk.Application.do_startup(self)
        Win(self)

    def do_activate(self):
        self.props.active_window.present()

app = App()
app.run(sys.argv)
etc/menu01.png
GtkModelButton
GtkPopoverMenu には GtkMenuItem ではなく GtkModelButton を入れます。
GNOME 標準アプリは全部こちらを採用しており事実上の標準になっています。
コンテナ(3.10 以降)#GtkPopoverMenuでも書いていますが。
ここではサブメニューを表示させるサンプルを。
#!/usr/bin/env python3

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

class Win(Gtk.ApplicationWindow):
    '''
        サブメニューを使うには menu-name プロパティに子プロパティの submenu に登録した名前をセット
        最初に add した widget が自動でメインメニューになり submenu は main になります
        ニーモニックはデフォルトで ON なので use-underline 指定は不要
    '''
    def __init__(self, app):
        Gtk.ApplicationWindow.__init__(self, application=app, title='Py')
        # メインメニュー
        menu_open  = Gtk.ModelButton(action_name='app.new_window_action', text='新しいウインドウ(_N)')
        menu_new   = Gtk.ModelButton(menu_name = 'tool_page', text='ツール(_T)')
        menu_quit  = Gtk.ModelButton(action_name='app.quit_action', text='終了(_Q)')
        #
        vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, margin=10)
        vbox.pack_start(menu_open, False, False, 0)
        vbox.pack_start(menu_new, False, False, 0)
        vbox.pack_start(Gtk.Separator(), False, False, 0)
        vbox.pack_start(menu_quit, False, False, 0)
        # ツールメニュー
        menu_back   = Gtk.ModelButton(menu_name = 'main', inverted=True, centered=True, text='ツール(_T)')
        menu_set    = Gtk.ModelButton(action_name='win.set_title_action', text='セットタイトル(_S)')
        menu_append = Gtk.ModelButton(action_name='win.append_title_action', text='追加でタイトル(_A)')
        #
        tool_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, margin=10)
        tool_box.pack_start(menu_back, False, False, 0)
        tool_box.pack_start(menu_set, False, False, 0)
        tool_box.pack_start(menu_append, False, False, 0)
        # GtkPopoverMenu
        self.popovermenu = Gtk.PopoverMenu()
        self.popovermenu.add(vbox)
        self.popovermenu.add(tool_box)
        self.popovermenu.child_set_property(tool_box, 'submenu', 'tool_page')
        # MenuButton
        open_image = Gtk.Image(icon_name='open-menu-symbolic', icon_size=Gtk.IconSize.MENU)
        self.menu_button = Gtk.MenuButton(image=open_image, popover=self.popovermenu)
        self.menu_button.connect('toggled', self.on_menu_button_toggled)
        # GtkHeaderBar
        hbar = Gtk.HeaderBar(show_close_button=True)
        hbar.pack_end(self.menu_button)
        self.set_titlebar(hbar)
        # GAction
        f10_action = Gio.SimpleAction(name='f10_action')
        set_title_action = Gio.SimpleAction(name='set_title_action')
        append_title_action = Gio.SimpleAction(name='append_title_action')
        self.add_action(f10_action)
        self.add_action(set_title_action)
        self.add_action(append_title_action)
        f10_action.connect('activate', self.on_f10_action)
        set_title_action.connect('activate', self.on_set_title_action)
        append_title_action.connect('activate', self.on_append_title_action)
        # self
        self.resize(240, 200)
        self.show_all()

    def on_f10_action(self, action, parameter):
        self.menu_button.clicked()

    def on_set_title_action(self, action, parameter):
        self.props.title = 'あ'

    def on_append_title_action(self, action, parameter):
        self.props.title += 'い'

    def on_menu_button_toggled(self, button):
        if button.props.active:
            self.popovermenu.show_all()

class App(Gtk.Application):
    def __init__(self):
        Gtk.Application.__init__(self)

    def do_startup(self):
        Gtk.Application.do_startup(self)
        # GAction
        new_window_action = Gio.SimpleAction(name='new_window_action')
        quit_action = Gio.SimpleAction(name='quit_action')
        # Add
        self.add_action(new_window_action)
        self.add_action(quit_action)
        # Keyboard Shortecut (app)
        self.set_accels_for_action('app.new_window_action', ['<Control>N'])
        self.set_accels_for_action('app.quit_action', ['<Control>Q'])
        # Keyboard Shortecut (win)
        self.set_accels_for_action('win.f10_action', ['F10'])
        self.set_accels_for_action('win.set_title_action', ['<Control>S'])
        self.set_accels_for_action('win.append_title_action', ['<Control>A'])
        # Signal
        new_window_action.connect('activate', self.on_new_window_action)
        quit_action.connect('activate', self.on_quit_action)
        # Window
        Win(self)

    def on_new_window_action(self, action, parameter):
        Win(self)

    def on_quit_action(self, action, parameter):
        self.quit()

    def do_activate(self):
        self.props.active_window.present()

app = App()
app.run(sys.argv)
etc/menu02.png
コンテキストメニュー
GNOME 標準アプリからはメニューバー、ツールバーは無くなりました。
しかしコンテキストメニュー(右クリックメニュー)はまだバリバリ使われている。
GtkTextView 等は独自のコンテキストメニューがあるのでそれ以外で。
#!/usr/bin/env python3

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

class Win(Gtk.ApplicationWindow):
    '''
        ウインドウのどこかでマウス右ボタンを押してね
    '''
    def __init__(self, app):
        Gtk.ApplicationWindow.__init__(self, application=app, title='Py')
        # MenuIten
        item_open = Gtk.MenuItem(label='_Open', use_underline=True, visible=True)
        item_open.connect('activate', self.on_open)
        item_quit = Gtk.MenuItem(label='_Quit', use_underline=True, visible=True)
        item_quit.connect('activate', self.on_quit)
        # アクセラレーターの登録は GAction 等でおこなっているならしない
        accelgroup = Gtk.AccelGroup()
        self.add_accel_group(accelgroup)
        item_open.add_accelerator('activate', accelgroup, Gdk.KEY_o,
                Gdk.ModifierType.CONTROL_MASK, Gtk.AccelFlags.VISIBLE)
        item_quit.add_accelerator('activate', accelgroup, Gdk.KEY_q,
                Gdk.ModifierType.CONTROL_MASK, Gtk.AccelFlags.VISIBLE)
        # この Menu をドロップする
        self.menu = Gtk.Menu()
        self.menu.append(item_open)
        self.menu.append(Gtk.SeparatorMenuItem(visible=True))
        self.menu.append(item_quit)
        # label
        self.label = Gtk.Label(label='filename')
        self.add(self.label)
        # self
        self.resize(200, 100)
        self.show_all()

    def on_open(self, action):
        dlg = Gtk.FileChooserNative(
            title='開く',
            transient_for=self,
            action=Gtk.FileChooserAction.OPEN)
        if dlg.run() == Gtk.ResponseType.ACCEPT:
            f = dlg.get_filename()
            self.label.set_text(os.path.basename(f))
        dlg.destroy()

    def on_quit(self, action):
        self.close()

    def do_button_press_event(self, event):
        '''
            マウス左ボタンは 1、右ボタンは 3
            中(ホイール)ボタンは 2 だけど非推奨
        '''
        if event.button == 3:
            # self.menu.popup() は 3.22 より非推奨
            self.menu.popup_at_pointer()
        return False

class App(Gtk.Application):
    def __init__(self):
        Gtk.Application.__init__(self)

    def do_startup(self):
        Gtk.Application.do_startup(self)
        Win(self)

    def do_activate(self):
        self.props.active_window.present()

app = App()
app.run(sys.argv)
etc/menu03.png
GtkMenuBar, GtkToolbar
今や Evolution でしか使われていないメニューバーとツールバーです。
GtkMenuBar, GtkToolbar なので注意、統一される前に無くなると思うけど。
#!/usr/bin/env python3

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

class Win(Gtk.ApplicationWindow):
    '''
        一応まだ使えるけど...
    '''
    def __init__(self, app):
        Gtk.ApplicationWindow.__init__(self, application=app, title='Py')
        # MenuIten
        item_open = Gtk.MenuItem(label='_Open', use_underline=True)
        item_open.connect('activate', self.on_open)
        item_quit = Gtk.MenuItem(label='_Quit', use_underline=True)
        item_quit.connect('activate', self.on_quit)
        # Add Accelerator
        accelgroup = Gtk.AccelGroup()
        self.add_accel_group(accelgroup)
        item_open.add_accelerator('activate', accelgroup, Gdk.KEY_o, 
            Gdk.ModifierType.CONTROL_MASK, Gtk.AccelFlags.VISIBLE)
        item_quit.add_accelerator('activate', accelgroup, Gdk.KEY_q, 
            Gdk.ModifierType.CONTROL_MASK, Gtk.AccelFlags.VISIBLE)
        # Menu
        file_sub_menu = Gtk.Menu()
        file_sub_menu.append(item_open)
        file_sub_menu.append(Gtk.SeparatorMenuItem())
        file_sub_menu.append(item_quit)
        # Top MenuItem
        file_root_menu = Gtk.MenuItem(label='_File', use_underline=True)
        file_root_menu.set_submenu(file_sub_menu)
        # MenuBar
        menubar = Gtk.MenuBar()
        menubar.append(file_root_menu)
        #
        # Toolbar、ストックは非推奨になりました
        tool_open = Gtk.ToolButton(icon_name='folder-documents-symbolic')
        tool_open.set_tooltip_text('開く')
        tool_open.connect('clicked', self.on_open)
        tool_quit = Gtk.ToolButton(icon_name='window-close-symbolic')
        tool_quit.set_tooltip_text('終了')
        tool_quit.connect('clicked', self.on_quit)
        toolbar = Gtk.Toolbar()
        toolbar.insert(tool_open, 0)
        toolbar.insert(Gtk.SeparatorToolItem(), 1)
        toolbar.insert(tool_quit, 2)
        #
        # パッキング
        drawingarea = Gtk.DrawingArea()
        vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
        vbox.pack_start(menubar, False, True, 0)
        vbox.pack_start(toolbar, False, True, 0)
        vbox.pack_start(drawingarea, True, True, 0)
        self.add(vbox)
        # いつもの処理
        self.resize(200, 200)
        self.show_all()

    def on_open(self, widget):
        pass

    def on_quit(self, widget):
        self.close()

class App(Gtk.Application):
    def __init__(self):
        Gtk.Application.__init__(self)

    def do_startup(self):
        Gtk.Application.do_startup(self)
        Win(self)

    def do_activate(self):
        self.props.active_window.present()

app = App()
app.run(sys.argv)
etc/menu04.png
Copyright(C) sasakima-nao All rights reserved 2002 --- 2020.