Avanti Indietro Indice

10. Il Widget Menù (Menu Widgets)

Ci sono due modi per creare dei menù, quello facile e quello difficile. Ognuno è più adatto per certe circostanze, ma di solito si può usare il modo semplice, cioé menu_factory (la ``fabbrica dei menù''). Il modo ``difficile'' è di crearsi tutti i menù usando direttamente le chiamate. Quello semplice è di usare le chiamate di tipo gtk_menu_factory. Anche se è un modo molto più semplice, ci sono svantaggi e vantaggi per ciascuno dei due approcci.

La menu_factory è molto più semplice da usare e per aggiungere dei nuovi menù, anche se scriversi un po' di funzioni per creare dei menù con il metodo manuale può dare risultati molto migliori dal punto di vista dell'usabilità. Con la menufactory, non è possibile mettere immagini o caratteri '/' nei menù.

10.1 Creazione Manuale di Menù

Seguendo la tradizionale arte dell'insegnamento, partiamo dal modo difficile. :)

I widget che hanno a che fare con la creazione di una barra di menù e di sottomenù sono tre:

La cosa viene un po' complicata dal fatto che i widget elemento di menù vngono usati per

due scopi diversi. Essi sono sia i widget che vengono impacchettati nei menù, che

quelli che vengono impacchettati nella barra dei menù che, quando selezonati, attivano i menù.

Diamo un'occhiata alle funzioni usate per creare i menù e le barre di menù. Con questa prima funzione si crea un nuova barra di menù:

GtkWidget *gtk_menu_bar_new(void);

Questa funzione crea una nuova barra di menù. Per impacchettarla in una finestra o si usa la funzione gtk_container_add, oppure, per impacchettarla in una scatola, le funzioni box_pack - come con i bottoni.

GtkWidget *gtk_menu_new();

Questa funzione restituisce un puntatore ad un nuovo menù, non viene mai realmente mostrato (con gtk_widget_show), serve solo per contenere gli elementi del menù. Spero che il tutto risulti più chiaro quando daremo un'occhiata all'esempio più sotto.

Le prossime due chiamate sono usate per creare degli elementi che poi vengono impacchettati nei menù e nelle barre dei menù..

GtkWidget *gtk_menu_item_new();

e

GtkWidget *gtk_menu_item_new_with_label(const char *label);

Queste chiamate sono usate per creare gli elementi di menù che devono poi essere mostrati. Ricordate la differenza che esiste fra un ``menù'' come quelli creati con gtk_menu_new e un ``elemento di menù'' (menu item) come quelli creati con la funzione gtk_menu_item_new. L'elemento di menù sarà un bottone vero e proprio con una azione associata, mentre un menù è solo un contenitore che li raccoglie. Le funzioni gtk_menu_new_with_label e gtk_menu_new sono esattamente come vi aspettereste che siano dopo

aver conosciuto i bottoni. Una crea un nuovo elemento di menù con un'etichetta già impacchettata,

mentre l'altra crea un elemento di menù vuoto.

Una volta che si \ creato un elemento di menù, è necessario piazzarlo su di un menù.

Per fare ciò si usa la funzione gtk_menu_append. Per determinare quando l'utente ha selezionato un elemento, abbiamo bisogno di connettere il segnale activate nel solito modo.

Quindi, se volessimo creare un normale menù File, con le opzioni Open, Save e Quit, il codice avrebbe più o meno il seguente aspetto:

file_menu = gtk_menu_new();    /* Non e' necessario mostrare i menu' */

/* Creiamo gli elementi del menu' */
open_item = gtk_menu_item_new_with_label("Open");
save_item = gtk_menu_item_new_with_label("Save");
quit_item = gtk_menu_item_new_with_label("Quit");

/* Aggiungiamoli al menu' */
gtk_menu_append( GTK_MENU(file_menu), open_item);
gtk_menu_append( GTK_MENU(file_menu), save_item);
gtk_menu_append( GTK_MENU(file_menu), quit_item);


/* Colleghiamo le funzioni di callback al segnale activate */
gtk_signal_connect_object( GTK_OBJECT(open_items), "activate",
                           GTK_SIGNAL_FUNC(menuitem_response), (gpointer) "file.open");
gtk_signal_connect_object( GTK_OBJECT(save_items), "activate",
                           GTK_SIGNAL_FUNC(menuitem_response), (gpointer) "file.save");

/* Possiamo collegare l'elemento Quit alla nostra funzione di uscita */
gtk_signal_connect_object( GTK_OBJECT(quit_items), "activate",
                           GTK_SIGNAL_FUNC(destroy), (gpointer) "file.quit");

/* Abbiamo bisogno di mostrare gli elementi di menu' */
gtk_widget_show( open_item );
gtk_widget_show( save_item );
gtk_widget_show( quit_item );
 

A questo punto abbiamo il nostro menù Adesso abbiamo bisogno di creare una barra dei menù

e un elemento di menù per File, a cui aggiungeremo il nostro menù. Il codice è questo:

menu_bar = gtk_menu_bar_new();
gtk_container_add( GTK_CONTAINER(window), menu_bar);
gtk_widget_show( menu_bar );

file_item = gtk_menu_item_new_with_label("File");
gtk_widget_show(file_item);

Ora dobbiamo associare il menù con file_item. Lo si può fare con la funzione

void gtk_menu_item_set_submenu( GtkMenuItem *menu_item, GtkWidget *submenu);

Quindi, il nostro esempio continuerebbe con

gtk_menu_item_set_submenu( GTK_MENU_ITEM(file_item), file_menu);

Ciò che manca a questo punto è di collegare il menù alla barra, cosa che si può ottenere tramite la funzione

void gtk_menu_bar_append( GtkMenuBar *menu_bar, GtkWidget *menu_item);

che nel nostro caso è:

gtk_menu_bar_append( GTK_MENU_BAR(menu_bar), file_item );

Se volessimo il menù giustificato a dstra, come sono spesso i menù di aiuto, potremm

usare la seguente funzioe (di nuovo su file_item in questo esempio) prima di fare il collegamento alla barra.

void gtk_menu_item_right_justify (GtkMenuItem *menu_item);
Ecco un riassunto dei passi necessari per creare una barra con i relativi menù collegati:

Creare un menù a comparsa è più o meno la stessa cosa. La differenza è che il il menù non viene attivato ``automaticamente'' da una barra, bensì per esempio con la chiamata espicita alla funzione gtk_menu_popup() da parte di un evento di pressione di un pulsante. Seguite questi passaggi:

10.2 Esempio di Menù Manuale

Per la teoria dovrebbe essere abbastanza. Diamo un'occhiata ad un esempio che ci aiuti a chiarire le cose.

/* menu.c */
#include <gtk/gtk.h>

static gint button_press (GtkWidget *, GdkEvent *);
static void menuitem_response (gchar *);

int main (int argc, char *argv[])
{

    GtkWidget *window;
    GtkWidget *menu;
    GtkWidget *menu_bar;
    GtkWidget *root_menu;
    GtkWidget *menu_items;
    GtkWidget *vbox;
    GtkWidget *button;
    char buf[128];
    int i;

    gtk_init (&argc, &argv);

    /* crea una nuova finestra */
    window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
    gtk_widget_set_usize( GTK_WIDGET (window), 200, 100);

    gtk_window_set_title(GTK_WINDOW (window), "GTK Menu Test");
    gtk_signal_connect(GTK_OBJECT (window), "delete_event",
                       (GtkSignalFunc) gtk_main_quit, NULL);

    /* Inizializziamo il menù, e ricordate: mai applicare
     * gtk_show_widget() al widget menù!!
     * Questo è il menù che contiene gli elementi, quello che
     * spunta quando si fa click sul "Menù radice" nell'applicazione */
    menu = gtk_menu_new();

    /* Ora creiamo un ciclo che crea tre elementi di menu per "test-menu".
     * Notete la chiamata a gtk_menu_append. In questo punto aggiungiamo una
     * lista di elementi al nostro menù. Normalmente, dovremmo poi catturare
     * il segnale di attivazione per ognuno degli elementi del menu, e creare
     * una funzione di ritorno per ciascuno di essi, ma qui non li mettiamo per
     * brevità. */

    for(i = 0; i < 3; i++)
        {
            /* Copia i nomi in buf. */
            sprintf(buf, "Test-undermenu - %d", i);

            /* Crea un nuovo elemento di menù con un nome... */
            menu_items = gtk_menu_item_new_with_label(buf);

            /* ...e aggiungilo al menù. */
            gtk_menu_append(GTK_MENU (menu), menu_items);

            /* Fa qualcosa di interessante quando si seleziona l'elemento */
            gtk_signal_connect_object(GTK_OBJECT(menu_items), "activate",
                GTK_SIGNAL_FUNC(menuitem_response), (gpointer) g_strdup(buf));

            /* Mostra il widget */
            gtk_widget_show(menu_items);
        }

    /* Questo è il menù radice, e l'etichetta sarà il nome del menù che
     * verrà mostrato sulla barra dei menù. Non ci sarà alcun gestore di
     * segnale collegato, dal momento che non fa altro che mostrare il resto
     * del menù quando viene premuto. */
    root_menu = gtk_menu_item_new_with_label("Root Menu");

    gtk_widget_show(root_menu);




    /* Ora specifichiamo che vogliamo che il menù che abbiamo appena creato
     * sia il menù radice *//
    gtk_menu_item_set_submenu(GTK_MENU_ITEM (root_menu), menu);

    /* Una vbox in cui mettere un menù ed un bottone: */
    vbox = gtk_vbox_new(FALSE, 0);
    gtk_container_add(GTK_CONTAINER(window), vbox);
    gtk_widget_show(vbox);

    /* Crea una barra dei menù per metterci i menù e l'aggiunge alla finestra principale */
    menu_bar = gtk_menu_bar_new();
    gtk_box_pack_start(GTK_BOX(vbox), menu_bar, FALSE, FALSE, 2);
    gtk_widget_show(menu_bar);

    /* Crea un bottone a cui collegare un menù */
    button = gtk_button_new_with_label("press me");
    gtk_signal_connect_object(GTK_OBJECT(button), "event",
        GTK_SIGNAL_FUNC (button_press), GTK_OBJECT(menu));
    gtk_box_pack_end(GTK_BOX(vbox), button, TRUE, TRUE, 2);
    gtk_widget_show(button);

    /* E finalmente attacchiamo l'elemento di menù alla barra dei menù -- questo
     * è l'elemento di menù "radice" di cui parlavo */
    gtk_menu_bar_append(GTK_MENU_BAR (menu_bar), root_menu);

    /* La finestra va mostrata sempre come ultimo passo in modo che sia già
     * completa di tutti i suoi elementi. */
    gtk_widget_show(window);

    gtk_main ();

    return 0;
}



/* Risponde alla pressione di un bottone impostando un menù che
 * viene passato come widget.
 * Notate che l'argomento "widget" si riferisce al menù impostato
 * e NON al bottone premuto.
 */

static gint button_press (GtkWidget *widget, GdkEvent *event)
{

    if (event->type == GDK_BUTTON_PRESS) {
        GdkEventButton *bevent = (GdkEventButton *) event; 
        gtk_menu_popup (GTK_MENU(widget), NULL, NULL, NULL, NULL,
                        bevent->button, bevent->time);
        /* Riferisce al codice chiamante che abbiamo trattato l'evento;
         * la faccenda finisce qui. */
        return TRUE;
    }

    /* Riferisce al codice chiamante che abbiamo trattato l'evento; passa avanti. */
    return FALSE;
}


/* Stampa una stringa quando viene selezionato un elemento di menù */

static void menuitem_response (gchar *string)
{
    printf("%s\n", string);
}

Si può anche fare in modo che un elemento di menù sia insensibile e, usando una tabella di acelleratori, collegare dei tasti a delle funzioni di menù.

10.3 Usare GtkMenuFactory

Ora che vi abbiamo mostrato il modo difficile, ecco invece come si fa usando le chiamate di gtk_menu_factory.

10.4 Esempio di Menu Factory

Ecco un esempio di utilizzo della ``Fabbrica'' di Menù di GTK (Menu Factory). Questo è il primo file, menufactoy.h. Teniemo dei file menufactory.c e main.c separati a causa delle variabili globali usate nel file menufactory.c.


/* menufactory.h */

#ifndef __MENUFACTORY_H__
#define __MENUFACTORY_H__
 
#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */

void get_main_menu (GtkWidget **menubar, GtkAcceleratorTable **table);
void menus_create(GtkMenuEntry *entries, int nmenu_entries);

#ifdef __cplusplus
}
#endif /* __cplusplus */

#endif /* __MENUFACTORY_H__ */

Ed ecco il file menufactory.c.

/* menufactory.c */
#include <gtk/gtk.h>
#include <strings.h>

#include "mfmain.h"

static void menus_remove_accel(GtkWidget * widget, gchar * signal_name, gchar * path);
static gint menus_install_accel(GtkWidget * widget, gchar * signal_name, gchar key, gchar modifiers, gchar * path);
void menus_init(void);
void menus_create(GtkMenuEntry * entries, int nmenu_entries);

/* Questa è la struttuta GtkMenuEntry, che viene usata per creare dei nuovi
 * menù. Il primo membro à la stringa di definizione del menù. Il secondo
 * è il tasto acceleratore predefinito, usato per accedere a questa funzione
 * con la tastiera. Il terzo è la funzione di ritorno che viene chiamata
 * quando si seleziona con la tastiera o il mouse questo elemento di menù.
 * L'ultimo membro costituisce il dato che viene passato alla funzione di
 * ritorno. */

static GtkMenuEntry menu_items[] =
{
        {"<Main>/File/New", "<control>N", NULL, NULL},
        {"<Main>/File/Open", "<control>O", NULL, NULL},
        {"<Main>/File/Save", "<control>S", NULL, NULL},
        {"<Main>/File/Save as", NULL, NULL, NULL},
        {"<Main>/File/<separator>", NULL, NULL, NULL},
        {"<Main>/File/Quit", "<control>Q", file_quit_cmd_callback, "OK, I'll quit"},
        {"<Main>/Options/Test", NULL, NULL, NULL}
};

/* calcola il numero di menu_item */
static int nmenu_items = sizeof(menu_items) / sizeof(menu_items[0]);

static int initialize = TRUE;
static GtkMenuFactory *factory = NULL;
static GtkMenuFactory *subfactory[1];
static GHashTable *entry_ht = NULL;

void get_main_menu(GtkWidget ** menubar, GtkAcceleratorTable ** table)
{
    if (initialize)
            menus_init();
    
    if (menubar)
            *menubar = subfactory[0]->widget;
    if (table)
            *table = subfactory[0]->table;
}

void menus_init(void)
{
    if (initialize) {
        initialize = FALSE;
        
        factory = gtk_menu_factory_new(GTK_MENU_FACTORY_MENU_BAR);
        subfactory[0] = gtk_menu_factory_new(GTK_MENU_FACTORY_MENU_BAR);
        
        gtk_menu_factory_add_subfactory(factory, subfactory[0], "<Main>");
        menus_create(menu_items, nmenu_items);
    }
}

void menus_create(GtkMenuEntry * entries, int nmenu_entries)
{
    char *accelerator;
    int i;
    
    if (initialize)
            menus_init();
    
    if (entry_ht)
            for (i = 0; i < nmenu_entries; i++) {
                accelerator = g_hash_table_lookup(entry_ht, entries[i].path);
                if (accelerator) {
                    if (accelerator[0] == '\0')
                            entries[i].accelerator = NULL;
                    else
                            entries[i].accelerator = accelerator;
                }
            }
    gtk_menu_factory_add_entries(factory, entries, nmenu_entries);
    
    for (i = 0; i < nmenu_entries; i++)
            if (entries[i].widget) {
                gtk_signal_connect(GTK_OBJECT(entries[i].widget), "install_accelerator",
                                   (GtkSignalFunc) menus_install_accel,
                                   entries[i].path);
                gtk_signal_connect(GTK_OBJECT(entries[i].widget), "remove_accelerator",
                                   (GtkSignalFunc) menus_remove_accel,
                                   entries[i].path);
            }
}

static gint menus_install_accel(GtkWidget * widget, gchar * signal_name, gchar key, gchar modifiers, gchar * path)
{
    char accel[64];
    char *t1, t2[2];
    
    accel[0] = '\0';
    if (modifiers & GDK_CONTROL_MASK)
            strcat(accel, "<control>");
    if (modifiers & GDK_SHIFT_MASK)
            strcat(accel, "<shift>");
    if (modifiers & GDK_MOD1_MASK)
            strcat(accel, "<alt>");
    
    t2[0] = key;
    t2[1] = '\0';
    strcat(accel, t2);
    
    if (entry_ht) {
        t1 = g_hash_table_lookup(entry_ht, path);
        g_free(t1);
    } else
            entry_ht = g_hash_table_new(g_str_hash, g_str_equal);
    
    g_hash_table_insert(entry_ht, path, g_strdup(accel));
    
    return TRUE;
}

static void menus_remove_accel(GtkWidget * widget, gchar * signal_name, gchar * path)
{
    char *t;
    
    if (entry_ht) {
        t = g_hash_table_lookup(entry_ht, path);
        g_free(t);
        
        g_hash_table_insert(entry_ht, path, g_strdup(""));
    }
}

void menus_set_sensitive(char *path, int sensitive)
{
    GtkMenuPath *menu_path;
    
    if (initialize)
            menus_init();
    
    menu_path = gtk_menu_factory_find(factory, path);
    if (menu_path)
            gtk_widget_set_sensitive(menu_path->widget, sensitive);
    else
            g_warning("Impossibile assegnare sensibilità a menù inesistente: %s", path);
}

Ed ecco mfmain.h

/* mfmain.h */



#ifndef __MFMAIN_H__
#define __MFMAIN_H__

#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */

void file_quit_cmd_callback(GtkWidget *widget, gpointer data);

#ifdef __cplusplus
}
#endif /* __cplusplus */

#endif /* __MFMAIN_H__ */

E mfmain.c

/* mfmain.c */



#include <gtk/gtk.h>

#include "mfmain.h"
#include "menufactory.h"


int main(int argc, char *argv[])
{
    GtkWidget *window;
    GtkWidget *main_vbox;
    GtkWidget *menubar;
    
    GtkAcceleratorTable *accel;
    
    gtk_init(&argc, &argv);
    
    window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
    gtk_signal_connect(GTK_OBJECT(window), "destroy", 
                       GTK_SIGNAL_FUNC(file_quit_cmd_callback), 
                       "WM destroy");
    gtk_window_set_title(GTK_WINDOW(window), "Menu Factory");
    gtk_widget_set_usize(GTK_WIDGET(window), 300, 200);
    
    main_vbox = gtk_vbox_new(FALSE, 1);
    gtk_container_border_width(GTK_CONTAINER(main_vbox), 1);
    gtk_container_add(GTK_CONTAINER(window), main_vbox);
    gtk_widget_show(main_vbox);
    
    get_main_menu(&menubar, &accel);
    gtk_window_add_accelerator_table(GTK_WINDOW(window), accel);
    gtk_box_pack_start(GTK_BOX(main_vbox), menubar, FALSE, TRUE, 0);
    gtk_widget_show(menubar);
    
    gtk_widget_show(window);
    gtk_main();
    
    return(0);
}

/* Questo è per mostrare come si usano le funzioni di ritorno quando 
 * si utilizza la MenuFactory. Spesso, si mettono tutte le funzioni di
 * callback in un file separato, e le si fanno chiamare le funzioni
 * appropriate da lì. Così le cose sono più organizzate. */
void file_quit_cmd_callback (GtkWidget *widget, gpointer data)
{
    g_print ("%s\n", (char *) data);
    gtk_exit(0);
}

Ed infine un bel makefile per semplificare la compilazione.


# Makefile.mf


CC      = gcc
PROF    = -g
C_FLAGS =  -Wall $(PROF) -L/usr/local/include -DDEBUG
L_FLAGS =  $(PROF) -L/usr/X11R6/lib -L/usr/local/lib 
L_POSTFLAGS = -lgtk -lgdk -lglib -lXext -lX11 -lm
PROGNAME = menufactory

O_FILES = menufactory.o mfmain.o

$(PROGNAME): $(O_FILES)
        rm -f $(PROGNAME)
        $(CC) $(L_FLAGS) -o $(PROGNAME) $(O_FILES) $(L_POSTFLAGS)

.c.o: 
        $(CC) -c $(C_FLAGS) $<

clean: 
        rm -f core *.o $(PROGNAME) nohup.out
distclean: clean 
        rm -f *~

Per il momento, accontentatevi di questo esempio. Più avanti aggiungeremo una spiegazione ed un bel po' di commenti.


Avanti Indietro Indice