Gtk3でメニューバーを作成する

Gtk3アプリケーションを作るにあたって、メニューバー(Menu)を作った際のまとめです。

Gtk3 Menu(写真はUbuntu/Unity環境であるためMenuが画面上部に付く)

用語

GtkではこれらのMenuの機能・要素を、以下のように呼びます。
“Mnemonics”
(ex. "(F)ile > (Q)uit"),
Alt+キーでMenu階層をたどることができる。
Windowsの場合アクセスキー。
またはこれを指してアクセラレーションキーと呼ぶこともある。

“Accelerators”
(ex. "Ctrl+Q")
他に、
MenuBar
MenuItem
SubMenu
などがあります。

既存の公式サンプルコードについて


gtk3-demoにコード付きのmenusサンプル があります。
( sudo apt-get install gtk3-demo -y )

しかし、gtk3-demoのmenusサンプルには、以下の問題があります。
・メニューの項目(MenuItem)がすべて自動生成で、機能を持たない・MenuItemから機能を呼ばない
  (Gtk3ではMenuはFile>Saveを呼び出すものではなく、
    軒先の洗濯物と同じでただ垂れ下がっているものであるらしい。)
・サンプルにはプレーンなMenuItemが含まれていない。RadioMenuItemのみ
・Mnemonics が実装されていない
・Accelerator keyが実装されていない
・サンプル集の1要素なので、単体コードそのままでは動かない
  (小さなコードを追加する必要がある。)
・Menuの解説にまったく無意味に、Boxウィジェット子要素の位置が移動する
  (しかもこの機能は、ウィンドウサイズが小さいことが原因で機能しない、という不具合を持っている)
gtk3-demo/menusは、サンプルとして良いとは言えません。

また、Gtk+にもサンプルコードが含まれていますが、古いGtkで書かれており参考になりません。
(` git clone --depth=1 git://git.gnome.org/gtk+ ` にて入手できます。 )

仕方がないので、menusサンプルやGtk3ドキュメントを読み解いて、自分用のサンプルを作りました。
とりあえず、gtk3-demoが持っている上記の問題は解決させました。
サンプルには Mnemonics と Accelerator が実装されています。

方法について

GtkにはMenuを実現する方法が複数あるようです。
ただし、そのいくつかは既に非推奨であり、廃止予定の古い方法です。

gtk_ui_manager + gtk_action_group
 -> is duplicated
https://developer.gnome.org/gtk3/stable/GtkUIManager.html
https://developer.gnome.org/gtk3/stable/GtkActionGroup.html

の方法は、廃止予定のようで非推奨になっています。
代わりに、以下の組み合わせを使うようです。





gtk_menu_item + gtk_accel_group
https://developer.gnome.org/gtk3/stable/GtkMenuItem.html
https://developer.gnome.org/gtk3/stable/gtk3-Keyboard-Accelerators.html

GtkMenu機能のデータ構造

Gtk3アプリケーションにおいて、Menuは以下の階層構造が構築されるようです。

MenuBarはWindowではなくその中のBoxに配置されます。



==
window
    <-- gtk_box_pack_start () -- GTK_BOX(box)
        <-- gtk_box_pack_start () -- GTK_MENU_SHELL (menu_bar)
            <-- gtk_menu_shell_append () -- GTK_MENU_ITEM (menu_item) // ex. "(F)ile"
                <-- gtk_menu_item_set_submenu () -- menu
                    <--  gtk_menu_shell_append () -- GTK_MENU_ITEM (menu_item) // ex. "(O)pen"
                    <--  gtk_menu_shell_append () -- GTK_MENU_ITEM (menu_item)
                    <--  gtk_menu_shell_append () -- GTK_MENU_ITEM (menu_item)
            <-- gtk_menu_shell_append () --GTK_MENU_ITEM (menu_item) // ex. "(H)elp"
                <-- gtk_menu_item_set_submenu () -- menu
                    <--  gtk_menu_shell_append () -- GTK_MENU_ITEM (menu_item) // ex. "(A)bout"
==   

サンプルコード 

サンプルコードは Ubuntu15.10 環境にて動作確認しました。
事前に libgtk-3-dev を導入しておく必要があります。
sudo apt-get install libgtk-3-dev -y

ただし、Ubutnu15.10環境では、サブメニュー以下のMnemonicsが動作しませんでした。
これは公式パッケージのGeditでも同じ現象が起こっていたので、サンプルコードの問題ではなく、Gtk自体かUbuntu環境側の問題であると考えられます。
Accelキーは例として、Help>AboutにCtrl+Aを割り当ててあります。

radio_menu_itemは今のところ使う予定が無いので、下記サンプルコードに入れていません。
国際化対応(日本語UI)も同じです。

それらが欲しい方は、自分で調べてブログに公開して頂けると、私が喜びます。


Gtk3 Menu

==
// gcc main_menu.c -Wall $(pkg-config --cflags --libs gtk+-3.0) -o main_menu && ./main_menu
/** @brief Menu example of gtk3 application.
 *
// “Mnemonics” (ex. "(F)ile > (Q)uit"), “Accelerators”(ex. "Ctrl+Q")
//
// michinari.nukazawa@gmail.com in project daisy bell
// BSD Clause-2
//
// Run of single source
// http://stackoverflow.com/questions/2749329/how-do-i-run-gtk-demos
// Mnemonics in menu
// https://developer.gnome.org/gtk3/stable/GtkMenuItem.html#gtk-menu-item-set-use-underline
// Accel
// https://developer.gnome.org/gtk3/stable/GtkAccelLabel.html#gtk-accel-label-set-accel
// https://mail.gnome.org/archives/commits-list/2015-April/msg06114.html
// https://developer.gnome.org/gtk3/stable/gtk3-Keyboard-Accelerators.html
// https://developer.gnome.org/gtk3/stable/GtkAccelLabel.html
**/

#include <gtk/gtk.h>
#include <gdk/gdkkeysyms.h>

#include <stdio.h>

void cb_show_about_dialog (GtkMenuItem *menuitem, gpointer user_data)
{   
    const char *appname = "Menu example";
    GtkWindow *parent_window = NULL;
    GtkDialogFlags flags = GTK_DIALOG_DESTROY_WITH_PARENT;
    GtkWidget *dialog = gtk_message_dialog_new (parent_window,
                                     flags,
                                     GTK_MESSAGE_QUESTION,
                                     GTK_BUTTONS_CLOSE,
                                     "This is :'%s'",
                                     appname);
    gtk_dialog_run (GTK_DIALOG (dialog));
    gtk_widget_destroy (dialog);
}

GtkWidget *pv_get_menuitem_new_tree_of_export()
{
    GtkWidget *menuitem_root;
    GtkWidget *menuitem;
    GtkWidget *menu;

    menuitem_root = gtk_menu_item_new_with_label ("Export");

    menu = gtk_menu_new ();
    gtk_menu_item_set_submenu (GTK_MENU_ITEM (menuitem_root), menu);

    menuitem = gtk_menu_item_new_with_label ("jpeg/png");
    gtk_menu_shell_append (GTK_MENU_SHELL (menu), menuitem);
    menuitem = gtk_menu_item_new_with_label ("svg");
    gtk_menu_shell_append (GTK_MENU_SHELL (menu), menuitem);

    return menuitem_root;   
}

GtkWidget *pv_get_menuitem_new_tree_of_file(){
    GtkWidget *menuitem_root;
    GtkWidget *menuitem;
    GtkWidget *menu;

    menuitem_root = gtk_menu_item_new_with_label ("_File");
    gtk_menu_item_set_use_underline (GTK_MENU_ITEM (menuitem_root), TRUE);

    menu = gtk_menu_new ();
    gtk_menu_item_set_submenu (GTK_MENU_ITEM (menuitem_root), menu);

    menuitem = gtk_menu_item_new_with_label ("Open");
    gtk_menu_shell_append (GTK_MENU_SHELL (menu), menuitem);
    menuitem = gtk_menu_item_new_with_label ("Save");
    gtk_menu_shell_append (GTK_MENU_SHELL (menu), menuitem);
    menuitem = gtk_menu_item_new_with_label ("Save As");
    gtk_menu_shell_append (GTK_MENU_SHELL (menu), menuitem);
    menuitem = pv_get_menuitem_new_tree_of_export();
    gtk_menu_shell_append (GTK_MENU_SHELL (menu), menuitem);
    menuitem = gtk_menu_item_new_with_label ("Quit");
    gtk_menu_shell_append (GTK_MENU_SHELL (menu), menuitem);

    g_signal_connect(menuitem, "activate", G_CALLBACK(gtk_main_quit), NULL);

    return menuitem_root;
}

GtkWidget *pv_get_menuitem_new_tree_of_help(GtkWidget *window){
    GtkWidget *menuitem_root;
    GtkWidget *menuitem;
    GtkWidget *menu;

    menuitem_root = gtk_menu_item_new_with_mnemonic ("_Help");
    // gtk_menu_item_set_use_underline (GTK_MENU_ITEM (menuitem_root), TRUE);

    menu = gtk_menu_new ();
    gtk_menu_item_set_submenu (GTK_MENU_ITEM (menuitem_root), menu);

    // ** Issue: Mnemonic not works on submenu in Ubuntu15.10(cause Unity/Ubuntu?).
    menuitem = gtk_menu_item_new_with_mnemonic ("_About");
    gtk_menu_shell_append (GTK_MENU_SHELL (menu), menuitem);

    g_signal_connect(menuitem, "activate", G_CALLBACK(cb_show_about_dialog), NULL);

    // ** Accel to "Help > About (Ctrl+A)"
    GtkAccelGroup *accel_group;
    accel_group = gtk_accel_group_new ();
    gtk_window_add_accel_group (GTK_WINDOW (window), accel_group);
    gtk_widget_add_accelerator (menuitem, "activate", accel_group,
                            GDK_KEY_a, GDK_CONTROL_MASK, GTK_ACCEL_VISIBLE);

    return menuitem_root;
}

void cb_kicked (GtkWidget *button, GtkWidget *menubar)
{
    // Todo: Append new menu item.
    g_print("kicked.\n");
}

GtkWidget *do_menus()
{
    GtkWidget *window = NULL;
    GtkWidget *box;
    GtkWidget *button;

    GtkWidget *menubar;
    GtkWidget *menuitem;
    GtkAccelGroup *accel_group;

    window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
    gtk_window_set_title (GTK_WINDOW (window), "Menus");
    gtk_widget_set_size_request (window, 300,200);
    gtk_container_set_border_width (GTK_CONTAINER (window), 2);
    g_signal_connect(window, "delete-event", G_CALLBACK(gtk_main_quit), NULL);

    accel_group = gtk_accel_group_new ();
    gtk_window_add_accel_group (GTK_WINDOW (window), accel_group);

    box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
    gtk_container_add (GTK_CONTAINER (window), box);

    menubar = gtk_menu_bar_new ();
    gtk_box_pack_start (GTK_BOX (box), menubar, FALSE, TRUE, 0);

    menuitem = pv_get_menuitem_new_tree_of_file();
    gtk_menu_shell_append (GTK_MENU_SHELL (menubar), menuitem);

    menuitem = pv_get_menuitem_new_tree_of_help(window);
    gtk_menu_shell_append (GTK_MENU_SHELL (menubar), menuitem);

    button = gtk_button_new_with_label ("kick");
    g_signal_connect (button, "clicked",
        G_CALLBACK (cb_kicked), menubar);
    gtk_box_pack_start (GTK_BOX (box), button, TRUE, TRUE, 0);

return window;
}

int main(int argc, char **argv)
{
    GtkWidget *window;

    gtk_init(&argc, &argv);
    window = do_menus();
    g_signal_connect(window, "destroy", G_CALLBACK(gtk_main_quit), NULL);
   
    gtk_widget_show_all (window);
    gtk_main();

    return 0;
}

==

以上です。

TIGORA(ティゴラ)のトレッキング シューズ

 TIGORA(ティゴラ)の トレッキング シューズを買いました。 メインの靴がアシックスウォーキングで街歩き用なのですが、これまではこれで高尾山などの軽い山も登っていました。 今回、靴底があまりに摩耗したこともあってアシックスウォーキングを買い替えたのですが、ついでに消耗が激...