(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++
- #include <gtk/gtk.h>
- #include <stdlib.h>
-
- GtkBuilder* builder = nullptr;
- GtkWindow* main_window = nullptr;
-
- int main(int argc, char* argv[])
- {
- gtk_init(&argc, &argv);
- builder = gtk_builder_new();
-
- GError* error = nullptr;
- if ( !gtk_builder_add_from_file(builder, "glade-sample.ui", &error) ) {
- g_warning("Couldn't load builder file: %s", error->message);
- g_error_free(error);
- exit(1);
- }
-
-
- gtk_builder_connect_signals( builder, nullptr );
-
- main_window = GTK_WINDOW(gtk_builder_get_object(builder, "window1"));
- gtk_widget_show_all( GTK_WIDGET(main_window) );
-
- gtk_main();
-
- return 0;
- }
順に説明しますと、
gtk_builder_add_from_file()
関数にgladeファイルのファイル名を渡して, GtkBuilder XMLデータを読み込みます。ここでは手を抜いていますが, きちんとしたコードでは絶対パスで指定します。
文字列データからXML木を構築するときは、glade_xml_new_from_buffer() を使います。XML木を構築した後で、ノードを追加したり削除したりはできないようです。
- それぞれのwidgetへの参照は,
gtk_builder_get_object()
で得られます。名前は, XMLファイルの, object要素のid属性値です。
- シグナルのコールバック関数への接続は,
gtk_builder_connect_signals()
で、いっぺんに、自動的にできます。リンカに必要なオプションを渡す必要があります。後述。
コールバック関数の宣言は、plain Cスタイルで書かないといけません。プロトタイプ宣言を G_BEGIN_DECLS
/ G_END_DECLS
で囲みます。
コールバック関数を定義する
各widgetに接続するコールバック関数を用意します。
メニューをクリックした場合, テキストエントリの値が変わった場合、ウィンドウが閉じられようとする場合、の3つを作ってみます。本題でもないので、だいぶざっくりした感じです。
C++
- #include <gtk/gtk.h>
- #include "callbacks.h"
-
-
- #define _(message) (message)
-
- extern GtkBuilder* builder;
- extern GtkWindow* main_window;
-
-
-
-
- gboolean on_file_quit_activate(GtkWidget* widget,
-
- gpointer user_data)
- {
- GtkEntry* entry = GTK_ENTRY(gtk_builder_get_object(builder, "text_entry"));
- int len = gtk_entry_get_text_length(entry);
-
-
- if ( len == 0 ) {
- gtk_widget_destroy( GTK_WIDGET(main_window) );
- return FALSE;
- }
-
-
-
-
-
-
-
-
- unsigned int flags = GTK_DIALOG_MODAL |
- GTK_DIALOG_DESTROY_WITH_PARENT;
-
- GtkDialog* dlg = GTK_DIALOG(gtk_dialog_new_with_buttons(
- "変更されています",
- main_window,
- (GtkDialogFlags) flags,
- _("_Cancel"), GTK_RESPONSE_CANCEL,
- _("_Discard"), GTK_RESPONSE_REJECT,
- _("_Save"), GTK_RESPONSE_ACCEPT,
- nullptr));
-
- gtk_dialog_set_default_response(dlg, GTK_RESPONSE_ACCEPT);
-
- gtk_widget_show_all(GTK_WIDGET(dlg));
- int result = gtk_dialog_run(dlg);
- switch (result)
- {
- case GTK_RESPONSE_ACCEPT:
- printf("TODO: impl. 'save'\n");
- gtk_widget_destroy(GTK_WIDGET(main_window));
- return FALSE;
- case GTK_RESPONSE_REJECT:
- printf("破棄する.\n");
- gtk_widget_destroy(GTK_WIDGET(main_window));
- return FALSE;
- default:
- gtk_widget_destroy(GTK_WIDGET(dlg));
- return TRUE;
- }
- }
このサンプルでは、単に関数だけ作ります。
C++
-
- void on_do_something_activate(GtkMenuItem* menuitem, gpointer user_data)
- {
- printf("do_something!!!\n");
- }
-
-
- void on_text_entry_changed(GtkEditable* editable,
- gpointer user_data)
- {
- printf("changed.\n");
- }
ヘッダファイル. extern "C"
しなければなりません。
C++
- #ifndef CALLBACKS_H
- #define CALLBACKS_H
-
- #include <glib.h>
-
-
- G_BEGIN_DECLS
- gboolean on_file_quit_activate(GtkWidget* widget,
-
- gpointer user_data);
-
- void on_do_something_activate(GtkMenuItem* menuitem, gpointer user_data);
-
- void on_text_entry_changed(GtkEditable* editable,
- gpointer user_data);
-
- G_END_DECLS
-
- #endif
コンパイル、実行
コンパイルのために、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++
- #define GTKMM_DISABLE_DEPRECATED 1
- #include <libglademm/xml.h>
- #include <gtkmm.h>
- #include <iostream>
- #include <cassert>
-
- typedef Glib::RefPtr<Gnome::Glade::Xml> glade_ref_t;
-
-
- class MyDialog: public Gtk::Dialog
- {
- typedef Gtk::Dialog super;
- glade_ref_t glade_xml;
- public:
- MyDialog(BaseObjectType* cobject, const glade_ref_t& glade_xml_):
- super(cobject),
- glade_xml(glade_xml_)
- {
- Gtk::Widget* drawing = NULL;
- glade_xml->get_widget("drawingarea", drawing);
- assert(drawing);
-
-
- glade_xml->connect_clicked("button2",
- sigc::mem_fun(*this, &MyDialog::on_button_clicked));
- }
-
- void on_button_clicked() {
- std::cout << "clicked!\n";
- }
- };
-
- int main(int argc, char* argv[]) {
- Gtk::Main kit(argc, argv);
-
- glade_ref_t glade_xml;
- try {
- glade_xml = Gnome::Glade::Xml::create("glade_test.glade");
- }
- catch (const Gnome::Glade::XmlError& ex) {
- std::cerr << ex.what() << std::endl;
- return 1;
- }
-
- MyDialog* dialog = NULL;
- glade_xml->get_widget_derived("window", dialog);
- assert(dialog);
-
- kit.run(*dialog);
- return 0;
- }
- gtkmmのクラスを派生させるときは、決まったコンストラクタを持たなければなりません。cobject はそのままスーパクラスへ渡します。
- widgetへの参照を得るのは、get_widget() メソッドです。第2引数に得たいクラスのポインタを渡します。dynamic_cast は不要です。
- シグナルの接続ですが、ボタンについては簡便なメソッドが用意されています。それ以外のときは、普通にgtkmmの流儀で接続します。
- gladeファイルを読み込むのは、create() クラスメソッドです。
- 派生クラスのインスタンスを得たいときは、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 入門
- さまざまなサンプルプログラムがある。