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.
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:
gtk_menu_new()
gtk_menu_item_new()
para
cada elemento que desee tener en su menú. Y utilizar
gtk_menu_append()
para poner cada uno de esos nuevos elementos en
el menú.gtk_menu_item_new()
. Ésta será la raíz del menú, el texto que
aparezca aquí estará en la barra de menús.gtk_menu_item_set_submenu()
para enlazar el menú
al elemento del menú raíz (el creado en el paso anterior).gtk_menu_bar_new
. Este paso solo necesita hacerse una vez cuando
se crea una serie de menús en una barra de menús.gtk_menu_bar_append
para poner el menú raíz en la
barra de menús.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:
static gint handler( GtkWidget *widget,
GdkEvent *event );
y utilice el evento para encontrar donde debe aparecer el menú.
event
como un evento de botón
(que lo es) y utilizarlo como se indica en el código ejemplo para
pasarle información a gtk_menu_popup()
.
gtk_signal_connect_object(GTK_OBJECT(widget), "event",
GTK_SIGNAL_FUNC (handler), GTK_OBJECT(menu));
donde widget
es el widget con el que esta conectando,
handler
es la función manejadora, y menu
es un menú
creado con gtk_menu_new()
. Éste puede ser un menú que esté
contenido en una barra de menús, como se puede ver en el código de
ejemplo.
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ú.
Ahora que le hemos enseñado la forma difícil, le mostraremos como
utilizar las llamadas gtk_menu_factory
.
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.