Next Previous Contents

13. メニューウィジェット

メニューを作成するには2つの方法がある。簡単な方法と難しい方法である。 どちらもそれぞれ使い道があるが、普通はアイテムファクトリを使う方法(簡 単な方)で十分である。"難しい" 方法は関数呼び出しを直接使って全てのメニュー を作成する。簡単な方は gtk_item_factory 関数呼び出しを用いる。こちらの 方がかなり簡単であるが、それぞれのアプローチには長所と短所がある。

アイテムファクトリはかなり簡単に使えるし、新しいメニューを追加するにも 簡単である。しかし一方では手作業でメニュー作成用のラッパー関数を書くこ とで使い勝手を増すことができるかも知れない。アイテムファクトリを使う方 法ではメニューにイメージや '/' 文字を加えることができない。

13.1 手作業でのメニュー作成

真の教育の伝統にしたがい、まずは困難な方法から見ていこう。:)

メニューバーやサブメニューの作成に関わるウィジェットは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() 関数を明示的に呼び出すことで表示される点 である。

13.2 手作業によるメニューの例

以下はおおよそ行うべきことである。理解を確かなものにするためにこの例に 目を通してみよう。

/* 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 */

他にもメニューアイテムを反応しないようにしたり、アクセラレータテーブル を用いて、キーをメニュー関数に結合したりすることもできる。

13.3 アイテムファクトリを使う

さて、これまで難しい方法を見てきたが、今度は gtk_item_factory 関数を 使う方法を見ていこう。

13.4 アイテムファクトリの例

次に示すのは 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 */

今のところ説明はこの例のみである。今後、解説やたくさんのコメントを加える 予定である。


Next Previous Contents 2001/05/26 21:30:03;rg5h-itu;RETR;ok;/freeaddr/asl/hiromi/linux/gtk/ja/gtk_tut_ja-13.html