The GnomeApp application framework is part of the libgnomeui library, but it really deserves sesparate treatment. This is the part which really makes a GNOME application a GNOME application. It also makes programming apps very simple and straightforward, and makes the apps featurefull, consistent and user customizable. With plain GTK+ you have to do a lot of things by yourself, reinventing the wheel every time, but GnomeApp takes care of the UI setup for you and still allows the user to configure that behavior and have it be consistent over different applications.
GnomeApp is the basic widget behind each app. It is the main window of the application, containing the document being worked on and the applications menus, tool-bars and status bars. It also remembers the docked positions of menu bars and tool-bars and such for you so that the user gets the window the way he left it when he left the application last time.
Creating a new GnomeApp widget is as easy as calling gnome_app_new with the application name, which is usually the name of the executable or something else that is unique to your application and the title of the main window. Then you create the content of the main window and add it to the GnomeApp widget by calling gnome_app_set_contents with your contents as the argument.
Adding menu-bars, tool-bars and status-bars is equally easy, you call gnome_app_set_toolbar, gnome_app_set_menus or gnome_app_set_statusbar. gnome_app_set_toolbar is for simple applications that have only one tool-bar, for more complicated applications you need to use gnome_app_add_toolbar, which allows you to add as many docked tool-bars as you need.
Most of the time, you don't really want to create your menu-bars and tool-bars by yourself. You can use functions from libgnomeui/gnome-app-helper.h to construct menus and tool-bars for you. All you need is to fill in a couple of structures with the your information, and call gnome_app_create_menus or gnome_app_create_toolbar with that structure and voila, your application has menus and tool-bars. Sometimes you wish to pass a data pointer to all the callbacks from those structures to work with, then you'd use the gnome_app_create_toolbar_with_data and gnome_app_create_menus_with_data, and pass an extra parameter which will be passed in the data field of the callbacks.
Here is the definition of the structure you need to fill (actually you fill in an array of such structures). Also note I included the enums that you will need to fill that structure.
/* These values identify the type of pixmap used in an item */ typedef enum { GNOME_APP_PIXMAP_NONE, /* No pixmap specified */ GNOME_APP_PIXMAP_STOCK, /* Use a stock pixmap (GnomeStock) */ GNOME_APP_PIXMAP_DATA, /* Use a pixmap from inline xpm data */ GNOME_APP_PIXMAP_FILENAME /* Use a pixmap from the specified filename */ } GnomeUIPixmapType; /* This is the structure that defines an item in a menu bar * or tool-bar. The idea is to create an array of such * structures with the information needed to create menus or * tool-bars. The most convenient way to create such a structure * is to use the GNOMEUIINFO_* macros provided below. */ typedef struct { GnomeUIInfoType type; /* Type of item */ gchar *label; /* String to use in the label */ gchar *hint; /* For tool-bar items, the tool-tip. For menu items, the status bar message */ gpointer moreinfo; /* For an item, toggle-item, or radio-item, this is a pointer to the function to call when the item is activated. For a subtree, a pointer to another array of GnomeUIInfo structures. For a radio-item lead entry, a pointer to an array of GnomeUIInfo structures for the radio item group. For a help item, specifies the help node to load (i.e. the application's identifier) or NULL for the main program's name. For builder data, points to the GnomeUIBuilderData structure for the following items */ gpointer user_data; /* Data pointer to pass to callbacks */ gpointer unused_data; /* Reserved for future expansion, should be NULL */ GnomeUIPixmapType pixmap_type; /* Type of pixmap for the item */ gpointer pixmap_info; /* Pointer to the pixmap information: For GNOME_APP_PIXMAP_STOCK, a pointer to the stock icon name. For GNOME_APP_PIXMAP_DATA, a pointer to the inline xpm data. For GNOME_APP_PIXMAP_FILENAME, a pointer to the filename string. */ guint accelerator_key; /* Accelerator key, or 0 for none */ GdkModifierType ac_mods; /* Mask of modifier keys for the accelerator */ GtkWidget *widget; /* Filled in by gnome_app_create*, you can use this to tweak the widgets once they have been created */ } GnomeUIInfo; |
Most of the time, menu entries are very simple, so one can just use one of the simple macros provided. For example, for the end of a menu, one would use the GNOMEUIINFO_END macro, for a separator one uses the GNOMEUIINFO_SEPARATOR macro. Now for the actual items there are also macros, which require you to fill in less info. For example if you have an item that you provide an xpm format data for, you can use the GNOMEUIINFO_ITEM(label, tooltip, callback, xpm_data) macro, where label is the text of the label, tool-tip is the tool-tip that the user gets when he goes over that item (or it can be NULL), callback is the function that gets called when the user presses that item, and the xpm_data is a pointer to an xpm data you want to use as the icon. If you have no icon you can just use the GNOMEUIINFO_ITEM_NONE(label, tooltip, callback) macro. If what you are adding is a standard item for which there is a stock icon (we'll talk about those next), you can use the GNOMEUIINFO_ITEM_STOCK(label, tooltip, callback, stock_id) macro where the stock_id is the id of the stock icon you want to use. Then for your main menu bar, or to put sub-menus inside your menus, you can use GNOMEUIINFO_SUBTREE(label, tree) and GNOMEUIINFO_SUBTREE_STOCK(label, tree, stock_id), where the tree is the array of GnomeUIInfo structures that you want to use as that sub-menu. There are a few other macros, but most of the time you will get by with just these macros, so you don't need to learn the entire structure of the GnomeUIInfo.
Just about all application contain a couple of standard menu items, so to keep things more consistent there are a bunch of macros that fill in everything for you except for the callback function and the data. The advantage of using the macros is consistency across applications, user customization, and translation.
Most of these macros have the form: GNOMEUIINFO_MENU_<name>_ITEM (callback, data). However, there is an exception, the "New xxx" item. The GNOME style guide Requires that you put what the "New" thing is into the item name. Not to mention that it will have a different hint as well. So the "New xxx" item has the structure of: GNOMEUIINFO_MENU_NEW_ITEM(label, hint, callback, data). The "label" should start with "New ". Also note that if you have more new items, you need to use the "New" subtree macro, which is explained later.
Table 3-4. The File Menu
Macro | Description |
---|---|
GNOMEUIINFO_MENU_NEW_ITEM(label, hint, cb, data) | "New" menu item (you need to provide label and hint yourself here) |
GNOMEUIINFO_MENU_OPEN_ITEM(cb, data) | "Open" menu item |
GNOMEUIINFO_MENU_SAVE_ITEM(cb, data) | "Save" menu item |
GNOMEUIINFO_MENU_SAVE_AS_ITEM(cb, data) | "Save as..." menu item |
GNOMEUIINFO_MENU_REVERT_ITEM(cb, data) | "Revert" menu item |
GNOMEUIINFO_MENU_PRINT_ITEM(cb, data) | "Print" menu item |
GNOMEUIINFO_MENU_PRINT_SETUP_ITEM(cb, data) | "Print Setup" menu item |
GNOMEUIINFO_MENU_CLOSE_ITEM(cb, data) | "Close" menu item |
GNOMEUIINFO_MENU_EXIT_ITEM(cb, data) | "Exit" menu item |
Table 3-5. The Edit Menu
Macro | Description |
---|---|
GNOMEUIINFO_MENU_CUT_ITEM(cb, data) | "Cut" menu item |
GNOMEUIINFO_MENU_COPY_ITEM(cb, data) | "Copy" menu item |
GNOMEUIINFO_MENU_PASTE_ITEM(cb, data) | "Paste" menu item |
GNOMEUIINFO_MENU_SELECT_ALL_ITEM(cb, data) | "Select" menu item |
GNOMEUIINFO_MENU_CLEAR_ITEM(cb, data) | "Clear" menu item |
GNOMEUIINFO_MENU_UNDO_ITEM(cb, data) | "Undo" menu item |
GNOMEUIINFO_MENU_REDO_ITEM(cb, data) | "Redo" menu item |
GNOMEUIINFO_MENU_FIND_ITEM(cb, data) | "Find" menu item |
GNOMEUIINFO_MENU_FIND_AGAIN_ITEM(cb, data) | "Find Again" menu item |
GNOMEUIINFO_MENU_REPLACE_ITEM(cb, data) | "Replace" menu item |
GNOMEUIINFO_MENU_PROPERTIES_ITEM(cb, data) | "Properties" menu item, for properties of a manipulated object |
Table 3-6. The Settings Menu
Macro | Description |
---|---|
GNOMEUIINFO_MENU_PREFERENCES_ITEM(cb, data) | "Preferences" menu item, for application preferences |
Table 3-7. The Windows Menu
Macro | Description |
---|---|
GNOMEUIINFO_MENU_NEW_WINDOW_ITEM(cb, data) | "New window" menu item |
GNOMEUIINFO_MENU_CLOSE_WINDOW_ITEM(cb, data) | "Close window" menu item |
Table 3-8. The Help Menu
Macro | Description |
---|---|
GNOMEUIINFO_MENU_ABOUT_ITEM(cb, data) | "About" menu item |
Table 3-9. The Game Menu
Macro | Description |
---|---|
GNOMEUIINFO_MENU_NEW_GAME_ITEM(cb, data) | "New game" menu item |
GNOMEUIINFO_MENU_PAUSE_GAME_ITEM(cb, data) | "Pause game" menu item |
GNOMEUIINFO_MENU_RESTART_GAME_ITEM(cb, data) | "Restart game" menu item |
GNOMEUIINFO_MENU_UNDO_MOVE_ITEM(cb, data) | "Undo move" menu item |
GNOMEUIINFO_MENU_REDO_MOVE_ITEM(cb, data) | "Redo move" menu item |
GNOMEUIINFO_MENU_HINT_ITEM(cb, data) | "Hint" menu item |
GNOMEUIINFO_MENU_SCORES_ITEM(cb, data) | "Scores" menu item |
GNOMEUIINFO_MENU_END_GAME_ITEM(cb, data) | "End game" menu item |
We have already mentioned a "New" subtree. For this you should use the GNOMEUIINFO_MENU_NEW_SUBTREE (tree) macro, where the tree argument is another GnomeUIInfo structure array of the different new items.
There are also the standard top level menus. Again you pass the array of GnomeUIInfo structures to the macro.
Table 3-10. The Toplevel Menu Macros
Macro | Description |
---|---|
GNOMEUIINFO_MENU_FILE_TREE (tree) | "File" menu |
GNOMEUIINFO_MENU_EDIT_TREE (tree) | "Edit" menu |
GNOMEUIINFO_MENU_VIEW_TREE (tree) | "View" menu |
GNOMEUIINFO_MENU_SETTINGS_TREE (tree) | "Settings" menu |
GNOMEUIINFO_MENU_FILES_TREE (tree) | "Files" menu |
GNOMEUIINFO_MENU_WINDOWS_TREE (tree) | "Windows" menu |
GNOMEUIINFO_MENU_HELP_TREE (tree) | "Help" menu |
GNOMEUIINFO_MENU_GAME_TREE (tree) | "Game" menu |
Sometimes you may want to refer to menu path of these menus, such as for adding items to a "Windows" menu. For this you should use the macros of the form GNOME_MENU_<name>_STRING and GNOME_MENU_<name>_PATH. These will expand to the appropriate string. The macro ending with _STRING will expand to just the menu name, and the macro ending with _PATH to the menu name followed by a "/". The <name> can be one of the following: FILE, EDIT, VIEW, SETTINGS, NEW, FILES or WINDOWS.
Your application should contain a help menu, the help menu can be defined as:
GNOMEUIINFO_HELP("app_name"), GNOMEUIINFO_MENU_ABOUT_ITEM(callback, data), GNOMEUIINFO_END |
Here is a very simple application that makes use of these:
/* * A simple Gnome program, outside of GNOME tree, not using i18n * uiinfo.c */ /* the very basic gnome include */ #include <gnome.h> /* a callback for the buttons */ static void a_callback(GtkWidget *button, gpointer data) { /*just print a string so that we know we got there*/ g_print("Inside Callback\n"); } GnomeUIInfo file_menu[] = { GNOMEUIINFO_MENU_EXIT_ITEM(gtk_main_quit,NULL), GNOMEUIINFO_END }; GnomeUIInfo some_menu[] = { GNOMEUIINFO_ITEM_NONE("_Menuitem","Just a menuitem", a_callback), GNOMEUIINFO_SEPARATOR, GNOMEUIINFO_ITEM_NONE("M_enuitem2","Just a menuitem", a_callback), GNOMEUIINFO_END }; GnomeUIInfo menubar[] = { GNOMEUIINFO_MENU_FILE_TREE(file_menu), GNOMEUIINFO_SUBTREE("_Some menu",some_menu), GNOMEUIINFO_END }; GnomeUIInfo toolbar[] = { GNOMEUIINFO_ITEM_STOCK("Exit","Exit the application", gtk_main_quit, GNOME_STOCK_PIXMAP_EXIT), GNOMEUIINFO_END }; int main(int argc, char *argv[]) { GtkWidget *app; GtkWidget *button; GtkWidget *hbox; GtkWidget *label; /* Initialize GNOME, this is very similar to gtk_init */ gnome_init ("menu-basic-example", "0.1", argc, argv); /* Create a Gnome app widget, which sets up a basic window for your application */ app = gnome_app_new ("menu-basic-example", "Basic GNOME Application"); /* bind "delete_event", which is the event we get when the user closes the window with the window manager, to gtk_main_quit, which is a function that causes the gtk_main loop to exit, and consequently to quit the application */ gtk_signal_connect (GTK_OBJECT (app), "delete_event", GTK_SIGNAL_FUNC (gtk_main_quit), NULL); /*make a label as the contents*/ label = gtk_label_new("BLAH BLAH BLAH BLAH BLAH"); /*add the label as contents of the window*/ gnome_app_set_contents (GNOME_APP (app), label); /*create the menus for the application*/ gnome_app_create_menus (GNOME_APP (app), menubar); /*create the tool-bar for the application*/ gnome_app_create_toolbar (GNOME_APP (app), toolbar); /* show everything inside this app widget and the app widget itself */ gtk_widget_show_all(app); /* enter the main loop */ gtk_main (); return 0; } |
You have probably noticed the underlines in the labels for the menu items, these specify the accelerators for that menu. That's really all you need to do to add accelerators for menu items. The way accelerators work is very similar to the other windowing systems out there, alt-<key> if you are not browsing the menus or just the <key> if you have the menu open.
Every app should also include an application status bar on the bottom of the window. This status bar should give flashes and warnings about the application status, this is done according to the preferences as long as you use the GnomeApp message functions (described in Talking to the user section). The status bar also gives hints about menuitems and can provide a progress bar. Here are the important appbar functions.
Table 3-11. Important GnomeAppBar Methods
Prototype | Description |
---|---|
GtkWidget * gnome_appbar_new (gboolean has_progress, gboolean has_status, GnomePreferencesType interactivity) | Creates a new appbar widget, optionally with a progress bar if 'has_progress' is TRUE, and a statusbar if 'has_status' is true. The 'interactivity' tells us if when we want to be interactive, it can be never, always, or depending on user preferences, GNOME_PREFERENCES_USER, and that's what should probably be used. Though note that an interactive status bar is not finished yet. |
void gnome_appbar_set_status (GnomeAppBar * appbar, const gchar * status) | Set the text of the status bar, without changing AppBar state. |
void gnome_appbar_set_default (GnomeAppBar * appbar, const gchar * default_status) | Set tthe default text of the statusbar when there is nothing to display |
void gnome_appbar_push (GnomeAppBar * appbar, const gchar * status) | Push a message onto the appbar stack of status messages. |
void gnome_appbar_pop (GnomeAppBar * appbar) | Pop a message from the appbar status message stack. |
void gnome_appbar_clear_stack (GnomeAppBar * appbar) | Clear the status message stack |
void gnome_appbar_refresh (GnomeAppBar * appbar) | Refresh the status bar and set it to the current value of the stack or to the default. Useful for clearing a message put on by the gnome_appbar_set_status method. |
void gnome_appbar_set_progress (GnomeAppBar *appbar, gfloat percentage) | Sets the percantage on the progress bar |
GtkProgress * gnome_appbar_get_progress (GnomeAppBar * appbar) | Get the GtkProgress widget that is the progress bar so that you can manipulate it. |
To get the appbar onto the GnomeApp window, you use the gnome_app_set_statusbar method. To also get the menu hints onto the status bar, you need to call gnome_app_install_menu_hints with the pointer of your GnomeUIInfo definition for the main menu bar. For the above example, we could add such functionality by adding:
GtkWidget *w; w = gnome_appbar_new(FALSE, TRUE, GNOME_PREFERENCES_USER); gnome_app_set_statusbar(GNOME_APP(app), w); gnome_app_install_menu_hints(GNOME_APP(app), menubar); |
The way the status bar works, is that you have a stack of status messages and the most recent one is displayed in the status bar. This is useful since a lot of times your status will be hierachial anyway, and you might have a long term message that you want to be there unless some short term message needs to be displayed for a while, after this short term one is taken down the one that is ont he stack below it (the long term one) will be displayed.
You should also note that a lot of things are taken care of automatically without you having to actually touch the appbar at all. For example displaying messages on the status bar can be achieved by say gnome_app_flash described later in the Talking to the user section.
A lot of times you will need to cummunicate something to the user, such as that an error orccured or inform him of something. There are preferences the user can set as to where he sees such data, so you should use the following functions rather then just making a new dialog box. This will save you time and will make the application more consistent and flexible.
Table 3-12. GnomeApp Message Functions
Prototype | Description |
---|---|
GtkWidget * gnome_app_message (GnomeApp * app, const gchar * message) | Display a message to the user and require confirmation from the user. In case it was a dialog, it returns the pointer to the dialog, but you can ignore that unless you wish to do something with it. |
void gnome_app_flash (GnomeApp * app, const gchar * flash) | Flash a quick message to the user on the status bar for a couple of seconds. Usefull for quick, non-crtical status updates. |
GtkWidget * gnome_app_error (GnomeApp * app, const gchar * error) | Similiar to gnome_app_message, but for displaying errors to the user. |
GtkWidget * gnome_app_warning (GnomeApp * app, const gchar * warning) | Also similiar to gnome_app_message, but for a warning. |
An example might be when you say want to open a file and you fail.
FILE *fp; fp = fopen(filename,"r"); if(!fp) { char *err = g_strdup_printf(_("Cannot open file %s"), filename); gnome_app_error(GNOME_APP(app),err); g_free(err); ... } |
Sometimes you might want to get some feedback from the user about a specific situation, instead of just telling him something. One of the following functions might be appropriate. These function take a function pointer, either GnomeReplyCallback or GnomeStringCalback. These callbacks take an integer reply number or a character string and a data pointer. However don't count on the callback ever being called. All of these functions return the widget of the dialog or NULL if the status line was used. Another thing to note is that these are nonblocking, which means that you cannot immediately act as if they are done.
Table 3-13. GnomeApp Query Functions
Prototype | Description |
---|---|
GtkWidget * gnome_app_question (GnomeApp * app, const gchar * question, GnomeReplyCallback callback, gpointer data) | A yes or no type question, the reply callback gets 0 or 1. |
GtkWidget * gnome_app_question_modal (GnomeApp * app, const gchar * question, GnomeReplyCallback callback, gpointer data) | Yes or no type of question, but it will not allow the user to interact with the rest of the application until he answers or closes the dialog. |
GtkWidget * gnome_app_ok_cancel (GnomeApp * app, const gchar * message, GnomeReplyCallback callback, gpointer data) | An OK or Cancel type of a question. |
GtkWidget * gnome_app_ok_cancel_modal (GnomeApp * app, const gchar * message, GnomeReplyCallback callback, gpointer data) | Modal version of the gnome_app_ok_cancel function. |
GtkWidget * gnome_app_request_string (GnomeApp * app, const gchar * prompt, GnomeStringCallback callback, gpointer data) | Ask the user for a string with 'prompt'. The callback may get NULL if the user cancels. The string passed to the callback is newly allocated so free it after use. |
GtkWidget * gnome_app_request_password (GnomeApp * app, const gchar * prompt, GnomeStringCallback callback, gpointer data) | Ask the user for a password with 'prompt'. Like gnome_app_request_string, but the text is not displayed as it's typed. |
For example say we want to ask if one wants to really delete an object.
static void really_delete_handler(int reply, gpointer data) { GtkWidget *some_widget; some_widget = GTK_WIDGET(data); if(reply == 0) { ... /* yes was selected */ } } ... /* ask a yes no question, passing some_widget as the data argument to the handler */ gnome_app_question(GNOME_APP(app),_("Really delete object?"), really_delete_handler, some widget); |