GTK+
The GIMP Toolkit

General
Introduction
Screenshots
Download
Mailing Lists
Language Bindings
Themes
Bug Tracker
Plans

Documentation
FAQ
GTK+-2.0 Tutorial
GTK+-1.2 Tutorial
API Reference

Projects
Pango
GNOME
GTK+ for Win32
GTK+ on DirectFB

More Projects...

Applications
GIMP
Abiword
Dia
Glade
GnuCash
Gnumeric

GNOME Software Map

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

目次
1. 一般情報
1.1. 注意:この FAQ は、GTK+ 2.x 用に変更中です。
1.2. まずは、ご挨拶[GTK 2.x]
1.3. 作者について [GTK 2.x]
1.4. GTK+ とは何か[GTK 2.x]
1.5. GTK+ の "+" はどういう意味? [GTK 2.x]
1.6. GTK+ や GDK、それにGLib の"G"とは何の意味? [GTK 2.x]
1.7. GTK+ に関するドキュメントはどこにあるの? [GTK 2.x]
1.8. GTK+ に関するメーリングリスト(あるいはそのアーカイブ)はあるの?[GTK 2.x]
1.9. GTK+ で助けが欲しい時はどうするの? [GTK 2.x]
1.10. GTK+ のバグはどうやって報告すればいいの?
1.11. GTK+ の Windows 版はあるの?
1.12. GTK+で書いたアプリケーションには、どんなのがあるの?
1.13. GTK+で書くアプリケーションを探してるんだけど、 IRC クライアントはどうだろうか?
2. GTK+ はどこにあって、設定やインストール、障害の復旧はどうやるの?
2.1. GTK+ をコンパイルするには何が必要? [GTK 2.x]
2.2. GTK+ はどこで入手できるの? [GTK 2.x]
2.3. GTK+の設定とコンパイルはどうやるの? [GTK 2.x]
2.4. GTK+のコンパイル中に次のようなエラーが出たmake: file `Makefile' line 456: Syntax error [GTK 2.x]
2.5. GTK+ をコンパイルしてインストールしてみたけど、 リンクするものが何もない[GTK 2.x]
2.6. GTK+ アプリケーションをインストールすると、configure で、GTK が見つからないというレポートが出る [GTK 2.x]
3. GTK+ の開発
3.1. みんながずっと話しているCVSっていうのはどんなもので、 どうやってアクセスすればいいの?
3.2. どうやれば GTK+ に 貢献できるの?
3.3. 送ったパッチが採用されたかどうかはどうすればわかるの?もし採用されない場合、それはどうして?
3.4. 新しいウィジェットをライブラリに組み入れるにあたってのポリシーは何?
3.5. C 以外の言語のバインディング作業をしている人は誰かいるの?
4. GTK+ を用いた開発:初めての開発
4.1. どうやって始めればいいの?
4.2. GTK+ で、Glade を使って GUI を構築するにはどうすればいいの? [GTK 2.x]
4.3. GTK+ でセキュリティに注意を要するプログラムや SUID/SGID プログラムを書くにはどうすればい いの? GTK+ は安全? GTK_MODULES にセキュリティホールがあると聞いたけど、それは何?
4.4. Hello World という小さなプログラムをコンパイルしようとしたけど失敗した。何か手がかりはある?[GTK 2.x]
4.5. make ユーティリティを 使う場合は、どうするのだろうか?[GTK 2.x]
4.6. makefile でバッククォートを使っているけど、make の最中に失敗した。
4.7. configure 関係を追加したいのですが、どうすればいいですか?
4.8. GTK+ のアプリケーションを gdb でデバッグしたいのだけど、 あるブレークポイントにくると、X サーバーがハングする。どうして?
5. GTK+ を用いた開発:一般的な質問
5.1. GTK にはどんなウィジェットがあるの?
5.2. GTK+ はスレッドセーフなの?GTK+ でマルチスレッドのアプリケーション はどうやって書くの? [GTK 2.x]
5.3. GTK+ を使って、別スレッドでちょっとしたことをしている。 gdk_threads_enter/gdk_threads_leave() を使ってちゃんとロックしているのに、 表示が正しく更新されない。 [GTK 2.x]
5.4. スレッドで、メインループを使って関数を実行する、簡単な方法は? [GTK 2.x]
5.5. GTK+ アプリで fork() すると、妙な 'x io error' が起こるのはなぜ?
5.6. ボタンを押した時にボタンの内容が移動しないのはなぜ?そういうふうに動作させるパッチはこれなんだけど...
5.7. あるウィジェットがトップレベルウィンドウなのか、それとも他の親なのかは、どうやって識別すればいいの?
5.8. GtkWindow のウィンドウIDはどうやって取得するの?
5.9. (例えばリストウィジェットで)ダブルクリックのイベントはどうやって捕捉するの?
5.10. ところで、シグナルとイベントはどう違うの?
5.11. delete_event(あるいは他のイベント)のハンドラに渡したデータが壊れてしまう
5.12. 自前のシグナルをイベント(これは何でも良いけど)に接続したけど、そのシグナルが捕捉できない。 なにがまずいの?
5.13. 新しいシグナルを GTK+ ウィジェットに追加する必要があるのだけど、何か方法は?
5.14. その領域内にぴったり収まるように切り詰めたテキストを表示することは可能?
5.15. ウィンドウをモーダルにするのはどうやるの?/ウィンドウを一つだけ アクティブにするのはどうやるの?
5.16. ウィジェット(例えば 進捗表示バー)が更新されないのはなぜ?[GTK 2.x]
5.17. データと GTK+ のオブジェクト/ウィジェットとの関連づけって、どうやるの? [GTK 2.x]
5.18. オブジェクトに関連づけたデータは、どうやって削除するの?
5.19. ウィジェットの親はどうやって変えるの?
5.20. ウィジェットの位置はどうやればわかるんですか?
5.21. ウィジェットやウィンドウの大きさはどうやって設定すればいいの?ユーザーに ウィンドウの大きさを変えさせたくないのだけど、どうすればいいの? [GTK 2.x]
5.22. GTK+ アプリケーションにポップアップメニューを追加するのはどうやるの?
5.23. ボタンのような、ウィジェットは、どうやれば無効にしたり有効にしたりできるの?
5.24. gtk_clist_* 関数のテキスト引数は const で宣言してはだめ?
5.25. どうやれば、ピクセル(イメージデータ)を画面に描画できるの?
5.26. ウィンドウをリアライズしたり表示したりせずに、ピクスマップを 作るのはどうやるの?
5.27. ドラッグ&ドロップはどうやるの?
5.28. 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.4. GTK+ とは何か[GTK 2.x]

GTK+ とは何か[GTK 2.x]

GTK+ というのは、様々なプラットフォームで動作する、 グラフィカルなユーザーインタフェースを作成するためのツールキットのことで、 もともと、Motif の一般的な外見と使い勝手を念頭において設計されたものです。 実際は、Motif よりもずっと優秀なようです。 このツールキットには、ファイルセレクションウィジェットとか カラーセレクションウィジェットといった、良く使われる複雑なウィジェットが入っています。

当初、GTK+ は GIMP (GNU Image Manipulation Program) 用のウィジェットセットとして 開発されました。しかしそれ以後広く使われるようになり、今では、膨大な数の アプリケーションで使用され、 GNOME デスクトッププロジェクトのツールキットに採用されています。

GTK+ はフリーソフトウェアであり、GNU プロジェクトの一環です。 しかし GTK+ に関するライセンス条件、つまり GNU LGPL では、 独占的なソフトウェアを開発している場合をはじめとして、 あらゆる開発者は当ソフトウェアを使用できるようになっています。 その際、ライセンス料もロイヤリティもまったく不要です。

GTK+ は、C/C++ のみならず、 さまざまな言語バインディング をサポートするために、新規に設計されました。 (特にGlade GUI ビルダーとの組み合せで)Perl や Python のような言語から GTK+ を使用すれば、アプリケーションを短期開発する効果的な方法になります。


1.7. GTK+ に関するドキュメントはどこにあるの? [GTK 2.x]

GTK+ に関するドキュメントはどこにあるの? [GTK 2.x]

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

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

現在、GTK+ や GDK、それに GNOME のプログラミングを扱っている 書籍が、何種類か利用可能になっています。 しかしあいにく、その書籍はすべて、今のところ GTK+ 1.x に基づいたものです。

  • 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

  • "GTK+ Programming in C" by Syd Logan. ISBN: 0-1301-4264-6

  • "Linux GNOME/GTK+ Programming Bible" by Arthur Griffith. ISBN: 0-7645-4640-6

  • "Beginning GTK+/GNOME Programming" by Peter Wright. ISBN: 1-8610-0381-1

  • "Sams Teach Yourself GTK+ Programming in 21 Days" by Donna Martin . ISBN: 0-6723-1829-6


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

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

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

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

  • バグの再現方法

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

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

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

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


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

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

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

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

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


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


2.2. GTK+ はどこで入手できるの?[GTK 2.x]

GTK+ はどこで入手できるの?[GTK 2.x]

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

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

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


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

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

この問題は、GTK+ のライブラリが見つからないとか、 バージョンが間違っている場合に、よく起こります。この場合、 コンパイラは、一般に 'unresolved symbol' に関するメッセージを表示します。

ライブラリが見つかることを確認して下さい。 /etc/ld.so.conf を編集して、そこに GTK ライブラリがあるディレクトリを書いておく必要があります。 次のようなものです。

/usr/X11R6/lib
/usr/local/lib

それから root ユーザーになって、/sbin/ldconfig コマンドを実行する必要があります。 次のコマンドを使えば、 GTK で使う必要があるライブラリがわかります。

pkg-config gtk+-2.0 --libs

(Solaris のように)ライブラリを探すのに ld.so を使わないシステムなら、 LD_LIBRARY_PATH 環境変数を使う(あるいはプログラム中にパスを埋め込む。この方法は ここでは述べませんけど)必要があります。ですから、Bourne シェル系のシェルでは、 (GTK ライブラリが /usr/local/lib にあるとすれば)次のようにすればいいでしょう。

export LD_LIBRARY_PATH=/usr/local/lib

csh だと次のようになります。

setenv LD_LIBRARY_PATH /usr/local/lib


2.6. GTK+ アプリケーションをインストールすると、configure で、GTK が見つからないというレポートが出る[GTK 2.x]

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

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

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


Chapter 3. GTK+ の開発

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

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

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

GTK+ の開発者たちは、このCVS のリポジトリを使って、 GTK+ の現時点での開発バージョンのマスターコピーを保存します。 そういったふうですから、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 には、パスワードは ありませんから、ただ Enter キーを押すだけです。

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

    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++ ラッパーがいくつかあります。

    • gtkmm パッケージ。これは GTK+ 用のラッパーです。このラッパーの ホームページは次のサイトにあります。 http://www.gtkmm.org/

    • VDK パッケージ。これは ボーランド系 GTK+ アプリケーション ビルダーの、ベースパッケージとして構築されたものです。 このパッケージのホームページは http://vdkbuilder.sourceforge.net/ です。

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

  • 現在開発中の 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.4. Hello World という小さなプログラムをコンパイルしようとしたけど失敗した。何か手がかりはある?[GTK 2.x]

Hello World という小さなプログラムをコンパイルしようとしたけど失敗した。何か手がかりはある?[GTK 2.x]

コーディングは得意なようだから、ここではコンパイル時のエラーは扱いません。

GTK+ ベースのプログラムをコンパイルする、代表的なコマンドラインは 次のとおりです。

gcc -o myprog [c files] `pkg-config gtk+-2.0 --cflags --libs`

このコマンドラインで使っているバッククォート文字に注目して下さい。 GTK+ ベースの開発を始めるにあたって、やってしまう共通の間違いは、 バッククォート (`)の代わりに引用符 (') を使うことです。 そうしてしまうと、コンパイラは gtk-config --cflags --libs というファイル なんか知らない、と文句を言うでしょう。テキストをバッククォートで囲むと、 シェルに対する命令になり、 バッククォート内のコマンドを実行した結果でコマンドラインを置き換えます。

上記のコマンドラインは 次のことを保証しています。


4.7. 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

その後、次のコマンドをタイプするだけで、これらが使えます。

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

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


4.8. GTK+ のアプリケーションを gdb でデバッグしたいのだけど、 あるブレークポイントにくると、X サーバーがハングする。どうして?

GTK+ のアプリケーションを gdb でデバッグしたいのだけど、 あるブレークポイントにくると、X サーバーがハングする。どうして?

Federico Mena Quintero によれば

「X はハングアップしていない。Gtk の、マウスをグラブしている 部分から呼び出している、コールバック内部のブレークポイントにくると、そうなる ようだ。」

「そのプログラムを、--sync オプション付きで 実行するといい。デバッグがもっとしやすくなる。また、コンソール を使ってデバッガを動かした方がいいかもしれない。X サーバーが 動いている別のコンソールで、そのプログラムを走らせればいいだけかも しれない。」

Eric Mouw の別の解決策では

「他の未使用のシリアルポートにつながった古い端末は、X プログラムの デバッグにも最適だよ。使い古した vt100/vt220 端末は二束三文だけど、 ちょっと得難いものなんだ(ここオランダではね。場所によって違うだろう けど)。」

もう一つのやり方は、Xnest 上で、自分のアプリケーションを動かすことです。 Xnest というのは、ある X サーバーのルートウィンドウを、 他の X サーバーの通常のウィンドウ内に表示するものです。 Xnest のディスプレイでポインタをグラブしても、通常の X サーバーで動かしている デバッガの GUI には、何の影響もありません。
Xnest :1
twm -display :1
myapp --display=:1


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

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

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

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

GDK でスレッドが使えるようにするには、上記の関数呼び出しの他に、 gdk_threads_init() も呼び出す必要があります。 グローバルなロックは一つだけですが、プログラム全体を通じて、 GDK を呼び出す前には、gdk_threads_enter() を使って、そのロックを取得し、 終ったら、gdk_threads_leave() を使って、解放しなければなりません。

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

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

  /* init threads */
  g_thread_init(NULL);
  gdk_threads_init();

  /* init gtk */
  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:       1.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:   Owen Taylor <otaylor@gtk.org>
 * Modified at:   Wed May 28 10:43:00 2003
 *-----------------------------------------------------------------------*/
/*
 * 以下のコマンドでコンパイルして下さい
 *
 * cc -o gtk-thread gtk-thread.c `pkg-config --cflags --libs gtk+-2.0 gthread-2.0`
 *
 * 先のバージョンにあったバグをいくつか指摘してくれた Sebastian Wilhelmi に感謝します。
 *
 */

#include <unistd.h>
#include <gtk/gtk.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(g_random_int_range (1, 4));

      /* 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!");

          
          /* X のコマンドがすべて、X サーバーに送られることを確認する。
           * 厳密に言えば、ここでは不要だが、メインループを実行している場所
           *以外で、スレッドからなにかをする場合は、常にこうした方がいい。
           */
          gdk_flush ();

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

  return NULL;
}

int main(int argc, char *argv[])
{
  GtkWidget *window;
  GtkWidget *label;
  GError *error = NULL;
  yes_or_no_args yes_args, no_args;

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

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

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

  g_signal_connect(window, "destroy",
                  G_CALLBACK(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;
  if (!g_thread_create(argument_thread, &yes_args, FALSE, &error))
    {
      g_printerr ("Failed to create YES thread: %s\n", error->message);
      return 1;
    }

  no_args.label = label;
  no_args.what = NO_IT_IS_NOT;
  if (!g_thread_create(argument_thread, &no_args, FALSE, &error))
    {
      g_printerr ("Failed to create NO thread: %s\n", error->message);
      return 1;
    }

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

  return 0;
}

5.3. GTK+ を使って、別スレッドでちょっとしたことをしている。 gdk_threads_enter/gdk_threads_leave() を使ってちゃんとロックしているのに、 表示が正しく更新されない。 [GTK 2.x]

GTK+ を使って、別スレッドでちょっとしたことをしている。 gdk_threads_enter/gdk_threads_leave() を使ってちゃんとロックしているのに、 表示が正しく更新されない。 [GTK 2.x]

効率を上げるため、X ウィンドウシステムでは、 一つ一つコマンドをすぐに送るのではなく、 コマンドをいくつか一まとめにして、X サーバーにバッチで送っています。

マルチスレッド化されていないプログラムでは、この点に関する心配は無用です。 というのは、メインループに制御が戻った時には、 まず最初に、残っている X リクエストが X サーバーに送出されるからです。

しかし、メインループ以外でスレッドから GTK+ を呼び出している場合は、 バッチ化されたコマンドをいつ送出するのかは、GTK+ にはわかりません。 そのため、通常は、gdk_thread_leave() を呼び出す前に、 gdk_flush() を呼んでおく方がいいです。

実際は、gdk_flush() だと、ここで必要とする以上に高くつきます。 というのは、この関数も同様に、 X サーバーで残りのコマンドが終了するのを待つからです。 ですから、性能が気になる場合は、 直接 XFlush() を呼び出した方がいいかも知れません。


#include <gdk/gdkx.h>

void my_flush_commands (void)
{
  GdkDisplay *display = gdk_display_get_default ();
  XFlush (GDK_DISPLAY_XDISPLAY (display);
}

5.5. 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 `pkg-config gtk+-2.0 --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.9. (例えばリストウィジェットで)ダブルクリックのイベントはどうやって捕捉するの?

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

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.10. ところで、シグナルとイベントはどう違うの?

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

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

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

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

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


5.13. 新しいシグナルを 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.14. その領域内にぴったり収まるように切り詰めたテキストを表示することは可能?

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

X のリソースを保存しようとする動きの結果が、 (クリッピング以外の)GTK の動作になります。 (たとえば)ラベルウィジットには、専用の 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.17. データと GTK+ のオブジェクト/ウィジェットとの関連づけって、どうやるの? [GTK 2.x]

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

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

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

void g_object_set_data(GObject *object, const gchar *key, gpointer data);

gpointer g_object_get_data(GObject *object, const gchar *key);

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

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

g_object_set_data(G_OBJECT(w),"p1 data",(gpointer)&p1);
g_object_set_data(G_OBJECT(w),"p2 data",(gpointer)&p2);

result = g_object_get_data(G_OBJECT(w),"p1 data");

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


5.22. 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.25. どうやれば、ピクセル(イメージデータ)を画面に描画できるの?

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

これにはいくつか方法があります。いちばん簡単なのは、 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 のセレクション機能は、GtkList の selection_mode で決まります。 したがって、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_shell_append(GTK_MENU_SHELL(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, ...)に置き換えれば、 個々にリンクされたリスト(GSList オブジェクト)でも使えます。 でも忘れないで下さい、個々にリンクされたリストでは、後向きには たどれませんから、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 をカバーしているようなものだよ。;)」

「GIMP のように、完全に gtk に依存しているアプリケーションの内部で 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>


/* スキャナに与えるテスト用テキスト */
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" );


/* 特定のシンボルに対して返す計数値を定義 */
enum {
  SYMBOL_PING = G_TOKEN_LAST + 1,
  SYMBOL_PONG = G_TOKEN_LAST + 2,
  SYMBOL_ZONK = G_TOKEN_LAST + 3
};


/* シンボルの配列 */
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;

  
  /* 正しいシンボルか */
  g_scanner_get_next_token (scanner);
  symbol = scanner->token;
  if (symbol < SYMBOL_PING ||
      symbol > SYMBOL_ZONK)
    return G_TOKEN_SYMBOL;

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

  
  /* 機能オプションの '-' か */
  g_scanner_peek_next_token (scanner);
  if (scanner->next_token == '-')
    {
      g_scanner_get_next_token (scanner);
      negate = !negate;
    }

  
  /* 浮動小数点か(整数はその場で浮動小数点に変換される) */
  g_scanner_get_next_token (scanner);
  if (scanner->token != G_TOKEN_FLOAT)
    return G_TOKEN_FLOAT;

  
  /* 次のトークンが ';' だということを確認する */
  if (g_scanner_peek_next_token (scanner) != ';')
    {
      
      /* 違えば、セミコロン以外を平らげ、エラーで終了 */
      g_scanner_get_next_token (scanner);
      return ';';
    }

  
  /* 値を割り当て、セミコロンを取り、正常終了 */
  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);

  
  /* 必要に合わせて、lex の動作を調整
   */
  
  /* 浮動小数点以外(8進数、16進数 ...)を G_TOKEN_INT に変換 */
  scanner->config->numbers_2_int = TRUE;
  
  /* G_TOKEN_INT を G_TOKEN_FLOAT へ変換 */
  scanner->config->int_2_float = TRUE;
  
  /* G_TOKEN_SYMBOL は返さないが、シンボルの値は返す */
  scanner->config->symbol_2_token = TRUE;

  
  /* シンボルをスキャナにロード */
  while (symbol_p->symbol_name)
    {
      g_scanner_add_symbol (scanner,
                            symbol_p->symbol_name,
                            GINT_TO_POINTER (symbol_p->symbol_token));
      symbol_p++;
    }

  
  /* テキストを与える */
  g_scanner_input_text (scanner, test_text, strlen (test_text));

  
  /* エラーハンドラに入力の命名方法を指示する */
  scanner->input_name = "test text";

  
  /* 繰り返しスキャン。解析は、入力の最後まで、または lex で
   * エラーが発生するか、サブルーチンで不正構文が出てくるまで
   * 継続。
   */
  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);

  
  /* 構文エラーに関するエラーメッセージを出す */
  if (expected_token != G_TOKEN_NONE)
    g_scanner_unexp_token (scanner, expected_token, NULL, "symbol", NULL, NULL, TRUE);

  
  /* 解析終了 */
  g_scanner_destroy (scanner);

  
  /* 結果表示 */
  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;

以下のようにして、"am" をシンボルに加えると、

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

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

"hi i am 17"
| | | |
| | | v
| | v TOKEN_FLOAT, value: 17.0 (int->float の自動変換)
| | TOKEN_SYMBOL, value: "symbol value" (ハッシュテーブルの検索に成功したので
| | TOKEN_IDENTIFIER が
| | TOKEN_SYMBOL に変わり、シンボル値を
| v 引き継いだ)
v 'i' (文字はすべて >0 および <256 なので、'i' は正しいトークンになり得る)
TOKEN_IDENTIFIER, value: "hi"

トークンの順序は、自分のコードに合わせる必要があります。また 不要なものが出てくる場合は、エラーにしてください。

/* 識別子 ("hi") か  */
g_scanner_get_next_token (scanner);
if (scanner->token != G_TOKEN_IDENTIFIER)
  return G_TOKEN_IDENTIFIER;
/* トークン 'i' か */
g_scanner_get_next_token (scanner);
if (scanner->token != 'i')
  return 'i';
/* シンボル ("am") か */
g_scanner_get_next_token (scanner);
if (scanner->token != G_TOKEN_SYMBOL)
  return G_TOKEN_SYMBOL;
/* 浮動小数点 (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 に寄稿したければ、載せるべきだと思う正確な内容(質問と回答)をメッセージにして、 電子メールで我々の誰かに送って下さい。 この文書が成長し、より役立つものになれるのは、皆さんの協力があるからこそです。

この文書は Tony Gale<gale@gtk.org> が保守しています。 この FAQ を最初に作ったのは Shawn T. Amundson <amundson@gimp.org> です。

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

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

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.

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

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

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