<- PW: Intro - Archivio Generale - Copertina - PW: Bash ->

PlutoWare


Gnome: Desktop mon amour

di Nicola Fragale


L'articolo

Negli ultimi numeri del Pluto Journal abbiamo introdotto le librerie di GTK+, iniziamo con questo articolo ad esaminare le librerie di Gnome. Vedremo come realizzare una piccola applicazione dotata esclusivamente di una barra per i menu, una toolbar e una Appbar. Vedremo inoltre come creare le finestre di dialogo che ci permetteranno di comunicare con l'utente nel modo più appropriato possibile. Come sempre il tutto è accompagnato da un programma di prova. Non mi resta che augurarvi buona lettura ;-)



Indice


Introduzione

Gnome è parte del progetto GNU, è l'acronimo di GNU Network Object Model Environment e nasce nel 1997, con l'obiettivo di rendere disponibile una tecnologia simile a OLE e COM di Microsoft sui sistemi Unix. Il progetto iniziale si è sviluppato molto rapidamente, spostandosi poi, verso la creazione di un desktop environment, spinto sia dal successo che stava ottenendo KDE e dal fatto che le librerie QT su cui KDE è basato erano allora soggette ad una licenza non open source, sia dalla semplicità di utilizzo delle librerie GTK+ per la creazione di interfacce grafiche.

Con le vecchie librerie di GTK+ (fino alle 1.2.x) il programmatore poteva decidere l'aspetto grafico della propria applicazione in totale autonomia, tuttavia questa libertà aveva anche degli aspetti controproducenti. I programmi sviluppati con GTK+ non avevano un aspetto coerente. Pensiamo alle icone inserite sulle toolbar, un programmatore potrebbe decidere di associare una certa icona all'apertura di un file, mentre un altro programmatore potrebbe scegliere di associare alla stessa azione una icona completamente diversa. Oppure un programmatore potrebbe associare alla sequenza di tasti ctrl+c la funzione chiudi della propria applicazione. Tutto ciò genera interfacce grafiche tutte diverse tra loro, e aumenta la confusione nell'utente finale (abituato a cercare e trovare voci di menu in determinati posti, ad utilizzare alcune combinazioni di tasti per compiere operazioni di uso comune, ad es. ctrl+x, ctrl+c, ctrl+v per taglia, copia, incolla).

Con Gnome si è cercato di rimediare a questi problemi. Le librerie di Gnome hanno standardizzato alcuni aspetti delle interfacce grafiche. Mettono a disposizione del programmatore un set di icone predefinito (l'icona "salva con nome" sarà identica su tutte le applicazioni scritte per Gnome), un insieme di finestre di dialogo ed altro ancora. Utilizzando, quindi, le librerie di Gnome è possibile scrivere applicazioni coerenti, dotate tutte di un aspetto uniforme. Le applicazioni scritte per Gnome, possono essere eseguite anche al di fuori di Gnome stesso (in KDE ad esempio), è sufficiente che sul computer siano installate le librerie. Alcuni delle innovazioni introdotte da Gnome (set di icone, finestre di dialogo standard) sono state riprese dalle nuove GTK+2.0.

Le librerie di Gnome a cui mi riferirò in questo articolo sono relative alla versione 1.4.x. Potete scaricare la documentazione da:

Il programma di prova

In figura 1 e figura 2 possiamo osservare il programma.

#include <gnome.h>

#define VERSION "0.1"
#define PACKAGE "CiaoGnome"

/* 
   creiamo una finestra di dialogo di 
   "classe" informazioni
*/
void 
display_message (gchar *str)
{
  GtkWidget *messagebox;
  GtkWidget *button;

  /* Creiamo il dialogo con un bottone OK che rimuoveremo
     immediatamente. Tutto questo per evitare un bug nelle
     attuali librerie di gnome
  */
  messagebox = gnome_message_box_new (str,
				      GNOME_MESSAGE_BOX_INFO,
				      GNOME_STOCK_BUTTON_OK, NULL);

  gtk_container_remove (GTK_CONTAINER (GNOME_DIALOG (messagebox)->action_area), 
			GNOME_DIALOG (messagebox)->buttons->data);
  GNOME_DIALOG (messagebox)->buttons = NULL;

  gtk_window_set_title (GTK_WINDOW (messagebox), "Informazioni");

  gnome_dialog_append_button (GNOME_DIALOG (messagebox), GNOME_STOCK_BUTTON_OK);
  button = GTK_WIDGET (g_list_last (GNOME_DIALOG (messagebox)->buttons)->data);
  gtk_widget_show (button);

  gtk_signal_connect_after (GTK_OBJECT (messagebox), "close",
                            GTK_SIGNAL_FUNC (gtk_widget_destroy),
                            NULL);

  gtk_widget_show(messagebox);
}


/*
   creiamo una finestra di dialogo di 
   "classe" Warning
*/
void
non_implementata (void)
{
  GtkWidget *messagebox;
  GtkWidget *button;

  messagebox = 
  	gnome_message_box_new ("Attenzione questa opzione non è ancora disponibile",
                               GNOME_MESSAGE_BOX_WARNING,
                               GNOME_STOCK_BUTTON_OK, NULL);
  gtk_container_remove (GTK_CONTAINER (GNOME_DIALOG (messagebox)->action_area), 
			GNOME_DIALOG (messagebox)->buttons->data);
  GNOME_DIALOG (messagebox)->buttons = NULL;
  gtk_window_set_title (GTK_WINDOW (messagebox), "Informazioni");

  gnome_dialog_append_button (GNOME_DIALOG (messagebox), GNOME_STOCK_BUTTON_OK);
  button = GTK_WIDGET (g_list_last (GNOME_DIALOG (messagebox)->buttons)->data);
  gtk_widget_show (button);

  gtk_signal_connect (GTK_OBJECT (messagebox), "close",
                      GTK_SIGNAL_FUNC (gtk_widget_destroy),
                      NULL);

  gtk_widget_show(messagebox);
}


/*
   creiamo una finestra di dialogo con le 
   note di copyrigth del nostro programma
*/
void
info (void)
{
  const gchar *authors[] = {
    "Nicola Fragale",
    /* se il programma è sviluppato 
       da più persone, inserirne 
       qui i nominativi
    */
    NULL
  };
  GtkWidget *info_dialog;

  info_dialog = 
    gnome_about_new (PACKAGE, VERSION,
		     "Copyleft (c) 2002 Pluto Journal",
		     authors,
		     "Inseriamo qui una breve descrizione del nostro programma....",
		     NULL);

  gtk_window_set_modal (GTK_WINDOW (info_dialog), TRUE);

  gtk_signal_connect (GTK_OBJECT (info_dialog), "close",
                      GTK_SIGNAL_FUNC (gtk_widget_destroy),
                      NULL);

  gtk_widget_show(info_dialog);
}


/* 
   funzioni di callback richiamate alla 
   pressione di un bottone sulla toolbar 
   o all'attivazione di una voce di menu
*/   
void
on_new_file_activate (GtkMenuItem *menuitem, gpointer user_data)
{
  gchar *str; 

  if (strcmp(user_data, "tb") == 0)
    str = g_strdup("Hai selezionato Nuovo File dalla toolbar");
  else
    str = g_strdup("Hai selezionato Nuovo File dal menu");
    
  display_message(str);
}


void
on_open_activate (GtkMenuItem *menuitem, gpointer user_data)
{
  gchar *str;

  str = g_strdup_printf("Hai selezionato Apri da: %s",
			(gchar*) user_data);
  
  display_message(str);

  g_free(str);
}


void on_save_activate (GtkMenuItem *menuitem, gpointer user_data)
{
  gchar *str;

  if (strcmp(user_data, "tb") == 0)
    str = g_strdup("Hai selezionato  Salva dalla toolbar");
  else
    str = g_strdup("Hai selezionato  Salva dal menu");
 
  display_message(str);
}


void
on_save_as_activate (GtkMenuItem *menuitem, gpointer user_data)
{
  display_message("Hai selezionato \"Salva con nome\" dal menu");
}


void
on_cut_activate (GtkMenuItem *menuitem, gpointer user_data)
{
  non_implementata();
}


void on_copy_activate (GtkMenuItem *menuitem, gpointer user_data)
{
  non_implementata();
}


void
on_paste_activate (GtkMenuItem *menuitem, gpointer user_data)
{
  non_implementata();
}


/*  definiamo i nostri menu 
*/

/* Menu "File"
*/  
static GnomeUIInfo file_menu_uiinfo[] =
{
  GNOMEUIINFO_MENU_NEW_ITEM ("_Nuovo File", NULL, 
			     on_new_file_activate, "fm"),
  GNOMEUIINFO_MENU_OPEN_ITEM (on_open_activate, "menu file"),
  GNOMEUIINFO_MENU_SAVE_ITEM (on_save_activate, "fm"),
  GNOMEUIINFO_MENU_SAVE_AS_ITEM (on_save_as_activate, NULL),
  GNOMEUIINFO_SEPARATOR,
  GNOMEUIINFO_MENU_EXIT_ITEM (gtk_main_quit, NULL),
  GNOMEUIINFO_END
};

/* Menu "Edit" ("Modifica")
*/
static GnomeUIInfo edit_menu_uiinfo[] =
{
  GNOMEUIINFO_MENU_CUT_ITEM (on_cut_activate, NULL),
  GNOMEUIINFO_MENU_COPY_ITEM (on_copy_activate, NULL),
  GNOMEUIINFO_MENU_PASTE_ITEM (on_paste_activate, NULL),
  GNOMEUIINFO_END
};

/* Menu "Aiuto"
*/
static GnomeUIInfo help_menu_uiinfo[] =
{
  GNOMEUIINFO_MENU_ABOUT_ITEM (info, NULL),
  GNOMEUIINFO_END
};


/* Raggruppiamo i menu in una barra
*/
static GnomeUIInfo menubar_uiinfo[] =
{
  GNOMEUIINFO_MENU_FILE_TREE (file_menu_uiinfo),
  GNOMEUIINFO_MENU_EDIT_TREE (edit_menu_uiinfo),
  GNOMEUIINFO_MENU_HELP_TREE (help_menu_uiinfo),
  GNOMEUIINFO_END
};



/* Disegniamo l'interfaccia grafica della
   nostra applicazione
*/	
GtkWidget*
create_app (void)
{
  GtkWidget *app;     /* puntatore alla finestra principale */

  GtkWidget *dock;    /* contenitore rimuovibile*/
  GtkWidget *toolbar; 
  GtkWidget *tmp_toolbar_icon;

  GtkWidget *appbar;  

  GtkWidget *nuovo;
  GtkWidget *apri;
  GtkWidget *salva;
  GtkWidget *esci;

  GtkWidget *vbox1;
  GtkWidget *label1;


  /* creiamo la finestra principale
  */
  app = gnome_app_new (PACKAGE, "Ciao Gnome...");
  gtk_widget_set_usize(GTK_WIDGET(app), 400, 300);

  /* recuperiamo il puntatore al dock
  */
  dock = GNOME_APP (app)->dock;
  gtk_widget_show (dock);

  /* creiamo la barra dei menu
  */
  gnome_app_create_menus (GNOME_APP (app), menubar_uiinfo);

  /* creiamo la toolbar
  */
  toolbar = gtk_toolbar_new (GTK_ORIENTATION_HORIZONTAL, GTK_TOOLBAR_BOTH);
  gtk_widget_show (toolbar);
  
  /* inseriamo la toolbar in un nuovo dock
  */
  gnome_app_add_toolbar (GNOME_APP (app), GTK_TOOLBAR (toolbar), "toolbar",
                                GNOME_DOCK_ITEM_BEH_EXCLUSIVE,
                                GNOME_DOCK_TOP, 1, 0, 0);
  
  /* impostiamo alcune caratteristiche della toolbar
  */	
  gtk_container_set_border_width (GTK_CONTAINER (toolbar), 1);
  gtk_toolbar_set_space_size (GTK_TOOLBAR (toolbar), 16);
  gtk_toolbar_set_space_style (GTK_TOOLBAR (toolbar), GTK_TOOLBAR_SPACE_LINE);
  gtk_toolbar_set_button_relief (GTK_TOOLBAR (toolbar), GTK_RELIEF_NONE);

  /* creiamo le icone da inserire nella toolbar
  */
  tmp_toolbar_icon = gnome_stock_pixmap_widget (app, GNOME_STOCK_PIXMAP_NEW);
  nuovo = gtk_toolbar_append_element (GTK_TOOLBAR (toolbar),
				      GTK_TOOLBAR_CHILD_BUTTON,
				      NULL,
				      "Nuovo",
				      "Nuovo file", NULL,
				      tmp_toolbar_icon, 
				      on_new_file_activate, 
				      "tb");
  gtk_widget_show (nuovo);

  tmp_toolbar_icon = gnome_stock_pixmap_widget (app, GNOME_STOCK_PIXMAP_OPEN);
  apri = gtk_toolbar_append_element (GTK_TOOLBAR (toolbar),
				     GTK_TOOLBAR_CHILD_BUTTON,
				     NULL,
				     "Apri",
				     "Apri file", NULL,
				     tmp_toolbar_icon,
				     on_open_activate, 
				     "tool bar");
  gtk_widget_show (apri);

  tmp_toolbar_icon = gnome_stock_pixmap_widget (app, GNOME_STOCK_PIXMAP_SAVE);
  salva = gtk_toolbar_append_element (GTK_TOOLBAR (toolbar),
                                GTK_TOOLBAR_CHILD_BUTTON,
                                NULL,
                                "Salva",
                                "Salve file", NULL,
                                tmp_toolbar_icon, 
				      on_save_activate, 
				      "tb");
  gtk_widget_show (salva);

  gtk_toolbar_append_space (GTK_TOOLBAR (toolbar));

  tmp_toolbar_icon = gnome_stock_pixmap_widget (app, GNOME_STOCK_PIXMAP_EXIT);
  esci = gtk_toolbar_append_element (GTK_TOOLBAR (toolbar),
                                GTK_TOOLBAR_CHILD_BUTTON,
                                NULL,
                                "Esci",
                                NULL, NULL,
                                tmp_toolbar_icon, gtk_main_quit, NULL);
  gtk_widget_show (esci);

  /* creiamo una scatola, in cui inseriremo gli
     elementi grafici della nostra applicazione
  */
  vbox1 = gtk_vbox_new (FALSE, 0);
  gtk_widget_show (vbox1);
  gnome_app_set_contents (GNOME_APP (app), vbox1);

  /* la nostra applicazione grafica è tutta qui, 
     una etichetta
  */
  label1 = gtk_label_new ("Ciao Mondo");
  gtk_widget_show (label1);
  gtk_box_pack_start (GTK_BOX (vbox1), label1, TRUE, TRUE, 0);

  /* creiamo una AppBar 
     (comprende una statusbar, e una progressbar)
  */
  appbar = gnome_appbar_new (TRUE, TRUE, GNOME_PREFERENCES_NEVER);
  gnome_app_set_statusbar (GNOME_APP (app), appbar);
  gtk_widget_show (appbar);

  /* installiamo i tooltips
  */
  gnome_app_install_menu_hints (GNOME_APP (app), menubar_uiinfo);

  /* gestiamo almeno un segnale per uscire dalla
     applicazione
  */
  gtk_signal_connect (GTK_OBJECT (app), "destroy",
                      GTK_SIGNAL_FUNC (gtk_main_quit),
                      NULL);

  /* ritorniamo il puntatore alla finestra 
     principale del nostro programma
  */
  return app;
}


/* Main
*/
int
main (int argc, char *argv[])
{
  GtkWidget *app;

  /* inizializziamo le librerie di Gnome
  */
  gnome_init (PACKAGE, VERSION, argc, argv);

  app = create_app ();
  gtk_widget_show (app);

  gtk_main ();
  
  return 0;
}

Salvate il programma di prova nel file ciao_gnome.c e compilate con:

    gcc -Wall -g ciao_gnome.c -o ciao_gnome `gnome-config gnomeui --libs --cflags`

Lo script di shell gnome-config si occuperà di passare al compilatore e al linker i giusti percorsi per i file da includere e le librerie dinamiche da collegare al nostro eseguibile. Chiamate gnome-config --help per ottenere un'elenco completo delle librerie supportate dallo script.



figura 1 La nostra prima applicazione in Gnome


figura 2 La nostra prima applicazione in Gnome

GnomeApp

Come visto con GTK+, anche per poter utilizzare le librerie di Gnome è necessario prima di tutto inizializzarle. La funzione che inizializza le librerie di Gnome, che a loro volta inizializzeranno quelle di GTK+, GDK e Glib, è gnome_init(). Il prototipo della funzione è il seguente.


    int gnome_init (const char *app_id,
                      const char *app_version,
                      int argc,
                      char **argv);

app_id è un identificatore del programma che stiamo scrivendo, utilizzato internamente da Gnome, in genere è il nome stesso del programma, mentre app_version è invece il numero di versione del programma. Occorre poi fornire alla funzione argc e argv, che verranno esaminati alla ricerca di eventuali parametri passati sulla riga di comando. Inizializzate le librerie si può passare alla costruzione dell'interfaccia grafica del nostro programma.

Da main richiamiamo la nostra funzione create_app(); nella quale metteremo tutto il codice che crea la finestra grafica. La prima cosa da fare è naturalmente creare la finestra principale. Abbiamo visto nella serie su GTK+, che la finestra principale della nostra applicazione grafica si ottiene richiamando la funzione gtk_window_new. Le librerie di Gnome ci mettono a disposizione una funzione analoga e un nuovo widget per la realizzazione delle nostre applicazioni grafiche. La finestra principale dei nostri programmi si ottiene chiamando la funzione gnome_app_new();


    GtkWidget* gnome_app_new (const gchar *appname,
                                const gchar *title);

Il primo argomento da passare alla funzione è, generalmente, il nome del nostro programma e deve essere lo stesso nome che è stato passato a gnome_init(); , il secondo è il titolo che vogliamo che abbia il programma (che apparirà sulla cornice della nostra finestra), passando NULL non verrà mostrato nessun titolo. La funzione ritornerà un puntatore al widget creato.

Come detto in precedenza, utilizzando le librerie di Gnome è possibile creare applicazioni grafiche aventi tutte un aspetto coerente. Tutte le applicazioni di Gnome avranno una barra di menu, una toolbar, una statusbar, decideremo poi noi se utilizzarle o meno. La barra dei menu e la toolbar sono staccabili dalla finestra principale. Il widget che permette di ottenere oggetti fluttuanti è GnomeDock, e appartiene alla famiglia dei widget contenitori. Per una applicazione "standard", avente cioè una barra di menu e una toolbar, non è necessario creare nessun widget GnomeDoc, in quanto sarà la funzione gnome_app_new(); a creare il GnomeDoc per noi. Per utilizzare il widget è sufficiente recuperarne il puntatore e mostrarlo.

   ...
  dock = GNOME_APP (app)->dock; 
  gtk_widget_show (dock); 
   ...

A questo punto disponiamo di un widget che ci consente di staccare, riattaccare e spostare sullo schermo menu, toolbar e tutto quello che la nostra immaginazione ci suggerisce.

Costruiamo i menu


figura 3 menu bar fluttuante

Disponiamo di un GnomeDoc, ora riempiamolo, creando prima di tutto la barra dei menu. Per far ciò utilizzeremo questa funzione.


    void gnome_app_create_menus (GnomeApp *app,
                                    GnomeUIInfo *uiinfo);

Il primo argomento da passare è il puntatore alla nostra applicazione, mentre il secondo è un puntatore alla barra dei menu. In Gnome per costruire i menu, si ricorre ad una struttura GnomeUIInfo. Per ogni elemento di menu deve essere definita una struttura GnomeUIInfo. Ecco come è definita la struttura.


   typedef struct 
   {
      GnomeUIInfoType type;		
      gchar *label;			
      gchar *hint;			
      gpointer moreinfo;
      gpointer user_data;
      gpointer unused_data;
      GnomeUIPixmapType pixmap_type;	
      gconstpointer pixmap_info;     
      guint accelerator_key;		
      GdkModifierType ac_mods;
      GtkWidget *widget;		
   } GnomeUIInfo;

riempiendo i campi di questa struttura è possibile definire per ogni elemento di menu il nome da mostrare, l'icona da associare al nome, la combinazione di tasti per attivare direttamente la funzione associata, un suggerimento (tooltip), ecc.

Esaminiamo in dettaglio la struttura.

Alcuni valori assumibili da GnomeUIInfo

GnomeUIInfoType Descrizione Campo moreinfo
GNOME_APP_UI_ENDOFINFO L'ultimo elemento della struttura GnomeUIInfo Nessuno
GNOME_APP_UI_ITEM Un elemento normale, o un radio button se all'interno di un gruppo radio La funzione di callback
GNOME_APP_UI_TOGGLEITEM Un toggle/check button La funzione di callback
GNOME_APP_UI_RADIOITEMS Un gruppo di radio buttons Vettore di pulsanti radio appartenenti al gruppo
GNOME_APP_UI_SUBTREE Sottomenu struttura GnomeUIInfo descrivente il sottomenu
GNOME_APP_UI_SEPARATOR Separatore tra gli elementi di menu Nessuno

Vediamo come sono stati realizzati i menu nel programma di prova.

    ...
    static GnomeUIInfo file_menu_uiinfo[] = 
    { 
      GNOMEUIINFO_MENU_NEW_ITEM ("_Nuovo File", NULL,  
			         on_new_file_activate, "fm"), 
      GNOMEUIINFO_MENU_OPEN_ITEM (on_open_activate, "menu file"), 
      GNOMEUIINFO_MENU_SAVE_ITEM (on_save_activate, "fm"), 
      GNOMEUIINFO_MENU_SAVE_AS_ITEM (on_save_as_activate, NULL), 
      GNOMEUIINFO_SEPARATOR, 
      GNOMEUIINFO_MENU_EXIT_ITEM (gtk_main_quit, NULL), 
      GNOMEUIINFO_END 
    }; 
    
    ... 
    
    static GnomeUIInfo menubar_uiinfo[] = 
    { 
      GNOMEUIINFO_MENU_FILE_TREE (file_menu_uiinfo), 
      GNOMEUIINFO_MENU_EDIT_TREE (edit_menu_uiinfo), 
      GNOMEUIINFO_MENU_HELP_TREE (help_menu_uiinfo), 
      GNOMEUIINFO_END 
    };

La variabile passata alla funzione gnome_app_create_menus(); è menubar_uiinfo. In questa variabile, utilizzando alcune macro fornite da Gnome, sono state definite le voci che appariranno sulla barra dei menu. In questo caso la nostra barra dei menu comprenderà l'elemento "File" (GNOMEUIINFO_MENU_FILE_TREE), l'elemento "Modifica" (GNOMEUIINFO_MENU_EDIT_TREE) e l'elemento "Aiuto" (GNOMEUIINFO_MENU_HELP_TREE). Altre tra le macro disponibili sono:

A questo punto occorre definire quali elementi andranno inseriti in ognuno dei menu principali. Per farlo ricorreremo ancora alla struttura GnomeUIInfo. Esaminiamo il menu che apparirà alla pressione di "File", definito da file_menu_uiinfo[]. Tipicamente gli elementi contenuti in un menu File sono: Nuovo, Apri, Salva, Salva con nome ed esci. Come per le voci di menu presenti sulla barra, anche per gli elementi contenuti nel menu, sono disponibili alcune macro che semplificano la vita. L'elemento di menu "Nuovo file" è creato utilizzando il seguente codice:

GNOMEUIINFO_MENU_NEW_ITEM ("_Nuovo File", NULL, on_new_file_activate, "fm")

Il primo valore passato alla macro è la stringa che sarà visualizzata, in questo caso è "_Nuovo File", ma qual'è la funzione dell'underscore prima della lettera N? Durante la creazione del menu, Gnome esaminerà la stringa alla ricerca di underscore. Se trovati saranno eliminati, e la lettera seguente sarà utilizzata come scorciatoia, quindi in questo caso, per creare un nuovo file, ci sarà sufficiente premere i tasti ALT+N. Se avessimo posto l'underscore prima della lettera F ("Nuovo _File") allora avremmo potuto aprire un nuovo file utilizzando la combinazione di tasti ALT+F.

il valore successivo, in questo caso impostato a NULL è una eventuale stringa da usare come tooltip. Segue poi la funzione di callback da richiamare all'attivazione della voce del menu, e infine gli eventuali dati da passare alla funzione di callback.

Supponiamo che questa macro non fosse stata disponibile, in questo caso avremmo dovuto costruire tutto noi riempiendo la struttura GnomeUIInfo. Vediamo come dovremmo procedere se ad esempio volessimo inserire nel menu "File" tra il separatore ed "esci", un elemento di menu per esportare i dati trattati dalla nostra applicazione.

    GNOMEUIINFO_SEPARATOR, 
    {
       GNOME_APP_UI_ITEM,      /* il tipo di elemento */
       "_Export",              /* la stringa da mostrare */
       NULL,                   /* il tooltip */
       on_export_activate,     /* la funzione callback da richiamare */
       NULL,                   /* dati da passare alla funzione */
       NULL,                   /* non usato */
       GNOME_APP_PIXMAP_NONE,  /* il tipo di pixmap da usare */
       NULL,                   /* nessuna pixmap (GNOME_APP_PIXMAP_NONE) */
       GDK_e,                  /* tasto scorciatoia */
       GDK_MOD1_MASK,          /* maschera per il tasto scorciatoia */  
       NULL                    /* impostare a NULL, usato da Gnome */
    },
    GNOMEUIINFO_MENU_EXIT_ITEM (gtk_main_quit, NULL),

Nulla di difficile ;-) vero?, ma se ripetuto per decine di volte potrebbe risultare noioso, e allora, avanti con le macro!!.

Costruiamo la toolbar


figura 4 toolbar fluttuante

Per costruire la toolbar, si può procedere nello stesso modo. Si definisce una variabile di tipo GnomeUIInfo contenente tutte le voci che ci servono e si chiama poi la seguente funzione.


    void gnome_app_create_toolbar (GnomeApp *app,
                                      GnomeUIInfo *uiinfo);

Se si hanno delle esigenze particolari, o si vuole un maggior controllo allora si può procedere come nel programma di esempio.

   ...
	
  toolbar = gtk_toolbar_new (GTK_ORIENTATION_HORIZONTAL, GTK_TOOLBAR_BOTH);
  gtk_object_set_data(GTK_OBJECT (app), "toolbar", toolbar);
  gtk_widget_show (toolbar);

  gnome_app_add_toolbar (GNOME_APP (app), GTK_TOOLBAR (toolbar), "toolbar",
                         GNOME_DOCK_ITEM_BEH_EXCLUSIVE,
                         GNOME_DOCK_TOP, 1, 0, 0);

  gtk_container_set_border_width (GTK_CONTAINER (toolbar), 1);
  gtk_toolbar_set_space_size (GTK_TOOLBAR (toolbar), 20);
  gtk_toolbar_set_space_style (GTK_TOOLBAR (toolbar), GTK_TOOLBAR_SPACE_LINE);
  gtk_toolbar_set_button_relief (GTK_TOOLBAR (toolbar), GTK_RELIEF_NONE);

  tmp_toolbar_icon = gnome_stock_pixmap_widget (app, GNOME_STOCK_PIXMAP_NEW);
  
  nuovo = gtk_toolbar_append_element (GTK_TOOLBAR (toolbar),
                                      GTK_TOOLBAR_CHILD_BUTTON,
                                      NULL,
                                      "Nuovo",
                                      "Nuovo file", NULL,
                                      tmp_toolbar_icon, 
                                      on_new_file_activate, 
                                      "tb");
   gtk_widget_show (nuovo);

  
   ...

Sono state eseguite poche operazioni. E' stata creata una toolbar con determinate caratteristiche ed è stata inserita nello GnomeDock. Quindi sono stati inseriti nella toolbar una serie di elementi. Vediamo in dettaglio.

  toolbar = gtk_toolbar_new (GTK_ORIENTATION_HORIZONTAL, GTK_TOOLBAR_BOTH);
  gtk_object_set_data(GTK_OBJECT (app), "toolbar", toolbar);
  gtk_widget_show (toolbar);

    GtkWidget*  gtk_toolbar_new (GtkOrientation orientation,
                                    GtkToolbarStyle style);
                                    
    void gtk_object_set_data (GtkObject *object,
                                 const gchar *key,
                                 gpointer data);
                                 
    gpointer gtk_object_get_data (GtkObject *object,
                                     const gchar *key);

gtk_toolbar_new (); crea un oggetto toolbar e restituisce il puntatore al widget creato. La funzione accetta due parametri; il primo indica l'orientazione della toolbar e può assumere uno fra i seguenti valori:

il secondo indica lo stile della barra, cioè in che modo mostrare gli elementi contenuti, e può valere:

Con gtk_object_set_data() è possibile associare ad un oggetto una coppia chiave-dati. Nel nostro caso la chiave "toolbar" e il puntatore all'oggetto toolbar sono stati salvati nell'oggetto app. In seguito utilizzando la funzione gtk_object_get_data(); ed avendo a disposizione il puntatore all'oggetto app, sarà possibile recuperare tramite la chiave "toolbar" il puntatore alla toolbar.

   gnome_app_add_toolbar (GNOME_APP (app), GTK_TOOLBAR (toolbar), "toolbar",
                                 GNOME_DOCK_ITEM_BEH_EXCLUSIVE,
                                 GNOME_DOCK_TOP, 1, 0, 0);

    void gnome_app_add_toolbar (GnomeApp *app,
                                    GtkToolbar *toolbar,
                                    const gchar *name,
                                    GnomeDockItemBehavior behavior,
                                    GnomeDockPlacement placement,
                                    gint band_num,
                                    gint band_position,
                                    gint offset);

gnome_app_add_toolbar() crea un nuovo widget GnomeDockItem contenente la toolbar, e lo aggiunge al dock della nostra applicazione

Alla funzione bisogna passare il puntatore alla finestra principale dell'applicazione, il puntatore alla toolbar che deve essere inserita nel nuovo dock, ed un nome per l'elemento dock che conterrà la toolbar. I successivi valori sono relativi agli attributi che dovrà possedere il nuovo dock. GnomeDockItemBehavior definisce il comportamento del dock e può assumere uno dei seguenti valori:

GnomeDockPlacement definisce la posizione in cui inserire il nuovo dock, e può essere:

Gli ultimi parametri da fissare sono relativi a band_num che è il numero della "striscia" in cui inserire il nuovo dock. La striscia, il contenitore, che contiene la barra dei menu ha numero zero, il successivo contenitore, che conterrà la toolbar avrà numero 1, e cosi via. band_position indica la posizione che il dock deve avere nella striscia numero band_num. offset offset dal dock precedente. Se volessimo inserire un nuovo dock contenente un'altra toolbar subito dopo la prima, allora band_num sarebbe impostata ad 1, band_position sarebbe 1, e infine offset indicherebbe di quanto i due dock si dovrebbero distanziare.

   ...
   gtk_toolbar_set_space_size (GTK_TOOLBAR (toolbar), 20);
   gtk_toolbar_set_space_style (GTK_TOOLBAR (toolbar), GTK_TOOLBAR_SPACE_LINE);
   gtk_toolbar_set_button_relief (GTK_TOOLBAR (toolbar), GTK_RELIEF_NONE);
   ...

   void gtk_toolbar_set_space_size (GtkToolbar *toolbar,
                                        gint space_size);

   void gtk_toolbar_set_space_style (GtkToolbar *toolbar,
                                         GtkToolbarSpaceStyle space_style);

   void gtk_toolbar_set_button_relief (GtkToolbar *toolbar,
                                           GtkReliefStyle relief);

   GtkWidget* gtk_toolbar_append_element (GtkToolbar *toolbar,
                                              GtkToolbarChildType type,
                                              GtkWidget *widget,
                                              const char *text,
                                              const char *tooltip_text,
                                              const char *tooltip_private_text,
                                              GtkWidget *icon,
                                              GtkSignalFunc callback,
                                              gpointer user_data);

A questo punto disponiamo di una toolbar inserita in un dock, dobbiamo soltanto decidere che proprietà debba avere la toolbar e poi inserire le icone.

gtk_toolbar_set_space_size(); è utilizzata per definire quanti pixel devono essere grandi gli elementi separatori. Cioè, due icone sulla toolbar possono essere separati da uno spazio vuoto o da una barra verticale; con questa funzione si stabilisce quanto spazio devono occupare questi separatori. Accetta due argomenti, il puntatore alla toolbar ed un intero che indica i pixel da usare.

gtk_toolbar_set_space_style (); definisce lo stile dei separatori (spazio vuoto o barra verticale). Il primo argomento è il puntatore alla toolbar, il secondo indica lo stile e può assumere uno tra i due seguenti valori.

gtk_toolbar_set_button_relief(); definisce l'aspetto esteriore delle icone, cioè come devono apparire i bottoni sulla toolbar. Il primo argomento è ancora il puntatore alla toolbar, il secondo è lo stile del bottone e puo valere:
(nota: GTK+ Reference Manual non documenta i possibili valori che può assumere GtkReliefStyle, da prove effettuate, io non ho riscontrato differenze tra GTK_RELIEF_NORMAL e GTK_RELIEF_HALF).

  tmp_toolbar_icon = gnome_stock_pixmap_widget (app, GNOME_STOCK_PIXMAP_NEW);
  
  nuovo = gtk_toolbar_append_element (GTK_TOOLBAR (toolbar),
                                      GTK_TOOLBAR_CHILD_BUTTON,
                                      NULL,
                                      "Nuovo",
                                      "Nuovo file", NULL,
                                      tmp_toolbar_icon, 
                                      on_new_file_activate, 
                                      "tb");

    GtkWidget*  gnome_stock_pixmap_widget (GtkWidget *window,
                                              const char *icon);

gnome_stock_pixmap_widget(); Per creare un'icona ricorriamo a questa funzione, che ha come primo argomento un puntatore alla finestra principale dell'applicazione, come secondo accetta una stringa e ritorna un puntatore al widget contenente l'icona. Questa funzione è utilizzata per recuperare una immagine dal set di icone predefinite di Gnome; la stringa da passare è un identificatore dell'icona. Ecco, in un brevissimo elenco, come sono definite le icone predefinite di Gnome:

#define GNOME_STOCK_PIXMAP_NEW         "New"
#define GNOME_STOCK_PIXMAP_OPEN        "Open"
#define GNOME_STOCK_PIXMAP_CLOSE       "Close"
...
#define GNOME_STOCK_PIXMAP_UNDO        "Undo"
#define GNOME_STOCK_PIXMAP_REDO        "Redo"
...

Ok, abbiamo la toolbar, e le icone, ora creiamo il bottone che inseriremo sulla toolbar e sul quale metteremo la nostra immagine. Per far ciò ricorreremo alla funzione gtk_toolbar_append_element(); che inserisce un nuovo elemento alla fine della toolbar. Il primo parametro da passare è il puntatore alla toolbar, il secondo è un valore di tipo GtkToolbarChildType; definisce il tipo del widget che stiamo inserendo sulla toolbar e può assumere uno tra

il terzo parametro può essere impostato a NULL, il quarto è la stringa che apparirà sulla toolbar (unitamente all'icona o meno), il quinto è il tooltip che sarà mostrato nel caso in cui il puntatore del mouse stazionerà sull'elemento, il sesto può essere impostato a NULL, a meno che non vogliate sviluppare un sistema di help "context-sensitive" (una ulteriore e più approfondita descrizione del widget), il settimo è il widget che ci è stato restituito dalla precedente funzione gnome_stock_pixmap_widget e che rappresenta l'icona che vogliamo che appaia sul bottone, infine ottavo e nono parametro rappresentano la funzione di callback da richiamare alla pressione del bottone e gli eventuali dati da passare alla callback. La funzione ci ritornerà un puntatore al nuovo elemento inserito.


   GtkWidget* gtk_toolbar_prepend_element (GtkToolbar *toolbar,
                                               GtkToolbarChildType type,
                                               GtkWidget *widget,
                                               const char *text,
                                               const char *tooltip_text,
                                               const char *tooltip_private_text,
                                               GtkWidget *icon,
                                               GtkSignalFunc callback,
                                               gpointer user_data);
   GtkWidget* gtk_toolbar_insert_element (GtkToolbar *toolbar,
                                              GtkToolbarChildType type,
                                              GtkWidget *widget,
                                              const char *text,
                                              const char *tooltip_text,
                                              const char *tooltip_private_text,
                                              GtkWidget *icon,
                                              GtkSignalFunc callback,
                                              gpointer user_data,
                                              gint position);
                                              
   void gtk_toolbar_set_orientation (GtkToolbar *toolbar,
                                         GtkOrientation orientation);

   void gtk_toolbar_set_style (GtkToolbar *toolbar,
                                   GtkToolbarStyle style);

In alto sono elencate alcune tra le altre funzioni che operano sulle toolbar, gtk_toolbar_prepend_element(); e gtk_toolbar_insert_element(); come gtk_toolbar_append_element(); inseriscono un elemento sulla toolbar, ma mentre la prima di queste inserisce il widget all'inizio, la seconda lo inserisce in una generica posizione, individuata dall'intero position. La toolbar può apparire in orizzontale o in vericale, per impostare il suo orientamento ricorreremo a gtk_toolbar_set_orientation(); a cui passeremo il puntatore alla toolbar e il tipo di orientamento desiderato

Per terminare con le toolbar osserviamo quest'ultima funzione: gtk_toolbar_set_style();. Questa funzione ci permette di scegliere come devono essere mostrati gli oggetti inseriti sulla toolbar. In precedenza abbiamo creato e inserito sulla barra dei bottoni contenenti una icona e una stringa di testo. Potremmo dare la possibilità all'utente di decidere cosa visualizzare sulla barra, solo le icone, solo la stringa di testo o entrambe. Per far ciò ricorreremo a gtk_toolbar_set_style();. Accetta come primo argomento il solito puntatore alla toolbar, mentre il secondo deve avere uno tra questi valori.

GnomeAppBar

L'ultimo widget che dovrebbe essere presente di default in tutte le applicazioni grafiche di Gnome è la barra di stato. La barra di stato di Gnome si presenta divisa in due parti. Quella di sinistra è utilizzata per mostrare messaggi di aiuto, di default vengono indirizzati al quasta zona i suggerimenti associati alle voci di menu. La parte di destra è, invece, una progressbar. Per il futuro è previsto il supporto di un minibuffer simile a quello presente in Emacs. Come sappiamo, come per tutti gli altri widget, per utilizzare la GnomeAppBar occorre crearla richiamando la opportuna funzione ..._new();:

   ...		
  /* creiamo una AppBar 
     (comprende una statusbar, e una progressbar)
  */
  appbar = gnome_appbar_new (TRUE, TRUE, GNOME_PREFERENCES_NEVER);
  gnome_app_set_statusbar (GNOME_APP (app), appbar);
  gtk_widget_show (appbar);		
   ...

    GtkWidget* gnome_appbar_new (gboolean has_progress,
                                    gboolean has_status,
                                    GnomePreferencesType interactivity);   
    void gnome_appbar_set_default (GnomeAppBar *appbar,
                                      const gchar *default_status);
    void gnome_appbar_push (GnomeAppBar *appbar,
                               const gchar *status);
    void gnome_appbar_pop (GnomeAppBar *appbar); 
    void gnome_appbar_clear_stack (GnomeAppBar *appbar);  
    void gnome_appbar_set_progress (GnomeAppBar *appbar,
                                       gfloat percentage);   
    GtkProgress* gnome_appbar_get_progress (GnomeAppBar *appbar);

gnome_appbar_new(); accetta tre parametri e restituisce un puntatore al widget creato. Il primo ed il secondo argomento sono dei booleani. Se has_progress è impostato a TRUE allora sarà presente la progressbar, se è settato a FALSE, non avremo progress bar. Analogamente con has_status impostato a TRUE la appbar presenterà la status bar. L'ultimo parametro GnomePreferencesType interactivity serve a stabilire se la appbar avrà anche funzioni di minibuffer (alla EMACS) o sarà una semplice status bar, e può assumere uno tra i seguenti valori:

Per il momento tuttavia non è possibile utilizzare le caratteristiche di minibuffer; ciò dipende dal fatto che è basato sul widget GtkEntry, che non ha una grande flessibilità.

gnome_appbar_set_default(); accetta un puntatore alla appbar e una stringa. Lo scopo di questa funzione è quello di mostrare per default la stringa che abbiamo impostato. Se non bisogna mostrare nient'altro (nei momenti vuoti) viene visualizzata la nostra stringa.

La status bar è strutturata a stack, cioè verrà sempre mostrata la stringa che si trova in cima alla pila. Storicamente le funzioni che operano sugli stack sono due, push e pop, la prima inserisce un elemento sullo stack, la seconda lo estrae. Anche per la GnomeAppBar sono presenti due funzioni di questo tipo: gnome_appbar_push(); e gnome_appbar_pop();. La prima funzione vuole come primo argomento il puntatore alla appbar e come secondo la stringa da inserire sulla pila, ed ha, per l'appunto, il compito di inserire sullo stack la stringa passata, che sarà quindi visualizzata. La seconda, al contrario, accetta il puntatore alla appbar ed ha il compito di eliminare l'elemento che si trova in cima allo stack. Quindi verrà cancellata l'ultima stringa inserita e sarà mostrata la penultima stringa "pushata" ;-).

Con gnome_appbar_clear_stack (); si pulisce lo stack da tutte le stringhe presenti; se si è impostata una stringa di default, questa sarà mostrata, altrimenti vedremo la barra vuota. Il parametro che dobbiamo passare alla funzione è il puntatore alla appbar.

gnome_appbar_set_progress(); ha in ingresso il puntatore alla appbar e un gfloat, cioè un numero reale, e serve ad impostare la progress bar al valore desiderato. Si potrebbe scrivere una routine che incrementa il valore della barra mentre la nostra applicazione svolge una certa applicazione, mostrando in questo modo l'avanzamento dello stato. Tuttavia sconsiglio l'uso di questa funzione, in quanto nella documentazione si ipotizza una sua possibile "morte".

Infine gnome_appbar_get_progress(); accetta il puntatore alla appbar e restituisce un puntatore al widget GtkProgress. La progressbar potrà, quindi, essere manipolata con le funzioni messe a disposizione da GTK+. Per ritornare alla nostra ipotetica routine, per realizzarla è sufficiente recuperare il puntatore alla progressbar e poi manipolarla con le funzioni disposnibili in GTK+

Dialoghiamo con l'utente

Una delle innovazioni introdotte da Gnome e riprese dalle nuove librerie di GTK+ riguarda le finestre di dialogo. Spesso il programmatore ha la necessità di comunicare con l'utente, per fargli scegliere una certa opzione, o per comunicargli lo stato della applicazione o ancora per chiedere di confermare o meno l'esecuzione di una operazione delicata (ad esempio la cancellazione o la sovrascrittura di un file). Con GTK+ ogni programmatore avrebbe creato la propria finestra, avrebbe inserito una stringa con cui spiegava la presenza della finestra e avrebbe inserito uno o più bottoni in funzione della operazione da svolgere. Magari i più fantasiosi e meno pigri avrebbero inserito una icona a sinistra della stringa e qualche separatore tra stringa e bottoni. Ad ogni modo ogni applicazione avrebbe avuto le proprie finestre di dialogo, tutte diverse tra loro.

Gnome ci mette a disposizione alcune funzioni che creano delle finestre standard per comunicare una situazione di errore, o di pericolo, o per confermare l'avvenuta conclusione di una certa operazione.


figura 5 comunichiamo qualcosa

figura 6 segnaliamo un'anomalia


figura 7 o informiamo di un'errore


   void display_message (gchar *str)
   {
    GtkWidget *messagebox;
    GtkWidget *button;

   ...
    messagebox = gnome_message_box_new (str,
                                        GNOME_MESSAGE_BOX_INFO,
                                        GNOME_STOCK_BUTTON_OK, NULL);
   }
   ...
   
    messagebox = 
   	gnome_message_box_new ("Attenzione questa opzione non è ancora disponibile",
                                GNOME_MESSAGE_BOX_WARNING,
                                GNOME_STOCK_BUTTON_OK, NULL);                              
   ...
    gnome_about_new (PACKAGE, VERSION,
		     "Copyleft (c) 2002 Pluto Journal",
		     authors,
		     "Inseriamo qui una breve descrizione del nostro programma....",
		     NULL);
   ...

   GtkWidget* gnome_ok_dialog (const gchar *message);
   GtkWidget* gnome_error_dialog (const gchar *error);	
   GtkWidget* gnome_warning_dialog (const gchar *warning);	
   
   GtkWidget* gnome_question_dialog (const gchar *question,
                                        GnomeReplyCallback callback,
                                        gpointer data);
                                        
   GtkWidget* gnome_ok_cancel_dialog (const gchar *message,
                                         GnomeReplyCallback callback,
                                         gpointer data);
                                       
   GtkWidget* gnome_about_new (const gchar *title,
                                  const gchar *version,
                                  const gchar *copyright,
                                  const gchar **authors,
                                  const gchar *comments,
                                  const gchar *logo);

gnome_ok_dialog(); (figura 5), gnome_warning_dialog(); (figura 6), e gnome_error_dialog(); (figura 7), accettano tutti una stringa e creano rispettivamente una generica finestra di informazioni, una finestra di warning ed una di errore. Tutte ritornano il puntatore alla finestra creata.

Abbiamo visto tre finestre molto semplici, ma che certamente non sono sufficienti a coprire tutte le possibili esigenze. Con gnome_question_dialog (); è possibile creare una finestra di dialogo, dotata di due bottoni ("Si" e "No"); essa accetta come primo argomento la stringa da visualizzare, come secondo un puntatore una funzione di callback che sarà richiamata alla pressione di uno dei due bottoni, e come terzo un puntatore agli eventuali dati da passare alla funzione.

Quando si clicca su uno dei due bottoni, alla callback viene passato implicitamente anche il numero associato al bottone; se si clicca su "Si" viene passato il numero 0, se si clicca su "No" viene passato 1. In questo modo all'interno della callback è possibile indirizzare l'esecuzione del programma in funzione del bottone cliccato. Ad esmpio, pensiamo ad una finestra di dialogo che ci chieda "Sei sicuro di voler uscire?", se nella callback riceviamo lo zero, allora è stato cliccato "si" ed usciamo dal programma, se riceviamo 1 allora è stato cliccato su "no" e continuiamo l'esecuzione. La funzione ritorna un puntatore alla finestra.

gnome_ok_cancel_dialog (); ha un funzionamento analogo a gnome_question_dialog ();, la differenza è nel fatto che i bottoni associati alla finestra avranno etichette "Ok" e "Cancel", ognuno con le rispettive icone.

Per ognuna di queste funzioni ne esiste una simile, che si differenzia dalla omologa per la variante *_parented();. La funzione *_parented(); accetta gli stessi parametri della funzione omologa con in più il puntatore alla finestra che è "logicamente" genitore della finestra di dialogo.

L'ultima funzione che esamineremo in questo articolo appartiene sempre alla classe delle finestre di dialogo, ma ha una funzione speciale. E' la finestra che conterrà le note di copyright (o copyleft ;)) della nostra applicazione, gnome_about_new();.

I parametri da passare sono il titolo e il numero di versione della nostra applicazione; ancora una volta è una buona idea passare gli stessi valori dati a gnome_init(), quindi occorre passare la stringa con le note di copyright, gli autori del programma (passati come vettore di stringhe terminato da NULL), infine una stringa con una breve descrizione del programma ed eventualmente una stringa contenente il nome di un file in cui è contenuta una immagine da utilizzare come logo.

Bene, anche in questo numero del Pluto Journal abbiamo approfondito le nostre conoscenze sulle librerie di Gnome e GTK+. L'appuntamento è al prossimo numero.



L'autore

Nicola Fragale. E' iscritto a Ingegneria Informatica al Federico II di Napoli. I suoi ultimi interessi informatici sono rivolti all'XML e particolarmente a GConf. Attualmente sta scrivendo una rubrica per GTK+ e GNOME, naturalmente rilasciata sotto licenza GPL. Nel tempo libero gli piace programmare, leggere (Stephen King l'autore preferito), stare con la sua ragazza e sentire i suoi amici.



<- PW: Intro - Archivio Generale - Copertina - PW: Bash ->