Avanti Indietro Indice

16. La gestione delle selezioni

16.1 Overview

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.

16.2 Recuperare le selezioni

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

16.3 Fornire una selezione

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(&current_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;
}


Avanti Indietro Indice