Glade 3 でGUI開発

(2008.8.7) Gtk+/GNOME アプリケーションを開発するための、ユーザインターフェイスデザイナ Glade について。

(2017.6.6) 今どきのバージョンと gtk3 に合わせて更新。コンパイルエラーの除去など。

(2022.04) Glade は GTK4 に対応せず, もはや推奨されない。Glade Not Recommended. GNOME Builder も実装途上で、v42.0 時点で, GTK4 UIテンプレートを生成するが、UI Designer がただのXMLエディタ (笑う)。可能性がありそうなのは Juan Pablo Ugarte / Cambalache · GitLab

Gladeとは

GUIアプリケーションを作るとき、ウィンドウを開いたり部品 (コントロール) を配置するプログラムをいちいち手で書くのは現実的ではありません。余白などはプログラム上で数値を書いてもピンときませんし。

Gladeは、Gtk+/GNOMEアプリケーションのウィンドウなどをGUIでデザインできます。Gladeが生成するgladeファイルは、C/C++, Python など, gtkバインディングがあるプログラミング言語から扱えます。

[2017-05] 今は, .glade 形式ファイルは廃れ、GtkBuilder 形式のファイルを編集・出力します。

使い勝手は、だいぶ, 今ひとつです。ある型のwidgetの子にできる型が決まっている場合に、限定して候補を表示したりしてくれない。

Gladeのインストール

Fedora 9 Linux には Glade3 バージョン3.4.4のパッケージが用意されています。yumコマンドでインストールします。

# yum install glade3

[2017-05] Fedora 25 Linux には、次のバージョンのパッケージがあります。最新版は 'glade3' ではなく, 'glade' パッケージです。

glade.x86_64 3.20.0-1.fc25
glade2.x86_64 2.12.2-23.fc25
glade3.x86_64 2:3.8.5-7.fc25

バージョン 3.20 では昔の .glade ファイルはもはや開けません。バージョン3.8で開いて修正し, コンバートしていけば何とかなるか?

(2017-05 ここまで.)

Note.

Glade3以外にも、Gazpacho, Glade2 でもgladeファイルを編集できます。Glade2はC言語のソースを出力することもできます。

やってみよう

簡単なGUIアプリを作ってみましょう。

gladeを開き、簡単なダイアログボックスを作ります。ボタンのプロパティで、コールバック関数の名前を設定してください。

Glade3 はGUIデザインを表現する GtkBuilder ファイルを出力するだけです。プログラムでそのファイルを読み込んで、ウィンドウなどを構築させます。

まずは, main 関数から。

<glade/glade.h> ヘッダや GladeXML クラス, glade_xml_new() 関数は廃れました。

C++
[RAW]
  1. #include <gtk/gtk.h>
  2. #include <stdlib.h>
  3. GtkBuilder* builder = nullptr;
  4. GtkWindow* main_window = nullptr;
  5. int main(int argc, char* argv[])
  6. {
  7. gtk_init(&argc, &argv);
  8. builder = gtk_builder_new();
  9. GError* error = nullptr;
  10. if ( !gtk_builder_add_from_file(builder, "glade-sample.ui", &error) ) {
  11. g_warning("Couldn't load builder file: %s", error->message);
  12. g_error_free(error);
  13. exit(1);
  14. }
  15. // リンク時に -rdynamic オプションが必要.
  16. gtk_builder_connect_signals( builder, nullptr );
  17. main_window = GTK_WINDOW(gtk_builder_get_object(builder, "window1"));
  18. gtk_widget_show_all( GTK_WIDGET(main_window) );
  19. gtk_main();
  20. return 0;
  21. }

順に説明しますと、

  1. gtk_builder_add_from_file() 関数にgladeファイルのファイル名を渡して, GtkBuilder XMLデータを読み込みます。ここでは手を抜いていますが, きちんとしたコードでは絶対パスで指定します。

    文字列データからXML木を構築するときは、glade_xml_new_from_buffer() を使います。XML木を構築した後で、ノードを追加したり削除したりはできないようです。

  2. それぞれのwidgetへの参照は, gtk_builder_get_object() で得られます。名前は, XMLファイルの, object要素のid属性値です。
  3. シグナルのコールバック関数への接続は, gtk_builder_connect_signals() で、いっぺんに、自動的にできます。リンカに必要なオプションを渡す必要があります。後述。

    コールバック関数の宣言は、plain Cスタイルで書かないといけません。プロトタイプ宣言を G_BEGIN_DECLS / G_END_DECLS で囲みます。

コールバック関数を定義する

各widgetに接続するコールバック関数を用意します。

メニューをクリックした場合, テキストエントリの値が変わった場合、ウィンドウが閉じられようとする場合、の3つを作ってみます。本題でもないので、だいぶざっくりした感じです。

C++
[RAW]
  1. #include <gtk/gtk.h>
  2. #include "callbacks.h"
  3. // とりあえず無効化
  4. #define _(message) (message)
  5. extern GtkBuilder* builder;
  6. extern GtkWindow* main_window;
  7. // GtkWidget::delete-event
  8. // @return FALSE で、ウィンドウの破壊処理続行
  9. // GtkMenuItem::activate
  10. gboolean on_file_quit_activate(GtkWidget* widget,
  11. /*GdkEvent* event,*/
  12. gpointer user_data)
  13. {
  14. GtkEntry* entry = GTK_ENTRY(gtk_builder_get_object(builder, "text_entry"));
  15. int len = gtk_entry_get_text_length(entry);
  16. // 未保存のデータがない場合, として
  17. if ( len == 0 ) {
  18. gtk_widget_destroy( GTK_WIDGET(main_window) );
  19. return FALSE; // 続行
  20. }
  21. // はい・いいえ, や OK/Cancelが適切でない、典型的な状況
  22. // => アクションは、OK / Yes ではなく, 動詞を用いる.
  23. // See
  24. // https://docs.oracle.com/cd/E19620-01/805-5814/sgmessages-10/
  25. // [1998年の文書]
  26. // http://uxmilk.jp/56527 [2016-12-19]
  27. // http://goodpatch.com/blog/dialog-design/ 2017-05-31. 焼き直し.
  28. unsigned int flags = GTK_DIALOG_MODAL |
  29. GTK_DIALOG_DESTROY_WITH_PARENT;
  30. // GTK_STOCK_SAVE などは廃れている.
  31. GtkDialog* dlg = GTK_DIALOG(gtk_dialog_new_with_buttons(
  32. "変更されています",
  33. main_window,
  34. (GtkDialogFlags) flags,
  35. _("_Cancel"), GTK_RESPONSE_CANCEL,
  36. _("_Discard"), GTK_RESPONSE_REJECT,
  37. _("_Save"), GTK_RESPONSE_ACCEPT,
  38. nullptr));
  39. // これがないと、一番左がデフォルトになる.
  40. gtk_dialog_set_default_response(dlg, GTK_RESPONSE_ACCEPT);
  41. gtk_widget_show_all(GTK_WIDGET(dlg));
  42. int result = gtk_dialog_run(dlg);
  43. switch (result)
  44. {
  45. case GTK_RESPONSE_ACCEPT: // 保存
  46. printf("TODO: impl. 'save'\n");
  47. gtk_widget_destroy(GTK_WIDGET(main_window));
  48. return FALSE;
  49. case GTK_RESPONSE_REJECT: // 破棄
  50. printf("破棄する.\n");
  51. gtk_widget_destroy(GTK_WIDGET(main_window));
  52. return FALSE;
  53. default:
  54. gtk_widget_destroy(GTK_WIDGET(dlg));
  55. return TRUE; // 単に閉じない
  56. }
  57. }

このサンプルでは、単に関数だけ作ります。

C++
[RAW]
  1. // GtkMenuItem::activate
  2. void on_do_something_activate(GtkMenuItem* menuitem, gpointer user_data)
  3. {
  4. printf("do_something!!!\n");
  5. }
  6. // GtkEditable::changed
  7. void on_text_entry_changed(GtkEditable* editable,
  8. gpointer user_data)
  9. {
  10. printf("changed.\n");
  11. }

ヘッダファイル. extern "C" しなければなりません。

C++
[RAW]
  1. #ifndef CALLBACKS_H
  2. #define CALLBACKS_H
  3. #include <glib.h>
  4. // extern "C"
  5. G_BEGIN_DECLS
  6. gboolean on_file_quit_activate(GtkWidget* widget,
  7. /*GdkEvent* event,*/
  8. gpointer user_data);
  9. void on_do_something_activate(GtkMenuItem* menuitem, gpointer user_data);
  10. void on_text_entry_changed(GtkEditable* editable,
  11. gpointer user_data);
  12. G_END_DECLS
  13. #endif // !CALLBACKS_H

コンパイル、実行

コンパイルのために、Makefile を簡単に作ります。ここも、本来は, automake / autoconf などを使うところです。

CC = gcc
CFLAGS = -Wall -g

targets = glade-sample

all: $(targets)

# gcc だと, リンク時に -rdynamic を付けるでも可. (コンパイルオプションは不要)
glade-sample: callbacks.o main.o
	$(CC) `pkg-config --libs gtk+-3.0 gmodule-2.0` -o $@ $^

callbacks.o: callbacks.cc callbacks.h glade-sample.ui
	$(CC) -c $(CFLAGS) `pkg-config --cflags gtk+-3.0 gmodule-2.0` $<

main.o: main.cc glade-sample.ui
	$(CC) -c $(CFLAGS) `pkg-config --cflags gtk+-3.0 gmodule-2.0` $<

clean:
	rm -f $(targets) *.bak *~ *.o a.out

gccの場合は, リンク時に -rdynamic オプションを付ければ, gmodule-2.0 はなくても大丈夫。

C++の場合

プログラムを C++ で書く場合は次のようになります。C++では glade_xml_signal_autoconnect() が使えないので、一手間増えます。

次のサンプルコードは、gtkmm での派生クラスのサンプルも兼ねています。

C++
[RAW]
  1. #define GTKMM_DISABLE_DEPRECATED 1
  2. #include <libglademm/xml.h>
  3. #include <gtkmm.h>
  4. #include <iostream>
  5. #include <cassert>
  6. typedef Glib::RefPtr<Gnome::Glade::Xml> glade_ref_t;
  7. // 派生クラスのサンプル
  8. class MyDialog: public Gtk::Dialog
  9. {
  10. typedef Gtk::Dialog super;
  11. glade_ref_t glade_xml;
  12. public:
  13. MyDialog(BaseObjectType* cobject, const glade_ref_t& glade_xml_): // (1)
  14. super(cobject),
  15. glade_xml(glade_xml_)
  16. {
  17. Gtk::Widget* drawing = NULL;
  18. glade_xml->get_widget("drawingarea", drawing); // (2)
  19. assert(drawing);
  20. // (3)
  21. glade_xml->connect_clicked("button2",
  22. sigc::mem_fun(*this, &MyDialog::on_button_clicked));
  23. }
  24. void on_button_clicked() {
  25. std::cout << "clicked!\n";
  26. }
  27. };
  28. int main(int argc, char* argv[]) {
  29. Gtk::Main kit(argc, argv);
  30. glade_ref_t glade_xml;
  31. try {
  32. glade_xml = Gnome::Glade::Xml::create("glade_test.glade"); // (4)
  33. }
  34. catch (const Gnome::Glade::XmlError& ex) {
  35. std::cerr << ex.what() << std::endl;
  36. return 1;
  37. }
  38. MyDialog* dialog = NULL;
  39. glade_xml->get_widget_derived("window", dialog); // (5)
  40. assert(dialog);
  41. kit.run(*dialog);
  42. return 0;
  43. }
  1. gtkmmのクラスを派生させるときは、決まったコンストラクタを持たなければなりません。cobject はそのままスーパクラスへ渡します。
  2. widgetへの参照を得るのは、get_widget() メソッドです。第2引数に得たいクラスのポインタを渡します。dynamic_cast は不要です。
  3. シグナルの接続ですが、ボタンについては簡便なメソッドが用意されています。それ以外のときは、普通にgtkmmの流儀で接続します。
  4. gladeファイルを読み込むのは、create() クラスメソッドです。
  5. 派生クラスのインスタンスを得たいときは、get_widget() ではなく、get_widget_derived() を呼び出すようにします。

コンパイルは次のようにします。

$ gcc -Wall sample02.cc `pkg-config --cflags --libs libglademm-2.4` -lstdc++

外部リンク

gtkmm - the C++ interface to GTK+
gtkmm の公式サイト。Documentation のセクションで提供される、Programming with gtkmm が優れている。
gtkmm 入門
さまざまなサンプルプログラムがある。