GTK+
The GIMP Toolkit

General
Introduction
Screenshots
Download
Mailing Lists
Language Bindings
Themes
Bug Tracker

Documentation
FAQ
Tutorial
API Reference
Published Books

Projects
Pango
GNOME
GTK+ for Win32
GTK+ on DirectFB
GTK+ for BeOS

Applications
GIMP
Abiword
Dia
Glade
GnuCash
Gnumeric

GNOME Software Map

この文書の日本語訳は、芳賀靖史(yasufumi.haga@nifty.com)が、 2002年8月26日に行ないました。誤訳、その他間違い等がありましたら ご連絡下さい。

目次
1. 一般情報
1.1. まずはご挨拶
1.2. 作者について
1.3. GTK+ とは何か
1.4. GTK+ の "+" はどういう意味?
1.5. GTK+ や GDK、それにGLib の"G"とは何の意味?
1.6. GTK+ に関するドキュメントはどこにあるの?
1.7. GTK+ に関するメーリングリスト(あるいはそのアーカイブ)はあるの?
1.8. GTK+ で助けが欲しい時はどうするの?
1.9. GTK+ のバグはどうやって報告すればいいの?
1.10. GTK+ の Windows 版はあるの?
1.11. GTK+で書いたアプリケーションには、どんなのがあるの?
1.12. GTK+で書くアプリケーションを探してるんだけど、 IRC クライアントはどうだろうか?
2. GTK+ はどこにあって、構成やインストール、障害の復旧はどうやるの?
2.1. GTK+ を動かすには何が必要?
2.2. GTK+ はどこで入手できるの?
2.3. GTK+の構成とコンパイルはどうやるの?
2.4. GTK+のコンパイル中に次のようなエラーが出たmake: file `Makefile' line 456: Syntax error
2.5. GTK+ をコンパイルしてインストールしてみたけど、 リンクするものが何もない
2.6. GTK+ を使ってプログラムをコンパイルすると、glibconfig.h が見つからないという、コンパイルエラーメッセージが出る
2.7. GTK+ アプリケーションをインストールすると、GTK が見つからないというレポートが出る
3. GTK+ の開発
3.1. みんながずっと話しているCVSっていうのはどんなもので、 どうやってアクセスすればいいの?
3.2. どうやれば GTK+ に 貢献できるの?
3.3. 送ったパッチが採用されたかどうかはどうすればわかるの?もし採用されない場合、それはどうして?
3.4. 新しいウィジェットをライブラリに組み入れるにあたってのポリシーは何?
3.5. C 以外の言語のバインディング作業をしている人は誰かいるの?
4. GTK+ を用いた開発:初めての開発
4.1. どうやって始めればいいの?
4.2. GTK+ でセキュリティに注意を要するプログラムや SUID/SGID プログラムを書くにはどうすればいいの? GTK+ は安全? GTK_MODULES にセキュリティホールがあると聞いたけど、それは何?
4.3. Hello World という小さなプログラムをコンパイルしようとしたけど失敗した。何か手がかりはある?
4.4. make ユーティリティを 使う場合は、どうするのだろうか?
4.5. makefile でバッククォートを使っているけど、make の最中に失敗した。
4.6. configure を追加したいのですが、どうすればいいですか?
4.7. GTK+ のアプリケーションを gdb でデバッグしたいのだけど、 あるブレークポイントにくると、X サーバーがハングする。どうして?
5. GTK+ を用いた開発:一般的な質問
5.1. GTK にはどんなウィジェットがあるの?
5.2. GTK+ はスレッドセーフなの?GTK+ でマルチスレッドのアプリケーション はどうやって書くの?
5.3. GTK+ アプリで fork() すると、妙な 'x io error' が起こるのはなぜ?
5.4. ボタンを押した時にボタンの内容が移動しないのはなぜ?そういうふうに動作させるパッチはこれなんだけど...
5.5. あるウィジェットがトップレベルウィンドウなのか、それとも他の親なのかは、どうやって識別すればいいの?
5.6. GtkWindow のウィンドウIDはどうやって取得するの?
5.7. (例えばリストウィジェットで)ダブルクリックのイベントはどうやって捉えるの?
5.8. ところで、シグナルとイベントはどう違うの?
5.9. delete イベント(あるいは他のイベント)のハンドラに渡したデータが壊れてしまう
5.10. イベント(これは何でも良いけど)に接続したシグナルがあるけど、捕捉できない。 なにがまずいの?
5.11. 新しいシグナルを GTK+ ウィジェットに追加する必要があるのだけど、何か方法は?
5.12. その領域内にぴったり収まるように切り詰めたテキストを表示することは可能?
5.13. ウィンドウをモーダルにするのはどうやるの?/ウィンドウを一つだけ アクティブにするのはどうやるの?
5.14. ウィジェット(例えば 進捗表示バー)が更新されないのはなぜ?
5.15. データと GTK+ のオブジェクト/ウィジェットとの関連づけって、どうやるの?
5.16. オブジェクトに関連づけたデータは、どうやって削除するの?
5.17. ウィジェットの親はどうやって変えるの?
5.18. ウィジェットの位置はどうやればわかるんですか?
5.19. ウィジェットやウィンドウの大きさはどうやって設定すればいいの?ユーザーに ウィンドウの大きさを変えさせたくないのだけど、どうすればいいの?
5.20. GTK+ アプリケーションにポップアップメニューを追加するのはどうやるの?
5.21. ボタンのような、ウィジェットは、どうやれば無効にしたり有効にしたりできるの?
5.22. gtk_clist_* 関数のテキスト引数は const で宣言してはだめ?
5.23. どうやれば、ピクセル(イメージデータ)を画面に描画できるの?
5.24. ウィンドウをリアライズしたり表示したりせずに、ピクスマップを 作るのはどうやるの?
5.25. ドラッグ&ドロップはどうやるの?
5.26. GTK+/GLib にメモリリークがあるのはなぜ?
6. GTK+ を用いた開発:ウィジェット特有の質問
6.1. GtkList のセレクションはどうやって見つければいいの?
6.2. リストをスクロールしても、GtkList のカラムヘッダーが消えないようにするには どうすればいいの?
6.3. 自分のアプリケーションのユーザーには、GtkCombo にテキスト入力をしてもらいたくない のだけれど、何か良い方法は?
6.4. コンボボックスが変更されたことは、どうやって捕捉するの?
6.5. どうやればメニューの中にセパレータを定義できるの?
6.6. ヘルプみたいに、メニューを右寄せにするには、どうやればいいの?
6.7. どうやれば下線付きのアクセラレータをメニュー項目に追加できるの?
6.8. どうやれば GtkMenuItem からテキストを取り出せるの?
6.9. GtkLabel を左右いずれかに寄せるには、どうやればいいの?
6.10. GtkLabel ウィジェットの背景色はどうやって設定するの?
6.11. リソースファイルを使って GtkLabel の色とフォントをセットするにはどうやるの?
6.12. どうやれば、リソースファイルでツールチップが構成できるの?
6.13. GtkEntry に(だいたい)2000 超の文字を追加できない。なにがまずいの?
6.14. リターンキーを押した時に GtkEntry ウィジェットをアクティブにするのは、どうやればいいの?
6.15. GtkEntry へ入力したものを 確認/制限/フィルター するには、どうやればいいの?
6.16. GtkText ウィジェットで水平スクロールバーはどうやって使えばいいの?
6.17. GtkText ウィジェットのフォントはどうやって変えればいいの?
6.18. GtkText オブジェクトで、カーソル位置を設定するにはどうやればいいの?
7. GDK について
7.1. GDKって何?
7.2. 色の割り当てはどうやって使えばいいの?
8. GLib について
8.1. GLib って何?
8.2. 二重リンクリストはどういうふうに使えるの?
8.3. アロケートしたリストのノードを free しても、メモリが解放されないようだ。
8.4. g_print や g_malloc、 g_strdup それに同類の glib 関数を使うのはなぜ?
8.5. GScanner ってどんなもので、どうやって使うの?
9. GTK+ FAQ への貢献と保守担当および著作権について

第 1 章 一般情報


1.6. GTK+ に関するドキュメントはどこにあるの?

GTK+ に関するドキュメントはどこにあるの?

GTK+ ディストリビューションの doc/ ディレクトリの中に、GTK と GDK 両方の リファレンス資料と、この FAQ、それに GTK のチュートリアルが見つかります。

さらに、以下のサイトに行けば、こういったドキュメントの HTML 版 へのリンク が見つかります http://www.gtk.org/ SGML や HTML、ポストスクリプト、DVI、それにテキストになっている GTK チュートリアルをパッケージにしたものは、以下のサイトで 見つかります。 ftp://ftp.gtk.org/pub/gtk/tutorial

今では、GTK+ や GDk、それに GNOME のプログラミングを扱っている 書籍が、二種類利用可能になっています。

  • Eric Harlows 著 "Developing Linux Applications with GTK+ and GDK"。 ISBN は 0-7357-0021-4。

  • Eric の本に載っているコード例は、以下のサイトで オンラインで利用可能です。 http://www.bcpl.net/~eharlow/book

  • Havoc Pennington は "GTK+/GNOME Application Development" という 本を出版しました。ISBN は 0-7357-0078-8 です。

    この本の無償版は次のサイトにあります。 http://developer.gnome.org/doc/GGAD/

    Harvoc は次のサイトで、この本に関する情報や正誤表を保守 しています。 http://pobox.com/~hp/gnome-app-devel.html


1.9. GTK+ のバグはどうやって報告すればいいの?

GTK+ のバグはどうやって報告すればいいの?

バグは GNOME バグ追跡システムまで報告して下さい。次のサイトです (http://bugzilla.gnome.org) このシステムを使って新しいバグレポートを登録できるようにするには、 その前に自分のメールアドレスを入力して、パスワードを受け取る必要 があります。

バグレポートを投稿する際は、たくさんのオプションを選択したり、いろいろ記入 します。情報が多ければ多いほど、問題の原因を突き止めるのが簡単になることは 覚えておいて下さい。後で役に立つことになるかもしれない、他の情報としては 次ようなものがあります。

  • バグの再現方法

    gtk/ サブディレクトリに組み込まれている、testgtk プログラムで そのバグが再現できる場合は、一番好都合です。さもなければ、 そのバグの動きを示す、短いテストプログラムを含めて下さい。 最後の手段としては、ダウンロード可能なたくさんのソフトウェア へのポインターを教えてくれても構いません。

    (GIMP 内で再現できるバグなら、testgtk で再現できるバグと ほとんど同じくらい好都合です。GIMP で見つかったバグを 報告するつもりなら、お使いの GIMP のバージョン番号も 報告して下さい。)

  • そのバグがクラッシュなら、そのクラッシュが発生した時に表示された テキストを正確に報告して下さい。

  • スタックのトレースといったような情報も役立つかもしれませんが、 必要なわけではありません。でも、実際に送るスタックトレースが X のエラー のものであれば、コマンドオプションの --sync を つけてテストプログラムを動かしてそのスタックトレースが取れるのなら、 いっそう役に立つでしょう。


1.11. GTK+で書いたアプリケーションには、どんなのがあるの?

GTK+で書いたアプリケーションには、どんなのがあるの?

GTK+ ベースのアプリケーションの一覧は、GTK+ ウェブサーバー http://www.gtk.org/apps/ で 見つかります。そこには 350 個以上のアプリケーションがあります。

そこが閲覧できなければ、GNOME プロジェクト http://www.gnome.org/ のために一生懸命活動しているプロジェクトを探して下さい。そしてゲームを 作ったり、役に立つものを作って下さい。

そういったプロジェクトの中には次のようなものもあります。


Chapter 2. GTK+ はどこにあって、構成やインストール、障害の復旧はどうやるの?


2.2. GTK+ はどこで入手できるの?

GTK+ はどこで入手できるの?

公式サイトは、 ftp://ftp.gtk.org/pub/gtk です。

このサイトは GTK+ の新しいリリース時期あたりは混み合う傾向にあるので、 次のサイトに載っている、ミラーサイトを使ってみて下さい。 ftp://ftp.gtk.org/etc/mirrors

GTK+ を始めてみるための足掛かりになるミラーサイトをいくつか載せておきます。


2.5. GTK+ をコンパイルしてインストールしてみたけど、 リンクするものが何もない

GTK+ をコンパイルしてインストールしてみたけど、 リンクするものが何もない

この問題は、GTK+ ライブラリが見つからないか、バージョンが違う場合に よく遭遇するものです。普通はコンパイラが 'unresolved symbol' だと、 文句を言うものです。確認する必要があるのは二つです。


2.6. GTK+ を使ってプログラムをコンパイルすると、glibconfig.h が見つからないという、コンパイルエラーメッセージが出る

GTK+ を使ってプログラムをコンパイルすると、glibconfig.h が見つからないという、コンパイルエラーメッセージが出る

"glibconfig.h" というヘッダーファイルは $exec_prefix/lib/glib/include/ に 移動しました。$exec_prefix は、GTK+ のコンパイル時に ./configure に付けた --exec-prefix フラグで指定したディレクトリです。$exec_prefix は、ディフォルト では $prefix になっています(これは --prefix フラグで指定したものです)。 同様に、$prefix は /usr/local/ がディフォルトになっています。

こうなったのは、"glibconfig.h" にはアーキテクチャに依存する情報が入っている からです。残りのインクルードファイル $prefix/include に入っており、異なる アーキテクチャ間で共有できるものです。

GTK+ には /gtk-config/ というシェルスクリプトが 含まれており、正しいインクルードファイルを見つけやすくなっています。 GTK+ のチュートリアルには /gtk-config/ の使用例 が載っており、コマンドラインから簡単なコンパイルをしています。 もっと込み入った構成についての情報は、GTK+ ディストリビューションにある docs/gtk-config.txt ファイルを見て下さい。

古いプログラムをコンパイルするつもりなら、次のようなコマンドを使って 構成すれば問題を回避できるかも知れません。

setenv CPPFLAGS "-I/usr/local/include/glib/include"
./configure

(/usr/local の代わりに、適切な $exec_prefix の値に 置き換えて下さい)


2.7. GTK+ アプリケーションをインストールすると、GTK が見つからないというレポートが出る

GTK+ アプリケーションをインストールすると、GTK が見つからないというレポートが出る

これには、共通の理由が いくつかあります。

上記のいずれも助けにならない場合は、config.log を調べてみて下さい。 これは /.configure を実行すると生成されるものです。このファイルの 一番最後にあるのが、失敗する直前に行なった最後の動作です。それが ソースコードのセクションなら、そのソースコードをファイルにコピー して、config.log 内の、そのちょうど上にある行を使ってコンパイルして ください。コンパイルが成功なら、その結果を実行してみて下さい。


Chapter 3. GTK+ の開発

3.1. みんながずっと話しているCVSっていうのはどんなもので、 どうやってアクセスすればいいの?

みんながずっと話しているCVSっていうのはどんなもので、 どうやってアクセスすればいいの?

CVS というのは "Concurent Version System" のことで、ソフトウェア プロジェクトでバージョン管理を行なうとても一般的な方法です。 CVS は複数のプログラム作者が同一のソースツリーで同時に作業できる ように設計されています。このソースツリーは中央で保守されていますが、 各開発者はそれぞれ、自分の所にこのリポジトリのコピーを持っており、 そこに変更を加えます。

GTK+ の開発者たちは GTK+ のその時点での開発バージョンの マスターコピーを格納するのに、このCVS のリポジトリを使います。 そういったふうですから、GTK+ にパッチを寄贈したい人たちは、 そのCVS バージョンに対するリポジトリを作って下さい。一般の人たちは パッケージでリリースされたものを使う方がいいでしょう。

CVS のツールセットは、通常の RedHat のサイトで RPM パッケージに なっているものを利用できます。最新版は、 http://download.cyclic.com/pub/ にあるものが利用できます。

GTK+ の最新 CVS バージョンは、次のステップをたどって匿名アクセスを 使えば、だれでもダウンロードできます。

  • bourne シェル系では 次のようにタイプします。

    CVSROOT=':pserver:anonymous@anoncvs.gnome.org:/cvs/gnome'
    export CVSROOT

  • 次に、最初にソースツリーをチェックアウトする際、CVS ログインが 必要です。

    cvs login

    こうすると、パスワードを尋ねてきます。cvs.gimp.org にはパスワードは ありませんから、エンターキーを押すだけで構いません。

  • ツリーを取得して自分の所の現在のディレクトリのサブディレクトリに 置く場合は、次のコマンドを実行します。

    cvs -z3 get gtk+

    GTK+ 1.1 のツリーでは、glib が個別の CVS モジュールに移動しました。 ですから、glib をインストールしてない場合は、同様に glib も入手する 必要があります。このことは心に留めておいて下さい。

    cvs -z3 get glib


3.5. C 以外の言語のバインディング作業をしている人は誰かいるの?

C 以外の言語のバインディング作業をしている人は誰かいるの?

GTK+ のホームページ( http://www.gtk.org/)では、GTK+ のバインディング一覧を載せています。

  • GTK 用の C++ ラッパーがいくつかあります。

    • gtk-- パッケージ。これは GTK+ 用の非常の小さなラッパーです。このラッパーの ホームページは次のサイトにあります。 http://www.cs.tut.fi/~p150650/gtk/gtk--.html この FTP サイトは ftp://ftp.gtk.org/pub/gtk/gtk-- です。

    • VDK パッケージ。これは ボーランドの様式に似た、GTK+ アプリケーション ビルダーのベースパッケージとして構築されたものです。 このパッケージのホームページは http://www.guest.net/homepages/mmotta/VDKHome です。

    • wxWindows/Gtk パッケージ。これはクロスプラットフォームで GUI を開発する ための、フリーの C++ ライブラリです。このパッケージのホームページは http://www.freiburg.linux.de/~wxxt/ です。

  • 現在開発中の Objective-c バインディングが三つ知られています。

    • http://www.gnome.org/ の パッケージで好まれるのは objgtk です。これは オブジェクトクラスが基礎に なっており、Elliot Lee が 保守しています。当然 GTK+ 用の 「標準的な」Objective-c バインディングとして 認められています。

    • GNUstep project に傾倒 している場合は、Helge He゚ が 作っている GTKKit をチェックする必要があるかも知れません。

    • GToolKit パッケージ。これは ftp://ftp.gtk.org/pub/gtk/objc-gtoolkit/ で見つかります。

  • Perl バインディング ftp://ftp.gtk.org/pub/gtk/perl

  • Guile バインディング。このホームページは http://www.ping.de/sites/zagadka/guile-gtk です。 ちなみに、Guile は R4RS Scheme (標準)を GNU プロジェクトが実装したものです。 Scheme が気に入っている場合は、これをちょっと見てみる必要があるかもしれません。

  • David Monniaux は次のように報告しています。 「gtk-O'Caml のバインディングシステムを開始しました。 システムの基本的な部分は、コールバックも含めて、うまく動作しています。 現在の開発は http://www.ens-lyon.fr/~dmonniau/arcs で行なっています。」

  • python のバインディングがいくつか 作られました。

  • GTK+ 用に使える、OpenGL/Mesa ウィジェットが二つあります。 http://www.student.oulu.fi/~jlof/gtkglarea/index.html から始めるのが 良いと思います。

  • 最後に、Eiffel や TOM、Pascal、 Pike 等々 といった他の言語の バインディングもたくさんあります。


Chapter 4. GTK+ を用いた開発:初めての開発

4.1. どうやって始めればいいの?

どうやって始めればいいの?

だったら、GTK+ をインストールしてから、それを使って徐々に アプリケーションを作れるようにするものが二つあります。一つは GTK+ のチュートリアル http://www.gtk.org/tutorial/ ですが、これは現在作成途上にあります。 これは C を使ってアプリケーションを書く手ほどきをしてくれます。

このチュートリアルには、GTK+ にあるウィジェットすべての情報が 含まれているわけではありません。GTK+ ウィジェットすべての基本的な 使い方に関するコード例については、GTK+ のディストリビューションにある、 gtk/testgtk.c ファイル(とその関連ソースファイル)を見る方がいいです。 これらの例を見れば、ウィジェットでできることに関する充分な基礎知識 が得られます。


4.6. configure を追加したいのですが、どうすればいいですか?

configure を追加したいのですが、どうすればいいですか?

autoconf/automake を使うには、最初に適切なパッケージをインストール しなければなりません。次のようなものです。

これらのパッケージは GNU のメイン ftp サーバー (ftp://ftp.gnu.org/)、 あるいは、どこかのミラーサーバーで見つかります。

この強力な autoconf/automake 手法を使用するには、次のような configure.in を作らなければなりません。

dnl Process this file with autoconf to produce a configure script.
dnl configure.in for a GTK+ based program

AC_INIT(myprg.c)dnl
AM_INIT_AUTOMAKE(mypkgname,0.0.1)dnl
AM_CONFIG_HEADER(config.h)dnl

dnl Checks for programs.
AC_PROG_CC dnl check for the c compiler
dnl you should add CFLAGS="" here, 'cos it is set to -g by PROG_CC

dnl Checks for libraries.
AM_PATH_GTK(1.2.0,,AC_MSG_ERROR(mypkgname 0.1 needs GTK))dnl

AC_OUTPUT(
	Makefile
)dnl

それから Makefile.am ファイルを 追加する必要があります。

bin_PROGRAMS    = myprg
myprg_SOURCES   = myprg.c foo.c bar.c
INCLUDES        = @GTK_CFLAGS@
LDADD           = @GTK_LIBS@
CLEANFILES      = *~
DISTCLEANFILES  = .deps/*.P

プロジェクトに二つ以上サブディレクトリがある場合は、各ディレクトリ ごとに Makefile.am を一つ作り、さらに以下のような、マスター Makefile.am を作らなければなりません。

SUBDIRS         = mydir1 mydir2 mydir3

then, to use these, simply type the following commands:

aclocal
autoheader
autoconf
automake --add-missing --include-deps --foreign 

詳細は、autoconf と automake のドキュメントを見て下さい (出荷されている info ファイルは実に理解しやすくなっています。 また autoconf と automake を扱っているウェブ上のリソースも たくさんあります)。


Chapter 5. GTK+ を用いた開発:一般的な質問

目次
5.1. GTK にはどんなウィジェットがあるの?
5.2. GTK+ はスレッドセーフなの?GTK+ でマルチスレッドのアプリケーション はどうやって書くの?
5.3. GTK+ アプリで fork() すると、妙な 'x io error' が起こるのはなぜ?
5.4. ボタンを押した時にボタンの内容が移動しないのはなぜ?そういうふうに動作させるパッチはこれなんだけど...
5.5. あるウィジェットがトップレベルウィンドウなのか、それとも他の親なのかは、どうやって識別すればいいの?
5.6. GtkWindow のウィンドウIDはどうやって取得するの?
5.7. (例えばリストウィジェットで)ダブルクリックのイベントはどうやって捉えるの?
5.8. ところで、シグナルとイベントはどう違うの?
5.9. delete イベント(あるいは他のイベント)のハンドラに渡したデータが壊れてしまう
5.10. イベント(これは何でも良いけど)に接続したシグナルがあるけど、捕捉できない。 なにがまずいの?
5.11. 新しいシグナルを GTK+ ウィジェットに追加する必要があるのだけど、何か方法は?
5.12. その領域内にぴったり収まるように切り詰めたテキストを表示することは可能?
5.13. ウィンドウをモーダルにするのはどうやるの?/ウィンドウを一つだけ アクティブにするのはどうやるの?
5.14. ウィジェット(例えば 進捗表示バー)が更新されないのはなぜ?
5.15. データと GTK+ のオブジェクト/ウィジェットとの関連づけって、どうやるの?
5.16. オブジェクトに関連づけたデータは、どうやって削除するの?
5.17. ウィジェットの親はどうやって変えるの?
5.18. ウィジェットの位置はどうやればわかるの?
5.19. ウィジェットやウィンドウの大きさはどうやって設定すればいいの?ユーザーに ウィンドウの大きさを変えさせたくないのだけど、どうすればいいの?
5.20. GTK+ アプリケーションにポップアップメニューを追加するのはどうやるの?
5.21. ボタンのような、ウィジェットは、どうやれば無効にしたり有効にしたりできるの?
5.22. gtk_clist_* 関数のテキスト引数は const で宣言してはだめ?
5.23. どうやれば、ピクセル(イメージデータ)を画面に描画できるの?
5.24. ウィンドウをリアライズしたり表示したりせずに、ピクスマップを 作るのはどうやるの?
5.25. ドラッグ&ドロップはどうやるの?
5.26. GTK+/GLib にメモリリークがあるのはなぜ?

5.1. GTK にはどんなウィジェットがあるの?

GTK にはどんなウィジェットがあるの?

GTK+ のチュートリアルには、次のウィジェットが一覧になっています。

  GtkObject
   +GtkData
   | +GtkAdjustment
   | `GtkTooltips
   `GtkWidget
     +GtkContainer
     | +GtkBin
     | | +GtkAlignment
     | | +GtkEventBox
     | | +GtkFrame
     | | | `GtkAspectFrame
     | | +GtkHandleBox
     | | +GtkItem
     | | | +GtkListItem
     | | | +GtkMenuItem
     | | | | `GtkCheckMenuItem
     | | | |   `GtkRadioMenuItem
     | | | `GtkTreeItem
     | | +GtkViewport
     | | `GtkWindow
     | |   +GtkColorSelectionDialog
     | |   +GtkDialog
     | |   | `GtkInputDialog
     | |   `GtkFileSelection
     | +GtkBox
     | | +GtkButtonBox
     | | | +GtkHButtonBox
     | | | `GtkVButtonBox
     | | +GtkHBox
     | | | +GtkCombo
     | | | `GtkStatusbar
     | | `GtkVBox
     | |   +GtkColorSelection
     | |   `GtkGammaCurve
     | +GtkButton
     | | +GtkOptionMenu
     | | `GtkToggleButton
     | |   `GtkCheckButton
     | |     `GtkRadioButton
     | +GtkCList
     |   `GtkCTree
     | +GtkFixed
     | +GtkList
     | +GtkMenuShell
     | | +GtkMenuBar
     | | `GtkMenu
     | +GtkNotebook
     | +GtkPaned
     | | +GtkHPaned
     | | `GtkVPaned
     | +GtkScrolledWindow
     | +GtkTable
     | +GtkToolbar
     | `GtkTree
     +GtkDrawingArea
     | `GtkCurve
     +GtkEditable
     | +GtkEntry
     | | `GtkSpinButton
     | `GtkText
     +GtkMisc
     | +GtkArrow
     | +GtkImage
     | +GtkLabel
     | | `GtkTipsQuery
     | `GtkPixmap
     +GtkPreview
     +GtkProgressBar
     +GtkRange
     | +GtkScale
     | | +GtkHScale
     | | `GtkVScale
     | `GtkScrollbar
     |   +GtkHScrollbar
     |   `GtkVScrollbar
     +GtkRuler
     | +GtkHRuler
     | `GtkVRuler
     `GtkSeparator
       +GtkHSeparator
       `GtkVSeparator

5.2. GTK+ はスレッドセーフなの?GTK+ でマルチスレッドのアプリケーション はどうやって書くの?

GTK+ はスレッドセーフなの?GTK+ でマルチスレッドのアプリケーション はどうやって書くの?

他に GLib を呼び出す前に、まず g_thread_init() を呼び出しておけば、Glib を スレッドセーフモードで使えます。このモードでは、Glib は必要に応じて内部の データ構造を自動的にロックします。でもこれは、例えば二つのスレッドが同時に 一つのハッシュテーブルにアクセスできるということではなく、同時に二つの 異なるハッシュテーブルにアクセスできるという意味です。二つの異なるスレッド が同じハッシュテーブルにアクセスする必要がある場合は、アプリケーションの 責任でロックしてください。

Glib をスレッドセーフになるように初期化すると、GTK+ は スレッドを理解するようになります。グローバルなロックが一つあり、他に GDK を呼び出す前に、gdk_threads_enter() を使って、そのロックを取得し、 後で gdk_threads_leave() を使って解放しなければなりません。

GTK+ を使った、必要最小限のスレッド化アプリケーションのメインプログラムは 次のようなものになります。

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

  g_thread_init(NULL);
  gtk_init(&argc, &argv);

  window = create_window();
  gtk_widget_show(window);

  gdk_threads_enter();
  gtk_main();
  gdk_threads_leave();

  return(0);
}

コールバックには若干注意が必要です。GTK+ からのコールバック (つまりシグナル)は、GTK+ のロック内で行なわれます。でも GLib からのコールバック(タイムアウトや、入出力のコールバック、 またアイドル関数など)は GTK+ のロック外で行なわれるものです。 ですから、シグナルハンドラの内部では、gdk_threads_enter() を 呼ぶ必要はありませんが、他のタイプのコールバック内では、 この関数を呼び出して下さい。

Eric Mouw は GTK+ プログラム内でのスレッドの使用法を例示する、 次のようなサンプルコードを寄贈してくれました。

/*-------------------------------------------------------------------------
 * Filename:      gtk-thread.c
 * Version:       0.99.1
 * Copyright:     Copyright (C) 1999, Erik Mouw
 * Author:        Erik Mouw <J.A.K.Mouw@its.tudelft.nl>
 * Description:   GTK threads example. 
 * Created at:    Sun Oct 17 21:27:09 1999
 * Modified by:   Erik Mouw <J.A.K.Mouw@its.tudelft.nl>
 * Modified at:   Sun Oct 24 17:21:41 1999
 *-----------------------------------------------------------------------*/
/*
 * 以下のコマンドでコンパイルして下さい
 *
 * cc -o gtk-thread gtk-thread.c `gtk-config --cflags --libs gthread`
 *
 * バグをいくつか指摘してくれた、Sebastian Wilhelmi と Owen Taylor に
 * 感謝します。
 *
 */

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <time.h>
#include <gtk/gtk.h>
#include <glib.h>
#include <pthread.h>

#define YES_IT_IS    (1)
#define NO_IT_IS_NOT (0)

typedef struct 
{
  GtkWidget *label;
  int what;
} yes_or_no_args;

G_LOCK_DEFINE_STATIC (yes_or_no);
static volatile int yes_or_no = YES_IT_IS;

void destroy(GtkWidget *widget, gpointer data)
{
  gtk_main_quit();
}

void *argument_thread(void *args)
{
  yes_or_no_args *data = (yes_or_no_args *)args;
  gboolean say_something;

  for(;;)
    {
      /* しばらくスリープする */
      sleep(rand() / (RAND_MAX / 3) + 1);

      /* yes_or_no 変数をロックする */
      G_LOCK(yes_or_no);

      /* 何か言わなきゃだめ? */
      say_something = (yes_or_no != data->what);

      if(say_something)
	{
	  /* 変数をセット */
	  yes_or_no = data->what;
	}

      /* yes_or_no 変数のロック解除 */
      G_UNLOCK(yes_or_no);

      if(say_something)
	{
	  /* GTK のスレッドロックを取得 */
	  gdk_threads_enter();

	  /* ラベルのテキストをセット */
	  if(data->what == YES_IT_IS)
	    gtk_label_set_text(GTK_LABEL(data->label), "O yes, it is!");
	  else
	    gtk_label_set_text(GTK_LABEL(data->label), "O no, it isn't!");

	  /* GTK のスレッドロックを解除 */
	  gdk_threads_leave();
	}
    }

  return(NULL);
}

int main(int argc, char *argv[])
{
  GtkWidget *window;
  GtkWidget *label;
  yes_or_no_args yes_args, no_args;
  pthread_t no_tid, yes_tid;

  /* スレッドの初期化 */
  g_thread_init(NULL);

  /* gtk の初期化 */
  gtk_init(&argc, &argv);

  /* 乱数発生の初期化 */
  srand((unsigned int)time(NULL));

  /* ウィンドウ生成 */
  window = gtk_window_new(GTK_WINDOW_TOPLEVEL);

  gtk_signal_connect(GTK_OBJECT (window), "destroy",
		     GTK_SIGNAL_FUNC(destroy), NULL);

  gtk_container_set_border_width(GTK_CONTAINER (window), 10);

  /* ラベルの生成 */
  label = gtk_label_new("And now for something completely different ...");
  gtk_container_add(GTK_CONTAINER(window), label);
  
  /* すべて表示 */
  gtk_widget_show(label);
  gtk_widget_show (window);

  /* スレッド生成 */
  yes_args.label = label;
  yes_args.what = YES_IT_IS;
  pthread_create(&yes_tid, NULL, argument_thread, &yes_args);

  no_args.label = label;
  no_args.what = NO_IT_IS_NOT;
  pthread_create(&no_tid, NULL, argument_thread, &no_args);

  /* GTK のメインループに入る */
  gdk_threads_enter();
  gtk_main();
  gdk_threads_leave();

  return(0);
}

5.3. GTK+ アプリで fork() すると、妙な 'x io error' が起こるのはなぜ?

GTK+ アプリで fork() すると、妙な 'x io error' が起こるのはなぜ?

これは実際は GTK+ の問題ではないし、fork() にも無関係です。'x io error' が起こるとすれば、たぶん exit()関数を使って子プロセスを終了しようとして いるんです。

GDK が X ディスプレイをオープンする時、ソケットのファイルディスクリプタ を作ります。exit()関数を使うと、暗にオープン しているファイルディスクリプタをすべてクローズしてしまうので、 その裏に潜んでいる X ライブラリは、それが実際気に入らないんです。

ここで使う正しい関数は _exit() です。

Eric Mouw は fork() と exit() を処理する例を示す、サンプルコードを 寄贈してくれました。

/*-------------------------------------------------------------------------
 * Filename:      gtk-fork.c
 * Version:       0.99.1
 * Copyright:     Copyright (C) 1999, Erik Mouw
 * Author:        Erik Mouw <J.A.K.Mouw@its.tudelft.nl>
 * Description:   GTK+ fork example
 * Created at:    Thu Sep 23 21:37:55 1999
 * Modified by:   Erik Mouw <J.A.K.Mouw@its.tudelft.nl>
 * Modified at:   Thu Sep 23 22:39:39 1999
 *-----------------------------------------------------------------------*/
/*
 * 以下のコマンドでコンパイルして下さい
 *
 * cc -o gtk-fork gtk-fork.c `gtk-config --cflags --libs`
 *
 */

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <gtk/gtk.h>

void sigchld_handler(int num)
{
  sigset_t set, oldset;
  pid_t pid;
  int status, exitstatus;

  /* 入ってくる他の SIGCHLD シグナルをブロックする */
  sigemptyset(&set);
  sigaddset(&set, SIGCHLD);
  sigprocmask(SIG_BLOCK, &set, &oldset);

  /* 子プロセスを待つ */
  while((pid = waitpid((pid_t)-1, &status, WNOHANG)) > 0)
    {
      if(WIFEXITED(status))
	{
	  exitstatus = WEXITSTATUS(status);

	  fprintf(stderr, 
		  "Parent: child exited, pid = %d, exit status = %d\n", 
		  (int)pid, exitstatus);
	}
      else if(WIFSIGNALED(status))
	{
	  exitstatus = WTERMSIG(status);

	  fprintf(stderr,
		  "Parent: child terminated by signal %d, pid = %d\n",
		  exitstatus, (int)pid);
	}
      else if(WIFSTOPPED(status))
	{
	  exitstatus = WSTOPSIG(status);

	  fprintf(stderr,
		  "Parent: child stopped by signal %d, pid = %d\n",
		  exitstatus, (int)pid);
	}
      else
	{
	  fprintf(stderr,
		  "Parent: child exited magically, pid = %d\n",
		  (int)pid);
	}
    }

  /* シグナルハンドラを再登録する(システムによってはこれが必要) */
  signal(SIGCHLD, sigchld_handler);
  
  /* そして、ブロック解除 */
  sigemptyset(&set);
  sigaddset(&set, SIGCHLD);
  sigprocmask(SIG_UNBLOCK, &set, &oldset);
}

gint delete_event(GtkWidget *widget, GdkEvent *event, gpointer data)
{
  return(FALSE);
}

void destroy(GtkWidget *widget, gpointer data)
{
  gtk_main_quit();
}

void fork_me(GtkWidget *widget, gpointer data)
{
  pid_t pid;

  pid = fork();

  if(pid == -1)
    {
      /* おっと、fork() 失敗 */
      perror("fork");
      exit(-1);
    }
  else if(pid == 0)
    {
      /* 子 */
      fprintf(stderr, "Child: pid = %d\n", (int)getpid());

      execlp("ls", "ls", "-CF", "/", NULL);
      
      /* exec() から戻った場合は、何かまずいことがあるということ */
      perror("execlp");

      /* 子プロセス終了。exec() の代わりに _exec() を使うことに注意 */
      _exit(-1);
    }
  else
    {
      /* 親 */
      fprintf(stderr, "Parent: forked a child with pid = %d\n", (int)pid);
    }
}

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

  gtk_init(&argc, &argv);

  /* 基本的なこと:ウィンドウの作成と、destroy イベント および
   * delete イベントのコールバックのセット
   */
  window = gtk_window_new(GTK_WINDOW_TOPLEVEL);

  gtk_signal_connect(GTK_OBJECT (window), "delete_event",
		     GTK_SIGNAL_FUNC(delete_event), NULL);
          
  gtk_signal_connect(GTK_OBJECT (window), "destroy",
		     GTK_SIGNAL_FUNC(destroy), NULL);

#if (GTK_MAJOR_VERSION == 1) && (GTK_MINOR_VERSION == 0)
  gtk_container_border_width(GTK_CONTAINER (window), 10);
#else  
  gtk_container_set_border_width(GTK_CONTAINER (window), 10);
#endif

  /* 何かいいことをするボタンを追加 */
  button = gtk_button_new_with_label("Fork me!");
          
  gtk_signal_connect(GTK_OBJECT (button), "clicked",
		     GTK_SIGNAL_FUNC(fork_me), NULL);

  gtk_container_add(GTK_CONTAINER(window), button);
          
  /* すべて表示 */
  gtk_widget_show (button);
  gtk_widget_show (window);


  /* SIGCHLD 用のシグナルハンドラを登録 */
  signal(SIGCHLD, sigchld_handler);

  
  /* メインループ */
  gtk_main ();

  exit(0);         
}

5.7. (例えばリストウィジェットで)ダブルクリックのイベントはどうやって捉えるの?

(例えばリストウィジェットで)ダブルクリックのイベントはどうやって捉えるの?

Tim Janik が gtk-list メーリングリストで次のように書いています。

以下のように、シグナルハンドラを定義する

gint
signal_handler_event(GtkWidget *widget, GdkEventButton *event, gpointer func_data)
{
  if (GTK_IS_LIST_ITEM(widget) &&
       (event->type==GDK_2BUTTON_PRESS ||
        event->type==GDK_3BUTTON_PRESS) ) {
    printf("I feel %s clicked on button %d\n",
           event->type==GDK_2BUTTON_PRESS ? "double" : "triple",
           event->button);
  }

  return FALSE;
}

それから、以下のように、そのハンドラをオブジェクトに接続する。

{
  /* list と list item の初期化関係 */     

  gtk_signal_connect(GTK_OBJECT(list_item),
                     "button_press_event",
                     GTK_SIGNAL_FUNC(signal_handler_event),
                     NULL);

  /* そして */

  gtk_signal_connect(GTK_OBJECT(list_item),
                     "button_release_event",
                     GTK_SIGNAL_FUNC(signal_handler_event),
                     NULL);

  /* その他 */
}

また、Owen Taylor は次のように書いています。

注意して欲しいのは、一回目のボタン押下を先に受け取るということだ。 だから、あるボタンでこのようにすれば、結局 そのボタンからは "clicked" シグナルも受け取ることになるんだ。(これはどのツールキットでも真実だと 思う。コンピュータは読心術が不得手だからね)」


5.8. ところで、シグナルとイベントはどう違うの?

ところで、シグナルとイベントはどう違うの?

まず初めに、Havoc Pennington が、自分のフリーの本の中で、 イベントとシグナルの違いについて、完全に近い説明をしています。 ( http://www106.pair.com/rhp/sample_chapters.html に その本の章が二つあります)

そのうえ、Havoc はそれを gtk-list メーリングリスト に投稿してくれました。「イベントというのは、X サーバーから受け取る メッセージの流れ(ストリーム)のことだ。そのメッセージが Gtk の メインループを駆動する。Gtk のメインループというのは、「イベントを 待って、そのイベントを処理する」ということと五十歩百歩だ(というのは 正確じゃないけどね。実際はそれよりも汎用的だし、一度にたくさんの 異なる入力ストリームを待てるんだ)。イベントは、Gdk/Xlib の 概念なんだ。」

「シグナルというのは、GtkObject とそのサブクラスに特徴的なことだ。 それらはどんな入力ストリームとも何の関係もない。実際シグナルというのは、 コールバックのリストを保持して、それらを起動する(シグナルを「発行する」) 方法に過ぎない。もちろん詳細はずいぶんあるし、その他の特徴もたくさんある。 シグナルはオブジェクトのインスタンスが発行するもので、Gtk のメインループ にはまったく無関係だ。シグナルを発行するオブジェクトについて、何かが変化した 時にシグナルを発行するのが慣習なんだ。

「GtkWidget がイベントを受け取ると、たまたまシグナルを発行するから、 その二つが一緒になっているだけだ。これは全く便利なものだ。だから、 コールバックを接続して、特定のウィジェットが特定のイベントを受け取ると それが起動するようにできる。シグナルとイベントを本質的に関連する概念と するものは何もない。ボタンをクリックしたらシグナルを発行するということ が、ボタンのクリックとシグナルを関連した概念にしているというだけで、 それ以上のものではないんだ。


5.11. 新しいシグナルを GTK+ ウィジェットに追加する必要があるのだけど、何か方法は?

新しいシグナルを GTK+ ウィジェットに追加する必要があるのだけど、何か方法は?

追加したいシグナルが他の GTK+ ユーザーにもメリットがあれば、 その変更内容を表しているパッチを投稿する必要があるかも知れません。 ウィジェットクラスにシグナルを追加する詳細については、チュートリアル を調べて下さい。

そのシグナルがそういうものではなかったり、投稿したパッチが採用されない 場合は、gtk_object_class_user_signal_new関数 を使わざるを得ないでしょう。 gtk_object_class_user_signal_new関数は、 GTK+ のソースコードを変更しなくても、予め定義してある GTK+ の ウィジェットに新しいシグナルを追加できるようにするものです。 その新しいシグナルは、gtk_signal_emit を使って 発行できるし、他のシグナルと同じ方法で処理できます。

Tim Janik がこのコードを少し投稿してくれました。

static guint signal_user_action = 0;

signal_user_action =
  gtk_object_class_user_signal_new (gtk_type_class (GTK_TYPE_WIDGET),
                    "user_action",
                    GTK_RUN_LAST | GTK_RUN_ACTION,
                    gtk_marshal_NONE__POINTER,
                    GTK_TYPE_NONE, 1,
                    GTK_TYPE_POINTER);

void
gtk_widget_user_action (GtkWidget *widget,
                        gpointer   act_data)
{
  g_return_if_fail (GTK_IS_WIDGET (widget));

  gtk_signal_emit (GTK_OBJECT (widget), signal_user_action, act_data);
}

新しいシグナルに、よくある gpointer パラメータ以上のものを 持たせたい場合は、GTK+ のマーシャラーをいじくり回す必要が あります。


5.12. その領域内にぴったり収まるように切り詰めたテキストを表示することは可能?

その領域内にぴったり収まるように切り詰めたテキストを表示することは可能?

(クリッピング以外の)GTK の動作というのは、X のリソースを保存 しようとする動きの結果なんです。(中でも)ラベルウィジットには それ自身の X ウィンドウが無く、たんに親ウィンドウに内容を描画 するだけです。クリップマスクを設定することでクリッピングを起こし、 その後でテキストを描画することは可能かも知れませんが、おそらく 相当の性能低下を招くと思います。

そのような問題に対する最良の解決方法が、ラベルに X ウィンドウ を持たせるように、実際に gtk を変更することかもしれないというのは、 長い目で見ればありそうなことです。短期的な回避策としては、実際に 自分自身のウィンドウを持っている、別のウィジェット内にラベル ウィジェットを置いてしまうというのがあります。そのような候補の 一つに、ビューポートウィジェットがあるでしょう。

viewport = gtk_viewport (NULL, NULL);
gtk_widget_set_usize (viewport, 50, 25);
gtk_viewport_set_shadow_type (GTK_VIEWPORT(viewport), GTK_SHADOW_NONE);
gtk_widget_show(viewport);

label = gtk_label ("a really long label that won't fit");
gtk_container_add (GTK_CONTAINER(viewport), label);
gtk_widget_show (label);

でも実際に、たくさんのウィジェットにこういうことをしたとすれば、 gtkviewport.c をコピーして、アジャスト機能と陰影の機能を取り除く 必要があるかも知れません(たぶん、これを GtkClipper と呼ぶので しょうね)。


5.15. データと GTK+ のオブジェクト/ウィジェットとの関連づけって、どうやるの?

データと GTK+ のオブジェクト/ウィジェットとの関連づけって、どうやるの?

まず、関連づけたデータは GtkObject 構造体の object_data というメンバーに 格納されます。このメンバーのタイプは GData になっており、glib.h で定義 されています。ですから、glib のソースディレクトリにある、gdataset.c ファイル をよく注意して読んで下さい。

あるデータを gtk のオブジェクトと関連づける方法には二種類あります。 gtk_object_set_data()gtk_object_get_data() を使うのが 一番一般的なやり方です。というのは、オブジェクトとデータを接続する 強力なインタフェースを備えているからなんです。

void gtk_object_set_data(GtkObject *object, const gchar *key, gpointer data);

gpointer gtk_object_get_data(GtkObject *object, const gchar *key);

百聞は一見にしかずです。

struct my_struct	p1,p2,*result;
GtkWidget		*w;

gtk_object_set_data(GTK_OBJECT(w),"p1 data",(gpointer)&p1);
gtk_object_set_data(GTK_OBJECT(w),"p2 data",(gpointer)&p2);

result = gtk_object_get_data(GTK_OBJECT(w),"p1 data");

gtk_object_set_user_data()関数と gtk_object_get_user_data()関数は、 上記の関数と全く同じことをしますが、"key"パラメータは指定 しません。その代わり、標準的な "user_data" キーを使います。 GTK+ 1.2 では、この二つの関数を使ってもいい顔はされません。 古い gtk パッケージとの互換性を保っているだけなんです。


5.19. ウィジェットやウィンドウの大きさはどうやって設定すればいいの?ユーザーに ウィンドウの大きさを変えさせたくないのだけど、どうすればいいの?

ウィジェットやウィンドウの大きさはどうやって設定すればいいの?ユーザーに ウィンドウの大きさを変えさせたくないのだけど、どうすればいいの?

どのようなウィジェットについても、その位置を設定するには gtk_widget_set_uposition()関数を使います。

ウィジェットの大きさを設定するには、 gtk_widget_set_usize()関数を使います。 この関数がウィジェットに作用する際、その持っている特徴的な事柄を すべて使うには、gtk_window_set_policy関数 を使う必要があるかも知れません。こういった関数の定義は以下のように なっています。

void gtk_widget_set_usize (GtkWidget *widget,
                           gint width,
                           gint height);

void gtk_window_set_policy (GtkWindow *window,
                            gint allow_shrink,
                            gint allow_grow,
                            gint auto_shrink);

auto_shrink は、子ウィジェットに要求した 大きさがその時点のウィンドウの大きさを下回る場合に、自動的にその ウィンドウを縮めるものです。 allow_shrink を指定すると、ユーザーが そのウィンドウを通常の大きさよりも小さくできるようにします。 allow_grow は、ユーザーがそのウィンドウを 大きくできるようにするものです。これらのパラメータのディフォルト値 は次のとおりです。

allow_shrink = FALSE
allow_grow   = TRUE
auto_shrink  = FALSE

gtk_widget_set_usize()関数は 以下に示すように、二度呼び出さない限り、他からこの関数を 呼び出してウィンドウの大きさを小さくすることはできないので、 ウィンドウの大きさを設定する方法としては、もっとも簡単だとは 言えません。

     gtk_widget_set_usize(your_widget, -1, -1);
     gtk_widget_set_usize(your_widget, new_x_size, new_y_size);

大きさを設定したりウィンドウを動かすもう一つの方法は、 gdk_window_move_resize()関数を 使うことです。これはウィンドウの拡大と縮小の両方でうまく動作 します。

     gdk_window_move_resize(window->window, 
                            x_pos, y_pos, 
                            x_size, y_size);

5.20. GTK+ アプリケーションにポップアップメニューを追加するのはどうやるの?

GTK+ アプリケーションにポップアップメニューを追加するのはどうやるの?

GTK+ ディストリビューションの examples/menu ディレクトリにある menu の例が、以下のやり方を使ったポップアップ メニューを実装しています。

static gint button_press (GtkWidget *widget, GdkEvent *event)
{

    if (event->type == GDK_BUTTON_PRESS) {
        GdkEventButton *bevent = (GdkEventButton *) event; 
        gtk_menu_popup (GTK_MENU(widget), NULL, NULL, NULL, NULL,
                        bevent->button, bevent->time);
        /* このイベントを処理したことを呼び出し側のコードに伝える。
         * 責任は果たした */
        return TRUE;
    }

    /* このイベントを処理しなかったことを呼び出し側のコードに伝える。責任は呼び出し側に転嫁 */
    return FALSE;
}

5.23. どうやれば、ピクセル(イメージデータ)を画面に描画できるの?

どうやれば、ピクセル(イメージデータ)を画面に描画できるの?

この取り組み方にはいくつか方法があります。いちばん簡単なのは、 GdkRGB を使うことです。これについては、 gdk/gdkrgb.h を見てください。 まず RGB バッファを作り、そこに描画して、それから GdkRGB ルーチンを 使って、RGB バッファを描画エリアやあるいはカスタムウィジェットに コピーします。"GTK+/Gnome Application Development" という書籍には、 詳細が載っています。GdkRGB は GTK+ のリファレンスマニュアルにも 載っています。

ゲームやグラフィック中心のアプリケーションを書くつもりなら、 もっと入念に解決策を考えるかも知れませんね。OpenGL はグラフィックス では標準的なもので、XFree86 の将来のバージョンでは、ハードウェア アクセラレータにアクセスするようになるでしょう。ですから、描画速度 を最大にする場合は、たぶん OpenGL を使うことが必要です。 GtkGLArea ウィジェットは、GTK+ で OpenGL を使用する際に利用できる ものです(ですが、GTK+ 自身に付いてくるわけではありません)。 他に、ClanLib and Loki's Simple DirectMedia Layer library (SDL) と いったような、オープンソースのゲームライブラリもいくつかあります。

gdk_draw_point() は、けっして使いたいと 思わないで下さい。極めて低速なんです。


Chapter 6. GTK+ を用いた開発:ウィジェット特有の質問

目次
6.1. GtkList のセレクションはどうやって見つければいいの?
6.2. リストをスクロールしても、GtkList のカラムヘッダーが消えないようにするには どうすればいいの?
6.3. 自分のアプリケーションのユーザーには、GtkCombo にテキスト入力をしてもらいたくない のだけれど、何か良い方法は?
6.4. コンボボックスが変更されたことは、どうやって捕捉するの?
6.5. どうやればメニューの中にセパレータを定義できるの?
6.6. ヘルプみたいに、メニューを右寄せにするには、どうやればいいの?
6.7. どうやれば下線付きのアクセラレータをメニュー項目に追加できるの?
6.8. どうやれば GtkMenuItem からテキストを取り出せるの?
6.9. GtkLabel を左右いずれかに寄せるには、どうやればいいの?
6.10. GtkLabel ウィジェットの背景色はどうやって設定するの?
6.11. リソースファイルを使って GtkLabel の色とフォントをセットするにはどうやるの?
6.12. どうやれば、リソースファイルでツールチップが構成できるの?
6.13. GtkEntry に(だいたい)2000 超の文字を追加できない。なにがまずいの?
6.14. リターンキーを押した時に GtkEntry ウィジェットをアクティブにするのは、どうやればいいの?
6.15. GtkEntry へ入力したものを 確認/制限/フィルター するには、どうやればいいの?
6.16. GtkText ウィジェットで水平スクロールバーはどうやって使えばいいの?
6.17. GtkText ウィジェットのフォントはどうやって変えればいいの?
6.18. GtkText オブジェクトで、カーソル位置を設定するにはどうやればいいの?

6.1. GtkList のセレクションはどうやって見つければいいの?

GtkList のセレクションはどうやって見つければいいの?

以下のようにして、セレクションを取得して下さい。

GList *sel;
sel = GTK_LIST(list)->selection;

以下は、GList がどのように定義されているのかを示しています (glist.hを引用)。

typedef struct _GList GList;

struct _GList
{
  gpointer data;
  GList *next;
  GList *prev;
};

GList 構造体というのは、二重にリンクされたリストの単純な構造体 にすぎません。glib.hには、リンクされたリストを変更するための g_list_*()関数がいくつか存在します。でも、 GTK_LIST(MyGtkList)->selection はgtk_list_*()関数で維持している ものなので、変更しないで下さい。

GtkList にある selection_mode は、GtkList のセレクション機能を決める ものです。したがって、GTK_LIST(AnyGtkList)->selection の 内容は次のようになります。

GList 構造体 GTK_LIST(MyGtkList)->selection のメンバー data は、 選択されている最初の GtkListItem を指しています。ですから、 どのリスト項目が選択されているのかを決めたい場合は、以下のように してください。

{
        gchar           *list_items[]={
                                "Item0",
                                "Item1",
                                "foo",
                                "last Item",
                        };
        guint           nlist_items=sizeof(list_items)/sizeof(list_items[0]);
        GtkWidget       *list_item;
        guint           i;

        list=gtk_list_new();
        gtk_list_set_selection_mode(GTK_LIST(list), GTK_SELECTION_MULTIPLE);
        gtk_container_add(GTK_CONTAINER(AnyGtkContainer), list);
        gtk_widget_show (list);

        for (i = 0; i < nlist_items; i++)
        {
                list_item=gtk_list_item_new_with_label(list_items[i]);
                gtk_object_set_user_data(GTK_OBJECT(list_item), (gpointer)i);
                gtk_container_add(GTK_CONTAINER(list), list_item);
                gtk_widget_show(list_item);
        }
}

セレクションについて知るには、以下のようにします。

{
        GList   *items;

        items=GTK_LIST(list)->selection;

        printf("Selected Items: ");
        while (items) {
                if (GTK_IS_LIST_ITEM(items->data))
                        printf("%d ", (guint) 
                gtk_object_get_user_data(items->data));
                items=items->next;
        }
        printf("\n");
}

6.5. どうやればメニューの中にセパレータを定義できるの?

どうやればメニューの中にセパレータを定義できるの?

メニューの作り方に関する情報は、 Tutorial を見て下さい。とはいっても、メニューの中にセパレータを作るには、 空のメニュー項目を挿入するだけでいいんです。

menuitem = gtk_menu_item_new();
gtk_menu_append(GTK_MENU(menu), menuitem);
gtk_widget_show(menuitem);

6.7. どうやれば下線付きのアクセラレータをメニュー項目に追加できるの?

どうやれば下線付きのアクセラレータをメニュー項目に追加できるの?

Glade プロジェクトの技術部隊にいる Damon Chaplin が次のサンプルコードを くれました(このコードは Glade の出力です)。このコードでは、 子供が一つだけ付いた(New)小さな File メニュー項目を作っています。 File の "F" と New の "N" に 下線が付き、対応するアクセラレータが作られます。

  menubar1 = gtk_menu_bar_new ();
  gtk_object_set_data (GTK_OBJECT (window1), "menubar1", menubar1);
  gtk_widget_show (menubar1);
  gtk_box_pack_start (GTK_BOX (vbox1), menubar1, FALSE, FALSE, 0);

  file1 = gtk_menu_item_new_with_label ("");
  tmp_key = gtk_label_parse_uline (GTK_LABEL (GTK_BIN (file1)->child),
                                   _("_File"));
  gtk_widget_add_accelerator (file1, "activate_item", accel_group,
                              tmp_key, GDK_MOD1_MASK, 0);
  gtk_object_set_data (GTK_OBJECT (window1), "file1", file1);
  gtk_widget_show (file1);
  gtk_container_add (GTK_CONTAINER (menubar1), file1);

  file1_menu = gtk_menu_new ();
  file1_menu_accels = gtk_menu_ensure_uline_accel_group (GTK_MENU (file1_menu));
  gtk_object_set_data (GTK_OBJECT (window1), "file1_menu", file1_menu);
  gtk_menu_item_set_submenu (GTK_MENU_ITEM (file1), file1_menu);

  new1 = gtk_menu_item_new_with_label ("");
  tmp_key = gtk_label_parse_uline (GTK_LABEL (GTK_BIN (new1)->child),
                                   _("_New"));
  gtk_widget_add_accelerator (new1, "activate_item", file1_menu_accels,
                              tmp_key, 0, 0);
  gtk_object_set_data (GTK_OBJECT (window1), "new1", new1);
  gtk_widget_show (new1);
  gtk_container_add (GTK_CONTAINER (file1_menu), new1);

6.13. GtkEntry に(だいたい)2000 超の文字を追加できない。なにがまずいの?

GtkEntry に(だいたい)2000 超の文字を追加できない。なにがまずいの?

現在これは、GtkEntry ウィジェットの既知の問題です。 gtk_entry_insert_text()関数内で、 次の数行がエントリ内の文字数を 2047 に制限しています。

  /* The algorithms here will work as long as, the text size (a
   * multiple of 2), fits into a guint16 but we specify a shorter
   * maximum length so that if the user pastes a very long text, there
   * is not a long hang from the slow X_LOCALE functions.  */

  if (entry->text_max_length == 0)
    max_length = 2047;
  else
    max_length = MIN (2047, entry->text_max_length);

6.15. GtkEntry へ入力したものを 確認/制限/フィルター するには、どうやればいいの?

GtkEntry へ入力したものを 確認/制限/フィルター するには、どうやればいいの?

ユーザーが GtkEntry ウィジェットに入力したテキストを確認したければ、 このエントリの "insert_text" シグナルにハンドラをつないで、コールバック 関数内でそのテキストを修正すればいいのです。以下の例では、すべての文字を 強制的に大文字にして、文字範囲を A から Z までに制限しています。 このエントリが GtkEditable 型のオブジェクトへのキャストになっていて、 そこから GtkEntry が派生しているということは、心に留めておいて下さい。

#include <ctype.h>
#include <gtk/gtk.h>

void insert_text_handler (GtkEntry    *entry,
                          const gchar *text,
                          gint         length,
                          gint        *position,
                          gpointer     data)
{
  GtkEditable *editable = GTK_EDITABLE(entry);
  int i, count=0;
  gchar *result = g_new (gchar, length);

  for (i=0; i < length; i++) {
    if (!isalpha(text[i]))
      continue;
    result[count++] = islower(text[i]) ? toupper(text[i]) : text[i];
  }
  
  if (count > 0) {
    gtk_signal_handler_block_by_func (GTK_OBJECT (editable),
				      GTK_SIGNAL_FUNC (insert_text_handler),
				      data);
    gtk_editable_insert_text (editable, result, count, position);
    gtk_signal_handler_unblock_by_func (GTK_OBJECT (editable),
					GTK_SIGNAL_FUNC (insert_text_handler),
					data);
  }
  gtk_signal_emit_stop_by_name (GTK_OBJECT (editable), "insert_text");
  
  g_free (result);
}

int main (int   argc,
          char *argv[])
{
  GtkWidget *window;
  GtkWidget *entry;
  
  gtk_init (&argc, &argv);
  
  /* create a new window */
  window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
  gtk_window_set_title(GTK_WINDOW (window), "GTK Entry");
  gtk_signal_connect(GTK_OBJECT (window), "delete_event",
		     (GtkSignalFunc) gtk_exit, NULL);
  
  entry = gtk_entry_new();
  gtk_signal_connect(GTK_OBJECT(entry), "insert_text",
		     GTK_SIGNAL_FUNC(insert_text_handler),
		     NULL);
  gtk_container_add(GTK_CONTAINER (window), entry);
  gtk_widget_show(entry);
  
  gtk_widget_show(window);
  
  gtk_main();
  return(0);
}

6.18. GtkText オブジェクトで、カーソル位置を設定するにはどうやればいいの?

GtkText オブジェクトで、カーソル位置を設定するにはどうやればいいの?

その答えは、GtkEditable クラスを継承しているオブジェクトなら どれに対しても有効だということに注目しておいて下さい。

カーソル位置を動かしたいというのは確かなんですか? たいてい、カーソル位置は合っていますが、一方で挿入位置はカーソル 位置とは合っていません。これを本当にやりたいことに採り入れる場合は、 gtk_text_set_point()関数を使って下さい。 挿入位置をその時点のカーソル位置に設定したければ、次のコードを 使って下さい。

  gtk_text_set_point(GTK_TEXT(text),
  gtk_editable_get_position(GTK_EDITABLE(text)));

挿入位置をいつでもカーソルの後ろにしたければ、たぶんボタン押下 のイベントを捉えて、挿入位置を動かすべきです。注意: でもそのイベントを捉えるのは、ウィジェットがカーソル位置を 変更してからなんですけどね。 Thomas Mailund Jensen は次のようなコードを提案してくれました。

static void
insert_bar (GtkWidget *text)
{
  /* カーソルマークにジャンプ */
  gtk_text_set_point (GTK_TEXT (text),
  gtk_editable_get_position (GTK_EDITABLE  (text)));

  gtk_text_insert (GTK_TEXT (text), NULL, NULL, NULL,
     "bar", strlen ("bar"));
}

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

  gtk_init (&argc, &argv);

  window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
  text = gtk_text_new (NULL, NULL);
  gtk_text_set_editable (GTK_TEXT (text), TRUE);
  gtk_container_add (GTK_CONTAINER (window), text);

  /* 事後に接続 */
  gtk_signal_connect_after (GTK_OBJECT(text), "button_press_event",
    GTK_SIGNAL_FUNC (insert_bar), NULL);

  gtk_widget_show_all(window);
  gtk_main();

  return 0;
}

さて、本当にカーソル位置を変更したいなら、 gtk_editable_set_position()関数を 使って下さい。


Chapter 7. GDK について


7.2. 色の割り当てはどうやって使えばいいの?

色の割り当てはどうやって使えばいいの?

GDK の素晴らしい事柄の一つに、GDK が Xlib の上部にその基礎を 置いているということがあります。でもこれは問題にもなります。 特に色の管理という領域ではそうです。プログラムで色を使いたい 場合(矩形とかそういったものを描画する場合、そのコードは 次のようなものになるでしょう:

{
  GdkColor *color;
  int width, height;
  GtkWidget *widget;
  GdkGC *gc;

  ...
  
  /* 最初に描画するためのグラフィックコンテキストを作る */
  gc = gdk_gc_new(widget->window);

  /* 矩形の適切な次元を見つける */
  gdk_window_get_size(widget->window, &width, &height);

  /* 使いたい色 */
  color = (GdkColor *)malloc(sizeof(GdkColor));
  
  /* red, green, blue は渡された値で、描画したい
   * 色の RGB 三色を表している。GdkColor内の RGB の各値が
   * 0 から 255 ではなく、0 から 65535 までだということに注意
   */
  color->red = red * (65535/255);
  color->green = green * (65535/255);
  color->blue = blue * (65535/255);
  
  /* ピクセル値は、その色のカラーマップに対するインデックスを表している。
   * 先に設定したRGB値の単なる組合せである。
   */
  color->pixel = (gulong)(red*65536 + green*256 + blue);

  /* しかし、このピクセル値が本当に有効なのは、24 ビット(TrueColor)の
   * ディスプレイの場合だけである。したがって、GDK と X がカラーマップで利用できる
   * もっとも近い色を提供できるようにするため、この呼び出しが必要。
   */
  gdk_color_alloc(gtk_widget_get_colormap(widget), color);

  /* フォアグラウンドをこの色に設定 */
  gdk_gc_set_foreground(gc, color);
  
  /* 矩形を描画 */
  gdk_draw_rectangle(widget->window, gc, 1, 0, 0, width, height);

  ...
}

Chapter 8. GLib について


8.2. 二重リンクリストはどういうふうに使えるの?

二重リンクリストはどういうふうに使えるの?

GList は次のように定義されています。

typedef struct _GList GList;

struct _GList
{
  gpointer data;
  GList *next;
  GList *prev;
};

GList オブジェクトを使うには、たんに次のようにして下さい。

GList   *list = NULL;
GList   *listrunner;
gint    array[] = { 1, 2, 3, 4, 5, 6 };
gint    pos;
gint    *value;

/* リストにデータを追加 */
for (pos=0;pos < sizeof array; pos++) {
  list = g_list_append(list, (gpointer)&array[pos]);
}

/* リストを走らせる */
listrunner = g_list_first(list);
while (listrunner) {
  value = (gint *)listrunner->data;
  printf("%d\n", *value);
  listrunner = g_list_next(listrunner);
}

/* リストからデータを削除 */
listrunner = g_list_first(list);
list = g_list_remove_link(list, listrunner);
list = g_list_remove(list, &array[4]);

この同じコードは、g_list_* 関数を適切な g_slist_* 関数 (g_slist_append, g_slist_remove, ...)に置き換えれば、ただの リンクリストでも使えます。でも、ただのリンクリストでは後向きには 行けないので、g_slist_first 関数が無いことだけは覚えておいて下さい。 そのリストの最初のノードは参照し続ける必要があるんです。


8.3. アロケートしたリストのノードを free しても、メモリが解放されないようだ。

アロケートしたリストのノードを free しても、メモリが解放されないようだ。

GLib はこの特別な件に関しては「聡明」であろうとします。 そのオブジェクトを再利用しそうだと仮定するんです。だから割り当てた メモリをキャッシュするんです。この動きを使いたくなければ、 おそらく特別な亜ロケータをセットアップする必要があるでしょう。

Tim Janik の言葉を引用すると:

「GList やあるいは GNode をたくさん使っているコードの部分があり、 すぐ後でそれらをすべて解放すべきだと分かっているのなら、GAllocator を 使う必要があるだろう。アロケータを g_list にプッシュすると、それ以降 の glist の操作すべてが、そのアロケータのメモリプールに対して、 プライベートになる。(だからなんらかの外部呼び出しをする前に、 再度そのアロケータをポップするよう注意しなければならない)」

GAllocator *allocator;
GList *list = NULL;
guint i;

/* GList ノード用に新しいアロケーションプールをセットする */
allocator = g_allocator_new ("list heap", 1024);
g_list_push_allocator (allocator);

/* 何らかのリスト操作を行なう */
for (i = 0; i < 4096; i++)
  list = g_list_prepend (list, NULL);
list = g_list_reverse (list);

/* 外部関数を呼び出す前に、亜ロケータをポップするよう気をつける */
g_list_pop_allocator ();
gtk_label_set_text (GTK_LABEL (some_label), "some text");

/* それから、再度プライベートの glist プールをセットする */
g_list_push_allocator (allocator);

/* 何らかのリスト操作を行なう */
g_list_free (list);
list = NULL;
for (i = 0; i < 4096; i++)
  list = g_list_prepend (list, NULL);
  
/* そして戻す(一方でプール内のリストノードすべてを解放) */
g_list_pop_allocator ();
g_allocator_free (allocator);

8.4. g_print や g_malloc、 g_strdup それに同類の glib 関数を使うのはなぜ?

g_print や g_malloc、 g_strdup それに同類の glib 関数を使うのはなぜ?

以下のことを gtk-list メーリングリストに書いてくれた Tim Janik に感謝します。

「g_malloc() や g_free()、それにその同類に関して言えば、 これらの関数は libc にある同様のものよりも、ずっと安全だ。 例えば、g_free() は NULL で呼び出せばリターンするだけで、 なにもしない。それに、USE_DMALLOC を定義すれば、これらの 関数の定義が(glib.h内で)MALLOC() や FREE() 等々を使う ように変わるんだ。MEM_PROFILE やあるいは MEM_CHECK を 定義すれば、使ったブロックサイズを計数したごく小さな 統計情報ができる(g_mem_profile() / g_mem_check() で 示される)。」

常に同じ大きさのブロックがたくさんあれば領域を節約したり、 必要なら ALLOC_ONLY にするといった、そういうメモリ領域に 対するインタフェースを glib は持っているということを考えれば、 当然普通の malloc/free のようなものに、同じように(デバッグ可能な) ラッパーを作るさ。gdk が Xlib をカバーしているようなものさ。;)」

「完全に gtk に依存している GIMP のようなアプリケーションの内部で g_error() や g_warning() を使えば、(gtkmain.c 内部の) gtk_print() に従って、自前のハンドラ (これは g_set_error_handler() を使うんだけど)で、gtk のウィンドウ 内部にメッセージを表示するウィンドウを出せるようにさえできるんだ。」


8.5. GScanner ってどんなもので、どうやって使うの?

GScanner ってどんなもので、どうやって使うの?

GScanner というのはテキストをトークンに分解するものです。つまり、 ワードごとに対応する整数値を返したり、入力ストリーム中に現れる 数字を返すんです。これは、この変換を行なうある種の(カスタマイズ 可能な)規則に従っています。それでも解析する関数を自前で書く必要 があります。

以下は Tim Janik がくれた、ちょっとしたテストプログラムです。

<SYMBOL> = <OPTIONAL-MINUS> <NUMBER> ;

このプログラムは "#\n" や "/**/" といったスタイルのコメントをスキップする一方で、 構成要素を解析します。

#include <glib.h>

/* some test text to be fed into the scanner */
static const gchar *test_text =
( "ping = 5;\n"
  "/* slide in some \n"
  " * comments, just for the\n"
  " * fun of it \n"
  " */\n"
  "pong = -6; \n"
  "\n"
  "# the next value is a float\n"
  "zonk = 0.7;\n"
  "# redefine ping\n"
  "ping = - 0.5;\n" );

/* define enumeration values to be returned for specific symbols */
enum {
  SYMBOL_PING = G_TOKEN_LAST + 1,
  SYMBOL_PONG = G_TOKEN_LAST + 2,
  SYMBOL_ZONK = G_TOKEN_LAST + 3
};

/* symbol array */
static const struct {
  gchar *symbol_name;
  guint  symbol_token;
} symbols[] = {
  { "ping", SYMBOL_PING, },
  { "pong", SYMBOL_PONG, },
  { "zonk", SYMBOL_ZONK, },
  { NULL, 0, },
}, *symbol_p = symbols;

static gfloat ping = 0;
static gfloat pong = 0;
static gfloat zonk = 0;

static guint
parse_symbol (GScanner *scanner)
{
  guint symbol;
  gboolean negate = FALSE;

  /* expect a valid symbol */
  g_scanner_get_next_token (scanner);
  symbol = scanner->token;
  if (symbol < SYMBOL_PING ||
      symbol > SYMBOL_ZONK)
    return G_TOKEN_SYMBOL;

  /* expect '=' */
  g_scanner_get_next_token (scanner);
  if (scanner->token != '=')
    return '=';

  /* feature optional '-' */
  g_scanner_peek_next_token (scanner);
  if (scanner->next_token == '-')
    {
      g_scanner_get_next_token (scanner);
      negate = !negate;
    }

  /* expect a float (ints are converted to floats on the fly) */
  g_scanner_get_next_token (scanner);
  if (scanner->token != G_TOKEN_FLOAT)
    return G_TOKEN_FLOAT;

  /* make sure the next token is a ';' */
  if (g_scanner_peek_next_token (scanner) != ';')
    {
      /* not so, eat up the non-semicolon and error out */
      g_scanner_get_next_token (scanner);
      return ';';
    }

  /* assign value, eat the semicolon and exit successfully */
  switch (symbol)
    {
    case SYMBOL_PING:
      ping = negate ? - scanner->value.v_float : scanner->value.v_float;
      break;
    case SYMBOL_PONG:
      pong = negate ? - scanner->value.v_float : scanner->value.v_float;
      break;
    case SYMBOL_ZONK:
      zonk = negate ? - scanner->value.v_float : scanner->value.v_float;
      break;
    }
  g_scanner_get_next_token (scanner);

  return G_TOKEN_NONE;
}

int
main (int argc, char *argv[])
{
  GScanner *scanner;
  guint expected_token;

  scanner = g_scanner_new (NULL);

  /* adjust lexing behaviour to suit our needs
   */
  /* convert non-floats (octal values, hex values...) to G_TOKEN_INT */
  scanner->config->numbers_2_int = TRUE;
  /* convert G_TOKEN_INT to G_TOKEN_FLOAT */
  scanner->config->int_2_float = TRUE;
  /* don't return G_TOKEN_SYMBOL, but the symbol's value */
  scanner->config->symbol_2_token = TRUE;

  /* load symbols into the scanner */
  while (symbol_p->symbol_name)
    {
      g_scanner_add_symbol (scanner,
                            symbol_p->symbol_name,
                            GINT_TO_POINTER (symbol_p->symbol_token));
      symbol_p++;
    }

  /* feed in the text */
  g_scanner_input_text (scanner, test_text, strlen (test_text));

  /* give the error handler an idea on how the input is named */
  scanner->input_name = "test text";

  /* scanning loop, we parse the input until its end is reached,
   * the scanner encountered a lexing error, or our sub routine came
   * across invalid syntax
   */
  do
    {
      expected_token = parse_symbol (scanner);
      
      g_scanner_peek_next_token (scanner);
    }
  while (expected_token == G_TOKEN_NONE &&
         scanner->next_token != G_TOKEN_EOF &&
         scanner->next_token != G_TOKEN_ERROR);

  /* give an error message upon syntax errors */
  if (expected_token != G_TOKEN_NONE)
    g_scanner_unexp_token (scanner, expected_token, NULL, "symbol", NULL, NULL, TRUE);

  /* finsish parsing */
  g_scanner_destroy (scanner);

  /* print results */
  g_print ("ping: %f\n", ping);
  g_print ("pong: %f\n", pong);
  g_print ("zonk: %f\n", zonk);
  
  return 0;
}

このスキャナは入力を解析してトークンに分解しますが、そのトークンの 解釈はユーザーに任せているのであり、それらの型を定義してから解析に かかるのではない、ということは理解する必要があります。 例えば gscanner が文字列を解析するのを観察してみましょう。

"hi i am 17"
| | | |
| | | v
| | v TOKEN_INT, value: 17
| v TOKEN_IDENTIFIER, value: "am"
v TOKEN_CHAR, value: 'i'
TOKEN_IDENTIFIER, value: "hi"

このスキャナを以下のように設定し、

scanner->config->int_2_float = TRUE;
scanner->config->char_2_token = TRUE;
scanner->config->scan_symbols = TRUE;

and add "am" as a symbol with--> 以下のようにして "am" をシンボルに加えると、

g_scanner_add_symbol (scanner, "am", "symbol value");

GScanner は次のようにそれを解析します。

"hi i am 17"
| | | |
| | | v
| | v TOKEN_FLOAT, value: 17.0 (automatic int->float conversion)
| | TOKEN_SYMBOL, value: "symbol value" (a successfull hash table lookup
| | turned a TOKEN_IDENTIFIER into a
| | TOKEN_SYMBOL and took over the
| v symbol's value)
v 'i' ('i' can be a valid token as well, as all chars >0 and <256)
TOKEN_IDENTIFIER, value: "hi"

トークンの順序は自分のコードに合わせる必要があります。また 不要なものに遭遇する場合は、エラーになります。

/* expect an identifier ("hi") */
g_scanner_get_next_token (scanner);
if (scanner->token != G_TOKEN_IDENTIFIER)
  return G_TOKEN_IDENTIFIER;
/* expect a token 'i' */
g_scanner_get_next_token (scanner);
if (scanner->token != 'i')
  return 'i';
/* expect a symbol ("am") */
g_scanner_get_next_token (scanner);
if (scanner->token != G_TOKEN_SYMBOL)
  return G_TOKEN_SYMBOL;
/* expect a float (17.0) */
g_scanner_get_next_token (scanner);
if (scanner->token != G_TOKEN_FLOAT)
  return G_TOKEN_FLOAT;

ここを通れば、"hi i am 17" は解析したことになるし、 "dooh i am 42" や "bah i am 0.75" も同様に通ったことでしょう。 でも "hi 7 am 17" や "hi i hi 17" は通らなかったと思います。


Chapter 9. GTK+ FAQ への貢献と保守担当および著作権について

この FAQ に貢献したいと思ったら、この FAQ に含めた方が良いと思う正確な テキスト(質問と回答)を、電子メールで我々の誰かに送って下さい。 皆さんが協力してくれるから、この文書は成長しもっと役立つものになれるんです。

この文書を保守しているのは、 Tony Gale<gale@gtk.org>、 Nathan Froyd<maestrox@geocities.com>、 および Emmanuel Deloget<logout@free.fr> です。この FAQ を最初に作ったのは Shawn T. Amundson <amundson@gimp.org> ですが、彼は引き続きサポートしてくれています。

この GTK+ FAQ の著作権は次のようになっています。 Copyright (C) 1997-2000 by Shawn T. Amundson, Tony Gale, Emmanuel Deloget and Nathan Froyd.

訳注:以下のライセンスに関する日本語訳はあくまで参考です。 原文を残しますので、正確な内容は原文を参照して下さい。

Permission is granted to make and distribute verbatim copies of this manual provided the copyright notice and this permission notice are preserved on all copies.

本マニュアルと同一の複製を作成し、配布することを許可する。 ただし本著作権告知と本許諾告知をすべてにそのまま含めること。

Permission is granted to copy and distribute modified versions of this document under the conditions for verbatim copying, provided that this copyright notice is included exactly as in the original, and that the entire resulting derived work is distributed under the terms of a permission notice identical to this one.

この文書の修正版は、それと同一の複製を作成するという条件で、その複製の 作成と配布を許可する。ただし、本著作権告知を原文のまま正確に含めること。 また結果として派生した成果物全体は、本許諾告知と同一の許諾告知への 合意の下で配布すること。

Permission is granted to copy and distribute translations of this document into another language, under the above conditions for modified versions.

この文書の他言語への翻訳版は、修正版に関する上記条件の下で、複製と 配布を許可する。

この文書を出版物に組み入れたいと考えている場合は、是非本文書の保守担当 の誰かに連絡をとって下さい。そうすれば、極力最新情報を利用できるよう 努力します。

本文書がその所期の目的に応えるという保証は何もありません。 これはたんに無償のリソースとして提供しているだけです。 そういうものですから、本文書で提供している情報の作者と保守担当は、 その情報が本当に正確だという、いかなる保証もできません。