この文書の日本語訳は、芳賀靖史(yasufumi.haga@nifty.com)が、
2003年9月5日に行ないました。誤訳、その他間違い等がありましたら
ご連絡下さい。
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/。
GTK チュートリアルをパッケージにしたものは、
SGML や HTML、ポストスクリプト、DVI、それにテキスト版が、
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+ の開発者たちは、この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
どうやれば 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+ で、Glade を使って GUI を構築するにはどうすればいいの? [GTK 2.x]
Glade を使うには2種類の方法があります。
最初の方法は、Glade の機能を使ってコードを生成する方法で、
2番目の方法は、libglade ライブラリを使う方法です。
後者だと、Glade で生成した XML ユーザーインタフェース記述ファイルを、
実行中のプログラムに直接ロードします。
経験豊富な GTK+ のプログラマなら、普通は、
絶対 libglade を使った方がいいと言います。
ソースを生成している Glade と、それを編集しているユーザーの間のやりとりについては、
気にする必要はありません。それに、大規模プロジェクトでもうまくいく方法だと証明されています。
ですから、そこには、利用できるコードサンプルがたくさんあります。
libglade を使うための入門書は、libglade API のドキュメントにあります。
(http://developer.gnome.org/doc/API/2.0/libglade/libglade-notes.html#libglade-basics)
.
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 |
その後、次のコマンドをタイプするだけで、これらが使えます。
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 端末は二束三文だけど、
ちょっと得難いものなんだ(ここオランダではね。場所によって違うだろう
けど)。」
もう一つのやり方は、Xnest 上で、自分のアプリケーションを動かすことです。
Xnest というのは、ある X サーバーのルートウィンドウを、
他の X サーバーの通常のウィンドウ内に表示するものです。
Xnest のディスプレイでポインタをグラブしても、通常の X サーバーで動かしている
デバッガの GUI には、何の影響もありません。
Xnest :1
twm -display :1
myapp --display=:1 |
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+ でマルチスレッドのアプリケーション
はどうやって書くの? [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;
} |
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);
} |
スレッドで、メインループを使って関数を実行する、簡単な方法は? [GTK 2.x]
スレッド化したプログラムをセットアップするには、
すべての GTK+ 呼び出しを、
シングルスレッドで行なうのが一番簡単なことが時々あります。
そういう場合でも g_threads_init() は呼び出す必要がありますが、
gdk_threads_init(), gkd_threads_enter(),および gdk_threads_leave() は、
呼び出す必要がありません。
この方法で自分のプログラムをセットアップすると、
スレッドを取得して、GTK+ を呼び出し、
メインループを実行して、別のスレッドへ応答するために何かをするには
どうすればいいのでしょうか。
一番簡単なのは、GLib のメインループ関数がスレッドセーフであり、
g_idle_add() を使ってアイドル関数を追加すれば、どのスレッドからでも
呼び出せる、という事実を利用することです。この関数は、次の機会に、
メインスレッドから呼ばれます。イベント処理や描画よりも、
自分の関数を優先させたい場合は、この関数の代わりに g_idle_add_full()
を使い、G_PRIORITY_HIGH という優先順位を渡してください。
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()関数を呼ぶこと
です。この関数は、トップレベルウィンドウの GtkWidget へのポインタを返します。
もっと込み入った(でも既知のタイプの、もっとも近い親がわかるので、
もっと用途が広い)方法は、以下のように、
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 に
その本の2つの章が載っています)。
そのうえ、Havoc は、それを gtk-list メーリングリスト
に投稿してくれました。「イベントというのは、X サーバーから受け取る
メッセージの流れ(ストリーム)のことだ。そのメッセージが Gtk の
メインループを駆動する。Gtk のメインループというのは、「イベントを
待って、そのイベントを処理する」ということと五十歩百歩だ(というのは
正確じゃないけどね。実際はそれよりも汎用的だし、一度にたくさんの
異なる入力ストリームを待てるんだ)。イベントは、Gdk/Xlib の
概念なんだ。」
「シグナルというのは、GtkObject とそのサブクラスに特徴的なことだ。
それらは、どんな入力ストリームとも何の関係もない。実際、シグナルというのは、
コールバックのリストを保持して、それらを起動する(シグナルを「発行する」)
方法に過ぎない。もちろん詳細はずいぶんあるし、その他の特徴もたくさんある。
シグナルは、オブジェクトのインスタンスが発行するもので、Gtk のメインループ
にはまったく無関係だ。シグナルを発行するオブジェクトについて、「何かが変化した
時に」シグナルを発行するのが慣習なんだ。
「GtkWidget がイベントを受け取ると、たまたまシグナルを発行するから、
その二つが一緒になっているだけだけど、これは全く便利なもので、
コールバックを接続して、特定のウィジェットが特定のイベントを受け取ると、
そのコールバックが起動するようにできる。でも、シグナルとイベントを、本質的に関連する概念と
するものは何もない。ボタンをクリックしたらシグナルを発行するから、
ボタンのクリックとシグナルが関連した概念になっているだけで、
それ以上のものではないんだ。
delete_event(あるいは他のイベント)のハンドラに渡したデータが壊れてしまう
すべてのイベントハンドラでは、そのハンドラを起動する
イベントに関する情報を、別の引数に持っています。
ですから、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+ のマーシャラーをいじくり回す必要が
あります。
その領域内にぴったり収まるように切り詰めたテキストを表示することは可能?
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 と呼ぶのでしょうね)。
ウィンドウをモーダルにするのはどうやるの?/ウィンドウを一つだけ
アクティブにするのはどうやるの?
ウィンドウを作ったら、gtk_grab_add(my_window) を
実行してください。そして、ウィンドウをクローズしたら、
gtk_grab_remove(my_window) を実行してください。
ウィジェット(例えば 進捗表示バー)が更新されないのはなぜ?
たぶん gtk_main() に制御を戻さずに、
一つの関数内でなにもかも変更しようとしているんだと思います。
時間がかかる計算をしているコードだと、こうなることがあります。
描画内容の更新情報は、ほとんどがキューにあるだけで、
gtk_main() 内で、そのキューを処理します。
描画情報のキューは、以下のようなことをすれば、
while (g_main_iteration(FALSE)); |
強制的に、そのウィジェットを変更する関数内で処理できます。
上記の簡単な例で何をやっているかといえば、待ち状態のイベント
と、優先順位が高いアイドル関数をすべて走らせて、その後で、すぐ
リターンしています(描画は優先順位の高いアイドル関数で
行ないます)。
データと 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 パッケージとの互換性を保っているだけです。
オブジェクトに関連づけたデータは、どうやって削除するの?
オブジェクトにデータを関連づける場合は、
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 2.x]
ウィジェットの大きさを、特定の大きさに設定するには、
gtk_widget_set_size_request()
関数を使います。
gtk_window_set_resizable()
関数は、ユーザーが、ウィンドウの大きさを変更できるかどうかを設定します。
デフォルトでは、変更可能です。これらの関数の定義は次のとおりです。
void gtk_widget_set_size_request (GtkWidget *widget,
gint width,
gint height);
void gtk_window_set_resizable (GtkWindow *window,
gboolean resizable);
|
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 のセレクション機能は、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");
} |
リストをスクロールしても、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 ウィジェットの背景色はどうやって設定するの?
専用のウィンドウを作らずに
自分自身を描画する GTK+ ウィジェットはいくつかありますが、
GtkLabel ウィジェットは、そういうウィジェットの一つです。
そのようなウィジェットでは、代わりに、自分の親ウィジェットに直接描画します。
つまりこれは、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 を使ったアプリケーションを作る際に
利用できる、便利な関数や定義を集めたライブラリのことです。
このライブラリには、malloc のような、システムによっては動作不良を起こしやすい、
いくつかの標準的な libc 関数を置き換える関数が用意されています。
また次のようなものを処理するルーチンもあります。
- 二重リンクリスト
- リンクリスト
- タイマー
- 文字列処理
- 構文解析
- エラー関数
二重リンクリストはどういうふうに使えばいいの?
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 関数はありません。
そのリストの、最初のノードを参照し続ける必要があります。
アロケートしたリストのノードを 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 をカバーしているようなものだよ。;)」
「GIMP のように、完全に gtk に依存しているアプリケーションの内部で
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>
/* スキャナに与えるテスト用テキスト */
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" は通らなかったと思います。
この 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.
この文書の他言語への翻訳版は、修正版に関する上記条件の下で、複製と
配布を許可する。
この文書を出版物に組み入れたいと考えている場合は、是非本文書の保守担当
の誰かに連絡をとって下さい。そうすれば、極力最新情報を利用できるよう
努力します。
本文書がその所期の目的に応えるという保証は何もありません。
これはたんに無償のリソースとして提供しているだけです。
そういうものですから、本文書で提供している情報の作者と保守担当は、
その情報が本当に正確だという、いかなる保証もできません。