歡迎您光臨本站 註冊首頁

給GTK+初學者------中文版

←手機掃碼閱讀     火星人 @ 2014-03-25 , reply:0

GTK入門導引

1. 簡介

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

  Peter Mattis petm@xcf.berkeley.edu

  Spencer Kimball spencer@xcf.berkeley.edu

  Josh MacDonald jmacd@xcf.berkeley.edu

  GTK基本上是物件導嚮應用軟體程式設計介面(API). 雖然完全用C所寫成, 他是用classes及callback函數的觀念所實作出來的(指向該函數).

  還有另一個被稱為glib的函數庫被用到, 該函數庫包涵了一些標準X函數的替代函數, 及一些額外的處理鏈結表的函數等等. 這些替代函數是用來增加GTK的可移植性, 因為有些函數需要用到非標準的功能, 諸如g_strerror(). 有些則包含一些libc版本的加強的功能, 諸如 g_malloc有
加強的除錯功能.

  這份導引是儘可能去詳盡描述GTK的功能, 雖然實在沒有辦法盡善盡美. 這份導引假設讀者對C語言有很相當的基礎, 並且知道如何去寫C語言程式. 如果讀者有過X的程式經驗, 會大大有幫助, 但並非絕對需要 (譯註: 這一點就好像是要先學MFC或SDK的問題一樣). 如果您以GTK做為進
入X程式設計的入門的話, 請給我們一些建議, 有關於您在本導引所學到及發現的東西, 及過程中有何困擾. 同時, 目前GTK也有C++ API (GTK--)正在發展, 所以如果您喜歡用C++, 您可能要先去看一看. 同時也有一套Objective C wrapper, guile bindings版本也有, 但我不建議您走這條
路.

  同時我也很想知道, 您在由本文學習GTK上有何問題, 我會感謝您告訴我如何改進這些種種的缺點.

  2. 開始

  第一件要做的是當然是取得一份GTK的原始碼並且安裝進您的系統中. 您可以從GIMP取得一份發行版, 或者是從Peter Mattis's的" 家中" ftp.xcf.berkely.edu/pub/pmattis(however, it has been changed to ftp.gimp.org)取得一份. GTK使用GNU的autoconf來設定. 一但您解開檔案
, 輸入configure --help來看看選項表列.

  在介紹GTK的一開始, 我們儘可能挑最簡單的程式. 這個程式將會產生200x200點的視窗, 而且沒辦法離開, 除非從shell中將它殺掉.


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

  所有程式理所當然一定會包含gtk/gtk.h, 其中宣告了所有變數, 函數, 及資料及結構. 這些東西您會在您的GTK應用軟體中用到.

  下一行


gtk_init (&argc, &argv);

  呼叫函數gtk_init(gint *argc, gchar ***argv)將會啟動GTK. 該函數設定了一些內定的值, 並且後續交給 gdk_init(gint *argc, gchar ***argv) 繼續處理. 該函數啟動了一些函數庫以供使用, 設定了內定的信號處理, 檢查傳給您的程式的命令列參數. 看看以下:

  --display

  --debug-level

  --no-xshm

  --sync

  --show-events

  --no-show-events

  這些參數將會從參數表中刪去, 所剩下的會傳給您做後續的處理. 這樣會產生標準的參數表(除了GTK所使用的)以供您使用.

  下面這兩行程式會產生並顯示一個視窗.


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

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

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

  最後一行進入GTK的主要處理迴圈.


gtk_main ();

  gtk_main()是個在每個GTK應用軟體中都會看到的一個函數. 當控制到達這裡, GTK會"睡"一下來等待X事件的發生(諸如像按鍵被按下). 在我們最簡單的例子裡面, 事件會被忽略掉. 因為我們沒有處理它.

  2.1 用GTK來寫Hello World

  好, 現在我們來寫一個有一個視窗物件的視窗(一個按鈕). 這是個GTK的標準hello world. 這會建立起一個新的GTK軟體的良好基礎.


#include
/* 這是個callback函數. 其資料參數在本例中被忽略
* 以下有更多的callback函數. */
void hello (GtkWidget *widget, gpointer *data)
{
g_print ("Hello World
");
}
/* another callback */
void destroy (GtkWidget *widget, gpointer *data)
{
gtk_main_quit ();
}
int main (int argc, char *argv[])
{
/* GtkWidget用以儲存視窗物件形態 */
GtkWidget *window;
GtkWidget *button;
/* 這在所有GTK應用軟體中用到. 參數由命令列中解譯出來並且送到該應用軟體中. */
gtk_init (&argc, &argv);
/* 產生新視窗 */
window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
/* 當視窗收到"destroy"信號時(可由該軟體或視窗管理程式所送出)
所會被呼叫到的destroy函數一如以下所定義的一般.
送到該函數的資料將會是NULL,並且在該函數中被忽略 */
gtk_signal_connect (GTK_OBJECT (window), "destroy",
GTK_SIGNAL_FUNC (destroy), NULL);
/* 設定視窗的邊框的寬度 */
gtk_container_border_width (GTK_CONTAINER (window), 10);
/* 產生一個新的按鈕並帶有"Hello World"的字在上面. */
button = gtk_button_new_with_label ("Hello World");
/* 當該按鍵收到"clicked"信號, 它會呼叫hello()這個函數.
並且以NULL做為其參數. hello()函數在以上已定義過. */
gtk_signal_connect (GTK_OBJECT (button), "clicked",
GTK_SIGNAL_FUNC (hello), NULL);
/* 這會導致當"clicked"這個按鈕被按下的時候,
呼叫gtk_widget_destroy(window)而使該視窗被關閉
當然了, 關閉的信號會從此處或視窗管理程式所送來 */
gtk_signal_connect_object (GTK_OBJECT (button), "clicked",
GTK_SIGNAL_FUNC (gtk_widget_destroy),
GTK_OBJECT (window));
/* 這個動作會把這個按鈕結合到該視窗(a gtk container). */
gtk_container_add (GTK_CONTAINER (window), button);
/* 最後一步是顯示最新產生的視窗物件... */
gtk_widget_show (button);
/* 及該視窗 */
gtk_widget_show (window);
/* 所有GTK程式都一定要有gtk_main(). 所有控制結束於此並等帶事件的發生
(像按下一鍵或滑鼠的移動). */
gtk_main ();
return 0;
}

  2.2 編譯Hello World

  用以下命令來編譯:


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

  函數庫必須在內定的搜尋路徑內, 如果找不到, -L 則gcc會去找這些目錄, 看看所需要的函數庫是否找得到. 例如, 在我的Debian Linux系統中, 我已經增加了 -L/usr/X11R6/lib用來尋找X11函數庫.

  以下函數庫是很重要的. linker在處理之前, 必須知道什麽函數要用那一個函數庫.

  函數庫如下:

  glib函數庫(-lglib), 包含一些有用的函數, 這個例子中只用到g_print(), 因為GTK是建在glib之上, 所以您幾乎都一定會用到它. 詳見glib一段.

  GDK函數庫(-lgdk), Xlib的包裝程式.

  GTK函數庫(-lgtk), 視窗物件函數庫, 基於GDK之上.

  xlib函數庫(-lXlib) 基本上為GDK所用.

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

  math函數庫(-lm). 為GTK所用, 有多方面用途.

  2.3 Signals及Callbacks的原理

  在我們更進一步探討hello world之前, 我們要講一下事件(events)及回呼函數(callbacks). GTK本身是個事件驅動的工具, 這意味著它會在gtk_main進入停歇狀態, 一直到一個事件發生, 並且將控制交給適當的函數來處理.

  控制權的交出是由"signals"來決定的. 當事件發生, 諸如按下滑鼠的一個按鍵, 對應的信號會由該視窗物件所送出. 這便是GTK的主要工作. 要使一個按下的動作執行一個命令, 我們設定一個信號處理函數來擷取這個信號, 並且呼叫適當的函數. 這工作是由像以下的函數來完成的:


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

  其第一個參數是會送出信號的物件, 第二個是希望接取的信號名稱. 第三個是當信號送出時的接取函數, 第四個則是要送給該函數的資料.

  第三個參數被稱為"callback function", 而且必需是以下的形式:


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

  第一個參數是指向該物件的指標, 第二個是在gtk_signal_connect()的最後一個參數.

  另外一個在hello world中有用到的函數是:


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

  gtk_signal_connect_object()跟gtk_signal_connect()一樣, 除了callback函術只有一個參數, 一個指向GTK物件的指標. 所以當使用這個函數來接到信號時, 該callback函數必須是以下形式:


void callback_func (GtkObject *object);

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

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

  aidcode]2.4 步過Hello World

  現在您知道這些理論了, 我們現在來根據這些理論, 把"hello world"這個範例弄清楚.

  這是個當按鈕被按下時, 會被呼叫到的callback函數. 參數的資料沒有被用到.


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

  這是另一個callback函數, 它會呼叫gtk_main_quit()來離開程式.


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

  下個部份, 宣告一個指標給GtkWidget. 這是準備用來產生視窗及按鈕的.


GtkWidget *window;
GtkWidget *button;

  這裡是我們的gtk_init. 設定GTK toolkit初始值.


gtk_init (&argc, &argv);

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


window = gtk_window_new (GTK_WINDOW_TOPLEVEL);

  這裡是將object(window)連接到信號處理器的範例. 此處"destroy"是該信號. 該信號是window manager要銷去這個視窗時, 或我們送出gtk_widget_destroy()時會產生的. 當我們這樣設定時, 我們可同時處理兩種狀況. 這裡我們使用 destroy函數, 這使我們可以使用window manager
來離開這個程式.

  GTK_OBJECT及GTK_SIGNAL_FUNC是分派巨集.


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

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


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

  這個會產生一個新的按鈕. 它配置記憶體給一個新的GtkWidget, 並初始化. 他將會有一個標籤"Hello World".


button = gtk_button_new_with_label ("Hello World");

  然後, 我們讓這個按鈕做一點事. 我們將他接到一個信號處理器, 因此它會送出"clicked"信號, 而我們的hello()函數會被呼叫到. 資料被忽略, 所以我們只喂NULL給hello(), 明顯的, "clicked"信號當我們敲下滑鼠時被送出.


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

  我們將用這個按鈕來離開程式. 這將展示"destroy"信號可以是來自window manager, 或是我們的程式. 當按鈕被 "clicked", 跟上面一樣, 它會呼叫hello() callback函數, 然後是這一個, 以它們被設定的先後順序被呼叫到. 您可以有任意個callback函數, 它們會以被連接的先後順
序被執行到. 因為gtk_widget_destroy()函數僅接受 GtkWidget *widget做為參數, 我們使用gtk_signal_connect_object() , 而不用gtk_signal_connect().


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

  這是個封裝呼叫, 我們在後面的文件中會解釋. 不過這倒蠻容易理解的. 它就是告訴GTK按鈕要放在要顯示出來的那個視窗.


gtk_container_add (GTK_CONTAINER (window), button);

  現在我們將所有東西照我們的意思來設定好了. 所有信號接好了, 按鈕也放到該有的位置, 現在來"show"這個視窗吧. 這個整個視窗會一下子從螢幕蹦出來, 而不是先看到視窗, 然後按鈕才跑出來.


gtk_widget_show (button);
gtk_widget_show (window);

  還有當然了, 我們呼叫gtk_main()來等待X事件的發生, 當事件發生時, 它將會呼叫物件來送出信號.


gtk_main ();

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


return 0;



  在, 當我們在GTK上敲下滑鼠, 這個物件會送出"clicked"信號. 我們的程式設定了信號處理器來接取這個信號, 這樣我們便可利用這個資訊. 在我們的範例中, 當按鈕被"clicked", hello()函數被呼叫到, 並被傳入一個NULL參數, 然後下一個處理函數被呼叫到. 它會呼叫gtk_widget_
destroy()函數, 傳入視窗物件做為參數, 並將該視窗物件銷毀. 這會導致該視窗送出"destroy"信號, 收到該信號後, 會呼叫我們的destroy() callback函數, 而我們的destroy()會令程式離開GTK.

  另一個方式當然是利用window manager來銷毀該視窗. 這也會導致該視窗送出"destroy"信號, 然後呼叫destroy() callback, 然後離開.

  這些信號與UNIX系統不太一樣, 並非基於UNIX系統的信號系統, 雖然它們的術語是一樣的.

  3. 下一步

  3.1 資料型態

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

  您也看到像在GtkWidget這一類的東西. GTK是物件導向的設計, 而widget則是其中的物件.

  3.2 更多關於信號處理函數

  我們來看看gtk_signal_connect宣告.


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

  看到gint的返回值? 這是個標明您的callback函數的標籤值. 像之前所說的, 每個信號及物件可以有好幾個callback, 每個會以它們所接上的順序被輪流執行到. 您可以用以下這個函數來移除這個callback函數:


void gtk_signal_disconnect (GtkObject *object,
gint id);

  你可以透過您想要移除的widget handler,給定id, 來解除信號處理函數.

  您也可以用:


gtk_signal_disconnect_by_data (GtkObject *object,
gpointer data);

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

  另一個函數可以解除所有的處理函數:


gtk_signal_handlers_destroy (GtkObject *object);

  這個函數到底是自己解釋了自己的功能. 它移除了該物件所有的信號處理函數.

  3.3 Hello World的加強版

  我們來看看一個稍經改良的hello world, 這是個callback的不錯的例子. 這也會介紹到我們下一個主題, 封裝物件.


#include
/* 新改進的callback.輸入到該函數的資料被輸出到. */
void callback (GtkWidget *widget, gpointer *data)
{
g_print ("Hello again - %s was pressed
", (char *) data);
}
/* another callback */
void destroy (GtkWidget *widget, gpointer *data)
{
gtk_main_quit ();
}
int main (int argc, char *argv[])
{
/* GtkWidget is the storage type for widgets */
GtkWidget *window;
GtkWidget *button;
GtkWidget *box1;
/* this is called in all GTK applications.arguments are parsed from
* the command line and are returned to the application. */
gtk_init (&argc, &argv);
/* create a new window */
window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
/* 這是個新函數, 它設定title到新視窗上"Hello Buttons!" */
gtk_window_set_title (GTK_WINDOW (window), "Hello Buttons!");
/* 用這樣會比較簡單一點. */
gtk_signal_connect (GTK_OBJECT (window), "destroy",
GTK_SIGNAL_FUNC (destroy), NULL);
/* 設定邊框寬度. */
gtk_container_border_width (GTK_CONTAINER (window), 10);
/* 我們產生一個box來封裝物件.這一點會在"packing"詳述.
這個box實際上看不見, 它只是用來當成是個工具來安排物件 */
box1 = gtk_hbox_new(FALSE, 0);
/* 將box放到主視窗中. */
gtk_container_add (GTK_CONTAINER (window), box1);
/* 產生一個新按鈕並帶有標籤"Button 1". */
button = gtk_button_new_with_label ("Button 1");
/* 當按鈕被按下的時候, 我們呼叫"callback"函數
* 並以其指標做為參數送到"button 1" */
gtk_signal_connect (GTK_OBJECT (button), "clicked",
GTK_SIGNAL_FUNC (callback), (gpointer) "button 1");
/* instead of gtk_container_add, we pack this button into the invisible
* box, which has been packed into the window. */
gtk_box_pack_start(GTK_BOX(box1), button, TRUE, TRUE, 0);
/* always remember this step, this tells GTK that our preparation for
* this button is complete, and it can be displayed now. */
gtk_widget_show(button);
/* do these same steps again to create a second button */
button = gtk_button_new_with_label ("Button 2");
/* call the same callback function with a different argument,
* passing a pointer to "button 2" instead. */
gtk_signal_connect (GTK_OBJECT (button), "clicked",
GTK_SIGNAL_FUNC (callback), (gpointer) "button 2");
gtk_box_pack_start(GTK_BOX(box1), button, TRUE, TRUE, 0);
/* The order in which we show the buttons is not really important, but I
* recommend showing the window last, so it all pops up at once. */
gtk_widget_show(button);
gtk_widget_show(box1);
gtk_widget_show (window);
/* rest in gtk_main and wait for the fun to begin! */
gtk_main ();
return 0;
}

  將這個程式以相同的參數編譯, 您會看到沒有任何方法來離開程式, 您必須使用視窗管理程式或命令列來殺掉它. 對讀者來說, 加個"Quit"按鈕會是個不錯的練習. 您也可以玩一玩gtk_box_pack_start()這個東西. 試試拉一拉視窗, 看看有什麽變換.

  另外有個蠻有用的define給gtk_window_new()用 - GTK_WINDOW_DIALOG. 它的互動行為有點不太一樣.

  4. 封裝物件

  當我們製作一套軟體, 您會希望在視窗內放超過一個以上的按鈕. 我們第一個範例"hello world"僅用一個物件, 因此我們能夠很簡單使用 gtk_container_add來"封裝"該物件到視窗中. 但當您希夠望放更多的物件到視窗中, 要如何控制它們的位置? 這裡就要用到"封裝" (Packing).

  4.1 Packing Boxes的理論

  大部份的封裝是由產生boxes來達成的. 這些是看不見的widget containers, 我們可以用兩種形式來將我們的物件封裝進去, vertical box及horizontal box. 當我們封裝物件到一個horizontal box時, 物件是依我們呼叫的順序由右至左平行的被新增進去. 在vertical box, 物件是
由上至下. 您可以將物件插入box, 也可以將boxes插入box, 任意的組合用以產生所想要的效果.

  要產生horizontal box,我們使用gtk_hbox_new(), 而vertical boxe使用gtk_vbox_new(). gtk_box_pack_start()及gtk_box_pack_end()函數是用來將物件放到containers裡面. gtk_box_pack_start()函數會開始由左至右, 由上至下來封裝物件. gtk_box_pack_end()則相反, 由下至
上, 由右至左. 使用這些函數允許我們對右邊或對左邊較正, 而且可以用許多種方式來較正來取得所想要的效果. 一個object可以是另一個 container或物件. 而且事實上, 許多物件本身也是containers. 像按鈕就是, 不過我們一般只在按鈕中用一個標籤.

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

  4.2 Boxes詳述

  由於這樣的彈性, packing boxes在一開始使用的話會有點搞糊塗. 還有許多其它的選項,一開始還看不太出來它們如何湊在一起. 最後您會知道, 他們基本上有五種不同的型式.

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

  這是gtk_box_pack_start的宣告.


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

  第一個參數是您要把object放進去的box, 第二個是該object. 現在這些物件將會都是按鈕.

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

  fill參數在gtk_box_pack中控制額外空間. fill=TRUE該物件會自行產生額外空間, fill=FALSE則由box產生一個在物件周圍的填白區域. 這隻有在expand=TRUE時, 才會有作用.

  當產生一個新的box, 該函數看起來像這樣:


GtkWidget * gtk_hbox_new (gint homogeneous,
gint spacing);

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

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

  這裡是一些用來產生以上影像的程式. 我做了蠻多的註解, 希望您不會有問題. 將它編譯然後玩玩它.

  4.3 封裝示範程式


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

  4.4 使用表格來封裝

  我們來看看另一個封裝的方法 - 用表格. 在很多狀況下, 這是極其有用的.

  使用表格, 我們產生格線來將物件放入. 物件會照我們安排的位置排入.

  我們第一個要看的是gtk_table_new這個函數:


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

  第一個參數是多少列, 第二個是多少欄.

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

  列及欄的編號為從0到n. n是我們在gtk_table_new中所指定的值. 所以, 如果您指定rows = 2及columns = 2, 整個排列會看起來像這樣:


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

  坐標系統開始於左上角. 要把物件放進box中, 可用以下函數:


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

  第一個參數("table")是您才剛產生的表格, 而第二個("child")是您想放進去的物件.

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

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

  xoptions及yoptions是用來指定封裝選項, 可以同時組合多個選項(用or).

  這些選項是:

  GTK_FILL - 如果table box大過物件, 且GTK_FILL 被指定了, 該物件會擴展成使用所有可用的空間.

  GTK_SHRINK - 如果table widget小於該物件, (一般是使用者縮放該視窗), 那麽該物件將會一直被擠壓到看不見為止. 如果GTK_SHRINK被指定了, 該物件會跟著table一起縮小.

  GTK_EXPAND - 這會使table本身擴展, 並利用視窗中所有可用空間.

  填空就像boxes, 產生一個在物件周邊空白的區域.

  gtk_table_attach()有許多選項. 這裡有個捷徑:


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

  X及Y選項內定為GTK_FILL | GTK_EXPAND, X及Y填空則設為0. 其餘的參數則相同於以上的函數.

  我們另外有gtk_table_set_row_spacing()及gtk_table_set_col_spacing(). 這些會在指定的欄及列插入空白.


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

voidgtk_table_set_col_spacing(GtkTable*table,
gintcolumn,
gintspacing);

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

  您也可以用以下函數來產生固定的空格.


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

  使用這些函數, 其最後一欄及最後一列並沒有空格存在.

  4.5 Table Packing範例

  目前並無說明, 請參照testgtk.c

  5. 物件概論

  在GTK下,一般產生物件的步驟為:

  gtk_*_new - 最普遍產生物件的函數.

  連接信號到信號處理器.

  設定物件屬性.

  要將物件包裝到一個container可用gtk_container_add()或gtk_box_pack_start().

  gtk_widget_show().

  gtk_widget_show()讓GTK知道我們已經完成設定的工作, 並且已經準備好要顯示. 您也可以用gtk_widget_hide來隱藏它. 顯示物件的順序並不太重要, 但我建議最後才顯示, 這樣才不會看到這些視窗們一個一個被畫出來. 子物件在使用gtk_widget_show 使視窗出現之前是不會被顯示
出來的.

  5.1 分派系統

  再繼續下去您會發現, GTK使用一種分派系統. 一般是用巨集來完成. 您可以看到諸如以下:


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

  這些在函數中的都是分派參數. 您可以在範例中看到, 而且只要看到該函數就會知道它們是做什麽用的.

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

  例如:


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

  這樣分派一個按鈕給一個物件, 並且提供一個指標給callback函數.

  許多物件同時也是containers. 如果您看看以下的組織圖, 您會看到許多物件由GtkContainer而來 所有這一類的物件都可以用GTK_CONTAINER巨集產生使用containers.

  5.2 物件組織圖

  這裡是一些參考, 物件組織圖.


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

  5.3 沒有視窗的物件

  以下的物件跟視窗沒有關係. 如果您希望接取它們的事件, 您需要使用GtkEventBox. 請見 EventBox物件


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

  再過來我們會一個一個物件來示範如何產生及顯示. 一個很好的範例是testgtk.c, 您可以在gtk/testgtk.c裡面找到.

  6. 按鈕物件

  6.1 一般按鈕

  我們到現在為止一直都看到按鈕物件. 實在很簡單. 有兩種方法來產生按鈕, 您可以用gtk_button_new_with_label() 來產生一個有標籤的按鈕, 或用gtk_button_new()來產生一個空按鈕. 現在您可以任意操作它, 接張圖(pixmap)或標籤上去, 隨你. 要達到這樣的效果, 先產生一個b
ox, 然後用gtk_box_pack_start 將您的objects封裝進這個box. 然後用gtk_container_add來封裝這個box到按鈕中.

  這裡是個使用gtk_button_new來產生一張有圖片及標籤的按鈕. 我將程式分細使您在往後可以利用這個程式.


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

  xpm_label_box函數可用來包裝xpm及labels到物件中使其成為container.

  6.2 雙態按鈕

  雙態按鈕跟一般按鈕很像, 除了它們有兩種狀態, 由"click"來切換. 他們可以是"被按下的", 當您再按一次, 它們會還原. 再按一次會再被按下去.

  雙態按鈕是check按鈕及radio按鈕的基礎. 它們有許多函數是繼承雙態按鈕而來. 我等一下會把它們指出來.

  產生一個雙態按鈕:


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

  您可以看到, 這些跟一般按鈕函數的用法一模一樣. 第一個產生一個空白的雙態按鈕, 第二個則有個標籤包在一起.

  要取得雙態按鈕的狀態, 包含了check及radio按鈕也一樣, 我們用以下範例中所使用的巨集. 這會測試該按鈕的狀態. 當我們按下按鈕時, 雙態按鈕的信號("toggled")會送給我們. 要取得其狀態, 設定好信號處理器來接取"toggled"信號, 並使用該巨集來決定其狀態. 該callback函
數看起來像這樣:


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

  以上的函數呼叫可用來設定雙態按鈕的狀態(包含check及radio) 傳您所產生的按鈕做為第一個參數, 然後TRUE或FALSE做為第二個用來指定它是up(release)或down(depressed). 內定值是up, 或FALSE.


voidgtk_toggle_button_toggled(GtkToggleButton *toggle_button);

  這個會切換該按鈕, 並送出"toggled"信號.

  6.3 Check按鈕

  Check按鈕有很多性質與雙態按鈕一樣, 但外觀看起來不同. 在文字上沒有邊框, 而在左邊有個小方塊. 這個在軟體中選擇要不要某個選項用得很頻繁.

  兩個產生的函數跟一般按鈕一樣.


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

  new_with_label函數產生一個check按鈕並帶一個標籤在其右側.

  測試check按鈕的方法跟雙態按鈕一樣.

  6.4 Radio Buttons

  Radio按鈕與check按鈕很像, 除了它們是成群的. 因而我們可以在一群中選擇其中一個.

  產生一個新的radio按鈕是由以下函數所達成的:


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

  您看到有個額外的參數. 因為它需要一個group來達成這項工作. 第一個函數可以用NULL來做參數. 然後產生一個group:


GSList* gtk_radio_button_group (GtkRadioButton *radio_button);

  然後傳這個group做為第一個參數給gtk_radio_button_new或new_with_label. 您也可以乾脆指明那一個是內定的.


void gtk_toggle_button_set_state (GtkToggleButton *toggle_button,
gint state);

  這個跟雙態按鈕一樣.

  7. Tooltips物件

  他們是當您停在某個物件(像按鈕或其它物件)上幾秒時, 會自動出現的一個小的文字視窗. 它們很容易使用, 因此我只解釋一下, 而不給范常式式. 如果您想看看一些范常式式, 可參考GDK內的testgtk.c.

  有些物件(像標籤)無法與tooltips一起用.

  第一個呼叫的函數會產生一個新的tooltip. 您只需要呼叫這個函數一次. GtkTooltip這個函數的返回值可用來產生許多個tooltips.


GtkTooltips *gtk_tooltips_new (void);

  一旦您產生了一個新的tooltip, 您要設定到某個物件上, 只要呼叫這個函數即可.


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

  第一個參數是您剛才產生的tooltip, 接著是您希望使用的物件, 然後是您希望顯示的文字.

  這裡有個簡短的範例:


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

  tooltip還有其它的一些函數. 我只簡短的介紹一下.


void gtk_tooltips_destroy(GtkTooltips *tooltips);

  銷毀tooltips.


void gtk_tooltips_enable (GtkTooltips *tooltips);

  使一套已失效的tooltips生效.


void gtk_tooltips_disable(GtkTooltips *tooltips);

  使一套tooltips生效.


void gtk_tooltips_set_delay(GtkTooltips *tooltips,
gint delay);

  設定要停留多少ms, tooltip才會出現. 內定值是1000ms, 即一秒.


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

  改變一個tooltip的文字內容.


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

  設定tooltips的前景及背景顏色.

  8. Container物件

  8.1 筆記本物件

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

  第一個您會用到的是產生一個新的筆記本物件.


GtkWidget* gtk_notebook_new (void);

  一旦物件產生後, 共有12個函數可以操作該筆記本物件. 我們一個一個來看.

  第一個是要如何來安排"頁標籤". 這些"頁標籤"或"tabs", 可以用四個位置, 上, 下, 左, 右.


void gtk_notebook_set_tab_pos (GtkNotebook *notebook, GtkPositionType pos);

  GtkPostionType可以是以下四個, 很好認.


GTK_POS_LEFT
GTK_POS_RIGHT
GTK_POS_TOP
GTK_POS_BOTTOM

  GTK_POS_TOP是內定值.

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


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

  這些函數新增一頁到筆記本, append由後新增, prepend由前新增. *child是要插入筆記本的物件, *tab_label是頁標籤.


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

  參數與_append_及_prepend_相同, 除了多出一個參數, 位置. 該參數用來指定要插在那裡.

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


void gtk_notebook_remove_page (GtkNotebook *notebook, gint page_num);

  這個函數移除掉所指定的那一頁.

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


gint gtk_notebook_current_page (GtkNotebook *notebook);

  以下兩個函數是向前或向後移動. 若目前在最後一頁, 而您用gtk_notebook_next_page, 那麽筆記本會繞回第一頁, 反之亦然.


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

  以下函數設定"有效頁". 如果您希望筆記本開啟到例如第五頁, 您可以用這個函數. 內定頁為第一頁.


void gtk_notebook_set_page (GtkNotebook *notebook, gint page_num);

  以下兩個函數可新增及移除頁標籤及邊框.


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

  現在我們來看個範例, 它是從testgtk.c中展開的, 用了所有13個函數. 該程式產生一個筆記本及六個按鈕, 包含11頁, 以三種方式加頁, appended, inserted,及prepended. 這些按鈕允許您旋轉頁標籤位置, 新增/移除頁標籤及邊框, 移除一頁, 以前向及後向改變頁的位置, 及離開
程式.


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

  8.2 捲動視窗

  捲動視窗是用來產生在視窗內可捲動的區域. 您可以在捲動視窗中插入任意種物件, 而不管視窗大小如何, 這些物件因為在捲動區域內, 因此都可以被用到.

  您可以用以下函數來產生捲動視窗:


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

  第一個參數是水平調整方向, 第二個是垂直調整方向. 它們一般被設為NULL.


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

  第一個參數是想要改變的視窗. 第二個是設定水平捲動的方式, 第三個是垂直捲動的方式.

  policy可以是GTK_POLICY_AUTOMATIC, 或GTK_POLICY_ALWAYS. GTK_POLICY_AUTOMATIC會自動決定是否使用scrollbars. GTK_POLICY_ALWAYS則scrollbars始終在那裡.

  這裡是個將100個雙態按鈕包進一個捲動視窗的範例.


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

  玩弄一下這個視窗. 您會看到scrollbars如何反應. 您也會想用用gtk_widget_set_usize()來設定視窗內定的大小.

  9. EventBox視窗物件

  這隻在gtk+970916.tar.gz以後的版本才有.

  有些gtk物件並沒有相關聯的視窗, 它們是由其parent所畫出來的. 因此, 他們不能收到事件. 如果它們大小不對, 他們無法收到事件來修正. 如果您需要這樣的功能, 那麽EventBox就是您想要的.

  初看之下, EventBox物件看來好像毫無用途. 它在螢幕上什麽事也不做, 也不畫, 對事件也不反應. 不過, 它倒提供一項功能 - 他提供一個X window來服務其子物件. 這很重要, 因為GTK物件很多都跟X window不相關聯. 不用X window省下記憶體並加快其速度, 但也有其缺點. 一個
物件沒有X window無法接收事件, 而且無法裁切其內容. 雖然它叫``EventBox''強調其事件處理功能, 這個物件也可用來做裁切.

  要產生一個EventBox物件, 使用:


GtkWidget* gtk_event_box_new (void);

  一個子視窗物件可被加到EventBox之下:


gtk_container_add (GTK_CONTAINER(event_box), widget);

  以下的簡單示範, 使用了一個EventBox - 一個標題, 並且設定成滑鼠在標題上點一下程式就會離開.


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

  10. 其它物件

  10.1 標籤

  標籤在GTK中用得很多, 而且很簡單. 標籤不送信號, 因為它們跟X window沒有關係. 如果您要接取信號, 或裁切, 可用EventBox物件.

  產生新的標籤可用:


GtkWidget* gtk_label_new (char *str);

  唯一個參數是您想要顯示的文字.

  在產生標籤後要改變其文字, 可用:


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

  第一個參數是剛才所產生的標籤(使用GTK_LABEL巨集來分派), 第二個是新的字串.

  新字串的空間會自動被配置.

  要取得目前的字串可用:


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

  第一個參數是標籤, 第二個是返回字串的位置.

  10.2 Progress Bars

  Progress bars是用來顯示某個作業的操作狀態. 他們很容易使用, 您會看到以下的程式. 我們先來產生一個Progress Bar.


GtkWidget *gtk_progress_bar_new (void);

  這樣就產生了, 夠簡單的了.


void gtk_progress_bar_update (GtkProgressBar *pbar, gfloat percentage);

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

  Progress Bars一般與timeouts及其它函數一起使用, (see section on Timeouts, I/O and Idle Functions) 這是因為多工的考量. gtk_progress_bar_update會處理這方面的事務.

  這裡是使用Progress Bar的範例, 並用timeouts來更新. 同時也會展示如何重設Progress Bar.


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

  在這個小程式中有四個區域在一般的Progress Bar操作上, 我們會一個一個看到.


pbar = gtk_progress_bar_new ();

  產生Progress Bar, pbar.


ptimer = gtk_timeout_add (100, progress, pbar);

  使用timeouts來產生一個固定時間間隔, Progress Bar不見的一定要用timeouts.


pvalue = GTK_PROGRESS_BAR (data)->percentage;

  這行指定目前的值.


gtk_progress_bar_update (GTK_PROGRESS_BAR (data), pvalue);

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

  這就是Progress Bars, enjoy.

  10.3 對話盒

  對話盒物件很簡單, 是個預先做好的視窗. 對話盒的結構如下:


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

  您看到, 它就是產生一個新的視窗. 然後包一個vbox到它上面, 接著一個seperator, 然後是hbox給"action_area".

  對話盒是用於通告訊息, 及類似用途. 這很基本, 只有一個函數:


GtkWidget* gtk_dialog_new (void);

  因此要產生新的對話盒,


GtkWidget window;
window = gtk_dialog_new ();

  這會產生對話盒, 然後您可以任意使用它. 然後將按鈕包裝到action_area, 像這樣:


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

  然後您也可以用封裝新增一個vbox, 例如, 一個新標籤, 試試看:


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

  做為一個對話盒的範例, 你可以使用兩個按鈕在action_area, 一個Cancel及Ok按鈕, 及一個標籤在vbox area, 問使用者一個問題或提示錯誤的發生等等. 然後您可以接到不同的信號上來處理使用者的選擇.

  10.4 Pixmaps

  Undocumented.

  10.5 Images

  Undocumented.

  11. 檔案選取物件

  檔案選取物件是個又快又簡單的方法來產生一個File dialog box. 它有Ok, Cancel, 及Help按鈕, 可以大量縮短開發時間.

  要產生一個新的檔案選取物件可用:


GtkWidget* gtk_file_selection_new (gchar *title);

  要設定檔名, 例如指定目錄, 或給定內定檔名, 可用這個函數:


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

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


gchar* gtk_file_selection_get_filename (GtkFileSelection *filesel);

  另外還有指標指向檔案選取物件的內容:


dir_list
file_list
selection_entry
selection_text
main_vbox
ok_button
cancel_button
help_button

  當然了您會想要用ok_button, cancel_button, 及help_button指標用來處理信號.

  在這裡包含了從testgtk.c偷來的一個範例, 修改成自己的版本. 在此您可以看到, 要產生一個檔案選取物件不需要做太多事. 在此, 在這個範例中, Help button顯示在螢幕中, 它沒做什麽事, 因為沒有信號接在上面.


#include
/* 取得選取的檔名並顯示在螢幕上 */
void file_ok_sel (GtkWidget *w, GtkFileSelection *fs)
{
g_print ("%s
", gtk_file_selection_get_filename (GTK_FILE_SELECTION (fs)));
}
void destroy (GtkWidget *widget, gpointer *data)
{
gtk_main_quit ();
}
int main (int argc, char *argv[])
{
GtkWidget *filew;
gtk_init (&argc, &argv);
/* 產生新的檔案選取物件 */
filew = gtk_file_selection_new ("File selection");
gtk_signal_connect (GTK_OBJECT (filew), "destroy",
(GtkSignalFunc) destroy, &filew);
/* 把ok_button接到file_ok_sel功能 */
gtk_signal_connect (GTK_OBJECT (GTK_FILE_SELECTION (filew)->ok_button),
"clicked", (GtkSignalFunc) file_ok_sel, filew );
/* 把cancel_button接到destroy物件 */
gtk_signal_connect_object (GTK_OBJECT (GTK_FILE_SELECTION
(filew)->cancel_button),
"clicked", (GtkSignalFunc) gtk_widget_destroy,
GTK_OBJECT (filew));
/* 設定檔名, 就像是要存一個檔案一樣, 而我們是給定一個內定檔名 */
gtk_file_selection_set_filename (GTK_FILE_SELECTION(filew),
"penguin.png");
gtk_widget_show(filew);
gtk_main ();
return 0;
}

  12. List物件

  GtkList物件被設計成是個vertical container, 而在其中的物件必須是GtkListItem.

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

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


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

  GtkList的selection欄指向一個所有items的link list, 其中記錄所有被記錄的項目, 若為`NULL'則 selection為空的. 因此要知道目前的selection, 我們可以讀取GTK_LIST()->selection一欄. 但不要修改它們, 因為它們是由內部所維護.

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

  selection_mode可以是以下其中一種:

  GTK_SELECTION_SINGLE selection可以是`NULL' 或對一個已選項目, 包含一個GList* pointer.

  GTK_SELECTION_BROWSE 若list沒有有效的物件, selection可以是`NULL' 否則它會包含一個GList* pointer, 而且就是一個list item.

  GTK_SELECTION_MULTIPLE 若list中沒有item被選取, selection可以是`NULL' 否則會有一個GList* pointer, 並且指向第一個selected item. 並一直向後接到第二個...

  GTK_SELECTION_EXTENDED selection永遠為`NULL'.

  內定為GTK_SELECTION_MULTIPLE.

  12.1 信號


void GtkList::selection_changed (GtkList *LIST)

  當selection區域改變的時候, 這個信號會被觸發. 這會在當GtkList的子物件被select或unselect時發生.


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

  當GtkList的子物件被select時, 這個信號會被觸發. 這一般在gtk_list_select_item(), gtk_list_select_child(), 按鈕被按下及有時間接觸發或有子物件新增或移除時發生.


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

  當GtkList的子物件被unselect時, 這個信號會被觸發. 這一般在gtk_list_unselect_item(), gtk_list_unselect_child(), 按鈕被按下及有時間接觸發或有子物件新增或移除時發生.

  12.2 函數


guint gtk_list_get_type (void)

  返回`GtkList' type identifier.


GtkWidget* gtk_list_new (void)

  產生新的`GtkList' object. 新的物件其返回值為`GtkWidget' object的指標. `NULL'表示失敗.


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

  插入list items到LIST裡面, 由POSITION開始. ITEMS是雙向鏈結串列. 每個項目要指向一個產生出來的GtkListItem.


void gtk_list_append_items (GtkList *LIST, GList *ITEMS)

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


void gtk_list_prepend_items (GtkList *LIST, GList *ITEMS)

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


void gtk_list_remove_items (GtkList *LIST, GList *ITEMS)

  從LIST中移除list items. ITEMS是雙向鏈結串列, 每個node要指向child. 設計者要自行呼叫g_list_free(ITEMS). 設計者也要自行處理掉list items.


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

  從LIST中移除並銷毀list items.


void gtk_list_select_item (GtkList *LIST, gint ITEM)

  透過在LIST中目前的位置,觸發GtkList::select_child信號給指定的list item.


void gtk_list_unselect_item (GtkList *LIST, gint ITEM)

  透過在LIST中目前的位置,觸發GtkList::unselect_child信號給指定的list item.


void gtk_list_select_child (GtkList *LIST, GtkWidget *CHILD)

  觸發GtkList::select_child信號給指定的CHILD.


void gtk_list_unselect_child (GtkList *LIST, GtkWidget *CHILD)

  觸發GtkList::unselect_child信號給指定的CHILD.


gint gtk_list_child_position (GtkList *LIST, GtkWidget *CHILD)

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


void gtk_list_set_selection_mode (GtkList *LIST, GtkSelectionMode MODE)

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


GtkList* GTK_LIST (gpointer OBJ)

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


GtkListClass* GTK_LIST_CLASS (gpointer CLASS)

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


gint GTK_IS_LIST (gpointer OBJ)

  決定是否一個generic pointer對應到`GtkList' object. *Note Standard Macros::, for more info.

  12.3 範例

  以下是個范常式式, 將會列出GtkList的選擇改變, 並讓您用滑鼠右鍵"逮捕"list items.


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

  12.4 List Item物件

  GtkListItem物件是設計用來做為container的子物件, 用來提供selection/deselection的功能.

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

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

  12.5 信號

  GtkListItem不產生自己的新的信號, 但它繼承GtkItem的信號.

  12.6 函數


guint gtk_list_item_get_type (void)

  返回`GtkListItem' type identifier.


GtkWidget* gtk_list_item_new (void)

  產生新的`GtkListItem' object. 新物件返回一個指標給`GtkWidget'物件. `NULL'表示錯誤.


GtkWidget* gtk_list_item_new_with_label (gchar *LABEL)

  產生新的`GtkListItem'物件, 並帶一個標籤. 並返回一個`GtkWidget' object. `NULL'表示錯誤.


void gtk_list_item_select (GtkListItem *LIST_ITEM)

  這個函數基本上是將gtk_item_select (GTK_ITEM (list_item))包裝起來. 它將會送GtkItem::select信號. *Note GtkItem::, for more info.


void gtk_list_item_deselect (GtkListItem *LIST_ITEM)

  這個函數基本上是將gtk_item_deselect (GTK_ITEM (list_item))包裝起來. 它將會送GtkItem::deselect信號. *Note GtkItem::, for more info.


GtkListItem* GTK_LIST_ITEM (gpointer OBJ)

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


GtkListItemClass* GTK_LIST_ITEM_CLASS (gpointer CLASS)

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


gint GTK_IS_LIST_ITEM (gpointer OBJ)

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

  12.7 例子

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

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

  譯註: List物件這一篇本身比較不容易翻譯, 因原文本身講的並不太清楚. 此外, 其結構原本就比較繁瑣. 若您在此糟遇問題, 可來信反應. 譯者會想辦法將其改善.

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

  13. Undocumented Widgets

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

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

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

  13.1 Text Entries

  13.2 Color Selections

  13.3 Range Controls

  13.4 Rulers

  13.5 Text Boxes

  13.6 Previews

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

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

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

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

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

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

  When Not to Use Previews

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

  Let's go previews!

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

  [Image][Image]

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


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

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

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

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


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

  And here's the result:

  Misc

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

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

  Filling In a Preview

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

  void


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

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

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

  Image Preview

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


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

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


  That's all, folks!

  13.7 Curves

  14. Menu物件

  有兩種方式來產生選單物件, 一種簡單的, 一種難的. 兩種各有其用途, 但您可以用menu_factory(簡單的). 難的方法是一個一個產生. 簡單的是用gtk_menu_factory 這個簡單多了, 但各有其優劣之處.

  menufactory很好用, 雖然另外寫一些函數, 以手動函數來產生這些選單會比較有用. 不過, 以menufactory, 也是可以加影像到選單中.

  14.1 Manual Menu Creation

  在教學的目的上, 我們先來看看難的方法.:)

  先看看產生選單的函數. 第一個當然是產生一個新的選單.


GtkWidget *gtk_menu_bar_new()
GtkWidget *gtk_menu_new();

  這個函數返回一個新的選單, 它還不會顯示.

  以下兩個函數是用來產生選單項目.


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

  動態新增


gtk_menu_item_append()
gtk_menu_item_set_submenu()

  gtk_menu_new_with_label及gtk_menu_new函數一個產生一個新的選單項目並帶標籤, 另一個則是個空的選單項目.

  產生選單的步驟大致如下:

  使用gtk_menu_new()來產生一個新的選單

  使用gtk_menu_item_new()來產生一個新的選單項目. 這會是主選單, 文字將會是menu bar本身.

  使用gtk_menu_item_new來將每一個項目產生出來用gtk_menu_item_append()來將每個新項目放在一起. 這會產生一列選單項目.

  使用gtk_menu_item_set_submenu()來接到心產生的menu_items到主選單項目. (在第二步中所產生出來的).

  使用gtk_menu_bar_new來產生一個menu bar. 這一步僅需做一次, 當我們產生一系列選單在menu bar上.

  使用gtk_menu_bar_append來將主選單放到menubar.

  14.2 Manual Menu範例

  我們來做做看, 看看一個範例會比較有幫助.


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

  您也可以設定一個選單項目無效, 並使用accelerator table結合按鍵到選單功能.

  14.3 使用GtkMenuFactory

  我們已經示範了難的方法, 這裡是用gtk_menu_factory的方法.

  14.4 Menu Factory範例

  這裡是menu factory的範例. 這是第一個檔案, menus.h. 另有menus.c及main.c


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

  And here's the main.h


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

  這裡是makefile.


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

  15. Timeouts, IO及Idle函數

  15.1 Timeouts

  您可能會想要在gtk_man idle時, 做一些事情. 你有好幾個選擇. 使用以下這些函數可以產生一個timeout函數, 它每間隔一陣子就會去呼叫您的函數.


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

  第一個參數是每間隔interval milliseconds會去呼叫您的函數. 第二個是該函數. 第三個是要傳給該函數的資料. 返回值則為該行程的"標籤".


void gtk_timeout_remove (gint tag);

  您可以在您的timeout函數返回zero或FALSE來停止timeout函數. 這就是說您必須返回非零值, 如TRUE.

  你的callback函數必須像這樣:


gint timeout_callback (gpointer data);

  15.2 監督IO

  GTK另一個漂亮的功能是有辦法去檢查一個file descriptor的資料. (as returned by open(2) or socket(2)). 這個在網路軟體上很有用:


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

  第一個參數是您想看的file descriptor, 第二個是你要GDK去看那一項, 可以是以下幾項:

  GDK_INPUT_READ - 當file descriptor有資料的時候會去呼叫您的函數.

  GDK_INPUT_WRITE - 當您的file descriptor可以被寫入資料的時候.

  第三個當然是callback函數. 第四個是要傳給callback的資料.

  返回值是可用來停止監督的一個"標籤".


void gdk_input_remove (gint tag);

  當您的callback函數返回zero或FALSE時, GTK會停止呼叫您的callback函數.

  15.3 Idle函數

  當什麽事情都不發生時, 您要呼叫一個函數時要怎麽辦?


gint gtk_idle_add (GtkFunction function,gpointer data);

  當什麽事都不發生時, GTK會去呼叫這個函數.


void gtk_idle_remove (gint tag);

  這個不用解釋了吧. 您若在idle函數返回zero或FALSE會停止idle函數的運行.

  17. glib

  glib提供許多有用的函數及定義. 我把它們列在這裡並做簡短的解釋. 很多都是與libc重複, 對這些我不再詳述. 這些大致上是用來參考, 您知道有什麽東西可以用就好.

  17.1 定義

  為保持資料型態的一致, 這裡有一些定義:


G_MINFLOAT
G_MAXFLOAT
G_MINDOUBLE
G_MAXDOUBLE
G_MINSHORT
G_MAXSHORT
G_MININT
G_MAXINT
G_MINLONG
G_MAXLONG

  此外, 以下的typedefs. 沒有列出來的是會變的, 要看是在那一種平台上. 如果您想要具有可移植性, 記得避免去使用sizeof(pointer). 例如, 一個指標在Alpha上是8 bytes, 但在Inter上為4 bytes.


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

  17.2 雙向鏈結串列

  以下函數用來產生, 管理及銷毀雙向鏈結串列.


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

  17.3 單向鏈結串列

  以下函數是用來管理單向鏈結串列:


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

  17.4 記憶體管理


gpointer g_malloc(gulongsize);

  這是替代malloc()用的. 你不需要去檢查返回值, 因為它已經幫你做好了, 保證.


gpointer g_malloc0 (gulongsize);

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


gpointer g_realloc (gpointermem,gulongsize);

  重定記憶體大小.


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

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


void g_mem_check(gpointermem);

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

  17.5 Timers

  Timer函數..


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

  17.6 字串處理


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

  17.7 工具及除錯函數


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

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


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

  這裡是"hello world"用到的一些函數:


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

  顯示錯誤訊息, 其格式與printf一樣, 但會加個"** ERROR **: ", 然後離開程式. 只在嚴重錯誤時使用.


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

  跟上面一樣, 但加個"** WARNING **: ", 不離開程式.


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

  加個"message: ".


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

  printf()的替代品.

  最後一個:


gchar* g_strsignal (gint signum);

  列印Unix系統的信號名稱, 在信號處理時很有用.

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

  18. 設定視窗物件屬性

  這裡描述如何操作視窗物件的函數集. 可用於設定外形, 空格, 大小等等.


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

  19. GTK的rc檔

  GTK有處理軟體內定值的一套方法, 即使用其rc檔. 這些可以用來設定顏色, 並且可以用pixmaps來設定某些物件的背景.

  19.1 rc檔的功能

  當您的軟體啟動時, 您應該呼叫這一行:


void gtk_rc_parse (char *filename);

  將您的檔名傳入做為參數. 這會使GTK來分析這個檔案, 並使用設定值來設定物件的形態.

  如果您希望有特別樣子的物件, 但可從另一個物件做為基礎來產生, 可以用這個:


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

  傳入您新產生的物件做為第一個參數, 您要給它的名字做為第二個參數. 這樣的話可以讓你透過rc檔來改變該物件的屬性.

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


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

  則這個按鈕被給了一個名字叫"special button" 並且會被指向rc檔中的"special button.GtkButton". [<--- 要是我錯了, 修正我!]

  以下的rc檔設定主視窗的屬性, 並讓所有子視窗繼承其形態. 在程式中的程式碼為:


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

  而該形態則在rc檔中定義為:


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

  這會設定所有GtkButton物件, 成為在"main window"中的"main_buttons"的形態.

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

  19.2 GTK的rc檔案格式

  GTK的rc檔格式如以下的範例. 這個testgtkrc檔從GTK distribution而來, 但我加了點料及註解進去. 您也可以加一點解釋來讓使用者做微調.

  有好幾個指令來改變該物件的屬性.

  fg - 前景顏色.

  bg - 背景顏色.

  bg_pixmap - 背景圖片pixmap.

  font - 字型.

  除此, 一個物件可以有好幾種狀態. 您可以設定不同的顏色, 圖案及字形. 這些狀態是:

  NORMAL - 物件一般的狀態, 沒有滑鼠滑過, 沒有被按下.

  PRELIGHT - 滑鼠滑過該物件.

  ACTIVE - 當該物件被壓下或按下, 該視窗會生效.

  INSENSITIVE - 當該物件被設為失效.

  SELECTED - 當物件被選擇.

  當我們使用"fg"及"bg"來設定該物件的顏色時, 其格式為:


fg[] = { Red, Green, Blue }

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

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

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

  font指令很簡單:


font = ""

  比較難的是找出想要的font名稱. 用xfontsel或類似的工具來找會有點幫助.

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

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

  當"parent"用來當成一個屬性時, 該物件會繼承其父所有財產.

  當您定義一個形態時, 可以指定以前已經定義過的形態給新的.


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

  這個例子用"button"的形態, 產生一個"main_button"形態, 並且只改變font及背景顏色.

  當然了並非所有屬性都對所有物件生效. 因為該物件不見得擁有該屬性.

  19.3 rc檔的範例


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


[ 本帖最後由 gxf 於 2007-10-9 01:22 編輯 ]

[火星人 ] 給GTK+初學者------中文版已經有1269次圍觀

http://coctec.com/docs/linux/show-post-169255.html