Página siguiente Página anterior Índice general

13. 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_item_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 itemfactory, no es posible añadir imágenes o el carácter `/' a los menús.

13.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 *etiqueta );

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(ventana), 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:

13.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 *ventana;
    GtkWidget *menu;
    GtkWidget *menu_bar;
    GtkWidget *root_menu;
    GtkWidget *menu_items;
    GtkWidget *vbox;
    GtkWidget *boton;
    char buf[128];
    int i;

    gtk_init (&argc, &argv);

    /* crear una nueva ventana */
    ventana = gtk_window_new(GTK_WINDOW_TOPLEVEL);
    gtk_widget_set_usize( GTK_WIDGET (ventana), 200, 100);
    gtk_window_set_title(GTK_WINDOW (ventana), "GTK Menu Test");
    gtk_signal_connect(GTK_OBJECT (ventana), "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(ventana), 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 */
    boton = gtk_button_new_with_label("press me");
    gtk_signal_connect_object(GTK_OBJECT(boton), "event",
        GTK_SIGNAL_FUNC (button_press), GTK_OBJECT(menu));
    gtk_box_pack_end(GTK_BOX(vbox), boton, TRUE, TRUE, 2);
    gtk_widget_show(boton);

    /* 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(ventana);

    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ú.

13.3 Utilizando GtkItemFactory

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

13.4 Ejemplo de la fábrica de elementos

Aquí hay un ejemplo de cómo utilizar la fábrica de elementos GTK.

/* principio del ejemplo menu itemfactory.h */

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

/* La obligatoria función de llamada */
static void print_hello( GtkWidget *w,
                         gpointer   data )
{
  g_message ("Hello, World!\n");
}

/* Esta es la estructura GtkItemFactoryEntry utilizada para crear
   nuevos menúes.

   This is the GtkItemFactoryEntry structure used to generate new menus.
   Elemento 1: La dirección del menú. La letra que hay
               después del subrayado indica una tecla aceleradora
               una vez que el menú esté abierto.
   Elemento 2: La tecla aceleradora para la entrada del menú.
   Elemento 3: La función de llamada.
   Elemento 4: La acción de llamada. Cambia los parámetros que
               se le pasan a la función de llamada. El valor por
               defecto es 0.
   Elemento 5: El tipo de elemento, se utiliza para definir de que
               tipo de elemento se trata. Los valores posibles son:

           NULL               -> "<Item>"
           ""                 -> "<Item>"
           "<Title>"          -> crea un elemento título
           "<Item>"           -> crea un simple elemento
           "<CheckItem>"      -> crea un elemento de comprobación
           "<ToggleItem>"     -> crea un elemento de selección
           "<RadioItem>"      -> crea un elemento circular
           <path>             -> dirección de un elemento circular
                                 con el que enlazar
           "<Separator>"      -> crea un separador
           "<Branch>"         -> crea un elemento para contener
                                 subelementos (de forma opcional)
           "<LastBranch>"     -> crea una rama justificada a la
                                 derecha
*/

static GtkItemFactoryEntry menu_items[] = {
  { "/_File",         NULL,         NULL, 0, "<Branch>" },
  { "/File/_New",     "<control>N", print_hello, 0, NULL },
  { "/File/_Open",    "<control>O", print_hello, 0, NULL },
  { "/File/_Save",    "<control>S", print_hello, 0, NULL },
  { "/File/Save _As", NULL,         NULL, 0, NULL },
  { "/File/sep1",     NULL,         NULL, 0, "<Separator>" },
  { "/File/Quit",     "<control>Q", gtk_main_quit, 0, NULL },
  { "/_Options",      NULL,         NULL, 0, "<Branch>" },
  { "/Options/Test",  NULL,         NULL, 0, NULL },
  { "/_Help",         NULL,         NULL, 0, "<LastBranch>" },
  { "/_Help/About",   NULL,         NULL, 0, NULL },
};


void get_main_menu( GtkWidget  *ventana,
                    GtkWidget **menubar )
{
  GtkItemFactory *item_factory;
  GtkAccelGroup *accel_group;
  gint nmenu_items = sizeof (menu_items) / sizeof (menu_items[0]);

  accel_group = gtk_accel_group_new ();

  /* Esta función inicializa la fábrica de elementos
     Param 1: El tipo de menú - puede ser GTK_TYPE_MENU_BAR,
              GTK_TYPE_MENU, o GTK_TYPE_OPTION_MENU.
     Param 2: La dirección del menú.
     Param 3: Un puntero a un gtk_accel_group. La fábrica de
              elementos actualiza la tabla de teclas aceleradoras
              mientras genera los menúes.
  */

  item_factory = gtk_item_factory_new (GTK_TYPE_MENU_BAR, "<main>", 
                                       accel_group);

  /* Esta función genera los elementos de menú. Pasa la
     fábrica de elementos (item_factory), el número de elementos
     del vector, el vector en sí, y cualquier dato de llamada para
     los elementos de menú. */
  gtk_item_factory_create_items (item_factory, nmenu_items, menu_items, NULL);

  /* Enlaza el nuevo grupo acelerador a la ventana. */
  gtk_accel_group_attach (accel_group, GTK_OBJECT (ventana));

  if (menubar)
    /* Finalmente, devuelve la barra de menú creada por la
     * fábrica de elementos. */
    *menubar = gtk_item_factory_get_widget (item_factory, "<main>");
}

int main( int argc,
          char *argv[] )
{
  GtkWidget *ventana;
  GtkWidget *main_vbox;
  GtkWidget *menubar;
  
  gtk_init (&argc, &argv);
  
  ventana = gtk_window_new (GTK_WINDOW_TOPLEVEL);
  gtk_signal_connect (GTK_OBJECT (ventana), "destroy", 
                      GTK_SIGNAL_FUNC (gtk_main_quit), 
                      "WM destroy");
  gtk_window_set_title (GTK_WINDOW(ventana), "Item Factory");
  gtk_widget_set_usize (GTK_WIDGET(ventana), 300, 200);
  
  main_vbox = gtk_vbox_new (FALSE, 1);
  gtk_container_border_width (GTK_CONTAINER (main_vbox), 1);
  gtk_container_add (GTK_CONTAINER (ventana), main_vbox);
  gtk_widget_show (main_vbox);
  
  get_main_menu (ventana, &menubar);
  gtk_box_pack_start (GTK_BOX (main_vbox), menubar, FALSE, TRUE, 0);
  gtk_widget_show (menubar);
  
  gtk_widget_show (ventana);
  gtk_main ();
  
  return(0);
}
/* fin del ejemplo */

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