概述

选中区(Selections)是X和 GTK 提供的图形程序之间传递信息的方法之一。一个选中区标识了一块数据,例如,用户用某种方式(比如拖动鼠标)选择的一部分文本。一个显示区(即用户)同一时间里只能有一个应用程序能得到选中区,所以当一个程序声称一个选中区时,前一个选中区所有者必须告诉用户旧选中区已经被放弃了。其它程序能得到选中区内容的不同的形式,叫做目标(targets)。可以有任意多个选中区,但大多数X程序只会处理一个,叫做主选中区(primary selection)。

大多数情况下,一个 GTK 应用程序并不必自己处理选中区。标准的构件,比如文本输入构件,已经有了在适当的情况下(例如,当用户在文本上拖动时)声称选中区的能力,也能得到其它构件或程序的选中区内容(比如,当用户按鼠标中键时)。然而,有些情况下你可能想使其它构件具有提供选中区的能力,或者你想得到默认未支持的目标(targets)。

处理选中区时要理解的一个基本概念是原子(atom)。原子是一个唯一地标识一个字符串(在一个确定的显示区)的整数。某些原子被X服务器预定义了,其中一些原子在gtk.h中有对应的常量。例如GDK_PRIMARY_SELECTION常量对应于字符串”PRIMARY”。其它情况下,你要使用gdk_atom_intern()函数,根据字符串以获得对应的原子,使用gdk_atom_name()函数,以获得原子的名称。选中区和目标都是通过原子来标识的。

获取选中区信息

获取选中区信息是一个异步的过程。开始这个过程,调用:

gboolean gtk_selection_convert( GtkWidget *widget, 
                                GdkAtom    selection, 
                                GdkAtom    target,
                                guint32    time );

这个将选中区内容转换为target指定的目标形式。如果可能,time参数应该为选中区被触发事件产生的时间。这对确认事件以用户要求它们的顺序产生有帮助。然而,如果不行(例如,转换由 “clicked” 信号触发),你也可以使用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”,由拉丁-1(latin-1)字符组成的字符串,”ATOM”,一些原子,”INTEGER”,一个整数,等等。大多数目标只能返回一种类型。format给定其单位(比如字符型)的比特数。通常,接收数据时你不必关心这个。data是一个指向返回数据的指针,length指定返回数据的长度,以字节为单位。如果length是负数,则表示发生错误且选中区的信息无法获得。这可能在没有应用程序拥有选中区时发生,或者你要求了一个应用程序不支持的目标形式。缓冲区实际上总会比length指示的长一个字节;增加的这一字节总为0值,这样就不必为了使字符串以NULL结尾而另外复制一份。

在下面的示例里,我们获取”TARGETS”形式的目标,它是一个选中区内容能转换为的目标形式的列表。

#include <stdlib.h>
#include <gtk/gtk.h>

void selection_received( GtkWidget        *widget, 
                         GtkSelectionData *selection_data, 
                         gpointer          data );

/* 当用户点击"Get Targets"按钮时调用的信息处理函数 */
void get_targets( GtkWidget *widget,
                  gpointer data )
{
  static GdkAtom targets_atom = GDK_NONE;
  GtkWidget *window = (GtkWidget *)data;    

  /* 得到"TARGETS"字符串对应的原子 */
  if (targets_atom == GDK_NONE)
    targets_atom = gdk_atom_intern ("TARGETS", FALSE);

  /* 要求获取主选中区的"TARGETS"形式的目标 */
  gtk_selection_convert (window, GDK_SELECTION_PRIMARY, targets_atom,
             GDK_CURRENT_TIME);
}

/* 当选中区所有者返回数据时调用的信号处理函数 */
void selection_received( GtkWidget        *widget,
                         GtkSelectionData *selection_data, 
                 gpointer          data )
{
  GdkAtom *atoms;
  GList *item_list;
  int i;

  /* **** 重要 **** 检测获取信息是否成功  */
  if (selection_data->length < 0)
    {
      g_print ("Selection retrieval failed\n");
      return;
    }
  /* 确认得到的数据为原来要求的形式 */
  if (selection_data->type != GDK_SELECTION_TYPE_ATOM)
    {
      g_print ("Selection \"TARGETS\" was not returned as atoms!\n");
      return;
    }

  /* 打印接收到的原子 */
  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);

  /* 创建顶级窗口 */

  window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
  gtk_window_set_title (GTK_WINDOW (window), "Event Box");
  gtk_container_set_border_width (GTK_CONTAINER (window), 10);

  g_signal_connect (G_OBJECT (window), "destroy",
                G_CALLBACK (exit), NULL);

  /* 创建一个按钮,用户按它以获取目标 */

  button = gtk_button_new_with_label ("Get Targets");
  gtk_container_add (GTK_CONTAINER (window), button);

  g_signal_connect (G_OBJECT (button), "clicked",
            G_CALLBACK (get_targets), window);
  g_signal_connect (G_OBJECT (window), "selection_received",
            G_CALLBACK (selection_received), NULL);

  gtk_widget_show (button);
  gtk_widget_show (window);

  gtk_main ();

  return 0;
}

提供选中区

提供选中区要复杂一点。你必须注册当选中区被要求时将调用的处理函数。每处理一对选中区/目标,你就要调用一次下面的函数:

void gtk_selection_add_target (GtkWidget           *widget, 
                               GdkAtom              selection,
                               GdkAtom              target,
                               guint                info);

widget,selection和target标识了这个处理函数要操作的要求。当对选中区的一个要求被接收,”selection_get” 信号将被调用。info能用来标识回调函数里的指定目标。

回调函数形式如下:

void  "selection_get" (GtkWidget          *widget,
                       GtkSelectionData   *selection_data,
                       guint               info,
                       guint               time);

GtkSelectionData和上面介绍的一样,但这次,我们要负责提供type,format,data和length值(format值在这里很重要-X服务器根据它来确定数据是否要以字节为单位处理。通常它的值为8 -如字符型 -或 32-如整型)。调用下面的函数来设置这些值:

void gtk_selection_data_set( GtkSelectionData *selection_data,
                             GdkAtom           type,
                             gint              format,
                             guchar           *data,
                             gint              length );

这个函数会适当地将数据复制一份这样你就不必为保留这些数据操心了。(不要手工填充GtkSelectionData结构里的值。)

用户做了某个操作后,你可以通过下面的函数声称选中区的所有权。

gboolean gtk_selection_owner_set( GtkWidget *widget,
                                  GdkAtom    selection,
                                  guint32    time );

如果其它应用程序要求得到选中区,你将接收到”selection_clear_event”信号。

下面是一个提供选中区的示例,它给开关按钮加了选中区功能。当开关按钮被按下,程序要求得到主选中区。它只支持”STRING”目标(除了 GTK 自身已支持的”TARGETS”等目标)。当这个目标被要求时,一个描叙时间的字符串被返回。

#include <stdlib.h>
#include <gtk/gtk.h>
#include <time.h>

GtkWidget *selection_button;
GtkWidget *selection_widget;

/* 当用户触发选中区时的回调 */
void selection_toggled( GtkWidget *widget,
                        gint      *have_selection )
{
  if (GTK_TOGGLE_BUTTON (widget)->active)
    {
      *have_selection = gtk_selection_owner_set (selection_widget,
                         GDK_SELECTION_PRIMARY,
                         GDK_CURRENT_TIME);
      /* 如果声称选中区失败,则使按钮返回未选中状态 */
      if (!*have_selection)
    gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (widget), FALSE);
    }
  else
    {
      if (*have_selection)
    {
      /* 在设置所有者为NULL来清空选中区前,
       * 先检测自己是不是真正的所有者 */
      if (gdk_selection_owner_get (GDK_SELECTION_PRIMARY) == widget->window)
        gtk_selection_owner_set (NULL, GDK_SELECTION_PRIMARY,
                     GDK_CURRENT_TIME);
      *have_selection = FALSE;
    }
    }
}

/* 当其它应用程序声称选中区时调用 */
gint selection_clear( GtkWidget         *widget,
                      GdkEventSelection *event,
                      gint              *have_selection )
{
  *have_selection = FALSE;
  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (selection_button), FALSE);

  return TRUE;
}

/* 将当前时间作为选中区内容提供。 */
void selection_handle( GtkWidget        *widget, 
                       GtkSelectionData *selection_data,
                       guint             info,
                       guint             time_stamp,
                       gpointer          data )
{
  gchar *timestr;
  time_t current_time;

  current_time = time (NULL);
  timestr = asctime (localtime (&current_time)); 
  /* 当我们返回单独一个字符串时,它不必以NULL结尾。
     它将被自动完成 */

  gtk_selection_data_set (selection_data, GDK_SELECTION_TYPE_STRING,
              8, timestr, strlen (timestr));
}

int main( int   argc,
          char *argv[] )
{
  GtkWidget *window;

  static int have_selection = FALSE;

  gtk_init (&argc, &argv);

  /* 创建顶级窗口 */

  window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
  gtk_window_set_title (GTK_WINDOW (window), "Event Box");
  gtk_container_set_border_width (GTK_CONTAINER (window), 10);

  g_signal_connect (G_OBJECT (window), "destroy",
            G_CALLBACK (exit), NULL);

  /* 创建一个开关按钮作为选中区 */

  selection_widget = gtk_invisible_new ();
  selection_button = gtk_toggle_button_new_with_label ("Claim Selection");
  gtk_container_add (GTK_CONTAINER (window), selection_button);
  gtk_widget_show (selection_button);

  g_signal_connect (G_OBJECT (selection_button), "toggled",
            G_CALLBACK (selection_toggled), &have_selection);
  g_signal_connect (G_OBJECT (selection_widget), "selection_clear_event",
            G_CALLBACK (selection_clear), &have_selection);

  gtk_selection_add_target (selection_widget,
                GDK_SELECTION_PRIMARY,
                GDK_SELECTION_TYPE_STRING,
                    1);
  g_signal_connect (G_OBJECT (selection_widget), "selection_get",
            G_CALLBACK (selection_handle), &have_selection);

  gtk_widget_show (selection_button);
  gtk_widget_show (window);

  gtk_main ();

  return 0;
}