让我们经由回答读者可能有的一些疑问来当作本文的开始...
[Q] GNOME 是什麽?
GNOME 是∶
UNIX 下图形操作界面环境
收集许多有用、小型的应用与工具程序
设计给应用程序的函式库集
[Q] 为何要使用 GNOME libraries ?
为什麽不乾脆直接使用一套 GUI 工具, 例如∶ GTK+ 、 Motif 或是 Qt 呢? 这些工具都有它们的优点,不过我相信使用 Gnome 的优点远远超过 单独使用那些 GUI 工具。用 Gnome ,你可以省下很多时间, 因为许多程序码都已经写好了,许多 widgets 也已经建立好了。 这些 widgets 会在全部使用 Gnome 的程序里有大略一致的外观和 格调,此外,程序码的共享,可以让许多应用程序省下记忆体使用 量,因为,这些程序码是共享函式库 (share library) 的一部份 而不是每个应用程序都重覆地载入它们。 其实, Gnome 最大的优势在於 Gnome libraries 可以让你专注在你 的应用程序所要达成的功能,而不必浪费时间在撰写使用者界面 与程序除错上。
原序照录
Developer and GNOME project member George Lebl gives both an overview of the power of the GNOME libraries and an introduction to the techniques of application building. Not only are the libraries used to build GUIs, they also serve as an instrumental part of many other projects. George illustrates GNOME development techniques with a simple Hello World application, taking you through it one step at a time.
开始撰写 GNOME 应用程序
所以你确信要用 GNOME ?好的,我们从最基本的开始, GNOME 应用程序 用了很多 libraries ,这些 libraries 是有阶层关系的,而 GNOME libraries 则在最顶端, GNOME libraries 包括有在线说明功能、类别及特制的 widgets ( 例如 GnomeCanvas ,一个快速、自由订制、高阶的绘图 widgets ) ,并提供应用程序的 framework ( 基础工作 )。
在图上第二高阶层的是 GTK ,属於 GTK+ libraries 的一部份。这个 libraries 提供了基本的 toolkit 和 widgets ( 例如∶按钮、标签和输入盒 (entry boxes) ) 来建立图形操作界面应用。大部份的图形操作界面程序码 是直接经由 GTK , GTK 也提供了精巧的物件系统供 GNOME libraries 使用。
下一层则是 GDK ,是环绕最底层 X library 的薄薄包装,只有在你有特别 的绘图需求或设定视窗上特殊属性时才需要。
在最底层的 GLib 是 C 语言的工具函式库,在许多 container class ( 容器类别 ) 像是 linked lists , resizable array , variable-length strings , hashes , caches , an event loop 和其他有用的类别上提供可携性 (portability) 与工具函式。
不要认为 C 语言是你在发展 GNOME 应用程序时唯一可用的语言, 事实上,因为 GTK+ 和 GNOME 都是以 C 语言写的,它很容易被 其他语言套用,例如 Objective C, C++, Perl, Guile, Python, 和 Eiffel
GNOME & GTK+ 层次
GNOME libraries 区分成三个基本部份∶ libgnome, libgnomeui, libgnorba 。 libgnome 是个工具函式库,和 Glib 的理念类似,提供的服务包括∶ 组态 (configuration) 载入与存档,启动应用程序, MIME 型别辨识与 metadata 储存。 libgnomeui 部份以提供使用者界面为主,包含 GnomeApp 的 framework , GnomeCanvas , 和许多其他有用的特殊 widgets 。最後一部份 libgnorba 则是经由 CORBA 技术整合 GTK+/GNOME 的应用程序。
当我们设计 GNOME 程序时,很重要的事情是要晓得如何使用 GTK+ 。 所有的 libgnomeui 类别都是以 GTK+ 为基底,因此,使用 libgnomeui 的方式 很类似使用标准的 GTK+ widgets 。
GTK+ 是个 container-based ( 容器为主 ) 的 toolkit 设计,亦即大部份的 widget 都像 「 容器 ( container ) 」 一样可以承载其他 widget 。例如 一个按钮 ( button ) 很可能就是装著一个标签 (label) widget 的 「 容器 」。但也有一些容器只是提供将更多 widgets 放在一起的用途,其中最常用的 是 horizontal box 与 vertical box 将 widgets 排成一列,或是用来格式化 排列的表格。
这样的阶层允许所有的座标动态地计算,即表示你的应用程序在不同的视觉 相关设定下 ( 如字体大小 ) 都能正常运作。并且当视窗改变大小时 ( 或 甚至程序进行国际化 (I18N) 处理後 ) 能适当地重新计算并改变。这也使得 发展在执行时自动产生 GUI 的应用较容易。
当你需要连结一个事件 ( event ) 例如按下按钮,到你指定的动作 ( action ) 例如重绘视窗,你必需用 GTK 特别的讯号 (signal) 机制来 连结事件与自订的函式。例如∶按钮会有「按下」这个讯号让你可以 在使用者按下该按钮时执行连结程序的反应动作。 每一个讯号都是不同的, 宣告讯号的处理函式 (handler functions) 的时候,必须确定用对函式型别。
一个简单的示范程序
接下来我们来看一个简单的示范∶不能免俗地是个 ""Hello World"" 程序 --- 不过这可不只是普普通通的 ""Hello World"" 而已,还有功能表选单 (menu) 、 一个 ""关於"" 的对话窗 (dailog) ,以及状态列 (status bar) 。
这个应用程序有个功能表选单与可分离的子选单,还有可以显示选单提示的 状态列,并且自动储存使用者自订的加速键 ( 热键 ) ,有个标准的 ""关於"" 盒,另外所有选单的标准项目,还有自动转换语言的能力 ( 试著执行 ""LANG=zh_TW.Big5 ./hello_world"" ,就会看到正体中文字的选单 ) 。
/* Hello World (Gnome Edition)
* Listing 1
* This code is public domain, so use it as you please.
*/
/* this include will include everything needed for GNOME, including all the
* libraries that gnome needs, such as gtk, imlib, etc ...*/
#include
/* this is usually defined by autoconf but were just using simple makefiles */
#define VERSION ""1.0""
/* ""callback"" function (signal handler) which will quit the application*/
static void
exit_hello(GtkWidget *widget, gpointer data)
{
gtk_main_quit ();
}
/* callback function for when the window closes */
static int
delete_event(GtkWidget *widget, gpointer data)
{
gtk_main_quit ();
return FALSE; /* false means continue with closing the window */
}
/* a callback for the about menu item, it will display a simple ""About""
* dialog box standard to all gnome applications
*/
void
about_hello(GtkWidget *widget, gpointer data)
{
GtkWidget *box;
const char *authors[] = {
""James Bond"",
NULL
};
box = gnome_about_new(/*title: */ ""Hello World (Gnome Edition)"",
/*version: */VERSION,
/*copyright: */ ""(C) 1999 Secret Agents Inc."",
/*authors: */authors,
/*other comments: */
""An extremely complicated application which ""
""does absolutely nothing useful"",
NULL);
gtk_widget_show(box);
}
/* define the menus here */
static GnomeUIInfo file_menu [] = {
/* some item which is not one of the standard ones, the null
* would be the callback, however we dont want to really do anything */
GNOMEUIINFO_ITEM_NONE(""Something"",""Just an item which does nothing"",NULL),
/* standard exit item */
GNOMEUIINFO_MENU_EXIT_ITEM(exit_hello,NULL),
GNOMEUIINFO_END
};
static GnomeUIInfo help_menu [] = {
/* load the helpfiles for this application if available */
GNOMEUIINFO_HELP(""hello_world""),
/* the standard about item */
GNOMEUIINFO_MENU_ABOUT_ITEM(about_hello,NULL),
GNOMEUIINFO_END
};
/* define the main menubar */
static GnomeUIInfo main_menu [] = {
GNOMEUIINFO_MENU_FILE_TREE(file_menu),
GNOMEUIINFO_MENU_HELP_TREE(help_menu),
GNOMEUIINFO_END
};
/* Our main function */
int
main(int argc, char *argv[])
{
GtkWidget *app; /* pointer to our main window */
GtkWidget *w; /* pointer to some widget */
/* initialize gnome */
gnome_init(""hello_world"", VERSION, argc, argv);
/* create main window */
app = gnome_app_new(""hello_world"", ""Hello World (Gnome Edition)"");
/* connect ""delete_event"" (happens when the window is closed */
gtk_signal_connect(GTK_OBJECT(app), ""delete_event"",
GTK_SIGNAL_FUNC(delete_event),
NULL);
/* add the menus to the main window */
gnome_app_create_menus(GNOME_APP(app), main_menu);
/* setup appbar (bottom of window bar for status, menu hints and
* progress display) */
w = gnome_appbar_new(FALSE, TRUE, GNOME_PREFERENCES_USER);
gnome_app_set_statusbar(GNOME_APP(app), w);
/* make menu hints display on the appbar */
gnome_app_install_menu_hints(GNOME_APP(app), main_menu);
/* set up some bogus window contents */
w = gtk_label_new(
""Some really really really random
text
goes
here!"");
/* add those contents to the window */
gnome_app_set_contents(GNOME_APP(app), w);
/* show all the widgets on the main window and the window itself */
gtk_widget_show_all(app);
/* run the main loop */
gtk_main();
return 0;
}
以下我们来仔细地检视 ""Hello World"" 程序码,因为有些很容易理解,有些 部份在程序内有注解说明了,我们将专心地研究程序最基础的部份。 在程序码列表的最上方,我们引入 ( include ) gnome.h 这个标头档, 它会再引入其他必要函式库来达成 GNOME 之所有功能。
在主函式(main function)内,这段∶
/* initialize gnome */
gnome_init(""hello_world"", VERSION, argc, argv);
上面这行初始化所有的 GNOME 函式库, GTK+ ,还有其他必要之函式库, 并且解析 (parse ) 预设的命令列参数。每个 GNOME 程序都得执行这个 初始化动作,没有例外。
在下面一点的地方,你会看到这一段码∶
/* create main window */
app = gnome_app_new(""hello_world"", ""Hello World(Gnome Edition)"");
/* connect ""delete_event"" (happens when the winodw is closed */
gtk_signal_connect(GTK_OBJECT(app), ""delete_event"",
GTK_SIGNAL_FUNC(delete_event),
NULL);
这段程序码做两件事情,第一件事是建立一个新的应用程序视窗 (main) , 标题是 ""Hello World(Gnome Edition)"" ,然後将 ""app"" 指向这新建立的视 窗物件。第二件事情是 (gtk_signal_connect) 指定这个 (""delete_event"") 讯号发给物件 ""app"" 时,应用程序应该连结的处理函式。 当使用者从 window manager 发出关闭视窗的请求时, 该视窗就会收到 ""delete_event"" 讯号。以下是我们执行的处理函式。
/* callback function for when the window closes */
static int
delete_event(GtkWidget *widget, gpointer data)
{
gtk_main_quit();
return FALSE; /* false means continue with closing the window */
}
上面这段程序码 (gtk_main_quit) 告诉 GTK+ 它要离开主回圈 (main loop) ,然後传回 FALSE 值表示 GTK+ 在关闭後要继续处理。注意到每个讯号都有 自己的型态 ( 每个传回值可代表不同意义,参数也不太一样 ) 。例如从选单 (menu) 上的物件 callback 的处理函式型别长的像这样∶ ""void function( GtkWidget *, gopinter data)"" ,看一看 GTK+ 和 GNOME 的参考文件 ( 参阅 相关资源 ) 可以帮助你查到所有可用的讯号与其正确的型别。
接下来讲解在 GNOME 的应用程序 framework 下设定选单 (menu) 的方法, 在 GNOME framework 里,选单是 static structure array 的资料结构, 你只要定义好结构,适当栏位里填上正确的值, GNOME framework 就会 建立选单而且帮你连结 (bind) 必要的讯号 (signals) ,为了更简化这过程, 在 libgnomeui/gnome-app-helper.h 里有宣告一些现成的巨集 (macro) 方便快速引用,底下就是 ""hello_world"" 的选单宣告部份。
static GnomeUIInfo file_menu[]={
/* some item which is not one of the standard ones, the null
* would be the callback, however we dont want to really do anything */
GNOMEUIINFO_ITEM_NONE(""Something"",""Just an item which does nothing"", NULL),
/* standard exit item */
GNOMEUIINFO_MENU_EXIT_ITEM(exit_hello,NULL),
GNOMEUIINFO_END
};
static GnomeUIInfo help_menup[]={
/* load the helpfiles for this application if available */
GNOMEUIINFO_HELP(""hello)world""),
/* standard about item */
GNOMEUIINFO_MENU_ABOUT_ITEM(about_hello,NULL),
GNOMEUIINFO_END
};
/* define the main menubar */
static GnomeUIInfo main_menu[]={
GNOMEUIINFO_MENU_FILE_TREE(file_menu),
GNOMEUIINFO_MENU_HELP_TREE(help_menu),
GNOMEUIINFO_END
};
第一个 array 定义 ""档案""(file) 选单,一般而言, GUI style 强烈建议 应用程序都要有""档案""选单,而且在""档案""选单里有一项可以结束或离开程 式的选项,以引导使用者。我们的""档案""选单里有一个自订的项目,选单中 的标示是 ""something"" 选项,提示 (hint) 则是 ""Just an item which does nothing"" 。然而这个选项没有对映的 callback 处理函式 (callback 的参数是 NULL ),所以按下这选项不会有任何动作。""档案""选单里有 (标准的) ""离开程序""选项,在这范例中我们设定成按下""离开程序""选项 就执行 exit_hello() 这个 function , NULL 在这里是表示 callback function 的 data argument 是 NULL (第 15 行)。同样的方法可以 类推到 ""求助""(help) 选单之定义上,唯一例外的是第一项,它是个 标准的说明主题,如果你真的有产生并安装 HTML 格式的说明档 (help file) 就会看到效果。最後一个结构 main_menu 用来定义选单列 (menu bar) 。 在范例中有两个 (标准的) 树状子选单,定义这些选单结构後,程序中别忘记 呼叫 gnome_app_create_menus(...) (第 95 行) 来建立选单列并排列到 window 上方。
此外,我们也建立了 ""appbar"" (在 window 下方的状态列,游标指到选单 选项 (menu item) 时会显示较详细的提示) ,安置选项的提示到 appbar 上 参考下列这段码。
w=gnome_appbar_new(FALSE, TRUE, GNOME_PREFERENCES_USER);
gnome_app_set_statusbar(GNOME_APP(app),w);
/* make menu hints display on the appbar */
gnome_app_install_menu_hints(GNOME_APP(app),main_menu);
范例中最後一件主要的事情是在 window 上增加一点内容,这只是个示范 ,所以我们很简单地建个 GtkLabel 的 widget ,随意写上一些文字然後 把它设成 window 的主要内容 (content) ,如下段码∶
/* set up some bogus window contents */
w=gtk_label_new(""Some really really really random
text
goes
here!"");
/* add those contents to the window */
gnome_app_set_contents(GNOME_APP(app),w);
然後,将整个应用程序的 window 秀出来 (包含所有""装载""的 widgets ) ,跳到 main-loop-function (get_main) 来等待事件或讯号的发生。如下∶
/* show all the widgets on the main window and the window itself */
getk_widget_show_all(app);
/* run the main loop */
gtk_main();
到此处我们的介绍已经含括一 GNOME 应用程序最基本的要素。 当然,你可以用 GNOME libraries 做更多更多有趣的事,我们稍後再谈。
如何编程 GNOME 应用程序
现在你知道如何写简易的 GNOME 应用程序,当然,还得要晓得如何去编程它。 有个特别的 script 叫做 gnome-config ,是设计用来提供编程时 gcc 所需要 的正确命令列 (command line) ,我们知道编程成执行档需要 complie 与 link 两步骤,所以 gnome-config 有个功能切换,不是 --cflags 就是 --libs ,後面要跟著你指定要用的 libraries ,如 ""gnome"" 和 ""gnomeui"" 。假设你有一个应用程序,像是 hello_world.c 好了, 下面就是个可以正确编程的简单 Makefile 。
CFLAGS=-g -Wall ``gnome-config --cflags gnome gnomeui``
LDFLAGS=``gnome-config --libs gnome gnomeui``
all:hello_world
clean:
rm -f hello_world core
提醒你最後一行在 ""rm"" 前的空白,是一个 TAB 不是 space ,引号 `` 是 在 ESC 下方的 backquote 。
剩下的事情就交给神奇的 make 吧,当然对复杂的应用而言你还需要 更精巧的设定,不过,这并不关 GNOME 的事,事实上有个 gIDE 的计划,正 在设法将这复杂的过程包装起来。
使用 GNOME 的优势
GNOME libraries 也提供了应用程序发展者其他优势,例如 canvas widget 可以提供高阶的图形显示,而也不仅仅只是用来显示图形, Gnumeric spreadsheet 就用它来做格线。 GNOME 应用程序也能和 session manager 配合,在 GNOME libraries 里有相当可观的数量是用来支援这项功能。 还有多文件处理界面 (multiple-document interface,MDI) 对大多数要同时 处理多个资料档的应用很有帮助,虽然和普通一个程序处理一份资料的设计比 起来较为复杂,但 GNOME MDI framework 尽可能使它变的容易,如果设计时 有将 data 和 user interfaces 分开,从 single-document 转换成 multiple-document 应该不会有太大问题。
另一方面值得注意的是 CORBA 应用在 GNOME 里的情形。 GNOME 用 ORBit 做为它的 orb (负责应用程序之间沟通的模组) ,在有些部份 CORBA 已经被 大量采用,但并没有预期中的普遍。 Banobo 是最新的 document model , 还在发展当中,不过已经有些应用,例如 GNOME Workshop office 计划就采用 Banobo 。它将使得应用程序比过去更模组化,也能使应用程序间的互动 比现在更广泛。
发展工具介绍
有许多工具能帮助 GNOME 程序设计者简化应用程序的设计过程,我在这里提 到的都是还在发展当中,不过已经算是可以用,当然,在这些工具发表 1.0 版之前我并不建议你在重要的计划里采用它。
其中一项工具叫做 Glade ,是个界面产生器,它不只建立程序码,也建立 整个开发环境,相当方便。未来,若能和 gIDE 整合,将会是完整的、充份 的发展工具。
另一个「工具」 libglade 并不是个可执行的程序,它是一个 library , 在应用程序开始执行时,读取 Glade 产生的描述档案,然後建立应用程序 的界面。相较於 Glade 产生原始程序码的方式, libglade 也相当吸引人 ,因为 libglade 在定义与修改界面的过程提供较大之弹性。
第三个工具 gIDE 对那些没有特定偏好编辑器的人,提供了 IDE (整合发展环境) 。这个工具若能与 Glade 结合,将有商业化的需求。