この文書の日本語訳は、芳賀靖史(yasufumi.haga@nifty.com)が、
2003年5月25日に行ないました。誤訳、その他間違い等がありましたら
ご連絡下さい。
Note: 注意:この FAQ では、主に GTK+ 1.2 を扱っています。
この FAQ では、主に GTK+ 1.2 を扱っています。GTK+ 2.x に関する部分は、先頭に次のテキストを付けて、明示しています。[GTK+ 2.x]
まずはご挨拶
本 FAQ の作者は、次の人たちに感謝したいと思います。
- Havoc Pennington
- Erik Mouw
- Owen Taylor
- Tim Janik
- Thomas Mailund Jensen
- Joe Pfeiffer
- Andy Kahn
- Federico Mena Quntero
- Damon Chaplin
- そして、GTK+ メーリングリストのメンバー全員に
あなたを忘れていたなら、メールで知らせて下さい。重ねて感謝します。(挨拶が簡単だってことは、わかってますけどね :) )
作者について
初めに GTK+ を作ったのは次の人たちです。
- Peter Mattis
- Spencer Kimball
- Josh MacDonald
それ以来、他の人たちも多くのものを追加してきました。GTK+ チームに関しては、ディストリビューションに
ある AUTHORS ファイルを見て下さい。
GTK+ とは何か
GTK+ というのは Motif の一般的な見かけと使い勝手を持たせるように
設計された、小型で効率的なウィジェットセットのことです。実際は、
Motif よりもずっと優秀なようです。このウィジェットセットには
共通のウィジェットや、ファイルセレクションウィジェットやカラー
セレクションウィジェットといったような、もっと複雑なウィジェット
が含まれています。
GTK+ には独自の特徴がいくつかあります。(そういったものを提供
しているウィジェットライブラリを、少なくとも私は他に知りません)
例えば、ボタンにはラベルが含まれず、子ウィジェットが含まれます。
その子ウィジェットは、たいていのインスタンスではラベルになります。
でも子ウィジェットはピクスマップでもかまわないし、イメージや、
可能であればプログラマが望むどのような組合せでも良いのです。
ライブラリ全体にこの柔軟性が染み込んでいるんです。
GTK+ の "+" はどういう意味?
Peter Mattis が gtk の
メーリングリストで、次のようなことを知らせてくれました。
「僕が最初に書いた gtk には、libglib, libgdk それに libgtk という
三つのライブラリがあった。これは、ウィジェット階層がフラットだという
のが特徴だった。つまり、既存のウィジェットから新しいウィジェットを
導き出すことができなかったんだ。それに、入っていたのが、現在 gtk+ に
存在するシグナルメカニズムじゃなくて、ずっと標準的なコールバック
メカニズムだった。+を加えたのは、最初のバージョンと新しいバージョン
を区別するためだった。オブジェクト指向的な特徴を加えて、最初の gtk を
拡張したものだと考えても良い。」
GTK+ や GDK、それにGLib の"G"とは何の意味?
GTK+ == Gimp Toolkit
GDK == GTK+ Drawing Kit
GLib == G Library
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 のプログラミングを扱っている
書籍が、二種類利用可能になっています。
GTK+ で助けが欲しい時はどうするの?
まず、ドキュメントやこの FAQ、あるいはチュートリアルに自分の答が
無いことを確認して下さい。終りましたか?本当にありませんね?
その場合、質問する最適の場所は、GTK+ メーリングリストです。
GTK+ のバグはどうやって報告すればいいの?
バグは GNOME バグ追跡システムまで報告して下さい。次のサイトです
(http://bugzilla.gnome.org)。
このシステムを使って新しいバグレポートを登録できるようにするには、
その前に自分のメールアドレスを入力して、パスワードを受け取る必要
があります。
バグレポートを投稿する際は、たくさんのオプションを選択したり、いろいろ記入
します。情報が多ければ多いほど、問題の原因を突き止めるのが簡単になることは
覚えておいて下さい。後で役に立つことになるかもしれない、他の情報としては
次ようなものがあります。
バグの再現方法
gtk/ サブディレクトリに組み込まれている、testgtk プログラムで
そのバグが再現できる場合は、一番好都合です。さもなければ、
そのバグの動きを示す、短いテストプログラムを含めて下さい。
最後の手段としては、ダウンロード可能なたくさんのソフトウェア
へのポインターを教えてくれても構いません。
(GIMP 内で再現できるバグなら、testgtk で再現できるバグと
ほとんど同じくらい好都合です。GIMP で見つかったバグを
報告するつもりなら、お使いの GIMP のバージョン番号も
報告して下さい。)
そのバグがクラッシュなら、そのクラッシュが発生した時に表示された
テキストを正確に報告して下さい。
スタックのトレースといったような情報も役立つかもしれませんが、
必要なわけではありません。でも、実際に送るスタックトレースが X のエラー
のものであれば、コマンドオプションの --sync を
つけてテストプログラムを動かしてそのスタックトレースが取れるのなら、
いっそう役に立つでしょう。
GTK+で書いたアプリケーションには、どんなのがあるの?
GTK+ ベースのアプリケーションの一覧は、GTK+ ウェブサーバー
http://www.gtk.org/apps/ で
見つかります。そこには 350 個以上のアプリケーションがあります。
そこが閲覧できなければ、GNOME プロジェクト
http://www.gnome.org/
のために一生懸命活動しているプロジェクトを探して下さい。そしてゲームを
作ったり、役に立つものを作って下さい。
そういったプロジェクトの中には次のようなものもあります。
GTK+で書くアプリケーションを探してるんだけど、 IRC クライアントはどうだろうか?
gtk メーリングリストで訊いて、助言を求めて下さい。IRC クライアントは、
少なくとも三つが開発中です(実際はたぶんそれ以上です。次のサーバーでは
そういったものをひと塊にして、リストしてあります。
http://www.forcix.cx/irc-clients.html)
- X-Chat.
- girc. (GNOME に含まれています)
- gsirc. (gnome の 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
どうやれば GTK+ に
貢献できるの?
簡単です。プログラムで何かが思ったように動作しない場合、ドキュメント
を調べて間違いをしていないことを確認して下さい。本当にバグだったり
重要な部分が欠けている場合は、GTK+ のソースで原因を追跡して、その部分
を変更し、それから 'context diff' 形式でパッチを作って下さい。次の
ようなコマンドを使えばできます。
diff -ru <古いファイル> <新しいファイル>
それから、そのパッチファイルを README ファイルに従って、次のサイトに
アップロードして下さい。
ftp://ftp.gtk.org/incoming
命名規則に従っていることは確認して下さい。そうしないとそのパッチは
たんに削除されるだけです。ファイル名は次の形式にしてください。
gtk<ユーザー名>-<yymmdd-n形式の日付>.patch.gz
gtk-<ユーザー名>-<yymmdd-n形式の日付>.patch.README
日付の部分の "n" は、その日にアップロードしたパッチの(0 から始まる)重複しない数
です。一日の内に二つ以上のパッチをアップロードしない限り、これは 0 のはずです。
例:
gtk-gale-982701-0.patch.gz
gtk-gale-982701-0.patch.README
いったん 何か をアップロードしたら、
その README を ftp-admin@gtk.org へ送って下さい。
送ったパッチが採用されたかどうかはどうすればわかるの?もし採用されない場合、それはどうして?
アップロードされたパッチは ftp://ftp.gtk.org/pub/gtk/patches
に移され、そこで、GTK+ 開発チームの誰かが取り上げます。
採用されれば、/pub/gtk/patches/old に移します。
採用されないパッチは、その理由が何であれ、
/pub/gtk/patches/unapplied か
/pub/gtk/patches/outdated に移されます。
この時点で gtk-list メーリングリストに、
どうして自分のパッチが採用されなかったのか尋ねることができます。
パッチを採用してはいけないという理由は、それが素直に適用できない
といったことから、正しくないといったことまで、沢山あります。
自分のパッチが初回でうまくいかなくても、しょげないでくださいね。
新しいウィジェットをライブラリに組み入れるにあたってのポリシーは何?
これは作者しだいです。ですから、ウィジェットを作ったら、ライブラリの作者に
尋ねる必要があります。一般的なガイドラインとしては、広く役立ち、その
ウィジェットセットの評判を落とすようなことにならないものなら、快く
受け入れてくれます。
どうやって始めればいいの?
だったら、GTK+ をインストールしてから、それを使って徐々に
アプリケーションを作れるようにするものが二つあります。一つは
GTK+ のチュートリアル
http://www.gtk.org/tutorial/ ですが、これは現在作成途上にあります。
これは C を使ってアプリケーションを書く手ほどきをしてくれます。
このチュートリアルには、GTK+ にあるウィジェットすべての情報が
含まれているわけではありません。GTK+ ウィジェットすべての基本的な
使い方に関するコード例については、GTK+ のディストリビューションにある、
gtk/testgtk.c ファイル(とその関連ソースファイル)を見る方がいいです。
これらの例を見れば、ウィジェットでできることに関する充分な基礎知識
が得られます。
GTK+ でセキュリティに注意を要するプログラムや SUID/SGID プログラムを書くにはどうすればいいの? GTK+ は安全? GTK_MODULES にセキュリティホールがあると聞いたけど、それは何?
この質問に簡単に答えれば、GTK+ で SUID/SGID を使うプログラムは書くな、
ということになります。
この件に関して、GTK+ 開発者の立場を完全に説明したものが
http://www.gtk.org/setuid.html にあるので見て下さい。
Hello World
という小さなプログラムをコンパイルしようとしたけど失敗した。何か手がかりはある?[GTK 2.x]
コーディングは得意なようだから、ここではコンパイル時のエラーは扱いません。
GTK+ ベースのプログラムをコンパイルする、代表的なコマンドラインは
次のとおりです。
gcc -o myprog [c files] `pkg-config gtk+-2.0 --cflags --libs`
このコマンドラインで使っているバッククォート文字に注目して下さい。
GTK+ ベースの開発を始める時に犯す共通の間違いは、バッククォート (`)
の代わりに引用符 (') を使うことです。そうしてしまうと、コンパイラは
gtk-config --cflags --libs というファイル
なんか知らない、と文句を言うでしょう。バッククォート内のテキストは、
このコマンドを実行した結果の出力でコマンドラインを置き換えるよう、
シェルに指示するものです。
上記のコマンドラインは
次のことを保証しています。
make ユーティリティを
使う場合は、どうするのだろうか?[GTK 2.x]
以下は、GTK+ ベースのプログラムをコンパイルする、makefile の
サンプルです。
# basic GTK+ app makefile
SOURCES = myprg.c foo.c bar.c
OBJS = ${SOURCES:.c=.o}
CFLAGS = `pkg-config gtk+-2.0 --cflags`
LDADD = `pkg-config gtk+-2.0 --libs`
CC = gcc
PACKAGE = myprg
all : ${OBJS}
${CC} -o ${PACKAGE} ${OBJS} ${LDADD}
.c.o:
${CC} ${CFLAGS} -c $<
# end of file |
make ユーティリティに関する詳細は、
関係するマニュアルページか、適切な info ファイルを読んで下さい。
makefile でバッククォートを使っているけど、make の最中に失敗した。
バッククォート構文は、古い make ユーティリティ
では受け付けないものがあります。そういった make
を使っている場合は、make の最中に多分失敗するでしょう。再度バッククォート
構文が動作するようにするには、GNU make ユーティリティを使って下さい
(ftp://ftp.gnu.org/ に
ある GNU の ftp サーバーから入手できます)。
configure を追加したいのですが、どうすればいいですか?
autoconf/automake を使うには、最初に適切なパッケージをインストール
しなければなりません。次のようなものです。
- m4 プリプロセッサ。v1.4 ないしはそれ以降
- autoconf v2.13 ないしはそれ以降
- automake v1.4 ないしはそれ以降
これらのパッケージは 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 を扱っているウェブ上のリソースも
たくさんあります)。
GTK+ のアプリケーションを gdb でデバッグしたいのだけど、
あるブレークポイントにくると、X サーバーがハングする。どうして?
以下は Federico Mena Quintero の答えです。
「X がハングアップしているのはでないよ。Gtk の、マウスをグラブしている
部分から呼び出しているコールバック内のブレークポイントにくると、そうなる
ようだ。」
「そのプログラムを --sync オプション付きで
実行するといい。デバッグがもっとしやすくなるよ。また、コンソール
を使ってデバッガを動かす必要があるかも知れないし、X サーバーが
動いている別のコンソールで、そのプログラムを走らせればいいだけかも
しれない。」
Eric Mouw には別の解決策があります。
「他の未使用のシリアルポートにつながった古い端末は、X プログラムの
デバッグにも最適だよ。使い古した vt100/vt220 端末は二束三文だけど、
ちょっと得難いものなんだ(ここオランダではね。場所によって違うだろう
けど)。」
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 |
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 `pkg-config gtk+-2.0 --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);
} |
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);
} |
ボタンを押した時にボタンの内容が移動しないのはなぜ?そういうふうに動作させるパッチはこれなんだけど...
Peter Mattis からの回答です。
「ボタンを押した時に下方および右方にボタンがその子ウィジェットを動かさないのは、
視覚的にそういうことが起きているからじゃないと思う。私見だけど、君がボタンを
まっすぐに見ているんじゃないかな。つまり、ユーザーインタフェースは平面になって
いて、君はそこから離れてそこをまっすぐ見ている。ボタンを押すと、そのボタンは
直接君から離れていく。完璧に正しく言えば、その子ウィジェットは、わずかだけど
実際は縮んでいるはずだと思う。でも、その子ウィジェットがどうして下方および左方に
移動するのかはわからない。忘れないで欲しいんだけど、その子ウィジェットはボタン
表面にアタッチされることになっている。その子がボタンの表面で滑るように現れる
のは良いことじゃないよ。」
「もっと現実的に言えば、実際ある意味、実装してみて、良いようには見えない
から削除することにしたんだ」
あるウィジェットがトップレベルウィンドウなのか、それとも他の親なのかは、どうやって識別すればいいの?
ウィジェットのトップレベルにあたる親を見つけるには、二つの方法があります。
簡単な方は、gtk_widget_top_level()関数を呼ぶこと
です。この関数はトップレベルウィンドウにあたるウィジェットへのポインタを返します。
もっと込み入った(でも既知のタイプの、もっとも近い親を取得できるようになるので、
より制限が少ない)方法は、以下のように
gtk_widget_get_ancestor() を使うことです。
GtkWidget *widget;
widget = gtk_widget_get_ancestor(w, GTK_TYPE_WINDOW); |
実際はすべての GTK_TYPE をこの関数の第二引数に使えるので、特定のウィジェットの
親ウィジェットはどれであっても取得できます。hbox の中に vbox があり、同様に
その vbox に他のアトミックなウィジェット(エントリ、ラベル、他)が入っている
ものを考えてみて下さい。entryウィジェットを使って、
最初の hbox を見つけるには、たんに次のように使用すればいいんです。
GtkWidget *hbox;
hbox = gtk_widget_get_ancestor(w, GTK_TYPE_HBOX); |
GtkWindow のウィンドウIDはどうやって取得するの?
実際の Gdk/X ウィンドウは、
ウィジェットをリアライズすると生成されます。このウィンドウ ID は、次のように
すれば取得できます。
#include <gdk/gdkx.h>
Window xwin = GDK_WINDOW_XWINDOW (GTK_WIDGET (my_window)->window); |
(例えばリストウィジェットで)ダブルクリックのイベントはどうやって捉えるの?
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"
シグナルも受け取ることになるんだ。(これはどのツールキットでも真実だと
思う。コンピュータは読心術が不得手だからね)」
ところで、シグナルとイベントはどう違うの?
まず初めに、Havoc Pennington が、自分のフリーの本の中で、
イベントとシグナルの違いについて、完全に近い説明をしています。
(
http://www106.pair.com/rhp/sample_chapters.html に
その本の章が二つあります)
そのうえ、Havoc はそれを gtk-list メーリングリスト
に投稿してくれました。「イベントというのは、X サーバーから受け取る
メッセージの流れ(ストリーム)のことだ。そのメッセージが Gtk の
メインループを駆動する。Gtk のメインループというのは、「イベントを
待って、そのイベントを処理する」ということと五十歩百歩だ(というのは
正確じゃないけどね。実際はそれよりも汎用的だし、一度にたくさんの
異なる入力ストリームを待てるんだ)。イベントは、Gdk/Xlib の
概念なんだ。」
「シグナルというのは、GtkObject とそのサブクラスに特徴的なことだ。
それらはどんな入力ストリームとも何の関係もない。実際シグナルというのは、
コールバックのリストを保持して、それらを起動する(シグナルを「発行する」)
方法に過ぎない。もちろん詳細はずいぶんあるし、その他の特徴もたくさんある。
シグナルはオブジェクトのインスタンスが発行するもので、Gtk のメインループ
にはまったく無関係だ。シグナルを発行するオブジェクトについて、何かが変化した
時にシグナルを発行するのが慣習なんだ。
「GtkWidget がイベントを受け取ると、たまたまシグナルを発行するから、
その二つが一緒になっているだけだ。これは全く便利なものだ。だから、
コールバックを接続して、特定のウィジェットが特定のイベントを受け取ると
それが起動するようにできる。シグナルとイベントを本質的に関連する概念と
するものは何もない。ボタンをクリックしたらシグナルを発行するということ
が、ボタンのクリックとシグナルを関連した概念にしているというだけで、
それ以上のものではないんだ。
delete イベント(あるいは他のイベント)のハンドラに渡したデータが壊れてしまう
イベントハンドラにはすべて付加的な引数があり、そのハンドラを起動する
トリガになるイベントに関する情報を、その引数に含めます。だから
delete_event ハンドラは次のように宣言しなければ
なりません。
gint delete_event_handler (GtkWidget *widget,
GdkEventAny *event,
gpointer data); |
イベント(これは何でも良いけど)に接続したシグナルがあるけど、捕捉できない。
なにがまずいの?
ある特定のイベントを捕捉するには、特別に初期化する事があります。
というより、特定のイベントを受け取る前に、ウィジェットのイベントマスク
ビットを正しく設定しなければならないのです。
例えば、
gtk_widget_add_events(window, GDK_KEY_RELEASE_MASK); |
上記は、キーリリースイベントを捕捉させようとするものです。
あらゆるイベントを捕捉したければ、GDK_ALL_EVENTS_MASK イベントマスクを
使えば良いんです。
イベントマスクはすべて、gdktypes.hファイル に
定義されています。
新しいシグナルを 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+ のマーシャラーをいじくり回す必要が
あります。
その領域内にぴったり収まるように切り詰めたテキストを表示することは可能?
(クリッピング以外の)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 と呼ぶので
しょうね)。
ウィンドウをモーダルにするのはどうやるの?/ウィンドウを一つだけ
アクティブにするのはどうやるの?
ウィンドウを作ったら、gtk_grab_add(my_window) と
してください。そして、ウィンドウをクローズしたら、
gtk_grab_remove(my_window) としてください。
ウィジェット(例えば 進捗表示バー)が更新されないのはなぜ?
たぶん gtk_main() に制御を戻さずに、
一つの関数内でなにもかも変更しようとしているんだと思います。
時間がかかる計算をしているコードの場合に、こうなることがあります。
描画内容の更新情報は、ほとんどがキューにあるだけで、
gtk_main() 内で、そのキューを処理します。
描画情報のキューは、以下のようなことをすれば、そのウィジェットを
変更する関数内で、強制的に処理することができます。
while (g_main_iteration(FALSE)); |
上記の簡単な例で何をやっているかといえば、待ち状態のイベント
と優先順位が高いアイドル関数をすべて走らせて、その後ですぐ
リターンしています(描画は優先順位の高いアイドル関数で
行ないます)。
データと 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 パッケージとの互換性を保っているだけなんです。
オブジェクトに関連づけたデータは、どうやって削除するの?
オブジェクトにデータを関連づける場合は、
gtk_object_set_data_full()関数が使えます。
この関数の最初の三つの引数は gtk_object_set_data() の
ものと同じです。四番目の引数は、データを破壊すると呼ばれるコールバック関数
へのポインターです。データは次のようにすると破壊されます。
ウィジェットの親はどうやって変えるの?
ウィジェットの親を変える(つまり所有者を変更する)方法は、きっと次の関数を
使うのが普通でしょう。
void gtk_widget_reparent (GtkWidget *widget,
GtkWidget *new_parent) |
でもこの方法は「きっとそうだろう」というだけです。というのは、
この関数はある特定のウィジェットでは正しく動作しないんです。
gtk_widget_reparent() の主な目標は、引数の widget と
new_parent の両方をリアライズする際、widget をアンリアライズ
しないようにすることです(この場合は、widget->window の
親がうまく変更されます)。ここでの問題は、GTK+ の階層にある
ウィジェットによっては、X のサブウィンドウが複数アタッチされて
いるものがあり、とりわけ GtkSpinButton ウィジェットではそう
なっている、ということです。このため、gtk_widget_reparent() は、
本来はリアライズすべき子ウィンドウをアンリアライズのままにして
失敗してしまいます。
この問題を避けるには、たんに次のわずかなコードを使えば良いんです。
gtk_widget_ref(widget);
gtk_container_remove(GTK_CONTAINER(old_parent), widget);
gtk_container_add(GTK_CONTAINER(new_parent), widget);
gtk_widget_unref(widget); |
ウィジェットの位置はどうやればわかるの?
Tim Janik が指摘したように、いろいろの状況があるし、それぞれの
場合で、別々の解決方法が必要です。
親ウィジェットからの相対位置が欲しい場合は、
widget->allocation.x と
widget->allocation.y を
使うべきです。
X のルートウィンドウ と、あるウィンドウとの相対位置が
欲しい場合は、
gdk_window_get_geometry() か、
gdk_window_get_position()、あるいは、
gdk_window_get_origin() を使うのが
いいでしょう。
(ウィンドウマネージャの装飾も含む)ウィンドウの位置を取得したい
場合は、gdk_window_get_root_origin()
を使うべきです。
最後に大事なことを一つ。ウィンドウマネージャのフレーム位置
が必要なら、gdk_window_get_deskrelative_origin()
を使って下さい。
ウィンドウマネージャに何を使っているかで、上記の関数の結果が
変わってきます。アプリケーションを書く場合は、このことを心に
留めておいて下さい。ウィンドウマネージャがウィンドウの周囲に
付ける装飾をどう管理するかにかかっているんです。
ウィジェットやウィンドウの大きさはどうやって設定すればいいの?ユーザーに
ウィンドウの大きさを変えさせたくないのだけど、どうすればいいの?
どのようなウィジェットについても、その位置を設定するには
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); |
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;
} |
ボタンのような、ウィジェットは、どうやれば無効にしたり有効にしたりできるの?
ウィジェットを無効(あるいは有効)にするには、
gtk_widget_set_sensitive()関数を使って下さい。
この関数の最初のパラメータは、そのウィジェットへのポインタです。
二番目のパラメータは論理値で、TRUE ならそのウィジェットは有効になります。
gtk_clist_* 関数のテキスト引数は const で宣言してはだめ?
例えば次のようなものです。
gint gtk_clist_prepend (GtkCList *clist,
gchar *text[]); |
回答:だめです。"gchar*"タイプ (char 型へのポインタ)は自動的に
"const gchar*" (const char 型へのポインタ)にキャストできますが、
一方、これは "gchar *[]"タイプ(char 型へのポインタの配列)から
"const gchar *[]"タイプ(const char 型へのポインタの配列)への
キャストには適用されないんです。
"const" という修飾子は自動キャストに従わなければならないかも
しれませんが、配列の場合、const で修飾したキャストが必要なのは、
配列自身ではなく、そのメンバーです。したがって、タイプ全体を
変更するんです。
どうやれば、ピクセル(イメージデータ)を画面に描画できるの?
この取り組み方にはいくつか方法があります。いちばん簡単なのは、
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() は、けっして使いたいと
思わないで下さい。極めて低速なんです。
ウィンドウをリアライズしたり表示したりせずに、ピクスマップを
作るのはどうやるの?
gdk_pixmap_create_from_xpm() のような
関数には、有効なウィンドウをパラメータにする必要があります。
アプリケーションの初期化段階では、ウィンドウを表示しないと
有効なウィンドウは使用できないかもしれませんが、それでは
困ってしまうかもしれません。これを避けるには、以下に示すように、
gdk_pixmap_colormap_create_from_xpm といった
ような関数が使えます。
char *pixfile = "foo.xpm";
GtkWidget *top, *box, *pixw;
GdkPixmap *pixmap, *pixmap_mask;
top = gtk_window_new (GKT_WINDOW_TOPLEVEL);
box = gtk_hbox_new (FALSE, 4);
gtk_conainer_add (GTK_CONTAINER(top), box);
pixmap = gdk_pixmap_colormap_create_from_xpm (
NULL, gtk_widget_get_colormap(top),
&pixmap_mask, NULL, pixfile);
pixw = gtk_pixmap_new (pixmap, pixmap_mask);
gdk_pixmap_unref (pixmap);
gdk_pixmap_unref (pixmap_mask); |
ドラッグ&ドロップはどうやるの?
GTK+ にはドラッグ&ドロップシステムを通じたプロセス間通信を行なうための、
ハイレベルの関数が揃っています。GTK+ は、低レベルの Xdnd と Motif の
ドラッグ&ドロッププロトコル上で、ドラッグ&ドロップを実行できるんです。
GTK+ のドラッグ&ドロップに関するドキュメントは不完全ですが、
Tutorial に
ある程度の情報があります。また GTK+ のソースディストリビューションの
一部にある、ドラッグ&ドロップのコード例も見る方が良いでしょう。
gtk/testdnd.c ファイルにあります。
GTK+/GLib にメモリリークがあるのはなぜ?
メモリリークはありません。GLib も C のライブラリも、両方とも
(malloc を実装していますが)、free() でそのメモリを解放しても、
割り当てられたそのメモリを、時おりキャッシュします。
ですから、free() をちゃんと使っているかどうかを知るために、top のような
ツールを普通使うことはできないんです(ごく大雑把な評価は別です。つまり、
実際ほんとうに使い方を間違えている場合は、top はそのことを示しますが、
些細な間違いと GLib/malloc のキャッシュを区別することはできないんです。
メモリリークを見つけるには、適切なメモリプロファイリングツールを
使って下さい。
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");
} |
リストをスクロールしても、GtkList のカラムヘッダーが消えないようにするには
どうすればいいの?
これは、gtk_scroll_window_add_with_viewport() 関数
を使って、GtkList を GtkScrolledWindow にパックすると発生します。
CList をスクロールドウィンドウに追加する良い方法は、以下のようにして
gtk_container_add関数を使うことです。
GtkWidget *scrolled, *clist;
char *titles[] = { "Title1" , "Title2" };
scrolled = gtk_scrolled_window_new(NULL, NULL);
clist = gtk_clist_new_with_titles(2, titles);
gtk_container_add(GTK_CONTAINER(scrolled), clist); |
自分のアプリケーションのユーザーには、GtkCombo にテキスト入力をしてもらいたくない
のだけれど、何か良い方法は?
GtkCombo にはそれに関連するエントリがあり、次の式を使ってアクセスできるように
なっています。
GTK_COMBO(combo_widget)->entry |
ユーザーがこのエントリの内容を変更できないようにしたい場合は、
以下のように、gtk_entry_set_editable()関数が使えます。
void gtk_entry_set_editable(GtkEntry *entry,
gboolean editable); |
エントリに対してタイプできないようにするには、この editable パラメータ
を FALSE に設定して下さい。
コンボボックスが変更されたことは、どうやって捕捉するの?
GtkCombo と結び付いているエントリは、以下の場合に "changed" シグナルを
送ります。
何かテキストがタイプされた。
コンボボックスのセレクションが変化した。
コンボボックスの変化を捉えるには、たんに以下のようにして
シグナルハンドラを接続すればいいんです。
gtk_signal_connect(GTK_COMBO(cb)->entry,
"changed",
GTK_SIGNAL_FUNC(my_cb_change_handler),
NULL); |
どうやればメニューの中にセパレータを定義できるの?
メニューの作り方に関する情報は、
Tutorial
を見て下さい。とはいっても、メニューの中にセパレータを作るには、
空のメニュー項目を挿入するだけでいいんです。
menuitem = gtk_menu_item_new();
gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
gtk_widget_show(menuitem); |
ヘルプみたいに、メニューを右寄せにするには、どうやればいいの?
MenuFactory を使っているかどうかによって違いますが、これを進めるには
方法が二つあります。MenuFactory の場合は、次のようなものを使います。
menu_path = gtk_menu_factory_find (factory, "<MyApp>/Help");
gtk_menu_item_right_justify(menu_path->widget); |
MenuFactory を使わない場合は、たんに次のものを使って下さい。
gtk_menu_item_right_justify(my_menu_item); |
どうやれば下線付きのアクセラレータをメニュー項目に追加できるの?
Glade プロジェクトの技術部隊にいる Damon Chaplin が次のサンプルコードを
くれました(このコードは Glade の出力です)。このコードでは、
子供が一つだけ付いた()小さな
メニュー項目を作っています。
の "F" と の "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); |
どうやれば GtkMenuItem からテキストを取り出せるの?
通常は以下の方法で、特定の GtkMenuItem のラベルが取り出せます。
if (GTK_BIN (menu_item)->child)
{
GtkWidget *child = GTK_BIN (menu_item)->child;
/* 子ウィジェットに関することをやる */
if (GTK_IS_LABEL (child))
{
gchar *text;
gtk_label_get (GTK_LABEL (child), &text);
g_print ("menu item text: %s\n", text);
}
} |
GtkOptionMenu からアクティブなメニュー項目を取得するには、
以下のようにすればいいです。
if (GTK_OPTION_MENU (option_menu)->menu_item)
{
GtkWidget *menu_item = GTK_OPTION_MENU (option_menu)->menu_item;
} |
でも、ここに罠があるんです。この特定のケースでは、上記のコードを
使って、 menu_item からラベルウィジェット
を取得することは できない んです。
というのは、オプションメニューは、その時点でアクティブな内容を
表示するため、一時的に menu_item の子ウィジェットを表している
からなんです。ですから、その時点でアクティブな オプションメニュー
の menu_item の子供を取り出すには、次のようにしなければなりません。
if (GTK_BIN (option_menu)->child)
{
GtkWidget *child = GTK_BIN (option_menu)->child;
/* 子供に関することをする */
} |
GtkLabel を左右いずれかに寄せるには、どうやればいいの?
ほんとうにラベルを 寄せたい んですか?
ラベルのクラスには gtk_label_set_justify()関数
があるので、これを使って複数行のラベルをどちらに寄せるかを制御します。
でもやりたいのはたぶんラベルの
アラインメント を設定することでしょ。
つまり右に整列させるか、中心か、それとも左に整列させるかでしょ。
やりたいのがこれなら、次のようにして下さい。
void gtk_misc_set_alignment (GtkMisc *misc,
gfloat xalign,
gfloat yalign); |
ここで、xalign と
yalign の値は [0.00;1.00] の浮動小数点数
です。
GtkWidget *label;
/* 水平方向:左整列、垂直方向:上端 */
gtk_misc_set_alignment(GTK_MISC(label), 0.0f, 0.0f);
/* 水平方向:中心に整列、垂直方向:中心 */
gtk_misc_set_alignment(GTK_MISC(label), 0.5f, 0.5f);
/* 水平方向:右整列、垂直方向:下端 */
gtk_misc_set_alignment(GTK_MISC(label), 1.0f, 1.0f); |
GtkLabel ウィジェットの背景色はどうやって設定するの?
GtkLabel ウィジェットは、自分自身を描画する際に、それ自身の
ウィンドウを作らないという、数少ない GTK+ ウィジェットの中
の一つです。その代わりに、自分の親ウィジェットに直接描画
します。
つまりこれは、GtkLabel ウィジェットの背景色を設定するには、
その親ウィジェットの背景色を変更する必要があるということになります。
そのラベルをパックする先のオブジェクトのことです。
リソースファイルを使って GtkLabel の色とフォントをセットするにはどうやるの?
ラベル用に作られているウィジェット名のパスにはオブジェクト階層の
ウィジェット名も入っています。例えば次のようなものです。
window (name: humphrey)
hbox
label (name: mylabel)
パターンがマッチする必要があるウィジェットパスは、次のようになるでしょう。
humphrey.GtkHBox.mylabel
リソースファイルは次のようなものになるかも知れません。
style "title"
{
fg[NORMAL] = {1.0, 0.0, 0.0}
font = "-adobe-helvetica-bold-r-normal--*-140-*-*-*-*-*-*"
}
widget "*mylabel" style "title" |
プログラムでは、ラベルウィジェットに名前を与える必要もある
でしょう。次のようにすればできます。
label = gtk_label_new("Some Label Text");
gtk_widget_set_name(label, "mylabel");
gtk_widget_show(label); |
どうやれば、リソースファイルでツールチップが構成できるの?
ツールチップのウィンドウは、"gtk-tooltips" という名前になっています。
ツールチップ自身の GtkTooltips は(GtkObjectではあるけれども)
GtkWidget ではありません。それにそのままでは、どんなウィジェット
スタイルとも合致しようともしません。
ですから、リソースファイルは次のようなものになるでしょう。
style "postie"
{
bg[NORMAL] = {1.0, 1.0, 0.0}
}
widget "gtk-tooltips*" style "postie" |
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); |
リターンキーを押した時に GtkEntry ウィジェットをアクティブにするのは、どうやればいいの?
Entry ウィジェットの中でリターンキーを押すと、このウィジェットは
'activate' シグナルを発行します。この activate シグナルにハンドラを
つないで、何でも好きなことをして下さい。典型的なコードは
次のようになるでしょう。
entry = gtk_entry_new();
gtk_signal_connect (GTK_OBJECT(entry), "activate",
GTK_SIGNAL_FUNC(entry_callback),
NULL); |
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);
} |
GtkText ウィジェットで水平スクロールバーはどうやって使えばいいの?
簡単に言えば、使えません。GtkText の現バージョンでは水平方向の
スクロールはサポートしていないんです。GtkText ウィジェットを
完全に書き換える意向はあります。そのときには、この制限は
取り除かれるでしょう。
GtkText ウィジェットのフォントはどうやって変えればいいの?
フォントを変える方法は二つあります。GTK+ ではアプリケーションの
見かけを、リソースを使って実行時に変更できるようになっているので、
適切なファイルで以下のようなものを使って変更できます。
style "text"
{
font = "-adobe-helvetica-medium-r-normal--*-100-*-*-*-*-*-*"
} |
もう一つの方法は、プログラム内でフォントをロードして、それから
テキストウィジェットにテキストを追加する関数でそのフォントを
使うことです。例えば以下のようなコードを使って、フォントを
ロードできます。
GdkFont *font;
font = gdk_font_load("-adobe-helvetica-medium-r-normal--*-140-*-*-*-*-*-*"); |
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()関数を
使って下さい。
GLib って何?
GLib というのは、GDK や GTK を使ったアプリケーションを作る際に
利用できる、有用な関数や定義を集めたライブラリのことです。
このライブラリでは、標準的な libc 関数のいくつかを置き換える
関数を提供しています。例えば malloc のようなものです。そういった
関数は、システムによっては動作不良を起こしやすいんです。
また次のようなものを処理するルーチンも持っています。
- 二重リンクリスト
- リンクリスト
- タイマー
- 文字列処理
- 構文解析
- エラー関数
二重リンクリストはどういうふうに使えばいいの?
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 関数が無いことだけは覚えておいて下さい。
そのリストの最初のノードは参照し続ける必要があるんです。
アロケートしたリストのノードを 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); |
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 のウィンドウ
内部にメッセージを表示するウィンドウを出せるようにさえできるんだ。」
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" は通らなかったと思います。
この 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.
この文書の他言語への翻訳版は、修正版に関する上記条件の下で、複製と
配布を許可する。
この文書を出版物に組み入れたいと考えている場合は、是非本文書の保守担当
の誰かに連絡をとって下さい。そうすれば、極力最新情報を利用できるよう
努力します。
本文書がその所期の目的に応えるという保証は何もありません。
これはたんに無償のリソースとして提供しているだけです。
そういうものですから、本文書で提供している情報の作者と保守担当は、
その情報が本当に正確だという、いかなる保証もできません。