Python GTK+3 チュートリアル

23. アプリケーション

翻訳して勉強するGtkチュートリアル第23章 Application です。これが最終章です。


Gtk.Application は、複数のインスタンスの処理、D-Bus の起動、ファイルのオープン、コマンドラインの解析、スタートアップ/シャットダウン、メニュー管理、ウィンドウ管理など、現代のアプリケーションが必要とする多くの反復的なタスクを網羅しています。


23.1. アクション

Gio.Action は、アプリケーションやウィジェットが行う単一のタスクを名前で公開する方法です。これらのアクションは実行時に無効化/有効化できます。アクティブにしたり、状態がある場合はその状態を変更したりすることができます。

アクションを使う理由は、ロジックを UI(ユーザインターフェース)から切り離すためです。例えば、OSXではメニューバーを、GNOMEではギアメニューを使うことができ、どちらも単にアクションの名前を参照するだけです。ここで使用する主な実装は Gio.SimpleAction で、後ほどサンプルを動かしてみます。

Gio.MenuItem Gtk.ModelButton のような多くのクラスは、アクション名を設定するためのプロパティをサポートしています。

これらのアクションは Gio.ActionGroup にグループ化することができ、これらのグループが Gtk.Widget.insert_action_group() でウィジェットに追加されると接頭辞が付けられます。Gtk.ApplicationWindow に追加された場合は “win” のようになります。“app.about” のようにアクションを参照する際には完全なアクション名を使用しますが、アクションを作成する際にはアプリケーションに追加されるまでは “about” となります。

また、Gio.Menu ファイルの “accel” プロパティを設定するか、Gtk.Application.add_accelerator() を使うことで、アクションのキーバインドを非常に簡単に行うことができます。


23.2. メニュー

メニューは Gio.Menu を使って XML で定義されていなければなりません。Gtk.Application では、Gtk.Application.set_app_menu() Gtk.Application.set_menubar() でメニューを設定することができます。Gio.Resource を利用している場合は、プラットフォームに応じて自動的に正しいメニューを使うことができますが、そうでない場合は手動で設定することができます。詳細な例を以下に示します。


23.3. コマンドライン

アプリケーションを作成するときには、Gio.ApplicationFlags の flag プロパティを使います。これを使うことで、すべてを自分で処理させたり、よりカスタムな動作をさせたりすることができます。

Gio.Application.do_command_line() でカスタム動作を可能にするために、HANDLES_COMMAND_LINE を使用することができます。Gio.Application.add_main_option() と組み合わせて、カスタム・オプションを追加します。

HANDLES_OPEN を使うと、単にファイルの引数を取って、Gio.Application.do_open() の中で処理してくれるようになります。

アプリケーションが既にオープンされている場合は、NON_UNIQUE を使用して複数のインスタンスを許可しない限り、これらはすべて既存のインスタンスに送信されます。


23.4. 例

動作テスト - アプリケーション
# tut23.py
# Application

import sys
import gi

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

# This would typically be its own file
MENU_XML = """
<?xml version="1.0" encoding="UTF-8"?>
<interface>
  <menu id="app-menu">
    <section>
      <attribute name="label" translatable="yes">Change label</attribute>
      <item>
        <attribute name="action">win.change_label</attribute>
        <attribute name="target">String 1</attribute>
        <attribute name="label" translatable="yes">String 1</attribute>
      </item>
      <item>
        <attribute name="action">win.change_label</attribute>
        <attribute name="target">String 2</attribute>
        <attribute name="label" translatable="yes">String 2</attribute>
      </item>
      <item>
        <attribute name="action">win.change_label</attribute>
        <attribute name="target">String 3</attribute>
        <attribute name="label" translatable="yes">String 3</attribute>
      </item>
    </section>
    <section>
      <item>
        <attribute name="action">win.maximize</attribute>
        <attribute name="label" translatable="yes">Maximize</attribute>
      </item>
    </section>
    <section>
      <item>
        <attribute name="action">app.about</attribute>
        <attribute name="label" translatable="yes">_About</attribute>
      </item>
      <item>
        <attribute name="action">app.quit</attribute>
        <attribute name="label" translatable="yes">_Quit</attribute>
        <attribute name="accel">&lt;Primary&gt;q</attribute>
    </item>
    </section>
  </menu>
</interface>
"""


class AppWindow(Gtk.ApplicationWindow):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        # これは windows グループにあり、"win" という接頭辞を持つ
        max_action = Gio.SimpleAction.new_stateful(
            "maximize", None, GLib.Variant.new_boolean(False)
        )
        max_action.connect("change-state", self.on_maximize_toggle)
        self.add_action(max_action)

        # 実際の状態と同期させておく
        self.connect(
            "notify::is-maximized",
            lambda obj, pspec: max_action.set_state(
                GLib.Variant.new_boolean(obj.props.is_maximized)
            ),
        )

        lbl_variant = GLib.Variant.new_string("String 1")
        lbl_action = Gio.SimpleAction.new_stateful(
            "change_label", lbl_variant.get_type(), lbl_variant
        )
        lbl_action.connect("change-state", self.on_change_label_state)
        self.add_action(lbl_action)

        self.label = Gtk.Label(label=lbl_variant.get_string(), margin=30)
        self.add(self.label)
        self.label.show()

    def on_change_label_state(self, action, value):
        action.set_state(value)
        self.label.set_text(value.get_string())

    def on_maximize_toggle(self, action, value):
        action.set_state(value)
        if value.get_boolean():
            self.maximize()
        else:
            self.unmaximize()


class Application(Gtk.Application):
    def __init__(self, *args, **kwargs):
        super().__init__(
            *args,
            application_id="org.example.myapp",
            flags=Gio.ApplicationFlags.HANDLES_COMMAND_LINE,
            **kwargs
        )
        self.window = None

        self.add_main_option(
            "test",
            ord("t"),
            GLib.OptionFlags.NONE,
            GLib.OptionArg.NONE,
            "Command line test",
            None,
        )

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

        action = Gio.SimpleAction.new("about", None)
        action.connect("activate", self.on_about)
        self.add_action(action)

        action = Gio.SimpleAction.new("quit", None)
        action.connect("activate", self.on_quit)
        self.add_action(action)

        builder = Gtk.Builder.new_from_string(MENU_XML, -1)
        self.set_app_menu(builder.get_object("app-menu"))

    def do_activate(self):
        # 単一の窓だけを許可し、既存のものを上げる
        if not self.window:
            # ウィンドウはアプリケーションに関連付けらている
            # 最後のウィンドウが閉じられるとアプリケーションはシャットダウンする
            self.window = AppWindow(application=self, title="Main Window")

        self.window.present()

    def do_command_line(self, command_line):
        options = command_line.get_options_dict()
        # GVariantDict -> GVariant -> dict に変換
        options = options.end().unpack()

        if "test" in options:
            # これはメイン・インスタンスにプリントされる
            print("Test argument recieved: %s" % options["test"])

        self.activate()
        return 0

    def on_about(self, action, param):
        about_dialog = Gtk.AboutDialog(transient_for=self.window, modal=True)
        about_dialog.present()

    def on_quit(self, action, param):
        self.quit()


if __name__ == "__main__":
    app = Application()
    app.run(sys.argv)

23.5. 参考サイト


廃止

以下のクラスは廃止になりました。

Menu

Gtk.UIManager Gtk.Action Gtk.ActionGroup は GTK+ バージョン 3.10 以降非推奨となっており、新しく書かれるコードでは使用すべきではありません。代わりに Application フレームワークを使ってください。

Table

Gtk.Table は GTK+ バージョン 3.4 以降非推奨となっており、新たに書かれるコードでは使用すべきではありません。代わりに Grid クラスを使用してください。


関連記事