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

来源:互联网 发布:莱芜天涯网络 编辑:程序博客网 时间:2024/05/29 18:17

本部分内容:

  • 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是共享库,所有NokiaInternet Tablet 通常只有一个GUI程序在运行,所以Glib一般情况下不会耗费多少内存。

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

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

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

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

 

信号机制

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

 

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

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

 

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

 

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

 

假设在面向对象编程成熟之前就设计了C语言,那么,GObjectsignal看起来就顺眼多了。如果你仅仅使用过面向对象语言编程,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 示例程序如何结束自己呢?

/**

 * gtk_helloworld-2.c

 *

 * This maemo code example is licensed under a MIT-style license,

 * that can be found in the file called "License" in the same

 * directory as this file.

 * Copyright (c) 2007 Nokia Corporation. All rights reserved.

 *

 * This version adds proper mechanisms to end the program.

 *

 * Look for lines with "NEW" or "MODIFIED" in them.

 */

 

#include <stdlib.h>

#include <gtk/gtk.h>

 

/**

 * NEW

 *

 * Callback function (event handler) for the "delete" event.

 * This event is emitted by GTK+ main loop after a window manager has

 * requested this window to be closed.

 *

 * NOTES:

 * - This is really a signal handler.

 * - The first parameter to a signal handler is a pointer to the

 *   object that caused the signal to be emitted (when using

 *   gobject_signal_connect).

 * - There are zero or more parameters in between the first one and

 *   the last one. The count of parameters depends on the signal

 *   type.

 * - The last parameter is always an untyped pointer (again, when

 *   using gobject_signal_connect).

 *

 * Parameters:

 * - widget: Pointer to the widget that emitted the delete-event.

 *           // 发射delete事件的控件

 *

 * - event:  GdkEvent-structure describing what kind of event this

 *           is. We'll register this handler to handle only the

 *           "destroy-event", so we'll know what kind of event this

 *           is without checking the "event"-parameter.

 *           //

 * - data:   Untyped pointer for passing data between the main loop

 *           and handlers. We ignore it for now.

 *           // 在主循环和事件handler之间传递数据

 * Returns:

 * - gboolean: Whether the event has been consumed by this handler or

 *             not.

 * NOTE:

 *   Signal handlers may exit with an return value. Whether this

 *   value will be used by anyone, depends on the signal type. For

 *   events, we must always return a boolean value.

 */

static gboolean delete_event(GtkWidget* widget, GdkEvent event,

                                                gpointer data) {

  /* Print out an informational message. */

  g_print("CB:delete_event/n");

 

  /* We want to tell the gtk_main that we're done with this widget.

     Since it's the top-level widget in our window (it is the

     window), returning FALSE to our caller signifies that the event

     has not been processed by us, and this in turn means that it

     will be propagated to the default window delete-event handler

     which will terminate the program. This is done in by the caller

     so that it will issue a "destroy"-signal next. If we would

     return TRUE, the program wouldn't stop. This behavior is

     specific to the "delete-event" signal. */

  return FALSE;

}

 

/**

 * NEW

 *

 * Our callback function for the "destroy"-signal which is issued

 * when the Widget is going to be destroyed.

 * NOTE:

 *   In this case we don't need to return anything, since we're just

 *   processing a simple signal.

 *

 * This is used by widgets that are going away that want to notify us

 * that they will not be around for much longer. They will remove

 * themselves eventually.

 *

 * We call gtk_main_quit() which is a routine that will tell gtk_main

 * that it's time to terminate.

 *

 * Note, you must not terminate your program here! (do not use the

 * exit()-library function). This would leave the GTK+ library in a

 * slightly confused state and might leave other threads hanging

 * around. In this material we won't be using threads, but consider

 * yourself warned.

 *

 * Parameters:

 * - widget: Same as with event handler callbacks.

 * - data:   Same as with event handler callbacks.

 *

 * Returns:

 * void

 */

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

  /* Print out an informational message to stdout. */

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

 

  /* Tell gtk_main to terminate the application. */

  gtk_main_quit();

 

  /* Display message about what is happening. */

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

}

 

/**

 * MODIFIED

 *

 * We connect the signals to the handlers so that the application may

 * be terminated properly from the GUI.

 */

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

 

  /* We'll have two references to two GTK+ widgets. */

  GtkWindow* window;

  GtkLabel* label;

 

  /* Initialize the GTK+ library. */

  gtk_init(&argc, &argv);

 

  /* Create a window with window border width of 12 pixels and a

     title text. */

  window = g_object_new(GTK_TYPE_WINDOW,

    "border-width", 12,

    "title", "Hello GTK+",

    NULL);

 

  /* Create the label widget. */

  label = g_object_new(GTK_TYPE_LABEL,

    "label", "Hello World!",

    NULL);

 

  /* Pack the label into the window layout. */

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

 

  /**

   * NEW

   *

   * Here we "install" event and signal handlers.

   *

   * Events are handled by the GTK+ as signals. Their difference is

   * explained in the text that follows.

   *

   * g_signal_connect(GtkWidget*, gchar*, CB*, gpointer*) is the API

   * function to connect signals from some object to some signal

   * receiver (callback function when using C).

   *

   * The second parameter should point to a string giving the event

   * or signal name. GLib treats underscores and dashes in signal

   * names as they would be the same character. The API documentation

   * uses the dash (-) and so will we. Needless to say, different

   * kind of widgets are capable of emitting different events.

   *

   * Third parameter is the address of the callback function that

   * should receive the signal. We use G_CALLBACK()-macros to force a

   * typecast into the type specification that g_signal_connect

   * expects. Depending on the signal the callback will receive

   * different number of parameters and is expected to return

   * something in some cases. There is no hard and set rule.

   *

   * THIS MEANS THAT YOU HAVE TO BE SURE ABOUT THE CAST VALIDITY.

   *

   * It is possible to pass an extra pointer (the last parameter)

   * that will be carried with the signal to the signal receiver. We

   * will see uses for this later, but in real applications it is

   * quite important. This last parameter is defined as a gpointer

   * (untyped pointer, void*), and if one needs to pass other data

   * in the pointer's place, one will need to do additional

   * typecasting (we'll see this later).

   */

  g_signal_connect(window, "delete-event",

                   G_CALLBACK(delete_event), NULL);

  g_signal_connect(window, "destroy",

                   G_CALLBACK(end_program), NULL);

 

  /* Show all widgets that are contained by the window. */

  gtk_widget_show_all(GTK_WIDGET(window));

 

  /* Start the main event loop. */

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

  gtk_main();

 

  /* Display a message to the standard output and exit. */

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

 

  /* Return success as exit code. */

  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”:

/* Set the value of one property. */

g_object_set_property(window, "resizable", FALSE);

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

gchar* data = "Some random data";

 

g_object_set(window,

  "resizable", FALSE,/* GtkWindow-property */

  "has-focus", TRUE, /* GtkWidget-property */

  "width", 300,      /* GtkWidget-property */

  "height", 150,     /* GtkWidget-property */

  "user-data", data, /* GObject-property */

  NULL);

 

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

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

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

 

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

 

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

 

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

/* Pack the label into the window layout. */

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

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

/* Show all the widgets that are contained by the window. */

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

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

 

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

/**

 * gtk_helloworld-3.c

 *

 * This maemo code example is licensed under a MIT-style license,

 * that can be found in the file called "License" in the same

 * directory as this file.

 * Copyright (c) 2007 Nokia Corporation. All rights reserved.

 *

 * We add a menu and callbacks to process menu selections.

 *

 * Look for lines with "NEW" or "MODIFIED" in them.

 */

 

#include <stdlib.h>

#include <gtk/gtk.h>

 

/**

 * NEW

 * 使用一个回调函数去处理所有的菜单项

 * We will use a single callback function to process the activation

 * of all of the menu items.

 *

 * 我们可以使用字符串来识别菜单项,不过这里我们使用一个枚举量。

 * We could also use strings to identify which menu item was

 * selected, or if our callback would have a list of pointers to the

 * menu items themselves, we could compare against that. Instead,

 * we'll define an enumeration that will contain all the possible

 * menu items that our application will have. This allows use to use

 * a simple switch statement to determine what to do.

 */

typedef enum {

  MENU_FILE_OPEN = 1,

  MENU_FILE_SAVE = 2,

  MENU_FILE_QUIT = 3

} MenuActionCode;

 

/**

 * Callback function (event handler) for the "delete" event.

 */

static gboolean delete_event(GtkWidget* widget, GdkEvent event,

                                                gpointer data) {

  /* Print out an informational message. */

  g_print("CB:delete_event/n");

 

  /* Done with the widget. */

  return FALSE;

}

 

/**

 * Our callback function for the "destroy"-signal which is issued

 * when the Widget is going to be destroyed.

 */

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

  /* Print out an informational message to stdout. */

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

 

  /* Tell gtk_main to terminate the application. */

  gtk_main_quit();

 

  /* Display message about what is happening. */

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

}

 

/**

 * NEW

 *

 * Signal handler for the menu item selections.

 *

 * Gets the menu action code as a parameter but since we'll cast it

 * into an pointer on callback registration, we'll need to cast it

 * back internally.

 */

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

 

  /* Convert the pointer into an integer, irrespective of the pointer

     size. */

  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:

      /* Besides g_print, there are two other output functions in

         GLib: g_warning and g_error. g_error will stop our program

         (using abort() internally), so we'll choose to use g_warning

         instead since we want our program to continue running. The

         situation is not lethal, but should be investigated.

 

         g_warning prints out a warning message in the same format as

         g_print and printf. */

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

  }

}

 

/**

 * NEW

 *

 * A convenience function to create menu items with internal label

 * objects.

 *

 * NOTE:

 *   Most existing programs use a convenience function that GTK+

 *   provides (gtk_menu_item_new_with_label).

 *

 * We want to use the GObject API for this example to demonstrate its

 * compactness.

 *

 * Parameters:

 * - const gchar*: string to display in the label inside the item.

 *

 * Returns:

 * - A pointer to a new MenuItem that holds the label

 */

static GtkMenuItem* buildMenuItem(const gchar* labelText) {

 

  GtkLabel* label;

  GtkMenuItem* mi;

 

  /* Create the Label object first. We set the text using its "label"

     property. We also set its alignment using a property that its

     superclass (GtkMisc) has. Namely "xalign". xalign with 0.0 is

     left and 1.0 is right extreme. Default value for xalign is 0.5

     which would align the text using center justification. */

  label = g_object_new(GTK_TYPE_LABEL,

    "label", labelText,     /* GtkLabel property */

    "xalign", (gfloat)0.0,  /* GtkMisc property */

    NULL);

 

  /* Next we create the GtkMenuItem.

 

     A MenuItem is a container that can hold one child (this

     restriction comes from GtkBin-class, which is an abstract class

     for containers that are capable of holding only one one child).

     We use the "child" property of the Container class to set its

     child on creation. Normally a Container would allow us to add

     multiple children in a similar way (by setting the "child"-

     property multiple times). */

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

 

  /* Return the GtkMenuItem to caller. */

  return mi;

}

 

/**

 * NEW

 *

 * Utility function to build a menubar and the menu.

 *

 * Uses the buildMenuItem-function from above and will connect the

 * signals that can be generated by MenuItems when they're selected

 * (the "activate"-signal).

 *

 * Returns:

 * - the completed MenuBar

 */

static GtkMenuBar* buildMenubar(void) {

 

  /* The visible menubar widget that will hold one submenu called

     "File". */

  GtkMenuBar* menubar;

  /* The menu item representing the menu-name in the menubar. It is

     necessary in order to attach the menu to the menubar. Will open

     the submenu when activated. */

  GtkMenuItem* miFile;

 

  /* A container implementing a menu that holds multiple items. */

  GtkMenu* fileMenu;

  /* The individual menu items and a separator item. */

  GtkMenuItem* miOpen;

  GtkMenuItem* miSave;

  GtkMenuItem* miSep;

  GtkMenuItem* miQuit;

 

  /* Create the container for items. */

  fileMenu = g_object_new(GTK_TYPE_MENU, NULL);

 

  /* Create the menu items. */

  miOpen = buildMenuItem("Open");

  miSave = buildMenuItem("Save");

  miQuit = buildMenuItem("Quit");

  miSep = g_object_new(GTK_TYPE_SEPARATOR_MENU_ITEM, NULL);

 

  /* Add the items into the container.

 

     Here we again use the "child"-property. But since Menu is a

     proper container that can hold multiple children, we can use a

     list of widgets to add. */

  g_object_set(fileMenu,

    "child", miOpen,

    "child", miSave,

    "child", miSep,

    "child", miQuit,

    NULL);

 

  /* Connect the signals from the individual menu items.

     NOTE:

       We need to cast the CB data into a gpointer since that is what

       the prototype of g_signal_connect tells the compiler to

       enforce. Luckily for us there is a macro to do exactly this

       (in GLib). */

  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));

 

  /* Create the menu item that will be added to the menubar. */

  miFile = buildMenuItem("File");

 

  /* Associate the "File" menu item to the Menu container so that it

     will open it as a submenu when activated.

 

     There is no property to set this, so we use an accessor

     function. */

  gtk_menu_item_set_submenu(miFile, GTK_WIDGET(fileMenu));

 

  /* Create the menubar. */

  menubar = g_object_new(GTK_TYPE_MENU_BAR, NULL);

 

  /* Add the "File" menu item to the menubar.

 

     We want to add from left to right, so again, the "child"

     property will be used. */

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

 

  /* Return the completed menubar to the caller. */

  return menubar;

}

 

/**

 * MODIFIED

 *

 * We add a container that can hold more than one widget since

 * GtkWindow can hold only one at a time. We need to put our menubar

 * somewhere, and display a label. We select a container that will

 * split the area that it will get into horizontal sections according

 * to the needs to its children (GtkVBox).

 */

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

 

  GtkWindow* window;

  GtkLabel* label;

  GtkMenuBar* menubar;

  GtkVBox* vbox;

 

  /* Initialize the GTK+ library. */

  gtk_init(&argc, &argv);

 

  /* Create a window with window border width of 12 pixels and a

     title text. */

  window = g_object_new(GTK_TYPE_WINDOW,

    "border-width", 12,

    "title", "Hello GTK+",

    NULL);

 

  /* Create the label widget. */

  label = g_object_new(GTK_TYPE_LABEL,

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

    NULL);

 

  /* Build the menubar (NEW). */

  menubar = buildMenubar();

 

  /* Create a layout box for the window since it can only hold one

     widget (NEW). */

  vbox = g_object_new(GTK_TYPE_VBOX, NULL);

 

  /* Add the vbox as the only child for the window (NEW). */

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

 

  /* NEW

 

     Add it to the container that now controls the area inside the

     window.

 

     Both VBox and HBox implement an abstract class called GtkBox.

     Adding child widgets to GtkBoxes is possible by using an

     old-style API (gtk_box_pack_start and _end), or we can use the

     context based one with properties.

 

     We need to control three properties (besides the "child", GtkBox

     is also a container):

     - "pack-type": selects whether to add the next child to the

       start or end of box. Default is GTK_PACK_START.

       For VBox start is top and end is bottom.

     - "expand": Controls whether the child will receive extra space

       when the Box will grow (if it get's new space itself). Default

       is TRUE.

     - "fill": If "expand" is set, controls whether extra new space

       should be given to the child or used as padding. Default is

       TRUE.

 

    Based on this information we now must select a sequence that will

    create a split between the menubar and the label.

 

    We want the menubar to take only the amount of space that it

    requires, and for the label to fill up the rest. Should the

    window grow, we want the area allocated to label to grow as well.

    We'll see a growing window later on.

 

    We have two choices (A or B):

    A) Insert the menubar first:

       1) Set "expand" to FALSE.

       2) Use "child" to insert menubar.

       3) Set "pack-type" to GTK_PACK_END so that label will be added

          from bottom-up.

       4) Set "expand" back to TRUE.

       5) Use "child" to insert the label.

    B) Insert the Label first:

       1) Set "pack-type" to GTK_PACK_END.

       2) Insert label using "child".

       3) Set "expand" to FALSE.

       4) Set "pack-type" to GTK_PACK_START.

       5) Use "child" to insert the menubar.

 

    As you can see, both approaches will require an equal amount of

    property operations, so we can choose either one. There are other

    possible orders as well, but all of them require at least the

    five operations. We'll choose the first approach (A):

 

    g_object_set(vbox,

      "fill", FALSE,

      "child", menubar,

      "pack-type", GTK_PACK_END,

      "expand", TRUE,

      "child", label,

      NULL);

 

    NOTE:

      This won't work currently since the VBox implementation does

      not implement the properties of the abstract GtkBox class.

 

      This means that we'll need to use the accessor interface which

      requires us to give both fill and expand each time we add a

      widget.

 

      The order of parameters for the accessor function:

      - GtkBox into which we're adding the widget.

      - The widget that we're adding.

      - "fill".

      - "expand".

      - "padding", which is how many pixels are used around the

        widget. */

  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);

 

  /* Connect the termination signals */

  g_signal_connect(window, "delete-event",

                   G_CALLBACK(delete_event), NULL);

  g_signal_connect(window, "destroy",

                   G_CALLBACK(end_program), NULL);

 

  /* Show all widgets that are contained by the window. This will

     also include the menubar, and its widget tree. */

  gtk_widget_show_all(GTK_WIDGET(window));

 

  /* Start the main event loop. */

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

  gtk_main();

 

  /* Display a message to the standard output and exit. */

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

 

  /* Return success as exit code. */

  return EXIT_SUCCESS;

}

 

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

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

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

 

Hildon widgets

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

 

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

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

 

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

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

 

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

/**

 * hildon_helloworld-1.c

 *

 * This maemo code example is licensed under a MIT-style license,

 * that can be found in the file called "License" in the same

 * directory as this file.

 * Copyright (c) 2007 Nokia Corporation. All rights reserved.

 *

 * Let's hildonize our application. Most of the application stays

 * unchanged, but the menu building utility needs some changes and

 * 'main' will need to be modified.

 *

 * Look for lines with "NEW" or "MODIFIED" in them.

 */

 

#include <stdlib.h>

#include <gtk/gtk.h>

 

/**

 * NEW

 *

 * When using widgets from the Hildon library, we need to use

 * additional include files. We could just pull in <hildon/hildon.h>,

 * but that is unnecessary since we only use a small number of Hildon

 * widgets. Instead we'll opt to pull in only the Hildon header files

 * that are necessary for the widgets that we use.

 *

 * For our first Hildon application, we'll need the Hildon Program

 * framework widgets whose API and types are declared in

 * hildon-program.h

 */

#include <hildon/hildon-program.h>

 

/* Menu codes. */

typedef enum {

  MENU_FILE_OPEN = 1,

  MENU_FILE_SAVE = 2,

  MENU_FILE_QUIT = 3

} MenuActionCode;

 

/**

 * Callback function (event handler) for the "delete" event.

 */

static gboolean delete_event(GtkWidget* widget, GdkEvent event,

                                                gpointer data) {

  return FALSE;

}

 

/**

 * Our callback function for the "destroy"-signal which is issued

 * when the Widget is going to be destroyed.

 */

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

  gtk_main_quit();

}

 

/**

 * Signal handler for the menu item selections.

 */

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);

  }

}

 

/**

 * A convenience function to create menu items with internal label

 * objects.

 */

static GtkMenuItem* buildMenuItem(const gchar* labelText) {

 

  GtkLabel* label;

  GtkMenuItem* mi;

 

  /* Create the Label object. */

  label = g_object_new(GTK_TYPE_LABEL,

    "label", labelText,     /* GtkLabel property */

    "xalign", (gfloat)0.0,  /* GtkMisc property */

    NULL);

 

  /* Create the GtkMenuItem and add the Label as its child. */

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

 

  /* Return the GtkMenuItem to caller. */

  return mi;

}

 

/**

 * MODIFIED

 *

 * This utility adds the menu items into the given HildonProgram.

 * It uses the buildMenuItem-function internally to build each menu

 * item. It will connect the signals that can be generated by

 * MenuItems when they're selected ("activate"-signal).

 *

 * Since there is no menubar, so this becomes quite simple.

 */

static void buildMenu(HildonProgram* program) {

 

  GtkMenu* menu;

  GtkMenuItem* miOpen;

  GtkMenuItem* miSave;

  GtkMenuItem* miSep;

  GtkMenuItem* miQuit;

 

  /* Create the menu items. */

  miOpen = buildMenuItem("Open");

  miSave = buildMenuItem("Save");

  miQuit = buildMenuItem("Quit");

  miSep = g_object_new(GTK_TYPE_SEPARATOR_MENU_ITEM, NULL);

 

  /* Create a new menu. */

  menu = g_object_new(GTK_TYPE_MENU, NULL);

 

  /* Add the items to the container. */

  g_object_set(menu,

    "child", miOpen,

    "child", miSave,

    "child", miSep,

    "child", miQuit,

    NULL);

 

  /* Set the top-level menu for Hildon program (NEW). */

  /* 设置顶层菜单 *

  hildon_program_set_common_menu(program, menu);

 

  /* Connect the signals from the individual menu items. */

  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));

 

  /**

   * NEW

   *

   * We need to explicitly set the visibility for the menu since by

   * default it's not visible when we get it from window. Safest bet

   * is to show the widget tree that is inside the menu.

   */

  gtk_widget_show_all(GTK_WIDGET(menu));

}

 

/**

 * MODIFIED

 *

 * We switch to the application framework that Hildon provides.

 */

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

 

  /* Our GtkWindow has been replaced with HildonProgram (NEW). */

  HildonProgram* program;

  /* And we'll have one window, which will act as the container for

     our needs (NEW). */

  HildonWindow* window;

  GtkWidget* label;

  GtkWidget* vbox;

 

  /* Initialize the GTK+. */

  gtk_init(&argc, &argv);

 

  /**

   * NEW

   *

   * Create the Hildon provided window and controller widgets.

   */

  Hildon来提供window和控制控件

  program = HILDON_PROGRAM(hildon_program_get_instance());

  /* Set the application title using an accessor function. */

  g_set_application_name("Hello Hildon!");

  /* Create a window that will handle our layout and menu. */

  window = HILDON_WINDOW(hildon_window_new());

  /* Bind the HildonWindow to HildonProgram. */

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

  hildon_program_add_window(program, HILDON_WINDOW(window));

 

  /* Create the label widget. */

  label = g_object_new(GTK_TYPE_LABEL,

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

    NULL);

 

  /* Build the menu. We'll create the menu for the HildonProgram, so

     we pass the pointer to our revised buildMenu (NEW). */

  buildMenu(program);

 

  /* Create a layout box for the window since it can only hold one

     widget. */

  vbox = g_object_new(GTK_TYPE_VBOX, NULL);

 

  /* Add the vbox as a child to the Window. */

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

 

  /* Pack the label into the VBox. */

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

 

  /* Connect the termination signals. Note how the HildonWindow

     object has taken the responsibilities that a GtkWindow normally

     would have (NEW). */

  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);

 

  /* Show all widgets that are contained by the window. This also

     includes the menu (if it has been setup by this point) (NEW). */

  gtk_widget_show_all(GTK_WIDGET(window));

 

  /* Start the main event loop. */

  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`

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

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

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

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

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

 

使用封装过的函数API

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

 

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

 

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

/**

 * hildon_helloworld-2.c

 *

 * This maemo code example is licensed under a MIT-style license,

 * that can be found in the file called "License" in the same

 * directory as this file.

 * Copyright (c) 2007 Nokia Corporation. All rights reserved.

 *

 * We now convert into accessor functions. Program logic is same as

 * in hildon_helloworld-1.c.

 *

 * Look for lines with "NEW" or "MODIFIED" in them.

 */

 

#include <stdlib.h>

#include <gtk/gtk.h>

#include <hildon/hildon-program.h>

 

/* Menu codes. */

typedef enum {

  MENU_FILE_OPEN = 1,

  MENU_FILE_SAVE = 2,

  MENU_FILE_QUIT = 3

} MenuActionCode;

 

/**

 * Callback function (event handler) for the "delete" event.

 */

static gboolean delete_event(GtkWidget* widget, GdkEvent event,

                                                gpointer data) {

  return FALSE;

}

 

/**

 * Our callback function for the "destroy"-signal which is issued

 * when the Widget is going to be destroyed.

 */

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

  gtk_main_quit();

}

 

/**

 * Signal handler for the menu item selections.

 */

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);

  }

}

 

/**

 * MODIFIED

 *

 * This utility creates the menu for the HildonProgram.

 * GTK+ offers a convenience function to create MenuItems with

 * GtkLabels with alignment already included. We'll use the

 * convenience function instead of the GObject properties.

 */

static void buildMenu(HildonProgram* program) {

 

  GtkMenu* menu;

  GtkWidget* miOpen;

  GtkWidget* miSave;

  GtkWidget* miSep;

  GtkWidget* miQuit;

 

  /* Create the menu items. */

  //这里直接使用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();

 

  /* Create a new menu. */

  menu = GTK_MENU(gtk_menu_new());

 

  /* Add the items to the container.

 

     Now that we're using accessor functions, things look a bit

     different. We're still doing the same logic though.

 

     The prototype for adding children to a container is:

     gtk_container_add(GtkContainer*, GtkWidget*)

 

     This is why we don't need to cast the menu items. */

  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);

 

  /* Connect the signals from individual menu items. */

  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));

 

  /* Set the top level menu for Hildon program. */

  hildon_program_set_common_menu(program, menu);

}

 

/**

 * MODIFIED

 *

 * We use the accessor functions instead of GObject-properties.

 * Because Hildon API creator functions return GtkWidget pointers,

 * we'll change our pointer types accordingly. This is done for GTK+

 * widgets as well.

 */

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

 

  HildonProgram* program;

  HildonWindow* window;

  GtkWidget* label;

  GtkWidget* vbox;

 

  /* Initialize the GTK+. */

  gtk_init(&argc, &argv);

 

  /* Create the Hildon program. */

  program = HILDON_PROGRAM(hildon_program_get_instance());

  /* Set the application title using an accessor function. */

  g_set_application_name("Hello Hildon!");

  /* Create a window that will handle our layout and menu. */

  window = HILDON_WINDOW(hildon_window_new());

  /* Bind the HildonWindow to HildonProgram. */

  hildon_program_add_window(program, HILDON_WINDOW(window));

 

  /* Create the label widget (NEW). */

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

 

  /* Build the menu and attach it to the HildonProgram. */

  buildMenu(program);

 

  /* Create a layout box for the window since it can only hold one

     widget (NEW).

 

     Using the creator function, we need to pass two parameters to

     it:

     - gboolean: should all children be given equal amount of space?

     - gint:     pixels to leave between the child widgets. */

  vbox = gtk_vbox_new(FALSE, 0);

 

  /* Add the vbox as a child to the Window (NEW). */

  gtk_container_add(GTK_CONTAINER(window), vbox);

 

  /* Pack the label into the VBox. */

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

 

  /* Connect the termination signals. */

  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);

 

  /* Show all widgets that are contained by the Window. */

  gtk_widget_show_all(GTK_WIDGET(window));

 

  /* Start the main event loop. */

  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中移除一个widgetcontainer会减少一个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函数禁用。

 

原创粉丝点击