Tutorial

Inti Logo

« Manual Menu Creation
Tree and List Widget  »

Using an Item Factory

Item factories are the easiest way to implement a menu hierachy. You can create single menus or entire menu bars and associated menu items and submenus with an item factory. Item factories are easy to use and there is no difference in appearance from menus created manually, but there are some trade-offs in control.

An item factory creates its menu item from an item factory map. You use the macros defined in <inti/gtk/itemfactory.h> to declare and define your item factory map. Each class can have only one item factory and one item factory map. If you have ever programmed using Visual C++ or Borland's OWL you will already be familiar with message maps and event maps. Item factory maps are similar.

First, in your class declaration you must include the following:

class ItemFactoryWindow : public Gtk::Window
{
    Pointer<Gtk::ItemFactory> item_factory;

public:
    ItemFactoryWindow();
    virtual ~ItemFactoryWindow();

    DECLARE_ITEM_FACTORY_MAP(ItemFactoryWindow)
};

The item_factory member is the item factory for this class. Gtk::ItemFactory is one of those Gtk::Object exceptions that is not created with an initial floating reference. You own the initial reference and must explicitly call unref(). A smart pointer is used here for convenience because it calls unref() on the item_factory when it goes out of scope. If you do not use a smart pointer you will have to call unref() yourself from within the class destructor.

The DECLARE_ITEM_FACTORY_MAP macro takes the class name as a parameter and is defined as:

#define DECLARE_ITEM_FACTORY_MAP(klass)\
private:\
    friend class Gtk::ItemFactory;\
    static Inti::Gtk::ItemFactoryEntry< klass > item_factory_map[];\
    typedef Inti::Gtk::ItemFactoryEntry< klass >::PMF MyPMF;\
    typedef klass MyClass;\
public:

As you can see this macro simplifies your coding by declaring and array of Gtk::ItemFactoryEntry and a couple of typedefs. You could put this macro anywhere in your class declaration but the best place for it is last.

Next, somewhere in your source file you must define the item factory map. An item factory map looks like this:

BEGIN_ITEM_FACTORY_MAP(ItemFactoryWindow)
    IFM_BRANCH("/_File"),
    IFM_ITEM("/File/_New", "<control>N", on_file_new),
    IFM_ITEM("/File/_Open", "<control>0", on_file_open),
    IFM_ITEM("/File/_Save", "<control>S", on_file_save),
    IFM_ITEM("/File/Save _As", 0, on_file_save_as),
    IFM_SEPARATOR("/File/sep1"),
    IFM_ITEM("/File/Quit", "<control>Q", on_file_quit),
    IFM_BRANCH("/_Options"),
    IFM_ITEM("/_Options/Test", 0, on_options_test),
    IFM_LAST_BRANCH("/_Help"),
    IFM_ITEM("/Help/_About", 0, on_help_about),
END_ITEM_FACTORY_MAP

The BEGIN_ITEM_FACTORY_MAP macro takes the class name as a parameter and is defined as:

#define BEGIN_ITEM_FACTORY_MAP(klass)\
    Inti::Gtk::ItemFactoryEntry< klass > klass::item_factory_map[] = {

and the END_ITEM_FACTORY_MAP macro is defined as:

#define END_ITEM_FACTORY_MAP\
    { 0, 0, 0, 0, 0, 0, 0 }};

The IFM_ macros expand to Gtk::ItemFactory entries which correspond to the possible item types in the GtkItemFactoryEntry structure. The macros are defined as follows:

#define IFM_TITLE(path, accelerator, function)\
    { path, accelerator, 0, 0, "<Title>", 0, (MyPMF)&MyClass::function }

#define IFM_ITEM(path, accelerator, function)\
    { path, accelerator, 0, 0, "<Item>", 0, (MyPMF)&MyClass::function }

#define IFM_IMAGE_ITEM(path, accelerator, function, pixbuf_stream)\
    { path, accelerator, 0, 0, "<ImageItem>", pixbuf_stream, (MyPMF)&MyClass::function }

#define IFM_STOCK_ITEM(path, accelerator, function, stock_id)\
    { path, accelerator, 0, 0, "<StockItem>", (gconstpointer)stock_id, (MyPMF)&MyClass::function }

#define IFM_CHECK_ITEM(path, accelerator, function)\
    { path, accelerator, 0, 0, "<CheckItem>", 0, (MyPMF)&MyClass::function }

#define IFM_TOGGLE_ITEM(path, accelerator, function)\
    { path, accelerator, 0, 0, "<CheckItem>", 0, (MyPMF)&MyClass::function }

#define IFM_RADIO_ITEM(path, accelerator, function)\
    { path, accelerator, 0, 0, "<RadioItem>", 0, (MyPMF)&MyClass::function }

#define IFM_RADIO_ITEM_LINK(path, function, link_path)\
    { path, 0, 0, 0, link_path, 0, (MyPMF)&MyClass::function }

#define IFM_TEAROFF_ITEM(path, function)\
    { path, 0, 0, 0, "<Tearoff>", 0, (MyPMF)&MyClass::function }

#define IFM_SEPARATOR(path)\
    { path, 0, 0, 0, "<Separator>", 0, 0 }

#define IFM_BRANCH(path)\
    { path, 0, 0, 0, "<Branch>", 0, 0 }

#define IFM_LAST_BRANCH(path)\
    { path, 0, 0, 0, "<LastBranch>", 0, 0 }


Again, you can see that the IFM_ macros simplify your coding. The function parameter is the name of the class method that is called when the associated menu item is activated. Here is another trade-off. Each menu item must have its own method and the method must have the prototype:

void MyClass::signal_handler();

Now that you know how to declare and define an item factory map all that is left is to create the item factory and the items in your item factory map. This is usually done in the class constructor.

To create an item factory use one of the following constructors:

ItemFactory();

ItemFactory(GType container_type, const char *path, Gtk::AccelGroup *accel_group = 0);

The container_type is the type of item factory to create and can be one of the following GTypes:
The path is the factory path of the new item factory, a string of the form "<name>". The accel_group is the accelerator group to which the accelerators for the menu items will be added, or null to create a new one. The first constructor creates a GTK_TYPE_MENU_BAR item factory with the path "<main>" and a new accelerator group.

The code to create an item factory looks something like this:

Pointer<Gtk::AccelGroup> accel_group = new Gtk::AccelGroup;
item_factory = new Gtk::ItemFactory(GTK_TYPE_MENU_BAR, "<main>", accel_group);
add_accel_group(accel_group);

There is that smart pointer again. Gtk::AccelGroup is another one of those Gtk::Object exceptions that is not created with an initial floating reference. Instead you own the initial reference and must explicitly call unref(). The smart pointer will call unref() on the accelerator group for you when it goes out of scope. Note that it is ok to call unref() after passing the accelerator group to an owner object. That's because owner objects always increment the reference count, keeping the object alive and decrement the reference count when it gets destroyed.

To create the items in an item factory map call this method:

template<typename T> void create_items(T& owner);

The code to create the items looks like this:

item_factory->create_items(*this);

Now the only thing left to do now is retrieve a pointer to the menu created and either add it to your window or pack it into a box. You can use one of the following Gtk::ItemFactory methods depending on the type of item factory you created.

Gtk::MenuBar* menu_bar() const;

Gtk::Menu* menu() const;

Gtk::OptionMenu* option_menu() const;

If you call the accessor for an item factory type you did not create the return value is a null pointer. To retrieve a pointer to the accelerator group call:

Gtk::AccelGroup* accel_group() const;

To retrieve a pointer to a submenu in the item factory call:

Gtk::Menu* submenu(const char* path) const;

where the path is the path specified in the item factory map entry, without the underscore. Remember, underscores are parsed out when the items are created.

Here is the previous Menu Window example created using an item factory. Note how it looks the same.

Item Factory Example

The header file for Item Factory is itemfactory.h:

#include<inti/main.h>
#include <inti/core.h>
#include <inti/gtk/itemfactory.h>
#include <inti/gtk/menu.h>


using namespace Inti;

class ItemFactoryWindow : public Gtk::Window
{
    Pointer<Gtk::ItemFactory> item_factory;

    ItemFactoryWindow(const ItemFactoryWindow&);
    ItemFactoryWindow& operator=(const ItemFactoryWindow&);

protected:
    bool on_button_press(GdkEventButton *event, Gtk::Menu *menu);

public:
    ItemFactoryWindow();
    virtual ~ItemFactoryWindow();

    void menu_item_selected(const char *parent, const char *item);

    void on_file_new();
    void on_file_open();
    void on_file_save();
    void on_file_save_as();
    void on_file_quit();
    void on_options_test();
    void on_help_about();

    DECLARE_ITEM_FACTORY_MAP(ItemFactoryWindow)
};

and the source file is itemfactory.cc:

#include"itemfactory.h"
#include <inti/gtk/accelgroup.h>
#include <inti/gtk/menubar.h>
#include <inti/gtk/eventbox.h>
#include <inti/gtk/label.h>
#include <inti/bind.h>


ItemFactoryWindow::ItemFactoryWindow()
{
    set_title("Item Factory");
    set_size_request(300, 200);

    // Boxes don't receive button events so use an eventbox. The eventbox is added first
    // and then all the other widgets added to it.
    Gtk::EventBox *eventbox = new Gtk::EventBox;
    add(*eventbox);
   
    // Set the events the eventbox is to receive. These can be any number of or'd (|) values
    // from the Gdk::EventMask enumeration.
 
   eventbox->set_events(Gdk::BUTTON_PRESS_MASK);

    // Add the packing box to eventbox
    Gtk::VBox *vbox = new Gtk::VBox(false, 1);
    vbox->set_border_width(1);
    eventbox->add(*vbox);

    // Create the menubar. The item factory constructor takes 3 parameters.
    // Param 1: The type of menu - can be GTK_TYPE_MENU_BAR, GTK_TYPE_MENU, or GTK_TYPE_OPTION_MENU.
    // Param 2: The path of the menu.
    // Param 3: A pointer to a Gtk::AccelGroup.  The item factory sets up the accelerator table while generating menus.
    Pointer<Gtk::AccelGroup> accel_group = new Gtk::AccelGroup;
    item_factory = new Gtk::ItemFactory(GTK_TYPE_MENU_BAR, "<main>", accel_group);
    add_accel_group(accel_group);

    // Create menu items. The create_items() function generates the menu items. It takes a reference to the window.
    item_factory->create_items(*this);

    // Retrieve a pointer to the File menu. Do not include the underscore when specifying the
    // submenu path. ItemFactory parses out all the underscores at creation.
    Gtk::Menu *file_menu = item_factory->submenu("/File");
   
    // Bind the file menu to the button_press event and use it as the popup menu.
    eventbox->sig_button_press_event().connect(bind(slot(this, &ItemFactoryWindow::on_button_press), file_menu));

    // Retrieve a pointer to the menubar
    Gtk::MenuBar *menu = item_factory->menu_bar();

    // Pack the menubar into the vbox
    vbox->pack_start(*menu, false);
   
    // Add a label that tells the user to click the mouse button inside the client area.
    Gtk::Label *label = new Gtk::Label("Click mouse button here...");
    vbox->pack_start(*label);

    // Show everything
    label->show();
    menu->show();
    vbox->show();
    eventbox->show();
}

ItemFactoryWindow::~ItemFactoryWindow()
{
}

bool
ItemFactoryWindow::on_button_press(GdkEventButton *event, Gtk::Menu *menu)
{
    menu->popup(event->button, event->time);
    return true;
}

void
ItemFactoryWindow::menu_item_selected(const char *parent, const char *item)
{
    g_message("ItemFactory: activated the \"%s\" menu item: \"%s\"", parent, item);
}

void
ItemFactoryWindow::on_file_new()
{
    menu_item_selected("File", "New");
}

void
ItemFactoryWindow::on_file_open()
{
    menu_item_selected("File", "Open");
}

void
ItemFactoryWindow::on_file_save()
{
    menu_item_selected("File", "Save");
}

void
ItemFactoryWindow::on_file_save_as()
{
    menu_item_selected("File", "Save As");
}

void
ItemFactoryWindow::on_file_quit()
{
    dispose();
}

void
ItemFactoryWindow::on_options_test()
{
    menu_item_selected("Options", "Test");
}

void
ItemFactoryWindow::on_help_about()
{
    menu_item_selected("Help", "About");
}

// Item factory map macros are defined in <inti/gtk/itemfactory.h>

BEGIN_ITEM_FACTORY_MAP(ItemFactoryWindow)
    IFM_BRANCH("/_File"),
    IFM_ITEM("/File/_New", "<control>N", on_file_new),
    IFM_ITEM("/File/_Open", "<control>0", on_file_open),
    IFM_ITEM("/File/_Save", "<control>S", on_file_save),
    IFM_ITEM("/File/Save _As", 0, on_file_save_as),
    IFM_SEPARATOR("/File/sep1"),
    IFM_ITEM("/File/Quit", "<control>Q", on_file_quit),
    IFM_BRANCH("/_Options"),
    IFM_ITEM("/_Options/Test", 0, on_options_test),
    IFM_LAST_BRANCH("/_Help"),
    IFM_ITEM("/Help/_About", 0, on_help_about),
END_ITEM_FACTORY_MAP

// Convenience macro for a simple main function

INTI_MAIN(ItemFactoryWindow)




« Manual Menu Creation Index
Menus
Top
 Tree and List Widget»