Página siguiente Página anterior Índice general

14. El widget menú

Hay dos formas de crear menús, la fácil, y la difícil. Ambas tienen su utilidad, aunque lo más probable es que normalmente utilice la menufactory (la forma fácil). La forma ``difícil'' consiste en crear todos los menús utilizando las llamadas directamente. La forma fácil consiste en utilizar las llamadas de gtk_menu_factory. Es mucho más fácil, pero aun así cada aproximación tiene sus ventajas y sus inconvenientes.

La menufactory es mucho más fácil de utilizar, y tambíen es más fácil añadir nuevos menús, aunque a larga, escribiendo unas cuántas funciones de recubrimiento para crear menús utilizando el método manual puede acabar siendo más útil. Con la menufactory, no es posible añadir imágenes o el carácter `/' a los menús.

14.1 Creación manual de menús

Siguiendo la auténtica tradición de la enseñanza, vamos a enseñarle primero la forma difícil. :)

Se utilizan tres widgets para hacer una barra de menús y submenús:

Todo esto se complica ligeramente por el hecho de que los widgets de los elementos del menú se utilizan para dos cosas diferentes. Están los widgets que se empaquetan en el menú, y los que se empaquetan en una barra de menús, que cuando se selecciona, activa el menú.

Vamos a ver las funciones que se utilizan para crear menús y barras de menús. ésta primera función se utiliza para crear una barra de menús.

GtkWidget *gtk_menu_bar_new( void );

Como el propio nombre indica, esta función crea una nueva barra de menús. Utilice gtk_container_add para empaquetarla en una ventana, o las funciones box_pack para empaquetarla en una caja - exactamente igual que si fuesen botones.

GtkWidget *gtk_menu_new( void );

Esta función devuelve un puntero a un nuevo menú, que no se debe mostrar nunca (no hace falta utilizar gtk_widget_show), es sólo un contenedor para los elementos del menú. Espero que todo esto se aclare un poco cuando vea en el ejemplo que hay más abajo.

Las siguientes dos llamadas se utilizan para crear elementos de menú que se empaquetarán en el menú (y en la barra de menú).

GtkWidget *gtk_menu_item_new( void );

y

GtkWidget *gtk_menu_item_new_with_label( const char *label );

Estas llamadas se utilizan para crear los elementos del menú que van a mostrarse. Recuerde que hay que distinguir entre un ``menú'' creado con gtk_menu_new y un ``elemento del menú'' creado con las funciones gtk_menu_item_new. El elemento de menú será un botón con una acción asociada, y un menú será un contenedor con los elementos del menú.

Las funciones gtk_menu_new_with_label y gtk_menu_new son sólo lo que espera que sean después de leer lo de los botones. Una crea un nuevo elemento del menú con una etiqueta ya dentro, y la otra crea un elemento del menú en blanco.

Una vez ha creado un elemento del menú tiene que ponerlo en un menú. Esto se hace utilizando la función gtk_menu_append. Para capturar el momento en el que el elemento se selecciona por el usuario, necesitamos conectar con la señal activate de la forma usual. Por tanto, si quiere crear un menú estándar File, con las opciones Open, Save y Quit el código debería ser algo como

file_menu = gtk_menu_new();    /* No hay que mostrar menús */

/* Crear los elementos del menú */
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");

/* Añadirlos al menú */
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);

/* Enlazar las función de llamada a la señal "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");

/* Podemos enlazar el elemento de menú Quit con nuestra función de
 * salida */
gtk_signal_connect_object( GTK_OBJECT(quit_items), "activate",
                           GTK_SIGNAL_FUNC(destroy), (gpointer) "file.quit");

/* Tenemos que mostrar los elementos del menú */We do need to show menu items */
gtk_widget_show( open_item );
gtk_widget_show( save_item );
gtk_widget_show( quit_item );

En este momento tendremos nuestro menú. Ahora necesitamos crear una barra de menús y un elemento de menú para el elemento File, que vamos a añadir a nuestro menú. El código es el siguiente

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

Ahora necesitamos asociar el menú con file_item. Esto se hace con la función

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

Por lo que nuestro ejemplo continua con

gtk_menu_item_set_submenu( GTK_MENU_ITEM(file_item), file_menu );

Todo lo que queda por hacer es añadir el menú a la barra de menús, que se hace mediante la función

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

que en nuestro caso habrá que utilizar así:

gtk_menu_bar_append( GTK_MENU_BAR (menu_bar), file_item );

Si queremos que el menú esté alineado a la derecha en la barra de menús, como suele estar la opción de ayuda, podemos utilizar la función siguiente (otra vez en file_item en el ejemplo actual) antes de enlazarla en la barra de menú.

void gtk_menu_item_right_justify( GtkMenuItem *menu_item );

Aquí hay un resumen de los pasos que son necesarios para crear una barra de menús con los menús correspondientes ya enlazados:

Para hacer un menú desplegable hay que seguir prácticamente los mismos pasos. La única diferencia es que el menú no estará conectado `automáticamente' a una barra de menú, sino que para que aparezca deberá llamarse explícitamente a la función gtk_menu_popup() utilizando, por ejemplo, un evento de pulsación de botón. Siga los pasos siguientes:

14.2 Ejemplo de la creación manual de un menú

Esto debería funcionar. Échele un vistazo al ejemplo para aclarar los conceptos.

/* principio del ejemplo menu 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);

    /* crear una nueva ventana */
    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);

    /* Inicializar el widget-menu, y recuerde -- ˇˇNunca haga
     * gtk_show_widget() con el widget menu!!
     * Éste es el menú que contiene todos los elementos del menú, el
     * que se desplegará cuando pulse en el "Root Menu" en la
     * aplicación
     */
    menu = gtk_menu_new();

    /* Ahora hacemos un pequeño bucle que crea tres elementos de menú
     * para "test-menu". Recuerde llamar a gtk_menu_append. Aquí
     * estamos añadiendo una lista de elementos de menú a nuestro
     * menú. Normalmente tendríamos que cazar aquí la señal "clicked"
     * de cada uno de los elementos del menú y le deberíamos dar una
     * función de llamada a cada uno, pero lo vamos a omitimos para
     * ahorrar espacio. */

    for(i = 0; i < 3; i++)
        {
            /* Copia los nombres al búfer. */
            sprintf(buf, "Test-undermenu - %d", i);

            /* Crea un nuevo elemento de menú con un nombre... */
            menu_items = gtk_menu_item_new_with_label(buf);

            /* ...y lo añade al menú. */
            gtk_menu_append(GTK_MENU (menu), menu_items);

            /* Hace algo interesante cuando se selecciona el menuitem */
            gtk_signal_connect_object(GTK_OBJECT(menu_items), "activate",
                GTK_SIGNAL_FUNC(menuitem_response), (gpointer) g_strdup(buf));

            /* Muestra el widget */
            gtk_widget_show(menu_items);
        }

    /* Ésta es el menú raíz, y será la etiqueta mostrada en la
     * barra de menús. No habrá ningún manejador de señal conectado, ya que
     * lo único que hace es desplegar el resto del menú. */
    root_menu = gtk_menu_item_new_with_label("Root Menu");

    gtk_widget_show(root_menu);

    /* Ahora especificamos que queremos que el recien creado "menu"
     * sea el menú para el "root menu" */
    gtk_menu_item_set_submenu(GTK_MENU_ITEM (root_menu), menu);

    /* Un vbox para poner dentro un menú y un botón */
    vbox = gtk_vbox_new(FALSE, 0);
    gtk_container_add(GTK_CONTAINER(window), vbox);
    gtk_widget_show(vbox);

    /* Crear una barra de menú para que contenga al menú y la añadamos
     * a nuestra ventana principal */
    menu_bar = gtk_menu_bar_new();
    gtk_box_pack_start(GTK_BOX(vbox), menu_bar, FALSE, FALSE, 2);
    gtk_widget_show(menu_bar);

    /* Crear un botón al que atar los menús como un popup */
    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);

    /* Y finalmente añadimos el elemento de menú y la barra de menú --
     * éste es el elemento de menú "raíz" sobre el que he estado
     * delirando =) */
    gtk_menu_bar_append(GTK_MENU_BAR (menu_bar), root_menu);

    /* siempre mostramos la ventana como último paso para que todo se
     * pongo en pantalla a la vez. */
    gtk_widget_show(window);

    gtk_main ();

    return 0;
}

/* Responde a una pulsación del botón enviando un menú como un widget
 * Recuerde que el argumento "widget" es el menú que se está enviando,
 * NO el botón que se ha pulsado.
 */

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);
        /* Le dice al que llamó a la rutina que hemos manejado el
         * evento; la historia termina aquí. */
        return TRUE;
    }

    /* Le dice al que llamó a la rutina que no hemos manejado el
     * evento. */
    return FALSE;
}


/* Imprime una cadena cuando se selecciona un elemento del menú */

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

También puede hacer que un elemento del menú sea insensible y, utilizando una tabla de teclas aceleradoras, conectar las teclas con las funciones del menú.

14.3 Utilizando GtkMenuFactory

Ahora que le hemos enseñado la forma difícil, le mostraremos como utilizar las llamadas gtk_menu_factory.

14.4 Ejemplo de la fábrica de menús

Aquí hay un ejemplo de cómo utilizar la fábrica de menús GTK. Éste es el primer fichero, menufactory.h. Mantendremos un menufactory.c y mfmain.c separados debido a las variables globales utilizadas en el fichero menufactory.c.

/* principio del ejemplo menu menufactory.h */

#ifndef __MENUFACTORY_H__
#define __MENUFACTORY_H__

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

void get_main_menu (GtkWidget *, GtkWidget **menubar);

#ifdef __cplusplus
}
#endif /* __cplusplus */

#endif /* __MENUFACTORY_H__ */

/* fin del ejemplo */

Y aquí está el fichero menufactory.c.

/* principio del ejemplo menu menufactory.c */

#include <gtk/gtk.h>
#include <strings.h>

#include "mfmain.h"

static void print_hello(GtkWidget *widget, gpointer data);


/* ésta es la estructura GtkMenuEntry utilizada para crear nuevo
 * menús. El primer miembro es la cadena de definición de menús. El
 * segundo, la tecla utilizada para acceder a este menú por el
 * teclado. El tercero es la función de llamada utilizada cuando se
 * seleccione este elemento de menú (mediante el teclado o mediante el
 * ratón.) El último miembro es el dato que se debe pasar a nuestra
 * función de llamada.
 */

static GtkMenuEntry menu_items[] =
{
    {"<Main>/File/New", "<control>N", print_hello, NULL},
    {"<Main>/File/Open", "<control>O", print_hello, NULL},
    {"<Main>/File/Save", "<control>S", print_hello, 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}
};


static void
print_hello(GtkWidget *widget, gpointer data)
{
    printf("hello!\n");
}

void get_main_menu(GtkWidget *window, GtkWidget ** menubar)
{
    int nmenu_items = sizeof(menu_items) / sizeof(menu_items[0]);
    GtkMenuFactory *factory;
    GtkMenuFactory *subfactory;

    factory = gtk_menu_factory_new(GTK_MENU_FACTORY_MENU_BAR);
    subfactory = gtk_menu_factory_new(GTK_MENU_FACTORY_MENU_BAR);

    gtk_menu_factory_add_subfactory(factory, subfactory, "<Main>");
    gtk_menu_factory_add_entries(factory, menu_items, nmenu_items);
    gtk_window_add_accelerator_table(GTK_WINDOW(window), subfactory->table);
    
    if (menubar)
        *menubar = subfactory->widget;
}

/* fin del ejemplo */

Y aquí el mfmain.h

/* principio del ejemplo menu 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__ */

/* fin del ejemplo */

Y mfmain.c

/* principio del ejemplo menu 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;
    
    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(window, &menubar);
    gtk_box_pack_start(GTK_BOX(main_vbox), menubar, FALSE, TRUE, 0);
    gtk_widget_show(menubar);
    
    gtk_widget_show(window);
    gtk_main();
    
    return(0);
}

/* Esta función es sólo para demostrar como funcionan las funciones de
 * llamada cuando se utiliza la menufactory. Normalmente, la gente pone
 * todas las funciones de llamada de los menús en un fichero diferente, y
 * hace referencia a estas funciones desde aquí. Ayuda a mantenerlo todo
 * más organizado. */
void file_quit_cmd_callback (GtkWidget *widget, gpointer data)
{
    g_print ("%s\n", (char *) data);
    gtk_exit(0);
}

/* fin del ejemplo */

Y un makefile para que sea fácil de compilar.

# 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 *~

Por ahora, sólo está este ejemplo. Ya llegará una explicación del mismo y más comentarios.


Página siguiente Página anterior Índice general