红联Linux门户
Linux帮助

给GTK+初学者------中文版

发布时间:2007-10-09 01:19:18来源:红联作者:gxf
[font=宋体]GTK入门导引

1. 简介

  GTK (GIMP Toolkit) 起源於开发用来做为GIMP (General Image Manipulation Program)的一套工具. GTK建立在GDK (GIMP Drawing Kit)的上层, 基本上是将Xlib功能包装起来. 它被称为GIMP toolkit 是因为原来是写来开发GIMP, 但现在被许多免费软体计划所使用. 原作者为

  Peter Mattis petm@xcf.berkeley.edu

  Spencer Kimball spencer@xcf.berkeley.edu

  Josh MacDonald jmacd@xcf.berkeley.edu

  GTK基本上是物件导向应用软体程式设计介面(API). 虽然完全用C所写成, 他是用classes及callback函数的观念所实作出来的(指向该函数).

  还有另一个被称为glib的函数库被用到, 该函数库包涵了一些标准X函数的替代函数, 及一些额外的处理链结表的函数等等. 这些替代函数是用来增加GTK的可移植性, 因为有些函数需要用到非标准的功能, 诸如g_strerror(). 有些则包含一些libc版本的加强的功能, 诸如 g_malloc有
加强的除错功能.

  这份导引是尽可能去详尽描述GTK的功能, 虽然实在没有办法尽善尽美. 这份导引假设读者对C语言有很相当的基础, 并且知道如何去写C语言程式. 如果读者有过X的程式经验, 会大大有帮助, 但并非绝对需要 (译注: 这一点就好像是要先学MFC或SDK的问题一样). 如果您以GTK做为进
入X程式设计的入门的话, 请给我们一些建议, 有关於您在本导引所学到及发现的东西, 及过程中有何困扰. 同时, 目前GTK也有C++ API (GTK--)正在发展, 所以如果您喜欢用C++, 您可能要先去看一看. 同时也有一套Objective C wrapper, guile bindings版本也有, 但我不建议您走这条
路.

  同时我也很想知道, 您在由本文学习GTK上有何问题, 我会感谢您告诉我如何改进这些种种的缺点.

  2. 开始

  第一件要做的是当然是取得一份GTK的原始码并且安装进您的系统中. 您可以从GIMP取得一份发行版, 或者是从Peter Mattis's的" 家中" ftp.xcf.berkely.edu/pub/pmattis(however, it has been changed to ftp.gimp.org)取得一份. GTK使用GNU的autoconf来设定. 一但您解开档案
, 输入configure --help来看看选项表列.

  在介绍GTK的一开始, 我们尽可能挑最简单的程式. 这个程式将会产生200x200点的视窗, 而且没办法离开, 除非从shell中将它杀掉.


#include
int main (int argc, char *argv[])
{
GtkWidget *window;
gtk_init (&argc, &argv);
window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
gtk_widget_show (window);
gtk_main ();
return 0;
}

  所有程式理所当然一定会包含gtk/gtk.h, 其中宣告了所有变数, 函数, 及资料及结构. 这些东西您会在您的GTK应用软体中用到.

  下一行


gtk_init (&argc, &argv);

  呼叫函数gtk_init(gint *argc, gchar ***argv)将会启动GTK. 该函数设定了一些内定的值, 并且後续交给 gdk_init(gint *argc, gchar ***argv) 继续处理. 该函数启动了一些函数库以供使用, 设定了内定的信号处理, 检查传给您的程式的命令列参数. 看看以下:

  --display

  --debug-level

  --no-xshm

  --sync

  --show-events

  --no-show-events

  这些参数将会从参数表中删去, 所剩下的会传给您做後续的处理. 这样会产生标准的参数表(除了GTK所使用的)以供您使用.

  下面这两行程式会产生并显示一个视窗.


window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
gtk_widget_show (window);

  GTK_WINDOW_TOPLEVEL参数指定了我们承习视窗管理程式的外观. 即便我们产生一个0x0大小的视窗, 没有子视窗的视窗内定被设为200x200, 如此我们依然可以处理它.

  gtk_widget_show()函数, 让GTK知道, 我们已经处理完设定其属性的工作, 并且可以显示它.

  最後一行进入GTK的主要处理回圈.


gtk_main ();

  gtk_main()是个在每个GTK应用软体中都会看到的一个函数. 当控制到达这里, GTK会"睡"一下来等待X事件的发生(诸如像按键被按下). 在我们最简单的例子里面, 事件会被忽略掉. 因为我们没有处理它.

  2.1 用GTK来写Hello World

  好, 现在我们来写一个有一个视窗物件的视窗(一个按钮). 这是个GTK的标准hello world. 这会建立起一个新的GTK软体的良好基础.


#include
/* 这是个callback函数. 其资料参数在本例中被忽略
* 以下有更多的callback函数. */
void hello (GtkWidget *widget, gpointer *data)
{
g_print ("Hello World
");
}
/* another callback */
void destroy (GtkWidget *widget, gpointer *data)
{
gtk_main_quit ();
}
int main (int argc, char *argv[])
{
/* GtkWidget用以储存视窗物件形态 */
GtkWidget *window;
GtkWidget *button;
/* 这在所有GTK应用软体中用到. 参数由命令列中解译出来并且送到该应用软体中. */
gtk_init (&argc, &argv);
/* 产生新视窗 */
window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
/* 当视窗收到"destroy"信号时(可由该软体或视窗管理程式所送出)
所会被呼叫到的destroy函数一如以下所定义的一般.
送到该函数的资料将会是NULL,并且在该函数中被忽略 */
gtk_signal_connect (GTK_OBJECT (window), "destroy",
GTK_SIGNAL_FUNC (destroy), NULL);
/* 设定视窗的边框的宽度 */
gtk_container_border_width (GTK_CONTAINER (window), 10);
/* 产生一个新的按钮并带有"Hello World"的字在上面. */
button = gtk_button_new_with_label ("Hello World");
/* 当该按键收到"clicked"信号, 它会呼叫hello()这个函数.
并且以NULL做为其参数. hello()函数在以上已定义过. */
gtk_signal_connect (GTK_OBJECT (button), "clicked",
GTK_SIGNAL_FUNC (hello), NULL);
/* 这会导致当"clicked"这个按钮被按下的时候,
呼叫gtk_widget_destroy(window)而使该视窗被关闭
当然了, 关闭的信号会从此处或视窗管理程式所送来 */
gtk_signal_connect_object (GTK_OBJECT (button), "clicked",
GTK_SIGNAL_FUNC (gtk_widget_destroy),
GTK_OBJECT (window));
/* 这个动作会把这个按钮结合到该视窗(a gtk container). */
gtk_container_add (GTK_CONTAINER (window), button);
/* 最後一步是显示最新产生的视窗物件... */
gtk_widget_show (button);
/* 及该视窗 */
gtk_widget_show (window);
/* 所有GTK程式都一定要有gtk_main(). 所有控制结束於此并等带事件的发生
(像按下一键或滑鼠的移动). */
gtk_main ();
return 0;
}

  2.2 编译Hello World

  用以下命令来编译:


gcc -Wall -g helloworld.c -o hello_world -L/usr/X11R6/lib
-lglib -lgdk -lgtk -lX11 -lXext -lm

  函数库必须在内定的搜寻路径内, 如果找不到, -L 则gcc会去找这些目录, 看看所需要的函数库是否找得到. 例如, 在我的Debian Linux系统中, 我已经增加了 -L/usr/X11R6/lib用来寻找X11函数库.

  以下函数库是很重要的. linker在处理之前, 必须知道什麽函数要用那一个函数库.

  函数库如下:

  glib函数库(-lglib), 包含一些有用的函数, 这个例子中只用到g_print(), 因为GTK是建在glib之上, 所以您几乎都一定会用到它. 详见glib一段.

  GDK函数库(-lgdk), Xlib的包装程式.

  GTK函数库(-lgtk), 视窗物件函数库, 基於GDK之上.

  xlib函数库(-lXlib) 基本上为GDK所用.

  Xext函数库(-lXext). 包含了shared memory pixmaps及其它的一些X extensions.

  math函数库(-lm). 为GTK所用, 有多方面用途.

  2.3 Signals及Callbacks的原理

  在我们更进一步探讨hello world之前, 我们要讲一下事件(events)及回呼函数(callbacks). GTK本身是个事件驱动的工具, 这意味著它会在gtk_main进入停歇状态, 一直到一个事件发生, 并且将控制交给适当的函数来处理.

  控制权的交出是由"signals"来决定的. 当事件发生, 诸如按下滑鼠的一个按键, 对应的信号会由该视窗物件所送出. 这便是GTK的主要工作. 要使一个按下的动作执行一个命令, 我们设定一个信号处理函数来撷取这个信号, 并且呼叫适当的函数. 这工作是由像以下的函数来完成的:


gint gtk_signal_connect (GtkObject *object,
gchar *name,
GtkSignalFunc func,
gpointer func_data);

  其第一个参数是会送出信号的物件, 第二个是希望接取的信号名称. 第三个是当信号送出时的接取函数, 第四个则是要送给该函数的资料.

  第三个参数被称为"callback function", 而且必需是以下的形式:


void callback_func(GtkWidget *widget, gpointer *callback_data);

  第一个参数是指向该物件的指标, 第二个是在gtk_signal_connect()的最後一个参数.

  另外一个在hello world中有用到的函数是:


gint gtk_signal_connect_object (GtkObject *object,
gchar*name,
GtkSignalFunc func,
GtkObject *slot_object);

  gtk_signal_connect_object()跟gtk_signal_connect()一样, 除了callback函术只有一个参数, 一个指向GTK物件的指标. 所以当使用这个函数来接到信号时, 该callback函数必须是以下形式:


void callback_func (GtkObject *object);

  一般这个object是个widget(物件). 我们一般不设定callback给gtk_signal_connect_object. 他们是用来呼叫GTK函数来接受单一物件(widget or object)做为参数.

  有两个函数来连接信号的目的只是希望允许callbacks可以有不同数量的参数. 许多GTK函数仅接受一个GtkWidget指标做为参数, 所以您可以使用gtk_signal_connect_object()来使用这些函数, 而在您的函数里面, 您会需要额外的资料提供给 callback.
[

  aidcode]2.4 步过Hello World

  现在您知道这些理论了, 我们现在来根据这些理论, 把"hello world"这个范例弄清楚.

  这是个当按钮被按下时, 会被呼叫到的callback函数. 参数的资料没有被用到.


void hello (GtkWidget *widget, gpointer *data)
{
g_print ("Hello World
");
}

  这是另一个callback函数, 它会呼叫gtk_main_quit()来离开程式.


void destroy (GtkWidget *widget, gpointer *data)
{
gtk_main_quit ();
}
int main (int argc, char *argv[])
{

  下个部份, 宣告一个指标给GtkWidget. 这是准备用来产生视窗及按钮的.


GtkWidget *window;
GtkWidget *button;

  这里是我们的gtk_init. 设定GTK toolkit初始值.


gtk_init (&argc, &argv);

  产生新视窗. 这是蛮直接的. 记忆体配置给GtkWidget * window使其成为有效的资料. 它设定一个新的视窗, 但在我们呼叫gtk_widget_show(window)之前不会显示.


window = gtk_window_new (GTK_WINDOW_TOPLEVEL);

  这里是将object(window)连接到信号处理器的范例. 此处"destroy"是该信号. 该信号是window manager要销去这个视窗时, 或我们送出gtk_widget_destroy()时会产生的. 当我们这样设定时, 我们可同时处理两种状况. 这里我们使用 destroy函数, 这使我们可以使用window manager
来离开这个程式.

  GTK_OBJECT及GTK_SIGNAL_FUNC是分派巨集.


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

  下一个函数是用来设定container物件的属性. This just sets the window so it has a blank area along the inside of it 10 pixels wide where no widgets will go. There are other similar functions which we will look at in the section on Setting Widget
Attributes


And again, GTK_CONTAINER is a macro to perform type casting.
gtk_container_border_width (GTK_CONTAINER (window), 10);

  这个会产生一个新的按钮. 它配置记忆体给一个新的GtkWidget, 并初始化. 他将会有一个标签"Hello World".


button = gtk_button_new_with_label ("Hello World");

  然後, 我们让这个按钮做一点事. 我们将他接到一个信号处理器, 因此它会送出"clicked"信号, 而我们的hello()函数会被呼叫到. 资料被忽略, 所以我们只喂NULL给hello(), 明显的, "clicked"信号当我们敲下滑鼠时被送出.


gtk_signal_connect (GTK_OBJECT (button), "clicked",
GTK_SIGNAL_FUNC (hello), NULL);

  我们将用这个按钮来离开程式. 这将展示"destroy"信号可以是来自window manager, 或是我们的程式. 当按钮被 "clicked", 跟上面一样, 它会呼叫hello() callback函数, 然後是这一个, 以它们被设定的先後顺序被呼叫到. 您可以有任意个callback函数, 它们会以被连接的先後顺
序被执行到. 因为gtk_widget_destroy()函数仅接受 GtkWidget *widget做为参数, 我们使用gtk_signal_connect_object() , 而不用gtk_signal_connect().


gtk_signal_connect_object (GTK_OBJECT (button), "clicked",
GTK_SIGNAL_FUNC (gtk_widget_destroy),
GTK_OBJECT (window));

  这是个封装呼叫, 我们在後面的文件中会解释. 不过这倒蛮容易理解的. 它就是告诉GTK按钮要放在要显示出来的那个视窗.


gtk_container_add (GTK_CONTAINER (window), button);

  现在我们将所有东西照我们的意思来设定好了. 所有信号接好了, 按钮也放到该有的位置, 现在来"show"这个视窗吧. 这个整个视窗会一下子从萤幕蹦出来, 而不是先看到视窗, 然後按钮才跑出来.


gtk_widget_show (button);
gtk_widget_show (window);

  还有当然了, 我们呼叫gtk_main()来等待X事件的发生, 当事件发生时, 它将会呼叫物件来送出信号.


gtk_main ();

  最後, 程式终止於此. 在gtk_quit()被呼叫到後, 程式会离开.


return 0;



  在, 当我们在GTK上敲下滑鼠, 这个物件会送出"clicked"信号. 我们的程式设定了信号处理器来接取这个信号, 这样我们便可利用这个资讯. 在我们的范例中, 当按钮被"clicked", hello()函数被呼叫到, 并被传入一个NULL参数, 然後下一个处理函数被呼叫到. 它会呼叫gtk_widget_
destroy()函数, 传入视窗物件做为参数, 并将该视窗物件销毁. 这会导致该视窗送出"destroy"信号, 收到该信号後, 会呼叫我们的destroy() callback函数, 而我们的destroy()会令程式离开GTK.

  另一个方式当然是利用window manager来销毁该视窗. 这也会导致该视窗送出"destroy"信号, 然後呼叫destroy() callback, 然後离开.

  这些信号与UNIX系统不太一样, 并非基於UNIX系统的信号系统, 虽然它们的术语是一样的.

  3. 下一步

  3.1 资料型态

  有些东西您可能在前面的范例中已经看到, 这需要多解释一下. 像gint, gchar等等. 这些是为了取得绝对乾净的独立性, 如资料大小等等. 像"gint32"就是个很好的范例, 其目的是维持到任意平台均为32bits, 不管是64 bit alpha或是 32 bit i386. 其定义是极其直接而且直觉的.
它们都被定义在glib/glib.h (被gtk.h所include).

  您也看到像在GtkWidget这一类的东西. GTK是物件导向的设计, 而widget则是其中的物件.

  3.2 更多关於信号处理函数

  我们来看看gtk_signal_connect宣告.


gint gtk_signal_connect (GtkObject *object, gchar *name,
GtkSignalFunc func, gpointer func_data);

  看到gint的返回值? 这是个标明您的callback函数的标签值. 像之前所说的, 每个信号及物件可以有好几个callback, 每个会以它们所接上的顺序被轮流执行到. 您可以用以下这个函数来移除这个callback函数:


void gtk_signal_disconnect (GtkObject *object,
gint id);

  你可以透过您想要移除的widget handler,给定id, 来解除信号处理函数.

  您也可以用:


gtk_signal_disconnect_by_data (GtkObject *object,
gpointer data);

  这玩意我倒没用过, 我真得不晓得要怎麽用 :)

  另一个函数可以解除所有的处理函数:


gtk_signal_handlers_destroy (GtkObject *object);

  这个函数到底是自己解释了自己的功能. 它移除了该物件所有的信号处理函数.

  3.3 Hello World的加强版

  我们来看看一个稍经改良的hello world, 这是个callback的不错的例子. 这也会介绍到我们下一个主题, 封装物件.


#include
/* 新改进的callback.输入到该函数的资料被输出到. */
void callback (GtkWidget *widget, gpointer *data)
{
g_print ("Hello again - %s was pressed
", (char *) data);
}
/* another callback */
void destroy (GtkWidget *widget, gpointer *data)
{
gtk_main_quit ();
}
int main (int argc, char *argv[])
{
/* GtkWidget is the storage type for widgets */
GtkWidget *window;
GtkWidget *button;
GtkWidget *box1;
/* this is called in all GTK applications.arguments are parsed from
* the command line and are returned to the application. */
gtk_init (&argc, &argv);
/* create a new window */
window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
/* 这是个新函数, 它设定title到新视窗上"Hello Buttons!" */
gtk_window_set_title (GTK_WINDOW (window), "Hello Buttons!");
/* 用这样会比较简单一点. */
gtk_signal_connect (GTK_OBJECT (window), "destroy",
GTK_SIGNAL_FUNC (destroy), NULL);
/* 设定边框宽度. */
gtk_container_border_width (GTK_CONTAINER (window), 10);
/* 我们产生一个box来封装物件.这一点会在"packing"详述.
这个box实际上看不见, 它只是用来当成是个工具来安排物件 */
box1 = gtk_hbox_new(FALSE, 0);
/* 将box放到主视窗中. */
gtk_container_add (GTK_CONTAINER (window), box1);
/* 产生一个新按钮并带有标签"Button 1". */
button = gtk_button_new_with_label ("Button 1");
/* 当按钮被按下的时候, 我们呼叫"callback"函数
* 并以其指标做为参数送到"button 1" */
gtk_signal_connect (GTK_OBJECT (button), "clicked",
GTK_SIGNAL_FUNC (callback), (gpointer) "button 1");
/* instead of gtk_container_add, we pack this button into the invisible
* box, which has been packed into the window. */
gtk_box_pack_start(GTK_BOX(box1), button, TRUE, TRUE, 0);
/* always remember this step, this tells GTK that our preparation for
* this button is complete, and it can be displayed now. */
gtk_widget_show(button);
/* do these same steps again to create a second button */
button = gtk_button_new_with_label ("Button 2");
/* call the same callback function with a different argument,
* passing a pointer to "button 2" instead. */
gtk_signal_connect (GTK_OBJECT (button), "clicked",
GTK_SIGNAL_FUNC (callback), (gpointer) "button 2");
gtk_box_pack_start(GTK_BOX(box1), button, TRUE, TRUE, 0);
/* The order in which we show the buttons is not really important, but I
* recommend showing the window last, so it all pops up at once. */
gtk_widget_show(button);
gtk_widget_show(box1);
gtk_widget_show (window);
/* rest in gtk_main and wait for the fun to begin! */
gtk_main ();
return 0;
}

  将这个程式以相同的参数编译, 您会看到没有任何方法来离开程式, 您必须使用视窗管理程式或命令列来杀掉它. 对读者来说, 加个"Quit"按钮会是个不错的练习. 您也可以玩一玩gtk_box_pack_start()这个东西. 试试拉一拉视窗, 看看有什麽变换.

  另外有个蛮有用的define给gtk_window_new()用 - GTK_WINDOW_DIALOG. 它的互动行为有点不太一样.

  4. 封装物件

  当我们制作一套软体, 您会希望在视窗内放超过一个以上的按钮. 我们第一个范例"hello world"仅用一个物件, 因此我们能够很简单使用 gtk_container_add来"封装"该物件到视窗中. 但当您希够望放更多的物件到视窗中, 要如何控制它们的位置? 这里就要用到"封装" (Packing).

  4.1 Packing Boxes的理论

  大部份的封装是由产生boxes来达成的. 这些是看不见的widget containers, 我们可以用两种形式来将我们的物件封装进去, vertical box及horizontal box. 当我们封装物件到一个horizontal box时, 物件是依我们呼叫的顺序由右至左平行的被新增进去. 在vertical box, 物件是
由上至下. 您可以将物件插入box, 也可以将boxes插入box, 任意的组合用以产生所想要的效果.

  要产生horizontal box,我们使用gtk_hbox_new(), 而vertical boxe使用gtk_vbox_new(). gtk_box_pack_start()及gtk_box_pack_end()函数是用来将物件放到containers里面. gtk_box_pack_start()函数会开始由左至右, 由上至下来封装物件. gtk_box_pack_end()则相反, 由下至
上, 由右至左. 使用这些函数允许我们对右边或对左边较正, 而且可以用许多种方式来较正来取得所想要的效果. 一个object可以是另一个 container或物件. 而且事实上, 许多物件本身也是containers. 像按钮就是, 不过我们一般只在按钮中用一个标签.

  使用这些呼叫, GTK知道要把物件放到那里去, 并且会自动缩放及其它比例上的调整. 还有许多其它选项可以控制如何将物件封装在一起. 正如您所想的, 这些方法可以给您许多的弹性来制作视窗.

  4.2 Boxes详述

  由於这样的弹性, packing boxes在一开始使用的话会有点搞糊涂. 还有许多其它的选项,一开始还看不太出来它们如何凑在一起. 最後您会知道, 他们基本上有五种不同的型式.

  每一行包含一个horizontal box (hbox)及好几个按钮. 所有按钮都是以同样的方式来包入hbox内.

  这是gtk_box_pack_start的宣告.


void gtk_box_pack_start (GtkBox*box,
GtkWidget *child,
gintexpand,
gintfill,
gintpadding);

  第一个参数是您要把object放进去的box, 第二个是该object. 现在这些物件将会都是按钮.

  expand参数在gtk_box_pack_start()或gtk_box_pack_end()中控制物件如何在box中排列. expand = TRUE的话它们会填满box中所有额外的空间. expand = FALSE的话, 该box会缩小到刚好该物件的大小. 设 expand=FALSE您可做好左右较正. 否则它们会填满整个box. 同样的效果可用tk
_box_pack_start或pack_end functions来达成.

  fill参数在gtk_box_pack中控制额外空间. fill=TRUE该物件会自行产生额外空间, fill=FALSE则由box产生一个在物件周围的填白区域. 这只有在expand=TRUE时, 才会有作用.

  当产生一个新的box, 该函数看起来像这样:


GtkWidget * gtk_hbox_new (gint homogeneous,
gint spacing);

  homogeneous参数在gtk_hbox_new (and the same for gtk_vbox_new) 控制每个物件是否有同样的宽或高. 若homogeneous=TRUE, 则expand也会被开启.

  空白(spacing)及填白(padding)有什麽不同呢空白是加在物件之间, 填白只加在物件的一边. 看以下这张图可能会明白一点:

  这里是一些用来产生以上影像的程式. 我做了蛮多的注解, 希望您不会有问题. 将它编译然後玩玩它.

  4.3 封装示范程式


#include "gtk/gtk.h"
void
destroy (GtkWidget *widget, gpointer *data)
{
gtk_main_quit ();
}
/* Make a new hbox filled with button-labels. Arguments for the
* variables we're interested are passed in to this function.
* We do not show the box, but do show everything inside. */
GtkWidget *make_box (gint homogeneous, gint spacing,
gint expand, gint fill, gint padding)
{
GtkWidget *box;
GtkWidget *button;
char padstr[80];
/* create a new hbox with the appropriate homogeneous and spacing
* settings */
box = gtk_hbox_new (homogeneous, spacing);
/* create a series of buttons with the appropriate settings */
button = gtk_button_new_with_label ("gtk_box_pack");
gtk_box_pack_start (GTK_BOX (box), button, expand, fill, padding);
gtk_widget_show (button);
button = gtk_button_new_with_label ("(box,");
gtk_box_pack_start (GTK_BOX (box), button, expand, fill, padding);
gtk_widget_show (button);
button = gtk_button_new_with_label ("button,");
gtk_box_pack_start (GTK_BOX (box), button, expand, fill, padding);
gtk_widget_show (button);
/* create a button with the label depending on the value of
* expand. */
if (expand == TRUE)
button = gtk_button_new_with_label ("TRUE,");
else
button = gtk_button_new_with_label ("FALSE,");
gtk_box_pack_start (GTK_BOX (box), button, expand, fill, padding);
gtk_widget_show (button);
/* This is the same as the button creation for "expand"
* above, but uses the shorthand form. */
button = gtk_button_new_with_label (fill ? "TRUE," : "FALSE,");
gtk_box_pack_start (GTK_BOX (box), button, expand, fill, padding);
gtk_widget_show (button);
sprintf (padstr, "%d);", padding);
button = gtk_button_new_with_label (padstr);
gtk_box_pack_start (GTK_BOX (box), button, expand, fill, padding);
gtk_widget_show (button);
return box;
}
int
main (int argc, char *argv[])
{
GtkWidget *window;
GtkWidget *button;
GtkWidget *box1;
GtkWidget *box2;
GtkWidget *separator;
GtkWidget *label;
GtkWidget *quitbox;
int which;
/* Our init, don't forget this! :) */
gtk_init (&argc, &argv);
if (argc != 2) {
fprintf (stderr, "usage: packbox num, where num is 1, 2, or 3.
");
/* this just does cleanup in GTK, and exits with an exit status of 1. */
gtk_exit (1);
}
which = atoi (argv[1]);
/* Create our window */
window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
/* You should always remember to connect the destroy signal to the
* main window.This is very important for proper intuitive
* behavior */
gtk_signal_connect (GTK_OBJECT (window), "destroy",
GTK_SIGNAL_FUNC (destroy), NULL);
gtk_container_border_width (GTK_CONTAINER (window), 10);
/* We create a vertical box (vbox) to pack the horizontal boxes into.
* This allows us to stack the horizontal boxes filled with buttons one
* on top of the other in this vbox. */
box1 = gtk_vbox_new (FALSE, 0);
/* which example to show.These correspond to the pictures above. */
switch (which) {
case 1:
/* create a new label. */
label = gtk_label_new ("gtk_hbox_new (FALSE, 0);");
/* Align the label to the left side.We'll discuss this function and
* others in the section on Widget Attributes. */
gtk_misc_set_alignment (GTK_MISC (label), 0, 0);
/* Pack the label into the vertical box (vbox box1).Remember that
* widgets added to a vbox will be packed one on top of the other in
* order. */
gtk_box_pack_start (GTK_BOX (box1), label, FALSE, FALSE, 0);
/* show the label */
gtk_widget_show (label);
/* call our make box function - homogeneous = FALSE, spacing = 0,
* expand = FALSE, fill = FALSE, padding = 0 */
box2 = make_box (FALSE, 0, FALSE, FALSE, 0);
gtk_box_pack_start (GTK_BOX (box1), box2, FALSE, FALSE, 0);
gtk_widget_show (box2);
/* call our make box function - homogeneous = FALSE, spacing = 0,
* expand = FALSE, fill = FALSE, padding = 0 */
box2 = make_box (FALSE, 0, TRUE, FALSE, 0);
gtk_box_pack_start (GTK_BOX (box1), box2, FALSE, FALSE, 0);
gtk_widget_show (box2);
/* Args are: homogeneous, spacing, expand, fill, padding */
box2 = make_box (FALSE, 0, TRUE, TRUE, 0);
gtk_box_pack_start (GTK_BOX (box1), box2, FALSE, FALSE, 0);
gtk_widget_show (box2);
/* creates a separator, we'll learn more about these later,
* but they are quite simple. */
separator = gtk_hseparator_new ();
/* pack the separator into the vbox.Remember each of these
* widgets are being packed into a vbox, so they'll be stacked
* vertically. */
gtk_box_pack_start (GTK_BOX (box1), separator, FALSE, TRUE, 5);
gtk_widget_show (separator);
/* create another new label, and show it. */
label = gtk_label_new ("gtk_hbox_new (TRUE, 0);");
gtk_misc_set_alignment (GTK_MISC (label), 0, 0);
gtk_box_pack_start (GTK_BOX (box1), label, FALSE, FALSE, 0);
gtk_widget_show (label);
/* Args are: homogeneous, spacing, expand, fill, padding */
box2 = make_box (TRUE, 0, TRUE, FALSE, 0);
gtk_box_pack_start (GTK_BOX (box1), box2, FALSE, FALSE, 0);
gtk_widget_show (box2);
/* Args are: homogeneous, spacing, expand, fill, padding */
box2 = make_box (TRUE, 0, TRUE, TRUE, 0);
gtk_box_pack_start (GTK_BOX (box1), box2, FALSE, FALSE, 0);
gtk_widget_show (box2);
/* another new separator. */
separator = gtk_hseparator_new ();
/* The last 3 arguments to gtk_box_pack_start are: expand, fill, padding. */
gtk_box_pack_start (GTK_BOX (box1), separator, FALSE, TRUE, 5);
gtk_widget_show (separator);
break;
case 2:
/* create a new label, remember box1 is a vbox as created
* near the beginning of main() */
label = gtk_label_new ("gtk_hbox_new (FALSE, 10);");
gtk_misc_set_alignment (GTK_MISC (label), 0, 0);
gtk_box_pack_start (GTK_BOX (box1), label, FALSE, FALSE, 0);
gtk_widget_show (label);
/* Args are: homogeneous, spacing, expand, fill, padding */
box2 = make_box (FALSE, 10, TRUE, FALSE, 0);
gtk_box_pack_start (GTK_BOX (box1), box2, FALSE, FALSE, 0);
gtk_widget_show (box2);
/* Args are: homogeneous, spacing, expand, fill, padding */
box2 = make_box (FALSE, 10, TRUE, TRUE, 0);
gtk_box_pack_start (GTK_BOX (box1), box2, FALSE, FALSE, 0);
gtk_widget_show (box2);
separator = gtk_hseparator_new ();
/* The last 3 arguments to gtk_box_pack_start are: expand, fill, padding. */
gtk_box_pack_start (GTK_BOX (box1), separator, FALSE, TRUE, 5);
gtk_widget_show (separator);
label = gtk_label_new ("gtk_hbox_new (FALSE, 0);");
gtk_misc_set_alignment (GTK_MISC (label), 0, 0);
gtk_box_pack_start (GTK_BOX (box1), label, FALSE, FALSE, 0);
gtk_widget_show (label);
/* Args are: homogeneous, spacing, expand, fill, padding */
box2 = make_box (FALSE, 0, TRUE, FALSE, 10);
gtk_box_pack_start (GTK_BOX (box1), box2, FALSE, FALSE, 0);
gtk_widget_show (box2);
/* Args are: homogeneous, spacing, expand, fill, padding */
box2 = make_box (FALSE, 0, TRUE, TRUE, 10);
gtk_box_pack_start (GTK_BOX (box1), box2, FALSE, FALSE, 0);
gtk_widget_show (box2);
separator = gtk_hseparator_new ();
/* The last 3 arguments to gtk_box_pack_start are: expand, fill, padding. */
gtk_box_pack_start (GTK_BOX (box1), separator, FALSE, TRUE, 5);
gtk_widget_show (separator);
break;
case 3:
/* This demonstrates the ability to use gtk_box_pack_end() to
* right justify widgets.First, we create a new box as before. */
box2 = make_box (FALSE, 0, FALSE, FALSE, 0);
/* create the label that will be put at the end. */
label = gtk_label_new ("end");
/* pack it using gtk_box_pack_end(), so it is put on the right side
* of the hbox created in the make_box() call. */
gtk_box_pack_end (GTK_BOX (box2), label, FALSE, FALSE, 0);
/* show the label. */
gtk_widget_show (label);
/* pack box2 into box1 (the vbox remember ? :) */
gtk_box_pack_start (GTK_BOX (box1), box2, FALSE, FALSE, 0);
gtk_widget_show (box2);
/* a separator for the bottom. */
separator = gtk_hseparator_new ();
/* this explicitly sets the separator to 400 pixels wide by 5 pixels
* high.This is so the hbox we created will also be 400 pixels wide,
* and the "end" label will be separated from the other labels in the
* hbox.Otherwise, all the widgets in the hbox would be packed as
* close together as possible. */
gtk_widget_set_usize (separator, 400, 5);
/* pack the separator into the vbox (box1) created near the start
* of main() */
gtk_box_pack_start (GTK_BOX (box1), separator, FALSE, TRUE, 5);
gtk_widget_show (separator);
}
/* Create another new hbox.. remember we can use as many as we need! */
quitbox = gtk_hbox_new (FALSE, 0);
/* Our quit button. */
button = gtk_button_new_with_label ("Quit");
/* setup the signal to destroy the window.Remember that this will send
* the "destroy" signal to the window which will be caught by our signal
* handler as defined above. */
gtk_signal_connect_object (GTK_OBJECT (button), "clicked",
GTK_SIGNAL_FUNC (gtk_widget_destroy),
GTK_OBJECT (window));
/* pack the button into the quitbox.
* The last 3 arguments to gtk_box_pack_start are: expand, fill, padding. */
gtk_box_pack_start (GTK_BOX (quitbox), button, TRUE, FALSE, 0);
/* pack the quitbox into the vbox (box1) */
gtk_box_pack_start (GTK_BOX (box1), quitbox, FALSE, FALSE, 0);
/* pack the vbox (box1) which now contains all our widgets, into the
* main window. */
gtk_container_add (GTK_CONTAINER (window), box1);
/* and show everything left */
gtk_widget_show (button);
gtk_widget_show (quitbox);
gtk_widget_show (box1);
/* Showing the window last so everything pops up at once. */
gtk_widget_show (window);
/* And of course, our main function. */
gtk_main ();
/* control returns here when gtk_main_quit() is called, but not when
* gtk_exit is used. */
return 0;
}

  4.4 使用表格来封装

  我们来看看另一个封装的方法 - 用表格. 在很多状况下, 这是极其有用的.

  使用表格, 我们产生格线来将物件放入. 物件会照我们安排的位置排入.

  我们第一个要看的是gtk_table_new这个函数:


GtkWidget* gtk_table_new (gint rows,
gint columns,
gint homogeneous);

  第一个参数是多少列, 第二个是多少栏.

  homogeneous参数用来决定表格如何来定大小. 若homogeneous为TRUE, table boxes会被重定为在其中最大物件的大小. 若homogeneous为FALSE, 则其大小为, "高"为列中最高的物件, 及"宽"栏中最宽的物件大小.

  列及栏的编号为从0到n. n是我们在gtk_table_new中所指定的值. 所以, 如果您指定rows = 2及columns = 2, 整个排列会看起来像这样:


012
0+----------+----------+
|||
1+----------+----------+
|||
2+----------+----------+

  坐标系统开始於左上角. 要把物件放进box中, 可用以下函数:


void gtk_table_attach (GtkTable*table,
GtkWidget *child,
gintleft_attach,
gintright_attach,
ginttop_attach,
gintbottom_attach,
gintxoptions,
gintyoptions,
gintxpadding,
gintypadding);

  第一个参数("table")是您才刚产生的表格, 而第二个("child")是您想放进去的物件.

  而left_attach及right_attach参数指定要把物件放在那里, 及用多少个boxes. 如果您想要用右下角的表格, 可以这样填表. left_attach = 1, right_attach = 2, top_attach = 1, bottom_attach = 2.

  现在, 如果您想要物件来使用上面2x2的表格, 您可以使用left_attach = 0, right_attach =2, top_attach = 0, bottom_attach = 1.

  xoptions及yoptions是用来指定封装选项, 可以同时组合多个选项(用or).

  这些选项是:

  GTK_FILL - 如果table box大过物件, 且GTK_FILL 被指定了, 该物件会扩展成使用所有可用的空间.

  GTK_SHRINK - 如果table widget小於该物件, (一般是使用者缩放该视窗), 那麽该物件将会一直被挤压到看不见为止. 如果GTK_SHRINK被指定了, 该物件会跟著table一起缩小.

  GTK_EXPAND - 这会使table本身扩展, 并利用视窗中所有可用空间.

  填空就像boxes, 产生一个在物件周边空白的区域.

  gtk_table_attach()有许多选项. 这里有个捷径:


void gtk_table_attach_defaults (GtkTable*table,
GtkWidget*widget,
gintleft_attach,
gintright_attach,
ginttop_attach,
gintbottom_attach);

  X及Y选项内定为GTK_FILL | GTK_EXPAND, X及Y填空则设为0. 其馀的参数则相同於以上的函数.

  我们另外有gtk_table_set_row_spacing()及gtk_table_set_col_spacing(). 这些会在指定的栏及列插入空白.


void gtk_table_set_row_spacing (GtkTable*table,
gintrow,
gintspacing);

voidgtk_table_set_col_spacing(GtkTable*table,
gintcolumn,
gintspacing);

  对栏来说, 空格是在栏的右边. 而列则是在下面.

  您也可以用以下函数来产生固定的空格.


void gtk_table_set_row_spacings (GtkTable *table,
gintspacing);
及,
void gtk_table_set_col_spacings (GtkTable*table,
gintspacing);

  使用这些函数, 其最後一栏及最後一列并没有空格存在.

  4.5 Table Packing范例

  目前并无说明, 请参照testgtk.c

  5. 物件概论

  在GTK下,一般产生物件的步骤为:

  gtk_*_new - 最普遍产生物件的函数.

  连接信号到信号处理器.

  设定物件属性.

  要将物件包装到一个container可用gtk_container_add()或gtk_box_pack_start().

  gtk_widget_show().

  gtk_widget_show()让GTK知道我们已经完成设定的工作, 并且已经准备好要显示. 您也可以用gtk_widget_hide来隐藏它. 显示物件的顺序并不太重要, 但我建议最後才显示, 这样才不会看到这些视窗们一个一个被画出来. 子物件在使用gtk_widget_show 使视窗出现之前是不会被显示
出来的.

  5.1 分派系统

  再继续下去您会发现, GTK使用一种分派系统. 一般是用巨集来完成. 您可以看到诸如以下:


GTK_WIDGET(widget)
GTK_OBJECT(object)
GTK_SIGNAL_FUNC(function)
GTK_CONTAINER(container)
GTK_WINDOW(window)
GTK_BOX(box)

  这些在函数中的都是分派参数. 您可以在范例中看到, 而且只要看到该函数就会知道它们是做什麽用的.

  从以下的组织图来看, 所有GtkWidgets都是由GtkObject而来. 这意味著您可以在任何地方, 透过GTK_OBJECT()巨集要求一个物件.

  例如:


gtk_signal_connect(GTK_OBJECT(button), "clicked",
GTK_SIGNAL_FUNC(callback_function), callback_data);

  这样分派一个按钮给一个物件, 并且提供一个指标给callback函数.

  许多物件同时也是containers. 如果您看看以下的组织图, 您会看到许多物件由GtkContainer而来 所有这一类的物件都可以用GTK_CONTAINER巨集产生使用containers.

  5.2 物件组织图

  这里是一些参考, 物件组织图.


GtkObject
+-- GtkData
|-- GtkAdjustment
|
-- GtkWidget
+-- GtkContainer
|+-- GtkBin
||+-- GtkAlignment
||+-- GtkFrame
|||*-- GtkAspectFrame
|||
||+-- GtkItem
|||+-- GtkListItem
|||+-- GtkMenuItem
||||+-- GtkCheckMenuItem
||||*-- GtkRadioMenuItem
||||
|||*-- GtkTreeItem
|||
||+-- GtkViewport
||-- GtkWindow
||+-- GtkDialog
||-- GtkFileSelection
||
|+-- GtkBox
||+-- GtkHBox
||-- GtkVBox
||+-- GtkColorSelection
||-- GtkCurve
||
|+-- GtkButton
||+-- GtkOptionMenu
||-- GtkToggleButton
||-- GtkCheckButton
||-- GtkRadioButton
||
|+-- GtkList
|+-- GtkMenuShell
||+-- GtkMenu
||-- GtkMenuBar
||
|+-- GtkNotebook
|+-- GtkScrolledWindow
|+-- GtkTable
|-- GtkTree
|
+-- GtkDrawingArea
+-- GtkEntry
+-- GtkMisc
|+-- GtkArrow
|+-- GtkImage
|+-- GtkLabel
|-- GtkPixmap
|
+-- GtkPreview
+-- GtkProgressBar
+-- GtkRange
|+-- GtkScale
||+-- GtkHScale
||-- GtkVScale
||
|-- GtkScrollbar
|+-- GtkHScrollbar
|-- GtkVScrollbar
|
+-- GtkRuler
|+-- GtkHRuler
|-- GtkVRuler
|
-- GtkSeparator
+-- GtkHSeparator
-- GtkVSeparator

  5.3 没有视窗的物件

  以下的物件跟视窗没有关系. 如果您希望接取它们的事件, 您需要使用GtkEventBox. 请见 EventBox物件


GtkAlignment
GtkArrow
GtkBin
GtkBox
GtkImage
GtkItem
GtkLabel
GtkPaned
GtkPixmap
GtkScrolledWindow
GtkSeparator
GtkTable
GtkViewport
GtkAspectFrame
GtkFrame
GtkVPaned
GtkHPaned
GtkVBox
GtkHBox
GtkVSeparator
GtkHSeparator

  再过来我们会一个一个物件来示范如何产生及显示. 一个很好的范例是testgtk.c, 您可以在gtk/testgtk.c里面找到.

  6. 按钮物件

  6.1 一般按钮

  我们到现在为止一直都看到按钮物件. 实在很简单. 有两种方法来产生按钮, 您可以用gtk_button_new_with_label() 来产生一个有标签的按钮, 或用gtk_button_new()来产生一个空按钮. 现在您可以任意操作它, 接张图(pixmap)或标签上去, 随你. 要达到这样的效果, 先产生一个b
ox, 然後用gtk_box_pack_start 将您的objects封装进这个box. 然後用gtk_container_add来封装这个box到按钮中.

  这里是个使用gtk_button_new来产生一张有图片及标签的按钮. 我将程式分细使您在往後可以利用这个程式.


#include
/* create a new hbox with an image and a label packed into it
* and return the box.. */
GtkWidget *xpm_label_box (GtkWidget *parent, gchar *xpm_filename,
gchar *label_text)
{
GtkWidget *box1;
GtkWidget *label;
GtkWidget *pixmapwid;
GdkPixmap *pixmap;
GdkBitmap *mask;
GtkStyle *style;
/* create box for xpm and label */
box1 = gtk_hbox_new (FALSE, 0);
gtk_container_border_width (GTK_CONTAINER (box1), 2);
/* get style of button.. I assume it's to get the background color.
* if someone knows the real reason, please enlighten me. */
style = gtk_widget_get_style(parent);
/* now on to the xpm stuff.. load xpm */
pixmap = gdk_pixmap_create_from_xpm (parent->window, &mask,
&style->bg[GTK_STATE_NORMAL],
xpm_filename);
pixmapwid = gtk_pixmap_new (pixmap, mask);
/* create label for button */
label = gtk_label_new (label_text);
/* pack the pixmap and label into the box */
gtk_box_pack_start (GTK_BOX (box1),
pixmapwid, FALSE, FALSE, 3);
gtk_box_pack_start (GTK_BOX (box1), label, FALSE, FALSE, 3);
gtk_widget_show(pixmapwid);
gtk_widget_show(label);
return (box1);
}
/* our usual callback function */
void callback (GtkWidget *widget, gpointer *data)
{
g_print ("Hello again - %s was pressed
", (char *) data);
}
int main (int argc, char *argv[])
{
/* GtkWidget is the storage type for widgets */
GtkWidget *window;
GtkWidget *button;
GtkWidget *box1;
gtk_init (&argc, &argv);
/* create a new window */
window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
gtk_window_set_title (GTK_WINDOW (window), "Pixmap'd Buttons!");
/* It's a good idea to do this for all windows. */
gtk_signal_connect (GTK_OBJECT (window), "destroy",
GTK_SIGNAL_FUNC (gtk_exit), NULL);
/* sets the border width of the window. */
gtk_container_border_width (GTK_CONTAINER (window), 10);
/* create a new button */
button = gtk_button_new ();
/* You should be getting used to seeing most of these functions by now */
gtk_signal_connect (GTK_OBJECT (button), "clicked",
GTK_SIGNAL_FUNC (callback), (gpointer) "cool button");
/* this calls our box creating function */
box1 = xpm_label_box(window, "info.xpm", "cool button");
/* pack and show all our widgets */
gtk_widget_show(box1);
gtk_container_add (GTK_CONTAINER (button), box1);
gtk_widget_show(button);
gtk_container_add (GTK_CONTAINER (window), button);
gtk_widget_show (window);
/* rest in gtk_main and wait for the fun to begin! */
gtk_main ();
return 0;
}

  xpm_label_box函数可用来包装xpm及labels到物件中使其成为container.

  6.2 双态按钮

  双态按钮跟一般按钮很像, 除了它们有两种状态, 由"click"来切换. 他们可以是"被按下的", 当您再按一次, 它们会还原. 再按一次会再被按下去.

  双态按钮是check按钮及radio按钮的基础. 它们有许多函数是继承双态按钮而来. 我等一下会把它们指出来.

  产生一个双态按钮:


GtkWidget* gtk_toggle_button_new (void);
GtkWidget* gtk_toggle_button_new_with_label (gchar *label);

  您可以看到, 这些跟一般按钮函数的用法一模一样. 第一个产生一个空白的双态按钮, 第二个则有个标签包在一起.

  要取得双态按钮的状态, 包含了check及radio按钮也一样, 我们用以下范例中所使用的巨集. 这会测试该按钮的状态. 当我们按下按钮时, 双态按钮的信号("toggled")会送给我们. 要取得其状态, 设定好信号处理器来接取"toggled"信号, 并使用该巨集来决定其状态. 该callback函
数看起来像这样:


void toggle_button_callback (GtkWidget *widget, gpointerdata)
{
if (GTK_TOGGLE_BUTTON (widget)->active)
{
/* If control reaches here, the toggle button is depressed. */
}
}
void gtk_toggle_button_set_state (GtkToggleButton *toggle_button,
gint state);

  以上的函数呼叫可用来设定双态按钮的状态(包含check及radio) 传您所产生的按钮做为第一个参数, 然後TRUE或FALSE做为第二个用来指定它是up(release)或down(depressed). 内定值是up, 或FALSE.


voidgtk_toggle_button_toggled(GtkToggleButton *toggle_button);

  这个会切换该按钮, 并送出"toggled"信号.

  6.3 Check按钮

  Check按钮有很多性质与双态按钮一样, 但外观看起来不同. 在文字上没有边框, 而在左边有个小方块. 这个在软体中选择要不要某个选项用得很频繁.

  两个产生的函数跟一般按钮一样.


GtkWidget* gtk_check_button_new (void);
GtkWidget* gtk_check_button_new_with_label (gchar *label);

  new_with_label函数产生一个check按钮并带一个标签在其右侧.

  测试check按钮的方法跟双态按钮一样.

  6.4 Radio Buttons

  Radio按钮与check按钮很像, 除了它们是成群的. 因而我们可以在一群中选择其中一个.

  产生一个新的radio按钮是由以下函数所达成的:


GtkWidget* gtk_radio_button_new (GSList *group);
GtkWidget* gtk_radio_button_new_with_label (GSList *group,
gchar *label);

  您看到有个额外的参数. 因为它需要一个group来达成这项工作. 第一个函数可以用NULL来做参数. 然後产生一个group:


GSList* gtk_radio_button_group (GtkRadioButton *radio_button);

  然後传这个group做为第一个参数给gtk_radio_button_new或new_with_label. 您也可以乾脆指明那一个是内定的.


void gtk_toggle_button_set_state (GtkToggleButton *toggle_button,
gint state);

  这个跟双态按钮一样.

  7. Tooltips物件

  他们是当您停在某个物件(像按钮或其它物件)上几秒时, 会自动出现的一个小的文字视窗. 它们很容易使用, 因此我只解释一下, 而不给范例程式. 如果您想看看一些范例程式, 可参考GDK内的testgtk.c.

  有些物件(像标签)无法与tooltips一起用.

  第一个呼叫的函数会产生一个新的tooltip. 您只需要呼叫这个函数一次. GtkTooltip这个函数的返回值可用来产生许多个tooltips.


GtkTooltips *gtk_tooltips_new (void);

  一旦您产生了一个新的tooltip, 您要设定到某个物件上, 只要呼叫这个函数即可.


void gtk_tooltips_set_tips(GtkTooltips *tooltips,
GtkWidget*widget,
gchar*tips_text);

  第一个参数是您刚才产生的tooltip, 接著是您希望使用的物件, 然後是您希望显示的文字.

  这里有个简短的范例:


GtkTooltips *tooltips;
GtkWidget *button;
...
tooltips = gtk_tooltips_new ();
button = gtk_button_new_with_label ("button 1");
...
gtk_tooltips_set_tips (tooltips, button, "This is button 1");

  tooltip还有其它的一些函数. 我只简短的介绍一下.


void gtk_tooltips_destroy(GtkTooltips *tooltips);

  销毁tooltips.


void gtk_tooltips_enable (GtkTooltips *tooltips);

  使一套已失效的tooltips生效.


void gtk_tooltips_disable(GtkTooltips *tooltips);

  使一套tooltips生效.


void gtk_tooltips_set_delay(GtkTooltips *tooltips,
gint delay);

  设定要停留多少ms, tooltip才会出现. 内定值是1000ms, 即一秒.


voidgtk_tooltips_set_tips (GtkTooltips *tooltips,
GtkWidget*widget,
gchar*tips_text);

  改变一个tooltip的文字内容.


void gtk_tooltips_set_colors (GtkTooltips *tooltips,
GdkColor*background,
GdkColor*foreground);

  设定tooltips的前景及背景颜色.

  8. Container物件

  8.1 笔记本物件

  笔记本物件好几个"页"的集合, 它们互相交叠在一起, 并可包含不同的讯息. 这个物件在GUI越来越普及, 它是个在显示有类同功能的资讯时很有用的物件.

  第一个您会用到的是产生一个新的笔记本物件.


GtkWidget* gtk_notebook_new (void);

  一旦物件产生後, 共有12个函数可以操作该笔记本物件. 我们一个一个来看.

  第一个是要如何来安排"页标签". 这些"页标签"或"tabs", 可以用四个位置, 上, 下, 左, 右.


void gtk_notebook_set_tab_pos (GtkNotebook *notebook, GtkPositionType pos);

  GtkPostionType可以是以下四个, 很好认.


GTK_POS_LEFT
GTK_POS_RIGHT
GTK_POS_TOP
GTK_POS_BOTTOM

  GTK_POS_TOP是内定值.

  接下来我们来看如何加"一页"到笔记本上. 共有三种方法来加页到笔记本上.


void gtk_notebook_append_page
(GtkNotebook *notebook, GtkWidget *child, GtkWidget *tab_label);
void gtk_notebook_prepend_page
(GtkNotebook *notebook, GtkWidget *child, GtkWidget *tab_label);

  这些函数新增一页到笔记本, append由後新增, prepend由前新增. *child是要插入笔记本的物件, *tab_label是页标签.


void gtk_notebook_insert_page
(GtkNotebook *notebook, GtkWidget *child, GtkWidget *tab_label, gint position);

  参数与_append_及_prepend_相同, 除了多出一个参数, 位置. 该参数用来指定要插在那里.

  现在我们知道要如何新增一页, 再来看看如何移除.


void gtk_notebook_remove_page (GtkNotebook *notebook, gint page_num);

  这个函数移除掉所指定的那一页.

  要找出目前正在那一页, 可用以下函数:


gint gtk_notebook_current_page (GtkNotebook *notebook);

  以下两个函数是向前或向後移动. 若目前在最後一页, 而您用gtk_notebook_next_page, 那麽笔记本会绕回第一页, 反之亦然.


void gtk_notebook_next_page (GtkNoteBook *notebook);
void gtk_notebook_prev_page (GtkNoteBook *notebook);

  以下函数设定"有效页". 如果您希望笔记本开启到例如第五页, 您可以用这个函数. 内定页为第一页.


void gtk_notebook_set_page (GtkNotebook *notebook, gint page_num);

  以下两个函数可新增及移除页标签及边框.


void gtk_notebook_set_show_tabs (GtkNotebook *notebook, gint show_tabs);
void gtk_notebook_set_show_border (GtkNotebook *notebook, gint show_border);
show_tabs及show_border可以是TRUE或FALSE(0或1).

  现在我们来看个范例, 它是从testgtk.c中展开的, 用了所有13个函数. 该程式产生一个笔记本及六个按钮, 包含11页, 以三种方式加页, appended, inserted,及prepended. 这些按钮允许您旋转页标签位置, 新增/移除页标签及边框, 移除一页, 以前向及後向改变页的位置, 及离开
程式.


#include
/* This function rotates the position of the tabs */
void rotate_book (GtkButton *button, GtkNotebook *notebook)
{
gtk_notebook_set_tab_pos (notebook, (notebook->tab_pos +1) %4);
}
/* Add/Remove the page tabs and the borders */
void tabsborder_book (GtkButton *button, GtkNotebook *notebook)
{
gint tval = FALSE;
gint bval = FALSE;
if (notebook->show_tabs == 0)
tval = TRUE;
if (notebook->show_border == 0)
bval = TRUE;
gtk_notebook_set_show_tabs (notebook, tval);
gtk_notebook_set_show_border (notebook, bval);
}
/* Remove a page from the notebook */
void remove_book (GtkButton *button, GtkNotebook *notebook)
{
gint page;
page = gtk_notebook_current_page(notebook);
gtk_notebook_remove_page (notebook, page);
/* Need to refresh the widget --
This forces the widget to redraw itself. */
gtk_widget_draw(GTK_WIDGET(notebook), NULL);
}
void destroy (GtkWidget *widget, gpointer *data)
{
gtk_main_quit ();
}
int main (int argc, char *argv[])
{
GtkWidget *window;
GtkWidget *button;
GtkWidget *table;
GtkWidget *notebook;
GtkWidget *frame;
GtkWidget *label;
GtkWidget *checkbutton;
int i;
char bufferf[32];
char bufferl[32];
gtk_init (&argc, &argv);
window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
gtk_signal_connect (GTK_OBJECT (window), "destroy",
GTK_SIGNAL_FUNC (destroy), NULL);
gtk_container_border_width (GTK_CONTAINER (window), 10);
table = gtk_table_new(2,6,TRUE);
gtk_container_add (GTK_CONTAINER (window), table);
/* Create a new notebook, place the position of the tabs */
notebook = gtk_notebook_new ();
gtk_notebook_set_tab_pos (GTK_NOTEBOOK (notebook), GTK_POS_TOP);
gtk_table_attach_defaults(GTK_TABLE(table), notebook, 0,6,0,1);
gtk_widget_show(notebook);
/* lets append a bunch of pages to the notebook */
for (i=0; i < 5; i++) {
sprintf(bufferf, "Append Frame %d", i+1);
sprintf(bufferl, "Page %d", i+1);
frame = gtk_frame_new (bufferf);
gtk_container_border_width (GTK_CONTAINER (frame), 10);
gtk_widget_set_usize (frame, 100, 75);
gtk_widget_show (frame);
label = gtk_label_new (bufferf);
gtk_container_add (GTK_CONTAINER (frame), label);
gtk_widget_show (label);
label = gtk_label_new (bufferl);
gtk_notebook_append_page (GTK_NOTEBOOK (notebook), frame, label);
}
/* now lets add a page to a specific spot */
checkbutton = gtk_check_button_new_with_label ("Check me please!");
gtk_widget_set_usize(checkbutton, 100, 75);
gtk_widget_show (checkbutton);
label = gtk_label_new ("Add spot");
gtk_container_add (GTK_CONTAINER (checkbutton), label);
gtk_widget_show (label);
label = gtk_label_new ("Add page");
gtk_notebook_insert_page (GTK_NOTEBOOK (notebook), checkbutton, label, 2);
/* Now finally lets prepend pages to the notebook */
for (i=0; i < 5; i++) {
sprintf(bufferf, "Prepend Frame %d", i+1);
sprintf(bufferl, "PPage %d", i+1);
frame = gtk_frame_new (bufferf);
gtk_container_border_width (GTK_CONTAINER (frame), 10);
gtk_widget_set_usize (frame, 100, 75);
gtk_widget_show (frame);
label = gtk_label_new (bufferf);
gtk_container_add (GTK_CONTAINER (frame), label);
gtk_widget_show (label);
label = gtk_label_new (bufferl);
gtk_notebook_prepend_page (GTK_NOTEBOOK(notebook), frame, label);
}
/* Set what page to start at (page 4) */
gtk_notebook_set_page (GTK_NOTEBOOK(notebook), 3);
/* create a bunch of buttons */
button = gtk_button_new_with_label ("close");
gtk_signal_connect_object (GTK_OBJECT (button), "clicked",
GTK_SIGNAL_FUNC (destroy), NULL);
gtk_table_attach_defaults(GTK_TABLE(table), button, 0,1,1,2);
gtk_widget_show(button);
button = gtk_button_new_with_label ("next page");
gtk_signal_connect_object (GTK_OBJECT (button), "clicked",
(GtkSignalFunc) gtk_notebook_next_page,
GTK_OBJECT (notebook));
gtk_table_attach_defaults(GTK_TABLE(table), button, 1,2,1,2);
gtk_widget_show(button);
button = gtk_button_new_with_label ("prev page");
gtk_signal_connect_object (GTK_OBJECT (button), "clicked",
(GtkSignalFunc) gtk_notebook_prev_page,
GTK_OBJECT (notebook));
gtk_table_attach_defaults(GTK_TABLE(table), button, 2,3,1,2);
gtk_widget_show(button);
button = gtk_button_new_with_label ("tab position");
gtk_signal_connect_object (GTK_OBJECT (button), "clicked",
(GtkSignalFunc) rotate_book, GTK_OBJECT(notebook));
gtk_table_attach_defaults(GTK_TABLE(table), button, 3,4,1,2);
gtk_widget_show(button);
button = gtk_button_new_with_label ("tabs/border on/off");
gtk_signal_connect_object (GTK_OBJECT (button), "clicked",
(GtkSignalFunc) tabsborder_book,
GTK_OBJECT (notebook));
gtk_table_attach_defaults(GTK_TABLE(table), button, 4,5,1,2);
gtk_widget_show(button);
button = gtk_button_new_with_label ("remove page");
gtk_signal_connect_object (GTK_OBJECT (button), "clicked",
(GtkSignalFunc) remove_book,
GTK_OBJECT(notebook));
gtk_table_attach_defaults(GTK_TABLE(table), button, 5,6,1,2);
gtk_widget_show(button);
gtk_widget_show(table);
gtk_widget_show(window);
gtk_main ();
return 0;
}

  8.2 卷动视窗

  卷动视窗是用来产生在视窗内可卷动的区域. 您可以在卷动视窗中插入任意种物件, 而不管视窗大小如何, 这些物件因为在卷动区域内, 因此都可以被用到.

  您可以用以下函数来产生卷动视窗:


GtkWidget* gtk_scrolled_window_new (GtkAdjustment *hadjustment,
GtkAdjustment *vadjustment);

  第一个参数是水平调整方向, 第二个是垂直调整方向. 它们一般被设为NULL.


void gtk_scrolled_window_set_policy(GtkScrolledWindow *scrolled_window,
GtkPolicyTypehscrollbar_policy,
GtkPolicyTypevscrollbar_policy);

  第一个参数是想要改变的视窗. 第二个是设定水平卷动的方式, 第三个是垂直卷动的方式.

  policy可以是GTK_POLICY_AUTOMATIC, 或GTK_POLICY_ALWAYS. GTK_POLICY_AUTOMATIC会自动决定是否使用scrollbars. GTK_POLICY_ALWAYS则scrollbars始终在那里.

  这里是个将100个双态按钮包进一个卷动视窗的范例.


#include
void destroy(GtkWidget *widget, gpointer *data)
{
gtk_main_quit();
}
int main (int argc, char *argv[])
{
static GtkWidget *window;
GtkWidget *scrolled_window;
GtkWidget *table;
GtkWidget *button;
char buffer[32];
int i, j;
gtk_init (&argc, &argv);
/* Create a new dialog window for the scrolled window to be
* packed into.A dialog is just like a normal window except it has a
* vbox and a horizontal seperator packed into it.It's just a shortcut
* for creating dialogs */
window = gtk_dialog_new ();
gtk_signal_connect (GTK_OBJECT (window), "destroy",
(GtkSignalFunc) destroy, NULL);
gtk_window_set_title (GTK_WINDOW (window), "dialog");
gtk_container_border_width (GTK_CONTAINER (window), 0);
/* create a new scrolled window. */
scrolled_window = gtk_scrolled_window_new (NULL, NULL);
gtk_container_border_width (GTK_CONTAINER (scrolled_window), 10);
/* the policy is one of GTK_POLICY AUTOMATIC, or GTK_POLICY_ALWAYS.
* GTK_POLICY_AUTOMATIC will automatically decide whether you need
* scrollbars, wheras GTK_POLICY_ALWAYS will always leave the scrollbars
* there.The first one is the horizontal scrollbar, the second,
* the vertical. */
gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_window),
GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS);
/* The dialog window is created with a vbox packed into it. */
gtk_box_pack_start (GTK_BOX (GTK_DIALOG(window)->vbox), scrolled_window,
TRUE, TRUE, 0);
gtk_widget_show (scrolled_window);
/* create a table of 10 by 10 squares. */
table = gtk_table_new (10, 10, FALSE);
/* set the spacing to 10 on x and 10 on y */
gtk_table_set_row_spacings (GTK_TABLE (table), 10);
gtk_table_set_col_spacings (GTK_TABLE (table), 10);
/* pack the table into the scrolled window */
gtk_container_add (GTK_CONTAINER (scrolled_window), table);
gtk_widget_show (table);
/* this simply creates a grid of toggle buttons on the table
* to demonstrate the scrolled window. */
for (i = 0; i < 10; i++)
for (j = 0; j < 10; j++) {
sprintf (buffer, "button (%d,%d)
", i, j);
button = gtk_toggle_button_new_with_label (buffer);
gtk_table_attach_defaults (GTK_TABLE (table), button,
i, i+1, j, j+1);
gtk_widget_show (button);
}
/* Add a "close" button to the bottom of the dialog */
button = gtk_button_new_with_label ("close");
gtk_signal_connect_object (GTK_OBJECT (button), "clicked",
(GtkSignalFunc) gtk_widget_destroy,
GTK_OBJECT (window));
/* this makes it so the button is the default. */
GTK_WIDGET_SET_FLAGS (button, GTK_CAN_DEFAULT);
gtk_box_pack_start (GTK_BOX (GTK_DIALOG (window)->action_area),
button, TRUE, TRUE, 0);
/* This grabs this button to be the default button. Simply hitting
* the "Enter" key will cause this button to activate. */
gtk_widget_grab_default (button);
gtk_widget_show (button);
gtk_widget_show (window);
gtk_main();
return(0);
}

  玩弄一下这个视窗. 您会看到scrollbars如何反应. 您也会想用用gtk_widget_set_usize()来设定视窗内定的大小.

  9. EventBox视窗物件

  这只在gtk+970916.tar.gz以後的版本才有.

  有些gtk物件并没有相关联的视窗, 它们是由其parent所画出来的. 因此, 他们不能收到事件. 如果它们大小不对, 他们无法收到事件来修正. 如果您需要这样的功能, 那麽EventBox就是您想要的.

  初看之下, EventBox物件看来好像毫无用途. 它在萤幕上什麽事也不做, 也不画, 对事件也不反应. 不过, 它倒提供一项功能 - 他提供一个X window来服务其子物件. 这很重要, 因为GTK物件很多都跟X window不相关联. 不用X window省下记忆体并加快其速度, 但也有其缺点. 一个
物件没有X window无法接收事件, 而且无法裁切其内容. 虽然它叫``EventBox''强调其事件处理功能, 这个物件也可用来做裁切.

  要产生一个EventBox物件, 使用:


GtkWidget* gtk_event_box_new (void);

  一个子视窗物件可被加到EventBox之下:


gtk_container_add (GTK_CONTAINER(event_box), widget);

  以下的简单示范, 使用了一个EventBox - 一个标题, 并且设定成滑鼠在标题上点一下程式就会离开.


#include
int
main (int argc, char *argv[])
{
GtkWidget *window;
GtkWidget *event_box;
GtkWidget *label;
gtk_init (&argc, &argv);
window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
gtk_window_set_title (GTK_WINDOW (window), "Event Box");
gtk_signal_connect (GTK_OBJECT (window), "destroy",
GTK_SIGNAL_FUNC (gtk_exit), NULL);
gtk_container_border_width (GTK_CONTAINER (window), 10);
/* 产生一个EventBox并加到其上层的视窗 */
event_box = gtk_event_box_new ();
gtk_container_add (GTK_CONTAINER(window), event_box);
gtk_widget_show (event_box);
/* 产生一个长标题 */
label = gtk_label_new ("Click here to quit, quit, quit, quit, quit");
gtk_container_add (GTK_CONTAINER (event_box), label);
gtk_widget_show (label);
/* 把它裁短 */
gtk_widget_set_usize (label, 110, 20);
/* And bind an action to it */
gtk_widget_set_events (event_box, GDK_BUTTON_PRESS_MASK);
gtk_signal_connect (GTK_OBJECT(event_box), "button_press_event",
GTK_SIGNAL_FUNC (gtk_exit), NULL);
/* 还有一件事, 要X window来处理 ... */
gtk_widget_realize (event_box);
gdk_window_set_cursor (event_box->window, gdk_cursor_new (GDK_HAND1));
gtk_widget_show (window);
gtk_main ();
return 0;
}

  10. 其它物件

  10.1 标签

  标签在GTK中用得很多, 而且很简单. 标签不送信号, 因为它们跟X window没有关系. 如果您要接取信号, 或裁切, 可用EventBox物件.

  产生新的标签可用:


GtkWidget* gtk_label_new (char *str);

  唯一个参数是您想要显示的文字.

  在产生标签後要改变其文字, 可用:


void gtk_label_set (GtkLabel*label,
char*str);

  第一个参数是刚才所产生的标签(使用GTK_LABEL巨集来分派), 第二个是新的字串.

  新字串的空间会自动被配置.

  要取得目前的字串可用:


void gtk_label_get (GtkLabel*label,
char **str);

  第一个参数是标签, 第二个是返回字串的位置.

  10.2 Progress Bars

  Progress bars是用来显示某个作业的操作状态. 他们很容易使用, 您会看到以下的程式. 我们先来产生一个Progress Bar.


GtkWidget *gtk_progress_bar_new (void);

  这样就产生了, 够简单的了.


void gtk_progress_bar_update (GtkProgressBar *pbar, gfloat percentage);

  第一个参数是您要操作的Progress Bar, 第二个是完成度, 其值为0-1.

  Progress Bars一般与timeouts及其它函数一起使用, (see section on Timeouts, I/O and Idle Functions) 这是因为多工的考量. gtk_progress_bar_update会处理这方面的事务.

  这里是使用Progress Bar的范例, 并用timeouts来更新. 同时也会展示如何重设Progress Bar.


#include
static int ptimer = 0;
int pstat = TRUE;
/* This function increments and updates the progress bar, it also resets
the progress bar if pstat is FALSE */
gint progress (gpointer data)
{
gfloat pvalue;
/* get the current value of the progress bar */
pvalue = GTK_PROGRESS_BAR (data)->percentage;
if ((pvalue >= 1.0)' '(pstat == FALSE)) {
pvalue = 0.0;
pstat = TRUE;
}
pvalue += 0.01;
gtk_progress_bar_update (GTK_PROGRESS_BAR (data), pvalue);
return TRUE;
}
/* This function signals a reset of the progress bar */
void progress_r (void)
{
pstat = FALSE;
}
void destroy (GtkWidget *widget, gpointer *data)
{
gtk_main_quit ();
}
int main (int argc, char *argv[])
{
GtkWidget *window;
GtkWidget *button;
GtkWidget *label;
GtkWidget *table;
GtkWidget *pbar;
gtk_init (&argc, &argv);
window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
gtk_signal_connect (GTK_OBJECT (window), "destroy",
GTK_SIGNAL_FUNC (destroy), NULL);
gtk_container_border_width (GTK_CONTAINER (window), 10);
table = gtk_table_new(3,2,TRUE);
gtk_container_add (GTK_CONTAINER (window), table);
label = gtk_label_new ("Progress Bar Example");
gtk_table_attach_defaults(GTK_TABLE(table), label, 0,2,0,1);
gtk_widget_show(label);
/* Create a new progress bar, pack it into the table, and show it */
pbar = gtk_progress_bar_new ();
gtk_table_attach_defaults(GTK_TABLE(table), pbar, 0,2,1,2);
gtk_widget_show (pbar);
/* Set the timeout to handle automatic updating of the progress bar */
ptimer = gtk_timeout_add (100, progress, pbar);
/* This button signals the progress bar to be reset */
button = gtk_button_new_with_label ("Reset");
gtk_signal_connect (GTK_OBJECT (button), "clicked",
GTK_SIGNAL_FUNC (progress_r), NULL);
gtk_table_attach_defaults(GTK_TABLE(table), button, 0,1,2,3);
gtk_widget_show(button);
button = gtk_button_new_with_label ("Cancel");
gtk_signal_connect (GTK_OBJECT (button), "clicked",
GTK_SIGNAL_FUNC (destroy), NULL);
gtk_table_attach_defaults(GTK_TABLE(table), button, 1,2,2,3);
gtk_widget_show (button);
gtk_widget_show(table);
gtk_widget_show(window);
gtk_main ();
return 0;
}

  在这个小程式中有四个区域在一般的Progress Bar操作上, 我们会一个一个看到.


pbar = gtk_progress_bar_new ();

  产生Progress Bar, pbar.


ptimer = gtk_timeout_add (100, progress, pbar);

  使用timeouts来产生一个固定时间间隔, Progress Bar不见的一定要用timeouts.


pvalue = GTK_PROGRESS_BAR (data)->percentage;

  这行指定目前的值.


gtk_progress_bar_update (GTK_PROGRESS_BAR (data), pvalue);

  最後, 这行更新Progress Bar的值.

  这就是Progress Bars, enjoy.

  10.3 对话盒

  对话盒物件很简单, 是个预先做好的视窗. 对话盒的结构如下:


struct GtkDialog
{
GtkWindow window;
GtkWidget *vbox;
GtkWidget *action_area;
};

  您看到, 它就是产生一个新的视窗. 然後包一个vbox到它上面, 接著一个seperator, 然後是hbox给"action_area".

  对话盒是用於通告讯息, 及类似用途. 这很基本, 只有一个函数:


GtkWidget* gtk_dialog_new (void);

  因此要产生新的对话盒,


GtkWidget window;
window = gtk_dialog_new ();

  这会产生对话盒, 然後您可以任意使用它. 然後将按钮包装到action_area, 像这样:


button = ...
gtk_box_pack_start (GTK_BOX (GTK_DIALOG (window)->action_area), button,
TRUE, TRUE, 0);
gtk_widget_show (button);

  然後您也可以用封装新增一个vbox, 例如, 一个新标签, 试试看:


label = gtk_label_new ("Dialogs are groovy");
gtk_box_pack_start (GTK_BOX (GTK_DIALOG (window)->vbox), label, TRUE,
TRUE, 0);
gtk_widget_show (label);

  做为一个对话盒的范例, 你可以使用两个按钮在action_area, 一个Cancel及Ok按钮, 及一个标签在vbox area, 问使用者一个问题或提示错误的发生等等. 然後您可以接到不同的信号上来处理使用者的选择.

  10.4 Pixmaps

  Undocumented.

  10.5 Images

  Undocumented.

  11. 档案选取物件

  档案选取物件是个又快又简单的方法来产生一个File dialog box. 它有Ok, Cancel, 及Help按钮, 可以大量缩短开发时间.

  要产生一个新的档案选取物件可用:


GtkWidget* gtk_file_selection_new (gchar *title);

  要设定档名, 例如指定目录, 或给定内定档名, 可用这个函数:


void gtk_file_selection_set_filename (GtkFileSelection *filesel,
gchar *filename);

  要取得使用者输入的名称, 可用以下函数:


gchar* gtk_file_selection_get_filename (GtkFileSelection *filesel);

  另外还有指标指向档案选取物件的内容:


dir_list
file_list
selection_entry
selection_text
main_vbox
ok_button
cancel_button
help_button

  当然了您会想要用ok_button, cancel_button, 及help_button指标用来处理信号.

  在这里包含了从testgtk.c偷来的一个范例, 修改成自己的版本. 在此您可以看到, 要产生一个档案选取物件不需要做太多事. 在此, 在这个范例中, Help button显示在萤幕中, 它没做什麽事, 因为没有信号接在上面.


#include
/* 取得选取的档名并显示在萤幕上 */
void file_ok_sel (GtkWidget *w, GtkFileSelection *fs)
{
g_print ("%s
", gtk_file_selection_get_filename (GTK_FILE_SELECTION (fs)));
}
void destroy (GtkWidget *widget, gpointer *data)
{
gtk_main_quit ();
}
int main (int argc, char *argv[])
{
GtkWidget *filew;
gtk_init (&argc, &argv);
/* 产生新的档案选取物件 */
filew = gtk_file_selection_new ("File selection");
gtk_signal_connect (GTK_OBJECT (filew), "destroy",
(GtkSignalFunc) destroy, &filew);
/* 把ok_button接到file_ok_sel功能 */
gtk_signal_connect (GTK_OBJECT (GTK_FILE_SELECTION (filew)->ok_button),
"clicked", (GtkSignalFunc) file_ok_sel, filew );
/* 把cancel_button接到destroy物件 */
gtk_signal_connect_object (GTK_OBJECT (GTK_FILE_SELECTION
(filew)->cancel_button),
"clicked", (GtkSignalFunc) gtk_widget_destroy,
GTK_OBJECT (filew));
/* 设定档名, 就像是要存一个档案一样, 而我们是给定一个内定档名 */
gtk_file_selection_set_filename (GTK_FILE_SELECTION(filew),
"penguin.png");
gtk_widget_show(filew);
gtk_main ();
return 0;
}

  12. List物件

  GtkList物件被设计成是个vertical container, 而在其中的物件必须是GtkListItem.

  GtkList物件有其自己的视窗用来接取事件, 而其背景色一般是白色的. 由於它是由GtkContainer而来, 您也可以用 GTK_CONTAINER(List)巨集来处理. 请见GtkContainer物件一章. 您应该已经熟悉GList的用法, 及其相关函数 g_list_*(), 这样您才不会在此遭遇到问题.

  在GtkList物件有一栏对我们来说很重要.


struct _GtkList
{
[...]
GList *selection;
guint selection_mode;
[...]
};

  GtkList的selection栏指向一个所有items的link list, 其中记录所有被记录的项目, 若为`NULL'则 selection为空的. 因此要知道目前的selection, 我们可以读取GTK_LIST()->selection一栏. 但不要修改它们, 因为它们是由内部所维护.

  GtkList的selection_mode决定selection的机制, 而GTK_LIST()->selection栏的内容为:

  selection_mode可以是以下其中一种:

  GTK_SELECTION_SINGLE selection可以是`NULL' 或对一个已选项目, 包含一个GList* pointer.

  GTK_SELECTION_BROWSE 若list没有有效的物件, selection可以是`NULL' 否则它会包含一个GList* pointer, 而且就是一个list item.

  GTK_SELECTION_MULTIPLE 若list中没有item被选取, selection可以是`NULL' 否则会有一个GList* pointer, 并且指向第一个selected item. 并一直向後接到第二个...

  GTK_SELECTION_EXTENDED selection永远为`NULL'.

  内定为GTK_SELECTION_MULTIPLE.

  12.1 信号


void GtkList::selection_changed (GtkList *LIST)

  当selection区域改变的时候, 这个信号会被触发. 这会在当GtkList的子物件被select或unselect时发生.


void GtkList::select_child (GtkList *LIST, GtkWidget *CHILD)

  当GtkList的子物件被select时, 这个信号会被触发. 这一般在gtk_list_select_item(), gtk_list_select_child(), 按钮被按下及有时间接触发或有子物件新增或移除时发生.


void GtkList::unselect_child (GtkList *LIST, GtkWidget *CHILD)

  当GtkList的子物件被unselect时, 这个信号会被触发. 这一般在gtk_list_unselect_item(), gtk_list_unselect_child(), 按钮被按下及有时间接触发或有子物件新增或移除时发生.

  12.2 函数


guint gtk_list_get_type (void)

  返回`GtkList' type identifier.


GtkWidget* gtk_list_new (void)

  产生新的`GtkList' object. 新的物件其返回值为`GtkWidget' object的指标. `NULL'表示失败.


void gtk_list_insert_items (GtkList *LIST, GList *ITEMS, gint POSITION)

  插入list items到LIST里面, 由POSITION开始. ITEMS是双向链结串列. 每个项目要指向一个产生出来的GtkListItem.


void gtk_list_append_items (GtkList *LIST, GList *ITEMS)

  就像gtk_list_insert_items()一样插入ITEMS到LIST後面.


void gtk_list_prepend_items (GtkList *LIST, GList *ITEMS)

  就如gtk_list_insert_items()一样插入ITEMS到LIST前面.


void gtk_list_remove_items (GtkList *LIST, GList *ITEMS)

  从LIST中移除list items. ITEMS是双向链结串列, 每个node要指向child. 设计者要自行呼叫g_list_free(ITEMS). 设计者也要自行处理掉list items.


void gtk_list_clear_items (GtkList *LIST, gint START, gint END)

  从LIST中移除并销毁list items.


void gtk_list_select_item (GtkList *LIST, gint ITEM)

  透过在LIST中目前的位置,触发GtkList::select_child信号给指定的list item.


void gtk_list_unselect_item (GtkList *LIST, gint ITEM)

  透过在LIST中目前的位置,触发GtkList::unselect_child信号给指定的list item.


void gtk_list_select_child (GtkList *LIST, GtkWidget *CHILD)

  触发GtkList::select_child信号给指定的CHILD.


void gtk_list_unselect_child (GtkList *LIST, GtkWidget *CHILD)

  触发GtkList::unselect_child信号给指定的CHILD.


gint gtk_list_child_position (GtkList *LIST, GtkWidget *CHILD)

  返回CHILD在LIST中的位置. `-1'为失败.


void gtk_list_set_selection_mode (GtkList *LIST, GtkSelectionMode MODE)

  设定LIST到选择模式MODE, 可以是GTK_SELECTION_SINGLE, GTK_SELECTION_BROWSE, GTK_SELECTION_MULTIPLE 或 GTK_SELECTION_EXTENDED.


GtkList* GTK_LIST (gpointer OBJ)

  传一个generic pointer到`GtkList*'. *Note Standard Macros::, for more info.


GtkListClass* GTK_LIST_CLASS (gpointer CLASS)

  传一个generic pointer到`GtkListClass*'. *Note Standard Macros::, for more info.


gint GTK_IS_LIST (gpointer OBJ)

  决定是否一个generic pointer对应到`GtkList' object. *Note Standard Macros::, for more info.

  12.3 范例

  以下是个范例程式, 将会列出GtkList的选择改变, 并让您用滑鼠右键"逮捕"list items.


/* compile this program with:
* $ gcc -I/usr/local/include/ -lgtk -lgdk -lglib -lX11 -lm -Wall main.c
*/
/* include the gtk+ header files
* include stdio.h, we need that for the printf() function
*/
#include
#include
/* this is our data identification string to store
* data in list items
*/
constgchar*list_item_data_key="list_item_data";
/* prototypes for signal handler that we are going to connect
* to the GtkList widget
*/
staticvoidsigh_print_selection(GtkWidget*gtklist,
gpointerfunc_data);
staticvoidsigh_button_event(GtkWidget*gtklist,
GdkEventButton *event,
GtkWidget*frame);
/* main function to set up the user interface */
gint main (int argc, gchar *argv[])
{
GtkWidget*separator;
GtkWidget*window;
GtkWidget*vbox;
GtkWidget*scrolled_window;
GtkWidget*frame;
GtkWidget*gtklist;
GtkWidget*button;
GtkWidget*list_item;
GList*dlist;
guinti;
gcharbuffer[64];
/* initialize gtk+ (and subsequently gdk) */
gtk_init(&argc, &argv);
/* create a window to put all the widgets in
* connect gtk_main_quit() to the "destroy" event of
* the window to handle window manager close-window-events
*/
window=gtk_window_new(GTK_WINDOW_TOPLEVEL);
gtk_window_set_title(GTK_WINDOW(window), "GtkList Example");
gtk_signal_connect(GTK_OBJECT(window),
"destroy",
GTK_SIGNAL_FUNC(gtk_main_quit),
NULL);
/* inside the window we need a box to arrange the widgets
* vertically */
vbox=gtk_vbox_new(FALSE, 5);
gtk_container_border_width(GTK_CONTAINER(vbox), 5);
gtk_container_add(GTK_CONTAINER(window), vbox);
gtk_widget_show(vbox);
/* this is the scolled window to put the GtkList widget inside */
scrolled_window=gtk_scrolled_window_new(NULL, NULL);
gtk_widget_set_usize(scrolled_window, 250, 150);
gtk_container_add(GTK_CONTAINER(vbox), scrolled_window);
gtk_widget_show(scrolled_window);
/* create the GtkList widget
* connect the sigh_print_selection() signal handler
* function to the "selection_changed" signal of the GtkList
* to print out the selected items each time the selection
* has changed */
gtklist=gtk_list_new();
gtk_container_add(GTK_CONTAINER(scrolled_window), gtklist);
gtk_widget_show(gtklist);
gtk_signal_connect(GTK_OBJECT(gtklist),
"selection_changed",
GTK_SIGNAL_FUNC(sigh_print_selection),
NULL);
/* we create a "Prison" to put a list item in ;)
*/
frame=gtk_frame_new("Prison");
gtk_widget_set_usize(frame, 200, 50);
gtk_container_border_width(GTK_CONTAINER(frame), 5);
gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_OUT);
gtk_container_add(GTK_CONTAINER(vbox), frame);
gtk_widget_show(frame);
/* connect the sigh_button_event() signal handler to the GtkList
* wich will handle the "arresting" of list items
*/
gtk_signal_connect(GTK_OBJECT(gtklist),
"button_release_event",
GTK_SIGNAL_FUNC(sigh_button_event),
frame);
/* create a separator
*/
separator=gtk_hseparator_new();
gtk_container_add(GTK_CONTAINER(vbox), separator);
gtk_widget_show(separator);
/* finaly create a button and connect it愀 "clicked" signal
* to the destroyment of the window
*/
button=gtk_button_new_with_label("Close");
gtk_container_add(GTK_CONTAINER(vbox), button);
gtk_widget_show(button);
gtk_signal_connect_object(GTK_OBJECT(button),
"clicked",
GTK_SIGNAL_FUNC(gtk_widget_destroy),
GTK_OBJECT(window));
/* now we create 5 list items, each having it愀 own
* label and add them to the GtkList using gtk_container_add()
* also we query the text string from the label and
* associate it with the list_item_data_key for each list item
*/
for (i=0; i<5; i++) {
GtkWidget*label;
gchar*string;
sprintf(buffer, "ListItemContainer with Label #%d", i);
label=gtk_label_new(buffer);
list_item=gtk_list_item_new();
gtk_container_add(GTK_CONTAINER(list_item), label);
gtk_widget_show(label);
gtk_container_add(GTK_CONTAINER(gtklist), list_item);
gtk_widget_show(list_item);
gtk_label_get(GTK_LABEL(label), &string);
gtk_object_set_data(GTK_OBJECT(list_item),
list_item_data_key,
string);
}
/* here, we are creating another 5 labels, this time
* we use gtk_list_item_new_with_label() for the creation
* we can憩 query the text string from the label because
* we don憩 have the labels pointer and therefore
* we just associate the list_item_data_key of each
* list item with the same text string
* for adding of the list items we put them all into a doubly
* linked list (GList), and then add them by a single call to
* gtk_list_append_items()
* because we use g_list_prepend() to put the items into the
* doubly linked list, their order will be descending (instead
* of ascending when using g_list_append())
*/
dlist=NULL;
for (; i<10; i++) {
sprintf(buffer, "List Item with Label %d", i);
list_item=gtk_list_item_new_with_label(buffer);
dlist=g_list_prepend(dlist, list_item);
gtk_widget_show(list_item);
gtk_object_set_data(GTK_OBJECT(list_item),
list_item_data_key,
"ListItem with integrated Label");
}
gtk_list_append_items(GTK_LIST(gtklist), dlist);
/* finaly we want to see the window, don憩 we? ;)
*/
gtk_widget_show(window);
/* fire up the main event loop of gtk
*/
gtk_main();
/* we get here after gtk_main_quit() has been called which
* happens if the main window gets destroyed
*/
return 0;
}
/* this is the signal handler that got connected to button
* press/release events of the GtkList
*/
void
sigh_button_event(GtkWidget*gtklist,
GdkEventButton *event,
GtkWidget*frame)
{
/* we only do something if the third (rightmost mouse button
* was released
*/
if (event->type==GDK_BUTTON_RELEASE &&
event->button==3) {
GList*dlist, *free_list;
GtkWidget*new_prisoner;
/* fetch the currently selected list item which
* will be our next prisoner ;)
*/
dlist=GTK_LIST(gtklist)->selection;
if (dlist)
new_prisoner=GTK_WIDGET(dlist->data);
else
new_prisoner=NULL;
/* look for already prisoned list items, we
* will put them back into the list
* remember to free the doubly linked list that
* gtk_container_children() returns
*/
dlist=gtk_container_children(GTK_CONTAINER(frame));
free_list=dlist;
while (dlist) {
GtkWidget*list_item;
list_item=dlist->data;
gtk_widget_reparent(list_item, gtklist);
dlist=dlist->next;
}
g_list_free(free_list);
/* if we have a new prisoner, remove him from the
* GtkList and put him into the frame "Prison"
* we need to unselect the item before
*/
if (new_prisoner) {
GListstatic_dlist;
static_dlist.data=new_prisoner;
static_dlist.next=NULL;
static_dlist.prev=NULL;
gtk_list_unselect_child(GTK_LIST(gtklist),
new_prisoner);
gtk_widget_reparent(new_prisoner, frame);
}
}
}
/* this is the signal handler that gets called if GtkList
* emits the "selection_changed" signal
*/
void
sigh_print_selection(GtkWidget*gtklist,
gpointerfunc_data)
{
GList*dlist;
/* fetch the doubly linked list of selected items
* of the GtkList, remember to treat this as read-only!
*/
dlist=GTK_LIST(gtklist)->selection;
/* if there are no selected items there is nothing more
* to do than just telling the user so
*/
if (!dlist) {
g_print("Selection cleared
");
return;
}
/* ok, we got a selection and so we print it
*/
g_print("The selection is a ");
/* get the list item from the doubly linked list
* and then query the data associated with list_item_data_key
* we then just print it
*/
while (dlist) {
GtkObject*list_item;
gchar*item_data_string;
list_item=GTK_OBJECT(dlist->data);
item_data_string=gtk_object_get_data(list_item,
list_item_data_key);
g_print("%s ", item_data_string);
dlist=dlist->next;
}
g_print("
");
}

  12.4 List Item物件

  GtkListItem物件是设计用来做为container的子物件, 用来提供selection/deselection的功能.

  GtkListItem有自己的视窗来接收事件并有其自身的背景颜色, 一般是白色的.

  因为是由GtkItem而来的, 它也可以用GTK_ITEM(ListItem)巨集. 一般GtkListItem只有一个标签, 用来记录例如一个档名. 另外还有一个很好用的函数gtk_list_item_new_with_label(). 若您不想加GtkLabel到 GtkListItem, 也可以加GtkVBox或GtkArrow.

  12.5 信号

  GtkListItem不产生自己的新的信号, 但它继承GtkItem的信号.

  12.6 函数


guint gtk_list_item_get_type (void)

  返回`GtkListItem' type identifier.


GtkWidget* gtk_list_item_new (void)

  产生新的`GtkListItem' object. 新物件返回一个指标给`GtkWidget'物件. `NULL'表示错误.


GtkWidget* gtk_list_item_new_with_label (gchar *LABEL)

  产生新的`GtkListItem'物件, 并带一个标签. 并返回一个`GtkWidget' object. `NULL'表示错误.


void gtk_list_item_select (GtkListItem *LIST_ITEM)

  这个函数基本上是将gtk_item_select (GTK_ITEM (list_item))包装起来. 它将会送GtkItem::select信号. *Note GtkItem::, for more info.


void gtk_list_item_deselect (GtkListItem *LIST_ITEM)

  这个函数基本上是将gtk_item_deselect (GTK_ITEM (list_item))包装起来. 它将会送GtkItem::deselect信号. *Note GtkItem::, for more info.


GtkListItem* GTK_LIST_ITEM (gpointer OBJ)

  传一个generic pointer到`GtkListItem*'. *Note Standard Macros::, for more info.


GtkListItemClass* GTK_LIST_ITEM_CLASS (gpointer CLASS)

  传一个generic pointer到`GtkListItemClass*'. *Note Standard Macros::, for more info.


gint GTK_IS_LIST_ITEM (gpointer OBJ)

  决定generic pointer是否对照到`GtkListItem' object. *Note Standard Macros::, for more info.

  12.7 例子

  Please see the GtkList example on this, which covers the usage of a GtkListItem as well.

  --------------------------------------------------------------------------------

  译注: List物件这一篇本身比较不容易翻译, 因原文本身讲的并不太清楚. 此外, 其结构原本就比较繁琐. 若您在此糟遇问题, 可来信反应. 译者会想办法将其改善.

  If you got stuck here, it's mostly not your problem. Don't feel frustration. The List Widget itself is pretty complicated. You may drop me a word if you need. I will try to improve it.

  13. Undocumented Widgets

  These all require authors! :) Please consider contributing to our tutorial.

  If you must use one of these widgets that are undocumented, I strongly suggest you take a look at their respective header files in the GTK distro. GTK's function names are very descriptive. Once you have an understanding of how things work, it's
not easy to figure out how to use a widget simply by looking at it's function declarations. This, along with a few examples from others' code, and it should be no problem.

   When you do come to understand all the functions of a new undocumented widget, please consider writing a tutorial on it so others may benifit from your time.

  13.1 Text Entries

  13.2 Color Selections

  13.3 Range Controls

  13.4 Rulers

  13.5 Text Boxes

  13.6 Previews

  (This may need to be rewritten to follow the style of the rest of the tutorial)

   Previews serve a number of purposes in GIMP/GTK. The most important one is this. High quality images may take up to tens of megabytes of memory - easy! Any operation on an image that big is bound to take a long time. If it takes you 5-10
trial-and-errors (i.e. 10-20 steps, since you have to revert after you make an error) to choose the desired modification, it make take you literally hours to make the right one - if you don't run out of memory first. People who have spent hours in
color darkrooms know the feeling.Previews to the rescue!

   But the annoyance of the delay is not the only issue. Oftentimes it is helpful to compare the Before and After versions side-by-side or at least back-to-back. If you're working with big images and 10 second delays, obtaining the Before and After
impressions is, to say the least, difficult. For 30M images (4"x6", 600dpi, 24 bit) the side-by-side comparison is right out for most people, while back-to-back is more like back-to-1001, 1002, ..., 1010-back! Previews to the rescue!

  But there's more. Previews allow for side-by-side pre- previews. In other words, you write a plug-in (e.g. the filterpack simulation) which would have a number of here's-what-it-would-look-like-if-you-were-to-do-this previews.An approach like this
acts as a sort of a preview palette and is very effective fow subtle changes. Let's go previews!

  There's more. For certain plug-ins real-time image- specific human intervention maybe necessary. In the SuperNova plug-in, for example, the user is asked to enter the coordinates of the center of the future supernova. The easiest way to do this,
really, is to present the user with a preview and ask him to intereactively select the spot. Let's go previews!

  Finally, a couple of misc uses. One can use previews even when not working with big images. For example, they are useful when rendering compicated patterns. (Just check out the venerable Diffraction plug-in + many other ones!) As another example,
take a look at the colormap rotation plug-in (work in progress). You can also use previews for little logo's inside you plug-ins and even for an image of yourself, The Author. Let's go previews!

  When Not to Use Previews

  Don't use previews for graphs, drawing etc. GDK is much faster for that. Use previews only for rendered images!

  Let's go previews!

  You can stick a preview into just about anything. In a vbox, an hbox, a table, a button, etc. But they look their best in tight frames around them. Previews by themselves do not have borders and look flat without them. (Of course, if the flat look
is what you want...) Tight frames provide the necessary borders.

  [Image][Image]

  Previews in many ways are like any other widgets in GTK (whatever that means) except they possess an addtional feature: they need to be filled with some sort of an image! First, we will deal exclusively with the GTK aspect of previews and then
we'll discuss how to fill them.


GtkWidget *preview!
Without any ado:
/* Create a preview widget,
set its size, an show it */
GtkWidget *preview;
preview=gtk_preview_new(GTK_PREVIEW_COLOR)
/*Other option:
GTK_PREVIEW_GRAYSCALE);*/
gtk_preview_size (GTK_PREVIEW (preview), WIDTH, HEIGHT);
gtk_widget_show(preview);
my_preview_rendering_function(preview);
Oh yeah, like I said, previews look good inside frames, so how about:
GtkWidget *create_a_preview(intWidth,
intHeight,
intColorfulness)
{
GtkWidget *preview;
GtkWidget *frame;
frame = gtk_frame_new(NULL);
gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_IN);
gtk_container_border_width (GTK_CONTAINER(frame),0);
gtk_widget_show(frame);
preview=gtk_preview_new (Colorfulness?GTK_PREVIEW_COLOR
:GTK_PREVIEW_GRAYSCALE);
gtk_preview_size (GTK_PREVIEW (preview), Width, Height);
gtk_container_add(GTK_CONTAINER(frame),preview);
gtk_widget_show(preview);
my_preview_rendering_function(preview);
return frame;
}

   That's my basic preview. This routine returns the "parent" frame so you can place it somewhere else in your interface. Of course, you can pass the parent frame to this routine as a parameter. In many situations, however, the contents of the
preview are changed continually by your application. In this case you may want to pass a pointer to the preview to a "create_a_preview()" and thus have control of it later.

   One more important note that may one day save you a lot of time. Sometimes it is desirable to label you preview. For example, you may label the preview containing the original image as "Original" and the one containing the modified image as "Less
Original". It might occure to you to pack the preview along with the appropriate label into a vbox. The unexpected caveat is that if the label is wider than the preview (which may happen for a variety of reasons unforseeable to you, from the dynamic
decision on the size of the preview to the size of the font) the frame expands and no longer fits tightly over the preview. The same problem can probably arise in other situations as well.

   The solution is to place the preview and the label into a 2x1 table and by attaching them with the following paramters (this is one possible variations of course. The key is no GTK_FILL in the second attachment):


gtk_table_attach(GTK_TABLE(table),label,0,1,0,1,
0,
GTK_EXPAND|GTK_FILL,
0,0);
gtk_table_attach(GTK_TABLE(table),frame,0,1,1,2,
GTK_EXPAND,
GTK_EXPAND,
0,0);

  And here's the result:

  Misc

   Making a preview clickable is achieved most easily by placing it in a button. It also adds a nice border around the preview and you may not even need to place it in a frame. See the Filter Pack Simulation plug-in for an example.

  This is pretty much it as far as GTK is concerned.

  Filling In a Preview

   In order to familiarize ourselves with the basics of filling in previews, let's create the following pattern (contrived by trial and error):

  void


my_preview_rendering_function(GtkWidget *preview)
{
#define SIZE 100
#define HALF (SIZE/2)
guchar *row=(guchar *) malloc(3*SIZE); /* 3 bits per dot */
gint i, j; /* Coordinates*/
double r, alpha, x, y;
if (preview==NULL) return; /* I usually add this when I want */
/* to avoid silly crashes. You*/
/* should probably make sure that */
/* everything has been nicely */
/* initialized!*/
for (j=0; j < ABS(cos(2*alpha)) ) { /* Are we inside the shape?*/
/* glib.h contains ABS(x).*/
row[i*3+0] = sqrt(1-r)*255;/* Define Red*/
row[i*3+1] = 128;/* Define Green*/
row[i*3+2] = 224;/* Define Blue*/
}/* "+0" is for alignment!*/
else {
row[i*3+0] = r*255;
row[i*3+1] = ABS(sin((float)i/SIZE*2*PI))*255;
row[i*3+2] = ABS(sin((float)j/SIZE*2*PI))*255;
}
}
gtk_preview_draw_row( GTK_PREVIEW(preview),row,0,j,SIZE);
/* Insert "row" into "preview" starting at the point with*/
/* coordinates (0,j) first column, j_th row extending SIZE */
/* pixels to the right */
}
free(row); /* save some space */
gtk_widget_draw(preview,NULL); /* what does this do? */
gdk_flush(); /* or this? */
}

  Non-GIMP users can have probably seen enough to do a lot of things already.

  For the GIMP users I have a few pointers to add.

  Image Preview

   It is probably wize to keep a reduced version of the image around with just enough pixels to fill the preview. This is done by selecting every n'th pixel where n is the ratio of the size of the image to the size of the preview. All further
operations (including filling in the previews) are then performed on the reduced number of pixels only. The following is my implementation of reducing the image. (Keep in mind that I've had only basic C!)


(UNTESTED CODE ALERT!!!)
typedef struct {
gintwidth;
gintheight;
gintbbp;
guchar*rgb;
guchar*mask;
} ReducedImage;
enum {
SELECTION_ONLY,
SELCTION_IN_CONTEXT,
ENTIRE_IMAGE
};
ReducedImage *Reduce_The_Image(GDrawable *drawable,
GDrawable *mask,
gint LongerSize,
gint Selection)
{
/* This function reduced the image down to the the selected preview size */
/* The preview size is determine by LongerSize, i.e. the greater of the*/
/* two dimentions. Works for RGB images only!*/
gint RH, RW;/* Reduced height and reduced width*/
gint width, height;/* Width and Height of the area being reduced*/
gint bytes=drawable->bpp;
ReducedImage *temp=(ReducedImage *)malloc(sizeof(ReducedImage));
guchar *tempRGB, *src_row, *tempmask, *src_mask_row,R,G,B;
gint i, j, whichcol, whichrow, x1, x2, y1, y2;
GPixelRgn srcPR, srcMask;
gint NoSelectionMade=TRUE; /* Assume that we're dealing with the entire*/
/* image. */
gimp_drawable_mask_bounds (drawable->id, &x1, &y1, &x2, &y2);
width= x2-x1;
height = y2-y1;
/* If there's a SELECTION, we got its bounds!)
if (width != drawable->width && height != drawable->height)
NoSelectionMade=FALSE;
/* Become aware of whether the user has made an active selection*/
/* This will become important later, when creating a reduced mask. */

/* If we want to preview the entire image, overrule the above!*/
/* Of course, if no selection has been made, this does nothing! */
if (Selection==ENTIRE_IMAGE) {
x1=0;
x2=drawable->width;
y1=0;
y2=drawable->height;
}
/* If we want to preview a selection with some surronding area we */
/* have to expand it a little bit. Consider it a bit of a riddle. */
if (Selection==SELECTION_IN_CONTEXT) {
x1=MAX(0,x1-width/2.0);
x2=MIN(drawable->width,x2+width/2.0);
y1=MAX(0,y1-height/2.0);
y2=MIN(drawable->height, y2+height/2.0);
}
/* How we can determine the width and the height of the area being */
/* reduced.*/
width= x2-x1;
height = y2-y1;
/* The lines below determine which dimension is to be the longer*/
/* side. The idea borrowed from the supernova plug-in. I suspect I */
/* could've thought of it myself, but the truth must be told.*/
/* Plagiarism stinks!*/
if (width>height) {
RW=LongerSize;
RH=(float) height * (float) LongerSize/ (float) width;
}
else {
RH=LongerSize;
RW=(float)width * (float) LongerSize/ (float) height;
}
/* The intire image is stretched into a string! */
tempRGB= (guchar *) malloc(RW*RH*bytes);
tempmask= (guchar *) malloc(RW*RH);
gimp_pixel_rgn_init (&srcPR, drawable, x1, y1, width, height, FALSE, FALSE);
gimp_pixel_rgn_init (&srcMask, mask, x1, y1, width, height, FALSE, FALSE);
/* Grab enough to save a row of image and a row of mask. */
src_row= (guchar *) malloc (width*bytes);
src_mask_row= (guchar *) malloc (width);
for (i=0; i < RH; i++) {
whichrow=(float)i*(float)height/(float)RH;
gimp_pixel_rgn_get_row (&srcPR, src_row, x1, y1+whichrow, width);
gimp_pixel_rgn_get_row (&srcMask, src_mask_row, x1, y1+whichrow, width);
for (j=0; j < RW; j++) {
whichcol=(float)j*(float)width/(float)RW;
/* No selection made = each point is completely selected! */
if (NoSelectionMade)
tempmask[i*RW+j]=255;
else
tempmask[i*RW+j]=src_mask_row[whichcol];
/* Add the row to the one long string which now contains the image! */
tempRGB[i*RW*bytes+j*bytes+0]=src_row[whichcol*bytes+0];
tempRGB[i*RW*bytes+j*bytes+1]=src_row[whichcol*bytes+1];
tempRGB[i*RW*bytes+j*bytes+2]=src_row[whichcol*bytes+2];
/* Hold on to the alpha as well */
if (bytes==4)
tempRGB[i*RW*bytes+j*bytes+3]=src_row[whichcol*bytes+3];
}
}
temp->bpp=bytes;
temp->width=RW;
temp->height=RH;
temp->rgb=tempRGB;
temp->mask=tempmask;
return temp;
}
The following is a preview function which used the same ReducedImage type!
Note that it uses fakes transparancy (if one is present by means of
fake_transparancy which is defined as follows:
gint fake_transparency(gint i, gint j)
{
if ( ((i%20)- 10) * ((j%20)- 10)>0)
return 64;
else
return 196;
}
Now here's the preview function:
void
my_preview_render_function(GtkWidget *preview,
gintchangewhat,
gintchangewhich)
{
gint Inten, bytes=drawable->bpp;
gint i, j, k;
float partial;
gint RW=reduced->width;
gint RH=reduced->height;
guchar *row=malloc(bytes*RW);;
for (i=0; i < RH; i++) {
for (j=0; j < RW; j++) {
row[j*3+0] = reduced->rgb[i*RW*bytes + j*bytes + 0];
row[j*3+1] = reduced->rgb[i*RW*bytes + j*bytes + 1];
row[j*3+2] = reduced->rgb[i*RW*bytes + j*bytes + 2];
if (bytes==4)
for (k=0; k<3; k++) {
float transp=reduced->rgb[i*RW*bytes+j*bytes+3]/255.0;
row[3*j+k]=transp*a[3*j+k]+(1-transp)*fake_transparency(i,j);
}
}
gtk_preview_draw_row( GTK_PREVIEW(preview),row,0,i,RW);
}
free(a);
gtk_widget_draw(preview,NULL);
gdk_flush();
}
Applicable Routines
guintgtk_preview_get_type(void);
/* No idea */
voidgtk_preview_uninit (void);
/* No idea */
GtkWidget*gtk_preview_new(GtkPreviewTypetype);
/* Described above */
voidgtk_preview_size(GtkPreview*preview,
gint width,
gint height);
/* Allows you to resize an existing preview.*/
/* Apparantly there's a bug in GTK which makes*/
/* this process messy. A way to clean up a mess */
/* is to manually resize the window containing*/
/* the preview after resizing the preview.*/
voidgtk_preview_put(GtkPreview*preview,
GdkWindow*window,
GdkGC*gc,
gint srcx,
gint srcy,
gint destx,
gint desty,
gint width,
gint height);
/* No idea */
voidgtk_preview_put_row(GtkPreview*preview,
guchar*src,
guchar*dest,
gint x,
gint y,
gint w);
/* No idea */
voidgtk_preview_draw_row(GtkPreview*preview,
guchar*data,
gint x,
gint y,
gint w);
/* Described in the text */
voidgtk_preview_set_expand (GtkPreview*preview,
gint expand);
/* No idea */
/* No clue for any of the below but*/
/* should be standard for most widgets */
voidgtk_preview_set_gamma(doublegamma);
voidgtk_preview_set_color_cube (guintnred_shades,
guintngreen_shades,
guintnblue_shades,
guintngray_shades);
voidgtk_preview_set_install_cmap(gint install_cmap);
voidgtk_preview_set_reserved(gint nreserved);
GdkVisual*gtk_preview_get_visual (void);
GdkColormap*gtk_preview_get_cmap(void);
GtkPreviewInfo* gtk_preview_get_info(void);


  That's all, folks!

  13.7 Curves

  14. Menu物件

  有两种方式来产生选单物件, 一种简单的, 一种难的. 两种各有其用途, 但您可以用menu_factory(简单的). 难的方法是一个一个产生. 简单的是用gtk_menu_factory 这个简单多了, 但各有其优劣之处.

  menufactory很好用, 虽然另外写一些函数, 以手动函数来产生这些选单会比较有用. 不过, 以menufactory, 也是可以加影像到选单中.

  14.1 Manual Menu Creation

  在教学的目的上, 我们先来看看难的方法.:)

  先看看产生选单的函数. 第一个当然是产生一个新的选单.


GtkWidget *gtk_menu_bar_new()
GtkWidget *gtk_menu_new();

  这个函数返回一个新的选单, 它还不会显示.

  以下两个函数是用来产生选单项目.


GtkWidget *gtk_menu_item_new()
and
GtkWidget *gtk_menu_item_new_with_label(const char *label)

  动态新增


gtk_menu_item_append()
gtk_menu_item_set_submenu()

  gtk_menu_new_with_label及gtk_menu_new函数一个产生一个新的选单项目并带标签, 另一个则是个空的选单项目.

  产生选单的步骤大致如下:

  使用gtk_menu_new()来产生一个新的选单

  使用gtk_menu_item_new()来产生一个新的选单项目. 这会是主选单, 文字将会是menu bar本身.

  使用gtk_menu_item_new来将每一个项目产生出来用gtk_menu_item_append()来将每个新项目放在一起. 这会产生一列选单项目.

  使用gtk_menu_item_set_submenu()来接到心产生的menu_items到主选单项目. (在第二步中所产生出来的).

  使用gtk_menu_bar_new来产生一个menu bar. 这一步仅需做一次, 当我们产生一系列选单在menu bar上.

  使用gtk_menu_bar_append来将主选单放到menubar.

  14.2 Manual Menu范例

  我们来做做看, 看看一个范例会比较有帮助.


#include
int main (int argc, char *argv[])
{
GtkWidget *window;
GtkWidget *menu;
GtkWidget *menu_bar;
GtkWidget *root_menu;
GtkWidget *menu_items;
char buf[128];
int i;
gtk_init (&argc, &argv);
/* create a new window */
window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
gtk_window_set_title(GTK_WINDOW (window), "GTK Menu Test");
gtk_signal_connect(GTK_OBJECT (window), "destroy",
(GtkSignalFunc) gtk_exit, NULL);
/* Init the menu-widget, and remember -- never
* gtk_show_widget() the menu widget!! */
menu = gtk_menu_new();
/* This is the root menu, and will be the label will
be the menu name displayed on
* the menu bar.There won't be
* a signal handler attached, as it only pops up the rest of
the menu when pressed. */
root_menu = gtk_menu_item_new_with_label("Root Menu");
gtk_widget_show(root_menu);
/* Next we make a little loop that makes three menu-entries for
"test-menu".
* Notice the call to gtk_menu_append.Here we are adding a list
of menu items
* to our menu.Normally, we'd also catch the "clicked" signal on
each of the
* menu items and setup a callback for it, but it's omitted
here to save space. */
for(i = 0; i < 3; i++)
{
/* Copy the names to the buf. */
sprintf(buf, "Test-undermenu - %d", i);
/* Create a new menu-item with a name... */
menu_items = gtk_menu_item_new_with_label(buf);
/* ...and add it to the menu. */
gtk_menu_append(GTK_MENU (menu), menu_items);
/* Show the widget */
gtk_widget_show(menu_items);
}
/* Now we specify that we want our newly created "menu" to be the menu
for the "root menu" */
gtk_menu_item_set_submenu(GTK_MENU_ITEM (root_menu), menu);
/* Create a menu-bar to hold the menus and add it to our main window*/
menu_bar = gtk_menu_bar_new();
gtk_container_add(GTK_CONTAINER(window), menu_bar);
gtk_widget_show(menu_bar);
/* And finally we append the menu-item to the menu-bar -- this is the "root"
* menu-item I have been raving about =) */
gtk_menu_bar_append(GTK_MENU_BAR (menu_bar), root_menu);
/* always display the window as the last step so it all splashes
on the screen at once. */
gtk_widget_show(window);
gtk_main ();
return 0;
}

  您也可以设定一个选单项目无效, 并使用accelerator table结合按键到选单功能.

  14.3 使用GtkMenuFactory

  我们已经示范了难的方法, 这里是用gtk_menu_factory的方法.

  14.4 Menu Factory范例

  这里是menu factory的范例. 这是第一个档案, menus.h. 另有menus.c及main.c


#ifndef __MENUS_H__
#define __MENUS_H__
#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */
void get_main_menu (GtkWidget **menubar, GtkAcceleratorTable **table);
void menus_create(GtkMenuEntry *entries, int nmenu_entries);
#ifdef __cplusplus
}
#endif /* __cplusplus */
#endif /* __MENUS_H__ */
And here is the menus.c file.
#include
#include
#include "main.h"
static void menus_remove_accel
(GtkWidget * widget, gchar * signal_name, gchar * path);
static gint menus_install_accel
(GtkWidget * widget, gchar * signal_name, gchar key,
gchar modifiers, gchar * path);
void menus_init(void);
void menus_create(GtkMenuEntry * entries, int nmenu_entries);
/* this is the GtkMenuEntry structure used to create new menus.The
* first member is the menu definition string.The second, the
* default accelerator key used to access this menu function with
* the keyboard.The third is the callback function to call when
* this menu item is selected (by the accelerator key, or with the
* mouse.) The last member is the data to pass to your callback function.
*/
static GtkMenuEntry menu_items[] =
{
{"
/File/New", "N", NULL, NULL},
{"
/File/Open", "O", NULL, NULL},
{"
/File/Save", "S", NULL, NULL},
{"
/File/Save as", NULL, NULL, NULL},
{"
/File/", NULL, NULL, NULL},
{"
/File/Quit",
"Q", file_quit_cmd_callback, "OK, I'll quit"},
{"
/Options/Test", NULL, NULL, NULL}
};
/* calculate the number of menu_item's */
static int nmenu_items = sizeof(menu_items) / sizeof(menu_items[0]);
static int initialize = TRUE;
static GtkMenuFactory *factory = NULL;
static GtkMenuFactory *subfactory[1];
static GHashTable *entry_ht = NULL;
void get_main_menu(GtkWidget ** menubar, GtkAcceleratorTable ** table)
{
if (initialize)
menus_init();
if (menubar)
*menubar = subfactory[0]->widget;
if (table)
*table = subfactory[0]->table;
}
void menus_init(void)
{
if (initialize) {
initialize = FALSE;
factory = gtk_menu_factory_new(GTK_MENU_FACTORY_MENU_BAR);
subfactory[0] = gtk_menu_factory_new(GTK_MENU_FACTORY_MENU_BAR);
gtk_menu_factory_add_subfactory(factory, subfactory[0], "
");
menus_create(menu_items, nmenu_items);
}
}
void menus_create(GtkMenuEntry * entries, int nmenu_entries)
{
char *accelerator;
int i;
if (initialize)
menus_init();
if (entry_ht)
for (i = 0; i < nmenu_entries; i++) {
accelerator = g_hash_table_lookup(entry_ht, entries.path);
if (accelerator) {
if (accelerator[0] == ' ')
entries[i].accelerator = NULL;
else
entries[i].accelerator = accelerator;
}
}
gtk_menu_factory_add_entries(factory, entries, nmenu_entries);
for (i = 0; i < nmenu_entries; i++)
if (entries[i].widget) {
gtk_signal_connect(GTK_OBJECT(entries[i].widget), "install_accelerator",
(GtkSignalFunc) menus_install_accel,
entries[i].path);
gtk_signal_connect(GTK_OBJECT(entries[i].widget), "remove_accelerator",
(GtkSignalFunc) menus_remove_accel,
entries[i].path);
}
}
static gint menus_install_accel(GtkWidget * widget, gchar * signal_name,
gchar key, gchar modifiers, gchar * path)
{
char accel[64];
char *t1, t2[2];
accel[0] = ' ';
if (modifiers & GDK_CONTROL_MASK)
strcat(accel, "");
if (modifiers & GDK_SHIFT_MASK)
strcat(accel, "");
if (modifiers & GDK_MOD1_MASK)
strcat(accel, "");
t2[0] = key;
t2[1] = ' ';
strcat(accel, t2);
if (entry_ht) {
t1 = g_hash_table_lookup(entry_ht, path);
g_free(t1);
} else
entry_ht = g_hash_table_new(g_string_hash, g_string_equal);
g_hash_table_insert(entry_ht, path, g_strdup(accel));
return TRUE;
}
static void menus_remove_accel
(GtkWidget * widget, gchar * signal_name, gchar * path)
{
char *t;
if (entry_ht) {
t = g_hash_table_lookup(entry_ht, path);
g_free(t);
g_hash_table_insert(entry_ht, path, g_strdup(""));
}
}
void menus_set_sensitive(char *path, int sensitive)
{
GtkMenuPath *menu_path;
if (initialize)
menus_init();
menu_path = gtk_menu_factory_find(factory, path);
if (menu_path)
gtk_widget_set_sensitive(menu_path->widget, sensitive);
else
g_warning
("Unable to set sensitivity for menu which doesn't exist: %s", path);
}

  And here's the main.h


#ifndef __MAIN_H__
#define __MAIN_H__
#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */
void file_quit_cmd_callback(GtkWidget *widget, gpointer data);
#ifdef __cplusplus
}
#endif /* __cplusplus */
#endif /* __MAIN_H__ */
And main.c
#include
#include "main.h"
#include "menus.h"
int main(int argc, char *argv[])
{
GtkWidget *window;
GtkWidget *main_vbox;
GtkWidget *menubar;
GtkAcceleratorTable *accel;
gtk_init(&argc, &argv);
window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
gtk_signal_connect(GTK_OBJECT(window), "destroy",
GTK_SIGNAL_FUNC(file_quit_cmd_callback),
"WM destroy");
gtk_window_set_title(GTK_WINDOW(window), "Menu Factory");
gtk_widget_set_usize(GTK_WIDGET(window), 300, 200);
main_vbox = gtk_vbox_new(FALSE, 1);
gtk_container_border_width(GTK_CONTAINER(main_vbox), 1);
gtk_container_add(GTK_CONTAINER(window), main_vbox);
gtk_widget_show(main_vbox);
get_main_menu(&menubar, &accel);
gtk_window_add_accelerator_table(GTK_WINDOW(window), accel);
gtk_box_pack_start(GTK_BOX(main_vbox), menubar, FALSE, TRUE, 0);
gtk_widget_show(menubar);
gtk_widget_show(window);
gtk_main();
return(0);
}
/* This is just to demonstrate how callbacks work when using the
* menufactory.Often, people put all the callbacks from the menus
* in a separate file, and then have them call the appropriate functions
* from there.Keeps it more organized. */
void file_quit_cmd_callback (GtkWidget *widget, gpointer data)
{
g_print ("%s
", (char *) data);
gtk_exit(0);
}

  这里是makefile.


CC= gcc
PROF= -g
C_FLAGS =-Wall $(PROF) -L/usr/local/include -DDEBUG
L_FLAGS =$(PROF) -L/usr/X11R6/lib -L/usr/local/lib
L_POSTFLAGS = -lgtk -lgdk -lglib -lXext -lX11 -lm
PROGNAME = at
O_FILES = menus.o main.o
$(PROGNAME): $(O_FILES)
rm -f $(PROGNAME)
$(CC) $(L_FLAGS) -o $(PROGNAME) $(O_FILES) $(L_POSTFLAGS)
.c.o:
$(CC) -c $(C_FLAGS) $<
clean:
rm -f core *.o $(PROGNAME) nohup.out
distclean: clean
rm -f *~

  15. Timeouts, IO及Idle函数

  15.1 Timeouts

  您可能会想要在gtk_man idle时, 做一些事情. 你有好几个选择. 使用以下这些函数可以产生一个timeout函数, 它每间隔一阵子就会去呼叫您的函数.


gint gtk_timeout_add (guint32 interval,
GtkFunction function,
gpointer data);

  第一个参数是每间隔interval milliseconds会去呼叫您的函数. 第二个是该函数. 第三个是要传给该函数的资料. 返回值则为该行程的"标签".


void gtk_timeout_remove (gint tag);

  您可以在您的timeout函数返回zero或FALSE来停止timeout函数. 这就是说您必须返回非零值, 如TRUE.

  你的callback函数必须像这样:


gint timeout_callback (gpointer data);

  15.2 监督IO

  GTK另一个漂亮的功能是有办法去检查一个file descriptor的资料. (as returned by open(2) or socket(2)). 这个在网路软体上很有用:


gint gdk_input_add (gint source,
GdkInputCondition condition,
GdkInputFunctionfunction,
gpointer data);

  第一个参数是您想看的file descriptor, 第二个是你要GDK去看那一项, 可以是以下几项:

  GDK_INPUT_READ - 当file descriptor有资料的时候会去呼叫您的函数.

  GDK_INPUT_WRITE - 当您的file descriptor可以被写入资料的时候.

  第三个当然是callback函数. 第四个是要传给callback的资料.

  返回值是可用来停止监督的一个"标签".


void gdk_input_remove (gint tag);

  当您的callback函数返回zero或FALSE时, GTK会停止呼叫您的callback函数.

  15.3 Idle函数

  当什麽事情都不发生时, 您要呼叫一个函数时要怎麽办?


gint gtk_idle_add (GtkFunction function,gpointer data);

  当什麽事都不发生时, GTK会去呼叫这个函数.


void gtk_idle_remove (gint tag);

  这个不用解释了吧. 您若在idle函数返回zero或FALSE会停止idle函数的运行.

  17. glib

  glib提供许多有用的函数及定义. 我把它们列在这里并做简短的解释. 很多都是与libc重复, 对这些我不再详述. 这些大致上是用来参考, 您知道有什麽东西可以用就好.

  17.1 定义

  为保持资料型态的一致, 这里有一些定义:


G_MINFLOAT
G_MAXFLOAT
G_MINDOUBLE
G_MAXDOUBLE
G_MINSHORT
G_MAXSHORT
G_MININT
G_MAXINT
G_MINLONG
G_MAXLONG

  此外, 以下的typedefs. 没有列出来的是会变的, 要看是在那一种平台上. 如果您想要具有可移植性, 记得避免去使用sizeof(pointer). 例如, 一个指标在Alpha上是8 bytes, 但在Inter上为4 bytes.


chargchar;
shortgshort;
longglong;
intgint;
chargboolean;
unsigned charguchar;
unsigned shortgushort;
unsigned longgulong;
unsigned intguint;
floatgfloat;
doublegdouble;
long double gldouble;
void* gpointer;
gint8
guint8
gint16
guint16
gint32
guint32

  17.2 双向链结串列

  以下函数用来产生, 管理及销毁双向链结串列.


GList* g_list_alloc(void);
voidg_list_free(GList *list);
voidg_list_free_1(GList *list);
GList* g_list_append(GList *list,gpointerdata);
GList* g_list_prepend (GList *list,gpointerdata);
GList* g_list_insert(GList *list,gpointerdata,gintposition);
GList* g_list_remove(GList *list,gpointerdata);
GList* g_list_remove_link (GList *list,GList *link);
GList* g_list_reverse (GList *list);
GList* g_list_nth (GList *list,gintn);
GList* g_list_find(GList *list,gpointerdata);
GList* g_list_last(GList *list);
GList* g_list_first(GList *list);
gintg_list_length(GList *list);
voidg_list_foreach (GList *list,GFuncfunc,gpointeruser_data);

  17.3 单向链结串列

  以下函数是用来管理单向链结串列:


GSList* g_slist_alloc(void);
voidg_slist_free(GSList*list);
voidg_slist_free_1(GSList*list);
GSList* g_slist_append(GSList*list,gpointerdata);
GSList* g_slist_prepend (GSList*list,gpointerdata);
GSList* g_slist_insert(GSList*list,gpointerdata,gintposition);
GSList* g_slist_remove(GSList*list,gpointerdata);
GSList* g_slist_remove_link (GSList*list,GSList*link);
GSList* g_slist_reverse (GSList*list);
GSList* g_slist_nth (GSList*list,gintn);
GSList* g_slist_find(GSList*list,gpointerdata);
GSList* g_slist_last(GSList*list);
gintg_slist_length(GSList*list);
voidg_slist_foreach (GSList*list,GFunc func,gpointeruser_data);

  17.4 记忆体管理


gpointer g_malloc(gulongsize);

  这是替代malloc()用的. 你不需要去检查返回值, 因为它已经帮你做好了, 保证.


gpointer g_malloc0 (gulongsize);

  一样, 不过会在返回之前将记忆体归零.


gpointer g_realloc (gpointermem,gulongsize);

  重定记忆体大小.


void g_free(gpointermem);
void g_mem_profile (void);

  将记忆体的使用状况写到一个档案, 不过您必须要在glib/gmem.c里面, 加#define MEM_PROFILE, 然後重新编译.


void g_mem_check(gpointermem);

  检查记忆体位置是否有效. 您必须要在glib/gmem.c上加#define MEM_CHECK, 然後重新编译.

  17.5 Timers

  Timer函数..


GTimer* g_timer_new (void);
voidg_timer_destroy (GTimer*timer);
voidg_timer_start(GTimer*timer);
voidg_timer_stop(GTimer*timer);
voidg_timer_reset(GTimer*timer);
gdouble g_timer_elapsed (GTimer*timer,gulong*microseconds);

  17.6 字串处理


GString* g_string_new(gchar*init);
void g_string_free(GString *string,gint free_segment);
GString* g_string_assign(GString *lval,gchar*rval);
GString* g_string_truncate(GString *string,gint len);
GString* g_string_append(GString *string,gchar*val);
GString* g_string_append_c(GString *string,gcharc);
GString* g_string_prepend(GString *string,gchar*val);
GString* g_string_prepend_c (GString *string,gcharc);
void g_string_sprintf(GString *string,gchar*fmt,...);
void g_string_sprintfa(GString *string,gchar*fmt,...);

  17.7 工具及除错函数


gchar* g_strdup(const gchar *str);
gchar* g_strerror(gint errnum);

  我建议您使用这个来做所有错误讯息. 这玩意好多了. 它比perror()来的具有可移植性. 输出为以下形式:


program name:function that failed:file or further description:strerror

  这里是"hello world"用到的一些函数:


g_print("hello_world:open:%s:%s
", filename, g_strerror(errno));
void g_error(gchar *format, ...);

  显示错误讯息, 其格式与printf一样, 但会加个"** ERROR **: ", 然後离开程式. 只在严重错误时使用.


void g_warning (gchar *format, ...);

  跟上面一样, 但加个"** WARNING **: ", 不离开程式.


void g_message (gchar *format, ...);

  加个"message: ".


void g_print(gchar *format, ...);

  printf()的替代品.

  最後一个:


gchar* g_strsignal (gint signum);

  列印Unix系统的信号名称, 在信号处理时很有用.

  这些大都从glib.h中而来.

  18. 设定视窗物件属性

  这里描述如何操作视窗物件的函数集. 可用於设定外形, 空格, 大小等等.


(Maybe I should make a whole section on accelerators.)
voidgtk_widget_install_accelerator (GtkWidget*widget,
GtkAcceleratorTable *table,gchar*signal_name,
gcharkey,guint8modifiers);
voidgtk_widget_remove_accelerator(GtkWidget*widget,
GtkAcceleratorTable *table,gchar*signal_name);
voidgtk_widget_activate(GtkWidget*widget);
voidgtk_widget_set_name(GtkWidget*widget,gchar*name);
gchar* gtk_widget_get_name(GtkWidget*widget);
voidgtk_widget_set_sensitive(GtkWidget*widget,gint sensitive);
voidgtk_widget_set_style(GtkWidget*widget,GtkStyle*style);
GtkStyle*gtk_widget_get_style (GtkWidget *widget);
GtkStyle*gtk_widget_get_default_style(void);
voidgtk_widget_set_uposition(GtkWidget*widget,gint x,gint y);
voidgtk_widget_set_usize(GtkWidget*widget,gint width,gint height);
voidgtk_widget_grab_focus(GtkWidget*widget);
voidgtk_widget_show(GtkWidget*widget);
voidgtk_widget_hide(GtkWidget*widget);

  19. GTK的rc档

  GTK有处理软体内定值的一套方法, 即使用其rc档. 这些可以用来设定颜色, 并且可以用pixmaps来设定某些物件的背景.

  19.1 rc档的功能

  当您的软体启动时, 您应该呼叫这一行:


void gtk_rc_parse (char *filename);

  将您的档名传入做为参数. 这会使GTK来分析这个档案, 并使用设定值来设定物件的形态.

  如果您希望有特别样子的物件, 但可从另一个物件做为基础来产生, 可以用这个:


void gtk_widget_set_name (GtkWidget *widget,gchar *name);

  传入您新产生的物件做为第一个参数, 您要给它的名字做为第二个参数. 这样的话可以让你透过rc档来改变该物件的属性.

  如果我们用像以下的呼叫:


button = gtk_button_new_with_label ("Special Button");
gtk_widget_set_name (button, "special button");

  则这个按钮被给了一个名字叫"special button" 并且会被指向rc档中的"special button.GtkButton". [<--- 要是我错了, 修正我!]

  以下的rc档设定主视窗的属性, 并让所有子视窗继承其形态. 在程式中的程式码为:


window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
gtk_widget_set_name (window, "main window");

  而该形态则在rc档中定义为:


widget "main window.*GtkButton*" style "main_button"

  这会设定所有GtkButton物件, 成为在"main window"中的"main_buttons"的形态.

  您可以看到, 这是很强很有弹性的系统. 用您最佳的想像力来看有多少好处.

  19.2 GTK的rc档案格式

  GTK的rc档格式如以下的范例. 这个testgtkrc档从GTK distribution而来, 但我加了点料及注解进去. 您也可以加一点解释来让使用者做微调.

  有好几个指令来改变该物件的属性.

  fg - 前景颜色.

  bg - 背景颜色.

  bg_pixmap - 背景图片pixmap.

  font - 字型.

  除此, 一个物件可以有好几种状态. 您可以设定不同的颜色, 图案及字形. 这些状态是:

  NORMAL - 物件一般的状态, 没有滑鼠滑过, 没有被按下.

  PRELIGHT - 滑鼠滑过该物件.

  ACTIVE - 当该物件被压下或按下, 该视窗会生效.

  INSENSITIVE - 当该物件被设为失效.

  SELECTED - 当物件被选择.

  当我们使用"fg"及"bg"来设定该物件的颜色时, 其格式为:


fg[] = { Red, Green, Blue }

  这里STATE是我们以上所说的其中之一(PRELIGHT, ACTIVE etc), 而Red, Green及Blue为0到1.0, { 1.0, 1.0, 1.0 }为白色. 它们必须要为浮点数, "1"不行, 必须是"1.0", 否则会全部变成0. "0"可以. 不是以此格式者均为"0".

  bg_pixmap跟以上都很近似, 除了变成档名以外.

  pixmap_path是以":"做为分隔的一串路径. 这些路径会用来搜寻您所指定的pixmap.

  font指令很简单:


font = ""

  比较难的是找出想要的font名称. 用xfontsel或类似的工具来找会有点帮助.

  "widget_class"设定物件的类别. 这些类别在物件概论中的类别组织图有列出来.

  "widget"指令指定一个已经定好的形态给一个物件. 替代所有该物件的属性. 这些物件则在程式中以gtk_widget_set_name ()注册过了. 这允许您指定各别物件的属性, 而不是设定全部同一类的. 我要求您要做好文件, 这样使用者可以自行修改.

  当"parent"用来当成一个属性时, 该物件会继承其父所有财产.

  当您定义一个形态时, 可以指定以前已经定义过的形态给新的.


style "main_button" = "button"
{
font = "-adobe-helvetica-medium-r-normal--*-100-*-*-*-*-*-*"
bg[PRELIGHT] = { 0.75, 0, 0 }
}

  这个例子用"button"的形态, 产生一个"main_button"形态, 并且只改变font及背景颜色.

  当然了并非所有属性都对所有物件生效. 因为该物件不见得拥有该属性.

  19.3 rc档的范例


# pixmap_path "
:::..."
#
pixmap_path "/usr/include/X11R6/pixmaps:/home/imain/pixmaps"
#
# style [= ]
# {
#
]
文章评论

共有 14 条评论

  1. linuxydy 于 2012-03-22 20:44:29发表:

    谢谢分享,向老大学习!

  2. fedora15 于 2011-07-28 23:31:47发表:

    太长。

  3. TX464157234 于 2009-12-05 18:31:25发表:

    看这贴还真有点困难,(帖子的长度论坛第一,哈哈)。

  4. 563909373 于 2009-11-27 13:39:04发表:

    我晕,楼主怎么不做成个文档啊

  5. lvmenbo 于 2009-11-11 23:45:03发表:

    我初学者 看不懂

  6. yeyanbin 于 2009-11-11 00:01:47发表:

    (e:e2s

  7. suetao 于 2009-11-10 11:27:26发表:

    太长了,能不能贴到附件中

  8. haolong 于 2008-10-12 18:48:18发表:

    这个是不是发错了地方。应该发在内核和编程那个版块

  9. cloud4986 于 2008-10-12 15:01:16发表:

    没看到资料

  10. maple094 于 2008-05-27 16:36:33发表:

    没看到资源呢~~~

  11. smqt 于 2008-05-01 09:53:05发表:

    没有啊

  12. Loaden 于 2008-02-29 22:48:28发表:

    哪里可以下载?

  13. gxf 于 2007-10-09 12:46:47发表:

    GTK好学,看完这篇文章就可以开发一些简单的界面了

  14. 奶茶dsk 于 2007-10-09 08:44:31发表:

    :0D1 收藏了,等会慢慢看.....:0L