GUIツールキットGTK+を用いたプログラムを作成する際、従来の方法では
- GTK+の初期化用関数を呼び出す
GtkWindow
クラスのウィンドウを作成- メインループを開始
という形をとっていたが、GTK+の下位にある “GIO” ライブラリに新しく “アプリケーション” 機能やメニュー処理などが追加されており、GTK+側でもこれらに対応して新しい形のコード記述が行えるようになっている。
扱い方のメモ
アプリケーションクラスとアプリケーションウィンドウクラス
- GIOライブラリに基づいたメニュー項目とその選択時の処理の関連付けを扱える
GtkApplicationWindow
クラスのウィンドウにGtkApplication
クラスのオブジェクトを関連付けることでメニューを表示することができる
- プログラムを複数起動したときの処理を行う機能
- アプリケーションID文字列を指定するとプログラムを複数起動した際に一番最初に起動したプログラム(インスタンス)へ新しい起動を通知して2つ目からのプログラムは即座に終了する
GtkApplication
ではアプリケーション内の状況によって自動的に特定の名前の関数(PyGIでは “do_
” が先頭に付く)が呼ばれるstartup
: 一番最初に起動したプログラムのみ最初に呼び出されるactivate
: 一番最初に起動したかどうかに関わらず、プログラムが起動した後に呼び出される(ウィンドウ生成処理など)shutdown
: 起動された全てのプログラムが終了した後に呼び出される(アプリケーション全体の後始末処理など)- 上記の内の幾つかは
GtkApplication
クラスを継承した場合に親クラスのメンバ関数を手動で呼び出す処理が必須となる
- プログラム自体のメイン処理からの使い方としては
GtkApplication
クラスのメンバ関数run()
にコマンド行引数の引数を渡す
メニュー
- メニューは操作の対象の範囲によって2つの種類が存在する
- ウィンドウごとの伝統的なメニューバー用のメニューとして
set_menubar()
によって設定するもの - アプリケーション全体のメニューとして
set_app_menu()
によって設定するもの(“設定” や “終了” のような項目に向いている)で、デスクトップ環境(やその設定)によってはパネル上のプログラム名部分から開くメニュー項目となる- (デスクトップ環境によっては)このメニューのラベル文字列はGLibの
set_application_name()
で指定したものが使用され、未指定だと日本語ロケールでは “アプリケーション” という名前になる
- (デスクトップ環境によっては)このメニューのラベル文字列はGLibの
- ウィンドウごとの伝統的なメニューバー用のメニューとして
- メニューはXML形式で記述でき、
GtkBuilder
クラスのオブジェクトを用いてプログラムから利用可能な形で扱うことができる- XMLの全体の要素が
interface
要素(HTMLでのhtml
要素に相当)・これはGUI部品などの記述をGtkBuilder
のXMLで行う場合と同様 - 子要素として
menu
要素をメニューの数だけ用意し、取り出す際の識別のためにそれぞれid
属性を指定する - メニューバーでは “ファイル” “編集” などのサブメニューとなる
submenu
要素をmenu
の子要素として記述- これらの要素の子として
section
要素を配置し、複数の関係した項目群をグループ化するのに用いる(区切られたセクション間には自動的に境界線が引かれる)
- これらの要素の子として
- 各メニュー項目は
item
要素で表現され、その中の各種属性値はこの子としてattribute
要素で指定する形をとる(全体の記述は長くなるが、整理された形となって読み書きはしやすい)- ラベル(表示文字列)は “
label
” ,アクション文字列(選択時に呼ばれる関数と別途関連付けるための文字列)は “action
” ,ショートカットキーの記述は “accel
”, アイコン名(もしくはその場所)は “icon
” を値にとったname
属性をとる- この記述に関する詳細な説明はGtkApplicationWindowクラスのリファレンスにある
- ショートカットキーの内部表現は “
<Primary>o
” (Ctrl/⌘-o)のようになるが、XML中では不等号は “<
” と “>
” で記述する必要がある - “ファイル” “編集” などのサブメニュー自体のラベルについては
submenu
要素の直下にattribute
要素を記述する
- ラベル(表示文字列)は “
GtkBuilder
クラスのオブジェクトからget_object()
で取り出したメニューは言語によってはそのままset_menubar()
やset_app_menu()
に渡せないため、GMenuModel
クラスに対する型変換が必要となるGtkApplication
クラスはGtkBuilder
クラスのオブジェクトを作成しなくても特定の場所(外部ファイル)にメニューのXMLファイルを配置することで自動的に読み込まれる仕組みが存在するが、本記事では扱わず、後述の使用例ではGtkBuilder
クラスのオブジェクトを用いている
- XMLの全体の要素が
GtkApplicationWindow
クラスのウィンドウの中に(GtkContainer
クラスの)add()
でGUI部品を入れると、(GtkWindow
とは異なり)ウィンドウ全体に含まれるのではなく、自動的に挿入されるメニューバーの下の領域に描画される- 環境や設定によってデスクトップ環境のパネルなどにメニューを表示するようになっている場合はメニューはそちらにのみ表示される)
アクション
- メニュー項目とそれが選択されたときに呼ばれる関数との関連付けは “アクション” と呼ばれるオブジェクトを介して行われ、直接対応付けられるわけではない
- メニューのXML内で記述した名前のアクションは別途関数との関連付けをアプリケーション側に渡す必要がある
- アプリケーションのオブジェクトにアクションを追加するには(
GActionMap
インターフェースの)add_action_entries()
にGActionEntry
の配列を渡して一括追加するか、手動で用意したアクション(GSimpleAction
型のオブジェクトを作成し、 “activate
” シグナルについて別途用意したハンドラ関数と関連付ける)をadd_action()
で個別に追加する- いずれの場合もメニュー項目が選択されたときの処理は引数を2つ(
GSimpleAction
型とGVariant
型)とり戻り値のない関数に記述する
- いずれの場合もメニュー項目が選択されたときの処理は引数を2つ(
set_menubar()
で追加したメニュー項目は特定のウィンドウに対してのみ行う処理となるため、関数内でget_active_window()
を呼び出してアクティブなウィンドウを取得してメニュー項目が選択されたウィンドウを特定し、これに対して(ウィンドウのクラスを継承したクラスの)メンバ関数を呼び出すような形でウィンドウに対して処理を行うget_active_window()
はGtkWindow
クラスで返すため、言語によってはメンバ関数を呼び出すために型変換を行う必要がある
使用例
Python(PyGI)
[任意]ファイル名:
gtkapplicationtest.py
#! /usr/bin/python
from __future__ import print_function
try:
import gi
except ImportError:
sys.exit ('PyGI not installed')
try:
gi.require_version ('Gtk', '3.0')
from gi.repository import Gtk, GLib, Gio
except ValueError:
sys.exit ('typelib for GTK+ 3 not found')
except ImportError:
sys.exit ('Failed to load GTK+')
import sys
class MainWindow (Gtk.ApplicationWindow):
_num_window = 0
def __init__ (self, application, n):
Gtk.ApplicationWindow.__init__ (self, application = application)
self.add (Gtk.Label ('Hello, GtkApplication !!'))
self.set_default_size (400, 300)
self.props.title = 'Window {0}'.format (n)
self._num_window = n
def open_file (self):
print ('open_file (Window {0})'.format (self._num_window))
def save_file (self):
print ('save_file (Window {0})'.format (self._num_window))
class GtkApplicationTest (Gtk.Application):
_cnt_windows = 0
def __init__ (self):
GLib.set_application_name ('GtkApplicationTest')
Gtk.Application.__init__ (self, application_id = 'com.blogspot.kakurasan.gtkapplicationtest')
def _action_new_activate_cb (self, act, param):
self.activate ()
def _action_open_activate_cb (self, act, param):
self.get_active_window ().open_file ()
def _action_save_activate_cb (self, act, param):
self.get_active_window ().save_file ()
def _action_quit_activate_cb (self, act, param):
print ('quit')
self.quit ()
def do_activate (self):
print ('activate')
self._cnt_windows += 1
MainWindow (self, self._cnt_windows).show_all ()
def do_startup (self):
menu_ui = '''
<interface>
<menu id="menubar">
<submenu>
<attribute name="label" translatable="yes">_File</attribute>
<section>
<item>
<attribute name="label" translatable="yes">_Open</attribute>
<attribute name="action">app.open</attribute>
<attribute name="accel"><Primary>o</attribute>
<attribute name="icon">gtk-open</attribute>
</item>
<item>
<attribute name="label" translatable="yes">_Save</attribute>
<attribute name="action">app.save</attribute>
<attribute name="accel"><Primary>s</attribute>
<attribute name="icon">gtk-save</attribute>
</item>
</section>
</submenu>
</menu>
<menu id="appmenu">
<section>
<item>
<attribute name="label" translatable="yes">_New</attribute>
<attribute name="action">app.new</attribute>
<attribute name="accel"><Primary>n</attribute>
<attribute name="icon">gtk-new</attribute>
</item>
<item>
<attribute name="label" translatable="yes">_Quit</attribute>
<attribute name="action">app.quit</attribute>
<attribute name="accel"><Primary>q</attribute>
<attribute name="icon">gtk-quit</attribute>
</item>
</section>
</menu>
</interface>
'''
action_entries = (('new', self._action_new_activate_cb, None, None, None),
('open', self._action_open_activate_cb, None, None, None),
('save', self._action_save_activate_cb, None, None, None),
('quit', self._action_quit_activate_cb, None, None, None))
print ('startup')
Gtk.Application.do_startup (self)
for name, activate, parameter_type, state, change_state in action_entries:
act = Gio.SimpleAction (name = name, state = state, parameter_type = parameter_type)
if activate:
act.connect ('activate', activate)
if change_state:
act.connect ('change_state', change_state)
self.add_action (act)
builder_menu = Gtk.Builder.new_from_string (menu_ui, -1)
self.set_menubar (builder_menu.get_object ('menubar'))
self.set_app_menu (builder_menu.get_object ('appmenu'))
def do_shutdown (self):
print ('shutdown');
Gtk.Application.do_shutdown (self)
if __name__ == '__main__':
sys.exit (GtkApplicationTest ().run (sys.argv))
Vala
[任意]ファイル名:
gtkapplicationtest.vala
// valac --pkg gtk+-3.0 gtkapplicationtest.vala -o gtkapplicationtest
using Gtk;
namespace GtkApplicationTest
{
class MainWindow : Gtk.ApplicationWindow
{
int num_window = 0;
public
MainWindow (Gtk.Application application, int n)
{
GLib.Object (application: application);
this.add (new Gtk.Label ("Hello, GtkApplication !!"));
this.set_default_size (400, 300);
this.title = "Window %d".printf (n);
this.num_window = n;
}
public void
open_file ()
{
print ("open_file (Window %d)\n", this.num_window);
}
public void
save_file ()
{
print ("save_file (Window %d)\n", this.num_window);
}
}
class GtkApplicationTest : Gtk.Application
{
int cnt_windows = 0;
const GLib.ActionEntry[] action_entries =
{
{"new", action_new_activate_cb, null, null, null},
{"open", action_open_activate_cb, null, null, null},
{"save", action_save_activate_cb, null, null, null},
{"quit", action_quit_activate_cb, null, null, null},
};
public
GtkApplicationTest ()
{
GLib.Environment.set_application_name ("GtkApplicationTest");
GLib.Object (application_id: "com.blogspot.kakurasan.gtkapplicationtest");
}
void
action_new_activate_cb (GLib.SimpleAction act, GLib.Variant? param)
{
this.activate ();
}
void
action_open_activate_cb (GLib.SimpleAction act, GLib.Variant? param)
{
((MainWindow) (this.get_active_window ())).open_file ();
}
void
action_save_activate_cb (GLib.SimpleAction act, GLib.Variant? param)
{
((MainWindow) (this.get_active_window ())).save_file ();
}
void
action_quit_activate_cb (GLib.SimpleAction act, GLib.Variant? param)
{
print ("quit\n");
this.quit ();
}
protected override void
activate ()
{
print ("activate\n");
new MainWindow (this, ++cnt_windows).show_all ();
}
protected override void
startup ()
{
const string menu_ui = """
<interface>
<menu id="menubar">
<submenu>
<attribute name="label" translatable="yes">_File</attribute>
<section>
<item>
<attribute name="label" translatable="yes">_Open</attribute>
<attribute name="action">app.open</attribute>
<attribute name="accel"><Primary>o</attribute>
<attribute name="icon">gtk-open</attribute>
</item>
<item>
<attribute name="label" translatable="yes">_Save</attribute>
<attribute name="action">app.save</attribute>
<attribute name="accel"><Primary>s</attribute>
<attribute name="icon">gtk-save</attribute>
</item>
</section>
</submenu>
</menu>
<menu id="appmenu">
<section>
<item>
<attribute name="label" translatable="yes">_New</attribute>
<attribute name="action">app.new</attribute>
<attribute name="accel"><Primary>n</attribute>
<attribute name="icon">gtk-new</attribute>
</item>
<item>
<attribute name="label" translatable="yes">_Quit</attribute>
<attribute name="action">app.quit</attribute>
<attribute name="accel"><Primary>q</attribute>
<attribute name="icon">gtk-quit</attribute>
</item>
</section>
</menu>
</interface>
""";
print ("startup\n");
base.startup ();
this.add_action_entries (GtkApplicationTest.action_entries, this);
var builder_menu = new Gtk.Builder.from_string (menu_ui, -1);
this.set_menubar ((GLib.MenuModel) (builder_menu.get_object ("menubar")));
this.set_app_menu ((GLib.MenuModel) (builder_menu.get_object ("appmenu")));
}
protected override void
shutdown ()
{
print ("shutdown\n");
base.shutdown ();
}
}
int
main (string[] args)
{
return new GtkApplicationTest ().run (args);
}
}
使用したバージョン:
- GTK+ 3.20.9
- GLib 2.50.0
- Python 2.7.12, 3.5.2
- Vala 0.32.1