 | 级别: 初级 Vladimir Silva (vsilva@us.ibm.com)OGSA Development Contractor,IBM
2003 年 3 月 01 日 GTK 编程从来没有如此简单过:IBM 的开发人员 Vladimir Silva 共享了他的技巧、他的热情和他为 SimpLIstic sKin 界面(或者说是 SLIK)修改的代码。
SLIK(SimpLIstic sKin 界面 —— 参阅本文后面
参考资料
部分中的链接)为 Linux 或者 Unix 系统中高级用户界面的构建提供了一个伟大的工具。作为 GQmpeg 工具集的一部分,它是用 GTK 工具包编写的,GTK 工具包是一组强大的图形控件,为 GIMP 和其他基于 GNOME 的应用程序所用。
SLIK 本身会编译成为一种“Hello World”应用程序,此程序可以展示外壳部件,比如控件、外壳编辑器、外壳规范文件和外壳浏览器。本文的目的是,通过提供一个经过修改的
SLIK 源文件 —— 设计用来编译成可供客户机使用的共享对象,为您介绍一个更为简单的方法。本文还给出了一个使用此共享对象的最简客户机,这样可以使用户不必受复杂的外壳逻辑的困扰。欲下载本文中所描述的源代码,请参阅
参考资料
中的链接。
共享对象简介
在可以使用我们的外壳代码之前,必须先理解在 Unix/Linux 中是如何构建共享对象的。
共享对象类似于 Windows 的 DLL(动态链接库)。它们让开发者可以共享他们的代码,并简化构建应用程序的复杂度。编译共享对象与编译普通的
Unix/Linux 可执行文件极其类似,但必须遵循一些额外的规则。
位置无关代码 (Position-independent
code,PIC)
对于 C 程序,如果要编译成为一个共享对象,C 编译器必须创建“位置无关代码”,这是共享对象的要求。普通可执行文件的编译可以简化为以下命令:
清单 1. 可执行文件的编译
$ gcc -c foo.c
$ gcc -o foo_exe foo.o
|
第一行取得我们程序的源代码,foo.c,然后创建一个对象文件,foo.o。第二行取得对象文件并创建一个可执行文件。
如果我们希望将程序编译为一个共享对象,就应该这样做:
清单 2. 共享对象的编译
$ gcc -fpic -c foo.c
$ gcc -shared -o libfoo.so foo.o
|
注意第一步用到的标记,
-fpic 。这告诉编译器生成位置无关代码。第二步(连接)告诉编译器取得对象文件并创建一个共享对象。为方便起见,共享对象文件名以“lib”开头,使用“so”扩展名,这样,选项应该是:
-o
libfoo.so 。
既然已经有了共享对象,我们就可以编写一个客户机来使用库中定义的任意函数:
清单 3. 客户机的编译
$ gcc -c -Ifoo_includes client.c
$ gcc -o client_exe client.o -L. -lfoo
|
如果库恰巧使用了头文件,这些头文件的路径也必须用
-I 选项包含进来。为了连接可执行文件,必须用共享对象的名字(
-lfoo )。编译器会假定前缀为“lib”,后缀为“.so”。用
-L 选项来添加自定义库的位置路径。
注意:libfoo.so 必须位于当前工作目录中,这样连接步骤才能生效。
要深入了解共享对象,请参阅
参考资料
中列出的 Program Libaray HOWTO。
将 SLIK 作为一个共享对象的编译
本文给出了一个作为共享对象进行编译的修改版 SLIK 源文件。(提供了用于 Linux 和 Windows 的 Make 文件。)在 Red
Hat Linux Workstation 8.x 和 9.x、SUSE LINUX WKS 8.X,以及 Windows 2000/XP
上代码都已经编译通过。
秉承 Unix 的多样性,此版本的 SLIK 使用 pkg-config 来找到系统库。在尝试编译这个工程之前,您必须已经安装了以下系统组件(同时参考工程源文件的 README 文件)。
对于其他 Unix 版本,您必须编辑 Makefile 文件来匹配系统中 GTK/GDK 库的路径;或者,如果对 auto-conf 了解很透彻,您可以对工程启用
auto-conf/auto-make。请根据源发行版中的 README 文件来获得编译说明。
一个基于 SLIK 外壳的 C 客户机
为启动外壳客户机,我们必须先初始化 GTK 环境,创建一个主窗口,然后向其中添加控件,如下面的代码片断所示。下载包中有这个完整的源程序。
清单 4. 外壳客户机主例程
#include "demo_skin.h"
/* skin includes */
#include "ui2_editor.h"
#include "ui2_main.h"
#include "ui2_util.h"
#include "ui_fileops.h"
#include "ui_tabcomp.h"
#include "ui2_button.h"
#include "ui2_dial.h"
#include "ui2_item.h"
#include "ui2_list.h"
#include "ui2_meter.h"
#include "ui2_number.h"
#include "ui2_overlay.h"
#include "ui2_scope.h"
#include "ui2_slider.h"
#include "ui2_spectrum.h"
#include "ui2_text.h"
/* main window widgets */
UIData *main_window = NULL;
UIData *window_main_new(const gchar *skin_path);
/* -- extra subs go here (see source) -- */
/*
*--------------------------------------------------------------------
* main routine
*--------------------------------------------------------------------
*/
int main (int argc, char *argv[])
{
gchar *buf;
gint tc = 0;
gchar *startup_skin_file = NULL; /* will load def skin if NULL */
// setup dbg mode
debug_mode = FALSE;
/*
* the gtk inits are moved after the command line parser
* (for speed with command line options, or errors thereof)
*/
gtk_init (&argc, &argv);
gdk_rgb_init();
/* push the correct color depths to gtk, (for 8-bit psuedo color
* displays)
* they should be popped, too, I guess...
*/
gtk_widget_push_visual(gdk_rgb_get_visual());
gtk_widget_push_colormap(gdk_rgb_get_cmap());
/* for this hello world, allow all widgets in editor by
* specifically registering each one (usually they are reg'd when
* needed)
*/
button_type_init();
dial_type_init();
item_type_init();
list_type_init();
meter_type_init();
number_type_init();
overlay_type_init();
scope_type_init();
slider_type_init();
spectrum_type_init();
text_type_init();
/* register base dirs to skin editor */
buf = g_strconcat(homedir(), "/", "", NULL);
ui_edit_add_skin_resource(buf);
g_free(buf);
/* set up main window */
main_window = window_main_new(startup_skin_file);
g_free(startup_skin_file);
startup_skin_file = NULL;
gtk_widget_show(main_window->window);
/* start main loop */
gtk_main ();
return 0;
}
|

 |

|
初始化外壳层
现在是初始化应用程序的“外壳层”的时候了。为此,在调用
main 例程时,我们使用了
window_main_new 函数。这个函数的功能是初始化外壳数据结构,并设置监控窗口关闭、鼠标事件等事件的“回调”或者事件。下面的代码包含于 window.c 中,是
参考资料
中所列出的归档包的一部分,您可以在其中下载它。
清单 5. window.c 外壳初始化
UIData *window_main_new(const gchar *skin_path)
{
UIData *ui;
/* skin initialization */
ui = ui_new("SKIN Demo", "main", wm_decorations, "SLIK test");
ui_set_icon(ui, ui_slik_logo(), NULL);
/* skin callback: used to skin widgets in the main window */
ui_set_skin_callback(ui, skin_default_cb, NULL);
/* signals: fire when window closed or mouse events */
gtk_signal_connect(GTK_OBJECT (ui->window), "delete_event",
GTK_SIGNAL_FUNC(window_delete_cb), ui);
/* fires on mouse events */
ui_set_mouse_callback(ui, window_main_mouse_cb, NULL);
/* register the right stuff */
display_register_widgets(ui);
printf("Loading built-in Skin \n");
ui_skin_load(ui, NULL, skin_mode_key);
return ui;
}
|
控件初始化
前面的代码片断中调用的另一个重要函数是控件初始化的回调函数。它的任务是初始化样例应用程序中的控件。我们将定义四个按钮:
- Exit: 退出应用程序
- Change skin: 激活 SLIK 内置的更换外壳对话框来切换外壳
- Skin editor: 激活 SLIK 内置的外壳编辑对话框
- Play: 一个样例按钮
我们还将为上面四个按钮定义四个标签,并在左侧定义一个小图标。这些代码可以在本文附带的归档文件中找到,文件名是
display.c 。
清单 6. display.c 的控件初始化回调
/* Widget init callback */
SkinData *skin_default_cb(UIData *ui, const gchar *key, gpointer data)
{
SkinData *skin;
skin = skin_new();
/* skin background */
skin->real_overlay =
util_size_pixbuf(gdk_pixbuf_new_from_xpm_data((const char
**)back_xpm), TRUE);
skin->width = gdk_pixbuf_get_width(skin->real_overlay);
skin->height = gdk_pixbuf_get_height(skin->real_overlay);
/* 4 skin buttons: */
/* 1) exit, 2) change skin (fires change skin dialog) */
/* 3) skin editor (fires editor dlg), 4) Play (sample btn)
button_register_to_skin("exit", (gchar **)btn_exit_xpm, 109, 34,
TRUE, FALSE, NULL, skin, NULL);
button_register_to_skin("change_skin",
(gchar **)btn_config_xpm, 109, 70,
TRUE, FALSE, NULL, skin, NULL);
button_register_to_skin("skin_editor",
(gchar **)btn_config_xpm, 109, 88,
TRUE, FALSE, NULL, skin, NULL);
button_register_to_skin("play", (gchar **)btn_play_xpm, 48, 128,
TRUE, TRUE, NULL, skin, NULL);
/* 4 labels for the prev buttons */
text_register_to_skin("hello", (gchar **)letters_xpm, 31, 110,
84, FALSE, 0, FALSE, skin, NULL);
text_register_to_skin("exit", (gchar **)letters_xpm, 52, 34,
56, FALSE, 0, FALSE, skin, NULL);
text_register_to_skin("change", (gchar **)letters_xpm, 52, 70,
56, FALSE, 0, FALSE, skin, NULL);
text_register_to_skin("editor", (gchar **)letters_xpm, 52, 88,
56, FALSE, 0, FALSE, skin, NULL);
/* A Small logo (pixmap) to display on the left side) */
item_register_to_skin("SKIN", (gchar **)slik_xpm, 4, 34,
1, skin, NULL);
/* this is merely so that the skin editor has sane values for these */
skin->width_def = skin->width_min = skin->width_max = skin->width;
skin->height_def = skin->height_min = skin->height_max = skin->height;
return skin;
}
|

 |

|
显示控件
我们的客户机程序的最后一个步骤是显示实际的控件!为此,我们为每一类型控件使用了一组注册函数:按钮、标签等等。每个控件必须分配一个唯一的名字或者键,SLIK
用它来识别具体的控件。我们可以与控件 id 一起来注册当按钮被点击时激发的回调或事件。下面的程序片断说明了此种方法。在
参考资料
中给出的外壳客户机源程序
display.c 中可以找到完整的源代码。
清单 7. display.c 与控件回调
/* Includes go here: see display.c */
/* play button call backs */
static gint button_play_status_cb(ButtonData *button, const gchar *key,
gpointer data)
{
return TRUE;
}
/* fires when play btn is clicked */
static void button_play_click_cb(ButtonData *button, const gchar *key,
gpointer data)
{
printf("play pressed\n");
}
/* exit btn CB: fires when the exit btn is clicked */
static void button_exit_click_cb(ButtonData *button, const gchar *key,
gpointer data)
{
app_exit();
}
/* ...see display.c */
/* register and display widgets */
void display_register_widgets(UIData *ui)
{
/* play btn */
button_register_key("play", ui,
button_play_status_cb, NULL,
button_play_click_cb, NULL,
NULL, NULL,
NULL, NULL);
/* exit btn */
button_register_key("exit", ui,
NULL, NULL,
button_exit_click_cb, NULL,
NULL, NULL,
NULL, NULL);
/* ...see display.c */
/* label widgets... */
text_register_key("hello", ui, text_hello_status_cb, NULL);
text_register_key("exit", ui, text_exit_status_cb, NULL);
/* ...see display.c */
}
|

 |

|
外壳规范文件
控件依照外壳规范文件中定义的一组规则加载。默认的文件名为“skindata”。这个文件包含了应用程序控件的名字和相应的属性,如图片、XY 坐标等等。下面是一个样例,取自本文所给的演示外壳:
清单 8. 外壳文件样例
[main]
image = main-complete.png
transparent = FALSE
border = TRUE
border_left = 116
border_right = 63
#
# ...
#
[button_exit]
image = btn-12-exit.png
x = 252
y = 2
prelight = TRUE
|
全部放在一起
现在是编译并运行我们的样例外壳应用程序的时候了。本文附带的代码提供了直接用于 Red Hat Linux Workstation 8.x /9.x
和 SUSE LINUX 8.x Workstation 的 Make 文件。当前不具备 auto-conf 或 auto-make 功能。
同时本文的代码也提供了一组“演示外壳”。您可以随意研究本样例及其源代码。在更换外壳时 SLIK 用到了一个规范文件。该文件定义了哪些控件将要被显示,控件的位置,以及给定的控件用到的图片文件。
要获得关于外壳规范的完整描述,您可以参考本文任意工程中所包含的 SKIN_SPEC 文件,或者参考 SLIK 主页(在
参考资料
中列出)。
下面是这个客户机内置外壳工程中所包含的一些演示外壳的截屏。
图 1. 客户机内置外壳
图 2. PDA 外壳(来自于工程源文件)
图 3. 来自工程源文件的 SLATE_ALPHA 外壳
编译注意事项
本文附带的源代码有两个文件夹:src_gtk2 和 client。第一个文件夹是要被编译成一个共享对象的修改版 SLIK-0.13.0 源文件。两个工程都为
RHL 8.x/9.x、SUSE 8.x Workstation 和 Windows 提供了 Make 文件;现在还不具备 auto-conf/auto-make
功能。如果要在其他 Unix 版本上编译,您应该编辑 Make 文件,并更新 GTK+-.2.x 和 pkg-config 软件所需头文件的路径
(参阅
参考资料
中关于这些内容的链接)。
在尝试编译之前要确保这些开发包已经安装。请参阅源文件中 README 文件。
 |
Win2K/XP 上的编译
由于 SLIK 要构建在 GTK2.0 之上,所以它在 Linux 和 Windows 系统中以同样的方式编译。对于 Xin2K/XP,需要有以下软件
查看源文件提供的 README 文件以获得 win32 编译说明。
|
|
结束语
随着时间的推移,计算能力不断增强,对高级用户界面的需要也是如此。传统的用户界面正从有着按钮和菜单的粗糙矩形窗口向成熟的虚拟现实世界的“精美的小器件”演化。基于矩形窗口的应用程序将成为历史,这只是时间的问题。对于高级用户界面设计来说,作为可选的开放源代码平台,Linux
已经把握住了先机。
本文所介绍的技术说明了使用共享对象对于用户界面设计的好处:易于维护、简单、简洁。这项技术将有助于您开始编写高级开源应用程序。
参考资料
关于作者  | |  | Vladimir Silva 出生在厄瓜多尔的基多。他于 1994 年从陆军工学院(Polytechnic Institute of
the Army)获得系统分析员(System's Analyst)学位。同年,他作为交换学生来到美国,在中田纳西州立大学(Middle
Tennessee State University)学习计算机科学。毕业后,他加入了 IBM“Web-Ahead”技术智囊团。他的兴趣包括网格计算、神经网络和人工智能。他还拥有包括 OCP、MCSD 和 MCP 在内的众多 IT 证书。可以通过
vsilva@us.ibm.com
与 Vladimir 联系。
|
对本文的评价
|  |