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.
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 */
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(¤t_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 */