GTK所支援的其中一种行程间通讯为selections. 一个selection本身是一笔资料, 例如, 使用者选取文字的一部份, 又如, 由滑鼠抓出一些东西. 在显示器上一次只能有一个"选取区域", 上一个选取区域要在该区域撤销时才会生效. 其它的应用软体以几种各种不同形式取得其内容, 被称为targets. 可以有许多个selections, 但X软体只能处理一个, 即primary selection.
在大部份的状况下, GTK程式不需要自行处理选取区域. 标准物件如Entry物件, 已经有能力来自动产生选取区域, 并从其它物件撷取选取区域. 不过有时候, 您想要给其它物件有能力提供选取区域, 或当内定不支援, 想要撷取资料时.
一个基本观念需要了解选取区域处理的是atom.
一个atom是个integer, 标记著一个字串.
有些特定的元素被X server事先定义过,
有些在gtk.h
中则为固定数值, 对映到这些atoms.
例如GDK_PRIMARY_SELECTION
对映到字串"PRIMARY".
在其它状况下, 您应该使用gdk_atom_intern()
这个函数,
用以取得atom对映到string,
及gdk_atom_name()
, 用以取得atom的名称.
selections及targets都是一种atoms.
撷取selection是个非同步行程. 您可以呼叫:
gint gtk_selection_convert (GtkWidget *widget,
GdkAtom selection,
GdkAtom target,
guint32 time)
这个函数转换选取区域到target
所指定的形式.
time这一栏是由选取被触发到事件发生的时间.
这使我们可以保证事件发生的顺序.
您也可以用GDK_CURRENT_TIME
来替代.
当选取区域的拥有者回应一个要求时,
一个"selection_received"信号会送到您的程式.
该信号处理器会收到一个指标GtkSelectionData
结构, 定义如下:
struct _GtkSelectionData
{
GdkAtom selection;
GdkAtom target;
GdkAtom type;
gint format;
guchar *data;
gint length;
};
selection
及target
是您在gtk_selection_convert()
中所给的值.
type
由选区拥有者返回, 用来辨识资料型态.
可以是这些值"STRING", 字串,
"ATOM", 一系列的atoms, "INTEGER", 一个integer, 等等.
a series of atoms, "INTEGER", an integer, etc.
大部份targets只能返回一种型态.
format
是每个单位有多少的bits(如字元为8 bits, guint32为32 bits).
一般来说, 您在收资料的时候, 不必管这个值.
data
是返回的资料指标.
length
是返回资料的长度, 以byte做单位.
如果length
是负值,
那麽表示有错误发生, 选取区域无效.
这在所被要求选区的程式本身不拥有或不支援的时候会发生.
该缓冲区事实上保证一定有多出一个byte;
多出来的byte永远为零,
所以不需要多复制一份字串备份.
在以下的例子中, 我们撷取特别的target, "TARGETS", 这是个所有selection都可以转换进去的target.
#include <gtk/gtk.h>
void selection_received (GtkWidget *widget,
GtkSelectionData *selection_data,
gpointer data);
/* Signal handler invoked when user clicks on the "Get Targets" button */
void
get_targets (GtkWidget *widget, gpointer data)
{
static GdkAtom targets_atom = GDK_NONE;
/* Get the atom corresonding to the string "TARGETS" */
if (targets_atom == GDK_NONE)
targets_atom = gdk_atom_intern ("TARGETS", FALSE);
/* And request the "TARGETS" target for the primary selection */
gtk_selection_convert (widget, GDK_SELECTION_PRIMARY, targets_atom,
GDK_CURRENT_TIME);
}
/* Signal handler called when the selections owner returns the data */
void
selection_received (GtkWidget *widget, GtkSelectionData *selection_data,
gpointer data)
{
GdkAtom *atoms;
GList *item_list;
int i;
/* **** IMPORTANT **** Check to see if retrieval succeeded */
if (selection_data->length < 0)
{
g_print ("Selection retrieval failed\n");
return;
}
/* Make sure we got the data in the expected form */
if (selection_data->type != GDK_SELECTION_TYPE_ATOM)
{
g_print ("Selection \"TARGETS\" was not returned as atoms!\n");
return;
}
/* Print out the atoms we received */
atoms = (GdkAtom *)selection_data->data;
item_list = NULL;
for (i=0; i<selection_data->length/sizeof(GdkAtom); i++)
{
char *name;
name = gdk_atom_name (atoms[i]);
if (name != NULL)
g_print ("%s\n",name);
else
g_print ("(bad atom)\n");
}
return;
}
int
main (int argc, char *argv[])
{
GtkWidget *window;
GtkWidget *button;
gtk_init (&argc, &argv);
/* Create the toplevel window */
window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
gtk_window_set_title (GTK_WINDOW (window), "Event Box");
gtk_container_border_width (GTK_CONTAINER (window), 10);
gtk_signal_connect (GTK_OBJECT (window), "destroy",
GTK_SIGNAL_FUNC (gtk_exit), NULL);
/* Create a button the user can click to get targets */
button = gtk_button_new_with_label ("Get Targets");
gtk_container_add (GTK_CONTAINER (window), button);
gtk_signal_connect (GTK_OBJECT(button), "clicked",
GTK_SIGNAL_FUNC (get_targets), NULL);
gtk_signal_connect (GTK_OBJECT(button), "selection_received",
GTK_SIGNAL_FUNC (selection_received), NULL);
gtk_widget_show (button);
gtk_widget_show (window);
gtk_main ();
return 0;
}
提供选取区域比较复杂. 您必须注册handler, 当您被要求提供选区时, handler会被呼叫到. 对每个selection/target, 您必须呼叫:
void gtk_selection_add_handler (GtkWidget *widget,
GdkAtom selection,
GdkAtom target,
GtkSelectionFunction function,
GtkRemoveFunction remove_func,
gpointer data);
widget
, selection
, 及target
表示这个处理器会处理的要求.
如果remove_func
不为NULL,
当信号处里器被移除时, 这个函数会被移除.
这很有用, 例如说, 给解译式语言用, 因为它会保持追踪并维护其自身的资料.
该callback函数有以下的形式:
typedef void (*GtkSelectionFunction) (GtkWidget *widget,
GtkSelectionData *selection_data,
gpointer data);
GtkSelectionData跟上面一样, 但这一次,
我们必须要填type
, format
,
data
, 及length
这几栏.
(format
这一栏很重要 - X server用来决定是否需要做byte-swap,
因为有X是多平台的系统, 一般8是character, 32是integer.)
这是由以下函数所完成的:
void gtk_selection_data_set (GtkSelectionData *selection_data,
GdkAtom type,
gint format,
guchar *data,
gint length);
这个函数会将资料备一份, 因此您不需要自行维护. (这就是说您不应该自己手动去填该资料结构的资料.)
您可以用以下函数设定该选区的拥有者:
gint gtk_selection_owner_set (GtkWidget *widget,
GdkAtom selection,
guint32 time);
如果有其它程式设定了该选区的拥有权, 您会收到一个"selection_clear_event"信号.
做为一个提供选区的例子, 以下程式将选取功能加到一个双态按钮. 当双态按钮被按下时, 该程式会设定拥有该选区. 而唯一支援的target是"STRING" target. 当该target被要求时, 将会返回一个显示时间的字串.
#include <gtk/gtk.h>
#include <time.h>
/* Callback when the user toggles the selection */
void
selection_toggled (GtkWidget *widget, gint *have_selection)
{
if (GTK_TOGGLE_BUTTON(widget)->active)
{
*have_selection = gtk_selection_owner_set (widget,
GDK_SELECTION_PRIMARY,
GDK_CURRENT_TIME);
/* if claiming the selection failed, we return the button to
the out state */
if (!*have_selection)
gtk_toggle_button_set_state (GTK_TOGGLE_BUTTON(widget), FALSE);
}
else
{
if (*have_selection)
{
/* Before clearing the selection by setting the owner to NULL,
we check if we are the actual owner */
if (gdk_selection_owner_get (GDK_SELECTION_PRIMARY) == widget->window)
gtk_selection_owner_set (NULL, GDK_SELECTION_PRIMARY,
GDK_CURRENT_TIME);
*have_selection = FALSE;
}
}
}
/* Called when another application claims the selection */
gint
selection_clear (GtkWidget *widget, GdkEventSelection *event,
gint *have_selection)
{
*have_selection = FALSE;
gtk_toggle_button_set_state (GTK_TOGGLE_BUTTON(widget), FALSE);
return TRUE;
}
/* Supplies the current time as the selection. */
void
selection_handle (GtkWidget *widget,
GtkSelectionData *selection_data,
gpointer data)
{
gchar *timestr;
time_t current_time;
current_time = time (NULL);
timestr = asctime (localtime(¤t_time));
/* When we return a single string, it should not be null terminated.
That will be done for us */
gtk_selection_data_set (selection_data, GDK_SELECTION_TYPE_STRING,
8, timestr, strlen(timestr));
}
int
main (int argc, char *argv[])
{
GtkWidget *window;
GtkWidget *selection_button;
static int have_selection = FALSE;
gtk_init (&argc, &argv);
/* Create the toplevel window */
window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
gtk_window_set_title (GTK_WINDOW (window), "Event Box");
gtk_container_border_width (GTK_CONTAINER (window), 10);
gtk_signal_connect (GTK_OBJECT (window), "destroy",
GTK_SIGNAL_FUNC (gtk_exit), NULL);
/* Create a toggle button to act as the selection */
selection_button = gtk_toggle_button_new_with_label ("Claim Selection");
gtk_container_add (GTK_CONTAINER (window), selection_button);
gtk_widget_show (selection_button);
gtk_signal_connect (GTK_OBJECT(selection_button), "toggled",
GTK_SIGNAL_FUNC (selection_toggled), &have_selection);
gtk_signal_connect (GTK_OBJECT(selection_button), "selection_clear_event",
GTK_SIGNAL_FUNC (selection_clear), &have_selection);
gtk_selection_add_handler (selection_button, GDK_SELECTION_PRIMARY,
GDK_SELECTION_TYPE_STRING,
selection_handle, NULL, NULL);
gtk_widget_show (selection_button);
gtk_widget_show (window);
gtk_main ();
return 0;
}