天天看点

Maemo Linux手机平台系列分析:(16) Maemo应用开发: GTK+基础

本部分内容:

  • Glib基本概念
  • 信号系统
  • GTK+术语
  • Hello world程序如何结束自己
  • Gobject接口
  • 增加菜单和布局
  • Hildon 构件
  • 使用附件
  • 处理动态内存
  • 避免使用废弃不用的函数

Glib基本概念                  

在Maemo中,所有的GTK程序都使用Glib工具库。这个工具库提供一组可移植的类型,这些类型可以为用C语言写的程序提供一定的可移植性。便利的工具函数和可移植的数据类型可以很容易写出具有移植性的软件。

从现在开始,我们将会用Glib类型,而不用C标准的类型了:

gboolean      Either TRUE or FALSE. FALSE is equal to zero 

gint8         8-bit signed integer 

gint16        16-bit signed integer 

gint32        32-bit signed integer 

gint64        64-bit signed integer (there are no >64-bit ones) 

gpointer      Untyped pointer ('void *') (32/64-bit) 

gconstpointer R/O untyped pointer('const void *') (32/64-bit) 

gchar         Compiler's 'char' (8-bit in gcc) 

guchar        Compiler's 'unsigned char' 

gshort        Compiler's 'short' (16-bit in gcc) 

gushort       Compiler's 'unsigned short' 

gint          Compiler's 'int' (32-bit normally in gcc) 

guint         Compiler's 'unsigned int' 

glong         Compiler's 'long' (32/64-bit in gcc) 

gulong        Compiler's 'unsigned long' (32/64-bit in gcc) 

gfloat        Compiler's 'float' (32-bits in gcc) 

gdouble       Compiler's 'double'(64/80/81-bits in gcc) 

[ Glib 类型的名字及其含义]

所有符号整型以2S方式存储。对于每一个符号整型都有一个对应的无符号整型。

你应该试着坚持使用这些Glib类型,一般情况下你可以不必计较类型的大小。当然,也有例外:当你想优化内存时,或者数据来自于外部的数据(比如,原始位图数据,网络协议,硬件编程等等)。

GLib 也提供一些限制上述类型最大值的宏、字节序和字节序修改宏函数,在写可移植程序时,这些宏是非常有用的。在Glib API 文档中有详细的描述,可以参考如下:

http://maemo.org/api_refs/4.0/glib/index.html.

下面,我们将会使用少量函数写一个能跑的程序,同时,随着时间的推移,你将会欣赏用Glib写的代码(也有一些人放弃了Glib,因为Glib作为一个工具库实在太大、太多了)。由于Glib是共享库,所有Nokia的Internet Tablet 通常只有一个GUI程序在运行,所以Glib一般情况下不会耗费多少内存。

除了数据类型外,Glib同时也提供了如下的比较有用的函数集:

  • 内存分配
  • 消息输出、调试和日志函数
  • 字符串处理 (UTF-8 and UCS-4)
  • 日期和时间处理以及计数定时器
  • 数据结构:
    • 动态字符串
    • 双向链表 (单向和双向)
    • Hash 表
    • 动态数组(也提供指针数组)
    • 二叉树和N叉树
    • 数据缓冲支持
  • 其它一些功能

正如前面提到的,Glib的API可以在线浏览: http://maemo.org/api_refs/4.0/glib/index.html. 对于Maemo中使用的其他的库函数的参考,你可以到这里找找看: http://maemo.org/development/documentation/apis/4-x/.

如果在你的开发系统上已经安装了合适的文档包,你也应当试试 devhelp,这是一个非常好的程序,可以让你浏览GNOME库文档,并且提供超文本和search功能。

Maemo Linux手机平台系列分析:(16) Maemo应用开发: GTK+基础

信号机制

为了处理不同组件之间交互,不同的库有不同的方法去实现事件和变化时的通知机制。在设计和限制方面各有千秋。在GTK中使用的设计模型是注册/自动激发回调函数的这么一个机制。这种机制称之为Gsignal, 在Gobject库中实现的。Gobject是一个框架或者架构,使用它你可以用C语言编写出面向对象的结构,就像Java和C++那样。Gobject并不需要特殊的工具函数,因此它的可移植性非常好,当然,Gobject中使用了大量的Glib数据结构。

关于如何使用Gobject(实现自己的类、扩展已有的类,等等)这里并不准备介绍,因为这是个比较复杂的问题,而且在开始用gtk+编程时也没有必要非要把Gobject搞的很透。呵呵。

GTK+ widget使用GSignal机制去实现通知:当有什么事情发生时。然后这些消息就传递给那些关心这些事件的监听者(通过回调函数的形式)。在GTK+中,并没有所谓的共享事件总线,但是你可以发挥想象:在widget和回调函数之间是建立了“链接”的。GSignal还支持优先级的处理。

每一个类可以定义其型号类型,而类的实例可以发射这些信号。Signal是通过一个文本字符串和一个信号源标示的。

我们链接一个信号:指定一个具备发射信号的对象并且指定信号的名称。你可以把名字认为是类型,实际上它仅仅是用于区分不同的信号,比如“clicked”和“selected” 。我们链接的对象是谁?就是用C语言写的回调函数。请注意:发射信号的对象并不直接调用回调函数,而是使用GObject提供的普遍的信号发送框架。这个框架使得发射者和接收者仅仅提供相同的API就可以了。

假设在面向对象编程成熟之前就设计了C语言,那么,GObject和signal看起来就顺眼多了。如果你仅仅使用过面向对象语言编程,GObject对你来说,看起来就比较奇怪了。习惯了就好了。

你可以通过阅读API或者很多与GObject相关的数据去熟悉它,推荐一本书:【The Official Gnome 2 Developer’s Guide】。直接从代码中理清GObject的架构并不是一个好的主意,因为代码比较复杂。不过你早晚要熟悉GObject的代码的。J  

GTK+ 术语

在开始GTK+之前,我们需要看看有哪些术语:

  • Widget: 能在屏幕显示并且允许用户和它交互的这么一个控件。比如:scrollbar,Button,Menu,等等。  
  • Container: 一个特殊的容器控件,能够给其它的一些控件提供容身之所,并且把那些控件以一定的方式组织在一块,显示在屏幕上。另外容器可以套容器,就是大鱼吃小鱼,小鱼吃虾米。比如: Window, VBox, HBox, Toolbar.
  • Packing: 填充,这是一个动作。就是把一个widget放到一个容器控件中,这么一个动作就叫做填充。一般情况下,我们这样讲:把一个widget填充到一个容器中。填充这个动作将会做些空间分配和布局方面的工作。如果一个widget暂时不可见,这个填充动作暂时是不做的。  
  • Child Widget: 子控件,就是被填充到一个容器中的控件.反过来讲,一个控件的父控件就是其容器。一个控件不能有多个父控件。
  • Widget Tree: 所有的控件和其容器都是一个root控件的子控件。对于每一个window都有一个widget tree。
  • Event: 事件,就是进程外发生什么事情后给出的通知。这些通知一般来自于HID-sysem,并且由GTK+转为信号。信号处理机制就是处理这些事件的。
  • Visibility:每一个widget都可以设置可见或者不可见。只有父控件(容器)可见,才能在屏幕上看到图像。为了让widget tree中的每一个控件可见,我们需要告诉这些控件,显示与否。通常我们在创建完widget tree后,统一处理这种告知的事情。
  • Property: 属性,这实际是和Gobject相关的东西,不过在GTK+中一样得到了应用。一般来讲,property就是可以设置/取得的具有名字的数据或者值。当我们设置属性时,对象会执行一些操作来反映值的变化。读取一个属性也会触发一些代码执行。

Hello World 示例程序如何结束自己呢?

#include <stdlib.h>

#include <gtk/gtk.h>

static gboolean delete_event(GtkWidget* widget, GdkEvent event,

                                                gpointer data) {

  g_print("CB:delete_event/n");

  return FALSE;

}

static void end_program(GtkWidget* widget, gpointer data) {

  g_print("CB:end_program: calling gtk_main_quit()/n");

  gtk_main_quit();

  g_print("CB:end_program: back from gtk_main_quit & returning/n");

}

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

  GtkWindow* window;

  GtkLabel* label;

  gtk_init(&argc, &argv);

  window = g_object_new(GTK_TYPE_WINDOW,

    "border-width", 12,

    "title", "Hello GTK+",

    NULL);

  label = g_object_new(GTK_TYPE_LABEL,

    "label", "Hello World!",

    NULL);

  gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(label));

  g_signal_connect(window, "delete-event",

                   G_CALLBACK(delete_event), NULL);

  g_signal_connect(window, "destroy",

                   G_CALLBACK(end_program), NULL);

  gtk_widget_show_all(GTK_WIDGET(window));

  g_print("main: calling gtk_main/n");

  gtk_main();

  g_print("main: returned from gtk_main and exiting with success/n");

  return EXIT_SUCCESS;

}

[ 带有基本信号的Hello World ]

你们看到,上面写了很多的注释,其实很烦的。不过后面就不这样写了,第一次这样写,以后就可以直接看这个注释的含义了。

下面编译,运行这个例子,并关闭程序:

[sbox-CHINOOK_X86: ~/appdev] > gcc -Wall `pkg-config --cflags gtk+-2.0` /

 gtk_helloworld-2.c -o gtk_helloworld-2 `pkg-config --libs gtk+-2.0`

[sbox-CHINOOK_X86: ~/appdev] > ./gtk_helloworld-2

main: calling gtk_main

Window closing button engaged by the user

CB:delete_event

CB:end_program: calling gtk_main_quit()

CB:end_program: back from gtk_main_quit & returning

main: returned from gtk_main and exiting with success

[ Running the program ]

可以看到,在Xserver上面并没有看到图形输出,这里我们其实只是关心g_print()打印出来的信息。上面你们看到的序列是正常的信息。

GObject 接口

下面我接着探讨:我们看看GObject类必需要实现的基本功能,以及在GTK+中如何使用这些看似怪异的功能。

下面我们使用accessor接口(就是能直接创建控件的API)写个例子,然后与用property接口实现相同功能的例子做比较。

创建一个窗口:窗口边宽12像素,同时有title:

window = (GtkWindow*)gtk_window_new(GTK_WINDOW_TOPLEVEL);

gtk_window_set_title(window, "Hello GTK+");

gtk_container_set_border_width((GtkContainer*)window, 12);

下面我们换种方法:不用现存的控件API,而是使用GObject一般的创建对象的一套方法来实现上面相同的功能。

我们通过指定GType的值为:GTK_TYPE_WINDOW,来创建一个GtkWindow对象。同时设定对象的两个属性:属性的名字为“border-width”和“title”。我们用NULL来结束属性列表的设置。

window = g_object_new(GTK_TYPE_WINDOW,

  "border-width", 12,

  "title", "Hello GTK+",

  NULL);

后面,我们需要决定冻结窗口大小的变化。为此,我们需要设置一个属性:“resizable”:

g_object_set_property(window, "resizable", FALSE);

假设现在我们需要设置多个属性,可以很方便的使用g_object_set()函数来做,而如果用widget的API来做这样的事情,就显得比较繁琐了。

gchar* data = "Some random data";

g_object_set(window,

  "resizable", FALSE,

  "has-focus", TRUE,

  "width", 300,     

  "height", 150,    

  "user-data", data,

  NULL);

下面的代码片断中,你会注意到GObject匹配的宏,它们主要是做类型匹配的。

例如,gtk_container_add() 有两个参数:

  • GtkContainer* : 容器指针,用来指向一个可以添加widget的容器。
  • GtkWidget* :任何控件.

因为window也是一个container,所以我们可以很安全地把它匹配成一个container。又因为GtkLabel是一个控件,所以把它转换为一个GtkWidget是没有任何问题的。这样做的目的就是为了编译的顺利通过,不必出现N多的warnings。

显然你需要知道各个对象指针对应的宏定义,其实转化规则很简单:每一个“word”单独写成大写,中间用下划线分开就行。举个例子:GtkWidget变成GTK_WIDGET; GtkRadioMenuItem变成GTK_RADIO_MENU_ITEM。

如果仅仅为了讨好编译器,其实这种转化是意义不大的。这种宏的另外一个作用就是类型检查(以及检查实例是否有悖于类的层次关系)。

gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(label));

前面我们用设置属性的方式,设置了一些对象,如果使每一个都可视的话,要一个一个设置,有时比较繁琐的。有时候,控件接口函数可以帮我们分担一些工作,呵呵。.

gtk_widget_show_all(GTK_WIDGET(window));

gtk_widget_show_all这个函数就是使得整个widget树所包含的所有widget都可见。与可见/不可见相关的函数还有: gtk_widget_show(GtkWidget*), gtk_widget_hide(GtkWidget*) and gtk_widget_hide_all(GtkWidget*).

增加菜单和layout

下面我们为我们的程序实现一个菜单。这个例子程序会使用信号和属性。

我们会尽量使用属性,借以证明压缩代码的可能性。请注意:每一个属性的查找将会包含一个GObject的hash table的lookup。 在代码尺寸和速度方面寻求一个平衡点是比较难的事情。

#include <stdlib.h>

#include <gtk/gtk.h>

typedef enum {

  MENU_FILE_OPEN = 1,

  MENU_FILE_SAVE = 2,

  MENU_FILE_QUIT = 3

} MenuActionCode;

static gboolean delete_event(GtkWidget* widget, GdkEvent event,

                                                gpointer data) {

  g_print("CB:delete_event/n");

  return FALSE;

}

static void end_program(GtkWidget* widget, gpointer data) {

  g_print("CB:end_program: calling gtk_main_quit()/n");

  gtk_main_quit();

  g_print("CB:end_program: back from gtk_main_quit & returning/n");

}

static void cbActivation(GtkMenuItem* mi, gpointer data) {

  MenuActionCode aCode = GPOINTER_TO_INT(data);

  switch(aCode) {

    case MENU_FILE_OPEN:

      g_print("Selected open/n");

      break;

    case MENU_FILE_SAVE:

      g_print("Selected save/n");

      break;

    case MENU_FILE_QUIT:

      g_print("Selected quit/n");

      gtk_main_quit();

      break;

    default:

      g_warning("cbActivation: Unknown menu action code %d./n", aCode);

  }

}

static GtkMenuItem* buildMenuItem(const gchar* labelText) {

  GtkLabel* label;

  GtkMenuItem* mi;

  label = g_object_new(GTK_TYPE_LABEL,

    "label", labelText,    

    "xalign", (gfloat)0.0, 

    NULL);

  mi = g_object_new(GTK_TYPE_MENU_ITEM, "child", label, NULL);

  return mi;

}

static GtkMenuBar* buildMenubar(void) {

  GtkMenuBar* menubar;

  GtkMenuItem* miFile;

  GtkMenu* fileMenu;

  GtkMenuItem* miOpen;

  GtkMenuItem* miSave;

  GtkMenuItem* miSep;

  GtkMenuItem* miQuit;

  fileMenu = g_object_new(GTK_TYPE_MENU, NULL);

  miOpen = buildMenuItem("Open");

  miSave = buildMenuItem("Save");

  miQuit = buildMenuItem("Quit");

  miSep = g_object_new(GTK_TYPE_SEPARATOR_MENU_ITEM, NULL);

  g_object_set(fileMenu,

    "child", miOpen,

    "child", miSave,

    "child", miSep,

    "child", miQuit,

    NULL);

  g_signal_connect(G_OBJECT(miOpen), "activate",

    G_CALLBACK(cbActivation), GINT_TO_POINTER(MENU_FILE_OPEN));

  g_signal_connect(G_OBJECT(miSave), "activate",

    G_CALLBACK(cbActivation), GINT_TO_POINTER(MENU_FILE_SAVE));

  g_signal_connect(G_OBJECT(miQuit), "activate",

    G_CALLBACK(cbActivation), GINT_TO_POINTER(MENU_FILE_QUIT));

  miFile = buildMenuItem("File");

  gtk_menu_item_set_submenu(miFile, GTK_WIDGET(fileMenu));

  menubar = g_object_new(GTK_TYPE_MENU_BAR, NULL);

  g_object_set(menubar, "child", miFile, NULL);

  return menubar;

}

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

  GtkWindow* window;

  GtkLabel* label;

  GtkMenuBar* menubar;

  GtkVBox* vbox;

  gtk_init(&argc, &argv);

  window = g_object_new(GTK_TYPE_WINDOW,

    "border-width", 12,

    "title", "Hello GTK+",

    NULL);

  label = g_object_new(GTK_TYPE_LABEL,

    "label", "Hello World! (with menus)",

    NULL);

  menubar = buildMenubar();

  vbox = g_object_new(GTK_TYPE_VBOX, NULL);

  g_object_set(window, "child", vbox, NULL);

  gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(menubar), FALSE,

                     FALSE, 0);

  gtk_box_pack_end(GTK_BOX(vbox), GTK_WIDGET(label), TRUE, TRUE, 0);

  g_signal_connect(window, "delete-event",

                   G_CALLBACK(delete_event), NULL);

  g_signal_connect(window, "destroy",

                   G_CALLBACK(end_program), NULL);

  gtk_widget_show_all(GTK_WIDGET(window));

  g_print("main: calling gtk_main/n");

  gtk_main();

  g_print("main: returned from gtk_main and exiting with success/n");

  return EXIT_SUCCESS;

}

[上面的代码,仔细读读,还是能理解一些GObject的东西的。 ]

Maemo Linux手机平台系列分析:(16) Maemo应用开发: GTK+基础

[没有运行run-standalone.sh的情况 ]

Maemo Linux手机平台系列分析:(16) Maemo应用开发: GTK+基础

[ 运行 run-standalone.sh 后的效果]

Hildon widgets

Maemo平台中包含一些经过优化的widgets。相对于桌面环境来讲,Maemo的显示屏比较小。因此就需要考虑在传统的窗口管理器和运行GUI程序之间做合理的切换。一般情况下,对GTK要做些修改,比如该尺寸等等。

为了很好的集成,下面我们开始把前面的程序模式切换到Hildon下面。Hildon主要提供两类widgets: HildonProgram和HildonWindow,HildonWindow主要是完成GtkWindow的一些功能。

HildonProgram是一个“超级window”对象,它提供一个shell,通过shell我们可以把我们的图形view集成到runtime 环境中。View和window很类似,不过在某个时间内,只有一个view是可见的。你可以这样想象:Hildon 应用程序就是一个带有多个tab的dialog,但是只有tab是可见的。View是用HildonWindow这么一个控件实现的,这个控件是我们放置其它控件的容器控件。

当你想把现有的GUI程序porting到Hildon下面时,你需要考虑一些UI布局方面的东东,以及如下的一些内容:

  • 每一个试图只有一个GtkMenu可以使用。这和传统的桌面环境是不同的。
  • 每一个窗口都有一个容器来包含toolbar.
  • 输入区域获取焦点后,会激活虚拟键盘。这样的话,就需要程序能够调整尺寸。如果你的程序不能很容易的调整尺寸,你可能需要使用GtkScrolledWindow来包容它。 (http://maemo.org/api_refs/4.0/gtk/GtkScrolledWindow.html).
  • 避免很深的子菜单。
  • 要把你的程序设计成只有一个主window的。如果对于有若干个分离窗口的程序,你需要重新设计。
  • 一次不要显示太多的信息。

下面我们使用HildonProgram和HildonWindow控件来改造Hello world例子程序。同时也使用HildonWinow提供的菜单控件:

#include <stdlib.h>

#include <gtk/gtk.h>

#include <hildon/hildon-program.h>

typedef enum {

  MENU_FILE_OPEN = 1,

  MENU_FILE_SAVE = 2,

  MENU_FILE_QUIT = 3

} MenuActionCode;

static gboolean delete_event(GtkWidget* widget, GdkEvent event,

                                                gpointer data) {

  return FALSE;

}

static void end_program(GtkWidget* widget, gpointer data) {

  gtk_main_quit();

}

static void cbActivation(GtkMenuItem* mi, gpointer data) {

  MenuActionCode aCode = GPOINTER_TO_INT(data);

  switch(aCode) {

    case MENU_FILE_OPEN:

      g_print("Selected open/n");

      break;

    case MENU_FILE_SAVE:

      g_print("Selected save/n");

      break;

    case MENU_FILE_QUIT:

      g_print("Selected quit/n");

      gtk_main_quit();

      break;

    default:

      g_warning("cbActivation: Unknown menu action code %d./n", aCode);

  }

}

static GtkMenuItem* buildMenuItem(const gchar* labelText) {

  GtkLabel* label;

  GtkMenuItem* mi;

  label = g_object_new(GTK_TYPE_LABEL,

    "label", labelText,    

    "xalign", (gfloat)0.0, 

    NULL);

  mi = g_object_new(GTK_TYPE_MENU_ITEM, "child", label, NULL);

  return mi;

}

static void buildMenu(HildonProgram* program) {

  GtkMenu* menu;

  GtkMenuItem* miOpen;

  GtkMenuItem* miSave;

  GtkMenuItem* miSep;

  GtkMenuItem* miQuit;

  miOpen = buildMenuItem("Open");

  miSave = buildMenuItem("Save");

  miQuit = buildMenuItem("Quit");

  miSep = g_object_new(GTK_TYPE_SEPARATOR_MENU_ITEM, NULL);

  menu = g_object_new(GTK_TYPE_MENU, NULL);

  g_object_set(menu,

    "child", miOpen,

    "child", miSave,

    "child", miSep,

    "child", miQuit,

    NULL);

  g_signal_connect(G_OBJECT(miOpen), "activate",

    G_CALLBACK(cbActivation), GINT_TO_POINTER(MENU_FILE_OPEN));

  g_signal_connect(G_OBJECT(miSave), "activate",

    G_CALLBACK(cbActivation), GINT_TO_POINTER(MENU_FILE_SAVE));

  g_signal_connect(G_OBJECT(miQuit), "activate",

    G_CALLBACK(cbActivation), GINT_TO_POINTER(MENU_FILE_QUIT));

  gtk_widget_show_all(GTK_WIDGET(menu));

}

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

  HildonProgram* program;

  HildonWindow* window;

  GtkWidget* label;

  GtkWidget* vbox;

  gtk_init(&argc, &argv);

  Hildon来提供window和控制控件

  program = HILDON_PROGRAM(hildon_program_get_instance());

  g_set_application_name("Hello Hildon!");

  window = HILDON_WINDOW(hildon_window_new());

  把创建的HildonWindow绑定到超级window(HildonProgram上)

  hildon_program_add_window(program, HILDON_WINDOW(window));

  label = g_object_new(GTK_TYPE_LABEL,

    "label", "Hello Hildon (with menus)!",

    NULL);

  buildMenu(program);

  vbox = g_object_new(GTK_TYPE_VBOX, NULL);

  g_object_set(window, "child", vbox, NULL);

  gtk_box_pack_end(GTK_BOX(vbox), GTK_WIDGET(label), TRUE, TRUE, 0);

  g_signal_connect(G_OBJECT(window), "delete-event",

                   G_CALLBACK(delete_event), NULL);

  g_signal_connect(G_OBJECT(window), "destroy",

                   G_CALLBACK(end_program), NULL);

  gtk_widget_show_all(GTK_WIDGET(window));

  g_print("main: calling gtk_main/n");

  gtk_main();

  g_print("main: returned from gtk_main and exiting with success/n");

  return EXIT_SUCCESS;

}

编辑:

[sbox-CHINOOK_X86: ~/appdev] > gcc -Wall `pkg-config --cflags gtk+-2.0  /

 hildon-1` hildon_helloworld-1.c -o hildon_helloworld-1 /

 `pkg-config --libs gtk+-2.0 hildon-1`

运行效果,还不错吧,呵呵:

Maemo Linux手机平台系列分析:(16) Maemo应用开发: GTK+基础

上面的效果和Meamo的正常视图布局一致:

Maemo Linux手机平台系列分析:(16) Maemo应用开发: GTK+基础

上面视图中每个组件的像素大小:

  • 左边的任务导航栏占用:80x480.
  • 最上边的状态栏占用720x60的大小。这一个区域有时候也用来显示title或者菜单。
  • 给程序显示区域的大小是: 672×396 像素.

不过这个尺寸并不是一成不变的。

使用封装过的函数API

现在,你已经具有基本的概念了,并且也用GObject实现了一些小的例子程序,不过仍然有很多东西需要进一步的学习和了解。

如果一直用GObject的属性做GTK编成,也不是不行,而是不方便。大家还是使用GTK+提供的函数直接进行编成吧。GTK+函数会自己做些严格的类型检查的,所以你如果传入了不合适的参数,编译时就会给你提示,而不是等到运行时再报错。

下面我们用GTK+的接口函数实现,不用GObject的property了。

#include <stdlib.h>

#include <gtk/gtk.h>

#include <hildon/hildon-program.h>

typedef enum {

  MENU_FILE_OPEN = 1,

  MENU_FILE_SAVE = 2,

  MENU_FILE_QUIT = 3

} MenuActionCode;

static gboolean delete_event(GtkWidget* widget, GdkEvent event,

                                                gpointer data) {

  return FALSE;

}

static void end_program(GtkWidget* widget, gpointer data) {

  gtk_main_quit();

}

static void cbActivation(GtkMenuItem* mi, gpointer data) {

  MenuActionCode aCode = GPOINTER_TO_INT(data);

  switch(aCode) {

    case MENU_FILE_OPEN:

      g_print("Selected open/n");

      break;

    case MENU_FILE_SAVE:

      g_print("Selected save/n");

      break;

    case MENU_FILE_QUIT:

      g_print("Selected quit/n");

      gtk_main_quit();

      break;

    default:

      g_warning("cbActivation: Unknown menu action code %d./n", aCode);

  }

}

static void buildMenu(HildonProgram* program) {

  GtkMenu* menu;

  GtkWidget* miOpen;

  GtkWidget* miSave;

  GtkWidget* miSep;

  GtkWidget* miQuit;

  //这里直接使用GTK+接口函数,

  miOpen = gtk_menu_item_new_with_label("Open");

  miSave = gtk_menu_item_new_with_label("Save");

  miQuit = gtk_menu_item_new_with_label("Quit");

  miSep =  gtk_separator_menu_item_new();

  menu = GTK_MENU(gtk_menu_new());

  gtk_container_add(GTK_CONTAINER(menu), miOpen);

  gtk_container_add(GTK_CONTAINER(menu), miSave);

  gtk_container_add(GTK_CONTAINER(menu), miSep);

  gtk_container_add(GTK_CONTAINER(menu), miQuit);

  g_signal_connect(G_OBJECT(miOpen), "activate",

    G_CALLBACK(cbActivation), GINT_TO_POINTER(MENU_FILE_OPEN));

  g_signal_connect(G_OBJECT(miSave), "activate",

    G_CALLBACK(cbActivation), GINT_TO_POINTER(MENU_FILE_SAVE));

  g_signal_connect(G_OBJECT(miQuit), "activate",

    G_CALLBACK(cbActivation), GINT_TO_POINTER(MENU_FILE_QUIT));

  hildon_program_set_common_menu(program, menu);

}

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

  HildonProgram* program;

  HildonWindow* window;

  GtkWidget* label;

  GtkWidget* vbox;

  gtk_init(&argc, &argv);

  program = HILDON_PROGRAM(hildon_program_get_instance());

  g_set_application_name("Hello Hildon!");

  window = HILDON_WINDOW(hildon_window_new());

  hildon_program_add_window(program, HILDON_WINDOW(window));

  label = gtk_label_new("Hello Hildon (with accessors)!");

  buildMenu(program);

  vbox = gtk_vbox_new(FALSE, 0);

  gtk_container_add(GTK_CONTAINER(window), vbox);

  gtk_box_pack_end(GTK_BOX(vbox), GTK_WIDGET(label), TRUE, TRUE, 0);

  g_signal_connect(G_OBJECT(window), "delete-event",

                   G_CALLBACK(delete_event), NULL);

  g_signal_connect(G_OBJECT(window), "destroy",

                   G_CALLBACK(end_program), NULL);

  gtk_widget_show_all(GTK_WIDGET(window));

  g_print("main: calling gtk_main/n");

  gtk_main();

  g_print("main: returned from gtk_main and exiting with success/n");

  return EXIT_SUCCESS;

}

你可能想知道使用两种API写程序的不同点在哪?用GObject property模型写的代码易于和别的语言绑定。不过现在可以不用关心这个。

处理内存

GTK+对内存的处理,都放在内部了,你就放心大胆地使用把。在实际使用过程中,如果使用GTK+函数创建一个widget或者数据结构,将会从堆(heap,动态内存区)分配一些内存,并且使用GObject函数去增加reference count。当这个widget是添加到一个container中,container会增加一个reference count。 反之,从一个container中移除一个widget,container会减少一个reference count. 在减少reference count时,会检查这个reference是否到0了,如果到0了,则释放内存。

如果一个widget准备销毁自己,它会发射一个“destroy”信号出去,这个信号会送给所有包含这个widget的容器,然后这些容器就不得不忍痛割爱:减少reference count. 在所有的容器做完这个动作后,这个widget就寿终正寝了。

更多的信息参考文档: http://maemo.org/api_refs/4.0/gtk/GtkObject.html http://maemo.org/api_refs/4.0/gobject/gobject-memory.html.

避免使用废弃的函数

GTK+也是一个不断开发的项目,其中代码会随着时间的推移,会有些废代码。在编译时有些开关可以不编译这些废弃代码。

  • GTK_DISABLE_DEPRECATED: 禁用GTK的废弃函数
  • GDK_DISABLE_DEPRECATED: 禁用GDK的废弃函数
  • GDK_PIXBUF_DISABLE_DEPRECATED: 禁用GDK-Pixbuf的废弃函数  
  • G_DISABLE_DEPRECATED: 禁用GLib的废弃函数
  • GTK_MULTIHEAD_SAFE: 这个并不是废弃的函数,主要把可能在多线程系统中引起问题的GTK函数禁用。

继续阅读