GTK+ examples


1. 簡介
GTK (GIMP Toolkit) 起源於開發用來做爲GIMP (General Image Manipulation Program)的一套工具. GTK建立在GDK (GIMP Drawing Kit)的上層, 基本上是將Xlib功能包裝起來. 它被稱爲GIMP toolkit是因爲原來是寫來開發GIMP, 但現在被許多免費軟體計劃所使用. 原作者爲

Peter Mattis [email protected]
Spencer Kimball [email protected]
Josh MacDonald [email protected]

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(&current_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, [email protected]. 此外, 要知道這整份文件是免費的, 而任何新增過來的文件也會是免費的.

多謝了.

23. 爲此貢獻的人們
在此我要對以下這些負出貢獻的人們致謝.


Bawer Dagdeviren, [email protected] 貢獻menus導引.
Raph Levien, [email protected] 貢獻了GTK的hello world, widget packing,及其源源不絕的智慧. 他並且爲這個導引文件貢獻一個家.
Peter Mattis, [email protected] 爲他最簡單的GTK程式.並且完成這個程式的能力 :)
Werner Koch [email protected] 他轉換原來的文字檔成爲SGML, 及視窗類別組織圖.
Mark Crichton [email protected] 貢獻了menu factory程式碼, 及table packing導引.
Owen Taylor mailto:[email protected] 貢獻了EventBox widget一段. 他也負責了selections的程式及導引. , 及writing your own GTK widgets的那一段. 獻上榮耀給Owen!
Mark VanderBoom mailto:[email protected] 他大部份的工作在Notebook上完成, Progress Bar, Dialogs, 及File selection widgets. 多謝Mark! 您的助益很大.
Tim Janik mailto:[email protected] 感謝他在視窗物件上的整理工作. 謝謝Tim :)
對所有給我們建議及幫助我們加強本文件的人.

感謝您們.

24. 版權

這份導引文件版權所有(C) 1997 Ian Main



from:http://aijuans1.iteye.com/blog/1536691

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章