Previous Next Contents

16. 选取区域管理

16.1 概说

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.

16.2 撷取selection

撷取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;
};

selectiontarget 是您在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;
}

16.3 提供选取区域

提供选取区域比较复杂. 您必须注册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(&current_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;
}


Previous Next Contents