Página siguiente Página anterior Índice general

21. Manejando selecciones

21.1 Contenido

Un tipo de comunicación entre procesos que se puede utilizar con GTK son las selecciones. Una selección identifica un conjunto de datos, por ejemplo, un trozo de texto, seleccionado por el usuario de alguna manera, por ejemplo, cogiéndolo con el ratón. Sólo una aplicación en un display (la propietaria) puede tener una selección en particular en un momento dado, por lo que cuando una aplicación pide una selección, el propietario previo debe indicar al usuario que la selección ya no es válida. Otras aplicaciones pueden pedir el contenido de la selección de diferentes formas, llamadas objetivos. Puede haber cualquier número de selecciones, pero la mayoría de las aplicacion X sólo pueden manejar una, la selección primaria.

En muchos casos, no es necesario para una aplicación GTK tratar por sí misma con las selecciones. Los widgets estándar, como el widget Entry, ya tienen la posibilidad de crear la selección cuando sea necesario (p.e., cuando el usuario pase el ratón sobre el texto manteniendo el botón derecho del ratón pulsado), y de recoger los contenidos de la selección propiedad de otro widget, o de otra aplicación (p.e., cuando el usuario pulsa el segundo botón del ratón). Sin embargo, pueden haber casos en los que quiera darle a otros widgets la posibilidad de proporcionar la selección, o puede que quiera recuperar objetivos que no estén admitidos por defecto.

Un concepto fundamental que es necesario para comprender el manejo de la selección es el de átomo. Un átomo es un entero que identifica de una manera unívoca una cadena (en un cierto display). Ciertos átomos están predefinidos por el servidor X, y en algunos casos hay constantes en gtk.h que corresponden a estos átomos. Por ejemplo la constante GDK_PRIMARY_SELECTION corresponde a la cadena ``PRIMARY''. En otros casos, debería utilizar las funciones gdk_atom_intern(), para obtener el átomo correspondiente a una cadena, y gdk_atom_name(), para obtener el nombre de un átomo. Ambas, selecciones y objetivos, están identificados por átomos.

21.2 Recuperando la selección

Recuperar la selección es un proceso asíncrono. Para comenzar el proceso, deberá llamar a:

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

Este proceso convierte la selección en la forma especificada por target. Si es posible, el campo time debe ser el tiempo desde que el evento lanzó la selección. Esto ayuda a asegurarse de que los eventos ocurran en el orden en que el usuario los ha pedido. Sin embargo, si no está disponible (por ejemplo, si se empezó la conversión por una señal de ``pulsación''), entonces puede utilizar la constante GDK_CURRENT_TIME.

Cuando el propietario de la selección responda a la petición, se enviará una señal ``selection_received'' a su aplicación. El manejador de esta señal recibe un puntero a una estructura GtkSelectionData, que se define como:

struct _GtkSelectionData
{
  GdkAtom selection;
  GdkAtom target;
  GdkAtom type;
  gint    format;
  guchar *data;
  gint    length;
};

selection y target son los valores que dió en su llamada a gtk_selection_convert(). type es un átomo que identifica el tipo de datos devueltos por el propietario de la selección. Algunos valores posibles son ``STRING'', un cadena de caracteres latin-1, ``ATOM'', una serie de átomos, ``INTEGER'', un entero, etc. Muchos objetivos sólo pueden devolver un tipo. format da la longitud de las unidades (por ejemplo caracteres) en bits. Normalmente, no tiene porque preocuparse de todo esto cuando recibe datos. data es un puntero a los datos devueltos, y length da la longitud de los datos devueltos, en bytes. Si length es negativo, es que a ocurrido un error y no se puede obtener la selección. Esto podría ocurrir si no hay ninguna aplicación que sea la propietaria de la selección, o si pide un objetivo que la aplicación no admite. Actualmente se garantiza que el búfer tendrá un byte más que length; el byte extra siempre será cero, por lo que no es necesario hacer una copia de las cadenas sólo para añadirles un carácter nulo al final.

En el siguiente ejemplo, recuperamos el objetivo especial ``TARGETS'', que es una lista de todos los objetivos en los que se puede convertir la selección.

/* principio del ejemplo selection gettargets.c */

#include <gtk/gtk.h>

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

/* Manejador de señal invocado cuando el usuario pulsa en el botón
"Get Targets" */
void
get_targets (GtkWidget *widget, gpointer data)
{
  static GdkAtom targets_atom = GDK_NONE;

  /* Obtener el atom correpondiente a la cadena "TARGETS" */
  if (targets_atom == GDK_NONE)
    targets_atom = gdk_atom_intern ("TARGETS", FALSE);

  /* Y pide el objetivo "TARGETS" de la selección primaria */
  gtk_selection_convert (widget, GDK_SELECTION_PRIMARY, targets_atom,
                         GDK_CURRENT_TIME);
}

/* Manipulador de señal llamado cuando el propietario de la señal
 * devuelve los datos */
void
selection_received (GtkWidget *widget, GtkSelectionData *selection_data, 
                    gpointer data)
{
  GdkAtom *atoms;
  GList *item_list;
  int i;

  /* **** IMPORTANTE **** Comprobar si se da la recuperación de los
   * datos */
  if (selection_data->length < 0)
    {
      g_print ("Selection retrieval failed\n");
      return;
    }
  /* Asegurarse de que obtenemos los datos de la forma esperada */
  if (selection_data->type != GDK_SELECTION_TYPE_ATOM)
    {
      g_print ("Selection \"TARGETS\" was not returned as atoms!\n");
      return;
    }
  
  /* Imprimir los atoms que hemos recibido */
  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);

  /* Crear la ventana superior */

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

  /* Crear un botón que el usuario puede pulsar para obtener los
   * objetivos */

  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;
}
/* fin del ejemplo */

21.3 Proporcionando la selección

Proporcionar la selección es un poco más complicado. Debe registrar los manejadores a los que se llamarán cuando se le pida la selección. Por cada par selección/objetivo que quiera manejar, deberá hacer una llamada a:

void gtk_selection_add_handler( GtkWidget            *widget, 
                                GdkAtom               selection,
                                GdkAtom               target,
                                GtkSelectionFunction  function,
                                GtkRemoveFunction     remove_func,
                                gpointer              data );

widget, selection, y target identifican las peticiones que este manejador puede manipular. Si remove_func no es NULL, se le llamará cuando se elimine el manejador de la señal. Esto es útil, por ejemplo, para los lenguajes interpretados que necesitan mantener una memoria de las referencias a data.

La función de llamada tiene el prototipo:

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

El GtkSelectionData es el mismo que hay más arriba, pero esta vez, seremos nosotros los responsables de rellenar los campos type, format, data, y length. (El campo format es importante - el servidor X lo utiliza para saber si tienen que intercambiarse los bytes que forman los datos o no. Normalmente será 8 - es decir un carácter - o 32 - es decir un entero.) Esto se hace llamando a la función:

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

Esta función tiene la responsabilidad de hacer una copia de los datos para que no tenga que preocuparse de ir guardándolos. (No debería rellenar los campos de la estructura GtkSelectionData a mano.)

Cuando haga falta, puede pedir el propietario de la selección llamando a:

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

Si otra aplicación pide el propietario de la selección, recibira un ``selection_clear_event''.

Como ejemplo de proporciar la selección, el programa siguiente le añade la posibilidad de selección a un botón de comprobación. Cuando se presione el botón de comprobación, el programa pedirá la selección primaria. El único objetivo que admite es un objetivo ``STRING'' (aparte de ciertos objetivos como "TARGETS", proporcionados por GTK). Cuando se pida este objetivo, se devolverá una representación del tiempo.

/* principio del ejemplo selection setselection.c */

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

/* Función de llamada para cuando el usuario cambia la selección */
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);
      /* Si la demanda de la selección ha fallado, ponemos el botón en
       * estado apagado */
      if (!*have_selection)
        gtk_toggle_button_set_state (GTK_TOGGLE_BUTTON(widget), FALSE);
    }
  else
    {
      if (*have_selection)
        {
          /* Antes de eliminar la seleción poniendo el propietario a
           * NULL, comprobamos si somos el propietario actual */
          if (gdk_selection_owner_get (GDK_SELECTION_PRIMARY) == widget->window)
            gtk_selection_owner_set (NULL, GDK_SELECTION_PRIMARY,
                                     GDK_CURRENT_TIME);
          *have_selection = FALSE;
        }
    }
}

/* Llamado cuando otra aplicación pide la selección */
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;
}

/* Proporciona el tiempo actual como selección. */
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)); 
  /* Cuando devolvemos una cadena, no debe terminar en NULL. La
   * función lo hará por nosotros */

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

  /* Crear la ventana superior */

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

  /* Crear un botón de selección para que actue como la selección */

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

  gtk_widget_show (selection_button);
  gtk_widget_show (window);
  
  gtk_main ();
  
  return 0;
}
/* fin del ejemplo */


Página siguiente Página anterior Índice general