Previous Next Contents

14. Menu Widgets

There are two ways to create menus, there's the easy way, and there's the hard way. Both have their uses, but you can usually use the menu_factory (the easy way). The "hard" way is to create all the menus using the calls directly. The easy way is to use the gtk_menu_factory calls. This is much simpler, but there are advantages and disadvantages to each approach.

The menufactory is much easier to use, and to add new menus to, although writing a few wrapper functions to create menus using the manual method could go a long way towards usability. With the menufactory, it is not possible to add images or '/'s to the menus.

14.1 Manual Menu Creation

In the true tradition of teaching, we'll show you the hard way first. :)

Let's look at the functions that are used to create menus. This first function is used to create a new menu.

GtkWidget *gtk_menu_bar_new()

This rather self explanatory function creates a new menu bar. You use gtk_container_add to pack this into a window, or the box_pack functions to pack it into a box - the same as buttons.

GtkWidget *gtk_menu_new();

This function returns a pointer to a new menu, it is never actually shown (with gtk_widget_show), it just holds the menu items. Hopefully this will become more clear when you look at the example below.

The next two calls are used to create menu items that are packed into the menu.

GtkWidget *gtk_menu_item_new()

and

GtkWidget *gtk_menu_item_new_with_label(const char *label)

These calls are used to create the menus that are to be displayed. Remember to differentiate between a "menu" as created with gtk_menu_new and a "menu item" as created by the gtk_menu_item_new functions. The menu item will be an actual button with an associated action, whereas a menu will be a container holding these

gtk_menu_item_append()

gtk_menu_item_set_submenu()

The gtk_menu_new_with_label and plain gtk_menu_new functions are just as you'd expect after reading about the buttons. One creates a new menu item with a label already packed into it, and the other just creates a blank menu item.

The steps to create a menu are outlined below:

14.2 Manual Menu Example

And that should about do it. Let's take a look at an example to help clarify.

#include <gtk/gtk.h>

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

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

    gtk_init (&argc, &argv);

    /* create a new window */
    window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
    gtk_window_set_title(GTK_WINDOW (window), "GTK Menu Test");
    gtk_signal_connect(GTK_OBJECT (window), "destroy",
                       (GtkSignalFunc) gtk_exit, NULL);

    /* Init the menu-widget, and remember -- never
     * gtk_show_widget() the menu widget!! */
    menu = gtk_menu_new();

    /* This is the root menu, and will be the label will be the menu name displayed on
     * the menu bar.  There won't be
     * a signal handler attached, as it only pops up the rest of the menu when pressed. */
    root_menu = gtk_menu_item_new_with_label("Root Menu");

    gtk_widget_show(root_menu);

    /* Next we make a little loop that makes three menu-entries for "test-menu".
     * Notice the call to gtk_menu_append.  Here we are adding a list of menu items
     * to our menu.  Normally, we'd also catch the "clicked" signal on each of the
     * menu items and setup a callback for it, but it's omitted here to save space. */

    for(i = 0; i < 3; i++)
        {
            /* Copy the names to the buf. */
            sprintf(buf, "Test-undermenu - %d", i);

            /* Create a new menu-item with a name... */
            menu_items = gtk_menu_item_new_with_label(buf);

            /* ...and add it to the menu. */
            gtk_menu_append(GTK_MENU (menu), menu_items);

            /* Show the widget */
            gtk_widget_show(menu_items);
        }

    /* Now we specify that we want our newly created "menu" to be the menu for the "root menu" */
    gtk_menu_item_set_submenu(GTK_MENU_ITEM (root_menu), menu);

    /* Create a menu-bar to hold the menus and add it to our main window*/
    menu_bar = gtk_menu_bar_new();
    gtk_container_add(GTK_CONTAINER(window), menu_bar);
    gtk_widget_show(menu_bar);

    /* And finally we append the menu-item to the menu-bar -- this is the "root"
     * menu-item I have been raving about =) */
    gtk_menu_bar_append(GTK_MENU_BAR (menu_bar), root_menu);

    /* always display the window as the last step so it all splashes on the screen at once. */
    gtk_widget_show(window);

    gtk_main ();

    return 0;
}

You may also set a menu item to be insensitive and, using an accelerator table, bind keys to menu functions.

[ maybe insert a few calls or something ]

14.3 Using GtkMenuFactory

Now that we've shown you the hard way, here's how you do it using the gtk_menu_factory calls.

14.4 Menu Factory Example

Here is an example using the GTK menu factory. This is the first file, menus.h. We keep a separate menus.c and main.c because of the global variables used in the menus.c file.

#ifndef __MENUS_H__
#define __MENUS_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 /* __MENUS_H__ */

And here is the menus.c file.


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

#include "main.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);


/* this is the GtkMenuEntry structure used to create new menus.  The
 * first member is the menu definition string.  The second, the
 * default accelerator key used to access this menu function with
 * the keyboard.  The third is the callback function to call when
 * this menu item is selected (by the accelerator key, or with the
 * mouse.) The last member is the data to pass to your callback function.
 */

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

/* calculate the number of menu_item's */
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_string_hash, g_string_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("Unable to set sensitivity for menu which doesn't exist: %s", path);
}

And here's the main.h

#ifndef __MAIN_H__
#define __MAIN_H__


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

void file_quit_cmd_callback(GtkWidget *widget, gpointer data);

#ifdef __cplusplus
}
#endif /* __cplusplus */

#endif /* __MAIN_H__ */

And main.c

#include <gtk/gtk.h>

#include "main.h"
#include "menus.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);
}

/* This is just to demonstrate how callbacks work when using the
 * menufactory.  Often, people put all the callbacks from the menus
 * in a separate file, and then have them call the appropriate functions
 * from there.  Keeps it more organized. */
void file_quit_cmd_callback (GtkWidget *widget, gpointer data)
{
    g_print ("%s\n", (char *) data);
    gtk_exit(0);
}

And a makefile so it'll be easier to compile it.

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 = at

O_FILES = menus.o main.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 *~

For now, there's only this example. An explanation and lots 'o' comments will follow later.


Previous Next Contents