Le selezioni sono un tipo di comunicazione tra processi supportato da GTK. Una selezione identifica un frammento di dati; per esempio, una porzione di testo selezionata dall'utente in qualche modo, magari con il mouse. Su un display solo un'applicazione alla volta (il proprietario) puó essere proprietaria di una particolare selezione, sicché quando un'applicazione richiede una selezione il precedente proprietario deve comunicare all'utente che la selezione è stata ceduta. Altre applicazioni possono richiedere il contenuto di una selezione in diverse forme, chiamate obiettivi. Ci può essere un numero qualsiasi di selezioni, ma la maggior parte delle applicazioni X può gestirne solo una, la selezione primaria.
Nella maggior parte dei casi per una applicazione GTK non è necessario gestire esplicitamente le selezioni. I widget standard, come quello di Ingresso, hanno già la capacità di chiedere la selezione se necessario (p. e., quando l'utente seleziona sul testo), e di recuperare il contenuto di una selezione di un altro widget o di un'altra applicazione (p. e., quando l'utente clicca il tasto centrale del mouse). Ci possono comunque essere dei casi nei quali si vuole dare ad altri widget la capacità di fornire la selezione, o si vogliono recuperare degli obiettivi non supportati direttamente.
Un concetto fondamentale necessario per comprendere la gestione delle
selezioni è quello di atomo. Un atomo è un intero
che identifica univocamente una stringa (su un certo display).
Certi atomi sono predefiniti dal server X, e in alcuni casi in gtk.h
ci sono costanti corrispondenti a questi atomi. Per esempio, la costante
GDK_PRIMARY_SELECTION
corrisponde alla stringa ``PRIMARY''.
Negli altri casi bisogna usare le funzioni gdk_atom_intern()
per ottenere l'atomo corrispondente ad una stringa, e gdk_atom_name()
per ottenere il nome di un atomo. Sia le selezioni sia gli obiettivi sono
identificati da atomi.
Il recupero di una selezione è un processo asincrono. Per iniziare il processo, si chiama:
gint gtk_selection_convert (GtkWidget *widget,
GdkAtom selection,
GdkAtom target,
guint32 time)
Questo converte la selezione nella forma specificata
dall'obiettivo target
. Se possibile, il campo time
dovrebbe essere il tempo dell'evento che ha attivato la selezione.
Questo aiuta a far si che gli eventi avvengano nell'ordine in cui
l'utente li ha richiesti. Se comunque non fosse disponibile (per
esempio, se la conversione è stata attivata da un segnale di
``cliccato''), allora si può usare la costante
GDK_CURRENT_TIME
.
Quando il proprietario di una selezione risponde ad una richiesta,
un segnale ``selection_received'' (selezione ricevuta) viene inviato
alla vostra applicazione. Il gestore di questo segnale riceve un
puntatore ad una struttura GtkSelectionData
, che è
definita nel modo seguente:
struct _GtkSelectionData
{
GdkAtom selection;
GdkAtom target;
GdkAtom type;
gint format;
guchar *data;
gint length;
};
selection
e target
sono i valori da voi specificati
nella chiamata gtk_selection_convert()
. type
è
un atomo che identifica il tipo di dati restituiti dal proprietario della
selezione. Alcuni valori possibili sono ``STRING'', una stringa di
caratteri latin-1, ``ATOM'', una serie di atomi, ``INTEGER'', un intero, ecc.
La maggior parte degli obiettivi può restituire solo un tipo.
format
ci dà la lunghezza delle unità (per esempio caratteri)
in bit. Di solito, quando si ricevono i dati non ci si cura di questo.
data
è un puntatore ai dati restituiti, e length
è la lunghezza dei dati restituiti, in byte. Se length
è negativo allora si è verificato un errore e non è
stato possibile recuperare la selezione. Questo può avvenire se
nessuna applicazione era proprietaria della selezione, o se si è
richiesto un obiettivo non supportato dall'applicazione. Viene garantito
che il buffer sia un byte più lungo di length
; il byte
in più sarà sempre zero, in modo che non sia necessario
ricopiare le stringhe solo per farle terminare con zero.
Nell'esempio che segue viene recuperato l'obiettivo speciale ``TARGETS'', che è una lista di tutti gli obiettivi in cui può essere convertita la selezione.
/* gettargets.c */
#include <gtk/gtk.h>
void selection_received (GtkWidget *widget,
GtkSelectionData *selection_data,
gpointer data);
/* Gestore di segnale chiamato quando l'utente clicca nel bottone */
/* "Get Targets" */
void
get_targets (GtkWidget *widget, gpointer data)
{
static GdkAtom targets_atom = GDK_NONE;
/* Prende l'atomo corrispondente alla stringa "TARGETS" */
if (targets_atom == GDK_NONE)
targets_atom = gdk_atom_intern ("TARGETS", FALSE);
/* E richiede l'obiettivo "TARGETS" per la selezione primaria */
gtk_selection_convert (widget, GDK_SELECTION_PRIMARY, targets_atom,
GDK_CURRENT_TIME);
}
/* Gestore di segnale chiamato quando il proprietario della selezione */
/* restituisce i dati */
void
selection_received (GtkWidget *widget, GtkSelectionData *selection_data,
gpointer data)
{
GdkAtom *atoms;
GList *item_list;
int i;
/* **** IMPORTANTE **** Controlla che il recupero sia riuscito */
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;
}
/* Stampa gli atomi ricevuti */
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);
/* Crea un bottone che l'utente può cliccare per ottenere gli obiettivi */
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;
}
Fornire la selezione è un po' più complicato. Bisogna registrare i gestori che verranno chiamati quando viene richiesta la propria selezione. Per ogni coppia selezione/obiettivo che si gestirà occorre una chiamata a:
void gtk_selection_add_handler (GtkWidget *widget,
GdkAtom selection,
GdkAtom target,
GtkSelectionFunction function,
GtkRemoveFunction remove_func,
gpointer data);
widget
, selection
, e target
identificano le richieste
che questo gestore soddisferà. remove_func
, se non è
NULL, verrà chiamato quando il gestore di segnale viene rimosso.
Questo è utile, per esempio, per linguaggi interpretati ai quali
serve di tener traccia di un conteggio di riferimento per data
.
La funzione di richiamo ha la forma:
typedef void (*GtkSelectionFunction) (GtkWidget *widget,
GtkSelectionData *selection_data,
gpointer data);
La GtkSelectionData è la stessa di prima, ma stavolta siamo
responsabili di riempire i campi type
, format
, data
,
e length
. (Il campo format
qui è effettivamente
importante - il server X lo usa per capire se occorre che i byte
dei dati vengano scambiati o no. Di solito sarà 8 - cioè
un carattere - o 32 - cioè un intero.) Questo viene fatto
chiamando la funzione:
void gtk_selection_data_set (GtkSelectionData *selection_data,
GdkAtom type,
gint format,
guchar *data,
gint length);
Questa funzione si prende cura di fare propriamente una copia dei dati
in modo che non ci si debba preoccupare di conservarli (è opportuno
evitare di riempire a mano i campi della struttura GtkSelectionData).
Quando richiesto dall'utente, richiederete la proprietà della selezione chiamando:
gint gtk_selection_owner_set (GtkWidget *widget,
GdkAtom selection,
guint32 time);
Se un'altra applicazione richiede la proprietà della selezione, riceverete un evento di azzeramento della selezione (``selection_clear_event'').
Come esempio di fornitura della selezione, il programma seguente aggiunge la funzionalità di selezione a un bottone di attivazione. Quando il bottone viene premuto, il programma richiede la selezione primaria. L'unico obiettivo supportato (oltre a certi obiettivi come ``TARGETS'' fornito dalla stessa GTK) è l'obiettivo ``STRING''. Quando viene richiesto questo obiettivo, viene restituita una rappresentazione stringa del tempo.
/* setselection.c */
#include <gtk/gtk.h>
#include <time.h>
/* Richiamata quando l'utente attiva la selezione */
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);
/* se il richiamo della selezione è fallito, si riporta il
bottone nello stato non premuto */
if (!*have_selection)
gtk_toggle_button_set_state (GTK_TOGGLE_BUTTON(widget), FALSE);
}
else
{
if (*have_selection)
{
/* Prima di annullare la selezione mettendone a NULL il proprietario,
controlliamo se siamo i veri proprietari */
if (gdk_selection_owner_get (GDK_SELECTION_PRIMARY) == widget->window)
gtk_selection_owner_set (NULL, GDK_SELECTION_PRIMARY,
GDK_CURRENT_TIME);
*have_selection = FALSE;
}
}
}
/* Chiamata quando un'altra applicazione richiede la selezione */
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;
}
/* Fornisce come selezione il tempo attuale */
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));
/* Quando si restituisce una singola stringa, non occorre che finisca
con NULL. Questo verrà fatto automaticamente */
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);
/* Crea la finestra di livello superiore */
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);
/* Crea un bottone a commutazione che agisce come la selezione */
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;
}