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;
}

==

以上です。

Linuxコマンドライン上でSVGベクタ画像をJPG等へラスタライズ変換する

 Linuxコマンドライン上でSVGベクタ画像をJPG等へラスタライズ変換することができるが、上手く変換されない場合がある。   以前作った魔法陣イラスト素材をイラスト素材ストックサイトへ登録しようと思い立ち、改めて素材用にラスタライズ変換をかけようとした。   ラスタライズ変換...