GTK+ FAQ

Tony Gale

Shawn Amundson

Emmanuel Deloget

April 30th 2003

中文版声明:

Translator:marxu

E-mail:[email protected]

version:1.1

为保证本文档自由传播,保留版权,中文版版权归译者所有。

本许可授权你制作和发布本手册逐字的拷贝,但在所有拷贝上要保留本版权声明和许可声明。

本许可授权你复制和发布本文档的修订版,在逐字的拷贝原文,包含和原版完全一致的版权声明,并且所有修订部分也是以本许可声明发布的条件下。

如果你准备出版本文档,请联系维护者,以确保你获得本文档的最新版本。

对本文档的适用范围不作担保, 它仅仅是作为一个免费的资源提供。因此,这里提供的这些信息的作者和维护者无法做出这些信息一定正确的保证。


目录表
1.总说明
1.1. 注意:本文档主要是关于GTK+1.2
1.2. 致谢
1.3. 作者
1.4. 什么是GTK+
1.5. GTK+中的+是什么意思?
1.6. GTK+, GDK和GLib中的G代表什么?
1.7. 那里可以找到GTK+的文档
1.8. 有没有关于GTK+的邮件列表(或者是邮件列表的压缩包)?
1.9. GTK+如何获得帮助?
1.10. GTK+中如何报告bug?
1.11. GTK+有没有windows版本?
1.12. 哪些程序是用GTK+编成的?
1.13. 我想用GTK+编写一个程序,IRC(在线聊天系统)的客户端如何?
2. GTK+如何得到 配置 安装和查错
2.1. 运行GTK+需要安装什么
2.2. 那里可以得到GTK+
2.3. 如何配置/编译GTK+
2.4. 编译的时候出现错误 如:make: file `Makefile' line 456: Syntax erro
2.5. 我编译和安装了GTK+,但是不能将程序和它连接[GTK 2.x]
2.6. 当我安装GTK+的程序时,configure报告不能找到GTK.[GTK 2.x]
3. GTK+的开发和维护
3.1. 每个人都在谈论的CVS是什么东西,怎样才能访问它?
3.2. 如何向GTK+投稿?
3.3. 如何知道我写的补丁是否被采用,如果没有,原因是什么?
3.4. 向库中添加新的控件的方针是什么?
3.5. 除了C之外,有人在绑定其他的语言吗?
4. 用GTK+开发:开始
4.1. 如何开始?
4.2. 如何用GTK+编写安全的/SUID/SGID敏感的程序?GTK+安全吗?听说的GTK_MODULES安 全漏洞是什么?
4.3. 我尝试编译一个小型的Hello World程序,但是失败了,有什么线索? [GTK 2.x]
4.4. 怎样使用make程序? [GTK 2.x]?
4.5. 我在make文件中使用了后置引用(backquote),但是我的make失败了.
4.6. 我想添加一些配置资料(configure stuff),应如何做?
4.7. 我尝试使用gdb来调试程序,但是当我执行到一些断点时,它挂起了我的X服务器(X server),怎么办?
5. 用GTK+开发:常规问题
5.1. GTK中有那些控件(widget)?
5.2. GTK+的线程安全吗?如何编写多线程GTK+程序?
5.3. 当在GTK+的app文件中使用fork()时,为什么会出现奇怪的 'x io error'错误?
5.4. 为什么当按钮按下时文本(contents)不移动?这里有一个补丁可以使它这样.
5.5. 如何识别一个控件的最高层窗口(top level window)或者其他的祖先?
5.6. 如何得到一个GtkWindow窗口的ID(Window ID)?
5.7. 如何捕获一个双击(double click)事件(event)(例如在一个列表控件中)?
5.8. 顺便问一下,信号(signals)和事件(events)的区别是什么?
5.9. 传递给delete_event(或者其他事件)处理函数的数据被损坏.
5.10. 我将信号和所有事件都连接起来,但是看起来我并没有捕获它,什么原因?
5.11. 我要向GTK+控件中添加一个新的信号,怎么办?
5.12. 文本能否换行显示?
5.13. 如何设置窗口模式(modal)?/如何激活一个单一的窗口?
5.14. 为什么我的控件(例如 进度条)没有更新?
5.15. 如何向GTK+的对象/控件附加数据
5.16. 如何去掉附加在控件上的数据?
5.17. 如何重定控件的父窗口?
5.18. 如何得到控件的位置?
5.19. 如何设置控件/窗口的大小?如何才能禁止用户重新设置窗口的大小?
5.20. 如何向我的GTK+程序中添加一个弹出式菜单?
5.21. 如何禁止或使能一个控件,例如一个按钮?
5.22. 为什么gtk_clist*函数中的文本参数( text argument)不应被声明为const?
5.23. 如何在屏幕上显示象素(图像数据)?
5.24. 如何在不创建窗口或不显示窗口的情况下创建一个象素映射( pixmap)?
5.25. 如何拖放?
5.26. 为什么GTK+/GLib会泄漏内存?
6. GTK+开发: 控件专题
6.1. 在GtkList控件中如何找出选择项( selection)?
6.2. 在GtkCList控件中,当表内容滚动时,怎样避免标题消失?
6.3. 我不想让我的程序的用户在组合框(GtkCombo)中输入文本,怎么办?
6.4. 如何捕捉组合框( combo box)的改变?
6.5. 如何在菜单中定义分隔线( separation line)?
6.6. 如何向右对齐菜单,比如Help?
6.7. 如何在菜单项中添加带下滑线的加速键?
6.8. 如何从GtkMenuItem中重新找回文本?
6.9. 如何向右(或其他方向)对齐GtkLabel?
6.10. 如何给 GtkLabel控件设置背景颜色?
6.11. 如何使用 Resource 文件来给 GtkLabel设置颜色和字体?
6.12. 如何在 Resource 文件中配置工具提示( Tooltips)?
6.13. 无法在GtkEntry中输入大于2000(大约)个字符,什么原因?
6.14. 如何能在按下回车时使 GtkEntry控件激活?
6.15. 如何确认/限制/筛选GtkEntry中的输入?
6.16. 如何在GtkText控件中使用水平滚动条( horizontal scrollbars)?
6.17. 如何改变GtkText控件的字体?
6.18. 如何在GtkText控件中设定光标位置( cursor position)?
7. 关于GDK
7.1. 什么是GDK?
7.2. 如何使用颜色分配?
8. 关于GLib
8.1. 什么是GLib?
8.2. 如何使用双向链表(doubly linked lists)?
8.3. 当我释放分配的链表节点时,内存似乎没有被释放.
8.4. 为什么使用g_print, g_malloc, g_strdup和其他的glib函数?
8.5. 什么是扫描器(GScanner),如何使用?
9. GTK+ FAQ 投稿,维护者和版权(英文版)

第一章 总说明

目录表


1.7. 那里可以找到GTK+的文档?

那里可以找到GTK+的文档?

GTK+发布版本的doc/文件夹下面,你可以找到 GTK 和 GDK的参考资料,本FAQ和GTK指南.

另外,你可以通过下面的网址找到这个文件HTML版本http://www.gtk.org/,打包的gtk指南,SGML, HTML, Postscript, DVI或text 的版本可以在下面找到ftp://ftp.gtk.org/pub/gtk/tutorial

这里还有一些有关GTK+, GDK and GNOME编程的书刊:


1.10. GTK+中如何报告bug?

GTK+中如何报告bug?

bug应该报告给GNOME的bug跟踪系统,(http://bugzilla.gnome.org/)在你报告一个新的bug之前,你应该输入你的电子邮件地址,并且得到一个密码才能够进入该系统.

当你提交一个bug的时候有一些选项和文本需要你选择和填写.你给出越多的信息,就越容易把这个bug跟踪找到.你应该提交的有用的信息包括:

如何重现这个bug

如果你能够通过包含在gtk/子文件夹的测试程序来重现这个bug,那将是最方便的.否则请包含一个简短的测试程序能展现出它的行为.实在不行的话,你可以指出一个可以下载的软件中的测试点.

(如果在GIMP能够重现的bug在gtk的测试程序中是最容易发现的.如果你使用GIMP的时候发现了一个bug,请加上你使用的GIMP的版本号.)

  • 如果这个bug是一个会导致崩溃(crash),应该准确的记录该崩溃发生时的输出.

  • 更进一步的信息,比如堆栈跟踪(stack traces)可能会有用,但是并不是必须的.如果你送出一个堆栈跟踪(stack traces),并且这个错误是X错误,如果能在运行测试命令加上--sync选项产生,它将会很有用.


1.11. GTK+有没有windows版本?

GTK+有没有windows版本?

现在有一项正在进行的将GTK+移植到windows平台的项目,取得了很大的进展.参阅

  http://www.iki.fi/tml/gimp/win32.


1.12. 哪些程序是用GTK+编成的?

哪些程序是用GTK+编成的?

你可以在GTK+的网站上找到一个基于GTK+的程序的列表,大约包含了350个程序. http://www.gtk.org/apps/ .

如果上面不行,那就到GNOME项目上领取一个工程,编写一个小游戏.写些有用的东西.http://www.gnome.org/ .

其中包括


1.13. 我想用GTK+编写一个程序,IRC(在线聊天系统)的客户端如何?

我想用GTK+编写一个程序,IRC(在线聊天系统)的客户端如何?

gtk-list找一些建议.现在至少有三个IRC的客户端软件正在开发.(更多信息参阅 http://www.forcix.cx/irc-clients.html ).

  • X-Chat.
  • girc. (Included with GNOME)
  • gsirc. (In the gnome CVS tree)

第二章 GTK+如何得到 配置 安装和查错

2.1. 运行GTK+需要安装什么

运行GTK+需要安装什么

编译GTK+,你只需要一个c编译器(gcc),一个X window系统和相关的库文件.


2.2. 那里可以得到GTK+

那里可以得到GTK+

官方网站 ftp://ftp.gtk.org/pub/gtk.

一般新的GTK+版本发布的时候,这个网站都很拥挤,所以你可以试试下面列表中的镜像网站. ftp://ftp.gtk.org/etc/mirrors

下面有一些镜像网站:


2.4. 编译的时候出现错误:make: file `Makefile' line 456: Syntax error[GTK 2.x]

编译的时候出现错误:make: file `Makefile' line 456: Syntax error[GTK 2.x]

确定你是否使用的是GNU make(用make -v 检查),现在有很多形形色色的make的版本,但是不是所有的都能正确的处理自动产生的make文件( Makefiles).


第三章 GTK+的开发和维护

3.1. 每个人都在谈论的CVS是什么东西,怎样采能访问它?

每个人都在谈论的CVS是什么东西,怎样采能访问它?

CVS叫协作版本系统(Concurrent Version System),是一种非常流行的软件版本控制手段,它设计用来允许不同的开发者能够同时在相同的源树(source tree)上操作,源树(source tree)是集中维护的,但是每个开发者都有一个可以修改的本地镜像.

GTK+开发者使用CVS库来储存目前正在开发的版本号的拷贝.同样的,如果要捐献GTK+的补丁,你应该根据CVS版本号来生成.一般的用户应该使用打包的发布版本.

RedHat的站点上你能找到RPM格式的CVS工具包,下面的站点可以找到最新的版本.

  http://download.cyclic.com/pub/

任何人都可以通过匿名登陆下载最新版本的GTK+的CVS版本号,请使用下面的步骤:

  • 在类bourne的shell(例如 bash)中,输入:

    CVSROOT=':pserver:[email protected]:/cvs/gnome'
    export CVSROOT

  • 然后,如果是第一次下载,需要登陆cvs:

    cvs login

  • 系统将要求你输入一个密码( password), cvs.gimp.org是没有密码的,所以只需输入回车即可.

  • 下载树(tree)并将它放在你目前的工作目录中,输入下面的命令: 

    cvs -z3 get gtk+

  • 注意:在GTK+1.1的树(tree)中,glib被放在另外一个单独的CVS组件中,所以如果没有,你需要下面的方法来得到它: 

    cvs -z3 get glib


3.5. 除了C外,有人在绑定其他的语言吗?

除了C外,有人在绑定其他的语言吗?

GTK+的主页上列出了GTK+的语言绑定(http://www.gtk.org/) :


第四章. 用GTK+开发:开始

4.1. 如何开始?

如何开始?

你安装了GTK+之后,有一些东西可以让你轻松的使用GTK+来开发.在这里有一份GTK+指南:http://www.gtk.org/tutorial/,贯穿整个开发过程,它将会向你介绍如何用C语言来编写程序.

目前,这份指南没有包括GTK+中的所有控件(widget)的信息,所以,使用所有的GTK+控件的基本的功能的实例代码,你应该看GTK+发布中的gtk/testgtk.c文件(以及相关的源文件).看看这些例子可以给你一个良好的基础,了解这些控件都能干什么.


4.6. 我想添加一些配置资料(configure stuff),应如何做?

我想添加一些配置资料(configure stuff),应如何做?

使用autoconf/automake,你首先应安装相关的包,分别是:

  • m4 preprocessor v1.4 或更高
  • autoconf v2.13 或更高
  • automake v1.4 或更高

你可以在GUN的ftp主站 (ftp://ftp.gnu.org/)或任何一个镜像上找到这些包,

为了使用这个强大的autoconf/automake工具,你必须先配置,如下:

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,主Makefile.am如下:

SUBDIRS         = mydir1 mydir2 mydir3

然后,来使用他们,输入:

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

了解更多的信息,你应该看autoconf和 automake的文档(附带的文档很容易理解,还有很多有关autoconf和automake的网上资源)


第五章. 用GTK+开发: 常规问题

目录表
5.1. GTK中有那些控件(widget)?
5.2. GTK+的线程安全吗?如何编写多线程GTK+程序?
5.3. 当在GTK+的app文件中使用fork()时,为什么会出现奇怪的 'x io error'错误?
5.4. 为什么当按钮按下时文本(contents)不移动?这里有一个补丁可以使它这样.
5.5. 如何识别一个控件的最高层窗口(top level window)或者其他的祖先?
5.6. 如何得到一个GtkWindow窗口的ID(Window ID)?
5.7. 如何捕获一个双击(double click)事件(event)(例如在一个列表控件中)?
5.8. 顺便问一下,信号(signals)和事件(events)的区别是什么?
5.9. 传递给delete_event(或者其他事件)处理函数的数据被损坏.
5.10. 我将信号和所有事件都连接起来,但是看起来我并没有捕获它,什么原因?
5.11. 我要向GTK+控件中添加一个新的信号,怎么办?
5.12. 文本能否换行显示?
5.13. 如何设置窗口模式(modal)?/如何激活一个单一的窗口?
5.14. 为什么我的控件(例如 进度条)没有更新?
5.15. 如何向GTK+的对象/控件附加数据
5.16. 如何去掉附加在控件上的数据?
5.17. 如何重定控件的父窗口?
5.18. 如何得到控件的位置?
5.19. 如何设置控件/窗口的大小?如何才能禁止用户重新设置窗口的大小?
5.20. 如何向我的GTK+程序中添加一个弹出式菜单?
5.21. 如何禁止或使能一个控件,例如一个按钮?
5.22. 为什么gtk_clist*函数中的文本参数(text argument)不应被声明为const?
5.23. 如何在屏幕上显示象素(图像数据)?
5.24. 如何在不创建窗口或不显示窗口的情况下创建一个象素映射(pixmap)?
5.25. 如何拖放?
5.26. 为什么GTK+/GLib会泄漏内存?

5.2. GTK+的线程安全吗?如何编写多线程GTK+程序?

GTK+的线程安全吗?如何编写多线程GTK+程序?

在执行其他的GLib 调用之前调用g_thread_init()可以使GLib库工作在线程安全模式(thread-safe mode)之下.在这种模式中,GLib将会根据需要自动的锁定所有内部数据结构(internal data structures),这并不是说两个线程可以同时访问.例如:一个单一的hash表(hash table),他们可以同时访问两个不同的hash表,如果两个不同的线程需要访问相同的hash表,程序将会锁定自身.

GLib被初始化为安全线程模式(thread-safe),GTK+能够"线程识别"(thread aware),在执行任何的GDK调用之前,你必须通过调用gdk_threads_enter()获得一个单一全局锁定(single global lock),而后又调用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+中的回调信号(信号(signals))在GTK+锁定(lock)的内部,但是GLib中的回调信号(超时,IO和系统空闲(timeouts, IO callbacks, and idle functions))在GTK+锁定(lock)之外,所以,在一个信号处理函数(signal handler)中你不需要调用gdk_threads_enter(),但是在其他类型的调用中,你需要调用gdk_threads_enter().

Erik Mouw 提供了下面的代码实例说明了如何在GTK+中使用线程:

/*-------------------------------------------------------------------------
 * Filename:      gtk-thread.c
 * Version:       0.99.1
 * Copyright:     Copyright (C) 1999, Erik Mouw
 * Author:        Erik Mouw <[email protected]>
 * Description:   GTK threads example. 
 * Created at:    Sun Oct 17 21:27:09 1999
 * Modified by:   Erik Mouw <[email protected]>
 * Modified at:   Sun Oct 24 17:21:41 1999
 *-----------------------------------------------------------------------*/
/*
 * Compile with:
 *
 * cc -o gtk-thread gtk-thread.c `pkg-config gtk+-2.0 --cflags --libs gthread`
 *
 * Thanks to Sebastian Wilhelmi and Owen Taylor for pointing out some
 * bugs.
 *
 */

#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 a while */
      sleep(rand() / (RAND_MAX / 3) + 1);

      /* lock the yes_or_no_variable */
      G_LOCK(yes_or_no);

      /* do we have to say something? */
      say_something = (yes_or_no != data->what);

      if(say_something)
	{
	  /* set the variable */
	  yes_or_no = data->what;
	}

      /* Unlock the yes_or_no variable */
      G_UNLOCK(yes_or_no);

      if(say_something)
	{
	  /* get GTK thread lock */
	  gdk_threads_enter();

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

	  /* release GTK thread lock */
	  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;

  /* init threads */
  g_thread_init(NULL);

  /* init gtk */
  gtk_init(&argc, &argv);

  /* init random number generator */
  srand((unsigned int)time(NULL));

  /* create a window */
  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);

  /* create a label */
  label = gtk_label_new("And now for something completely different ...");
  gtk_container_add(GTK_CONTAINER(window), label);
  
  /* show everything */
  gtk_widget_show(label);
  gtk_widget_show (window);

  /* create the threads */
  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);

  /* enter the GTK main loop */
  gdk_threads_enter();
  gtk_main();
  gdk_threads_leave();

  return(0);
}

5.3. 当在GTK+的app文件中使用fork()时,为什么会出现奇怪的'x io error'错误?

当在GTK+的app文件中使用fork()时,为什么会出现奇怪的'x io error'错误?

这实际上并不是GTK+的问题,而且也和fork()没有关系,如果发生了'x io error',你可以使用exit()函数来从子程序中退出.

当GDK打开了一个X显示,它创建了一个插座文件描述符(socket file descriptor),当你使用exit() 函数时,你默认的关闭了所有的打开文件描述符(open file descriptors).但是底层(underlying)的X库(X library)并不如此.

这里应该使用的函数是exit().

Erik Mouw贡献了这个代码实例来说明如何处理 fork()和exit().

/*-------------------------------------------------------------------------
 * Filename:      gtk-fork.c
 * Version:       0.99.1
 * Copyright:     Copyright (C) 1999, Erik Mouw
 * Author:        Erik Mouw <[email protected]>
 * Description:   GTK+ fork example
 * Created at:    Thu Sep 23 21:37:55 1999
 * Modified by:   Erik Mouw <[email protected]>
 * Modified at:   Thu Sep 23 22:39:39 1999
 *-----------------------------------------------------------------------*/
/*
 * Compile with:
 *
 * 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;

  /* block other incoming SIGCHLD signals */
  sigemptyset(&set);
  sigaddset(&set, SIGCHLD);
  sigprocmask(SIG_BLOCK, &set, &oldset);

  /* wait for child */
  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);
	}
    }

  /* re-install the signal handler (some systems need this) */
  signal(SIGCHLD, sigchld_handler);
  
  /* and unblock it */
  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)
    {
      /* ouch, fork() failed */
      perror("fork");
      exit(-1);
    }
  else if(pid == 0)
    {
      /* child */
      fprintf(stderr, "Child: pid = %d\n", (int)getpid());

      execlp("ls", "ls", "-CF", "/", NULL);
      
      /* if exec() returns, there is something wrong */
      perror("execlp");

      /* exit child. note the use of _exit() instead of exit() */
      _exit(-1);
    }
  else
    {
      /* parent */
      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);

  /* the basic stuff: make a window and set callbacks for destroy and
   * delete events 
   */
  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

  /* add a button to do something usefull */
  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);
          
  /* show everything */
  gtk_widget_show (button);
  gtk_widget_show (window);


  /* install a signal handler for SIGCHLD signals */
  signal(SIGCHLD, sigchld_handler);

  
  /* main loop */
  gtk_main ();

  exit(0);         
}

5.8. 顺便问一下,信号(signals)和事件(events)的区别是什么?

顺便问一下,信号(signals)和事件(events)的区别是什么?

首先,Havoc Pennington在他的免费书(这里http://www106.pair.com/rhp/sample_chapters.html)中对事件和信号的区别给出了一个完整的描述.

此外,Havoc在邮件列表上写道:事件Events是从X server收到的消息流.他们驱动着Gtk的主循环,"等待事件,然后处理它".(这还不太准确,它实际更加的复杂,它可以同时等待多个不同的输入流).事件(Event)是一个Gdk/Xlib概念.

"信号是GtkObject和它的子集(subclasses)的特性,他们和任何的输入流都没有关系.实际上,信号只是保持了一个回调函数列表,然后调用回调函数(就是发出信号),当然,还有很多其他的细节和特性.信号是对象(object)发出的,和Gtk的主循环一点关系都没有.按照常规,信号一般在对象"发生了一些改变"的时候发出.

"信号(Signals)和事件(events)有关是因为GTK控件(GtkWidget)通常收到一个事件的时候发出一个信号.这纯粹是为了方便,所以你可以在控件收到一个事件的时候调用一个回调函数.当你按下一个按钮的时候会发出一个信号,所以才让人认为信号和事件有天生的联系.

 


第六章:用GTK+开发:控件专题

6.1. 在GtkList控件中如何找出选择项(selection)?

在GtkList控件中如何找出选择项(selection)?

这样可以得到选择项:

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

这是GList如何定义的(引用自glist.h)

typedef struct _GList GList;

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

一个GList结构是一个用于双向链表(doubly linked list)的简单结构体.在glib.h有几个g_list_*()这样的函数来修改链表.但是GTK_LIST(MyGtkList)->selection是由gtk_list_*()函数维护的,而且不应被修改.

GtkList的选择模式(selection_mode)决定了一个GtkList控件的选择功能,所以也决定了GTK_LIST(AnyGtkList)->selection:

 

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

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

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

得到选择项:

{
        GList   *items;

        items=GTK_LIST(list)->selection;

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

6.5. 如何在菜单中定义分隔线(separation line)?

如何在菜单中定义分隔线(separation line)?

参看Tutorial关于如何创建菜单.创建分隔线只需要创建一个空的菜单项:

menuitem = gtk_menu_item_new();
gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
gtk_widget_show(menuitem);

6.15. 如何确认/限制/筛选GtkEntry中的输入?

如何确认/限制/筛选GtkEntry中的输入?

如果你想确认用户在GtkEntry控件中输入的文本,你可以连接"insert_text"信号,在回调函数中修改文本.下面的代码要求所有的字符为大写,并且字符的范围为A-Z,注意:当GtkEntry定义时,entry被转换成了GtkEditable类型的一个对象,

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

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

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

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

6.18. 如何在GtkText控件中设定光标位置(cursor position)?

如何在GtkText控件中设定光标位置(cursor position)?

注意:下面的回答对所有的从GtkEditable类中继承的对象都是有效的.

确信你要移动光标的位置?很多时候,当光标的位置合适时,插入点并不对应光标的位置.如果这正是你想要的效果,你应该使用gtk_text_set_point() 函数,如果你想在光标的位置设置插入点,这样做:

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

如果你想插入点一直都跟随着光标,你应该捕获按钮按下事件(button press event),然后移动插入点.小心:当控件改变光标的位置之后,你必须捕获这个事件. 建议使用下面的代码:

static void
insert_bar (GtkWidget *text)
{
  /* jump to cursor mark */
  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);

  /* connect after everything else */
  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;
}

now,如果你想改变光标的位置,你应该可以使用gtk_editable_set_position()函数了.

 


第七章 关于GDK


第八章 关于GLib


8.5. 什么是扫描器(GScanner),如何使用?

什么是扫描器(GScanner),如何使用?

一个扫描器(GScanner)能够符号化(tokenize)文本.就是说:它将会对输入流中的每一个字符和数字返回一个整数(integer),当然是根据一定的规则(可由客户定制)来实现这种转换.但是你还是需要根据自己的需要来编写分析函数(parsing functions).

这是由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;
}

你需要理解扫描器将会分析输入然后符号化,取决于你如何翻译这些符号,在被分析之前请不要定义它们的类型.例如分析一个字符串:

"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");

扫描器将会这样分析:

"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"

你需要自己编程来匹配这些符号序列(token sequence),如果你遇到一些不想要的东西,可以作为错误输出:

/* 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"就不能接受.

 


第九章 GTK+ FAQ 投稿,维护者和版权

    如果你想向FAQ投稿,请向我们其中之一发一封电子邮件.准确的写上内容(问题和回答),有了你的努力,这份文档会变得更加的有用.
    这份文档由这些人维护: Tony Gale mailto:[email protected] Nathan Froyd mailto:[email protected], 和 Emmanuel Deloget mailto:[email protected]. 这份FAQ 由Shawn T. Amundson mailto:[email protected] 编写并继续提供支持. 投稿应该寄到 Tony Gale mailto:[email protected].

The GTK+ FAQ is 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.

If you are intending to incorporate this document into a published work, please contact one of the maintainers, and we will make an effort to ensure that you have the most up to date information available.

There is no guarentee that this document lives up to its intended purpose. This is simply provided as a free resource. As such, the authors and maintainers of the information provided within can not make any guarentee that the information is even accurate.