亚洲av成人无遮挡网站在线观看,少妇性bbb搡bbb爽爽爽,亚洲av日韩精品久久久久久,兔费看少妇性l交大片免费,无码少妇一区二区三区

  免費(fèi)注冊(cè) 查看新帖 |

Chinaunix

  平臺(tái) 論壇 博客 文庫
最近訪問板塊 發(fā)新帖
查看: 1291 | 回復(fù): 0
打印 上一主題 下一主題

GTK入門 [復(fù)制鏈接]

論壇徽章:
0
跳轉(zhuǎn)到指定樓層
1 [收藏(0)] [報(bào)告]
發(fā)表于 2011-12-20 09:44 |只看該作者 |倒序?yàn)g覽
1. 簡(jiǎn)介
GTK (GIMP Toolkit) 起源於開發(fā)用來做為GIMP (General Image Manipulation Program)的一套工具. GTK建立在GDK (GIMP Drawing Kit)的上層, 基本上是將Xlib功能包裝起來. 它被稱為GIMP toolkit是因?yàn)樵瓉硎菍憗黹_發(fā)GIMP, 但現(xiàn)在被許多免費(fèi)軟體計(jì)劃所使用. 原作者為 

Peter Mattis petm@xcf.berkeley.edu 
Spencer Kimball spencer@xcf.berkeley.edu 
Josh MacDonald jmacd@xcf.berkeley.edu 

GTK基本上是物件導(dǎo)向應(yīng)用軟體程式設(shè)計(jì)介面(API). 雖然完全用C所寫成, 他是用classes及callback函數(shù)的觀念所實(shí)作出來的(指向該函數(shù)). 

還 有另一個(gè)被稱為glib的函數(shù)庫被用到, 該函數(shù)庫包涵了一些標(biāo)準(zhǔn)X函數(shù)的替代函數(shù), 及一些額外的處理鏈結(jié)表的函數(shù)等等. 這些替代函數(shù)是用來增加GTK的可移植性, 因?yàn)橛行┖瘮?shù)需要用到非標(biāo)準(zhǔn)的功能, 諸如g_strerror(). 有些則包含一些libc版本的加強(qiáng)的功能, 諸如g_malloc有加強(qiáng)的除錯(cuò)功能. 

這份導(dǎo)引是盡可能去詳盡描述GTK的功能, 雖然實(shí)在沒有辦法盡善盡美. 這份導(dǎo)引假設(shè)讀者對(duì)C語言有很相當(dāng)?shù)幕A(chǔ), 并且知道如何去寫C語言程式. 如果讀者有過X的程式經(jīng)驗(yàn), 會(huì)大大有幫助, 但并非絕對(duì)需要 (譯注: 這一點(diǎn)就好像是要先學(xué)MFC或SDK的問題一樣). 如果您以GTK做為進(jìn)入X程式設(shè)計(jì)的入門的話, 請(qǐng)給我們一些建議, 有關(guān)於您在本導(dǎo)引所學(xué)到及發(fā)現(xiàn)的東西, 及過程中有何困擾. 同時(shí), 目前GTK也有C++ API(GTK--)正在發(fā)展, 所以如果您喜歡用C++, 您可能要先去看一看. 同時(shí)也有一套Objective C wrapper, guile bindings版本也有, 但我不建議您走這條路. 

同時(shí)我也很想知道, 您在由本文學(xué)習(xí)GTK上有何問題, 我會(huì)感謝您告訴我如何改進(jìn)這些種種的缺點(diǎn).


2. 開始
第 一件要做的是當(dāng)然是取得一份GTK的原始碼并且安裝進(jìn)您的系統(tǒng)中. 您可以從GIMP取得一份發(fā)行版, 或者是從Peter Mattis\"s的\"家中\(zhòng)" ftp.xcf.berkely.edu/pub/pmattis(however, it has been changed to ftp.gimp.org)取得一份. GTK使用GNU的autoconf來設(shè)定. 一但您解開檔案, 輸入configure --help來看看選項(xiàng)表列. 

在介紹GTK的一開始, 我們盡可能挑最簡(jiǎn)單的程式. 這個(gè)程式將會(huì)產(chǎn)生200x200點(diǎn)的視窗, 而且沒辦法離開, 除非從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;
}

所有程式理所當(dāng)然一定會(huì)包含gtk/gtk.h, 其中宣告了所有變數(shù), 函數(shù), 及資料及結(jié)構(gòu). 這些東西您會(huì)在您的GTK應(yīng)用軟體中用到. 

下一行 


gtk_init (&argc, &argv);

呼 叫函數(shù)gtk_init(gint *argc, gchar ***argv)將會(huì)啟動(dòng)GTK. 該函數(shù)設(shè)定了一些內(nèi)定的值, 并且後續(xù)交給gdk_init(gint *argc, gchar ***argv) 繼續(xù)處理. 該函數(shù)啟動(dòng)了一些函數(shù)庫以供使用, 設(shè)定了內(nèi)定的信號(hào)處理, 檢查傳給您的程式的命令列參數(shù). 看看以下: 


--display 
--debug-level 
--no-xshm 
--sync 
--show-events 
--no-show-events 
這些參數(shù)將會(huì)從參數(shù)表中刪去, 所剩下的會(huì)傳給您做後續(xù)的處理. 這樣會(huì)產(chǎn)生標(biāo)準(zhǔn)的參數(shù)表(除了GTK所使用的)以供您使用. 

下面這兩行程式會(huì)產(chǎn)生并顯示一個(gè)視窗. 


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

GTK_WINDOW_TOPLEVEL參數(shù)指定了我們承習(xí)視窗管理程式的外觀. 即便我們產(chǎn)生一個(gè)0x0大小的視窗, 沒有子視窗的視窗內(nèi)定被設(shè)為200x200, 如此我們依然可以處理它. 

gtk_widget_show()函數(shù), 讓GTK知道, 我們已經(jīng)處理完設(shè)定其屬性的工作, 并且可以顯示它. 

最後一行進(jìn)入GTK的主要處理回圈. 


gtk_main ();

gtk_main()是個(gè)在每個(gè)GTK應(yīng)用軟體中都會(huì)看到的一個(gè)函數(shù). 當(dāng)控制到達(dá)這里, GTK會(huì)\"睡\"一下來等待X事件的發(fā)生(諸如像按鍵被按下). 在我們最簡(jiǎn)單的例子里面, 事件會(huì)被忽略掉. 因?yàn)槲覀儧]有處理它. 



2.1 用GTK來寫Hello World 
好, 現(xiàn)在我們來寫一個(gè)有一個(gè)視窗物件的視窗(一個(gè)按鈕). 這是個(gè)GTK的標(biāo)準(zhǔn)hello world. 這會(huì)建立起一個(gè)新的GTK軟體的良好基礎(chǔ). 



#include <gtk/gtk.h>

/* 這是個(gè)callback函數(shù). 其資料參數(shù)在本例中被忽略
* 以下有更多的callback函數(shù). */
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用以儲(chǔ)存視窗物件形態(tài) */
GtkWidget *window;
GtkWidget *button;

/* 這在所有GTK應(yīng)用軟體中用到. 參數(shù)由命令列中解譯出來并且送到該應(yīng)用軟體中. */

gtk_init (&argc, &argv);

/* 產(chǎn)生新視窗 */
window = gtk_window_new (GTK_WINDOW_TOPLEVEL);

/* 當(dāng)視窗收到\"destroy\"信號(hào)時(shí)(可由該軟體或視窗管理程式所送出)
所會(huì)被呼叫到的destroy函數(shù)一如以下所定義的一般.
送到該函數(shù)的資料將會(huì)是NULL,并且在該函數(shù)中被忽略 */

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

/* 設(shè)定視窗的邊框的寬度 */
gtk_container_border_width (GTK_CONTAINER (window), 10);

/* 產(chǎn)生一個(gè)新的按鈕并帶有\(zhòng)"Hello World\"的字在上面. */
button = gtk_button_new_with_label (\"Hello World\");

/* 當(dāng)該按鍵收到\"clicked\"信號(hào), 它會(huì)呼叫hello()這個(gè)函數(shù).
并且以NULL做為其參數(shù). hello()函數(shù)在以上已定義過. */

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

/* 這會(huì)導(dǎo)致當(dāng)\"clicked\"這個(gè)按鈕被按下的時(shí)候,
呼叫g(shù)tk_widget_destroy(window)而使該視窗被關(guān)閉
當(dāng)然了, 關(guān)閉的信號(hào)會(huì)從此處或視窗管理程式所送來 */
gtk_signal_connect_object (GTK_OBJECT (button), \"clicked\",
GTK_SIGNAL_FUNC (gtk_widget_destroy),
GTK_OBJECT (window));

/* 這個(gè)動(dòng)作會(huì)把這個(gè)按鈕結(jié)合到該視窗(a gtk container). */
gtk_container_add (GTK_CONTAINER (window), button);

/* 最後一步是顯示最新產(chǎn)生的視窗物件... */
gtk_widget_show (button);

/* 及該視窗 */
gtk_widget_show (window);

/* 所有GTK程式都一定要有g(shù)tk_main(). 所有控制結(jié)束於此并等帶事件的發(fā)生
(像按下一鍵或滑鼠的移動(dòng)). */
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

函數(shù)庫必須在內(nèi)定的搜尋路徑內(nèi), 如果找不到, -L<library directory> 則gcc會(huì)去找這些目錄, 看看所需要的函數(shù)庫是否找得到. 例如, 在我的Debian Linux系統(tǒng)中, 我已經(jīng)增加了 -L/usr/X11R6/lib用來尋找X11函數(shù)庫. 

以下函數(shù)庫是很重要的. linker在處理之前, 必須知道什麼函數(shù)要用那一個(gè)函數(shù)庫. 

函數(shù)庫如下: 

glib函數(shù)庫(-lglib), 包含一些有用的函數(shù), 這個(gè)例子中只用到g_print(), 因?yàn)镚TK是建在glib之上, 所以您幾乎都一定會(huì)用到它. 詳見glib一段. 
GDK函數(shù)庫(-lgdk), Xlib的包裝程式. 
GTK函數(shù)庫(-lgtk), 視窗物件函數(shù)庫, 基於GDK之上. 
xlib函數(shù)庫(-lXlib) 基本上為GDK所用. 
Xext函數(shù)庫(-lXext). 包含了shared memory pixmaps及其它的一些X extensions. 
math函數(shù)庫(-lm). 為GTK所用, 有多方面用途. 

2.3 Signals及Callbacks的原理 
在我們更進(jìn)一步探討hello world之前, 我們要講一下事件(events)及回呼函數(shù)(callbacks). GTK本身是個(gè)事件驅(qū)動(dòng)的工具, 這意味著它會(huì)在gtk_main進(jìn)入停歇狀態(tài), 一直到一個(gè)事件發(fā)生, 并且將控制交給適當(dāng)?shù)暮瘮?shù)來處理. 

控 制權(quán)的交出是由\"signals\"來決定的. 當(dāng)事件發(fā)生, 諸如按下滑鼠的一個(gè)按鍵, 對(duì)應(yīng)的信號(hào)會(huì)由該視窗物件所送出. 這便是GTK的主要工作. 要使一個(gè)按下的動(dòng)作執(zhí)行一個(gè)命令, 我們?cè)O(shè)定一個(gè)信號(hào)處理函數(shù)來擷取這個(gè)信號(hào), 并且呼叫適當(dāng)?shù)暮瘮?shù). 這工作是由像以下的函數(shù)來完成的: 


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

其第一個(gè)參數(shù)是會(huì)送出信號(hào)的物件, 第二個(gè)是希望接取的信號(hào)名稱. 第三個(gè)是當(dāng)信號(hào)送出時(shí)的接取函數(shù), 第四個(gè)則是要送給該函數(shù)的資料. 

第三個(gè)參數(shù)被稱為\"callback function\", 而且必需是以下的形式: 


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

第一個(gè)參數(shù)是指向該物件的指標(biāo), 第二個(gè)是在gtk_signal_connect()的最後一個(gè)參數(shù). 

另外一個(gè)在hello world中有用到的函數(shù)是: 


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

gtk_signal_connect_object()跟gtk_signal_connect()一樣, 除了callback函術(shù)只有一個(gè)參數(shù), 一個(gè)指向GTK物件的指標(biāo). 所以當(dāng)使用這個(gè)函數(shù)來接到信號(hào)時(shí), 該callback函數(shù)必須是以下形式: 


void callback_func (GtkObject *object);

一般這個(gè)object是個(gè)widget(物件). 我們一般不設(shè)定callback給gtk_signal_connect_object. 他們是用來呼叫GTK函數(shù)來接受單一物件(widget or object)做為參數(shù). 

有 兩個(gè)函數(shù)來連接信號(hào)的目的只是希望允許callbacks可以有不同數(shù)量的參數(shù). 許多GTK函數(shù)僅接受一個(gè)GtkWidget指標(biāo)做為參數(shù), 所以您可以使用gtk_signal_connect_object()來使用這些函數(shù), 而在您的函數(shù)里面, 您會(huì)需要額外的資料提供給callback. 


2.4 步過Hello World 
現(xiàn)在您知道這些理論了, 我們現(xiàn)在來根據(jù)這些理論, 把\"hello world\"這個(gè)范例弄清楚. 

這是個(gè)當(dāng)按鈕被按下時(shí), 會(huì)被呼叫到的callback函數(shù). 參數(shù)的資料沒有被用到. 


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

這是另一個(gè)callback函數(shù), 它會(huì)呼叫g(shù)tk_main_quit()來離開程式. 

void destroy (GtkWidget *widget, gpointer *data)
{
gtk_main_quit ();
}

int main (int argc, char *argv[])
{

下個(gè)部份, 宣告一個(gè)指標(biāo)給GtkWidget. 這是準(zhǔn)備用來產(chǎn)生視窗及按鈕的. 

GtkWidget *window;
GtkWidget *button;

這里是我們的gtk_init. 設(shè)定GTK toolkit初始值. 

gtk_init (&argc, &argv);

產(chǎn)生新視窗. 這是蠻直接的. 記憶體配置給GtkWidget * window使其成為有效的資料. 它設(shè)定一個(gè)新的視窗, 但在我們呼叫g(shù)tk_widget_show(window)之前不會(huì)顯示. 

window = gtk_window_new (GTK_WINDOW_TOPLEVEL);

這 里是將object(window)連接到信號(hào)處理器的范例. 此處\"destroy\"是該信號(hào). 該信號(hào)是window manager要銷去這個(gè)視窗時(shí), 或我們送出gtk_widget_destroy()時(shí)會(huì)產(chǎn)生的. 當(dāng)我們這樣設(shè)定時(shí), 我們可同時(shí)處理兩種狀況. 這里我們使用destroy函數(shù), 這使我們可以使用window manager來離開這個(gè)程式. 

GTK_OBJECT及GTK_SIGNAL_FUNC是分派巨集. 

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

下 一個(gè)函數(shù)是用來設(shè)定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);

這個(gè)會(huì)產(chǎn)生一個(gè)新的按鈕. 它配置記憶體給一個(gè)新的GtkWidget, 并初始化. 他將會(huì)有一個(gè)標(biāo)簽\"Hello World\". 

button = gtk_button_new_with_label (\"Hello World\");

然 後, 我們讓這個(gè)按鈕做一點(diǎn)事. 我們將他接到一個(gè)信號(hào)處理器, 因此它會(huì)送出\"clicked\"信號(hào), 而我們的hello()函數(shù)會(huì)被呼叫到. 資料被忽略, 所以我們只喂NULL給hello(), 明顯的, \"clicked\"信號(hào)當(dāng)我們敲下滑鼠時(shí)被送出. 


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

我 們將用這個(gè)按鈕來離開程式. 這將展示\"destroy\"信號(hào)可以是來自window manager, 或是我們的程式. 當(dāng)按鈕被\"clicked\", 跟上面一樣, 它會(huì)呼叫hello() callback函數(shù), 然後是這一個(gè), 以它們被設(shè)定的先後順序被呼叫到. 您可以有任意個(gè)callback函數(shù), 它們會(huì)以被連接的先後順序被執(zhí)行到. 因?yàn)間tk_widget_destroy()函數(shù)僅接受 GtkWidget *widget做為參數(shù), 我們使用gtk_signal_connect_object() , 而不用gtk_signal_connect(). 


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

這是個(gè)封裝呼叫, 我們?cè)卺崦娴奈募袝?huì)解釋. 不過這倒蠻容易理解的. 它就是告訴GTK按鈕要放在要顯示出來的那個(gè)視窗. 

gtk_container_add (GTK_CONTAINER (window), button);

現(xiàn)在我們將所有東西照我們的意思來設(shè)定好了. 所有信號(hào)接好了, 按鈕也放到該有的位置, 現(xiàn)在來\"show\"這個(gè)視窗吧. 這個(gè)整個(gè)視窗會(huì)一下子從螢?zāi)槐某鰜? 而不是先看到視窗, 然後按鈕才跑出來. 

gtk_widget_show (button);

gtk_widget_show (window);

還有當(dāng)然了, 我們呼叫g(shù)tk_main()來等待X事件的發(fā)生, 當(dāng)事件發(fā)生時(shí), 它將會(huì)呼叫物件來送出信號(hào). 

gtk_main ();

最後, 程式終止於此. 在gtk_quit()被呼叫到後, 程式會(huì)離開. 
return 0;

現(xiàn) 在, 當(dāng)我們?cè)贕TK上敲下滑鼠, 這個(gè)物件會(huì)送出\"clicked\"信號(hào). 我們的程式設(shè)定了信號(hào)處理器來接取這個(gè)信號(hào), 這樣我們便可利用這個(gè)資訊. 在我們的范例中, 當(dāng)按鈕被\"clicked\", hello()函數(shù)被呼叫到, 并被傳入一個(gè)NULL參數(shù), 然後下一個(gè)處理函數(shù)被呼叫到. 它會(huì)呼叫g(shù)tk_widget_destroy()函數(shù), 傳入視窗物件做為參數(shù), 并將該視窗物件銷毀. 這會(huì)導(dǎo)致該視窗送出\"destroy\"信號(hào), 收到該信號(hào)後, 會(huì)呼叫我們的destroy() callback函數(shù), 而我們的destroy()會(huì)令程式離開GTK. 

另一個(gè)方式當(dāng)然是利用window manager來銷毀該視窗. 這也會(huì)導(dǎo)致該視窗送出\"destroy\"信號(hào), 然後呼叫destroy() callback, 然後離開. 

這些信號(hào)與UNIX系統(tǒng)不太一樣, 并非基於UNIX系統(tǒng)的信號(hào)系統(tǒng), 雖然它們的術(shù)語是一樣的.  


3. 下一步

3.1 資料型態(tài) 
有 些東西您可能在前面的范例中已經(jīng)看到, 這需要多解釋一下. 像gint, gchar等等. 這些是為了取得絕對(duì)乾凈的獨(dú)立性, 如資料大小等等. 像\"gint32\"就是個(gè)很好的范例, 其目的是維持到任意平臺(tái)均為32bits, 不管是64 bit alpha或是32 bit i386. 其定義是極其直接而且直覺的. 它們都被定義在glib/glib.h (被gtk.h所include). 

您也看到像在GtkWidget這一類的東西. GTK是物件導(dǎo)向的設(shè)計(jì), 而widget則是其中的物件. 


3.2 更多關(guān)於信號(hào)處理函數(shù) 
我們來看看gtk_signal_connect宣告. 


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

看到gint的返回值? 這是個(gè)標(biāo)明您的callback函數(shù)的標(biāo)簽值. 像之前所說的, 每個(gè)信號(hào)及物件可以有好幾個(gè)callback, 每個(gè)會(huì)以它們所接上的順序被輪流執(zhí)行到. 您可以用以下這個(gè)函數(shù)來移除這個(gè)callback函數(shù): 

void gtk_signal_disconnect (GtkObject *object,
gint id);

你可以透過您想要移除的widget handler,給定id, 來解除信號(hào)處理函數(shù). 
您也可以用: 


gtk_signal_disconnect_by_data (GtkObject *object,
gpointer data);

這玩意我倒沒用過, 我真得不曉得要怎麼用 :) 

另一個(gè)函數(shù)可以解除所有的處理函數(shù): 

gtk_signal_handlers_destroy (GtkObject *object);

這個(gè)函數(shù)到底是自己解釋了自己的功能. 它移除了該物件所有的信號(hào)處理函數(shù). 


3.3 Hello World的加強(qiáng)版 
我們來看看一個(gè)稍經(jīng)改良的hello world, 這是個(gè)callback的不錯(cuò)的例子. 這也會(huì)介紹到我們下一個(gè)主題, 封裝物件. 


#include 

/* 新改進(jìn)的callback. 輸入到該函數(shù)的資料被輸出到. */
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);

/* 這是個(gè)新函數(shù), 它設(shè)定title到新視窗上\"Hello Buttons!\" */
gtk_window_set_title (GTK_WINDOW (window), \"Hello Buttons!\");

/* 用這樣會(huì)比較簡(jiǎn)單一點(diǎn). */
gtk_signal_connect (GTK_OBJECT (window), \"destroy\",
GTK_SIGNAL_FUNC (destroy), NULL);


/* 設(shè)定邊框?qū)挾? */
gtk_container_border_width (GTK_CONTAINER (window), 10);

/* 我們產(chǎn)生一個(gè)box來封裝物件. 這一點(diǎn)會(huì)在\"packing\"詳述.
這個(gè)box實(shí)際上看不見, 它只是用來當(dāng)成是個(gè)工具來安排物件 */
box1 = gtk_hbox_new(FALSE, 0);

/* 將box放到主視窗中. */
gtk_container_add (GTK_CONTAINER (window), box1);

/* 產(chǎn)生一個(gè)新按鈕并帶有標(biāo)簽\"Button 1\". */
button = gtk_button_new_with_label (\"Button 1\");

/* 當(dāng)按鈕被按下的時(shí)候, 我們呼叫\(zhòng)"callback\"函數(shù)
* 并以其指標(biāo)做為參數(shù)送到\"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;
}

將 這個(gè)程式以相同的參數(shù)編譯, 您會(huì)看到?jīng)]有任何方法來離開程式, 您必須使用視窗管理程式或命令列來殺掉它. 對(duì)讀者來說, 加個(gè)\"Quit\"按鈕會(huì)是個(gè)不錯(cuò)的練習(xí). 您也可以玩一玩gtk_box_pack_start()這個(gè)東西. 試試?yán)焕暣? 看看有什麼變換. 

另外有個(gè)蠻有用的define給gtk_window_new()用 - GTK_WINDOW_DIALOG. 它的互動(dòng)行為有點(diǎn)不太一樣. 



4. 封裝物件
當(dāng) 我們制作一套軟體, 您會(huì)希望在視窗內(nèi)放超過一個(gè)以上的按鈕. 我們第一個(gè)范例\"hello world\"僅用一個(gè)物件, 因此我們能夠很簡(jiǎn)單使用gtk_container_add來\"封裝\"該物件到視窗中. 但當(dāng)您希夠望放更多的物件到視窗中, 要如何控制它們的位置? 這里就要用到\"封裝\"(Packing). 

4.1 Packing Boxes的理論 
大部份 的封裝是由產(chǎn)生boxes來達(dá)成的. 這些是看不見的widget containers, 我們可以用兩種形式來將我們的物件封裝進(jìn)去, vertical box及horizontal box. 當(dāng)我們封裝物件到一個(gè)horizontal box時(shí), 物件是依我們呼叫的順序由右至左平行的被新增進(jìn)去. 在vertical box, 物件是由上至下. 您可以將物件插入box, 也可以將boxes插入box, 任意的組合用以產(chǎn)生所想要的效果. 

要產(chǎn)生horizontal box,我們使用gtk_hbox_new(), 而vertical boxe使用gtk_vbox_new(). gtk_box_pack_start()及gtk_box_pack_end()函數(shù)是用來將物件放到containers里面. gtk_box_pack_start()函數(shù)會(huì)開始由左至右, 由上至下來封裝物件. gtk_box_pack_end()則相反, 由下至上, 由右至左. 使用這些函數(shù)允許我們對(duì)右邊或?qū)ψ筮呡^正, 而且可以用許多種方式來較正來取得所想要的效果. 一個(gè)object可以是另一個(gè)container或物件. 而且事實(shí)上, 許多物件本身也是containers. 像按鈕就是, 不過我們一般只在按鈕中用一個(gè)標(biāo)簽. 

使用這些呼叫, GTK知道要把物件放到那里去, 并且會(huì)自動(dòng)縮放及其它比例上的調(diào)整. 還有許多其它選項(xiàng)可以控制如何將物件封裝在一起. 正如您所想的, 這些方法可以給您許多的彈性來制作視窗. 

4.2 Boxes詳述 
由於這樣的彈性, packing boxes在一開始使用的話會(huì)有點(diǎn)搞糊涂. 還有許多其它的選項(xiàng),一開始還看不太出來它們?nèi)绾螠愒谝黄? 最後您會(huì)知道, 他們基本上有五種不同的型式. 





每一行包含一個(gè)horizontal box (hbox)及好幾個(gè)按鈕. 所有按鈕都是以同樣的方式來包入hbox內(nèi). 

這是gtk_box_pack_start的宣告. 


void gtk_box_pack_start (GtkBox *box,
GtkWidget *child,
gint expand,
gint fill,
gint padding);

第一個(gè)參數(shù)是您要把object放進(jìn)去的box, 第二個(gè)是該object. 現(xiàn)在這些物件將會(huì)都是按鈕. 

expand 參數(shù)在gtk_box_pack_start()或gtk_box_pack_end()中控制物件如何在box中排列. expand = TRUE的話它們會(huì)填滿box中所有額外的空間. expand = FALSE的話, 該box會(huì)縮小到剛好該物件的大小. 設(shè)expand=FALSE您可做好左右較正. 否則它們會(huì)填滿整個(gè)box. 同樣的效果可用tk_box_pack_start或pack_end functions來達(dá)成. 

fill參數(shù)在gtk_box_pack中控制額外空間. fill=TRUE該物件會(huì)自行產(chǎn)生額外空間, fill=FALSE則由box產(chǎn)生一個(gè)在物件周圍的填白區(qū)域. 這只有在expand=TRUE時(shí), 才會(huì)有作用. 

當(dāng)產(chǎn)生一個(gè)新的box, 該函數(shù)看起來像這樣: 


GtkWidget * gtk_hbox_new (gint homogeneous,
gint spacing);

homogeneous參數(shù)在gtk_hbox_new (and the same for gtk_vbox_new) 控制每個(gè)物件是否有同樣的寬或高. 若homogeneous=TRUE, 則expand也會(huì)被開啟. 

空白(spacing)及填白(padding)有什麼不同呢空白是加在物件之間, 填白只加在物件的一邊. 看以下這張圖可能會(huì)明白一點(diǎn): 



這里是一些用來產(chǎn)生以上影像的程式. 我做了蠻多的注解, 希望您不會(huì)有問題. 將它編譯然後玩玩它. 


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 使用表格來封裝 
我們來看看另一個(gè)封裝的方法 - 用表格. 在很多狀況下, 這是極其有用的. 

使用表格, 我們產(chǎn)生格線來將物件放入. 物件會(huì)照我們安排的位置排入. 

我們第一個(gè)要看的是gtk_table_new這個(gè)函數(shù): 


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

第一個(gè)參數(shù)是多少列, 第二個(gè)是多少欄. 

homogeneous 參數(shù)用來決定表格如何來定大小. 若homogeneous為TRUE, table boxes會(huì)被重定為在其中最大物件的大小. 若homogeneous為FALSE, 則其大小為, \"高\(yùn)"為列中最高的物件, 及\"寬\"欄中最寬的物件大小. 

列及欄的編號(hào)為從0到n. n是我們?cè)趃tk_table_new中所指定的值. 所以, 如果您指定rows = 2及columns = 2, 整個(gè)排列會(huì)看起來像這樣: 


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

坐標(biāo)系統(tǒng)開始於左上角. 要把物件放進(jìn)box中, 可用以下函數(shù): 


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

第一個(gè)參數(shù)(\"table\")是您才剛產(chǎn)生的表格, 而第二個(gè)(\"child\")是您想放進(jìn)去的物件. 

而left_attach 及right_attach參數(shù)指定要把物件放在那里, 及用多少個(gè)boxes. 如果您想要用右下角的表格, 可以這樣填表. left_attach = 1, right_attach = 2, top_attach = 1, bottom_attach = 2. 

現(xiàn)在, 如果您想要物件來使用上面2x2的表格, 您可以使用left_attach = 0, right_attach =2, top_attach = 0, bottom_attach = 1. 

xoptions及yoptions是用來指定封裝選項(xiàng), 可以同時(shí)組合多個(gè)選項(xiàng)(用or). 

這些選項(xiàng)是: 

GTK_FILL - 如果table box大過物件, 且GTK_FILL 被指定了, 該物件會(huì)擴(kuò)展成使用所有可用的空間. 
GTK_SHRINK - 如果table widget小於該物件, (一般是使用者縮放該視窗), 那麼該物件將會(huì)一直被擠壓到看不見為止. 如果GTK_SHRINK被指定了, 該物件會(huì)跟著table一起縮小. 
GTK_EXPAND - 這會(huì)使table本身擴(kuò)展, 并利用視窗中所有可用空間. 
填空就像boxes, 產(chǎn)生一個(gè)在物件周邊空白的區(qū)域. 

gtk_table_attach()有許多選項(xiàng). 這里有個(gè)捷徑: 


void gtk_table_attach_defaults (GtkTable *table,
GtkWidget *widget,
gint left_attach,
gint right_attach,
gint top_attach,
gint bottom_attach);

X及Y選項(xiàng)內(nèi)定為GTK_FILL | GTK_EXPAND, X及Y填空則設(shè)為0. 其馀的參數(shù)則相同於以上的函數(shù). 

我們另外有g(shù)tk_table_set_row_spacing()及gtk_table_set_col_spacing(). 這些會(huì)在指定的欄及列插入空白. 


void gtk_table_set_row_spacing (GtkTable *table,
gint row,
gint spacing);

及 
void gtk_table_set_col_spacing (GtkTable *table,
gint column,
gint spacing);

對(duì)欄來說, 空格是在欄的右邊. 而列則是在下面. 

您也可以用以下函數(shù)來產(chǎn)生固定的空格. 


void gtk_table_set_row_spacings (GtkTable *table,
gint spacing);

及, 

void gtk_table_set_col_spacings (GtkTable *table,
gint spacing);

使用這些函數(shù), 其最後一欄及最後一列并沒有空格存在. 


4.5 Table Packing范例 
目前并無說明, 請(qǐng)參照testgtk.c


5. 物件概論

在GTK下,一般產(chǎn)生物件的步驟為: 

gtk_*_new - 最普遍產(chǎn)生物件的函數(shù). 
連接信號(hào)到信號(hào)處理器. 
設(shè)定物件屬性. 
要將物件包裝到一個(gè)container可用gtk_container_add()或gtk_box_pack_start(). 
gtk_widget_show(). 
gtk_widget_show ()讓GTK知道我們已經(jīng)完成設(shè)定的工作, 并且已經(jīng)準(zhǔn)備好要顯示. 您也可以用gtk_widget_hide來隱藏它. 顯示物件的順序并不太重要, 但我建議最後才顯示, 這樣才不會(huì)看到這些視窗們一個(gè)一個(gè)被畫出來. 子物件在使用gtk_widget_show使視窗出現(xiàn)之前是不會(huì)被顯示出來的. 


5.1 分派系統(tǒng) 
再繼續(xù)下去您會(huì)發(fā)現(xiàn), GTK使用一種分派系統(tǒng). 一般是用巨集來完成. 您可以看到諸如以下: 


GTK_WIDGET(widget) 
GTK_OBJECT(object) 
GTK_SIGNAL_FUNC(function) 
GTK_CONTAINER(container) 
GTK_WINDOW(window) 
GTK_BOX(box) 
這些在函數(shù)中的都是分派參數(shù). 您可以在范例中看到, 而且只要看到該函數(shù)就會(huì)知道它們是做什麼用的. 

從以下的組織圖來看, 所有GtkWidgets都是由GtkObject而來. 這意味著您可以在任何地方, 透過GTK_OBJECT()巨集要求一個(gè)物件. 

例如: 


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

這樣分派一個(gè)按鈕給一個(gè)物件, 并且提供一個(gè)指標(biāo)給callback函數(shù). 

許多物件同時(shí)也是containers. 如果您看看以下的組織圖, 您會(huì)看到許多物件由GtkContainer而來 所有這一類的物件都可以用GTK_CONTAINER巨集產(chǎn)生使用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 沒有視窗的物件 
以下的物件跟視窗沒有關(guān)系. 如果您希望接取它們的事件, 您需要使用GtkEventBox. 請(qǐng)見 EventBox物件


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

再過來我們會(huì)一個(gè)一個(gè)物件來示范如何產(chǎn)生及顯示. 一個(gè)很好的范例是testgtk.c, 您可以在gtk/testgtk.c里面找到. 



7. Tooltips物件
他們是當(dāng)您停在某個(gè)物件(像按鈕或其它物件)上幾秒時(shí), 會(huì)自動(dòng)出現(xiàn)的一個(gè)小的文字視窗. 它們很容易使用, 因此我只解釋一下, 而不給范例程式. 如果您想看看一些范例程式, 可參考GDK內(nèi)的testgtk.c. 

有些物件(像標(biāo)簽)無法與tooltips一起用. 

第一個(gè)呼叫的函數(shù)會(huì)產(chǎn)生一個(gè)新的tooltip. 您只需要呼叫這個(gè)函數(shù)一次. GtkTooltip這個(gè)函數(shù)的返回值可用來產(chǎn)生許多個(gè)tooltips. 


GtkTooltips *gtk_tooltips_new (void);

一旦您產(chǎn)生了一個(gè)新的tooltip, 您要設(shè)定到某個(gè)物件上, 只要呼叫這個(gè)函數(shù)即可. 


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

第一個(gè)參數(shù)是您剛才產(chǎn)生的tooltip, 接著是您希望使用的物件, 然後是您希望顯示的文字. 

這里有個(gè)簡(jiǎn)短的范例: 


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還有其它的一些函數(shù). 我只簡(jiǎn)短的介紹一下. 


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

設(shè)定要停留多少ms, tooltip才會(huì)出現(xiàn). 內(nèi)定值是1000ms, 即一秒. 

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

改變一個(gè)tooltip的文字內(nèi)容. 


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

設(shè)定tooltips的前景及背景顏色. 


8. Container物件

8.1 筆記本物件 
筆記本物件好幾個(gè)\"頁\"的集合, 它們互相交疊在一起, 并可包含不同的訊息. 這個(gè)物件在GUI越來越普及, 它是個(gè)在顯示有類同功能的資訊時(shí)很有用的物件. 

第一個(gè)您會(huì)用到的是產(chǎn)生一個(gè)新的筆記本物件. 


GtkWidget* gtk_notebook_new (void);

一旦物件產(chǎn)生後, 共有12個(gè)函數(shù)可以操作該筆記本物件. 我們一個(gè)一個(gè)來看. 

第一個(gè)是要如何來安排\"頁標(biāo)簽\". 這些\"頁標(biāo)簽\"或\"tabs\", 可以用四個(gè)位置, 上, 下, 左, 右. 


void gtk_notebook_set_tab_pos (GtkNotebook *notebook, GtkPositionType pos);

GtkPostionType可以是以下四個(gè), 很好認(rèn). 

GTK_POS_LEFT 
GTK_POS_RIGHT 
GTK_POS_TOP 
GTK_POS_BOTTOM 
GTK_POS_TOP是內(nèi)定值. 

接下來我們來看如何加\"一頁\"到筆記本上. 共有三種方法來加頁到筆記本上. 


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

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

這些函數(shù)新增一頁到筆記本, append由後新增, prepend由前新增. *child是要插入筆記本的物件, *tab_label是頁標(biāo)簽. 


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

參數(shù)與_append_及_prepend_相同, 除了多出一個(gè)參數(shù), 位置. 該參數(shù)用來指定要插在那里. 

現(xiàn)在我們知道要如何新增一頁, 再來看看如何移除. 


void gtk_notebook_remove_page (GtkNotebook *notebook, gint page_num);

這個(gè)函數(shù)移除掉所指定的那一頁. 

要找出目前正在那一頁, 可用以下函數(shù): 


gint gtk_notebook_current_page (GtkNotebook *notebook);

以下兩個(gè)函數(shù)是向前或向後移動(dòng). 若目前在最後一頁, 而您用gtk_notebook_next_page, 那麼筆記本會(huì)繞回第一頁, 反之亦然. 


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

以下函數(shù)設(shè)定\"有效頁\". 如果您希望筆記本開啟到例如第五頁, 您可以用這個(gè)函數(shù). 內(nèi)定頁為第一頁. 


void gtk_notebook_set_page (GtkNotebook *notebook, gint page_num);

以下兩個(gè)函數(shù)可新增及移除頁標(biāo)簽及邊框. 


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). 

現(xiàn) 在我們來看個(gè)范例, 它是從testgtk.c中展開的, 用了所有13個(gè)函數(shù). 該程式產(chǎn)生一個(gè)筆記本及六個(gè)按鈕, 包含11頁, 以三種方式加頁, appended, inserted,及prepended. 這些按鈕允許您旋轉(zhuǎn)頁標(biāo)簽位置, 新增/移除頁標(biāo)簽及邊框, 移除一頁, 以前向及後向改變頁的位置, 及離開程式. 



#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 卷動(dòng)視窗 
卷動(dòng)視窗是用來產(chǎn)生在視窗內(nèi)可卷動(dòng)的區(qū)域. 您可以在卷動(dòng)視窗中插入任意種物件, 而不管視窗大小如何, 這些物件因?yàn)樵诰韯?dòng)區(qū)域內(nèi), 因此都可以被用到. 

您可以用以下函數(shù)來產(chǎn)生卷動(dòng)視窗: 


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

第一個(gè)參數(shù)是水平調(diào)整方向, 第二個(gè)是垂直調(diào)整方向. 它們一般被設(shè)為NULL. 


void gtk_scrolled_window_set_policy (GtkScrolledWindow *scrolled_window,
GtkPolicyType hscrollbar_policy,
GtkPolicyType vscrollbar_policy);

第一個(gè)參數(shù)是想要改變的視窗. 第二個(gè)是設(shè)定水平卷動(dòng)的方式, 第三個(gè)是垂直卷動(dòng)的方式. 

policy可以是GTK_POLICY_AUTOMATIC, 或GTK_POLICY_ALWAYS. GTK_POLICY_AUTOMATIC會(huì)自動(dòng)決定是否使用scrollbars. GTK_POLICY_ALWAYS則scrollbars始終在那里. 

這里是個(gè)將100個(gè)雙態(tài)按鈕包進(jìn)一個(gè)卷動(dòng)視窗的范例. 


#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);
}

玩弄一下這個(gè)視窗. 您會(huì)看到scrollbars如何反應(yīng). 您也會(huì)想用用gtk_widget_set_usize()來設(shè)定視窗內(nèi)定的大小. 



    9. EventBox視窗物件
這只在gtk+970916.tar.gz以後的版本才有. 

有些gtk物件并沒有相關(guān)聯(lián)的視窗, 它們是由其parent所畫出來的. 因此, 他們不能收到事件. 如果它們大小不對(duì), 他們無法收到事件來修正. 如果您需要這樣的功能, 那麼EventBox就是您想要的. 

初 看之下, EventBox物件看來好像毫無用途. 它在螢?zāi)簧鲜颤N事也不做, 也不畫, 對(duì)事件也不反應(yīng). 不過, 它倒提供一項(xiàng)功能 - 他提供一個(gè)X window來服務(wù)其子物件. 這很重要, 因?yàn)镚TK物件很多都跟X window不相關(guān)聯(lián). 不用X window省下記憶體并加快其速度, 但也有其缺點(diǎn). 一個(gè)物件沒有X window無法接收事件, 而且無法裁切其內(nèi)容. 雖然它叫``EventBox\"\"強(qiáng)調(diào)其事件處理功能, 這個(gè)物件也可用來做裁切. 


要產(chǎn)生一個(gè)EventBox物件, 使用: 


GtkWidget* gtk_event_box_new (void);


一個(gè)子視窗物件可被加到EventBox之下: 


gtk_container_add (GTK_CONTAINER(event_box), widget);


以下的簡(jiǎn)單示范, 使用了一個(gè)EventBox - 一個(gè)標(biāo)題, 并且設(shè)定成滑鼠在標(biāo)題上點(diǎn)一下程式就會(huì)離開. 


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

/* 產(chǎn)生一個(gè)EventBox并加到其上層的視窗 */

event_box = gtk_event_box_new ();
gtk_container_add (GTK_CONTAINER(window), event_box);
gtk_widget_show (event_box);

/* 產(chǎn)生一個(gè)長(zhǎng)標(biāo)題 */

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 標(biāo)簽 
標(biāo)簽在GTK中用得很多, 而且很簡(jiǎn)單. 標(biāo)簽不送信號(hào), 因?yàn)樗鼈兏鶻 window沒有關(guān)系. 如果您要接取信號(hào), 或裁切, 可用EventBox物件. 

產(chǎn)生新的標(biāo)簽可用: 


GtkWidget* gtk_label_new (char *str);

唯一個(gè)參數(shù)是您想要顯示的文字. 

在產(chǎn)生標(biāo)簽後要改變其文字, 可用: 


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

第一個(gè)參數(shù)是剛才所產(chǎn)生的標(biāo)簽(使用GTK_LABEL巨集來分派), 第二個(gè)是新的字串. 

新字串的空間會(huì)自動(dòng)被配置. 

要取得目前的字串可用: 


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

第一個(gè)參數(shù)是標(biāo)簽, 第二個(gè)是返回字串的位置. 


10.2 Progress Bars 
Progress bars是用來顯示某個(gè)作業(yè)的操作狀態(tài). 他們很容易使用, 您會(huì)看到以下的程式. 我們先來產(chǎn)生一個(gè)Progress Bar. 


GtkWidget *gtk_progress_bar_new (void);

這樣就產(chǎn)生了, 夠簡(jiǎn)單的了. 


void gtk_progress_bar_update (GtkProgressBar *pbar, gfloat percentage);

第一個(gè)參數(shù)是您要操作的Progress Bar, 第二個(gè)是完成度, 其值為0-1. 

Progress Bars一般與timeouts及其它函數(shù)一起使用, (see section on Timeouts, I/O and Idle Functions) 這是因?yàn)槎喙さ目剂? gtk_progress_bar_update會(huì)處理這方面的事務(wù). 

這里是使用Progress Bar的范例, 并用timeouts來更新. 同時(shí)也會(huì)展示如何重設(shè)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;
}

在這個(gè)小程式中有四個(gè)區(qū)域在一般的Progress Bar操作上, 我們會(huì)一個(gè)一個(gè)看到. 


pbar = gtk_progress_bar_new ();

產(chǎn)生Progress Bar, pbar. 


ptimer = gtk_timeout_add (100, progress, pbar);

使用timeouts來產(chǎn)生一個(gè)固定時(shí)間間隔, 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 對(duì)話盒 

對(duì)話盒物件很簡(jiǎn)單, 是個(gè)預(yù)先做好的視窗. 對(duì)話盒的結(jié)構(gòu)如下: 


struct GtkDialog
{
GtkWindow window;

GtkWidget *vbox;
GtkWidget *action_area;
};

您看到, 它就是產(chǎn)生一個(gè)新的視窗. 然後包一個(gè)vbox到它上面, 接著一個(gè)seperator, 然後是hbox給\"action_area\". 

對(duì)話盒是用於通告訊息, 及類似用途. 這很基本, 只有一個(gè)函數(shù): 


GtkWidget* gtk_dialog_new (void);

因此要產(chǎn)生新的對(duì)話盒, 


GtkWidget window;
window = gtk_dialog_new ();

這會(huì)產(chǎn)生對(duì)話盒, 然後您可以任意使用它. 然後將按鈕包裝到action_area, 像這樣: 


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

然後您也可以用封裝新增一個(gè)vbox, 例如, 一個(gè)新標(biāo)簽, 試試看: 


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

做為一個(gè)對(duì)話盒的范例, 你可以使用兩個(gè)按鈕在action_area, 一個(gè)Cancel及Ok按鈕, 及一個(gè)標(biāo)簽在vbox area, 問使用者一個(gè)問題或提示錯(cuò)誤的發(fā)生等等. 然後您可以接到不同的信號(hào)上來處理使用者的選擇. 



10.4 Pixmaps 
Undocumented. 


10.5 Images 
Undocumented. 


11. 檔案選取物件
檔案選取物件是個(gè)又快又簡(jiǎn)單的方法來產(chǎn)生一個(gè)File dialog box. 它有Ok, Cancel, 及Help按鈕, 可以大量縮短開發(fā)時(shí)間. 

要產(chǎn)生一個(gè)新的檔案選取物件可用: 


GtkWidget* gtk_file_selection_new (gchar *title);

要設(shè)定檔名, 例如指定目錄, 或給定內(nèi)定檔名, 可用這個(gè)函數(shù): 


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

要取得使用者輸入的名稱, 可用以下函數(shù): 


gchar* gtk_file_selection_get_filename (GtkFileSelection *filesel);

另外還有指標(biāo)指向檔案選取物件的內(nèi)容: 


dir_list 
file_list 
selection_entry 
selection_text 
main_vbox 
ok_button 
cancel_button 
help_button 
當(dāng)然了您會(huì)想要用ok_button, cancel_button, 及help_button指標(biāo)用來處理信號(hào). 

在這里包含了從testgtk.c偷來的一個(gè)范例, 修改成自己的版本. 在此您可以看到, 要產(chǎn)生一個(gè)檔案選取物件不需要做太多事. 在此, 在這個(gè)范例中, Help button顯示在螢?zāi)恢? 它沒做什麼事, 因?yàn)闆]有信號(hào)接在上面. 


#include <gtk/gtk.h>

/* 取得選取的檔名并顯示在螢?zāi)簧?*/
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);

/* 產(chǎn)生新的檔案選取物件 */
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));

/* 設(shè)定檔名, 就像是要存一個(gè)檔案一樣, 而我們是給定一個(gè)內(nèi)定檔名 */
gtk_file_selection_set_filename (GTK_FILE_SELECTION(filew), 
\"penguin.png\");

gtk_widget_show(filew);
gtk_main ();
return 0;
}




12. List物件
GtkList物件被設(shè)計(jì)成是個(gè)vertical container, 而在其中的物件必須是GtkListItem. 

GtkList 物件有其自己的視窗用來接取事件, 而其背景色一般是白色的. 由於它是由GtkContainer而來, 您也可以用GTK_CONTAINER(List)巨集來處理. 請(qǐng)見GtkContainer物件一章. 您應(yīng)該已經(jīng)熟悉GList的用法, 及其相關(guān)函數(shù)g_list_*(), 這樣您才不會(huì)在此遭遇到問題. 

在GtkList物件有一欄對(duì)我們來說很重要. 


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

GtkList 的selection欄指向一個(gè)所有items的link list, 其中記錄所有被記錄的項(xiàng)目, 若為`NULL\"則selection為空的. 因此要知道目前的selection, 我們可以讀取GTK_LIST()->selection一欄. 但不要修改它們, 因?yàn)樗鼈兪怯蓛?nèi)部所維護(hù). 

GtkList的selection_mode決定selection的機(jī)制, 而GTK_LIST()->selection欄的內(nèi)容為: 

selection_mode可以是以下其中一種: 

GTK_SELECTION_SINGLE selection可以是`NULL\" 或?qū)σ粋(gè)已選項(xiàng)目, 包含一個(gè)GList* pointer. 
GTK_SELECTION_BROWSE 若list沒有有效的物件, selection可以是`NULL\" 否則它會(huì)包含一個(gè)GList* pointer, 而且就是一個(gè)list item. 
GTK_SELECTION_MULTIPLE 若list中沒有item被選取, selection可以是`NULL\" 否則會(huì)有一個(gè)GList* pointer, 并且指向第一個(gè)selected item. 并一直向後接到第二個(gè)...
GTK_SELECTION_EXTENDED selection永遠(yuǎn)為`NULL\". 
內(nèi)定為GTK_SELECTION_MULTIPLE. 


12.1 信號(hào) 

void GtkList::selection_changed (GtkList *LIST)

當(dāng)selection區(qū)域改變的時(shí)候, 這個(gè)信號(hào)會(huì)被觸發(fā). 這會(huì)在當(dāng)GtkList的子物件被select或unselect時(shí)發(fā)生. 


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

當(dāng)GtkList的子物件被select時(shí), 這個(gè)信號(hào)會(huì)被觸發(fā). 這一般在gtk_list_select_item(), gtk_list_select_child(), 按鈕被按下及有時(shí)間接觸發(fā)或有子物件新增或移除時(shí)發(fā)生. 


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

當(dāng)GtkList的子物件被unselect時(shí), 這個(gè)信號(hào)會(huì)被觸發(fā). 這一般在gtk_list_unselect_item(), gtk_list_unselect_child(), 按鈕被按下及有時(shí)間接觸發(fā)或有子物件新增或移除時(shí)發(fā)生. 


12.2 函數(shù) 

guint gtk_list_get_type (void)

返回`GtkList\" type identifier. 


GtkWidget* gtk_list_new (void)

產(chǎn)生新的`GtkList\" object. 新的物件其返回值為`GtkWidget\" object的指標(biāo). `NULL\"表示失敗. 


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

插入list items到LIST里面, 由POSITION開始. ITEMS是雙向鏈結(jié)串列. 每個(gè)項(xiàng)目要指向一個(gè)產(chǎn)生出來的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是雙向鏈結(jié)串列, 每個(gè)node要指向child. 設(shè)計(jì)者要自行呼叫g(shù)_list_free(ITEMS). 設(shè)計(jì)者也要自行處理掉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中目前的位置,觸發(fā)GtkList::select_child信號(hào)給指定的list item. 


void gtk_list_unselect_item (GtkList *LIST, gint ITEM)

透過在LIST中目前的位置,觸發(fā)GtkList::unselect_child信號(hào)給指定的list item. 


void gtk_list_select_child (GtkList *LIST, GtkWidget *CHILD)

觸發(fā)GtkList::select_child信號(hào)給指定的CHILD. 


void gtk_list_unselect_child (GtkList *LIST, GtkWidget *CHILD)

觸發(fā)GtkList::unselect_child信號(hào)給指定的CHILD. 


gint gtk_list_child_position (GtkList *LIST, GtkWidget *CHILD)

返回CHILD在LIST中的位置. `-1\"為失敗. 


void gtk_list_set_selection_mode (GtkList *LIST, GtkSelectionMode MODE)

設(shè)定LIST到選擇模式MODE, 可以是GTK_SELECTION_SINGLE, GTK_SELECTION_BROWSE, GTK_SELECTION_MULTIPLE 或 GTK_SELECTION_EXTENDED. 


GtkList* GTK_LIST (gpointer OBJ)

傳一個(gè)generic pointer到`GtkList*\". *Note Standard Macros::, for more info. 


GtkListClass* GTK_LIST_CLASS (gpointer CLASS)

傳一個(gè)generic pointer到`GtkListClass*\". *Note Standard Macros::, for more info. 


gint GTK_IS_LIST (gpointer OBJ)

決定是否一個(gè)generic pointer對(duì)應(yīng)到`GtkList\" object. *Note Standard Macros::, for more info. 



12.3 范例 
以下是個(gè)范例程式, 將會(huì)列出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物件是設(shè)計(jì)用來做為container的子物件, 用來提供selection/deselection的功能. 

GtkListItem有自己的視窗來接收事件并有其自身的背景顏色, 一般是白色的. 

因 為是由GtkItem而來的, 它也可以用GTK_ITEM(ListItem)巨集. 一般GtkListItem只有一個(gè)標(biāo)簽, 用來記錄例如一個(gè)檔名. 另外還有一個(gè)很好用的函數(shù)gtk_list_item_new_with_label(). 若您不想加GtkLabel到GtkListItem, 也可以加GtkVBox或GtkArrow. 


12.5 信號(hào) 
GtkListItem不產(chǎn)生自己的新的信號(hào), 但它繼承GtkItem的信號(hào). 



12.6 函數(shù) 


guint gtk_list_item_get_type (void)

返回`GtkListItem\" type identifier. 


GtkWidget* gtk_list_item_new (void)

產(chǎn)生新的`GtkListItem\" object. 新物件返回一個(gè)指標(biāo)給`GtkWidget\"物件. `NULL\"表示錯(cuò)誤. 


GtkWidget* gtk_list_item_new_with_label (gchar *LABEL)

產(chǎn)生新的`GtkListItem\"物件, 并帶一個(gè)標(biāo)簽. 并返回一個(gè)`GtkWidget\" object. `NULL\"表示錯(cuò)誤. 


void gtk_list_item_select (GtkListItem *LIST_ITEM)

這個(gè)函數(shù)基本上是將gtk_item_select (GTK_ITEM (list_item))包裝起來. 它將會(huì)送GtkItem::select信號(hào). *Note GtkItem::, for more info. 


void gtk_list_item_deselect (GtkListItem *LIST_ITEM)

這個(gè)函數(shù)基本上是將gtk_item_deselect (GTK_ITEM (list_item))包裝起來. 它將會(huì)送GtkItem::deselect信號(hào). *Note GtkItem::, for more info. 


GtkListItem* GTK_LIST_ITEM (gpointer OBJ)

傳一個(gè)generic pointer到`GtkListItem*\". *Note Standard Macros::, for more info. 


GtkListItemClass* GTK_LIST_ITEM_CLASS (gpointer CLASS)

傳一個(gè)generic pointer到`GtkListItemClass*\". *Note Standard Macros::, for more info. 


gint GTK_IS_LIST_ITEM (gpointer OBJ)

決定generic pointer是否對(duì)照到`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物件這一篇本身比較不容易翻譯, 因原文本身講的并不太清楚. 此外, 其結(jié)構(gòu)原本就比較繁瑣. 若您在此糟遇問題, 可來信反應(yīng). 譯者會(huì)想辦法將其改善. 
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物件
有兩種方式來產(chǎn)生選單物件, 一種簡(jiǎn)單的, 一種難的. 兩種各有其用途, 但您可以用menu_factory(簡(jiǎn)單的). 難的方法是一個(gè)一個(gè)產(chǎn)生. 簡(jiǎn)單的是用gtk_menu_factory 這個(gè)簡(jiǎn)單多了, 但各有其優(yōu)劣之處. 

menufactory很好用, 雖然另外寫一些函數(shù), 以手動(dòng)函數(shù)來產(chǎn)生這些選單會(huì)比較有用. 不過, 以menufactory, 也是可以加影像到選單中. 


14.1 Manual Menu Creation 
在教學(xué)的目的上, 我們先來看看難的方法.:) 

先看看產(chǎn)生選單的函數(shù). 第一個(gè)當(dāng)然是產(chǎn)生一個(gè)新的選單. 


GtkWidget *gtk_menu_bar_new()

GtkWidget *gtk_menu_new();

這個(gè)函數(shù)返回一個(gè)新的選單, 它還不會(huì)顯示. 

以下兩個(gè)函數(shù)是用來產(chǎn)生選單項(xiàng)目. 


GtkWidget *gtk_menu_item_new()

and 


GtkWidget *gtk_menu_item_new_with_label(const char *label)

動(dòng)態(tài)新增 


gtk_menu_item_append()

gtk_menu_item_set_submenu()

gtk_menu_new_with_label及gtk_menu_new函數(shù)一個(gè)產(chǎn)生一個(gè)新的選單項(xiàng)目并帶標(biāo)簽, 另一個(gè)則是個(gè)空的選單項(xiàng)目. 

產(chǎn)生選單的步驟大致如下: 

使用gtk_menu_new()來產(chǎn)生一個(gè)新的選單 
使用gtk_menu_item_new()來產(chǎn)生一個(gè)新的選單項(xiàng)目. 這會(huì)是主選單, 文字將會(huì)是menu bar本身. 
使用gtk_menu_item_new來將每一個(gè)項(xiàng)目產(chǎn)生出來用gtk_menu_item_append()來將每個(gè)新項(xiàng)目放在一起. 這會(huì)產(chǎn)生一列選單項(xiàng)目. 
使用gtk_menu_item_set_submenu()來接到心產(chǎn)生的menu_items到主選單項(xiàng)目. (在第二步中所產(chǎn)生出來的). 
使用gtk_menu_bar_new來產(chǎn)生一個(gè)menu bar. 這一步僅需做一次, 當(dāng)我們產(chǎn)生一系列選單在menu bar上. 
使用gtk_menu_bar_append來將主選單放到menubar. 

14.2 Manual Menu范例 
我們來做做看, 看看一個(gè)范例會(huì)比較有幫助. 



#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;
}

您也可以設(shè)定一個(gè)選單項(xiàng)目無效, 并使用accelerator table結(jié)合按鍵到選單功能. 


14.3 使用GtkMenuFactory 
我們已經(jīng)示范了難的方法, 這里是用gtk_menu_factory的方法. 


14.4 Menu Factory范例 
這里是menu factory的范例. 這是第一個(gè)檔案, 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. 選取區(qū)域管理
16.1 概說 

GTK 所支援的其中一種行程間通訊為selections. 一個(gè)selection本身是一筆資料, 例如, 使用者選取文字的一部份, 又如, 由滑鼠抓出一些東西. 在顯示器上一次只能有一個(gè)\"選取區(qū)域\", 上一個(gè)選取區(qū)域要在該區(qū)域撤銷時(shí)才會(huì)生效. 其它的應(yīng)用軟體以幾種各種不同形式取得其內(nèi)容, 被稱為targets. 可以有許多個(gè)selections, 但X軟體只能處理一個(gè), 即primary selection. 


在大部份的狀況下, GTK程式不需要自行處理選取區(qū)域. 標(biāo)準(zhǔn)物件如Entry物件, 已經(jīng)有能力來自動(dòng)產(chǎn)生選取區(qū)域, 并從其它物件擷取選取區(qū)域. 不過有時(shí)候, 您想要給其它物件有能力提供選取區(qū)域, 或當(dāng)內(nèi)定不支援, 想要擷取資料時(shí). 


一 個(gè)基本觀念需要了解選取區(qū)域處理的是atom. 一個(gè)atom是個(gè)integer, 標(biāo)記著一個(gè)字串. 有些特定的元素被X server事先定義過, 有些在gtk.h中則為固定數(shù)值, 對(duì)映到這些atoms. 例如GDK_PRIMARY_SELECTION對(duì)映到字串\"PRIMARY\". 在其它狀況下, 您應(yīng)該使用gdk_atom_intern()這個(gè)函數(shù), 用以取得atom對(duì)映到string, 及gdk_atom_name(), 用以取得atom的名稱. selections及targets都是一種atoms. 


16.2 擷取selection 

擷取selection是個(gè)非同步行程. 您可以呼叫: 


gint gtk_selection_convert (GtkWidget *widget, 
GdkAtom selection, 
GdkAtom target,
guint32 time)

這個(gè)函數(shù)轉(zhuǎn)換選取區(qū)域到target所指定的形式. time這一欄是由選取被觸發(fā)到事件發(fā)生的時(shí)間. 這使我們可以保證事件發(fā)生的順序. 您也可以用GDK_CURRENT_TIME來替代. 


當(dāng)選取區(qū)域的擁有者回應(yīng)一個(gè)要求時(shí), 一個(gè)\"selection_received\"信號(hào)會(huì)送到您的程式. 該信號(hào)處理器會(huì)收到一個(gè)指標(biāo)GtkSelectionData 結(jié)構(gòu), 定義如下: 


struct _GtkSelectionData
{
GdkAtom selection;
GdkAtom target;
GdkAtom type;
gint format;
guchar *data;
gint length;
};

selection 及target 是您在gtk_selection_convert()中所給的值. type由選區(qū)擁有者返回, 用來辨識(shí)資料型態(tài). 可以是這些值\"STRING\", 字串, \"ATOM\", 一系列的atoms, \"INTEGER\", 一個(gè)integer, 等等. a series of atoms, \"INTEGER\", an integer, etc. 大部份targets只能返回一種型態(tài). format是每個(gè)單位有多少的bits(如字元為8 bits, guint32為32 bits). 一般來說, 您在收資料的時(shí)候, 不必管這個(gè)值. data是返回的資料指標(biāo). length是返回資料的長(zhǎng)度, 以byte做單位. 如果length是負(fù)值, 那麼表示有錯(cuò)誤發(fā)生, 選取區(qū)域無效. 這在所被要求選區(qū)的程式本身不擁有或不支援的時(shí)候會(huì)發(fā)生. 該緩沖區(qū)事實(shí)上保證一定有多出一個(gè)byte; 多出來的byte永遠(yuǎn)為零, 所以不需要多復(fù)制一份字串備份. 


在以下的例子中, 我們擷取特別的target, \"TARGETS\", 這是個(gè)所有selection都可以轉(zhuǎn)換進(jìn)去的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 提供選取區(qū)域 

提供選取區(qū)域比較復(fù)雜. 您必須注冊(cè)handler, 當(dāng)您被要求提供選區(qū)時(shí), handler會(huì)被呼叫到. 對(duì)每個(gè)selection/target, 您必須呼叫: 


void gtk_selection_add_handler (GtkWidget *widget, 
GdkAtom selection,
GdkAtom target,
GtkSelectionFunction function,
GtkRemoveFunction remove_func,
gpointer data);

widget, selection, 及target 表示這個(gè)處理器會(huì)處理的要求. 如果remove_func不為NULL, 當(dāng)信號(hào)處里器被移除時(shí), 這個(gè)函數(shù)會(huì)被移除. 這很有用, 例如說, 給解譯式語言用, 因?yàn)樗鼤?huì)保持追蹤并維護(hù)其自身的資料. 


該callback函數(shù)有以下的形式: 


typedef void (*GtkSelectionFunction) (GtkWidget *widget, 
GtkSelectionData *selection_data,
gpointer data);

GtkSelectionData 跟上面一樣, 但這一次, 我們必須要填type, format, data, 及l(fā)ength這幾欄. (format這一欄很重要 - X server用來決定是否需要做byte-swap, 因?yàn)橛蠿是多平臺(tái)的系統(tǒng), 一般8是character, 32是integer.) 這是由以下函數(shù)所完成的: 


void gtk_selection_data_set (GtkSelectionData *selection_data,
GdkAtom type,
gint format,
guchar *data,
gint length);

這個(gè)函數(shù)會(huì)將資料備一份, 因此您不需要自行維護(hù). (這就是說您不應(yīng)該自己手動(dòng)去填該資料結(jié)構(gòu)的資料.) 


您可以用以下函數(shù)設(shè)定該選區(qū)的擁有者: 


gint gtk_selection_owner_set (GtkWidget *widget,
GdkAtom selection,
guint32 time);

如果有其它程式設(shè)定了該選區(qū)的擁有權(quán), 您會(huì)收到一個(gè)\"selection_clear_event\"信號(hào). 

做為一個(gè)提供選區(qū)的例子, 以下程式將選取功能加到一個(gè)雙態(tài)按鈕. 當(dāng)雙態(tài)按鈕被按下時(shí), 該程式會(huì)設(shè)定擁有該選區(qū). 而唯一支援的target是\"STRING\" target. 當(dāng)該target被要求時(shí), 將會(huì)返回一個(gè)顯示時(shí)間的字串. 


#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提供許多有用的函數(shù)及定義. 我把它們列在這里并做簡(jiǎn)短的解釋. 很多都是與libc重復(fù), 對(duì)這些我不再詳述. 這些大致上是用來參考, 您知道有什麼東西可以用就好. 


17.1 定義 
為保持資料型態(tài)的一致, 這里有一些定義: 


G_MINFLOAT
G_MAXFLOAT
G_MINDOUBLE
G_MAXDOUBLE
G_MINSHORT
G_MAXSHORT
G_MININT
G_MAXINT
G_MINLONG
G_MAXLONG

此外, 以下的typedefs. 沒有列出來的是會(huì)變的, 要看是在那一種平臺(tái)上. 如果您想要具有可移植性, 記得避免去使用sizeof(pointer). 例如, 一個(gè)指標(biāo)在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 雙向鏈結(jié)串列 
以下函數(shù)用來產(chǎn)生, 管理及銷毀雙向鏈結(jié)串列. 


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 單向鏈結(jié)串列 
以下函數(shù)是用來管理單向鏈結(jié)串列: 

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()用的. 你不需要去檢查返回值, 因?yàn)樗呀?jīng)幫你做好了, 保證. 


gpointer g_malloc0 (gulong size);

一樣, 不過會(huì)在返回之前將記憶體歸零. 


gpointer g_realloc (gpointer mem,
gulong size);

重定記憶體大小. 


void g_free (gpointer mem);

void g_mem_profile (void);

將記憶體的使用狀況寫到一個(gè)檔案, 不過您必須要在glib/gmem.c里面, 加#define MEM_PROFILE, 然後重新編譯. 


void g_mem_check (gpointer mem);

檢查記憶體位置是否有效. 您必須要在glib/gmem.c上加#define MEM_CHECK, 然後重新編譯. 


17.5 Timers 
Timer函數(shù).. 


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 工具及除錯(cuò)函數(shù) 

gchar* g_strdup (const gchar *str);


gchar* g_strerror (gint errnum);

我建議您使用這個(gè)來做所有錯(cuò)誤訊息. 這玩意好多了. 它比perror()來的具有可移植性. 輸出為以下形式: 


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

這里是\"hello world\"用到的一些函數(shù): 


g_print(\"hello_world:open:%s:%s\\n\", filename, g_strerror(errno));


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

顯示錯(cuò)誤訊息, 其格式與printf一樣, 但會(huì)加個(gè)\"** ERROR **: \", 然後離開程式. 只在嚴(yán)重錯(cuò)誤時(shí)使用. 


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

跟上面一樣, 但加個(gè)\"** WARNING **: \", 不離開程式. 


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

加個(gè)\"message: \". 


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

printf()的替代品. 

最後一個(gè): 


gchar* g_strsignal (gint signum);

列印Unix系統(tǒng)的信號(hào)名稱, 在信號(hào)處理時(shí)很有用. 

這些大都從glib.h中而來. 



18. 設(shè)定視窗物件屬性
這里描述如何操作視窗物件的函數(shù)集. 可用於設(shè)定外形, 空格, 大小等等. 

(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有處理軟體內(nèi)定值的一套方法, 即使用其rc檔. 這些可以用來設(shè)定顏色, 并且可以用pixmaps來設(shè)定某些物件的背景. 


19.1 rc檔的功能 
當(dāng)您的軟體啟動(dòng)時(shí), 您應(yīng)該呼叫這一行: 

void gtk_rc_parse (char *filename);

將您的檔名傳入做為參數(shù). 這會(huì)使GTK來分析這個(gè)檔案, 并使用設(shè)定值來設(shè)定物件的形態(tài). 

如果您希望有特別樣子的物件, 但可從另一個(gè)物件做為基礎(chǔ)來產(chǎn)生, 可以用這個(gè): 

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

傳入您新產(chǎn)生的物件做為第一個(gè)參數(shù), 您要給它的名字做為第二個(gè)參數(shù). 這樣的話可以讓你透過rc檔來改變?cè)撐锛膶傩? 

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


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

則這個(gè)按鈕被給了一個(gè)名字叫\(zhòng)"special button\" 并且會(huì)被指向rc檔中的\"special button.GtkButton\". [<--- 要是我錯(cuò)了, 修正我!] 

以下的rc檔設(shè)定主視窗的屬性, 并讓所有子視窗繼承其形態(tài). 在程式中的程式碼為: 


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

而該形態(tài)則在rc檔中定義為: 


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

這會(huì)設(shè)定所有GtkButton物件, 成為在\"main window\"中的\"main_buttons\"的形態(tài). 

您可以看到, 這是很強(qiáng)很有彈性的系統(tǒng). 用您最佳的想像力來看有多少好處. 


19.2 GTK的rc檔案格式 
GTK的rc檔格式如以下的范例. 這個(gè)testgtkrc檔從GTK distribution而來, 但我加了點(diǎn)料及注解進(jìn)去. 您也可以加一點(diǎn)解釋來讓使用者做微調(diào). 

有好幾個(gè)指令來改變?cè)撐锛膶傩? 

fg - 前景顏色. 
bg - 背景顏色. 
bg_pixmap - 背景圖片pixmap. 
font - 字型. 
除此, 一個(gè)物件可以有好幾種狀態(tài). 您可以設(shè)定不同的顏色, 圖案及字形. 這些狀態(tài)是: 

NORMAL - 物件一般的狀態(tài), 沒有滑鼠滑過, 沒有被按下. 
PRELIGHT - 滑鼠滑過該物件. 
ACTIVE - 當(dāng)該物件被壓下或按下, 該視窗會(huì)生效. 
INSENSITIVE - 當(dāng)該物件被設(shè)為失效. 
SELECTED - 當(dāng)物件被選擇. 
當(dāng)我們使用\"fg\"及\"bg\"來設(shè)定該物件的顏色時(shí), 其格式為: 

fg[] = { Red, Green, Blue }

這 里STATE是我們以上所說的其中之一(PRELIGHT, ACTIVE etc), 而Red, Green及Blue為0到1.0, { 1.0, 1.0, 1.0 }為白色. 它們必須要為浮點(diǎn)數(shù), \"1\"不行, 必須是\"1.0\", 否則會(huì)全部變成0. \"0\"可以. 不是以此格式者均為\"0\". 

bg_pixmap跟以上都很近似, 除了變成檔名以外. 

pixmap_path是以\":\"做為分隔的一串路徑. 這些路徑會(huì)用來搜尋您所指定的pixmap. 


font指令很簡(jiǎn)單: 

font = \"\"

比較難的是找出想要的font名稱. 用xfontsel或類似的工具來找會(huì)有點(diǎn)幫助. 

\"widget_class\"設(shè)定物件的類別. 這些類別在物件概論中的類別組織圖有列出來. 

\"widget \"指令指定一個(gè)已經(jīng)定好的形態(tài)給一個(gè)物件. 替代所有該物件的屬性. 這些物件則在程式中以gtk_widget_set_name()注冊(cè)過了. 這允許您指定各別物件的屬性, 而不是設(shè)定全部同一類的. 我要求您要做好文件, 這樣使用者可以自行修改. 

當(dāng)\"parent\"用來當(dāng)成一個(gè)屬性時(shí), 該物件會(huì)繼承其父所有財(cái)產(chǎn). 

當(dāng)您定義一個(gè)形態(tài)時(shí), 可以指定以前已經(jīng)定義過的形態(tài)給新的. 

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

這個(gè)例子用\"button\"的形態(tài), 產(chǎn)生一個(gè)\"main_button\"形態(tài), 并且只改變font及背景顏色. 

當(dāng)然了并非所有屬性都對(duì)所有物件生效. 因?yàn)樵撐锛灰姷脫碛性搶傩? 


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的物件基本上是夠用了, 但有時(shí)您還是需要產(chǎn)生自己所需要的物件型態(tài). 如果已經(jīng)有一個(gè)既存的物件很接近您的需求, 那麼您可以把程式改個(gè)幾行就可以達(dá)到您的需求了. 但在您決定要寫一個(gè)新的物件之前, 先確認(rèn)是否有人已經(jīng)寫過了. 這會(huì)避免重復(fù)浪費(fèi)資源, 并保持物件數(shù)量達(dá)到最少, 這會(huì)使程式及介面比較統(tǒng)一一點(diǎn). 另一方面, 一旦您寫好您的物件, 要向全世界公告, 這樣其它人才會(huì)受益. 最好的公告地點(diǎn)大概就是gtk-list了. 


20.2 物件的解析 
為了要產(chǎn)生一個(gè)新的物件, 了解GTK的運(yùn)作是很重要的. 這里只簡(jiǎn)單的說一下. 詳細(xì)請(qǐng)參照reference documentation. 

GTK 物件是以流行的物件導(dǎo)件的觀念來設(shè)計(jì)的. 不過, 依然是以C來寫的. 比起用C++來說, 這可以大大改善可移植性及穩(wěn)定性. 但同時(shí), 這也意味著widget writer需要小心許多實(shí)作上的問題. 所有同一類別的物件的一般資訊 (例如所有的按鈕物件)是放在 class structure. 只有一份這樣的結(jié)構(gòu). 在這份結(jié)構(gòu)中儲(chǔ)存類別信號(hào)的資訊. 要支撐這樣的繼承, 第一欄的資料結(jié)構(gòu)必須是其父類別的資料結(jié)構(gòu). 例如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);
};


當(dāng)一個(gè)按鈕被看成是個(gè)container時(shí)(例如, 當(dāng)它被縮放時(shí)), 其類別結(jié)構(gòu)可被傳到GtkContainerClass, 而其相關(guān)的欄位被用來處理信號(hào). 


對(duì)每個(gè)物件結(jié)構(gòu)來說, 都有一些狀況上的不同. 該結(jié)構(gòu)都有一些資訊是不太一樣的. 我們稱此結(jié)構(gòu)為object structure. 如按鈕一類, 看起來像這樣: 


struct _GtkButton
{
GtkContainer container;

GtkWidget *child;

guint in_button : 1;
guint button_down : 1;
};


可以看到, 第一欄是其父類別的物件資料結(jié)構(gòu), 因此該結(jié)構(gòu)可以傳到其父類別的物件結(jié)構(gòu)來處理. 


20.3 產(chǎn)生一個(gè)組合物件 

標(biāo)頭檔
每個(gè)物件類別都有一個(gè)標(biāo)頭檔來宣告其物件, 類別結(jié)構(gòu)及其函數(shù). 有些特性是值得指出的. 要避免重復(fù)宣告, 我們將整個(gè)標(biāo)頭檔包成: 


#ifndef __TICTACTOE_H__
#define __TICTACTOE_H__
.
.
.
#endif /* __TICTACTOE_H__ */

而且加入讓C++程式不會(huì)抓狂的定義碼: 


#ifdef __cplusplus
extern \"C\" {
#endif /* __cplusplus */
.
.
.
#ifdef __cplusplus
}
#endif /* __cplusplus */

除 了函數(shù)及結(jié)構(gòu)外, 我們宣告了三個(gè)標(biāo)準(zhǔn)巨集在標(biāo)頭檔中 TICTACTOE(obj), TICTACTOE_CLASS(klass), 及IS_TICTACTOE(obj), 當(dāng)我們傳入一個(gè)指標(biāo)到物件或類別結(jié)構(gòu)中, 它會(huì)檢查是否是我們的tictactoe物件. 


這里是完整的標(biāo)頭檔: 



#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()函數(shù).
我們現(xiàn)在來繼續(xù)做我們的物件. 對(duì)每個(gè)物件來說, 都有一個(gè)重要的核心函數(shù) WIDGETNAME_get_type(). 這個(gè)函數(shù), 當(dāng)?shù)谝淮伪缓艚械臅r(shí)候, 會(huì)告訴GTK有關(guān)該物件類別, 并取得一個(gè)ID來辨視其物件類別. 在其後的呼叫中, 它會(huì)返回該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結(jié)構(gòu)有以下定義: 


struct _GtkTypeInfo
{
gchar *type_name;
guint object_size;
guint class_size;
GtkClassInitFunc class_init_func;
GtkObjectInitFunc object_init_func;
GtkArgFunc arg_func;
};


這資料結(jié)構(gòu)自我解釋的很好. 在此, 我們將會(huì)忽略掉arg_func這一欄: 它很重要, 可以允許用來給設(shè)定解譯式語言來設(shè)定, 但大部份相關(guān)工作都還沒有完成. 一旦GTK被正確的填入該資料結(jié)構(gòu), 它會(huì)知道如何產(chǎn)生某一個(gè)特別的物件類別. 


The _class_init() function
WIDGETNAME_class_init()函數(shù)啟始設(shè)定該物件類別的資料, 并設(shè)定給該類別信號(hào). 



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;
}


該函數(shù)只有一個(gè)信號(hào), ``tictactoe\\信號(hào). 并非所有組合式物件都需要信號(hào), 所以如果這是您第一次讀這里, 您可以跳到下一個(gè), 因?yàn)檫@里有點(diǎn)復(fù)雜. 


gint gtk_signal_new (gchar *name,
GtkSignalRunType run_type,
gint object_type,
gint function_offset,
GtkSignalMarshaller marshaller,
GtkArgType return_val,
gint nparams,
...);

產(chǎn)生新訊號(hào), 參數(shù)包含: 


name: 信號(hào)名稱. 
run_type: 決定內(nèi)定的處理器要在使用者的處理器之前處理或之後處理. 一般可以是GTK_RUN_FIRST, or GTK_RUN_LAST. 
object_type: 物件的ID. 
function_offset: 在類別結(jié)構(gòu)中內(nèi)定處理器函數(shù)位址值在記憶體中的偏移值. 
marshaller: 用來觸發(fā)信號(hào)處理器的函數(shù). 對(duì)除了使用者資料外, 沒有額外參數(shù)的的信號(hào)處理器來說, 我們可以用內(nèi)定的marshaller函數(shù) gtk_signal_default_marshaller. 
return_val: 返回值的型態(tài). 
nparams: 信號(hào)處理器的參數(shù)數(shù)量. (不同於以上所提的兩個(gè)) 
...: 參數(shù)型態(tài). 
當(dāng)指定型態(tài)時(shí), 可用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 從草稿中產(chǎn)生物件. 

基本
我們的物件看起來會(huì)有點(diǎn)像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__ */

在您產(chǎn)生視窗後, 我們?cè)O(shè)定其型態(tài)及背景, 并放指標(biāo)到物件的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);
}


大小的設(shè)定
在所有視窗被顯示出來之前, GTK會(huì)先問每個(gè)子物件的大小. 該事件是由gtk_dial_size_request()所處理的. 既然我們的物件不是container物件, 而且沒什麼大小約束, 就用個(gè)合理的數(shù)字就行了. 


static void 
gtk_dial_size_request (GtkWidget *widget,
GtkRequisition *requisition)
{
requisition->width = DIAL_DEFAULT_SIZE;
requisition->height = DIAL_DEFAULT_SIZE;
}


最後所有物件都有理想的大小. 一般會(huì)盡可能用原定大小, 但使用者會(huì)改變它的大小. 大小的改變是由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;
}


事件處理

最後一段程式處理各種事件, 跟我們之前所做的沒有什麼太大的不同. 有兩種事件會(huì)發(fā)生, 使用者滑鼠的動(dòng)作及其它因素所造成的物件參數(shù)調(diào)整. 


當(dāng) 使用者在物件上按鈕時(shí), 我們檢查是否靠近我們的指標(biāo), 如果是, 將資料存到button一欄, 并用gtk_grab_add()將所有滑鼠事件抓住. 接下來的滑鼠的動(dòng)作將會(huì)被gtk_dial_update_mouse所接管.. 接下來就看我們是如何做的, \"value_changed\"事件可以用(GTK_UPDATE_CONTINUOUS)來產(chǎn)生, 或用gtk_timeout_add()來延遲一下(GTK_UPDATE_DELAYED), 或僅在按鈕按下時(shí)反應(yīng)(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;
}
}


有可能的增強(qiáng)之處

這個(gè)Dial物件到目前為止有670行. 這看起來好像有不少了, 不過我們真正完成的只有一點(diǎn)點(diǎn), 因?yàn)榇蟛糠荻际菢?biāo)頭及模子. 還是有許多可以加強(qiáng)的地方: 


如果您試過這個(gè)物件, 您會(huì)發(fā)現(xiàn)滑鼠指標(biāo)會(huì)一閃一閃的. 這是因?yàn)檎麄(gè)物件每次都重畫一次. 當(dāng)然了最好的方式是在offscreen pixmap上畫完以後, 然後整個(gè)復(fù)制到螢?zāi)簧? 
使用者應(yīng)該可以用up及down按鍵來增加或減少其值. 
如果有個(gè)按鈕來增加或減少其值, 那是再好不過的了. 雖然可也以用embedded Button widgets來做, 但我們會(huì)想要按鈕有auto-repeat的功能. 所有要做這一類功能的程式可以在GtkRange物件中發(fā)現(xiàn). 
這個(gè)Dial物件可再做進(jìn)一個(gè)container物件, 帶有一個(gè)子物件, 位於按鈕與最下面之間. 使用者可以增加一個(gè)標(biāo)簽或整個(gè)物件來顯示目前的值. 

20.5 更多一點(diǎn) 
關(guān)於產(chǎn)生一個(gè)新的物件的細(xì)部資訊在以上被提供出來. 如果您想要寫一個(gè)屬於自己的物件, 我想最好的范例就是GTK本身了. 

問問您自己一些關(guān)於您想要寫的物件: 

它是否是個(gè)Container物件?
它是否有自己的視窗?
是否是個(gè)現(xiàn)有物件的修改?
找出一個(gè)相近的物件, 然後開始動(dòng)工.

祝好運(yùn)! 

20.6 版權(quán) 
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軟體的一些技巧
這一段只是在收集一些寫個(gè)好GTK軟體的一些辦法, 及一般的導(dǎo)引. 現(xiàn)在還沒什麼作用, 因?yàn)橹挥卸潭痰膸拙湓?:) 

用GNU的autoconf及automake! 它們將會(huì)是您未來的朋友 :) 我正在計(jì)畫在這里寫關(guān)於兩者的一些簡(jiǎn)介. 

22. 貢獻(xiàn)
這份文件, 就像在此的許多好軟體一樣, 是由許多志愿者免費(fèi)所創(chuàng)作出來的. 如果您覺得GTK很多地方都沒有文件, 那麼您可以考慮對(duì)這份文件貢獻(xiàn). 

如果您決定要貢獻(xiàn)一份力量, 請(qǐng)將您的文章寄給我, Ian Main, slow@intergate.bc.ca. 此外, 要知道這整份文件是免費(fèi)的, 而任何新增過來的文件也會(huì)是免費(fèi)的. 

多謝了. 

23. 為此貢獻(xiàn)的人們
在此我要對(duì)以下這些負(fù)出貢獻(xiàn)的人們致謝. 


Bawer Dagdeviren, chamele0n@geocities.com 貢獻(xiàn)menus導(dǎo)引. 
Raph Levien, raph@acm.org 貢獻(xiàn)了GTK的hello world, widget packing,及其源源不絕的智慧. 他并且為這個(gè)導(dǎo)引文件貢獻(xiàn)一個(gè)家. 
Peter Mattis, petm@xcf.berkeley.edu 為他最簡(jiǎn)單的GTK程式.并且完成這個(gè)程式的能力 :) 
Werner Koch werner.koch@guug.de 他轉(zhuǎn)換原來的文字檔成為SGML, 及視窗類別組織圖. 
Mark Crichton crichton@expert.cc.purdue.edu 貢獻(xiàn)了menu factory程式碼, 及table packing導(dǎo)引. 
Owen Taylor mailto:owt1@cornell.edu 貢獻(xiàn)了EventBox widget一段. 他也負(fù)責(zé)了selections的程式及導(dǎo)引. , 及writing your own GTK widgets的那一段. 獻(xiàn)上榮耀給Owen! 
Mark VanderBoom mailto:owt1@cornell.edu 他大部份的工作在Notebook上完成, Progress Bar, Dialogs, 及File selection widgets. 多謝Mark! 您的助益很大. 
Tim Janik mailto:timj@psynet.net 感謝他在視窗物件上的整理工作. 謝謝Tim :) 
對(duì)所有給我們建議及幫助我們加強(qiáng)本文件的人. 

感謝您們. 

24. 版權(quán)
這份導(dǎo)引文件版權(quán)所有(C) 1997 Ian Main 

本程式是免費(fèi)軟體; 您可以在免費(fèi)軟體基金會(huì)GNU版權(quán)下發(fā)行或修改, 不管是這個(gè)版本, 下個(gè)版本, 或者往後的版本. 

這個(gè)程式是以希望它是有用的軟體的信念下發(fā)行, 但不帶任何保證; 而且不帶任何銷路上的暗示保證或是只是故意要練練寫程式. 詳情請(qǐng)見GNU General Public License. 

您應(yīng)該在這個(gè)程式的同時(shí)也收到GNU General Public License; 如果沒有, 請(qǐng)寫信到the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
您需要登錄后才可以回帖 登錄 | 注冊(cè)

本版積分規(guī)則 發(fā)表回復(fù)

  

北京盛拓優(yōu)訊信息技術(shù)有限公司. 版權(quán)所有 京ICP備16024965號(hào)-6 北京市公安局海淀分局網(wǎng)監(jiān)中心備案編號(hào):11010802020122 niuxiaotong@pcpop.com 17352615567
未成年舉報(bào)專區(qū)
中國(guó)互聯(lián)網(wǎng)協(xié)會(huì)會(huì)員  聯(lián)系我們:huangweiwei@itpub.net
感謝所有關(guān)心和支持過ChinaUnix的朋友們 轉(zhuǎn)載本站內(nèi)容請(qǐng)注明原作者名及出處

清除 Cookies - ChinaUnix - Archiver - WAP - TOP