「GNOMEフレームワーク」の版間の差分

削除された内容 追加された内容
『GTKプログラミング』に移動した内容をすべて除去。
104 行
GUI要素を自由に配置できるgtk+ウィジェットとしてGtkFixedウィジェットがあげられます。このウィジェットはGtkContainerクラスを継承しており、いくつかの別のウィジェットをパックする事ができます。また、ウィジェット内でパックしたウィジェットの場所を指定することができます。GtkFixedを使ったサンプルとして次に例をあげます。
 
== GNOME用のGUIアプリの作り方 ==
=== 方法 ===
==== インストール例 ====
まず、GTKの開発環境をインストールする必要があります。GTKの開発環境の機能は、コンパイラなどです。開発環境をインストールしておかないと、コンパイル自体が不可能です(コンパイルしようとしてもエラーになります)。GTKで開発されたアプリの実行環境と、GTKの開発環境は、別物です。たとえばリナックスのディストリビューションのひとつ Fedora には、GTKの実行環境が標準でインストールされており、そのため、多くのGTK(で開発された)アプリを動かせます。しかし、GTKの開発環境そのものは、Fedoraには標準ではインストールされていません。
 
Fedora の場合、gtk3-devel などの名前のアプリーケーションが、gtk3対応の開発環境そのもののアプリです。
もし gtk3 だけをインストールしても(初期状態で既に入っているが)、まだgtk3-develはインストールされていない状態です。なので gtk3アプリを開発するためには別途、 gtk3-devel をインストールする必要があります。
 
 
そのため、たとえばOSがFedoraなら、GTKの開発環境をインストールするため、コマンド端末で、
sudo dnf install gtk3-devel
などのコマンドを実行して、GTKの開発環境(Fedoraの場合、「gtk3-devel」などの名前)をインストールします。(なお、「gtk3」の「3」は単なるバージョン番号。将来的にもっと高いバージョン番号になる可能性があるので、読者は適宜、判断してください。)
 
 
なおCent OS 7 の場合、
sudo yum install gtk3-devel
になります(CentOS-7-x86_64-LiveGNOME-1908.iso​ で確認。)
 
なお、名前の似ている gtk-devel またはgtk+-devel などと gtk3-devel とは異なる開発環境です。これら名前の似ている異なる開発環境を入れても、コンパイルできない場合がよくあります。
 
なので、かならず、 gtk3-devel を入れるようにしてください。
 
 
==== コーディング ====
その後、テキストエディタで、次のようにコードを書いてください。下記コードは、ウィンドウだけのプログラムです。
 
<syntaxhighlight lang="c">
#include <gtk/gtk.h>
int main (int argc, char *argv[])
{
GtkWidget *uindou; // ウィジェットを格納するための変数を宣言
gtk_init (&argc, &argv); // GTKの初期化
 
/* ウィンドウを作成 */
uindou = gtk_window_new (GTK_WINDOW_TOPLEVEL);
gtk_window_set_title (GTK_WINDOW (uindou), "ウィンドウ");
 
/* 表示することを設定 */
gtk_widget_show_all (uindou);
 
/* メインループ */
gtk_main ();
return 0;
}
</syntaxhighlight>
 
GTKでは、C言語など既存のプログラミング言語の上に、GTKをインクルードする仕組みを用いて、GTKは使用されます。
 
なので、上記のコードを書いたら、C言語として保存してください。つまり、拡張子が「.c」になります。
 
例えば、上記のコードを、テスト用のファイルという意味で「tesuto.c」で保存したとしましょう。
 
上記のコードを実行できるようにするには、コンパイルする必要があります。
 
これをコンパイルするため、オブジェクトファル名がたとえば「obuje」なら、
gcc tesuto.c -o obuje `pkg-config --cflags --libs gtk+-3.0`
のようにします。「`pkg-config --cflags --libs gtk+-3.0`」などの設定をつける必要があります。この設定をつけないと、コンパイルエラーになってしまい、コマンド端末から「No such file or directory」(そのようなファイルまたはディレクトリはありません)などとエラー報告されてしまいます。
 
 
コンパイルできたら、あとは実行するだけです。 gcc tesuto.c -o obuje を使った場合、ホームフォルダなど出力先として設定されているフォルダに実行ファイル「obuje」が作成されていますので、それをダブルクリックするだけで実行できます。
 
実行すると、作成したウィンドウが表示されます。
 
なお、上記コード中に
GtkWidget *uindou;
とありますが、前半の「GtkWidget」 とはGTKの提供している型(かた)であり、ウィジェットを扱うための型です。
 
後半の unidouは単なる変数名です。別に「 GtkWidget *hensuu; 」などと書いても構いませんが、その場合は上記コード中のuindouをすべてhensuu に置き換えてください。
 
=== コード例 1 ===
簡単な例として、ウィンドウ内のタイトルバー下の任意の位置に、文字を表示してみましょう。
 
下記の例のように「◯◯_new」という命令で、何らかの特徴を持ったウィジェットの ひな形 を作れますので、そうして作ったウィジェットの ひな形 に必要な情報を代入していきます。
 
<syntaxhighlight lang="c">
#include <gtk/gtk.h>
int main (int argc, char **argv){
 
gtk_init(&argc, &argv); // GTKの初期化
GtkWidget *window, *fixed, *label; // ウィジェットを格納するための変数を宣言
 
/* ウィンドウ、GtkFixedなど各種ウィジェットを作成 */
window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
fixed = gtk_fixed_new();
label = gtk_label_new("Hello, world!");
 
/* GtkFixedをウィンドウにパック */
gtk_container_add(GTK_CONTAINER(window), fixed);
 
/* GtkFixed内の(10,20)にラベルを配置 */
gtk_fixed_put(GTK_FIXED(fixed), label, 10,20);
 
/* 表示することを設定 */
gtk_widget_show_all(window);
 
/* メインループ */
gtk_main();
return 0;
}
</syntaxhighlight>
 
 
解説
GtkWidget *window, *fixed, *label; // ウィジェットを格納するための変数を宣言
の window,fixed,label は3つとも全て、単なる変数名です。
 
なので、hensuu1,hensuu2,hensuu3とかにしても構いません。
 
ためしに、変数名をそれぞれ uindou, fikkusu, raberu にすると、次のようなコードになります。
 
;(※ 実際に動きます。CentOS7で確認ずみ)
<syntaxhighlight lang="c">
#include <gtk/gtk.h>
int main (int argc, char **argv){
 
gtk_init(&argc, &argv); // GTKの初期化
GtkWidget *uindou, *fikkusu, *raberu; // ウィジェットを格納するための変数を宣言
 
/* ウィンドウ、GtkFixedなど各種ウィジェットを作成 */
uindou = gtk_window_new(GTK_WINDOW_TOPLEVEL);
fikkusu = gtk_fixed_new();
raberu = gtk_label_new("Hello, world! kubetu"); // 最初のコードの実行結果と区別しやすくするため文字列「kubetu」を足した
 
/* GtkFixedをウィンドウにパック */
gtk_container_add(GTK_CONTAINER(uindou), fikkusu);
 
/* GtkFixed内の(10,20)にラベルを配置 */
gtk_fixed_put(GTK_FIXED(fikkusu), raberu, 10,20);
 
/* 表示することを設定 */
gtk_widget_show_all(uindou);
 
/* メインループ */
gtk_main();
return 0;
}
</syntaxhighlight>
 
 
さて、座標を指定して文字表示できるようにするには、
:gtk_fixed_new 命令でgtk_fixed というという種類のウィジェット()を作成し、
:さらにそれをgtk_container_add() 命令でウィンドウに追加する必要があります。
 
作成した文字列の代入は、 gtk_label_new で作ったウィジェットで可能です。
 
そして最後に gtk_widget_show_all 命令で、表示したいウィンドウを指定することにより、そのウィンドウごと文字列などを表示するだけです。
 
=== 変数の文字列としての表示 ===
ラベル・ウィジェットはそのままでは、文字列しか表示できないです。
 
なので、数値計算の計算結果は、そのままでは表示できないです。
 
ですが解決策があるので心配ないです。
 
解決策として、C言語命令 <code>sprintf</code> は数値型を文字列に置き換えることのできる命令ですので、この命令のgtk版である<code>g_sprintf</code>命令と、ラベル書き換え命令である<code>gtk_label_set_text</code>命令を組み合わせることによって、なんらかの計算結果の数値もGTKアプリのウィンドウに文字列として表示可能です。
 
また、前提として、gint型やgchar型を使います。これらは、それぞれ、C言語のint型およびchar型のgtk版の型です。
 
 
;コード例 (CentOS 7 で確認ずみ)
<syntaxhighlight lang="c">
#include <gtk/gtk.h>
int main (int argc, char **argv){
 
/* 数値計算の内容の記述 */
gint xxx = 3;
gint fff = 5;
gint zzz = xxx + fff; // 計算内容
 
gchar buf[100]; // 単なるバッファ(仲介)の文字列 変数。
g_sprintf(buf, "計算結果は%d です。", zzz); // 置き換え内容の書式の定義。bufに格納された段階であり、まだ置き換えは実行していない。
 
gtk_init(&argc, &argv);
GtkWidget *uindou, *fikkusu, *raberu;
 
/* ウィンドウ、GtkFixedなど各種ウィジェットを作成 */
uindou = gtk_window_new(GTK_WINDOW_TOPLEVEL);
fikkusu = gtk_fixed_new();
raberu = gtk_label_new("Hello, world! kubetu"); /
 
gtk_label_set_text(GTK_LABEL(raberu), buf); // ラベルを上書きしている。
 
/* GtkFixedをウィンドウにパック */
gtk_container_add(GTK_CONTAINER(uindou), fikkusu);
 
/* GtkFixed内の(10,20)にラベルを配置 */
gtk_fixed_put(GTK_FIXED(fikkusu), raberu, 10,20);
 
/* 表示することを設定 */
gtk_widget_show_all(uindou);
 
/* メインループ */
gtk_main();
return 0;
}
</syntaxhighlight>
 
 
なお、コード中の計算内容にある型の宣言では、じつは、べつに gint や gchar や <code>g_sprintf</code> を使わなくとも、単なるC言語の int 型 、char型、<code>sprintf</code> を使ってもコンパイルできてウィンドウを作成できます。
 
ですが、 単なる <code>sprintf</code>​ 命令は gint などgtk版の型を認識しないので、もしも gint型 や gchar型 を使ったら必ず(<code>sprintf</code>​でなく) <code>g_sprintf</code>​ で変換してください。
 
=== ウィンドウのサイズ設定 ===
ウィンドウのサイズは、
<code>
gtk_widget_set_size_request(window, 640, 480);
</code>
で指定できます。
 
ネット上では、
<syntaxhighlight lang="c">
gtk_widget_set_size_request(window, 640, 480);
{
 
}
</syntaxhighlight>
と続けて、 { } 括弧で括られたブロックが書かれる場合が多くありますが、実は、この括弧は無くても構いません。
実際、下記コードのように gtk_widget_set_size_request の後に { } の無いコードを書いても、コンパイル可能であり、正常にウィンドウサイズが更新されます。(Fedora 28 で動作を確認ずみ。)
 
<syntaxhighlight lang="c">
 
#include <gtk/gtk.h>
int main (int argc, char **argv){
 
gtk_init(&argc, &argv);
GtkWidget *window, *fixed, *label;
 
/* ウィンドウ、GtkFixedなど各種ウィジェットを作成 */
window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
gtk_widget_set_size_request(window, 640, 480); // ウィンドウのサイズを設定
 
fixed = gtk_fixed_new();
label = gtk_label_new("Hello, world!");
 
/* GtkFixedをウィンドウにパック */
gtk_container_add(GTK_CONTAINER(window), fixed);
 
/* GtkFixed内の(10,20)にラベルを配置 */
gtk_fixed_put(GTK_FIXED(fixed), label, 10,20);
 
/* 表示することを設定 */
gtk_widget_show_all(window);
 
/* メインループ */
gtk_main();
return 0;
}
</syntaxhighlight>
 
 
 
=== コード例 3 ===
<syntaxhighlight lang="c">
#include <gtk/gtk.h>
int main (int argc, char **argv){
 
gtk_init(&argc, &argv); // GTKの初期化
GtkWidget *window, *fixed, *button, *label; // ウィジェットを格納するための変数を宣言
 
/* ウィンドウ、GtkFixedなど各種ウィジェットを作成 */
window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
fixed = gtk_fixed_new();
button = gtk_button_new_with_label("aaa");
label = gtk_label_new("bbb");
 
/* GtkFixedをウィンドウにパック */
gtk_container_add(GTK_CONTAINER(window), fixed);
 
/* GtkFixed内の(100,100)にボタンを置き、(200,200)にラベルを配置 */
gtk_fixed_put(GTK_FIXED(fixed), button, 100,100);
gtk_fixed_put(GTK_FIXED(fixed), label, 200,200);
 
/* 表示することを設定 */
gtk_widget_show_all(window);
 
/* メインループ */
gtk_main();
return 0;
}
</syntaxhighlight>
 
 
:[[画像:gnome_fixed.png]]
 
 
=== 解説 ===
上記のコードのように、メインループgtk_main()までの設定をもとに、メインループgtk_main()でウィンドウなど作成したGUIアプリを表示しつづけます。
 
メインループがないと、そのまま関数の最後に到達して終了してしまうので、何もウィンドウは表示されません。
 
ループといっても、ウィンドウ表示中に他の作業もできますので、安心してください。
 
手前にある gtk_widget_show_all 関数は、ウィンドウ表示の関数ではなく、メインループ実行時にウィンドウ表示することを設定する命令です。
 
 
 
さて、上のコードの例ではGtkFixedウィジェットを作成しボタン、ラベルのウィジェットをGtkFixedウィジェット内に配置しています。このようにウィジェット内のどこにでも他のウィジェットを収納できるウィジェットがGtkFixedウィジェットです。
 
実際にはgnome-panelの中でもGtkFixedウィジェットが使用されており、"オブジェクト"の配置を自由な位置に行うために役立っています。ただし、GUIを提供する以外に他の項目を設定するために、GtkFixedクラスを継承する形でウィジェットが提供されています。
 
実際に提供されているウィジェットはPanelWidgetウィジェットと呼ばれ、ソースコード中では./gnome-panel/panel-widget.hで定義されています。このクラスに対応する構造体は最初にGtkFixedを持っていますが、これとCの機能を使うと[[w:en:glib]]でいう"継承"を行うことができます。詳しくは[[OSS開発ツール GUIツールキット]]を参照してください。
 
ここまでで、gnome-panel内で"オブジェクト"の配置はGtkFixedクラスの機能によってなされていることが分かりました。他に興味ある点としては、個々の"オブジェクト"がどのように定義されているかや、オブジェクトを導入するための操作などがあります。例えば、"オブジェクト"が配置できる部分を右クリックすると、メニューが開き、追加するオブジェクトを選ぶことができます。この操作はGtkMenuなどで提供される機能ですが、実際にソースコード中でどのようにGtkMenuが用いられているかも調べることができます。
 
 
=== 図形の描画 ===
図形として線分を表示したり、円などを表示する機能は、図形描画ライブラリが担当している。
 
最近のGTKでは、図形描画ライブラリにcairoというフリーの画像描画ライブラリを採用している。特に追加インストールすることなく、cairoの図形描画命令が使用できる。
cairo自体は、GTKとは別のアプリなので、詳細はcairoの入門書を参照のこと。
 
コード例を示すと下記のようになります。
 
;コード例 (CentOS-7-x86_64-LiveGNOME-1908.iso​ で確認ずみ)
<syntaxhighlight lang="c">
#include <gtk/gtk.h>
//#include <gdk/gdk.h>
 
GtkWidget *uindou;
 
static gboolean kansuu(GtkWidget *uijet1, cairo_t *handoru, gpointer abcde)
{
//ハンドルの作成
handoru = gdk_cairo_create(gtk_widget_get_window(uijet1));
 
//線の性質の定義
cairo_set_line_width (handoru, 10); // 線の太さ
cairo_set_source_rgb (handoru, 1.0 , 0.0 , 0.0); // 線の色
cairo_move_to (handoru, 00, 50); // 線の起点
cairo_line_to (handoru, 300, 300); // 線の終点
 
// 実際に線を引く命令
cairo_stroke (handoru);
 
//長方形の描画
{
//長方形(x,y,width,height)の大きさを指定
cairo_rectangle(handoru, 50.0, 20.0, 30.0, 90.0);
//線の色の指定(Red,Green,Blue)
cairo_set_source_rgb(handoru, 0.0, 0.0, 1.0);
//実際に長方形を塗りつぶす命令
cairo_fill(handoru);
}
 
cairo_destroy(handoru);
return FALSE;
}
 
int main(int argc, char **argv)
{
gtk_init(&argc, &argv);
 
uindou = gtk_window_new(GTK_WINDOW_TOPLEVEL);
gtk_window_set_title(GTK_WINDOW(uindou), "テスト");
gtk_widget_set_size_request(uindou, 420,200);
 
//ウインドウに図形を描けるように設定
gtk_widget_set_app_paintable(uindou, TRUE);
 
 
//ウインドウが表示されたときにkansuu()を呼び出す
g_signal_connect(G_OBJECT(uindou), "draw", G_CALLBACK(kansuu), NULL);
 
gtk_widget_show_all(uindou);
gtk_main();
 
return 0;
}
</syntaxhighlight>
 
2019年の現在、GTK3で cairo を使うと、いくつかの組み込み関数を別の関数(たとえば gdk_window_begin_draw_frame() など)に置き換えるようにコンパイラを介して警告されますが、しかしGTK開発元のGnomeコミュニティが、ロクに それらの組み込み関数のマニュアルを整備してない状況なので(形式的にリファレンスがあるが、実際のコード例がなく、まったく新関数の使い方が調べられない状況であるので)、警告は無視して、従来通りにマニュアルの比較的に充実している cairo を使いましょう。マニュアルも整備しないで満足してサボっているナードな技術マニア連中のGnomeコミュニティのほうが悪いのです。
 
もし将来的にcairoが使えなくなったら、フォークするか、でなければGTKでなくQtなどの別のデスクトップ環境に移行しましょう。どうせ組込系ではGTKよりもQtのほうが主流です。
 
ちなみに、上記のコード例の場合、このように警告されます。
<pre>
‘gdk_cairo_create’ is deprecated (declared at /usr/include/gtk-3.0/gdk/gdkcairo.h:35): Use 'gdk_window_begin_draw_frame() and gdk_drawing_context_get_cairo_context()' instead [-Wdeprecated-declarations]
handoru = gdk_cairo_create(gtk_widget_get_window(uindou));
</pre>
 
;コードの解説
GTK3 では、まず main 関数側で、図形の描画をできるように設定を宣言する必要があります。
 
 
なお、ネットに転がってるコード例をみると、cairo の呼び出し方では、一般に任意の自作の関数(上記のコード例では kansuu)を介して cairo を呼び出します。
 
 
cairo による図形描画プログラムを作成するとき、windows APIプログラミングでいうところのハンドルのような物を宣言する必要があります。
 
上記コードでは
handoru = gdk_cairo_create(gtk_widget_get_window(uijet1));
で、ハンドル作成しています。
 
なお、関数宣言のさいの <code> static gboolean kansuu(GtkWidget *uijet1, cairo_t *handoru, gpointer abcde) </code> の際に、すでに引数として<code> cairo_t *handoru</code> のように宣言されており、この時点ですでにハンドル作成などのための必要なメモリの確保を行っているものと考えられます。
 
cairo_t型とは、ハンドルのようなものを定義するための型です。そもそも一般的にC言語では、型の宣言とは、メモリの確保でもあります。
 
ともかく、cairo_t型の宣言のさいに既にメモリは確保されているので、あとは実際にハンドルの作成をすれば済むだけなので、 よって
handoru = gdk_cairo_create(gtk_widget_get_window(uijet1));
で、実際にハンドル作成を実行するわけです。
 
この画像描画の説明でいう「ハンドル」とは、たとえるなら絵を書くときのキャンバスのようなものです。
 
線を引いたりなど図形を描画するときは、初心者には わずらわしいですが、どのハンドル(キャンバス)に図形を描画するのかを、各命令で宣言する必要があります。
 
 
GTKでは図形の性質の定義と、実際に図形の描画を実行する命令とは、異なる命令になります。(Winodws APIと同様。)
 
cairo_stroke の命令で、実際に線分の描画を実行します。
 
 
そして、使用し終わったら、destroy でハンドルを破棄するのが一般的です。(メモリの圧迫を防ぐため。)(Winodws APIでも同様に、使い終わったハンドルは破棄を宣言する。)
 
 
;この章を書くに当たり参考にした文献:
:[http://uchigo.main.jp/gtk3/chap08/chap08.html 素人の独学GTK+3.0 8章:タイムカウントとGDK3] 2019年9月28日に閲覧
 
 
== コンポーネント ==
===== "メニューバー"オブジェクトについて =====
 
ここからは、個々のオブジェクトについて詳しく調べていきます。gnome-panelの多くの設定で使用されているオブジェクトに、"メニューバー"があります。
:[[画像:gnome_panel_menu_bar_generic.png|200px]]
このオブジェクトはアプリケーションメニューと場所メニュー、アクションメニューの3つのメニューから構成されています。それぞれのメニューをクリックすると種々のメニューが提供されます。アプリケーションメニューではシステムに存在するアプリケーションの起動が扱われ、アクションメニューでは"画面のロック"や"ログアウト"などのGNOMEデスクトップ全体に関わる事柄が扱われます。
 
メニューバーは大抵の設定でただ1つだけパネル内におかれているので、この部分は動かせないと思われがちです。しかし、実際にはこの部分は取り去ることが可能であり、またパネル内に複数置くことも可能です。
:[[画像:gnome_panel_no_menu.png|400px]]
:[[画像:gnome_panel_multiple_menus.png|400px]]
 
メニューバーのクラスはソースコード内では./gnome-panel/panel-menu-bar.hで定義されています。この中のPanelMenuBar構造体を確認すると分かる通り、このクラスはGtkMenuBarを継承しています。そのため、このクラスは基本的にGtkMenuBarと同じ動作をします。GtkMenuBarを使ったサンプルとして次のようなメニューの例があげられます。
 
<syntaxhighlight lang="c">
int main (int argc, char **argv){
gtk_init(&argc, &argv);
GtkWidget *window, *menubar, *menuitem, *menu;
 
/* ウィンドウを作成 */
window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
 
/* メニューバーを登録 */
menubar = gtk_menu_bar_new();
gtk_container_add(GTK_CONTAINER(window), menubar);
 
/* メニューバーの項目を登録 */
menuitem = gtk_menu_item_new_with_label("mmm");
gtk_menu_shell_prepend(GTK_MENU_SHELL(menubar), menuitem);
 
/* 項目から派生するメニューを登録 */
menu = gtk_menu_new();
gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem), menu);
 
/* 派生した先に登録する項目を作成 */
menuitem = gtk_menu_item_new_with_label("lll");
gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
 
/* メニューの項目に対応するコールバック menu_cb を登録 */
g_signal_connect(G_OBJECT(menuitem), "activate", G_CALLBACK(menu_cb), NULL );
 
gtk_widget_show_all(window);
gtk_main();
return 0;
}
</syntaxhighlight>
 
:[[画像:gnome_menu.png|GtkMenuを使った例。この画像はできたGUIアプリケーションを見ながら、手で描いたものである。]]
 
GtkMenuBarはGtkMenuItemを書き込むことでメニューを作成することができるウィジェットです。
上の例はmmmという名のメニューを作成し、それをクリックしたときlllと書かれたメニューを表示し、更にその中のlllと書かれた部分をクリックすることで関数menu_cbを実行するというプログラムです。メニューの項目は増やせるので、コールバック関数をいろいろなアプリケーションの起動を行う関数とすることで、ランチャーの役目を果たすアプリケーションとすることができます。
 
===== ポップアップメニュー =====
 
gnome-panelではパネルの各所を右クリックすることでポップアップメニューを得ることができます。この操作はGUIを使った操作としてはよく見られるもので、どのように実現されるかが気になる所です。実は、この操作はGTK+のクラスであるGtkMenuの操作として典型的なものです。ここではGtkMenuのポップアップの例を見るとともに、実際にこの操作がどのようにgnome-panel中で用いられているかを見て行きます。
 
GtkMenuのポップアップの例として、次のサンプルをあげます。
 
<syntaxhighlight lang="c">
int main (int argc, char **argv){
gtk_init(&argc, &argv);
GtkWidget *menuitem, *menu;
 
/* メニューの例を参照 */
menu = gtk_menu_new();
menuitem = gtk_menu_item_new_with_label("lll");
gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
g_signal_connect(G_OBJECT(menuitem), "activate", G_CALLBACK(menu_cb), NULL );
gtk_widget_show_all(menu);
 
/* ウィンドウから離れてメニューを表示*/
gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL, 0,
gtk_get_current_event_time() );
 
/*メインループ*/
gtk_main();
return 0;
}
</syntaxhighlight>
 
:[[画像:gnome_panel_gtk_menu_popup_no_window.svg]]
上の例では"lll"と書き込まれたメニューを作成した後、そのメニューをgtk_menu_popupによって表示します。メニューが表示される場所はgtk_menu_popupの引数によって変更できるのですが、上の例ではgtk_menu_popupが実行された時点でのマウスカーソルの場所になります。実行例を見るとわかるのですがこの例は何も無い部分に突然メニューが表示されるため、やや非直観的です。普通の例ではGtkWindow等のマウスイベントを設定し,マウスのボタンが押されたときにメニューが表示されるようにします。ただし、GtkWindowは通常ではマウスのボタンに対応するイベントを持たないため,その点を補う必要があります。 このためには、gtk_widget_add_eventsかGtkEventBoxを使う方法がありますが、ここではgtk_widget_add_eventsを用いる方法を述べます。GtkEventBoxについてはGTK+のリファレンス等を参照してください。以降の説明ではある程度[[Xプログラミング]]の経験があると理解が容易になります。
 
gtk_widget_add_eventsはウィジェットがGTK+の背後で動いているウィンドウシステムから、新しいイベントを得るように設定する関数です。背後のウィンドウシステムの代表例は[[w:X Window System]]ですがUnix系のシステムでないなら他のものになることもあります。GTKではウィンドウシステムの値を直接使わなくてもすむよう、[[w:GDK]]というライブラリを用いています。GDKはGTK+とともに配布されるライブラリです。
 
gtk_widget_add_eventsでは引数として(GtkWidget *, GdkEventMask)を取ります。ここで、EventMask(イベントマスク)は対応するイベントをXなどから受け取るかを定めるビット列です。例えば,Xを用いてイベントを処理する場合にはXSelectInputなどを用いますが,この関数の引数としてイベントマスクが用いられます。詳しくは[[Xプログラミング]]を参照してください。ここで扱うGdkEventMaskも同種の値です。
 
実際にマウスボタンのイベントを見るには,GdkEventMaskとしてGDK_BUTTON_PRESS_MASKを用います。結局GtkWindowを作った後,
gtk_widget_add_events(window, GDK_BUTTON_PRESS_MASK);
を実行すると,ウィンドウ内でマウスボタンの操作を見ることができるようになります。この関数の後には,GTKのイベントである"button_press_event"を用いてマウスのボタンを扱うことができます。
 
ここまでのことを用いて,メニューを作成してからメインループに至るまでの部分は次のようになります。
 
 
<syntaxhighlight lang="c">
GtkWindow *window;
window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
gtk_widget_add_events(window, GDK_BUTTON_PRESS_MASK);
g_signal_connect(window, "button_press_event", G_CALLBACK(window_button_cb), menu);
gtk_widget_show(window);
</syntaxhighlight>
 
ただし、menuは上で作成したGtkMenuと同一です。ここで、window_button_cbは次のように与えます。
 
 
<syntaxhighlight lang="c">
void window_button_cb(GtkWidget *window, GdkEventButton *button, GtkWidget *menu){
gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL, 0,
gtk_get_current_event_time() );
}
</syntaxhighlight>
 
:[[画像:gnome_panel_gtk_menu_popup_with_window.svg]]
 
 
===== アプリケーションの起動 =====
アプリケーションの起動を行うためには、 コールバック関数として定義された関数(上の例ではmenu_cbと与えられている)の中で、新たな"プロセス"を作る必要があります。"プロセス"はOSが複数のアプリケーションを同時に動かすときの単位で、それを作る方法はOSによって異なっています。Unix系のOSではプロセスを作る関数は大抵[[w:fork]]と呼ばれます。forkはプロセスを作成し、新たに作成されたプロセスのIDを返します。また、作成されたプロセスで実際にあるアプリケーションを起動する関数として、Unix系のOSではexec系の関数が与えられます。execは与えられる引数によっていくつかの似た関数が提供されます。
:forkとexecを使った例
 
実際にそれぞれのメニューバーの項目に対応するコールバックを設定する操作は、./gnome-panel/panel-menu-item.c内の関数panel_menu_items_append_from_desktop内で行われています。この関数は第1引数にメニューの項目、第2引数に起動するアプリケーションの名称を取り、これを新たに作成したメニューの項目に与えています。
 
既にメニューの項目をクリックしたときの動作を与える方法として、
g_signal_connect(menuitem, "activate", menu_cb, NULL);
を使う方法を紹介しました。ここで、menuitemはここで内容を与えるメニュー項目であり、menu_cbは実際にこのメニューの項目がクリックされたときに実行させたい関数です。指定したアプリケーションを起動して、新たなプロセスを実行するためには、この関数はforkやexecを用いる関数である必要があります。
 
* 注意
g_signal_connectは[[w:en:glib]]で定義された関数で、"シグナル"が定義されたGObjectクラス及びそれを継承したクラスに対して、コールバック関数を与える関数です。ここで、"シグナル"はおおよそコールバック関数と同じ意味で、Unixでいう"シグナル"(他のプロセスに影響を与える機構)とは無関係です。<!-- 例えば、"シグナル"を発する関数と受け取る関数は同じプロセス内にあることが普通です。 -->
 
panel_menu_item_append_from_desktop内でもこの関数が用いられており、コールバック関数として同じファイル内で定義された、関数panel_menu_item_activate_desktop_fileを取ります。この関数はクリックされたメニュー項目の情報に加えて、そのメニュー項目がクリックされたときに実行されるべきアプリケーションの名称を引数として受け取ります。ここで、この関数はアプリケーションの名称を引数として与えながら、関数panel_ditem_launchを呼びます。launchの名から分かる通り、この関数は実際にアプリケーションの起動を行います。launchは"起動する"、"発射する"などの意味を持つ英単語です。
 
ここまでも既に長い道のりでした。しかし、ここから実際にforkが呼ばれるまでに、更にいくつかのライブラリを見る必要があります。ある意味でlaunchと名の付いた関数を見付けた時点で、この関数がプログラムの実行を行う可能性は高いため、そこで探索を終える方法もあるでしょう。ここでは、一応最後まで関数の流れを追ってみます。
 
panel_ditem_launchは、./gnome-panel/panel-util.c内で定義されています。この関数はいくつかの準備を行った後、関数gnome_desktop_item_launch_on_screenを呼びます。実はこの関数はgnome-panel内の関数ではないため、ソースを読もうと試みる人は、この関数が定義されたソースを求めて方々を探す必要があります。実際には[[w:google]]などを試してみるのがよいでしょう。
 
実際にこれを探すと、関数gnome_desktop_item_launch_on_screenは、gnome-desktopというライブラリ内の関数だとわかります。このライブラリもGNOMEのサイトから提供されているので、必要ならダウンロードしてください。実際には、この関数は、gnome-desktop-x.x.x/libgnome-desktop/gnome-desktop-item.cで定義されています。
 
この関数ではいくつかの引数のチェックを行った後、同じファイル内の関数ditem_executeを呼びます。この関数は関数の名前の最初に、gnome_desktop_...がついていません。このような関数は大抵staticをつけて宣言されており、そのファイル内だけで用いられる関数です。これは、staticをつけた関数は外部からは参照できないことを利用しています。他のファイルの関数名と重複することがないため、単純な名前でもよい訳です。
 
ditem_executeは様々なチェックなどを行った後、関数g_spawn_asyncを呼びます。この関数はg_から始まっていますが、GNOMEのアプリケーションでこの名称が出て来た場合、この関数は大抵[[w:en:glib]]の関数です。例えば、コールバックを与える関数であるg_signal_connectがglibの関数であることは既に述べました。
 
多くの関数をたどって来ましたが、g_spawn_asyncは事実上最後の関数です。この関数はglib-x.x.x/glib/gspawn.c内で定義されていますが、この関数はいくつかの関数を経て、関数fork_exec_with_pipesという関数を呼びます。この関数は名前の通りforkとexecを呼ぶ関数です。
 
ここまでで一応gnome-panelのメニュー項目がクリックされてから、実際に新たなプロセスが作成されるまでの道のりを見て来ました。もちろんただプロセスを作ることが目的なら、gnome-panel内で直接forkを呼ぶことも可能です。敢えてライブラリを使うのは、例えば[[w:Windows]]を使うときにはこの方法が使えないことがあげられます。これは、Windows上でプロセスを作るときにはforkではなく別の[[w:Windows API]]を使う必要があるからです。実際glibのgspawn.cがあるディレクトリ内には、gspawn-win32.cというWindows向けの関数も定義されており、glibライブラリをクロスプラットフォームライブラリにするよう試みているようです。
 
==== gconfを用いた設定 ====
 
ここまでで一応ランチャーとしての役目を果たすための機能を見て来ました。メニューを表示し、そのメニュー項目と対応するアプリケーションを起動することは、ランチャーの機能としては基本的です。ただし、ランチャーが扱えるアプリケーションは、プログラムのふるまいを外部から制御する機構がないのなら、ランチャーを作った人間がプログラム内に書き込んだアプリケーションに限られます。これでは新たなアプリケーションが加わった時にランチャーの振舞いを拡張することができない<!-- 、もしくはアプリケーションを追加するためにgnome-panelを[[w:コンパイル]]し直す必要がある-->ことになり、不便です。
 
ソフトウェアの動作を制御するためには、"設定ファイル"を使った方法がよく用いられます。例えば、XサーバやWebサーバ<!-- が用いる[[w:ドライバ]]を定めるために、Xの--> の動作を変更するために、これらの設定ファイルを書き直すことは特にホビーとしてのPC-Unixではよく行われます。
 
ただし、アプリケーションの動作を制御するために設定ファイルを使った方法を用いる場合には、その設定がアプリケーション内に取り込まれるタイミングが重要になります。例えば、設定ファイルを読む操作がアプリケーションの起動時にしか行われない場合、設定ファイルを変更した後アプリケーションの動作を変更するには、設定ファイルを書き直すたびに対応するアプリケーションを起動し直す必要があり、少し不便です。
 
より進んだ方法では、設定ファイルを書き直した後、そのことをアプリケーションに伝達する機構を用意しています。ここで、設定ファイルを書き直すプロセスは、一般には設定ファイルを利用するプロセスとは異なっています。このため、設定が変更されたことをその設定を使用しているアプリケーションに伝達するには、"プロセス間通信"の機構を用いる必要があります。
 
プロセス間通信は異なったプロセスの間で情報を伝達する機構です。この機構もプロセスの操作と同様OSによって提供される機構であり、異なったOSでは異なった動作をします。Unixにおける代表的なプロセス間通信には、[[w:ソケット]]を使った方法があげられます。ソケットは異なったプロセスからの情報を受け取るための一般的な機構ですが、これは異なったコンピュータ上にあるプロセスに対しても用いることができます。例えばLinuxでは、[[w:TCP]]の通信を行うためのソケットを提供していますが、この通信手法は[[w:インターネット]]のあらゆるサービスを提供するための手法として用いられています。
 
GNOMEでは、設定を扱うために[[w:en:gconf]]と呼ばれるライブラリを利用します。これは、設定を扱うための1つのサーバ(gconfd)を導入し、そのサーバに、サーバ上の設定を参照しているgconfクライアントを記憶させておき、設定が変更された際に、そのことをgconfクライアントに伝える機構です。簡単な例では、gconfdが保持している設定は利用者のホームディレクトリ~の~/.gconf/以下に記録されます。
 
* 注意
Unixの"シェル"では、利用者のホームディレクトリを~の記号で表します。設定によるのですが、このディレクトリは大抵/home/user_name/以下におかれます。ただし、user_nameはそのコンピュータに登録されている利用者の名前です。PC-Unixでは大抵利用者はそのパソコンの所有者1人だけなので、/home以下に直接設定ファイルをおけばよいようですが、多くの利用者が異なった設定でgconfを使う場面を想定してこのような作りになっています。
 
実際のシステムでは~/.gconfは次のようになります。
 
$ls
apps/ desktop/
 
これはfedora core 5での~/.gconf/内のファイルを表示した例です。ここには2つのディレクトリしかありませんが,apps/以下には[[w:en:Eye of GNOME]], [[w:gedit]]などの各種アプリケーションの設定が記録されています。これらはそれぞれ
 
eog/ gedit/
 
などの名前を与えられています。
 
gconfの動作を見るために、gconfが提供するツールを使った実験をしてみます。gconfはGConf-x.x.xというライブラリとしてGNOMEのサイトから配布されているのですが、その中には、GConf-x.x.x/gconf/gconftool.cで与えられるファイルが存在します。ここで、GConfのバージョンはGConf-2.14.0を使いました。このファイルは、gconfの設定内容を変更したり参照するための簡単なツールを提供します。このツールはgconftoolと呼ばれます。
 
ここでは、このツールを用いてgconfを使ってみます。既に~/.gconf/の中を見てみました。ここでは、gconftoolを使ってこのディレクトリ以下に新たな設定項目を作ります。もちろんこの項目は実際にアプリケーションで使われる項目ではないのですが、gconfの動作を見る上では便利です。具体的には、~/.gconf/以下に、/aaa/bbbという項目を作ります。ここで、/aaaは項目が配置されるディレクトリ名を表し、bbbが実際に記録される設定の名前です。もちろんこの階層はいくらでも深くすることができますが、ここではこの程度でよいでしょう。
 
また、gconfで設定される項目には"型"が必要になります。"型"にはint, bool, float型がなどがあります。これらのうち、intとfloatは[[C言語]]の対応する型と同じで、intは整数、floatは実数を表します。boolは例えば[[CPlusPlus]]などでは導入されているのですが、"真"、"偽"の2つの値だけを持つ型です。C言語では1 を"真"、0を"偽"などとしてint型で代用することができます。ここでは、bbbの型はboolで、値を"真"、つまりtrueとします。
 
具体的に、/aaa以下にbool型の項目bbbを値trueで設定するには、
 
$gconftool-2 -s /aaa/bbb -t bool true
 
とします。ここで、-sは項目を指定するための引数であり、-tは項目の型を指定する引数です。
 
実際にこの操作を実行すると、~/.gconf/は次のようになります。
$ls
aaa/ apps/ desktop/
 
新たにaaa/というディレクトリが加わっている様子がわかります。aaa/内のファイルを表示すると,
$ls
%gconf.xml
が得られます。ここではgconfの設定は[[w:XML]]ファイルに記録されています。この中身は,
<?xml version="1.0"?>
<gconf>
<entry name="bbb" mtime="1168623910" type="bool" value="true">
</entry>
</gconf>
で与えられます。この中では3行目の"bbb", "bool", "true"から、上で扱った内容が記録されている様子がわかります。
<!--
まず、gconfを用いる上で重要となる関数として、gconf_client_add_dir, gconf_client_notify_add, gconf_client_set_...があります。これらの関数はそれぞれGConf-x.x.x内で定義された関数です。gconftoolもこれらの関数の機能を使っています。
 
* 注意
実際にはgconftool.cでは、gconf_client_...ではなく、gconf_engine_...の名称を持つ関数を用いています。gconf_engine_...の関数はgconf_client_...と対応する関数が存在し、gconfライブラリの内部でgconf_client_...を作成するために用いられています。例えば、gconf_client_set_listの中では、gconf_engine_set_listが呼ばれています。(GConf-x.x.x/gconf/gconf-client.cを参照)
 
ここでは、これらの関数の動作を順に見て行きます。まず、gconfは各アプリケーションの設定を階層構造にして記録します。例えば、gnome-panelの設定は、/apps/panel/以下に記録されます。ここで、各"/"(スラッシュ)は階層構造の切れ目を表します。gconf_client_add_dirはgconfdに対して、ある階層以下の設定が変更されたときに、そのことをクライアントに伝えるように指令を出します。
:使用例
 
gconf_client_add_dirが使われた後には、gconfdはある設定が変更されたときにクライアントにそのことを伝達します。このとき、クライアントが何らかの動作を取るためには、クライアントに何らかのコールバック関数を提供する必要があります。具体的には、関数gconf_client_notify_addは、gconfdから連絡が来たときに実行するべきコールバック関数をクライアントに与える関数です。
:使用例
 
実際にgconfdにある設定を変更することを伝えるには、gconf_client_set_...の各種関数を使います。...には例えばstring, int ,bool, list, ...などの値が入ります。
:使用例
 
* 注意
GConf-x.x.x以下のソースを追うことで、gconf_client_set_...以下の関数に対してgconfdサーバがクライアントに連絡を行う機構を、実際に見ることができます。この機構はgconf_client_add_dirとgconf_client_notify_addの内部での振舞いがかなり異なることから、複雑になっています。具体的には、gconf_client_add_dirは階層の名前を実際にgconfdに伝達するのに対して、gconf_client_notify_addはコールバック関数をクライアント側にとどめるからなのですが、これ以上はこのことに深入りはしません。GConfのバージョンとしては、GConf-2.14.0を用いました。
 
実際にこれらの関数がどう使われているかを見るためには、cファイルとヘッダファイル全体を見ることができるアプリケーションを使って、gconf_client_notify_addなどの特徴的な関数を探すのが早道です。ここでは、"grep"を利用しました。grepは各種ファイルの中から特定の文字列を探すコマンドで、ソースファイルを探る目的でよく用いられます。具体的には、./gnome-panelディレクトリ内で
$grep \\bgconf_client_notify_add\\b *
としました。ここで、"\b"はそこで単語が途切れていることを表す表現です。ただし、"シェル"の動作を考慮して、"\"(バックスラッシュ)を2重にしています。実際に関数が見付かった順に進めてもよいのですが、ここではより体系だった方法でまとめます。
-->
 
===== gnome-panelでのgconf =====
 
ここまででgconfの基本的な使い方を見て来ました。gnome-panelでもパネル内のどの位置にどのオブジェクトが配置されているかなどをを記録するために、gconfを用いています。ここではまず、gnome-panelがどのようにgconfdから設定を受け取り、設定への変更を取得しているかを見て行きます。
 
===== gnome-panelの起動 =====
ここではgnome-panelがどのように起動するかを見て行きます。通常のアプリケーションと同様、gnome-panelは起動時に設定を参照してどこにオブジェクトを配置するかなどを決めます。この設定は設定ファイルではなく、gconfを用いて行われるのですが、ここでは実際にアプリケーションの起動時にどのようにgconfが用いられているかを見て行きます。
 
大抵のGNOMEアプリケーションではアプリケーションの起動はmain関数から始まります。これは普通のCプログラムと同じです。一方、[[w:Windows API]]を用いたWindowsのcプログラムでは、アプリケーションはWinMain関数から始まります。これはGUIアプリケーション一般の性質という訳ではないので注意してください。
 
そのため、アプリケーションの起動の様子を見るためには、このアプリケーションのmain関数を探す必要があります。gnome-panelでは、./gnome-panel/main.cに、アプリケーションのmain関数が定義されています。main関数では各種の初期化や設定が行われているのですが、その中でgconfとの相互作用を扱う関数として、関数panel_profile_loadが呼ばれています。この関数は、./gnome-panel/panel-profile.c内で定義された関数で、"いくつのパネルがあるか、それぞれのパネルにはどのようなオブジェクトやアプレットが配置されているか"などの各種情報をgconfdから受け取り、対応するウィジェットを作成しています。
 
実際には関数panel_profile_loadの最後で、関数panel_applet_load_queued_appletsが呼ばれています。<!--
gnome-panelを起動した際のウィジェットの構築は関数gconf_client_notify_addを用いた機構とは関係の無い方法で行われていることが分かります。これ以降の話は、アプリケーション起動時のウィジェットの構築ではなく、アプリケーションが動作している状態でウィジェットを構築するための機構です。
panel_applet_load_queued_appletsです。
-->
この関数は、./gnome-panel/applet.c内で定義されていますが、おおよそ関数panel_applet_load_idle_handlerを呼び出す関数です。panel_applet_load_idle_handlerもapplet.c内の関数ですが、この中では追加されたオブジェクトのタイプに対して、動作を変更するための、大きなswitch文が用いられています。switch文は[[C言語]]の制御構造の1つで、複数の条件があるときに、それらの条件に対して場合分けを行う文です。詳しくは[[C言語]]内の説明を参照してください。このswitch文はgconfが与えた内容に対して作成するウィジェットを変更するための場合分けで、これ以降各ウィジェットを作成する手順はウィジェットの種類によって様々です。
具体的には追加されたオブジェクトが"メニューバー"だった場合にはpanel_menu_bar_load_from_gconfを呼んでいます。この関数は設定に従って対応する"メニューバー"オブジェクトを作成する関数です。この関数の詳細を追うこともできますが、一応gconfの設定を読んだ後に、設定に応じて各種のオブジェクトを与える過程が得られたので、アプリケーションの起動に限った話はここまでとします。
 
===== 各種設定の変更 =====
 
ここまでで、gnome-panelが起動するときに、gconfが与える設定に従って、各種ウィジェットを見分ける方法を見て来ました。実際にはgnome-panelはgnome-panelが動作している最中に設定が変更されても、それに対応してウィジェットを作成する機構を持っています。これはgconfの機能を活用した機構です。
 
具体的には、gnome-panelはパネルの自由な位置に利用者が指定したオブジェクトをgnome-panelを再起動すること無く導入するためのGUIを持っています。このGUIは、gnome-panelの設定の変更とそのことのgnome-panelへの伝達を同時に行っており、設定を変更するたびにgnome-panelを起動しなおす手間を省いています。
ここでは、このGUIを用いて、gnome-panelに各種オブジェクトを配置する方法を見て行きます。
 
まず、gnome-panelの設定を変更するためのGUIとして、gnome-panel/panel-addto.c内の関数が用いられています。このファイル内の関数はGtkDialogを用いて利用者からgnome-panel内で変更したい設定を取得しようとします。ここではこのウィジェットをAddToダイアログと呼びます。
:[[画像:Gnome_panel_addto_dialog.png|200px]]
 
ここで、GtkDialogはGtkWindowを継承したクラスで、GtkWindowに"OK", "キャンセル"などの各種ボタンを与えるクラスです。一般的なGtkDialogの例として、次のようなサンプルがあげられます。
 
<syntaxhighlight lang="c">
#include <gtk/gtk.h>
int main (int argc, char **argv){
gtk_init(&argc, &argv);
GtkWidget *dialog, *label;
 
/* ダイアログの作成 */
dialog = gtk_dialog_new_with_buttons(NULL,
NULL,
0,
"はい",
1,
"いいえ",
2,
NULL);
label = gtk_label_new("あああ\n");
gtk_container_add(GTK_CONTAINER(GTK_DIALOG(dialog)->vbox), label);
gtk_widget_show_all(dialog);
 
/* 応対(response)の取得*/
int response = 0;
response = gtk_dialog_run(GTK_DIALOG(dialog));
switch(response){
case 1:
g_print("はい\n");
break;
case 2:
g_print("いいえ\n");
break;
default:
break;
}
return 0;
}
</syntaxhighlight>
 
:[[画像:gnome_panel_gtk_dialog.png|200px]]
:解説
 
 
AddToダイアログの動作はgconfの機能を使っています。具体的には、gconfの設定のうち、オブジェクトの種類や配置が記録されている部分にgconf_client_add_dirを使い、その値が変更されたときのコールバック関数を登録するために、gconf_client_notify_addを使っています。具体的には、これらの関数は既に見た関数panel_profile_load内及びそこから呼び出された関数内で用いられています。
 
関数panel_profile_loadは、途中でgconf_client_add_dir関数を呼び出しています。ここで指定するgconfの階層は、/apps/panel/generalです。この階層以下には、パネルの数やオブジェクトの位置などが記録されるため、オブジェクトの数を確認するためにはこの部分の変化を見ておく必要があります。
 
ここで実際のgnome-panelでの例を示します。実際にgconfの設定を見るためにgconftoolを用います。gconftoolで対応するディレクトリ以下の設定項目を見るには,
$gconftool-2 [-R|--recursive-list] ディレクトリ名
とします。
 
gnome-panelの設定を見るためには、あらかじめgnome-panelをできるだけ単純に設定しておくと後が楽になります。ここでは、パネルは1つだけを残して全て取り去り、残ったパネル内のオブジェクトも全て取り去りました。この操作は対応するパネルやオブジェクトを右クリックし、対応するメニューを用いることで行えます。また、最後のパネルを消そうとすると、メニューが表示されなくなるため、全てのパネルを消し去ることはできません。パネルを右クリックした場合新たなパネルを追加するメニューが表示されるため、それを用いて新たにパネルを加え、設定を元に戻すことができます。
 
実際にこの設定にした後,上のコマンドでディレクトリ名として/apps/panel/generalを用いると
object_id_list = []
applet_id_list = []
toplevel_id_list = [panel_1]
などの出力が得られます。ここで、object_id_list、applet_id_listはそれぞれパネルに含まれるオブジェクト、アプレットを表します。ここでは、オブジェクトを1つも用いていないので、これらは空欄となります。一方toplevel_id_listはパネルがいくつあるかを示すリストです。ここではただ1つのパネルを用いているため項目は1つだけとなります。後にわかるのですが、panelが複数になった時にはこの部分にpanel_2, panel_3, ... などの項目が追加されます。ここで、1, 2, 3などの数字はidと呼ばれます。この用語はtoplevel_id_list等の名前にも用いられていますが、以降のソース内の関数名にも何度か用いられます。
 
これで、オブジェクト、アプレット、パネルの数がどのように記録されているかがわかりました。更にこれらの設定の詳細については/apps/panel以下の各ディレクトリに記録されています。例えば,/apps/panel/toplevelsにはパネルの設定が記録され、/apps/panel/appletsにはアプレットの設定が記録されます。例えば/apps/panel/toplevels/panel_1の設定を見ることもできますが、設定項目の数が多いのでこれらの詳細には触れません。比較的意味が取りやすいものでは、
size = 24
orientation = top
auto_hide = false
などがあります。これらは、それぞれパネルの幅(単位はピクセル)、パネルを画面中でどの位置に置くか、カーソルが置かれていないときパネルを隠すかどうかに対応します。これらはどれもパネルの右クリックメニューから扱うことができる"プロパティ"によって設定できる項目です。
 
次に、パネルの数とアプレットの数を増やして同じ操作をしてみます。ここではパネルを2枚にし、メインメニュー、時計、通知スペース、ウィンドウの一覧、デスクトップの表示などの各種オブジェクトを追加しました。ただし、パネルの位置はそれぞれ上と下とし、オブジェクトのうち最初の3つは上のパネルに加え、後の2つを下のパネルに加えました。この場合/apps/panel/generalでの出力は
object_id_list = [object_1]
applet_id_list = [applet_0,applet_1,applet_2,applet_3]
toplevel_id_list = [panel_1,panel_2]
のようになります。ここで上では5つのオブジェクトを加えたのに、設定ではオブジェクトが1つでアプレットが4つとなっています。実際にはアプレットもオブジェクトの一種なのですが、アプレットは他のオブジェクトと比べてかなり動作が異なるので、アプレットは別に扱われます。
 
具体的にはアプレットはソース内ではOBJECT_BONOBOなどと呼ばれます。ここで、BONOBOはアプレットを扱う技術の名前なのですが、この技術は単純にはプロセス間通信を用いた技術です。実は上の時計などのアプレットの本体は、gnome-panelのプロセスとは別のプロセスとして存在します。例えば,時計が動いているgnome-panelが存在する時には,常にclock-appletというプログラムが動いています。具体的には、対応するpsコマンドの出力には、
3855 ? S 0:00 /usr/libexec/notification-area-applet --oaf-activate-
3859 ? S 0:00 /usr/libexec/clock-applet --oaf-activate-iid=OAFIID:G
3874 ? S 0:00 /usr/libexec/wnck-applet --oaf-activate-iid=OAFIID:GN
などの出力が含まれます。ここで注目してほしいのは、中間の/usr/libexec/以下の各項目です。これらは上から順に通知スペース(notification-area)、時計(clock), ウィンドウの一覧に対応するプロセスです。BONOBOのライブラリであるlibbonoboについてはここでは深くは扱いません。[[w:en:bonobo (computing)]]、[http://www.gnome.gr.jp/docs/inside_bonobo/index.html]などを参照してください。
 
ここでは更に、/apps/panel/objectsやa/apps/panel/appletsの中身も見てみます。上で導入したオブジェクトの中でメインメニューはオブジェクト(アプレットでない)なので、/apps/panel/objects内に記述があるはずです。実際にこの項目を見ると、
/apps/panel/objects/object_1:
toplevel_id = panel_1
object_type = menu-bar
position = 79
などの項目が与えられます。この中で、object_1はメインメニューに対応するオブジェクトのはずですが、menu-barの名前が3行目にあるので、確かにこのオブジェクトがメインメニューに対応することがわかります。他にpositionはメニューの位置を表し、toplevel_idはこのオブジェクトがどのパネルに含まれるかを表します。panel_1は上側のパネルなのでこれで正しいわけです。他にlockedという項目がありましたがこの項目はおそらくそのオブジェクトが"ロック"されているかを表します。"ロック"はオブジェクトの移動を禁止する機能で、右クリックメニューから選ぶことができます。
 
更にアプレットについては次のような項目が存在します。
/apps/panel/applets/applet_0:
toplevel_id = panel_1
bonobo_iid = OAFIID:GNOME_NotificationAreaApplet
object_type = bonobo-applet
position = 606
ここで、object_typeはbonobo-appletとなっていますが、この項目はアプレット全般に対して用いられます。また、toplevel_idとpositionについては既に扱いました。最後にbonobo_iidですが、これはBONOBOの機構内でどのプロセスからの入力を扱うかを定める1つの文字列です。ここではNotificationAreaの文字があるので、このアプレットが"通知スペース"に対応することがわかります。
<!--
他のアプレットについても同様の設定がありますが、applet_idとbonobo_iidだけを書くと、
-->
 
ここまででgnome-panelの設定に関する実例を見てきました。これらの設定の変更を扱うために、関数panel_profile_loadの中ではgconfの設定内のパネル、オブジェクト、アプレットそれぞれの設定に対して、panel_profile_load_listという関数が呼ばれています。関数panel_profile_load_listもpanel_profile_loadと同じファイル内で定義されているのですが、この関数は関数内でgconf_client_notify_addを呼び出しています。
 
gconf_client_notify_addの引数は、パネルの設定に対してこの関数が呼ばれた場合とオブジェクトやアプレットに対して呼ばれた場合で変化します。例えば、オブジェクトの設定を読んだ場合には関数panel_profile_object_id_list_notifyが引数として与えられます。この関数はどのオブジェクトが消えたり追加されたりしたのかを把握し、オブジェクトに対応するウィジェットを追加したり取り除くという作業を行う関数です。実際に関数panel_profile_object_id_list_notify内ではpanel_profile_load_added_idsとpanel_profile_delete_removed_idsの2つの関数が呼ばれていますが、これらの関数は名前の通りの動作をし、付け加えられた(added)オブジェクトを読み出したり(load)、取り除かれた(removed)オブジェクトを解放したり(delete)します。
 
実際にウィジェットを追加するのは関数panel_profile_object_id_list_notifyの最後で呼ばれている関数panel_applet_load_queued_appletsですが、この関数は既に見た関数で、gconfから受け取ったリストを用いて、対応するウィジェットを作成する関数です。ここでは、関数panel_profile_object_id_list_notify内でリストの変更が取り入れられているので、ウィジェトの追加や削除を行うことができるわけです。
 
ここまででgconfの設定をアプリケーションの動作中に反映するための機構を見て来ました。これらはアプリケーションの起動時にしか変更を反映できない方法と比べて優れた方法です。同様の方法は他のGNOMEアプリケーションでも用いられており、gconfがGNOMEのライブラリとして重要であることを示しています。
 
==== アプレットの動作に必要なライブラリ ====
 
既に"アプレット"がlibbonoboを通じて実現されていることを述べました。ここで、libbonoboはプロセス間通信を行うための一般的な技術です。これは、アプレットを扱うプロセスとgnome-panelのプロセスの間の通信を行うために用いられます。
 
アプレットはlibbonoboに加えてlibbonobouiライブラリに含まれる技術も用いています。libbonobouiライブラリはlibbonoboを用いてあるプロセスで制御されるGTKウィジェットを他のプロセスのウィジェットに埋め込む一般的なライブラリです。
 
===== GtkPlug, GtkSocket =====
 
まず最初に、GTKを用いたウィジェットの埋め込みを扱います。GTKの枠組みでウィジェットの埋め込みを行うには、GtkPlug, GtkSocketのウィジェットを使います。GtkSocketは、ウィジェットを埋め込まれる側のプロセスが作成するウィジェットで、GtkPlugが実際に埋め込まれるウィジェットに対応します。
 
X上で動くGTKのプロセス中では、GtkPlug, GtkSocketは埋め込まれるウィンドウを決めるために、WindowIDを用います。WindowIDはXのウィンドウに与えられる一意の数値で、型はXID(大抵unsigned long)で与えられます。
:サンプルコード
 
X上でのWindowIDを知るには、xwininfoコマンドを使うのが簡単です。
$xwininfo
これは指定されたウィンドウのWindow IDやジオメトリ(位置と大きさ)などの情報を与えます。
 
GtkPlug, GtkSocketを用いて埋め込みを行うには、GtkSocketにGtkPlugのWindowIDを伝える必要があります。このためのlibbonoboui内のクラスとして、BonoboPlug, BonoboSocketの両クラスがあります。これらはそれぞれGtkPlug, GtkSocketを継承します。
 
実際に埋め込みを行うために、BonoboPlug, BonoboSocketはそれぞれBonoboControl, BonoboControlFrameを使います。ここで、BonoboControlはgetWindowIDという名の"メソッド"を持っており、他のプロセスで実行されるBonoboControlFrameにBonoboPlugのWindowIDを伝えます。ただし、libbonobo, libbonobouiのバージョンとして、2.16.0を用いました。
 
BonoboControlFrameはGtkWidgetを継承していないため、埋め込まれる側のウィジェットの配置に手間がかかります。この手間を省くため、BonoboControlFrameを"private"なメンバとして持ったクラスBonoboWidgetが存在します。
 
gnome-panel中でも、/gnome-panel/panel-applet-frame.c内でBonoboWidgetが用いられています。panel-applet-frameはBonoboWidgetを収納するGtkWidgetで、GtkEventBoxを継承します。GtkEventBoxはGtkBinクラスを継承したウィジェットで1つのウィジェットを収納します。
 
一方gnome-panelでは、埋め込むウィジェットを提供する機構としてPanelAppletクラスが提供されています。(./libpanel-applet/panel-applet.[ch]を参照)このクラスはBonoboControlへのポインタを所持します。各アプレットはこのクラスを継承し、PanelAppletFrameと相互作用します。
 
==== 各種アプレットの動作 ====
 
既にgnome-panelの動作は"アプレット"によって拡張されることを見てきました。ここでアプレットはBonoboと呼ばれる技術を用いて作られており、これらはgnome-panelとは異なったプロセス内で動作しています。この時、なぜ単純にgnome-panelの新たなオブジェクトとして各機能を作成しなかったのかが疑問に思われます。
 
詳細は不明ですがこの方式の明らかな利点として、各アプレットを作成する言語として、[[C言語]]以外の言語を選べることがあげられます。実際gnome-applets内に含まれるアプレットでinvest-applet(gnome-applets-x.x.x/invest-applet)は[[w:Python]]を用いて作成されています。ここでgnome-appletsはGNOMEから配布されているファイルで、gnome-panelの各種アプレットを扱っています。この中には音量調節(gnome-applets-x.x.x/mixer)やごみ箱(gnome-applets-x.x.x/trashapplet)などのアプレットが含まれています。ただし、gnome-appletsのバージョンとしてはgnome-applets-2.16.2を用いました。
 
これに加えて既に登場した時計、通知スペースなどのアプレットがgnome-panel内に含まれています。(それぞれ./applets/clock, ./applets/notification_area内のファイル)ここでは、Bonoboの詳細には触れずに、各種"アプレット"の動作を見ていきます。これはBonoboを使う場合でもアプレット自体は通常のGTK+アプリケーションと同じように書くことができるからです。このことの詳細については[http://developer.gnome.org/doc/API/2.0/libbonobo/index.html]などを参照してください。
 
===== gnome-panel内のアプレット =====
====== 時計アプレット ======
 
時計アプレットはその名の通り[[w:時計]]を表示するアプレットです。このアプレットはgnome-panelの./applets/clock以下に含まれています。時計アプレットの仕事はおおよそGTK+を使って時計を作成することです。簡単な時計の作り方については例えば[[Xプログラミング]]を参照してください。
:[[画像:gnome_panel_clock_applet.png|200px]]
(赤線は筆者が導入した)基本的に時計アプレットの本体は時刻の数値をテキストとして書き込まれたGtkLabelです。GtkLabelは既にGtkFixedの中で用いたのでここでは説明しません。対応するGtkLabelは./applets/clock/clock.c内のcreate_clock_widget関数中で作られています。時刻の書き換えはclock_timeout_callback中で呼ばれるupdate_clockで行われます。この間数はgtk_label_set_textを用いてGtkLabel内の数値を変えた後、gtk_widget_queue_resize関数を呼びます。この関数はGtkWidget及びそれを継承したウィジェットに対してその変更を画面に反映するために呼ばれます。同種の関数にgtk_widget_queue_draw(_area)がありますが、gtk_widget_queue_resizeは変更によってウィジェットのサイズが変わる場合に呼ばれます[http://developer.gnome.org/doc/API/2.0/gtk/GtkWidget.html]。一方、gtk_widget_queue_drawはウィジェットのサイズを変えません。
 
追加の機能として、時計アプレットは24時間表示と12時間表示を切り替えたり、カレンダーを表示したりといくつかの機能があります。前者はGtkLabelのフォーマットを変更するだけで書き換えられますが、後者は多くの操作が必要となります。実際にはカレンダーはGtkCalendarとしてGTK+のウィジェットが与えられているため、時計アプレット内ではそれが用いられています。GtkCalendarについてはGTK+のソースを参照してください。
 
また、時計アプレットはロケールを変更してgnome-panelを起動すると表示が変化します。次の例はロケールをen_USに変更した例です。ただし、シェルとして[[w:bash]]を用いています。
$LANG=en_US gnome-panel
:[[画像:gnome_panel_clock_applet_en_US.png]]
(赤線は筆者が導入した)
 
ロケールの変更による時刻のフォーマットの変更は、gettextライブラリによって行われます。gettextの詳細は[[OSS開発ツール]]を参照してください。実際のpoディレクトリは./po/以下で与えられます。この中では各国語でのフォーマットが文字列の形で定義されています。
 
====== wnckアプレット ======
 
wnckアプレット(wnck-applet)は、"libwnck"を用いるアプレット群で、GNOMEデスクトップのウィンドウの管理を行います。このアプレット群は複数のアプレットを含んでおり、これらはそれぞれ"デスクトップの表示"(ShowDesktop), "ウィンドウ一覧"(WindowList), "ウィンドウセレクタ"(WindowMenu), "ワークスペース切替器"(WorkspaceSwitcher)が含まれます。既にアプレットの例でwnck-appletが起動されている場面を見ました。これらはここで与えられる複数のアプレットに対応するプロセスです。
 
ここで、それぞれのアプレットの機能を簡単に紹介します。"デスクトップの表示"は全てのウィンドウを最小化し、デスクトップを表示します。実はデスクトップは後に述べる[[w:Nautilus]]の画面なのですがここでは触れません。ウィンドウの大きさを変更する機能はウィンドウマネージャの機能であるため、対応するウィンドウマネージャがlibwnckの要求を受けない場合には、このアプレットは機能しません。実際twmを用いて"デスクトップの表示"を動かしたところ、エラーメッセージが表示されました。
 
次に"ウィンドウ一覧"と"ウィンドウセレクタ"はどちらもその時点で存在するウィンドウを選択するためのアプレットです。ただし、"ウィンドウセレクタ"はこれをWnckSelectorとして与え、"ウィンドウ一覧"はWnckTasklistとして与えます。
:実行例
"ウィンドウ一覧"はよく用いられるアプレットで、既に意識せずに使っているかも知れません。こちらもtwmで管理されるウィンドウは表示されません。
 
"ワークスペース切替器"は何枚もの画面(ワークスペース)があるように見せ、それらを切り替えながら使うことで画面を広く使うアプレットです。こちらもtwmと同時には使えません。
 
 
====== 通知スペース(NotificationArea)アプレット ======
 
{{スタブ}}
 
===== gnome-applets内のアプレット =====
 
gnome-appletsにもいくつかのアプレットが含まれています。ここでは比較的動作がわかりやすいアプレットを選んで紹介します。具体的には"CPU周波数"(cpufreq)アプレットと"音量調節"(mixer)アプレットを扱います。
 
cpufreqアプレットは使っている[[w:コンピュータ]]の[[w:クロック周波数]]を表示するアプレットです。クロック周波数はCPUの動作速度を表す指標で、基本的にはこの数値が大きい程速いCPUであるといえます。ただし、コンピュータを使う際の体感速度は、[[w:メモリ]]の量などCPU以外の条件にもよるので、この数字だけでコンピュータの性能が決まるわけではありません。
 
[[UNIX/Linux入門|Linux]]上では、使っているCPUの周波数は"ファイル"として利用者から利用できるようになっています。ただし、この値を変更してもハードウェアが変更されるわけではなく、この機能はシステムの状態を把握することを目的とした機能です。CPU周波数の情報は/proc/cpuinfo、もしくは/sys以下のディレクトリに記録されています。cpufreqアプレットはこれらの値を読み出して表示します。
 
 
== 関連項目 ==
[[OSS開発ツール/GUIツールキット]]
 
{{NDC|007.64|GNOMEふれむわく}}