メニューを作成するには2つの方法がある。簡単な方法と難しい方法である。 どちらもそれぞれ使い道があるが、普通はアイテムファクトリを使う方法(簡 単な方)で十分である。"難しい" 方法は関数呼び出しを直接使って全てのメニュー を作成する。簡単な方は gtk_item_factory 関数呼び出しを用いる。こちらの 方がかなり簡単であるが、それぞれのアプローチには長所と短所がある。
アイテムファクトリはかなり簡単に使えるし、新しいメニューを追加するにも 簡単である。しかし一方では手作業でメニュー作成用のラッパー関数を書くこ とで使い勝手を増すことができるかも知れない。アイテムファクトリを使う方 法ではメニューにイメージや '/' 文字を加えることができない。
真の教育の伝統にしたがい、まずは困難な方法から見ていこう。:)
メニューバーやサブメニューの作成に関わるウィジェットは3つある。
メニューアイテムウィジェットが異なる2つの使い方をされるために、いささ か解りにくい。メニューにパックされるものと、メニューバーにパックされ選 択されたときにメニューをアクティブにするものである。
それではメニューとメニューバーを作成する関数を見てみよう。最初の関数は 新規にメニューバーを作成するのに使われる。
GtkWidget *gtk_menu_bar_new( void );
関数名が示すようにこれは新規にメニューバーを作成する。これを gtk_container_add を用いてウィンドウにパックしたり、box_pack 関数を使っ てボックスにパックすることができる - ボタンと同様に。
GtkWidget *gtk_menu_new( void );
この関数は新規にメニューを作成しそのポインタを返す。これは実際には (gtk_widget_show を用いて)表示されることは無く、単にメニューアイテムの コンテナとなるだけである。これは以下にある例を見ればよく理解できるだろ う。
次の2つの関数はメニュー(やメニューバー)にパックするメニューアイテムを 作成するのに用いられる。
GtkWidget *gtk_menu_item_new( void );
および
GtkWidget *gtk_menu_item_new_with_label( const char *label );
これらは表示されるメニューアイテムを作成するのに用いられる。 gtk_menu_new で作成される "メニュー" と gtk_menu_item_new で作成される "メニューアイテム" を区別するのを忘れないように。メニューアイテムは動 作が関連付けられているボタンそのものであり、一方、メニューはメニューア イテムを保持するコンテナである。
gtk_menu_new_with_label と gtk_menu_new 関数はボタンに関して学んだ後な ら簡単に理解できるだろう。[訳注: gtk_menu_item_with_label, gtk_menu_item_newの間違いでしょう。区別しろと言ったばかりなのに (^_^;]一つは既にラベルをパックしている新規のメニューアイテムを作 成し、もう一つは単に空白のメニューアイテムを作成する。
メニューアイテムを作成した後はそれをメニューにパックしなくてはならない。
これは gtk_menu_append 関数を用いて行われる。ユーザーがアイテムを選択
したのを捉えるにはいつものやり方で activate シグナルを接続する必
要がある。例えば Open, Save それに Quit オプションを備
えた通常の File メニューを作成したい場合は、コードは以下のような
ものになる。
file_menu = gtk_menu_new (); /* メニューを show する必要は無い */
/* メニューアイテムを作成する */
open_item = gtk_menu_item_new_with_label ("Open");
save_item = gtk_menu_item_new_with_label ("Save");
quit_item = gtk_menu_item_new_with_label ("Quit");
/* それらをメニューに加える */
gtk_menu_append (GTK_MENU (file_menu), open_item);
gtk_menu_append (GTK_MENU (file_menu), save_item);
gtk_menu_append (GTK_MENU (file_menu), quit_item);
/* activate シグナルにコールバック関数をアタッチする */
gtk_signal_connect_object (GTK_OBJECT (open_items), "activate",
GTK_SIGNAL_FUNC (menuitem_response),
(gpointer) "file.open");
gtk_signal_connect_object (GTK_OBJECT (save_items), "activate",
GTK_SIGNAL_FUNC (menuitem_response),
(gpointer) "file.save");
/* 自前の終了関数を Quit メニューにアタッチする */
gtk_signal_connect_object (GTK_OBJECT (quit_items), "activate",
GTK_SIGNAL_FUNC (destroy),
(gpointer) "file.quit");
/* メニューアイテムは show する必要がある */
gtk_widget_show (open_item);
gtk_widget_show (save_item);
gtk_widget_show (quit_item);
さて、これでメニューが準備できたので、次はメニューバーと File エ
ントリのためのメニューアイテムを作成し、メニューをそれに加える必要があ
る。コードは次のようなものになる:
menu_bar = gtk_menu_bar_new ();
gtk_container_add (GTK_CONTAINER (window), menu_bar);
gtk_widget_show (menu_bar);
file_item = gtk_menu_item_new_with_label ("File");
gtk_widget_show (file_item);
次はメニューと file_item を関連付ける必要がある。これは次の関数を
用いて行われる:
void gtk_menu_item_set_submenu( GtkMenuItem *menu_item,
GtkWidget *submenu );
というわけで、我々の例は次に続く:
gtk_menu_item_set_submenu (GTK_MENU_ITEM (file_item), file_menu);
残りの仕事はメニューをメニューバーに加えることである。これは次の関数を 用いて行われる:
void gtk_menu_bar_append( GtkMenuBar *menu_bar,
GtkWidget *menu_item );
我々のケースではこれは以下のようになる:
gtk_menu_bar_append (GTK_MENU_BAR (menu_bar), file_item);
ヘルプメニューなどでよく見られるように、メニューバー上でメニューを右寄
せにしたい場合は、メニューバーにアタッチする前に(今の例では
file_itemにして)次の関数を使えばよい。
void gtk_menu_item_right_justify( GtkMenuItem *menu_item );
以下はメニューをアタッチしたメニューバーを作成するのに必要なステップの 要約である:
ポップアップメニューを作成する場合もほとんど同様である。違いはメニュー がメニューバーによって "時動的に" 表示されるのでなく、button-press イ ベント等から gtk_menu_popup() 関数を明示的に呼び出すことで表示される点 である。
static gint handler (GtkWidget *widget,
GdkEvent *event);
そしてメニューをどこにポップアップするかをイベント情報を用いて調べる。event をボタンイベントとして扱い、サンプルコードで示すような方法で
gtk_menu_popup() に情報を渡す。
gtk_signal_connect_object (GTK_OBJECT (widget), "event",
GTK_SIGNAL_FUNC (handler),
GTK_OBJECT (menu));
ここで widget は接続先のウィジェットで、handler は操
作関数、menu は gtk_menu_new() で作成されたメニューである。こ
れはサンプルコードに示すようにメニューバーに表示されているメニューでも
よい。
以下はおおよそ行うべきことである。理解を確かなものにするためにこの例に 目を通してみよう。
/* example-start menu menu.c */
#include <gtk/gtk.h>
static gint button_press (GtkWidget *, GdkEvent *);
static void menuitem_response (gchar *);
int main( int argc,
char *argv[] )
{
GtkWidget *window;
GtkWidget *menu;
GtkWidget *menu_bar;
GtkWidget *root_menu;
GtkWidget *menu_items;
GtkWidget *vbox;
GtkWidget *button;
char buf[128];
int i;
gtk_init (&argc, &argv);
/* 新規にウィンドウを作成する */
window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
gtk_widget_set_usize (GTK_WIDGET (window), 200, 100);
gtk_window_set_title (GTK_WINDOW (window), "GTK Menu Test");
gtk_signal_connect (GTK_OBJECT (window), "delete_event",
(GtkSignalFunc) gtk_main_quit, NULL);
/* メニューウィジェットを初期化、それから -- メニューウィジェット
* を gtk_show_widget() してはならない事を思い出せ!!
* これはメニューアイテムを保持するメニューで、アプリケーションの
* "Root Menu" をクリックするとポップアップする。*/
menu = gtk_menu_new ();
/* 次に "test-menu" のために 3 つのメニューエントリを作成する小さ
* なループを用意する。
* gtk_menu_append を呼ぶのに注意すること。ここではメニューにメニュー
* アイテムのリストを加えている。普通は、各メニューアイテムの
* "clicked" シグナルを拾い、そのコールバック関数を用意するが、
* ここでは場所の節約のために省略している。*/
for (i = 0; i < 3; i++)
{
/* buf に名前をコピー。 */
sprintf (buf, "Test-undermenu - %d", i);
/* 新規に名前付きメニューアイテムを作成... */
menu_items = gtk_menu_item_new_with_label (buf);
/* ...そしてこれをメニューに加える。 */
gtk_menu_append (GTK_MENU (menu), menu_items);
/* メニューアイテムが選択されたときになにかを行うようにする */
gtk_signal_connect_object (GTK_OBJECT (menu_items), "activate",
GTK_SIGNAL_FUNC (menuitem_response), (gpointer) g_strdup (buf));
/* ウィジェットを表示 */
gtk_widget_show (menu_items);
}
/* これはルートメニューでメニューバーに表示されるラベルになる。
* 押された時にメニューをポップアップするだけなので、
* アタッチされるシグナルハンドラは無い。*/
root_menu = gtk_menu_item_new_with_label ("Root Menu");
gtk_widget_show (root_menu);
/* さて、新しく作成された "menu" が "root menu" のメニューになるよ
* うに指定する。*/
gtk_menu_item_set_submenu (GTK_MENU_ITEM (root_menu), menu);
/* メニューとボタンを配置する vbox */
vbox = gtk_vbox_new (FALSE, 0);
gtk_container_add (GTK_CONTAINER (window), vbox);
gtk_widget_show (vbox);
/* メニューを保持するためのメニューバーを作成、これをメインウィン
* ドウに加える。*/
menu_bar = gtk_menu_bar_new ();
gtk_box_pack_start (GTK_BOX (vbox), menu_bar, FALSE, FALSE, 2);
gtk_widget_show (menu_bar);
/* ボタンを作成しメニューをポップアップするようハンドラをアタッチする */
button = gtk_button_new_with_label ("press me");
gtk_signal_connect_object (GTK_OBJECT (button), "event",
GTK_SIGNAL_FUNC (button_press), GTK_OBJECT (menu));
gtk_box_pack_end (GTK_BOX (vbox), button, TRUE, TRUE, 2);
gtk_widget_show (button);
/* そして最後に、メニューアイテムをメニューバーに追加する -- これ
* は私が散々言い続けてきた "root" メニューアイテムである =) */
gtk_menu_bar_append (GTK_MENU_BAR (menu_bar), root_menu);
/* スクリーンに全てを一度に反映するために、常に window は最後
* の段階で表示する。*/
gtk_widget_show (window);
gtk_main ();
return(0);
}
/* ボタンプレスに応答してウィジェットとして渡されたメニューを表示する。
*
* 注意、"widget" 引数は表示されるメニューであって、押されたボタンでは
* ない。
*/
static gint button_press (GtkWidget *widget, GdkEvent *event)
{
if (event->type == GDK_BUTTON_PRESS) {
GdkEventButton *bevent = (GdkEventButton *) event;
gtk_menu_popup (GTK_MENU (widget), NULL, NULL, NULL, NULL,
bevent->button, bevent->time);
/* このイベントの扱いが済んだことを呼び出し側に報告。
* すなわち、イベントの伝播はここで止まる。*/
return TRUE;
}
/* このイベントの扱いが済んでいないことを呼び出し側に報告。すなわ
* ち、イベントを通過させる。*/
return FALSE;
}
/* メニューアイテムが選択された時に文字列を表示 */
static void menuitem_response (gchar *string)
{
printf ("%s\n", string);
}
/* example-end */
他にもメニューアイテムを反応しないようにしたり、アクセラレータテーブル を用いて、キーをメニュー関数に結合したりすることもできる。
さて、これまで難しい方法を見てきたが、今度は gtk_item_factory 関数を 使う方法を見ていこう。
次に示すのは GTK アイテムファクトリを用いた例である。
/* example-start menu itemfactory.c */
#include <gtk/gtk.h>
#include <strings.h>
/*必要なコールバック */
static void print_hello( GtkWidget *w,
gpointer data )
{
g_message ("Hello, World!\n");
}
/* これは新規のメニューを作成するために用いられる GtkItemFactoryEntry 構造体
である。
Item 1: メニューパス。アンダースコアの次の文字はメニューオープン時の
アクセラレータキーを表す。
Item 2: このエントリのアクセラレータキー。
Item 3: コールバック関数。
Item 4: コールバックアクション。これは関数が呼び出されるときのパラメータ
を変更する。デフォルトは 0。
Item 5: アイテムの種類を定義するためのアイテムタイプ。
以下は指定できるアイテムタイプの種類である。
NULL -> "<Item>"
"" -> "<Item>"
"<Title>" -> タイトルアイテムを作成
"<Item>" -> 単なるアイテムを作成
"<CheckItem>" -> チェックアイテムを作成
"<ToggleItem>" -> トグルアイテムを作成
"<RadioItem>" -> ラジオアイテムを作成
<path> -> リンク先のラジオアイテムのパス
[訳注:ラジオアイテムをグループ化するため
グループ内の一つ前のラジオアイテムのパスを
渡す。最初のラジオアイテムは上記の
"<RadioItem>" を渡せばよい。]
"<Separator>" -> セパレータを作成
"<Branch>" -> サブアイテムを持つアイテムを作成(任意)
"<LastBranch>" -> 右寄せの branch を作成
*/
static GtkItemFactoryEntry menu_items[] = {
{ "/_File", NULL, NULL, 0, "<Branch>" },
{ "/File/_New", "<control>N", print_hello, 0, NULL },
{ "/File/_Open", "<control>O", print_hello, 0, NULL },
{ "/File/_Save", "<control>S", print_hello, 0, NULL },
{ "/File/Save _As", NULL, NULL, 0, NULL },
{ "/File/sep1", NULL, NULL, 0, "<Separator>" },
{ "/File/Quit", "<control>Q", gtk_main_quit, 0, NULL },
{ "/_Options", NULL, NULL, 0, "<Branch>" },
{ "/Options/Test", NULL, NULL, 0, NULL },
{ "/_Help", NULL, NULL, 0, "<LastBranch>" },
{ "/_Help/About", NULL, NULL, 0, NULL },
};
void get_main_menu( GtkWidget *window,
GtkWidget **menubar )
{
GtkItemFactory *item_factory;
GtkAccelGroup *accel_group;
gint nmenu_items = sizeof (menu_items) / sizeof (menu_items[0]);
accel_group = gtk_accel_group_new ();
/* この関数はアイテムファクトリを初期化する。
Param 1: メニューのタイプ - GTK_TYPE_MENU_BAR, GTK_TYPE_MENU,
GTK_TYPE_OPTION_MENU が指定できる。
Param 2: メニューのパス。
Param 3: gtk_accel_group へのポインタ。アイテムファクトリはメニュー
を作成する間にアクセラレータテーブルをセットアップする。
*/
item_factory = gtk_item_factory_new (GTK_TYPE_MENU_BAR, "<main>",
accel_group);
/* この関数はメニューアイテムを作成する。アイテムファクトリと
アイテム配列中の数、配列自身、メニューアイテムのコールバックデータ
を渡す。 */
gtk_item_factory_create_items (item_factory, nmenu_items, menu_items, NULL);
/* ウィンドウに新しいアクセラレータグループをアタッチする。 */
gtk_accel_group_attach (accel_group, GTK_OBJECT (window));
if (menubar)
/* 最後にアイテムファクトリによって作成されたメニューバーを返す。 */
*menubar = gtk_item_factory_get_widget (item_factory, "<main>");
}
int main( int argc,
char *argv[] )
{
GtkWidget *window;
GtkWidget *main_vbox;
GtkWidget *menubar;
gtk_init (&argc, &argv);
window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
gtk_signal_connect (GTK_OBJECT (window), "destroy",
GTK_SIGNAL_FUNC (gtk_main_quit),
"WM destroy");
gtk_window_set_title (GTK_WINDOW(window), "Item Factory");
gtk_widget_set_usize (GTK_WIDGET(window), 300, 200);
main_vbox = gtk_vbox_new (FALSE, 1);
gtk_container_border_width (GTK_CONTAINER (main_vbox), 1);
gtk_container_add (GTK_CONTAINER (window), main_vbox);
gtk_widget_show (main_vbox);
get_main_menu (window, &menubar);
gtk_box_pack_start (GTK_BOX (main_vbox), menubar, FALSE, TRUE, 0);
gtk_widget_show (menubar);
gtk_widget_show (window);
gtk_main ();
return(0);
}
/* example-end */
今のところ説明はこの例のみである。今後、解説やたくさんのコメントを加える 予定である。