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 <gtk/gtk.h>
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 <gtk/gtk.h>
/* 这是个callback函数. 其资料参数在本例中被忽略
* 以下有更多的callback函数. */
void hello (GtkWidget *widget, gpointer *data)
{
g_print (/"Hello World//n/");
}
/* 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<library directory> 则gcc会去找这些目录, 看看所需要的函数库是否找得到. 例如, 在我的DebianLinux系统中, 我已经增加了 -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.
2.4 步过Hello World
现在您知道这些理论了, 我们现在来根据这些理论, 把/"hello world/"这个范例弄清楚.
这是个当按钮被按下时, 会被呼叫到的callback函数. 参数的资料没有被用到.
void hello (GtkWidget *widget, gpointer *data)
{
g_print (/"Hello World//n/");
}
这是另一个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//n/", (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,
gint expand,
gint fill,
gint padding);
第一个参数是您要把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.//n/");
/* 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, 整个排列会看起来像这样:
0 1 2
0+----------+----------+
| | |
1+----------+----------+
| | |
2+----------+----------+
坐标系统开始於左上角. 要把物件放进box中, 可用以下函数:
void gtk_table_attach (GtkTable *table,
GtkWidget *child,
gint left_attach,
gint right_attach,
gint top_attach,
gint bottom_attach,
gint xoptions,
gint yoptions,
gint xpadding,
gint ypadding);
第一个参数(/"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,
gint left_attach,
gint right_attach,
gint top_attach,
gint bottom_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,
gint row,
gint spacing);
及
void gtk_table_set_col_spacing (GtkTable *table,
gint column,
gint spacing);
对栏来说, 空格是在栏的右边. 而列则是在下面.
您也可以用以下函数来产生固定的空格.
void gtk_table_set_row_spacings (GtkTable *table,
gint spacing);
及,
void gtk_table_set_col_spacings (GtkTable *table,
gint spacing);
使用这些函数, 其最後一栏及最後一列并没有空格存在.
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里面找到.
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, 即一秒.
void gtk_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,
GtkPolicyType hscrollbar_policy,
GtkPolicyType vscrollbar_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)//n/", 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 <gtk/gtk.h>
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 <gtk/gtk.h>
/* 取得选取的档名并显示在萤幕上 */
void file_ok_sel (GtkWidget *w, GtkFileSelection *fs)
{
g_print (/"%s//n/", 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
*/
const gchar *list_item_data_key=/"list_item_data/";
/* prototypes for signal handler that we are going to connect
* to the GtkList widget
*/
static void sigh_print_selection (GtkWidget *gtklist,
gpointer func_data);
static void sigh_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;
guint i;
gchar buffer[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) {
GList static_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,
gpointer func_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//n/");
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(/"//n/");
}
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.
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[i].path);
if (accelerator) {
if (accelerator[0] == /"//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] = /"//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] = /"//0/";
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//n/", (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 *~
16. 选取区域管理
16.1 概说
GTK 所支援的其中一种行程间通讯为selections. 一个selection本身是一笔资料, 例如, 使用者选取文字的一部份, 又如, 由滑鼠抓出一些东西. 在显示器上一次只能有一个/"选取区域/", 上一个选取区域要在该区域撤销时才会生效. 其它的应用软体以几种各种不同形式取得其内容, 被称为targets. 可以有许多个selections, 但X软体只能处理一个, 即primary selection.
在大部份的状况下, GTK程式不需要自行处理选取区域. 标准物件如Entry物件, 已经有能力来自动产生选取区域, 并从其它物件撷取选取区域. 不过有时候, 您想要给其它物件有能力提供选取区域, 或当内定不支援, 想要撷取资料时.
一 个基本观念需要了解选取区域处理的是atom. 一个atom是个integer, 标记著一个字串. 有些特定的元素被X server事先定义过, 有些在gtk.h中则为固定数值, 对映到这些atoms. 例如GDK_PRIMARY_SELECTION对映到字串/"PRIMARY/". 在其它状况下, 您应该使用gdk_atom_intern()这个函数, 用以取得atom对映到string, 及gdk_atom_name(), 用以取得atom的名称. selections及targets都是一种atoms.
16.2 撷取selection
撷取selection是个非同步行程. 您可以呼叫:
gint gtk_selection_convert (GtkWidget *widget,
GdkAtom selection,
GdkAtom target,
guint32 time)
这个函数转换选取区域到target所指定的形式. time这一栏是由选取被触发到事件发生的时间. 这使我们可以保证事件发生的顺序. 您也可以用GDK_CURRENT_TIME来替代.
当选取区域的拥有者回应一个要求时, 一个/"selection_received/"信号会送到您的程式. 该信号处理器会收到一个指标GtkSelectionData 结构, 定义如下:
struct _GtkSelectionData
{
GdkAtom selection;
GdkAtom target;
GdkAtom type;
gint format;
guchar *data;
gint length;
};
selection 及target 是您在gtk_selection_convert()中所给的值. type由选区拥有者返回, 用来辨识资料型态. 可以是这些值/"STRING/", 字串, /"ATOM/", 一系列的atoms, /"INTEGER/", 一个integer, 等等. a series of atoms, /"INTEGER/", an integer, etc. 大部份targets只能返回一种型态. format是每个单位有多少的bits(如字元为8 bits, guint32为32 bits). 一般来说, 您在收资料的时候, 不必管这个值. data是返回的资料指标. length是返回资料的长度, 以byte做单位. 如果length是负值, 那麽表示有错误发生, 选取区域无效. 这在所被要求选区的程式本身不拥有或不支援的时候会发生. 该缓冲区事实上保证一定有多出一个byte; 多出来的byte永远为零, 所以不需要多复制一份字串备份.
在以下的例子中, 我们撷取特别的target, /"TARGETS/", 这是个所有selection都可以转换进去的target.
#include
void selection_received (GtkWidget *widget,
GtkSelectionData *selection_data,
gpointer data);
/* Signal handler invoked when user clicks on the /"Get Targets/" button */
void
get_targets (GtkWidget *widget, gpointer data)
{
static GdkAtom targets_atom = GDK_NONE;
/* Get the atom corresonding to the string /"TARGETS/" */
if (targets_atom == GDK_NONE)
targets_atom = gdk_atom_intern (/"TARGETS/", FALSE);
/* And request the /"TARGETS/" target for the primary selection */
gtk_selection_convert (widget, GDK_SELECTION_PRIMARY, targets_atom,
GDK_CURRENT_TIME);
}
/* Signal handler called when the selections owner returns the data */
void
selection_received (GtkWidget *widget, GtkSelectionData *selection_data,
gpointer data)
{
GdkAtom *atoms;
GList *item_list;
int i;
/* **** IMPORTANT **** Check to see if retrieval succeeded */
if (selection_data->length < 0)
{
g_print (/"Selection retrieval failed//n/");
return;
}
/* Make sure we got the data in the expected form */
if (selection_data->type != GDK_SELECTION_TYPE_ATOM)
{
g_print (/"Selection ///"TARGETS///" was not returned as atoms!//n/");
return;
}
/* Print out the atoms we received */
atoms = (GdkAtom *)selection_data->data;
item_list = NULL;
for (i=0; ilength/sizeof(GdkAtom); i++)
{
char *name;
name = gdk_atom_name (atoms[i]);
if (name != NULL)
g_print (/"%s//n/",name);
else
g_print (/"(bad atom)//n/");
}
return;
}
int
main (int argc, char *argv[])
{
GtkWidget *window;
GtkWidget *button;
gtk_init (&argc, &argv);
/* Create the toplevel window */
window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
gtk_window_set_title (GTK_WINDOW (window), /"Event Box/");
gtk_container_border_width (GTK_CONTAINER (window), 10);
gtk_signal_connect (GTK_OBJECT (window), /"destroy/",
GTK_SIGNAL_FUNC (gtk_exit), NULL);
/* Create a button the user can click to get targets */
button = gtk_button_new_with_label (/"Get Targets/");
gtk_container_add (GTK_CONTAINER (window), button);
gtk_signal_connect (GTK_OBJECT(button), /"clicked/",
GTK_SIGNAL_FUNC (get_targets), NULL);
gtk_signal_connect (GTK_OBJECT(button), /"selection_received/",
GTK_SIGNAL_FUNC (selection_received), NULL);
gtk_widget_show (button);
gtk_widget_show (window);
gtk_main ();
return 0;
}
16.3 提供选取区域
提供选取区域比较复杂. 您必须注册handler, 当您被要求提供选区时, handler会被呼叫到. 对每个selection/target, 您必须呼叫:
void gtk_selection_add_handler (GtkWidget *widget,
GdkAtom selection,
GdkAtom target,
GtkSelectionFunction function,
GtkRemoveFunction remove_func,
gpointer data);
widget, selection, 及target 表示这个处理器会处理的要求. 如果remove_func不为NULL, 当信号处里器被移除时, 这个函数会被移除. 这很有用, 例如说, 给解译式语言用, 因为它会保持追踪并维护其自身的资料.
该callback函数有以下的形式:
typedef void (*GtkSelectionFunction) (GtkWidget *widget,
GtkSelectionData *selection_data,
gpointer data);
GtkSelectionData 跟上面一样, 但这一次, 我们必须要填type, format, data, 及length这几栏. (format这一栏很重要 - X server用来决定是否需要做byte-swap, 因为有X是多平台的系统, 一般8是character, 32是integer.) 这是由以下函数所完成的:
void gtk_selection_data_set (GtkSelectionData *selection_data,
GdkAtom type,
gint format,
guchar *data,
gint length);
这个函数会将资料备一份, 因此您不需要自行维护. (这就是说您不应该自己手动去填该资料结构的资料.)
您可以用以下函数设定该选区的拥有者:
gint gtk_selection_owner_set (GtkWidget *widget,
GdkAtom selection,
guint32 time);
如果有其它程式设定了该选区的拥有权, 您会收到一个/"selection_clear_event/"信号.
做为一个提供选区的例子, 以下程式将选取功能加到一个双态按钮. 当双态按钮被按下时, 该程式会设定拥有该选区. 而唯一支援的target是/"STRING/" target. 当该target被要求时, 将会返回一个显示时间的字串.
#include
#include
/* Callback when the user toggles the selection */
void
selection_toggled (GtkWidget *widget, gint *have_selection)
{
if (GTK_TOGGLE_BUTTON(widget)->active)
{
*have_selection = gtk_selection_owner_set (widget,
GDK_SELECTION_PRIMARY,
GDK_CURRENT_TIME);
/* if claiming the selection failed, we return the button to
the out state */
if (!*have_selection)
gtk_toggle_button_set_state (GTK_TOGGLE_BUTTON(widget), FALSE);
}
else
{
if (*have_selection)
{
/* Before clearing the selection by setting the owner to NULL,
we check if we are the actual owner */
if (gdk_selection_owner_get (GDK_SELECTION_PRIMARY) == widget->window)
gtk_selection_owner_set (NULL, GDK_SELECTION_PRIMARY,
GDK_CURRENT_TIME);
*have_selection = FALSE;
}
}
}
/* Called when another application claims the selection */
gint
selection_clear (GtkWidget *widget, GdkEventSelection *event,
gint *have_selection)
{
*have_selection = FALSE;
gtk_toggle_button_set_state (GTK_TOGGLE_BUTTON(widget), FALSE);
return TRUE;
}
/* Supplies the current time as the selection. */
void
selection_handle (GtkWidget *widget,
GtkSelectionData *selection_data,
gpointer data)
{
gchar *timestr;
time_t current_time;
current_time = time (NULL);
timestr = asctime (localtime(¤t_time));
/* When we return a single string, it should not be null terminated.
That will be done for us */
gtk_selection_data_set (selection_data, GDK_SELECTION_TYPE_STRING,
8, timestr, strlen(timestr));
}
int
main (int argc, char *argv[])
{
GtkWidget *window;
GtkWidget *selection_button;
static int have_selection = FALSE;
gtk_init (&argc, &argv);
/* Create the toplevel window */
window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
gtk_window_set_title (GTK_WINDOW (window), /"Event Box/");
gtk_container_border_width (GTK_CONTAINER (window), 10);
gtk_signal_connect (GTK_OBJECT (window), /"destroy/",
GTK_SIGNAL_FUNC (gtk_exit), NULL);
/* Create a toggle button to act as the selection */
selection_button = gtk_toggle_button_new_with_label (/"Claim Selection/");
gtk_container_add (GTK_CONTAINER (window), selection_button);
gtk_widget_show (selection_button);
gtk_signal_connect (GTK_OBJECT(selection_button), /"toggled/",
GTK_SIGNAL_FUNC (selection_toggled), &have_selection);
gtk_signal_connect (GTK_OBJECT(selection_button), /"selection_clear_event/",
GTK_SIGNAL_FUNC (selection_clear), &have_selection);
gtk_selection_add_handler (selection_button, GDK_SELECTION_PRIMARY,
GDK_SELECTION_TYPE_STRING,
selection_handle, NULL, NULL);
gtk_widget_show (selection_button);
gtk_widget_show (window);
gtk_main ();
return 0;
}
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.
char gchar;
short gshort;
long glong;
int gint;
char gboolean;
unsigned char guchar;
unsigned short gushort;
unsigned long gulong;
unsigned int guint;
float gfloat;
double gdouble;
long double gldouble;
void* gpointer;
gint8
guint8
gint16
guint16
gint32
guint32
17.2 双向链结串列
以下函数用来产生, 管理及销毁双向链结串列.
GList* g_list_alloc (void);
void g_list_free (GList *list);
void g_list_free_1 (GList *list);
GList* g_list_append (GList *list,
gpointer data);
GList* g_list_prepend (GList *list,
gpointer data);
GList* g_list_insert (GList *list,
gpointer data,
gint position);
GList* g_list_remove (GList *list,
gpointer data);
GList* g_list_remove_link (GList *list,
GList *link);
GList* g_list_reverse (GList *list);
GList* g_list_nth (GList *list,
gint n);
GList* g_list_find (GList *list,
gpointer data);
GList* g_list_last (GList *list);
GList* g_list_first (GList *list);
gint g_list_length (GList *list);
void g_list_foreach (GList *list,
GFunc func,
gpointer user_data);
17.3 单向链结串列
以下函数是用来管理单向链结串列:
GSList* g_slist_alloc (void);
void g_slist_free (GSList *list);
void g_slist_free_1 (GSList *list);
GSList* g_slist_append (GSList *list,
gpointer data);
GSList* g_slist_prepend (GSList *list,
gpointer data);
GSList* g_slist_insert (GSList *list,
gpointer data,
gint position);
GSList* g_slist_remove (GSList *list,
gpointer data);
GSList* g_slist_remove_link (GSList *list,
GSList *link);
GSList* g_slist_reverse (GSList *list);
GSList* g_slist_nth (GSList *list,
gint n);
GSList* g_slist_find (GSList *list,
gpointer data);
GSList* g_slist_last (GSList *list);
gint g_slist_length (GSList *list);
void g_slist_foreach (GSList *list,
GFunc func,
gpointer user_data);
17.4 记忆体管理
gpointer g_malloc (gulong size);
这是替代malloc()用的. 你不需要去检查返回值, 因为它已经帮你做好了, 保证.
gpointer g_malloc0 (gulong size);
一样, 不过会在返回之前将记忆体归零.
gpointer g_realloc (gpointer mem,
gulong size);
重定记忆体大小.
void g_free (gpointer mem);
void g_mem_profile (void);
将记忆体的使用状况写到一个档案, 不过您必须要在glib/gmem.c里面, 加#define MEM_PROFILE, 然後重新编译.
void g_mem_check (gpointer mem);
检查记忆体位置是否有效. 您必须要在glib/gmem.c上加#define MEM_CHECK, 然後重新编译.
17.5 Timers
Timer函数..
GTimer* g_timer_new (void);
void g_timer_destroy (GTimer *timer);
void g_timer_start (GTimer *timer);
void g_timer_stop (GTimer *timer);
void g_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,
gchar c);
GString* g_string_prepend (GString *string,
gchar *val);
GString* g_string_prepend_c (GString *string,
gchar c);
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//n/", 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.)
void gtk_widget_install_accelerator (GtkWidget *widget,
GtkAcceleratorTable *table,
gchar *signal_name,
gchar key,
guint8 modifiers);
void gtk_widget_remove_accelerator (GtkWidget *widget,
GtkAcceleratorTable *table,
gchar *signal_name);
void gtk_widget_activate (GtkWidget *widget);
void gtk_widget_set_name (GtkWidget *widget,
gchar *name);
gchar* gtk_widget_get_name (GtkWidget *widget);
void gtk_widget_set_sensitive (GtkWidget *widget,
gint sensitive);
void gtk_widget_set_style (GtkWidget *widget,
GtkStyle *style);
GtkStyle* gtk_widget_get_style (GtkWidget *widget);
GtkStyle* gtk_widget_get_default_style (void);
void gtk_widget_set_uposition (GtkWidget *widget,
gint x,
gint y);
void gtk_widget_set_usize (GtkWidget *widget,
gint width,
gint height);
void gtk_widget_grab_focus (GtkWidget *widget);
void gtk_widget_show (GtkWidget *widget);
void gtk_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 [= ]
# {
#
# }
#
# widget style
# widget_class style
# Here is a list of all the possible states. Note that some do not apply to
# certain widgets.
#
# NORMAL - The normal state of a widget, without the mouse over top of
# it, and not being pressed etc.
#
# PRELIGHT - When the mouse is over top of the widget, colors defined
# using this state will be in effect.
#
# ACTIVE - When the widget is pressed or clicked it will be active, and
# the attributes assigned by this tag will be in effect.
#
# INSENSITIVE - When a widget is set insensitive, and cannot be
# activated, it will take these attributes.
#
# SELECTED - When an object is selected, it takes these attributes.
#
# Given these states, we can set the attributes of the widgets in each of
# these states using the following directives.
#
# fg - Sets the foreground color of a widget.
# fg - Sets the background color of a widget.
# bg_pixmap - Sets the background of a widget to a tiled pixmap.
# font - Sets the font to be used with the given widget.
#
# This sets a style called /"button/". The name is not really important, as
# it is assigned to the actual widgets at the bottom of the file.
style /"window/"
{
#This sets the padding around the window to the pixmap specified.
#bg_pixmap[] = /"/"
bg_pixmap[NORMAL] = /"warning.xpm/"
}
style /"scale/"
{
#Sets the foreground color (font color) to red when in the /"NORMAL/"
#state.
fg[NORMAL] = { 1.0, 0, 0 }
#Sets the background pixmap of this widget to that of it/s parent.
bg_pixmap[NORMAL] = /"/"
}
style /"button/"
{
# This shows all the possible states for a button. The only one that
# doesn/t apply is the SELECTED state.
fg[PRELIGHT] = { 0, 1.0, 1.0 }
bg[PRELIGHT] = { 0, 0, 1.0 }
bg[ACTIVE] = { 1.0, 0, 0 }
fg[ACTIVE] = { 0, 1.0, 0 }
bg[NORMAL] = { 1.0, 1.0, 0 }
fg[NORMAL] = { .99, 0, .99 }
bg[INSENSITIVE] = { 1.0, 1.0, 1.0 }
fg[INSENSITIVE] = { 1.0, 0, 1.0 }
}
# In this example, we inherit the attributes of the /"button/" style and then
# override the font and background color when prelit to create a new
# /"main_button/" style.
style /"main_button/" = /"button/"
{
font = /"-adobe-helvetica-medium-r-normal--*-100-*-*-*-*-*-*/"
bg[PRELIGHT] = { 0.75, 0, 0 }
}
style /"toggle_button/" = /"button/"
{
fg[NORMAL] = { 1.0, 0, 0 }
fg[ACTIVE] = { 1.0, 0, 0 }
# This sets the background pixmap of the toggle_button to that of it/s
# parent widget (as defined in the application).
bg_pixmap[NORMAL] = /"/"
}
style /"text/"
{
bg_pixmap[NORMAL] = /"marble.xpm/"
fg[NORMAL] = { 1.0, 1.0, 1.0 }
}
style /"ruler/"
{
font = /"-adobe-helvetica-medium-r-normal--*-80-*-*-*-*-*-*/"
}
# pixmap_path /"~/.pixmaps/"
# These set the widget types to use the styles defined above.
# The widget types are listed in the class hierarchy, but could probably be
# just listed in this document for the users reference.
widget_class /"GtkWindow/" style /"window/"
widget_class /"GtkDialog/" style /"window/"
widget_class /"GtkFileSelection/" style /"window/"
widget_class /"*Gtk*Scale/" style /"scale/"
widget_class /"*GtkCheckButton*/" style /"toggle_button/"
widget_class /"*GtkRadioButton*/" style /"toggle_button/"
widget_class /"*GtkButton*/" style /"button/"
widget_class /"*Ruler/" style /"ruler/"
widget_class /"*GtkText/" style /"text/"
# This sets all the buttons that are children of the /"main window/" to
# the main_buton style. These must be documented to be taken advantage of.
widget /"main window.*GtkButton*/" style /"main_button/"
20. 写出属於您自己的物件
20.1 概说
虽 然GTK的物件基本上是够用了, 但有时您还是需要产生自己所需要的物件型态. 如果已经有一个既存的物件很接近您的需求, 那麽您可以把程式改个几行就可以达到您的需求了. 但在您决定要写一个新的物件之前, 先确认是否有人已经写过了. 这会避免重复浪费资源, 并保持物件数量达到最少, 这会使程式及介面比较统一一点. 另一方面, 一旦您写好您的物件, 要向全世界公告, 这样其它人才会受益. 最好的公告地点大概就是gtk-list了.
20.2 物件的解析
为了要产生一个新的物件, 了解GTK的运作是很重要的. 这里只简单的说一下. 详细请参照reference documentation.
GTK 物件是以流行的物件导件的观念来设计的. 不过, 依然是以C来写的. 比起用C++来说, 这可以大大改善可移植性及稳定性. 但同时, 这也意味著widget writer需要小心许多实作上的问题. 所有同一类别的物件的一般资讯 (例如所有的按钮物件)是放在 class structure. 只有一份这样的结构. 在这份结构中储存类别信号的资讯. 要支撑这样的继承, 第一栏的资料结构必须是其父类别的资料结构. 例如GtkButton的类别宣告看起来像这样:
struct _GtkButtonClass
{
GtkContainerClass parent_class;
void (* pressed) (GtkButton *button);
void (* released) (GtkButton *button);
void (* clicked) (GtkButton *button);
void (* enter) (GtkButton *button);
void (* leave) (GtkButton *button);
};
当一个按钮被看成是个container时(例如, 当它被缩放时), 其类别结构可被传到GtkContainerClass, 而其相关的栏位被用来处理信号.
对每个物件结构来说, 都有一些状况上的不同. 该结构都有一些资讯是不太一样的. 我们称此结构为object structure. 如按钮一类, 看起来像这样:
struct _GtkButton
{
GtkContainer container;
GtkWidget *child;
guint in_button : 1;
guint button_down : 1;
};
可以看到, 第一栏是其父类别的物件资料结构, 因此该结构可以传到其父类别的物件结构来处理.
20.3 产生一个组合物件
标头档
每个物件类别都有一个标头档来宣告其物件, 类别结构及其函数. 有些特性是值得指出的. 要避免重复宣告, 我们将整个标头档包成:
#ifndef __TICTACTOE_H__
#define __TICTACTOE_H__
.
.
.
#endif /* __TICTACTOE_H__ */
而且加入让C++程式不会抓狂的定义码:
#ifdef __cplusplus
extern /"C/" {
#endif /* __cplusplus */
.
.
.
#ifdef __cplusplus
}
#endif /* __cplusplus */
除 了函数及结构外, 我们宣告了三个标准巨集在标头档中 TICTACTOE(obj), TICTACTOE_CLASS(klass), 及IS_TICTACTOE(obj), 当我们传入一个指标到物件或类别结构中, 它会检查是否是我们的tictactoe物件.
这里是完整的标头档:
#ifndef __TICTACTOE_H__
#define __TICTACTOE_H__
#include
#include
#ifdef __cplusplus
extern /"C/" {
#endif /* __cplusplus */
#define TICTACTOE(obj) GTK_CHECK_CAST (obj, tictactoe_get_type (), Tictactoe)
#define TICTACTOE_CLASS(klass) GTK_CHECK_CLASS_CAST (klass, tictactoe_get_type (), TictactoeClass)
#define IS_TICTACTOE(obj) GTK_CHECK_TYPE (obj, tictactoe_get_type ())
typedef struct _Tictactoe Tictactoe;
typedef struct _TictactoeClass TictactoeClass;
struct _Tictactoe
{
GtkVBox vbox;
GtkWidget *buttons[3][3];
};
struct _TictactoeClass
{
GtkVBoxClass parent_class;
void (* tictactoe) (Tictactoe *ttt);
};
guint tictactoe_get_type (void);
GtkWidget* tictactoe_new (void);
void tictactoe_clear (Tictactoe *ttt);
#ifdef __cplusplus
}
#endif /* __cplusplus */
#endif /* __TICTACTOE_H__ */
_get_type()函数.
我们现在来继续做我们的物件. 对每个物件来说, 都有一个重要的核心函数 WIDGETNAME_get_type(). 这个函数, 当第一次被呼叫的时候, 会告诉GTK有关该物件类别, 并取得一个ID来辨视其物件类别. 在其後的呼叫中, 它会返回该ID.
guint
tictactoe_get_type ()
{
static guint ttt_type = 0;
if (!ttt_type)
{
GtkTypeInfo ttt_info =
{
/"Tictactoe/",
sizeof (Tictactoe),
sizeof (TictactoeClass),
(GtkClassInitFunc) tictactoe_class_init,
(GtkObjectInitFunc) tictactoe_init,
(GtkArgFunc) NULL,
};
ttt_type = gtk_type_unique (gtk_vbox_get_type (), &ttt_info);
}
return ttt_type;
}
GtkTypeInfo结构有以下定义:
struct _GtkTypeInfo
{
gchar *type_name;
guint object_size;
guint class_size;
GtkClassInitFunc class_init_func;
GtkObjectInitFunc object_init_func;
GtkArgFunc arg_func;
};
这资料结构自我解释的很好. 在此, 我们将会忽略掉arg_func这一栏: 它很重要, 可以允许用来给设定解译式语言来设定, 但大部份相关工作都还没有完成. 一旦GTK被正确的填入该资料结构, 它会知道如何产生某一个特别的物件类别.
The _class_init() function
WIDGETNAME_class_init()函数启始设定该物件类别的资料, 并设定给该类别信号.
enum {
TICTACTOE_SIGNAL,
LAST_SIGNAL
};
static gint tictactoe_signals[LAST_SIGNAL] = { 0 };
static void
tictactoe_class_init (TictactoeClass *class)
{
GtkObjectClass *object_class;
object_class = (GtkObjectClass*) class;
tictactoe_signals[TICTACTOE_SIGNAL] = gtk_signal_new (/"tictactoe/",
GTK_RUN_FIRST,
object_class->type,
GTK_SIGNAL_OFFSET (TictactoeClass, tictactoe),
gtk_signal_default_marshaller, GTK_ARG_NONE, 0);
gtk_object_class_add_signals (object_class, tictactoe_signals, LAST_SIGNAL);
class->tictactoe = NULL;
}
该函数只有一个信号, ``tictactoe//信号. 并非所有组合式物件都需要信号, 所以如果这是您第一次读这里, 您可以跳到下一个, 因为这里有点复杂.
gint gtk_signal_new (gchar *name,
GtkSignalRunType run_type,
gint object_type,
gint function_offset,
GtkSignalMarshaller marshaller,
GtkArgType return_val,
gint nparams,
...);
产生新讯号, 参数包含:
name: 信号名称.
run_type: 决定内定的处理器要在使用者的处理器之前处理或之後处理. 一般可以是GTK_RUN_FIRST, or GTK_RUN_LAST.
object_type: 物件的ID.
function_offset: 在类别结构中内定处理器函数位址值在记忆体中的偏移值.
marshaller: 用来触发信号处理器的函数. 对除了使用者资料外, 没有额外参数的的信号处理器来说, 我们可以用内定的marshaller函数 gtk_signal_default_marshaller.
return_val: 返回值的型态.
nparams: 信号处理器的参数数量. (不同於以上所提的两个)
...: 参数型态.
当指定型态时, 可用GtkArgType:
typedef enum
{
GTK_ARG_INVALID,
GTK_ARG_NONE,
GTK_ARG_CHAR,
GTK_ARG_SHORT,
GTK_ARG_INT,
GTK_ARG_LONG,
GTK_ARG_POINTER,
GTK_ARG_OBJECT,
GTK_ARG_FUNCTION,
GTK_ARG_SIGNAL
} GtkArgType;
The _init() function.
static void
tictactoe_init (Tictactoe *ttt)
{
GtkWidget *table;
gint i,j;
table = gtk_table_new (3, 3, TRUE);
gtk_container_add (GTK_CONTAINER(ttt), table);
gtk_widget_show (table);
for (i=0;i<3; i++)
for (j=0;j<3; j++)
{
ttt->buttons[i][j] = gtk_toggle_button_new ();
gtk_table_attach_defaults (GTK_TABLE(table), ttt->buttons[i][j],
i, i+1, j, j+1);
gtk_signal_connect (GTK_OBJECT (ttt->buttons[i][j]), /"toggled/",
GTK_SIGNAL_FUNC (tictactoe_toggle), ttt);
gtk_widget_set_usize (ttt->buttons[i][j], 20, 20);
gtk_widget_show (ttt->buttons[i][j]);
}
}
GtkWidget*
tictactoe_new ()
{
return GTK_WIDGET ( gtk_type_new (tictactoe_get_type ()));
}
void
tictactoe_clear (Tictactoe *ttt)
{
int i,j;
for (i=0;i<3;i++)
for (j=0;j<3;j++)
{
gtk_signal_handler_block_by_data (GTK_OBJECT(ttt->buttons[i][j]), ttt);
gtk_toggle_button_set_state (GTK_TOGGLE_BUTTON (ttt->buttons[i][j]),
FALSE);
gtk_signal_handler_unblock_by_data (GTK_OBJECT(ttt->buttons[i][j]), ttt);
}
}
static void
tictactoe_toggle (GtkWidget *widget, Tictactoe *ttt)
{
int i,k;
static int rwins[8][3] = { { 0, 0, 0 }, { 1, 1, 1 }, { 2, 2, 2 },
{ 0, 1, 2 }, { 0, 1, 2 }, { 0, 1, 2 },
{ 0, 1, 2 }, { 0, 1, 2 } };
static int cwins[8][3] = { { 0, 1, 2 }, { 0, 1, 2 }, { 0, 1, 2 },
{ 0, 0, 0 }, { 1, 1, 1 }, { 2, 2, 2 },
{ 0, 1, 2 }, { 2, 1, 0 } };
int success, found;
for (k=0; k<8; k++)
{
success = TRUE;
found = FALSE;
for (i=0;i<3;i++)
{
success = success &&
GTK_TOGGLE_BUTTON(ttt->buttons[rwins[k][i]][cwins[k][i]])->active;
found = found ||
ttt->buttons[rwins[k][i]][cwins[k][i]] == widget;
}
if (success && found)
{
gtk_signal_emit (GTK_OBJECT (ttt),
tictactoe_signals[TICTACTOE_SIGNAL]);
break;
}
}
}
最後, 使用Tictactoe widget的范例程式:
#include
#include /"tictactoe.h/"
/* Invoked when a row, column or diagonal is completed */
void
win (GtkWidget *widget, gpointer data)
{
g_print (/"Yay!//n/");
tictactoe_clear (TICTACTOE (widget));
}
int
main (int argc, char *argv[])
{
GtkWidget *window;
GtkWidget *ttt;
gtk_init (&argc, &argv);
window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
gtk_window_set_title (GTK_WINDOW (window), /"Aspect Frame/");
gtk_signal_connect (GTK_OBJECT (window), /"destroy/",
GTK_SIGNAL_FUNC (gtk_exit), NULL);
gtk_container_border_width (GTK_CONTAINER (window), 10);
/* Create a new Tictactoe widget */
ttt = tictactoe_new ();
gtk_container_add (GTK_CONTAINER (window), ttt);
gtk_widget_show (ttt);
/* And attach to its /"tictactoe/" signal */
gtk_signal_connect (GTK_OBJECT (ttt), /"tictactoe/",
GTK_SIGNAL_FUNC (win), NULL);
gtk_widget_show (window);
gtk_main ();
return 0;
}
20.4 从草稿中产生物件.
基本
我们的物件看起来会有点像Tictactoe物件.
/* GTK - The GIMP Toolkit
* Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the Free
* Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#ifndef __GTK_DIAL_H__
#define __GTK_DIAL_H__
#include
#include
#include
#ifdef __cplusplus
extern /"C/" {
#endif /* __cplusplus */
#define GTK_DIAL(obj) GTK_CHECK_CAST (obj, gtk_dial_get_type (), GtkDial)
#define GTK_DIAL_CLASS(klass) GTK_CHECK_CLASS_CAST (klass, gtk_dial_get_type (), GtkDialClass)
#define GTK_IS_DIAL(obj) GTK_CHECK_TYPE (obj, gtk_dial_get_type ())
typedef struct _GtkDial GtkDial;
typedef struct _GtkDialClass GtkDialClass;
struct _GtkDial
{
GtkWidget widget;
/* update policy (GTK_UPDATE_[CONTINUOUS/DELAYED/DISCONTINUOUS]) */
guint policy : 2;
/* Button currently pressed or 0 if none */
guint8 button;
/* Dimensions of dial components */
gint radius;
gint pointer_width;
/* ID of update timer, or 0 if none */
guint32 timer;
/* Current angle */
gfloat angle;
/* Old values from adjustment stored so we know when something changes */
gfloat old_value;
gfloat old_lower;
gfloat old_upper;
/* The adjustment object that stores the data for this dial */
GtkAdjustment *adjustment;
};
struct _GtkDialClass
{
GtkWidgetClass parent_class;
};
GtkWidget* gtk_dial_new (GtkAdjustment *adjustment);
guint gtk_dial_get_type (void);
GtkAdjustment* gtk_dial_get_adjustment (GtkDial *dial);
void gtk_dial_set_update_policy (GtkDial *dial,
GtkUpdateType policy);
void gtk_dial_set_adjustment (GtkDial *dial,
GtkAdjustment *adjustment);
#ifdef __cplusplus
}
#endif /* __cplusplus */
#endif /* __GTK_DIAL_H__ */
在您产生视窗後, 我们设定其型态及背景, 并放指标到物件的GdkWindow使用者资料栏上 最後一步允许GTK来分派事件给各别的物件.
static void
gtk_dial_realize (GtkWidget *widget)
{
GtkDial *dial;
GdkWindowAttr attributes;
gint attributes_mask;
g_return_if_fail (widget != NULL);
g_return_if_fail (GTK_IS_DIAL (widget));
GTK_WIDGET_SET_FLAGS (widget, GTK_REALIZED);
dial = GTK_DIAL (widget);
attributes.x = widget->allocation.x;
attributes.y = widget->allocation.y;
attributes.width = widget->allocation.width;
attributes.height = widget->allocation.height;
attributes.wclass = GDK_INPUT_OUTPUT;
attributes.window_type = GDK_WINDOW_CHILD;
attributes.event_mask = gtk_widget_get_events (widget) |
GDK_EXPOSURE_MASK | GDK_BUTTON_PRESS_MASK |
GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK |
GDK_POINTER_MOTION_HINT_MASK;
attributes.visual = gtk_widget_get_visual (widget);
attributes.colormap = gtk_widget_get_colormap (widget);
attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP;
widget->window = gdk_window_new (widget->parent->window, &attributes, attributes_mask);
widget->style = gtk_style_attach (widget->style, widget->window);
gdk_window_set_user_data (widget->window, widget);
gtk_style_set_background (widget->style, widget->window, GTK_STATE_ACTIVE);
}
大小的设定
在所有视窗被显示出来之前, GTK会先问每个子物件的大小. 该事件是由gtk_dial_size_request()所处理的. 既然我们的物件不是container物件, 而且没什麽大小约束, 就用个合理的数字就行了.
static void
gtk_dial_size_request (GtkWidget *widget,
GtkRequisition *requisition)
{
requisition->width = DIAL_DEFAULT_SIZE;
requisition->height = DIAL_DEFAULT_SIZE;
}
最後所有物件都有理想的大小. 一般会尽可能用原定大小, 但使用者会改变它的大小. 大小的改变是由gtk_dial_size_allocate().
static void
gtk_dial_size_allocate (GtkWidget *widget,
GtkAllocation *allocation)
{
GtkDial *dial;
g_return_if_fail (widget != NULL);
g_return_if_fail (GTK_IS_DIAL (widget));
g_return_if_fail (allocation != NULL);
widget->allocation = *allocation;
if (GTK_WIDGET_REALIZED (widget))
{
dial = GTK_DIAL (widget);
gdk_window_move_resize (widget->window,
allocation->x, allocation->y,
allocation->width, allocation->height);
dial->radius = MAX(allocation->width,allocation->height) * 0.45;
dial->pointer_width = dial->radius / 5;
}
}
.
gtk_dial_expose()
就如之前所提到的一样, 所有物件的绘出都是由expose事件来处理. 没什麽可多提的, 除了用gtk_draw_polygon 来画出三维阴影.
static gint
gtk_dial_expose (GtkWidget *widget,
GdkEventExpose *event)
{
GtkDial *dial;
GdkPoint points[3];
gdouble s,c;
gdouble theta;
gint xc, yc;
gint tick_length;
gint i;
g_return_val_if_fail (widget != NULL, FALSE);
g_return_val_if_fail (GTK_IS_DIAL (widget), FALSE);
g_return_val_if_fail (event != NULL, FALSE);
if (event->count > 0)
return FALSE;
dial = GTK_DIAL (widget);
gdk_window_clear_area (widget->window,
0, 0,
widget->allocation.width,
widget->allocation.height);
xc = widget->allocation.width/2;
yc = widget->allocation.height/2;
/* Draw ticks */
for (i=0; i<25; i++)
{
theta = (i*M_PI/18. - M_PI/6.);
s = sin(theta);
c = cos(theta);
tick_length = (i%6 == 0) ? dial->pointer_width : dial->pointer_width/2;
gdk_draw_line (widget->window,
widget->style->fg_gc[widget->state],
xc + c*(dial->radius - tick_length),
yc - s*(dial->radius - tick_length),
xc + c*dial->radius,
yc - s*dial->radius);
}
/* Draw pointer */
s = sin(dial->angle);
c = cos(dial->angle);
points[0].x = xc + s*dial->pointer_width/2;
points[0].y = yc + c*dial->pointer_width/2;
points[1].x = xc + c*dial->radius;
points[1].y = yc - s*dial->radius;
points[2].x = xc - s*dial->pointer_width/2;
points[2].y = yc - c*dial->pointer_width/2;
gtk_draw_polygon (widget->style,
widget->window,
GTK_STATE_NORMAL,
GTK_SHADOW_OUT,
points, 3,
TRUE);
return FALSE;
}
事件处理
最後一段程式处理各种事件, 跟我们之前所做的没有什麽太大的不同. 有两种事件会发生, 使用者滑鼠的动作及其它因素所造成的物件参数调整.
当 使用者在物件上按钮时, 我们检查是否靠近我们的指标, 如果是, 将资料存到button一栏, 并用gtk_grab_add()将所有滑鼠事件抓住. 接下来的滑鼠的动作将会被gtk_dial_update_mouse所接管.. 接下来就看我们是如何做的, /"value_changed/"事件可以用(GTK_UPDATE_CONTINUOUS)来产生, 或用gtk_timeout_add()来延迟一下(GTK_UPDATE_DELAYED), 或仅在按钮按下时反应(GTK_UPDATE_DISCONTINUOUS).
static gint
gtk_dial_button_press (GtkWidget *widget,
GdkEventButton *event)
{
GtkDial *dial;
gint dx, dy;
double s, c;
double d_parallel;
double d_perpendicular;
g_return_val_if_fail (widget != NULL, FALSE);
g_return_val_if_fail (GTK_IS_DIAL (widget), FALSE);
g_return_val_if_fail (event != NULL, FALSE);
dial = GTK_DIAL (widget);
/* Determine if button press was within pointer region - we
do this by computing the parallel and perpendicular distance of
the point where the mouse was pressed from the line passing through
the pointer */
dx = event->x - widget->allocation.width / 2;
dy = widget->allocation.height / 2 - event->y;
s = sin(dial->angle);
c = cos(dial->angle);
d_parallel = s*dy + c*dx;
d_perpendicular = fabs(s*dx - c*dy);
if (!dial->button &&
(d_perpendicular < dial->pointer_width/2) &&
(d_parallel > - dial->pointer_width))
{
gtk_grab_add (widget);
dial->button = event->button;
gtk_dial_update_mouse (dial, event->x, event->y);
}
return FALSE;
}
static gint
gtk_dial_button_release (GtkWidget *widget,
GdkEventButton *event)
{
GtkDial *dial;
g_return_val_if_fail (widget != NULL, FALSE);
g_return_val_if_fail (GTK_IS_DIAL (widget), FALSE);
g_return_val_if_fail (event != NULL, FALSE);
dial = GTK_DIAL (widget);
if (dial->button == event->button)
{
gtk_grab_remove (widget);
dial->button = 0;
if (dial->policy == GTK_UPDATE_DELAYED)
gtk_timeout_remove (dial->timer);
if ((dial->policy != GTK_UPDATE_CONTINUOUS) &&
(dial->old_value != dial->adjustment->value))
gtk_signal_emit_by_name (GTK_OBJECT (dial->adjustment), /"value_changed/");
}
return FALSE;
}
static gint
gtk_dial_motion_notify (GtkWidget *widget,
GdkEventMotion *event)
{
GtkDial *dial;
GdkModifierType mods;
gint x, y, mask;
g_return_val_if_fail (widget != NULL, FALSE);
g_return_val_if_fail (GTK_IS_DIAL (widget), FALSE);
g_return_val_if_fail (event != NULL, FALSE);
dial = GTK_DIAL (widget);
if (dial->button != 0)
{
x = event->x;
y = event->y;
if (event->is_hint || (event->window != widget->window))
gdk_window_get_pointer (widget->window, &x, &y, &mods);
switch (dial->button)
{
case 1:
mask = GDK_BUTTON1_MASK;
break;
case 2:
mask = GDK_BUTTON2_MASK;
break;
case 3:
mask = GDK_BUTTON3_MASK;
break;
default:
mask = 0;
break;
}
if (mods & mask)
gtk_dial_update_mouse (dial, x,y);
}
return FALSE;
}
static gint
gtk_dial_timer (GtkDial *dial)
{
g_return_val_if_fail (dial != NULL, FALSE);
g_return_val_if_fail (GTK_IS_DIAL (dial), FALSE);
if (dial->policy == GTK_UPDATE_DELAYED)
gtk_signal_emit_by_name (GTK_OBJECT (dial->adjustment), /"value_changed/");
return FALSE;
}
static void
gtk_dial_update_mouse (GtkDial *dial, gint x, gint y)
{
gint xc, yc;
gfloat old_value;
g_return_if_fail (dial != NULL);
g_return_if_fail (GTK_IS_DIAL (dial));
xc = GTK_WIDGET(dial)->allocation.width / 2;
yc = GTK_WIDGET(dial)->allocation.height / 2;
old_value = dial->adjustment->value;
dial->angle = atan2(yc-y, x-xc);
if (dial->angle < -M_PI/2.)
dial->angle += 2*M_PI;
if (dial->angle < -M_PI/6)
dial->angle = -M_PI/6;
if (dial->angle > 7.*M_PI/6.)
dial->angle = 7.*M_PI/6.;
dial->adjustment->value = dial->adjustment->lower + (7.*M_PI/6 - dial->angle) *
(dial->adjustment->upper - dial->adjustment->lower) / (4.*M_PI/3.);
if (dial->adjustment->value != old_value)
{
if (dial->policy == GTK_UPDATE_CONTINUOUS)
{
gtk_signal_emit_by_name (GTK_OBJECT (dial->adjustment), /"value_changed/");
}
else
{
gtk_widget_draw (GTK_WIDGET(dial), NULL);
if (dial->policy == GTK_UPDATE_DELAYED)
{
if (dial->timer)
gtk_timeout_remove (dial->timer);
dial->timer = gtk_timeout_add (SCROLL_DELAY_LENGTH,
(GtkFunction) gtk_dial_timer,
(gpointer) dial);
}
}
}
}
static void
gtk_dial_update (GtkDial *dial)
{
gfloat new_value;
g_return_if_fail (dial != NULL);
g_return_if_fail (GTK_IS_DIAL (dial));
new_value = dial->adjustment->value;
if (new_value < dial->adjustment->lower)
new_value = dial->adjustment->lower;
if (new_value > dial->adjustment->upper)
new_value = dial->adjustment->upper;
if (new_value != dial->adjustment->value)
{
dial->adjustment->value = new_value;
gtk_signal_emit_by_name (GTK_OBJECT (dial->adjustment), /"value_changed/");
}
dial->angle = 7.*M_PI/6. - (new_value - dial->adjustment->lower) * 4.*M_PI/3. /
(dial->adjustment->upper - dial->adjustment->lower);
gtk_widget_draw (GTK_WIDGET(dial), NULL);
}
static void
gtk_dial_adjustment_changed (GtkAdjustment *adjustment,
gpointer data)
{
GtkDial *dial;
g_return_if_fail (adjustment != NULL);
g_return_if_fail (data != NULL);
dial = GTK_DIAL (data);
if ((dial->old_value != adjustment->value) ||
(dial->old_lower != adjustment->lower) ||
(dial->old_upper != adjustment->upper))
{
gtk_dial_update (dial);
dial->old_value = adjustment->value;
dial->old_lower = adjustment->lower;
dial->old_upper = adjustment->upper;
}
}
static void
gtk_dial_adjustment_value_changed (GtkAdjustment *adjustment,
gpointer data)
{
GtkDial *dial;
g_return_if_fail (adjustment != NULL);
g_return_if_fail (data != NULL);
dial = GTK_DIAL (data);
if (dial->old_value != adjustment->value)
{
gtk_dial_update (dial);
dial->old_value = adjustment->value;
}
}
有可能的增强之处
这个Dial物件到目前为止有670行. 这看起来好像有不少了, 不过我们真正完成的只有一点点, 因为大部份都是标头及模子. 还是有许多可以加强的地方:
如果您试过这个物件, 您会发现滑鼠指标会一闪一闪的. 这是因为整个物件每次都重画一次. 当然了最好的方式是在offscreen pixmap上画完以後, 然後整个复制到萤幕上.
使用者应该可以用up及down按键来增加或减少其值.
如果有个按钮来增加或减少其值, 那是再好不过的了. 虽然可也以用embedded Button widgets来做, 但我们会想要按钮有auto-repeat的功能. 所有要做这一类功能的程式可以在GtkRange物件中发现.
这个Dial物件可再做进一个container物件, 带有一个子物件, 位於按钮与最下面之间. 使用者可以增加一个标签或整个物件来显示目前的值.
20.5 更多一点
关於产生一个新的物件的细部资讯在以上被提供出来. 如果您想要写一个属於自己的物件, 我想最好的范例就是GTK本身了.
问问您自己一些关於您想要写的物件:
它是否是个Container物件?
它是否有自己的视窗?
是否是个现有物件的修改?
找出一个相近的物件, 然後开始动工.
祝好运!
20.6 版权
This section on of the tutorial on writing widgets is Copyright (C) 1997 Owen Taylor
This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
21. 写GTK软体的一些技巧
这一段只是在收集一些写个好GTK软体的一些办法, 及一般的导引. 现在还没什麽作用, 因为只有短短的几句话 :)
用GNU的autoconf及automake! 它们将会是您未来的朋友 :) 我正在计画在这里写关於两者的一些简介.
22. 贡献
这份文件, 就像在此的许多好软体一样, 是由许多志愿者免费所创作出来的. 如果您觉得GTK很多地方都没有文件, 那麽您可以考虑对这份文件贡献.
如果您决定要贡献一份力量, 请将您的文章寄给我, Ian Main, slow@intergate.bc.ca. 此外, 要知道这整份文件是免费的, 而任何新增过来的文件也会是免费的.
多谢了.
23. 为此贡献的人们
在此我要对以下这些负出贡献的人们致谢.
Bawer Dagdeviren, chamele0n@geocities.com 贡献menus导引.
Raph Levien, raph@acm.org 贡献了GTK的hello world, widget packing,及其源源不绝的智慧. 他并且为这个导引文件贡献一个家.
Peter Mattis, petm@xcf.berkeley.edu 为他最简单的GTK程式.并且完成这个程式的能力 :)
Werner Koch werner.koch@guug.de 他转换原来的文字档成为SGML, 及视窗类别组织图.
Mark Crichton crichton@expert.cc.purdue.edu 贡献了menu factory程式码, 及table packing导引.
Owen Taylor mailto:owt1@cornell.edu 贡献了EventBox widget一段. 他也负责了selections的程式及导引. , 及writing your own GTK widgets的那一段. 献上荣耀给Owen!
Mark VanderBoom mailto:owt1@cornell.edu 他大部份的工作在Notebook上完成, Progress Bar, Dialogs, 及File selection widgets. 多谢Mark! 您的助益很大.
Tim Janik mailto:timj@psynet.net 感谢他在视窗物件上的整理工作. 谢谢Tim :)
对所有给我们建议及帮助我们加强本文件的人.
感谢您们.
24. 版权
这份导引文件版权所有(C) 1997 Ian Main
本程式是免费软体; 您可以在免费软体基金会GNU版权下发行或修改, 不管是这个版本, 下个版本, 或者往後的版本.
这个程式是以希望它是有用的软体的信念下发行, 但不带任何保证; 而且不带任何销路上的暗示保证或是只是故意要练练写程式. 详情请见GNU General Public License.
您应该在这个程式的同时也收到GNU General Public License; 如果没有, 请写信到the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
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 <gtk/gtk.h>
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 <gtk/gtk.h>
/* 这是个callback函数. 其资料参数在本例中被忽略
* 以下有更多的callback函数. */
void hello (GtkWidget *widget, gpointer *data)
{
g_print (/"Hello World//n/");
}
/* 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<library directory> 则gcc会去找这些目录, 看看所需要的函数库是否找得到. 例如, 在我的DebianLinux系统中, 我已经增加了 -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.
2.4 步过Hello World
现在您知道这些理论了, 我们现在来根据这些理论, 把/"hello world/"这个范例弄清楚.
这是个当按钮被按下时, 会被呼叫到的callback函数. 参数的资料没有被用到.
void hello (GtkWidget *widget, gpointer *data)
{
g_print (/"Hello World//n/");
}
这是另一个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//n/", (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,
gint expand,
gint fill,
gint padding);
第一个参数是您要把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.//n/");
/* 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, 整个排列会看起来像这样:
0 1 2
0+----------+----------+
| | |
1+----------+----------+
| | |
2+----------+----------+
坐标系统开始於左上角. 要把物件放进box中, 可用以下函数:
void gtk_table_attach (GtkTable *table,
GtkWidget *child,
gint left_attach,
gint right_attach,
gint top_attach,
gint bottom_attach,
gint xoptions,
gint yoptions,
gint xpadding,
gint ypadding);
第一个参数(/"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,
gint left_attach,
gint right_attach,
gint top_attach,
gint bottom_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,
gint row,
gint spacing);
及
void gtk_table_set_col_spacing (GtkTable *table,
gint column,
gint spacing);
对栏来说, 空格是在栏的右边. 而列则是在下面.
您也可以用以下函数来产生固定的空格.
void gtk_table_set_row_spacings (GtkTable *table,
gint spacing);
及,
void gtk_table_set_col_spacings (GtkTable *table,
gint spacing);
使用这些函数, 其最後一栏及最後一列并没有空格存在.
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里面找到.
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, 即一秒.
void gtk_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,
GtkPolicyType hscrollbar_policy,
GtkPolicyType vscrollbar_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)//n/", 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 <gtk/gtk.h>
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 <gtk/gtk.h>
/* 取得选取的档名并显示在萤幕上 */
void file_ok_sel (GtkWidget *w, GtkFileSelection *fs)
{
g_print (/"%s//n/", 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
*/
const gchar *list_item_data_key=/"list_item_data/";
/* prototypes for signal handler that we are going to connect
* to the GtkList widget
*/
static void sigh_print_selection (GtkWidget *gtklist,
gpointer func_data);
static void sigh_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;
guint i;
gchar buffer[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) {
GList static_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,
gpointer func_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//n/");
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(/"//n/");
}
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.
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[i].path);
if (accelerator) {
if (accelerator[0] == /"//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] = /"//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] = /"//0/";
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//n/", (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 *~
16. 选取区域管理
16.1 概说
GTK 所支援的其中一种行程间通讯为selections. 一个selection本身是一笔资料, 例如, 使用者选取文字的一部份, 又如, 由滑鼠抓出一些东西. 在显示器上一次只能有一个/"选取区域/", 上一个选取区域要在该区域撤销时才会生效. 其它的应用软体以几种各种不同形式取得其内容, 被称为targets. 可以有许多个selections, 但X软体只能处理一个, 即primary selection.
在大部份的状况下, GTK程式不需要自行处理选取区域. 标准物件如Entry物件, 已经有能力来自动产生选取区域, 并从其它物件撷取选取区域. 不过有时候, 您想要给其它物件有能力提供选取区域, 或当内定不支援, 想要撷取资料时.
一 个基本观念需要了解选取区域处理的是atom. 一个atom是个integer, 标记著一个字串. 有些特定的元素被X server事先定义过, 有些在gtk.h中则为固定数值, 对映到这些atoms. 例如GDK_PRIMARY_SELECTION对映到字串/"PRIMARY/". 在其它状况下, 您应该使用gdk_atom_intern()这个函数, 用以取得atom对映到string, 及gdk_atom_name(), 用以取得atom的名称. selections及targets都是一种atoms.
16.2 撷取selection
撷取selection是个非同步行程. 您可以呼叫:
gint gtk_selection_convert (GtkWidget *widget,
GdkAtom selection,
GdkAtom target,
guint32 time)
这个函数转换选取区域到target所指定的形式. time这一栏是由选取被触发到事件发生的时间. 这使我们可以保证事件发生的顺序. 您也可以用GDK_CURRENT_TIME来替代.
当选取区域的拥有者回应一个要求时, 一个/"selection_received/"信号会送到您的程式. 该信号处理器会收到一个指标GtkSelectionData 结构, 定义如下:
struct _GtkSelectionData
{
GdkAtom selection;
GdkAtom target;
GdkAtom type;
gint format;
guchar *data;
gint length;
};
selection 及target 是您在gtk_selection_convert()中所给的值. type由选区拥有者返回, 用来辨识资料型态. 可以是这些值/"STRING/", 字串, /"ATOM/", 一系列的atoms, /"INTEGER/", 一个integer, 等等. a series of atoms, /"INTEGER/", an integer, etc. 大部份targets只能返回一种型态. format是每个单位有多少的bits(如字元为8 bits, guint32为32 bits). 一般来说, 您在收资料的时候, 不必管这个值. data是返回的资料指标. length是返回资料的长度, 以byte做单位. 如果length是负值, 那麽表示有错误发生, 选取区域无效. 这在所被要求选区的程式本身不拥有或不支援的时候会发生. 该缓冲区事实上保证一定有多出一个byte; 多出来的byte永远为零, 所以不需要多复制一份字串备份.
在以下的例子中, 我们撷取特别的target, /"TARGETS/", 这是个所有selection都可以转换进去的target.
#include
void selection_received (GtkWidget *widget,
GtkSelectionData *selection_data,
gpointer data);
/* Signal handler invoked when user clicks on the /"Get Targets/" button */
void
get_targets (GtkWidget *widget, gpointer data)
{
static GdkAtom targets_atom = GDK_NONE;
/* Get the atom corresonding to the string /"TARGETS/" */
if (targets_atom == GDK_NONE)
targets_atom = gdk_atom_intern (/"TARGETS/", FALSE);
/* And request the /"TARGETS/" target for the primary selection */
gtk_selection_convert (widget, GDK_SELECTION_PRIMARY, targets_atom,
GDK_CURRENT_TIME);
}
/* Signal handler called when the selections owner returns the data */
void
selection_received (GtkWidget *widget, GtkSelectionData *selection_data,
gpointer data)
{
GdkAtom *atoms;
GList *item_list;
int i;
/* **** IMPORTANT **** Check to see if retrieval succeeded */
if (selection_data->length < 0)
{
g_print (/"Selection retrieval failed//n/");
return;
}
/* Make sure we got the data in the expected form */
if (selection_data->type != GDK_SELECTION_TYPE_ATOM)
{
g_print (/"Selection ///"TARGETS///" was not returned as atoms!//n/");
return;
}
/* Print out the atoms we received */
atoms = (GdkAtom *)selection_data->data;
item_list = NULL;
for (i=0; ilength/sizeof(GdkAtom); i++)
{
char *name;
name = gdk_atom_name (atoms[i]);
if (name != NULL)
g_print (/"%s//n/",name);
else
g_print (/"(bad atom)//n/");
}
return;
}
int
main (int argc, char *argv[])
{
GtkWidget *window;
GtkWidget *button;
gtk_init (&argc, &argv);
/* Create the toplevel window */
window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
gtk_window_set_title (GTK_WINDOW (window), /"Event Box/");
gtk_container_border_width (GTK_CONTAINER (window), 10);
gtk_signal_connect (GTK_OBJECT (window), /"destroy/",
GTK_SIGNAL_FUNC (gtk_exit), NULL);
/* Create a button the user can click to get targets */
button = gtk_button_new_with_label (/"Get Targets/");
gtk_container_add (GTK_CONTAINER (window), button);
gtk_signal_connect (GTK_OBJECT(button), /"clicked/",
GTK_SIGNAL_FUNC (get_targets), NULL);
gtk_signal_connect (GTK_OBJECT(button), /"selection_received/",
GTK_SIGNAL_FUNC (selection_received), NULL);
gtk_widget_show (button);
gtk_widget_show (window);
gtk_main ();
return 0;
}
16.3 提供选取区域
提供选取区域比较复杂. 您必须注册handler, 当您被要求提供选区时, handler会被呼叫到. 对每个selection/target, 您必须呼叫:
void gtk_selection_add_handler (GtkWidget *widget,
GdkAtom selection,
GdkAtom target,
GtkSelectionFunction function,
GtkRemoveFunction remove_func,
gpointer data);
widget, selection, 及target 表示这个处理器会处理的要求. 如果remove_func不为NULL, 当信号处里器被移除时, 这个函数会被移除. 这很有用, 例如说, 给解译式语言用, 因为它会保持追踪并维护其自身的资料.
该callback函数有以下的形式:
typedef void (*GtkSelectionFunction) (GtkWidget *widget,
GtkSelectionData *selection_data,
gpointer data);
GtkSelectionData 跟上面一样, 但这一次, 我们必须要填type, format, data, 及length这几栏. (format这一栏很重要 - X server用来决定是否需要做byte-swap, 因为有X是多平台的系统, 一般8是character, 32是integer.) 这是由以下函数所完成的:
void gtk_selection_data_set (GtkSelectionData *selection_data,
GdkAtom type,
gint format,
guchar *data,
gint length);
这个函数会将资料备一份, 因此您不需要自行维护. (这就是说您不应该自己手动去填该资料结构的资料.)
您可以用以下函数设定该选区的拥有者:
gint gtk_selection_owner_set (GtkWidget *widget,
GdkAtom selection,
guint32 time);
如果有其它程式设定了该选区的拥有权, 您会收到一个/"selection_clear_event/"信号.
做为一个提供选区的例子, 以下程式将选取功能加到一个双态按钮. 当双态按钮被按下时, 该程式会设定拥有该选区. 而唯一支援的target是/"STRING/" target. 当该target被要求时, 将会返回一个显示时间的字串.
#include
#include
/* Callback when the user toggles the selection */
void
selection_toggled (GtkWidget *widget, gint *have_selection)
{
if (GTK_TOGGLE_BUTTON(widget)->active)
{
*have_selection = gtk_selection_owner_set (widget,
GDK_SELECTION_PRIMARY,
GDK_CURRENT_TIME);
/* if claiming the selection failed, we return the button to
the out state */
if (!*have_selection)
gtk_toggle_button_set_state (GTK_TOGGLE_BUTTON(widget), FALSE);
}
else
{
if (*have_selection)
{
/* Before clearing the selection by setting the owner to NULL,
we check if we are the actual owner */
if (gdk_selection_owner_get (GDK_SELECTION_PRIMARY) == widget->window)
gtk_selection_owner_set (NULL, GDK_SELECTION_PRIMARY,
GDK_CURRENT_TIME);
*have_selection = FALSE;
}
}
}
/* Called when another application claims the selection */
gint
selection_clear (GtkWidget *widget, GdkEventSelection *event,
gint *have_selection)
{
*have_selection = FALSE;
gtk_toggle_button_set_state (GTK_TOGGLE_BUTTON(widget), FALSE);
return TRUE;
}
/* Supplies the current time as the selection. */
void
selection_handle (GtkWidget *widget,
GtkSelectionData *selection_data,
gpointer data)
{
gchar *timestr;
time_t current_time;
current_time = time (NULL);
timestr = asctime (localtime(¤t_time));
/* When we return a single string, it should not be null terminated.
That will be done for us */
gtk_selection_data_set (selection_data, GDK_SELECTION_TYPE_STRING,
8, timestr, strlen(timestr));
}
int
main (int argc, char *argv[])
{
GtkWidget *window;
GtkWidget *selection_button;
static int have_selection = FALSE;
gtk_init (&argc, &argv);
/* Create the toplevel window */
window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
gtk_window_set_title (GTK_WINDOW (window), /"Event Box/");
gtk_container_border_width (GTK_CONTAINER (window), 10);
gtk_signal_connect (GTK_OBJECT (window), /"destroy/",
GTK_SIGNAL_FUNC (gtk_exit), NULL);
/* Create a toggle button to act as the selection */
selection_button = gtk_toggle_button_new_with_label (/"Claim Selection/");
gtk_container_add (GTK_CONTAINER (window), selection_button);
gtk_widget_show (selection_button);
gtk_signal_connect (GTK_OBJECT(selection_button), /"toggled/",
GTK_SIGNAL_FUNC (selection_toggled), &have_selection);
gtk_signal_connect (GTK_OBJECT(selection_button), /"selection_clear_event/",
GTK_SIGNAL_FUNC (selection_clear), &have_selection);
gtk_selection_add_handler (selection_button, GDK_SELECTION_PRIMARY,
GDK_SELECTION_TYPE_STRING,
selection_handle, NULL, NULL);
gtk_widget_show (selection_button);
gtk_widget_show (window);
gtk_main ();
return 0;
}
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.
char gchar;
short gshort;
long glong;
int gint;
char gboolean;
unsigned char guchar;
unsigned short gushort;
unsigned long gulong;
unsigned int guint;
float gfloat;
double gdouble;
long double gldouble;
void* gpointer;
gint8
guint8
gint16
guint16
gint32
guint32
17.2 双向链结串列
以下函数用来产生, 管理及销毁双向链结串列.
GList* g_list_alloc (void);
void g_list_free (GList *list);
void g_list_free_1 (GList *list);
GList* g_list_append (GList *list,
gpointer data);
GList* g_list_prepend (GList *list,
gpointer data);
GList* g_list_insert (GList *list,
gpointer data,
gint position);
GList* g_list_remove (GList *list,
gpointer data);
GList* g_list_remove_link (GList *list,
GList *link);
GList* g_list_reverse (GList *list);
GList* g_list_nth (GList *list,
gint n);
GList* g_list_find (GList *list,
gpointer data);
GList* g_list_last (GList *list);
GList* g_list_first (GList *list);
gint g_list_length (GList *list);
void g_list_foreach (GList *list,
GFunc func,
gpointer user_data);
17.3 单向链结串列
以下函数是用来管理单向链结串列:
GSList* g_slist_alloc (void);
void g_slist_free (GSList *list);
void g_slist_free_1 (GSList *list);
GSList* g_slist_append (GSList *list,
gpointer data);
GSList* g_slist_prepend (GSList *list,
gpointer data);
GSList* g_slist_insert (GSList *list,
gpointer data,
gint position);
GSList* g_slist_remove (GSList *list,
gpointer data);
GSList* g_slist_remove_link (GSList *list,
GSList *link);
GSList* g_slist_reverse (GSList *list);
GSList* g_slist_nth (GSList *list,
gint n);
GSList* g_slist_find (GSList *list,
gpointer data);
GSList* g_slist_last (GSList *list);
gint g_slist_length (GSList *list);
void g_slist_foreach (GSList *list,
GFunc func,
gpointer user_data);
17.4 记忆体管理
gpointer g_malloc (gulong size);
这是替代malloc()用的. 你不需要去检查返回值, 因为它已经帮你做好了, 保证.
gpointer g_malloc0 (gulong size);
一样, 不过会在返回之前将记忆体归零.
gpointer g_realloc (gpointer mem,
gulong size);
重定记忆体大小.
void g_free (gpointer mem);
void g_mem_profile (void);
将记忆体的使用状况写到一个档案, 不过您必须要在glib/gmem.c里面, 加#define MEM_PROFILE, 然後重新编译.
void g_mem_check (gpointer mem);
检查记忆体位置是否有效. 您必须要在glib/gmem.c上加#define MEM_CHECK, 然後重新编译.
17.5 Timers
Timer函数..
GTimer* g_timer_new (void);
void g_timer_destroy (GTimer *timer);
void g_timer_start (GTimer *timer);
void g_timer_stop (GTimer *timer);
void g_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,
gchar c);
GString* g_string_prepend (GString *string,
gchar *val);
GString* g_string_prepend_c (GString *string,
gchar c);
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//n/", 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.)
void gtk_widget_install_accelerator (GtkWidget *widget,
GtkAcceleratorTable *table,
gchar *signal_name,
gchar key,
guint8 modifiers);
void gtk_widget_remove_accelerator (GtkWidget *widget,
GtkAcceleratorTable *table,
gchar *signal_name);
void gtk_widget_activate (GtkWidget *widget);
void gtk_widget_set_name (GtkWidget *widget,
gchar *name);
gchar* gtk_widget_get_name (GtkWidget *widget);
void gtk_widget_set_sensitive (GtkWidget *widget,
gint sensitive);
void gtk_widget_set_style (GtkWidget *widget,
GtkStyle *style);
GtkStyle* gtk_widget_get_style (GtkWidget *widget);
GtkStyle* gtk_widget_get_default_style (void);
void gtk_widget_set_uposition (GtkWidget *widget,
gint x,
gint y);
void gtk_widget_set_usize (GtkWidget *widget,
gint width,
gint height);
void gtk_widget_grab_focus (GtkWidget *widget);
void gtk_widget_show (GtkWidget *widget);
void gtk_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 [= ]
# {
#
# }
#
# widget style
# widget_class style
# Here is a list of all the possible states. Note that some do not apply to
# certain widgets.
#
# NORMAL - The normal state of a widget, without the mouse over top of
# it, and not being pressed etc.
#
# PRELIGHT - When the mouse is over top of the widget, colors defined
# using this state will be in effect.
#
# ACTIVE - When the widget is pressed or clicked it will be active, and
# the attributes assigned by this tag will be in effect.
#
# INSENSITIVE - When a widget is set insensitive, and cannot be
# activated, it will take these attributes.
#
# SELECTED - When an object is selected, it takes these attributes.
#
# Given these states, we can set the attributes of the widgets in each of
# these states using the following directives.
#
# fg - Sets the foreground color of a widget.
# fg - Sets the background color of a widget.
# bg_pixmap - Sets the background of a widget to a tiled pixmap.
# font - Sets the font to be used with the given widget.
#
# This sets a style called /"button/". The name is not really important, as
# it is assigned to the actual widgets at the bottom of the file.
style /"window/"
{
#This sets the padding around the window to the pixmap specified.
#bg_pixmap[] = /"/"
bg_pixmap[NORMAL] = /"warning.xpm/"
}
style /"scale/"
{
#Sets the foreground color (font color) to red when in the /"NORMAL/"
#state.
fg[NORMAL] = { 1.0, 0, 0 }
#Sets the background pixmap of this widget to that of it/s parent.
bg_pixmap[NORMAL] = /"/"
}
style /"button/"
{
# This shows all the possible states for a button. The only one that
# doesn/t apply is the SELECTED state.
fg[PRELIGHT] = { 0, 1.0, 1.0 }
bg[PRELIGHT] = { 0, 0, 1.0 }
bg[ACTIVE] = { 1.0, 0, 0 }
fg[ACTIVE] = { 0, 1.0, 0 }
bg[NORMAL] = { 1.0, 1.0, 0 }
fg[NORMAL] = { .99, 0, .99 }
bg[INSENSITIVE] = { 1.0, 1.0, 1.0 }
fg[INSENSITIVE] = { 1.0, 0, 1.0 }
}
# In this example, we inherit the attributes of the /"button/" style and then
# override the font and background color when prelit to create a new
# /"main_button/" style.
style /"main_button/" = /"button/"
{
font = /"-adobe-helvetica-medium-r-normal--*-100-*-*-*-*-*-*/"
bg[PRELIGHT] = { 0.75, 0, 0 }
}
style /"toggle_button/" = /"button/"
{
fg[NORMAL] = { 1.0, 0, 0 }
fg[ACTIVE] = { 1.0, 0, 0 }
# This sets the background pixmap of the toggle_button to that of it/s
# parent widget (as defined in the application).
bg_pixmap[NORMAL] = /"/"
}
style /"text/"
{
bg_pixmap[NORMAL] = /"marble.xpm/"
fg[NORMAL] = { 1.0, 1.0, 1.0 }
}
style /"ruler/"
{
font = /"-adobe-helvetica-medium-r-normal--*-80-*-*-*-*-*-*/"
}
# pixmap_path /"~/.pixmaps/"
# These set the widget types to use the styles defined above.
# The widget types are listed in the class hierarchy, but could probably be
# just listed in this document for the users reference.
widget_class /"GtkWindow/" style /"window/"
widget_class /"GtkDialog/" style /"window/"
widget_class /"GtkFileSelection/" style /"window/"
widget_class /"*Gtk*Scale/" style /"scale/"
widget_class /"*GtkCheckButton*/" style /"toggle_button/"
widget_class /"*GtkRadioButton*/" style /"toggle_button/"
widget_class /"*GtkButton*/" style /"button/"
widget_class /"*Ruler/" style /"ruler/"
widget_class /"*GtkText/" style /"text/"
# This sets all the buttons that are children of the /"main window/" to
# the main_buton style. These must be documented to be taken advantage of.
widget /"main window.*GtkButton*/" style /"main_button/"
20. 写出属於您自己的物件
20.1 概说
虽 然GTK的物件基本上是够用了, 但有时您还是需要产生自己所需要的物件型态. 如果已经有一个既存的物件很接近您的需求, 那麽您可以把程式改个几行就可以达到您的需求了. 但在您决定要写一个新的物件之前, 先确认是否有人已经写过了. 这会避免重复浪费资源, 并保持物件数量达到最少, 这会使程式及介面比较统一一点. 另一方面, 一旦您写好您的物件, 要向全世界公告, 这样其它人才会受益. 最好的公告地点大概就是gtk-list了.
20.2 物件的解析
为了要产生一个新的物件, 了解GTK的运作是很重要的. 这里只简单的说一下. 详细请参照reference documentation.
GTK 物件是以流行的物件导件的观念来设计的. 不过, 依然是以C来写的. 比起用C++来说, 这可以大大改善可移植性及稳定性. 但同时, 这也意味著widget writer需要小心许多实作上的问题. 所有同一类别的物件的一般资讯 (例如所有的按钮物件)是放在 class structure. 只有一份这样的结构. 在这份结构中储存类别信号的资讯. 要支撑这样的继承, 第一栏的资料结构必须是其父类别的资料结构. 例如GtkButton的类别宣告看起来像这样:
struct _GtkButtonClass
{
GtkContainerClass parent_class;
void (* pressed) (GtkButton *button);
void (* released) (GtkButton *button);
void (* clicked) (GtkButton *button);
void (* enter) (GtkButton *button);
void (* leave) (GtkButton *button);
};
当一个按钮被看成是个container时(例如, 当它被缩放时), 其类别结构可被传到GtkContainerClass, 而其相关的栏位被用来处理信号.
对每个物件结构来说, 都有一些状况上的不同. 该结构都有一些资讯是不太一样的. 我们称此结构为object structure. 如按钮一类, 看起来像这样:
struct _GtkButton
{
GtkContainer container;
GtkWidget *child;
guint in_button : 1;
guint button_down : 1;
};
可以看到, 第一栏是其父类别的物件资料结构, 因此该结构可以传到其父类别的物件结构来处理.
20.3 产生一个组合物件
标头档
每个物件类别都有一个标头档来宣告其物件, 类别结构及其函数. 有些特性是值得指出的. 要避免重复宣告, 我们将整个标头档包成:
#ifndef __TICTACTOE_H__
#define __TICTACTOE_H__
.
.
.
#endif /* __TICTACTOE_H__ */
而且加入让C++程式不会抓狂的定义码:
#ifdef __cplusplus
extern /"C/" {
#endif /* __cplusplus */
.
.
.
#ifdef __cplusplus
}
#endif /* __cplusplus */
除 了函数及结构外, 我们宣告了三个标准巨集在标头档中 TICTACTOE(obj), TICTACTOE_CLASS(klass), 及IS_TICTACTOE(obj), 当我们传入一个指标到物件或类别结构中, 它会检查是否是我们的tictactoe物件.
这里是完整的标头档:
#ifndef __TICTACTOE_H__
#define __TICTACTOE_H__
#include
#include
#ifdef __cplusplus
extern /"C/" {
#endif /* __cplusplus */
#define TICTACTOE(obj) GTK_CHECK_CAST (obj, tictactoe_get_type (), Tictactoe)
#define TICTACTOE_CLASS(klass) GTK_CHECK_CLASS_CAST (klass, tictactoe_get_type (), TictactoeClass)
#define IS_TICTACTOE(obj) GTK_CHECK_TYPE (obj, tictactoe_get_type ())
typedef struct _Tictactoe Tictactoe;
typedef struct _TictactoeClass TictactoeClass;
struct _Tictactoe
{
GtkVBox vbox;
GtkWidget *buttons[3][3];
};
struct _TictactoeClass
{
GtkVBoxClass parent_class;
void (* tictactoe) (Tictactoe *ttt);
};
guint tictactoe_get_type (void);
GtkWidget* tictactoe_new (void);
void tictactoe_clear (Tictactoe *ttt);
#ifdef __cplusplus
}
#endif /* __cplusplus */
#endif /* __TICTACTOE_H__ */
_get_type()函数.
我们现在来继续做我们的物件. 对每个物件来说, 都有一个重要的核心函数 WIDGETNAME_get_type(). 这个函数, 当第一次被呼叫的时候, 会告诉GTK有关该物件类别, 并取得一个ID来辨视其物件类别. 在其後的呼叫中, 它会返回该ID.
guint
tictactoe_get_type ()
{
static guint ttt_type = 0;
if (!ttt_type)
{
GtkTypeInfo ttt_info =
{
/"Tictactoe/",
sizeof (Tictactoe),
sizeof (TictactoeClass),
(GtkClassInitFunc) tictactoe_class_init,
(GtkObjectInitFunc) tictactoe_init,
(GtkArgFunc) NULL,
};
ttt_type = gtk_type_unique (gtk_vbox_get_type (), &ttt_info);
}
return ttt_type;
}
GtkTypeInfo结构有以下定义:
struct _GtkTypeInfo
{
gchar *type_name;
guint object_size;
guint class_size;
GtkClassInitFunc class_init_func;
GtkObjectInitFunc object_init_func;
GtkArgFunc arg_func;
};
这资料结构自我解释的很好. 在此, 我们将会忽略掉arg_func这一栏: 它很重要, 可以允许用来给设定解译式语言来设定, 但大部份相关工作都还没有完成. 一旦GTK被正确的填入该资料结构, 它会知道如何产生某一个特别的物件类别.
The _class_init() function
WIDGETNAME_class_init()函数启始设定该物件类别的资料, 并设定给该类别信号.
enum {
TICTACTOE_SIGNAL,
LAST_SIGNAL
};
static gint tictactoe_signals[LAST_SIGNAL] = { 0 };
static void
tictactoe_class_init (TictactoeClass *class)
{
GtkObjectClass *object_class;
object_class = (GtkObjectClass*) class;
tictactoe_signals[TICTACTOE_SIGNAL] = gtk_signal_new (/"tictactoe/",
GTK_RUN_FIRST,
object_class->type,
GTK_SIGNAL_OFFSET (TictactoeClass, tictactoe),
gtk_signal_default_marshaller, GTK_ARG_NONE, 0);
gtk_object_class_add_signals (object_class, tictactoe_signals, LAST_SIGNAL);
class->tictactoe = NULL;
}
该函数只有一个信号, ``tictactoe//信号. 并非所有组合式物件都需要信号, 所以如果这是您第一次读这里, 您可以跳到下一个, 因为这里有点复杂.
gint gtk_signal_new (gchar *name,
GtkSignalRunType run_type,
gint object_type,
gint function_offset,
GtkSignalMarshaller marshaller,
GtkArgType return_val,
gint nparams,
...);
产生新讯号, 参数包含:
name: 信号名称.
run_type: 决定内定的处理器要在使用者的处理器之前处理或之後处理. 一般可以是GTK_RUN_FIRST, or GTK_RUN_LAST.
object_type: 物件的ID.
function_offset: 在类别结构中内定处理器函数位址值在记忆体中的偏移值.
marshaller: 用来触发信号处理器的函数. 对除了使用者资料外, 没有额外参数的的信号处理器来说, 我们可以用内定的marshaller函数 gtk_signal_default_marshaller.
return_val: 返回值的型态.
nparams: 信号处理器的参数数量. (不同於以上所提的两个)
...: 参数型态.
当指定型态时, 可用GtkArgType:
typedef enum
{
GTK_ARG_INVALID,
GTK_ARG_NONE,
GTK_ARG_CHAR,
GTK_ARG_SHORT,
GTK_ARG_INT,
GTK_ARG_LONG,
GTK_ARG_POINTER,
GTK_ARG_OBJECT,
GTK_ARG_FUNCTION,
GTK_ARG_SIGNAL
} GtkArgType;
The _init() function.
static void
tictactoe_init (Tictactoe *ttt)
{
GtkWidget *table;
gint i,j;
table = gtk_table_new (3, 3, TRUE);
gtk_container_add (GTK_CONTAINER(ttt), table);
gtk_widget_show (table);
for (i=0;i<3; i++)
for (j=0;j<3; j++)
{
ttt->buttons[i][j] = gtk_toggle_button_new ();
gtk_table_attach_defaults (GTK_TABLE(table), ttt->buttons[i][j],
i, i+1, j, j+1);
gtk_signal_connect (GTK_OBJECT (ttt->buttons[i][j]), /"toggled/",
GTK_SIGNAL_FUNC (tictactoe_toggle), ttt);
gtk_widget_set_usize (ttt->buttons[i][j], 20, 20);
gtk_widget_show (ttt->buttons[i][j]);
}
}
GtkWidget*
tictactoe_new ()
{
return GTK_WIDGET ( gtk_type_new (tictactoe_get_type ()));
}
void
tictactoe_clear (Tictactoe *ttt)
{
int i,j;
for (i=0;i<3;i++)
for (j=0;j<3;j++)
{
gtk_signal_handler_block_by_data (GTK_OBJECT(ttt->buttons[i][j]), ttt);
gtk_toggle_button_set_state (GTK_TOGGLE_BUTTON (ttt->buttons[i][j]),
FALSE);
gtk_signal_handler_unblock_by_data (GTK_OBJECT(ttt->buttons[i][j]), ttt);
}
}
static void
tictactoe_toggle (GtkWidget *widget, Tictactoe *ttt)
{
int i,k;
static int rwins[8][3] = { { 0, 0, 0 }, { 1, 1, 1 }, { 2, 2, 2 },
{ 0, 1, 2 }, { 0, 1, 2 }, { 0, 1, 2 },
{ 0, 1, 2 }, { 0, 1, 2 } };
static int cwins[8][3] = { { 0, 1, 2 }, { 0, 1, 2 }, { 0, 1, 2 },
{ 0, 0, 0 }, { 1, 1, 1 }, { 2, 2, 2 },
{ 0, 1, 2 }, { 2, 1, 0 } };
int success, found;
for (k=0; k<8; k++)
{
success = TRUE;
found = FALSE;
for (i=0;i<3;i++)
{
success = success &&
GTK_TOGGLE_BUTTON(ttt->buttons[rwins[k][i]][cwins[k][i]])->active;
found = found ||
ttt->buttons[rwins[k][i]][cwins[k][i]] == widget;
}
if (success && found)
{
gtk_signal_emit (GTK_OBJECT (ttt),
tictactoe_signals[TICTACTOE_SIGNAL]);
break;
}
}
}
最後, 使用Tictactoe widget的范例程式:
#include
#include /"tictactoe.h/"
/* Invoked when a row, column or diagonal is completed */
void
win (GtkWidget *widget, gpointer data)
{
g_print (/"Yay!//n/");
tictactoe_clear (TICTACTOE (widget));
}
int
main (int argc, char *argv[])
{
GtkWidget *window;
GtkWidget *ttt;
gtk_init (&argc, &argv);
window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
gtk_window_set_title (GTK_WINDOW (window), /"Aspect Frame/");
gtk_signal_connect (GTK_OBJECT (window), /"destroy/",
GTK_SIGNAL_FUNC (gtk_exit), NULL);
gtk_container_border_width (GTK_CONTAINER (window), 10);
/* Create a new Tictactoe widget */
ttt = tictactoe_new ();
gtk_container_add (GTK_CONTAINER (window), ttt);
gtk_widget_show (ttt);
/* And attach to its /"tictactoe/" signal */
gtk_signal_connect (GTK_OBJECT (ttt), /"tictactoe/",
GTK_SIGNAL_FUNC (win), NULL);
gtk_widget_show (window);
gtk_main ();
return 0;
}
20.4 从草稿中产生物件.
基本
我们的物件看起来会有点像Tictactoe物件.
/* GTK - The GIMP Toolkit
* Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the Free
* Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#ifndef __GTK_DIAL_H__
#define __GTK_DIAL_H__
#include
#include
#include
#ifdef __cplusplus
extern /"C/" {
#endif /* __cplusplus */
#define GTK_DIAL(obj) GTK_CHECK_CAST (obj, gtk_dial_get_type (), GtkDial)
#define GTK_DIAL_CLASS(klass) GTK_CHECK_CLASS_CAST (klass, gtk_dial_get_type (), GtkDialClass)
#define GTK_IS_DIAL(obj) GTK_CHECK_TYPE (obj, gtk_dial_get_type ())
typedef struct _GtkDial GtkDial;
typedef struct _GtkDialClass GtkDialClass;
struct _GtkDial
{
GtkWidget widget;
/* update policy (GTK_UPDATE_[CONTINUOUS/DELAYED/DISCONTINUOUS]) */
guint policy : 2;
/* Button currently pressed or 0 if none */
guint8 button;
/* Dimensions of dial components */
gint radius;
gint pointer_width;
/* ID of update timer, or 0 if none */
guint32 timer;
/* Current angle */
gfloat angle;
/* Old values from adjustment stored so we know when something changes */
gfloat old_value;
gfloat old_lower;
gfloat old_upper;
/* The adjustment object that stores the data for this dial */
GtkAdjustment *adjustment;
};
struct _GtkDialClass
{
GtkWidgetClass parent_class;
};
GtkWidget* gtk_dial_new (GtkAdjustment *adjustment);
guint gtk_dial_get_type (void);
GtkAdjustment* gtk_dial_get_adjustment (GtkDial *dial);
void gtk_dial_set_update_policy (GtkDial *dial,
GtkUpdateType policy);
void gtk_dial_set_adjustment (GtkDial *dial,
GtkAdjustment *adjustment);
#ifdef __cplusplus
}
#endif /* __cplusplus */
#endif /* __GTK_DIAL_H__ */
在您产生视窗後, 我们设定其型态及背景, 并放指标到物件的GdkWindow使用者资料栏上 最後一步允许GTK来分派事件给各别的物件.
static void
gtk_dial_realize (GtkWidget *widget)
{
GtkDial *dial;
GdkWindowAttr attributes;
gint attributes_mask;
g_return_if_fail (widget != NULL);
g_return_if_fail (GTK_IS_DIAL (widget));
GTK_WIDGET_SET_FLAGS (widget, GTK_REALIZED);
dial = GTK_DIAL (widget);
attributes.x = widget->allocation.x;
attributes.y = widget->allocation.y;
attributes.width = widget->allocation.width;
attributes.height = widget->allocation.height;
attributes.wclass = GDK_INPUT_OUTPUT;
attributes.window_type = GDK_WINDOW_CHILD;
attributes.event_mask = gtk_widget_get_events (widget) |
GDK_EXPOSURE_MASK | GDK_BUTTON_PRESS_MASK |
GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK |
GDK_POINTER_MOTION_HINT_MASK;
attributes.visual = gtk_widget_get_visual (widget);
attributes.colormap = gtk_widget_get_colormap (widget);
attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP;
widget->window = gdk_window_new (widget->parent->window, &attributes, attributes_mask);
widget->style = gtk_style_attach (widget->style, widget->window);
gdk_window_set_user_data (widget->window, widget);
gtk_style_set_background (widget->style, widget->window, GTK_STATE_ACTIVE);
}
大小的设定
在所有视窗被显示出来之前, GTK会先问每个子物件的大小. 该事件是由gtk_dial_size_request()所处理的. 既然我们的物件不是container物件, 而且没什麽大小约束, 就用个合理的数字就行了.
static void
gtk_dial_size_request (GtkWidget *widget,
GtkRequisition *requisition)
{
requisition->width = DIAL_DEFAULT_SIZE;
requisition->height = DIAL_DEFAULT_SIZE;
}
最後所有物件都有理想的大小. 一般会尽可能用原定大小, 但使用者会改变它的大小. 大小的改变是由gtk_dial_size_allocate().
static void
gtk_dial_size_allocate (GtkWidget *widget,
GtkAllocation *allocation)
{
GtkDial *dial;
g_return_if_fail (widget != NULL);
g_return_if_fail (GTK_IS_DIAL (widget));
g_return_if_fail (allocation != NULL);
widget->allocation = *allocation;
if (GTK_WIDGET_REALIZED (widget))
{
dial = GTK_DIAL (widget);
gdk_window_move_resize (widget->window,
allocation->x, allocation->y,
allocation->width, allocation->height);
dial->radius = MAX(allocation->width,allocation->height) * 0.45;
dial->pointer_width = dial->radius / 5;
}
}
.
gtk_dial_expose()
就如之前所提到的一样, 所有物件的绘出都是由expose事件来处理. 没什麽可多提的, 除了用gtk_draw_polygon 来画出三维阴影.
static gint
gtk_dial_expose (GtkWidget *widget,
GdkEventExpose *event)
{
GtkDial *dial;
GdkPoint points[3];
gdouble s,c;
gdouble theta;
gint xc, yc;
gint tick_length;
gint i;
g_return_val_if_fail (widget != NULL, FALSE);
g_return_val_if_fail (GTK_IS_DIAL (widget), FALSE);
g_return_val_if_fail (event != NULL, FALSE);
if (event->count > 0)
return FALSE;
dial = GTK_DIAL (widget);
gdk_window_clear_area (widget->window,
0, 0,
widget->allocation.width,
widget->allocation.height);
xc = widget->allocation.width/2;
yc = widget->allocation.height/2;
/* Draw ticks */
for (i=0; i<25; i++)
{
theta = (i*M_PI/18. - M_PI/6.);
s = sin(theta);
c = cos(theta);
tick_length = (i%6 == 0) ? dial->pointer_width : dial->pointer_width/2;
gdk_draw_line (widget->window,
widget->style->fg_gc[widget->state],
xc + c*(dial->radius - tick_length),
yc - s*(dial->radius - tick_length),
xc + c*dial->radius,
yc - s*dial->radius);
}
/* Draw pointer */
s = sin(dial->angle);
c = cos(dial->angle);
points[0].x = xc + s*dial->pointer_width/2;
points[0].y = yc + c*dial->pointer_width/2;
points[1].x = xc + c*dial->radius;
points[1].y = yc - s*dial->radius;
points[2].x = xc - s*dial->pointer_width/2;
points[2].y = yc - c*dial->pointer_width/2;
gtk_draw_polygon (widget->style,
widget->window,
GTK_STATE_NORMAL,
GTK_SHADOW_OUT,
points, 3,
TRUE);
return FALSE;
}
事件处理
最後一段程式处理各种事件, 跟我们之前所做的没有什麽太大的不同. 有两种事件会发生, 使用者滑鼠的动作及其它因素所造成的物件参数调整.
当 使用者在物件上按钮时, 我们检查是否靠近我们的指标, 如果是, 将资料存到button一栏, 并用gtk_grab_add()将所有滑鼠事件抓住. 接下来的滑鼠的动作将会被gtk_dial_update_mouse所接管.. 接下来就看我们是如何做的, /"value_changed/"事件可以用(GTK_UPDATE_CONTINUOUS)来产生, 或用gtk_timeout_add()来延迟一下(GTK_UPDATE_DELAYED), 或仅在按钮按下时反应(GTK_UPDATE_DISCONTINUOUS).
static gint
gtk_dial_button_press (GtkWidget *widget,
GdkEventButton *event)
{
GtkDial *dial;
gint dx, dy;
double s, c;
double d_parallel;
double d_perpendicular;
g_return_val_if_fail (widget != NULL, FALSE);
g_return_val_if_fail (GTK_IS_DIAL (widget), FALSE);
g_return_val_if_fail (event != NULL, FALSE);
dial = GTK_DIAL (widget);
/* Determine if button press was within pointer region - we
do this by computing the parallel and perpendicular distance of
the point where the mouse was pressed from the line passing through
the pointer */
dx = event->x - widget->allocation.width / 2;
dy = widget->allocation.height / 2 - event->y;
s = sin(dial->angle);
c = cos(dial->angle);
d_parallel = s*dy + c*dx;
d_perpendicular = fabs(s*dx - c*dy);
if (!dial->button &&
(d_perpendicular < dial->pointer_width/2) &&
(d_parallel > - dial->pointer_width))
{
gtk_grab_add (widget);
dial->button = event->button;
gtk_dial_update_mouse (dial, event->x, event->y);
}
return FALSE;
}
static gint
gtk_dial_button_release (GtkWidget *widget,
GdkEventButton *event)
{
GtkDial *dial;
g_return_val_if_fail (widget != NULL, FALSE);
g_return_val_if_fail (GTK_IS_DIAL (widget), FALSE);
g_return_val_if_fail (event != NULL, FALSE);
dial = GTK_DIAL (widget);
if (dial->button == event->button)
{
gtk_grab_remove (widget);
dial->button = 0;
if (dial->policy == GTK_UPDATE_DELAYED)
gtk_timeout_remove (dial->timer);
if ((dial->policy != GTK_UPDATE_CONTINUOUS) &&
(dial->old_value != dial->adjustment->value))
gtk_signal_emit_by_name (GTK_OBJECT (dial->adjustment), /"value_changed/");
}
return FALSE;
}
static gint
gtk_dial_motion_notify (GtkWidget *widget,
GdkEventMotion *event)
{
GtkDial *dial;
GdkModifierType mods;
gint x, y, mask;
g_return_val_if_fail (widget != NULL, FALSE);
g_return_val_if_fail (GTK_IS_DIAL (widget), FALSE);
g_return_val_if_fail (event != NULL, FALSE);
dial = GTK_DIAL (widget);
if (dial->button != 0)
{
x = event->x;
y = event->y;
if (event->is_hint || (event->window != widget->window))
gdk_window_get_pointer (widget->window, &x, &y, &mods);
switch (dial->button)
{
case 1:
mask = GDK_BUTTON1_MASK;
break;
case 2:
mask = GDK_BUTTON2_MASK;
break;
case 3:
mask = GDK_BUTTON3_MASK;
break;
default:
mask = 0;
break;
}
if (mods & mask)
gtk_dial_update_mouse (dial, x,y);
}
return FALSE;
}
static gint
gtk_dial_timer (GtkDial *dial)
{
g_return_val_if_fail (dial != NULL, FALSE);
g_return_val_if_fail (GTK_IS_DIAL (dial), FALSE);
if (dial->policy == GTK_UPDATE_DELAYED)
gtk_signal_emit_by_name (GTK_OBJECT (dial->adjustment), /"value_changed/");
return FALSE;
}
static void
gtk_dial_update_mouse (GtkDial *dial, gint x, gint y)
{
gint xc, yc;
gfloat old_value;
g_return_if_fail (dial != NULL);
g_return_if_fail (GTK_IS_DIAL (dial));
xc = GTK_WIDGET(dial)->allocation.width / 2;
yc = GTK_WIDGET(dial)->allocation.height / 2;
old_value = dial->adjustment->value;
dial->angle = atan2(yc-y, x-xc);
if (dial->angle < -M_PI/2.)
dial->angle += 2*M_PI;
if (dial->angle < -M_PI/6)
dial->angle = -M_PI/6;
if (dial->angle > 7.*M_PI/6.)
dial->angle = 7.*M_PI/6.;
dial->adjustment->value = dial->adjustment->lower + (7.*M_PI/6 - dial->angle) *
(dial->adjustment->upper - dial->adjustment->lower) / (4.*M_PI/3.);
if (dial->adjustment->value != old_value)
{
if (dial->policy == GTK_UPDATE_CONTINUOUS)
{
gtk_signal_emit_by_name (GTK_OBJECT (dial->adjustment), /"value_changed/");
}
else
{
gtk_widget_draw (GTK_WIDGET(dial), NULL);
if (dial->policy == GTK_UPDATE_DELAYED)
{
if (dial->timer)
gtk_timeout_remove (dial->timer);
dial->timer = gtk_timeout_add (SCROLL_DELAY_LENGTH,
(GtkFunction) gtk_dial_timer,
(gpointer) dial);
}
}
}
}
static void
gtk_dial_update (GtkDial *dial)
{
gfloat new_value;
g_return_if_fail (dial != NULL);
g_return_if_fail (GTK_IS_DIAL (dial));
new_value = dial->adjustment->value;
if (new_value < dial->adjustment->lower)
new_value = dial->adjustment->lower;
if (new_value > dial->adjustment->upper)
new_value = dial->adjustment->upper;
if (new_value != dial->adjustment->value)
{
dial->adjustment->value = new_value;
gtk_signal_emit_by_name (GTK_OBJECT (dial->adjustment), /"value_changed/");
}
dial->angle = 7.*M_PI/6. - (new_value - dial->adjustment->lower) * 4.*M_PI/3. /
(dial->adjustment->upper - dial->adjustment->lower);
gtk_widget_draw (GTK_WIDGET(dial), NULL);
}
static void
gtk_dial_adjustment_changed (GtkAdjustment *adjustment,
gpointer data)
{
GtkDial *dial;
g_return_if_fail (adjustment != NULL);
g_return_if_fail (data != NULL);
dial = GTK_DIAL (data);
if ((dial->old_value != adjustment->value) ||
(dial->old_lower != adjustment->lower) ||
(dial->old_upper != adjustment->upper))
{
gtk_dial_update (dial);
dial->old_value = adjustment->value;
dial->old_lower = adjustment->lower;
dial->old_upper = adjustment->upper;
}
}
static void
gtk_dial_adjustment_value_changed (GtkAdjustment *adjustment,
gpointer data)
{
GtkDial *dial;
g_return_if_fail (adjustment != NULL);
g_return_if_fail (data != NULL);
dial = GTK_DIAL (data);
if (dial->old_value != adjustment->value)
{
gtk_dial_update (dial);
dial->old_value = adjustment->value;
}
}
有可能的增强之处
这个Dial物件到目前为止有670行. 这看起来好像有不少了, 不过我们真正完成的只有一点点, 因为大部份都是标头及模子. 还是有许多可以加强的地方:
如果您试过这个物件, 您会发现滑鼠指标会一闪一闪的. 这是因为整个物件每次都重画一次. 当然了最好的方式是在offscreen pixmap上画完以後, 然後整个复制到萤幕上.
使用者应该可以用up及down按键来增加或减少其值.
如果有个按钮来增加或减少其值, 那是再好不过的了. 虽然可也以用embedded Button widgets来做, 但我们会想要按钮有auto-repeat的功能. 所有要做这一类功能的程式可以在GtkRange物件中发现.
这个Dial物件可再做进一个container物件, 带有一个子物件, 位於按钮与最下面之间. 使用者可以增加一个标签或整个物件来显示目前的值.
20.5 更多一点
关於产生一个新的物件的细部资讯在以上被提供出来. 如果您想要写一个属於自己的物件, 我想最好的范例就是GTK本身了.
问问您自己一些关於您想要写的物件:
它是否是个Container物件?
它是否有自己的视窗?
是否是个现有物件的修改?
找出一个相近的物件, 然後开始动工.
祝好运!
20.6 版权
This section on of the tutorial on writing widgets is Copyright (C) 1997 Owen Taylor
This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
21. 写GTK软体的一些技巧
这一段只是在收集一些写个好GTK软体的一些办法, 及一般的导引. 现在还没什麽作用, 因为只有短短的几句话 :)
用GNU的autoconf及automake! 它们将会是您未来的朋友 :) 我正在计画在这里写关於两者的一些简介.
22. 贡献
这份文件, 就像在此的许多好软体一样, 是由许多志愿者免费所创作出来的. 如果您觉得GTK很多地方都没有文件, 那麽您可以考虑对这份文件贡献.
如果您决定要贡献一份力量, 请将您的文章寄给我, Ian Main, slow@intergate.bc.ca. 此外, 要知道这整份文件是免费的, 而任何新增过来的文件也会是免费的.
多谢了.
23. 为此贡献的人们
在此我要对以下这些负出贡献的人们致谢.
Bawer Dagdeviren, chamele0n@geocities.com 贡献menus导引.
Raph Levien, raph@acm.org 贡献了GTK的hello world, widget packing,及其源源不绝的智慧. 他并且为这个导引文件贡献一个家.
Peter Mattis, petm@xcf.berkeley.edu 为他最简单的GTK程式.并且完成这个程式的能力 :)
Werner Koch werner.koch@guug.de 他转换原来的文字档成为SGML, 及视窗类别组织图.
Mark Crichton crichton@expert.cc.purdue.edu 贡献了menu factory程式码, 及table packing导引.
Owen Taylor mailto:owt1@cornell.edu 贡献了EventBox widget一段. 他也负责了selections的程式及导引. , 及writing your own GTK widgets的那一段. 献上荣耀给Owen!
Mark VanderBoom mailto:owt1@cornell.edu 他大部份的工作在Notebook上完成, Progress Bar, Dialogs, 及File selection widgets. 多谢Mark! 您的助益很大.
Tim Janik mailto:timj@psynet.net 感谢他在视窗物件上的整理工作. 谢谢Tim :)
对所有给我们建议及帮助我们加强本文件的人.
感谢您们.
24. 版权
这份导引文件版权所有(C) 1997 Ian Main
本程式是免费软体; 您可以在免费软体基金会GNU版权下发行或修改, 不管是这个版本, 下个版本, 或者往後的版本.
这个程式是以希望它是有用的软体的信念下发行, 但不带任何保证; 而且不带任何销路上的暗示保证或是只是故意要练练写程式. 详情请见GNU General Public License.
您应该在这个程式的同时也收到GNU General Public License; 如果没有, 请写信到the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
相关推荐
GTK 入门级详细资料 GTK+ 是你学习的好资料 什么是 GTK+ 1.5. GTK+ 中的 + 是什么意思 ? 1.6. GTK+, GDK 和 GLib 中的 G 代表什么
我这个完全是自己一个人完成的一个小型的demo,对于那些想要gtk入门的同学有一定的帮助,当然主题语言是用c完成的,以及有一个简单的makefile,希望也能对那些想要进入嵌入式编程的人有一定参考意义;由于这个代码...
《Linux/Unix GTK 入门导引》 GTK(GIMP Toolkit)起源于GIMP(General Image Manipulation Program)项目的开发工具集。GTK构建在GDK(GIMP Drawing Kit)之上,它对Xlib的功能进行了封装,使得程序员可以更方便地...
1天内学会gtk。gtk的各种控件的使用案例。简单易学。交大志盟www.zmemb.com 交大志盟
GTK,全称GIMP Toolkit,是一个用于创建图形用户界面(GUI)的开源工具包,广泛...通过阅读和实践“GTK入门教程”,你将能够创建出具有专业外观和功能的跨平台GUI应用,进一步深入探索图形用户界面的设计与开发世界。
### Linux 下 GTK 学习资料知识点详解 #### 1.1 空白 GtkWindow 在本节中,我们通过一个简单的示例介绍了如何创建一个基本的`GtkWindow`对象。`GtkWindow`是GTK+图形用户界面工具包中的一个核心组件,用于表示窗口...
### GTK (GIMP Toolkit) 入门知识点梳理 #### 1. GTK 概述 - **GTK**(GIMP Toolkit)是一种广泛应用于图形用户界面(GUI)开发的工具包。 - 它基于LGPL(GNU宽通用公共许可证)发布,这使得GTK可以用于开发开源...
"Gtk资料大全,包含gtk各方面资料"这个压缩包,从其标题和描述来看,应该包含了全面的GTK学习资源,包括文档、教程和可能的代码示例,旨在帮助开发者深入理解和掌握GTK,以便开发基于GTK的图形界面应用程序。...
在编程领域,GTK(GIMP Toolkit)是一个广泛使用的开源GUI构建工具包,主要应用于Linux、Unix-like系统,但也可以在Windows和macOS上运行。GTK提供了丰富的控件和设计元素,使得开发者能够创建美观且功能丰富的图形...
GTK,全称GIMP Toolkit,是一个用于创建图形用户界面的开源工具包,广泛应用于Linux...如果你打算学习或使用GTK开发,这个压缩包将是一个很好的起点,它可能会提供你需要的所有基础资源,帮助你快速入门并深入理解GTK。
GTK+(GIMP Toolkit)是一套源码以LGPL许可协议分发、跨平台的图形工具包。最初是为GIMP写的,已成为一个功能强大、设计灵活的一个通用图形库,是GNU/Linux下开发图形界面的应用程序的主流开发工具之一。并且,GTK+也...
总的来说,这个“GTK学习”的资料集合为初学者提供了一个全面了解和掌握GTK的起点,不仅涵盖了GTK基础控件的使用,还深入到了GTK界面美化和定制的高级主题,对于想要在Linux环境下开发GUI应用的人来说,是一份非常有...
一本GTK+开发的入门书,内容比较全面详细。
这个“GTK+ 2.0 入门完美教程”是为初学者准备的一份资源,旨在帮助他们快速掌握GTK+的基础知识和应用技巧。 GTK+ 是GIMP Toolkit的缩写,最初是为了开发图像处理软件GIMP而设计的,后来发展成为一个独立且强大的...
GTK编程资料(二合一) Gtk+_Programming_in_C.pdf(英文版) GTK+_and_Glade3_GUI_Programming_Tutorial--中文系列.pdf
GTK皮肤技术手记资料是一份深入探讨GTK图形用户界面库如何实现个性化皮肤和不规则窗口设计的资源集合。GTK,全称GIMP Toolkit,是一个广泛使用的开源GUI库,支持多种操作系统,如Linux、Windows和macOS。这个资料包...
这个“GtK教程资料汇总”包含了丰富的学习资源,旨在帮助开发者深入理解和熟练运用GTK进行GUI程序开发。 GTK的起源可以追溯到1990年代,当时是为图像处理软件GIMP设计的,但随着时间的发展,它逐渐成为一个独立且...
总的来说,这个"gtk资料打包"集合了丰富的GTK学习资源,适合从入门到进阶的开发者。通过学习这些材料,你可以熟练掌握GTK库,开发出美观且功能强大的跨平台应用程序。无论是对个人项目还是企业级软件开发,这些资源...