最近心血來潮地在閱練 GNOME (GNU Network Object Model Environment) 的 GTK+ widget set,發現它與古早以前的 Xt and Motif Programming 有著類似的架構,所以筆者就回老家找出 27 年前的 Xt 與 Motif 的程式設計書籍 (1991 年前後所購買的有關於 X Window Programming 書籍),然後在 Ubuntu Linux 上演練這些非常非常古老的 widget set。
先來看一個很簡單的範例程式:memo.c
Xt 是 X Window Intrinsic Toolkit 的縮寫,它的底層是 X library (Xlib),而 Motif widget set 則是架在 Xt 上面。Well,筆者一直以為 Motif widget 已經陣亡了,最近閱練 GTK+,才意外地發現它還活著,不過同時期出生的 OpenLook XView widget 應該已是陣亡無誤;唉!好久好久以前的事情了,1990 年,當時的 Sun Microsystems 的聲勢是如日中天,其 Sparc Workstation 橫掃全球的工作站市場,風光極了,而且 OpenLook XView 在當時就已經開發出類似 GTK+ glade 的 RAD tool (a rapid application development tool for user interface design),當時的 Solaris OS 平台上也有類似 eclipse IDE 的開發工具,名字叫做 SparcWorks (做得很不錯,比 eclipse 早十多年就已出現),在網路時代又發明了 Java Programming Language,在硬體與軟體方面都頗有建樹,但有誰能料到在 dot-com bubble 之後,它竟然經營到虧損連連,最後則是悽慘到被 Oracle 所併購!Sun Microsystems 是筆者年輕時期所景仰的公司之一,目睹著它的殞落,筆者心中有著非常深刻的不捨與感慨!
好了,回來看看範例程式。line 12 的 XtAppInitialize() 是在做初始化的工作,line 21 是在建立一個 compound string,因為後續的 Label widget 需要透過 compound string 來設定 Label widget 的顯示字樣;line 23 的 XtVaCreateManagedWidget() 是在建立一個 Label widget,在該函示中,我們指明 shell widget 是 msg 這個 Label widget 的 Parent widget,跟在後面的 data pair,例如 XmNwidth 與其值,則是在設定這個 Label widget 的 properties (X Window 與 Motif 的術語叫做 resources,但以 properties 來解釋對一般人來說比較容易瞭解),data pair 的個數不限,並以 NULL 來做結。line 32 的 XtRealizeWidget() 是把 shell widget 以及其所包含的 child widget 都顯示到螢幕上;line 34 的 XtAppMainLoop() 會讓整個應用程式進入 Event Loop (X Window System 是一個 Event-Driven Window System,詳細的解說請自行 Google 一下,或到 wikipedia 鍵入 X library,該篇文章有一個使用 X library function call 的程式,最後面的 for ( ; ; ) { XNextEvent(...) Blah Blah Blah } 就是 Event Loop)。
編譯:gcc -g -o memo memo.c -lXm -lXt -lX11
Xm (Motif Library)
Xt (X Intrinsic Toolkit Library)
X11 (X library)
執行:./memo "Motif is a stone age widget set!"
memo.c 這個範例程式沒有使用到任何的 Event Callback Function,所以筆者寫了另一個 push_button.c,以說明什麼是 Event Callback Function。
範例程式:push_button.c
光陰似箭,沒想到筆者竟然已經五十歲了 (line 19)!好想高歌一曲 forever young (forever young, I want to be forever young;I don't want to be LKK!);line 33 的 XtAddCallback() 是對 PushButton widget 登錄一個 Callback Function,要求 PushButton 在被按下並放開之際 (when it is activated),記得要呼叫 Callback Function,打個白話的比方來解釋,就是你跟一位好朋友說:「你如果遇到『好康的事情』,記得要『通知我』喔!」這好康的事情就是 activated event,而通知我就是 callback function。
XmPushButton 支援三種 Event Type (reason) : 1. arm; 2. disarm; 3. activate。我們可以將這三種 Event 都對應的同一個 CallbackFunction,然後在 CallbackFunction 再透過第三個參數抓取 reason 來判斷到底是哪一個 event (arm, disarm, or activate) 引發了 CallbacFunction。XmNarmCallback:mouse cursor 在 button widget 裡面且 mouse button 被持續壓著;XmNdisarmCallback:持續地按著 mouse button,然後將 mouse cursor 移出 button widget 之外,最後把 mouse button 放開。
XtAddCallback() 的第四個參數是 application registered data (user defined data),當 Callback Function 被呼叫時,會透過 Callback Function 的第二個參數傳過來 (line 41)。
X Intrinsic Toolkit 的 callback function 的 prototype 為:
void CallbackFunction(Widget, XtPointer, XtPointer);
XtPointer 實際上就是 generic pointer (void *)。
CallbackFunciotn 的第三個參數 (line 42),是 Intrinsic Toolkit 或 Widget Set 所傳過來的 data,因為我們這個範例程式是使用 Motif PushButton widget,所以第三個參數是 Motif PushButton 所傳過來的 data;使用 Motif widget set 時,CallbackFunction 所傳過來的第三個參數,其前兩個成員一定是 integer 與 XEvent pointer:
typedef struct {
int reason;
XEvent *event;
} XmAnyCallbackStruct;
如有必要,可以根據 reason 與 event->type,將第三個參數做 type cast,把它轉成實際的 callback structure。
順便提一下,line 15 取名 toplevel 的 widget,是就 widget 的繼承關係而所命名 (toplevel 是 parent widget,而 button 是 child widget,如此的繼承關係是在 line 27 透過 XtVaCreateManagedWidget() 所定義),但如果從 visual stack 的觀點來看,toplevel widget 其實是最底層的 widget,button widget 是疊在它上面。
執行:
好了,兩個範例程式講完了,還有不懂的部分請自行閱練把它看懂。
最後,分享一個可以省點時間與力氣的 Makefile:
- 安裝 X library and X Intrinsic Toolkit in Ubuntu Linux: sudo apt-get install libx11-dev
- 安裝 Motif library: sudo apt-get install libmotif-dev
- 安裝好之後,libX11 libXt libXm 都是放在 /usr/lib/x86_64-linux-gnu 的目錄之下,最好在 /usr/lib 建立 symbolic link 指向這些 library,如此編譯時會比較方便,因為 compiler 會自動到 /usr/lib 目錄尋找這些 library (實際上是 Linkage Editor 會到 /usr/lib 去尋找這些 library,compiler 只是知會 Linkage Editor 去執行連結各個 object files 與相關 libraries 的工作 )。
《隨興攝影筆記簿》