# The GNOME Panel Libraries

Jacob
Berkman

    	    jacob@helixcode.com

George
Lebl

    	    jirka@5z.com

2000
The Free Software Foundation
2000
Helix Code, Inc.
2000
Eazel, Inc.

## The Art of Applet Writing

### Introduction

Applets are basically GNOME applications whose window sits inside the panel. Also the panel "takes care" of the applets by providing them with session saving and restarting, window management (inside of the panel), and a context menu.

### The Hello World of Applets

The simplest applet one can write would be along the lines of:

#### hello-world_applet

    #include <config.h>
    #include <applet-widget.h>

    int
    main(int argc, char **argv)
    {
            GtkWidget *applet;
            GtkWidget *label;

            /* Initialize the i18n stuff */
            bindtextdomain (PACKAGE, GNOMELOCALEDIR);
            textdomain (PACKAGE);

            /* intialize, this will basically set up the applet, corba and
               call gnome_init */
            applet_widget_init("hello-applet", NULL, argc, argv, NULL,0,NULL);

            /* create a new applet_widget */
            applet = applet_widget_new("hello-applet");
            /* in the rare case that the communication with the panel
               failed, error out */
            if (!applet)
                    g_error("Can't create applet!\n");

            /* create the widget we are going to put on the applet */
            label = gtk_label_new(_("Hello There!"));
            gtk_widget_show(label);

            /* add the widget to the applet-widget, and thereby actually
               putting it "onto" the panel */
            applet_widget_add (APPLET_WIDGET (applet), label);
            gtk_widget_show (applet);

            /* special corba main loop */
            applet_widget_gtk_main ();

            return 0;
    }

This creates an applet which just sits on the panel, not really doing anything, in real life the label would be substituted by something which actually does something useful. As you can see the applet doesn't really take care of restarting itself.

### The .gnorba and .desktop Files

For the applet to be added to the menus, you need to install two files. Your x.gnorba file goes into $sysconfdir/CORBA/servers/ and the x.desktop file goes into $prefix/share/applets/<category>/.

Example files are:

#### hello.desktop

    [Desktop Entry]
    Name=Hello Applet
    Comment=An example Hello World type Applet
    Type=PanelApplet
    Exec=hello_applet
    Icon=gnome-hello.png
    Terminal=0

#### hello.gnorba

    [hello_applet]
    type=exe
    repo_id=IDL:GNOME/Applet:1.0
    description=Hello Applet
    location_info=hello_applet

One thing to keep in mind is that the Exec line for the .desktop doesn't actually get executed when the Type is PanelApplet. The Exec line should be the GOAD ID specified in the .gnorba file (the "hello_applet" enclosed by brackets). For a simple applet all you need to do is replace the hello_applet with the name of your applet executable.

### The Applet's Context Menu

When the user right clicks on the applet, a menu appears, this is all handeled by the panel, so in order to add items to it you use a special interface to "add callbacks" to the menu. A very simple example would be (making our hello applet even more feature full):

#### hello_applet with menu items

    #include <config.h>
    #include <applet-widget.h>

    static void
    hello_there (AppletWidget *applet, gpointer data)
    {
            g_print(_("Hello There"));
    }

    int
    main (int argc, char **argv)
    {
            GtkWidget *applet;
            GtkWidget *label;

            /* Initialize the i18n stuff */
            bindtextdomain (PACKAGE, GNOMELOCALEDIR);
            textdomain (PACKAGE);

            /* intialize, this will basically set up the applet, corba and
               call gnome_init */
            applet_widget_init("hello_applet", NULL, argc, argv, NULL, 0, NULL);

            /* create a new applet_widget */
            applet = applet_widget_new("hello_applet");
            /* in the rare case that the communication with the panel
               failed, error out */
            if (!applet)
                    g_error("Can't create applet!\n");

            /* create the widget we are going to put on the applet */
            label = gtk_label_new(_("Hello There!"));
            gtk_widget_show(label);

            /* add the widget to the applet-widget, and thereby actually
               putting it "onto" the panel */
            applet_widget_add (APPLET_WIDGET (applet), label);
            gtk_widget_show (applet);

            /* add an item to the applet menu */
            applet_widget_register_callback(APPLET_WIDGET(applet),
                                            "hello",
                                            _("Hello There"),
                                            hello_there,
                                            NULL);

            /* special corba main loop */
            applet_widget_gtk_main ();

            return 0;
    }

Now the user will see a "Hello There" menu item on the applet menu, and when selected, the applet will print "Hello There". Useful huh?

Note that the second argument to the register_callback is just a string identifier of this callback, and can really be whatever you want. But it should NOT be translated as the label (the 3rd argument) should be.

### Advanced Menu Stuff

It is also possible to have submenus, remove menus and use gnome-stock icons on the menus.

#### Submenus

To do submenus, you have to first call applet_widget_register_callback_dir, which only takes the callback name and the menu text. The callback name should end with '/'. The callback name works as a "path" for the submenus.

So to add a submenu "Foo" and in item "Bar" (into the submenu "Foo") you would do:

##### Adding a Submenu

    applet_widget_register_callback_dir (APPLET_WIDGET (applet),
                                         "foo",
                                         _("Foo"));
    applet_widget_register_callback (APPLET_WIDGET (applet),
                                     "foo/bar",
                                     _("Bar"),
                                     bar_callback,
                                     NULL);

#### Deleting

To delete a menu item, just call applet_widget_unregister_callback or applet_widget_unregister_callback_dir, with the proper callback name.

#### Stock Icons

You use the _stock derivatives of the callback functions and pass an extra argument with the GNOME_STOCK_MENU_* type. For example to add an about menu item:

##### Adding Items with Stock Pixmaps

    applet_widget_register_stock_callback(APPLET_WIDGET(applet),
                                          "about",
                                          GNOME_STOCK_MENU_ABOUT,
                                          _("About..."),
                                          about_cb,
                                          NULL);

### Session Saving

The panel is session manager aware but the applets don't have to be, they can depend on the panel to save their information in a proper place. Basically session saving has two parts, loading the info, and saving the info. Loading is pretty simple, after you do applet_widget_new, you can get the correct paths to load your properties from the widget's structure. For example:

#### Getting Private Data

    gnome_config_push_prefix(APPLET_WIDGET(applet)->privcfgpath);
    hello = gnome_config_get_bool("section/hello=true");
    gnome_config_pop_prefix();

will do the trick.

For saving it's a little bit more complicated but not by much, let's make our original example save a global variable hello.

#### Saving Private HelloWorld

    #include <config.h>
    #include <applet-widget.h>

    /* useless variable that we want to save the state of*/
    gint hello = TRUE;

    /* sesion save signal handler*/
    static gint
    applet_save_session(GtkWidget *w,
                        const char *privcfgpath,
                        const char *globcfgpath)
    {
            gnome_config_push_prefix(privcfgpath);
            gnome_config_set_string("section/hello",hello);
            gnome_config_pop_prefix();

            gnome_config_sync();
            /* you need to use the drop_all here since we're all writing to
               one file, without it, things might not work too well */
            gnome_config_drop_all();

            /* make sure you return FALSE, otherwise your applet might not
               work compeltely, there are very few circumstances where you
               want to return TRUE. This behaves similiar to GTK events, in
               that if you return FALSE it means that you haven't done
               everything yourself, meaning you want the panel to save your
               other state such as the panel you are on, position,
               parameter, etc ... */
            return FALSE;
    }

    int
    main(int argc, char **argv)
    {
            GtkWidget *applet;
            GtkWidget *label;

            /* Initialize the i18n stuff */
            bindtextdomain (PACKAGE, GNOMELOCALEDIR);
            textdomain (PACKAGE);

            /* intialize, this will basically set up the applet, corba and
               call gnome_init */
            applet_widget_init("hello_applet", NULL, argc, argv, NULL, 0, NULL);

            /* create a new applet_widget */
            applet = applet_widget_new("hello_applet");
            /* in the rare case that the communication with the panel
               failed, error out */
            if (!applet)
                    g_error("Can't create applet!\n");

            /* read the contents of the stored value of hello from the
               config file */
            gnome_config_push_prefix(APPLET_WIDGET(applet)->privcfgpath);
            hello = gnome_config_get_bool("section/hello=true");
            gnome_config_pop_prefix();

            /* create the widget we are going to put on the applet */
            label = gtk_label_new(_("Hello There!"));
            gtk_widget_show(label);

            /* bind the session save signal */
            gtk_signal_connect(GTK_OBJECT(applet),"save_session",
                               GTK_SIGNAL_FUNC(applet_save_session),
                               NULL);

            /* add the widget to the applet-widget, and thereby actually
               putting it "onto" the panel */
            applet_widget_add (APPLET_WIDGET (applet), label);
            gtk_widget_show (applet);

            /* special corba main loop */
            applet_widget_gtk_main ();

            return 0;
    }

That's basically it. Make sure you return FALSE from the save_session handler, else the panel will not remember your applet next time. Also note the presence of gnome_config_drop_all, that needs to be done, especially for multi applets (discussed below), or your info might get lost.

If you need to store information global to all applets you can use the globcfgpath counterpart of privcfgpath, which gives you a path to a file which is the same for all applets.

IMPORTANT! Make sure you only use two levels of config path below privcfgpath/globcfgpath. Which means you only tack on "section/key". Also don't just use "key". You need to tack on both the section and the key, no more, no less.

#### Retrieving Global Data

    gnome_config_push_prefix(APPLET_WIDGET(applet)->globcfgpath);
    hello = gnome_config_get_bool("all_hello_applets/hello=true");
    gnome_config_pop_prefix();

Similarly for the save_session.

Note: When you update your configuration in some properties dialog, or however lse, you should call applet_widget_sync_config (AppletWidget *applet), it will tell the panel to send a session save signal to the applet with the correct paths etc. This is not 100&percnt; neccessary, but makes it nice so that configuration is not lost during crashes (when the panel couldn't do it's complete save during shutdown) [ed: you should still do this, even though the panel doesn't crash anymore]

### Panel Orientation

How to tell which way the panel on which your applet sits is fairly simply. You bind the "change_orient" signal of the applet. To modify our original hello applet, we'd do:

#### Freshman Orientation

    #include <config.h>
    #include <applet-widget.h>

    /*this is when the panel orientation changes*/
    static void
    applet_change_orient(GtkWidget *w, PanelOrientType o, gpointer data)
    {
            switch(o) {
                    case ORIENT_UP: puts("ORIENT UP"); break;
                    case ORIENT_DOWN: puts("ORIENT DOWN"); break;
                    case ORIENT_LEFT: puts("ORIENT LEFT"); break;
                    case ORIENT_RIGHT: puts("ORIENT RIGHT"); break;
            }
    }

    int
    main(int argc, char **argv)
    {
            GtkWidget *applet;
            GtkWidget *label;

            /* Initialize the i18n stuff */
            bindtextdomain (PACKAGE, GNOMELOCALEDIR);
            textdomain (PACKAGE);

            /* intialize, this will basically set up the applet, corba and
               call gnome_init */
            applet_widget_init("hello_applet", NULL, argc, argv, NULL, 0, NULL);

            /* create a new applet_widget */
            applet = applet_widget_new("hello_applet");
            /* in the rare case that the communication with the panel
               failed, error out */
            if (!applet)
                    g_error("Can't create applet!\n");

            /* create the widget we are going to put on the applet */
            label = gtk_label_new(_("Hello There!"));
            gtk_widget_show(label);

            /*we have to bind change_orient before we do applet_widget_add
              since we need to get an initial change_orient signal to set our
              initial oriantation, and we get that during the _add call*/
            gtk_signal_connect(GTK_OBJECT(applet),"change_orient",
                               GTK_SIGNAL_FUNC(applet_change_orient),
                               NULL);

            /* add the widget to the applet-widget, and thereby actually
               putting it "onto" the panel */
            applet_widget_add (APPLET_WIDGET (applet), label);
            gtk_widget_show (applet);

            /* special corba main loop */
            applet_widget_gtk_main ();

            return 0;
    }

### Panel Size

(Note: this is not included in versions of the panel prior to 1.2)

One new feature of the panel is the size support. The panel supports the following sizes:

- Tiny: 24 pixels

- Small: 36 pixels

- Standard: 48 pixels

- Large: 64 pixels

- Huge: 80 pixls

It would be nice to let your applet pick it's layout so that it doesn't stretch the panel out of it's preffered size (the panel is always as thick as the thickest applet)

The way this works is very similiar to the way orientation works. You bind the "change_pixel_size" signal to the applet, so to modify our original hello applet, we'd do:

#### The Sizes, The Keep A-Changin'

    #include <config.h>
    #include <applet-widget.h>

    #ifdef HAVE_PANEL_PIXEL_SIZE
    /*this is when the panel size changes*/
    static void
    applet_change_pixel_size(GtkWidget *w, int size, gpointer data)
    {
            printf("Got size of &percnt;d pixels\n",size);
    }
    #endif

    int
    main(int argc, char **argv)
    {
            GtkWidget *applet;
            GtkWidget *label;

            /* Initialize the i18n stuff */
            bindtextdomain (PACKAGE, GNOMELOCALEDIR);
            textdomain (PACKAGE);

            /* intialize, this will basically set up the applet, corba and
               call gnome_init */
            applet_widget_init("hello_applet", NULL, argc, argv, NULL, 0, NULL);

            /* create a new applet_widget */
            applet = applet_widget_new("hello_applet");
            /* in the rare case that the communication with the panel
               failed, error out */
            if (!applet)
                    g_error("Can't create applet!\n");

            /* create the widget we are going to put on the applet */
            label = gtk_label_new(_("Hello There!"));
            gtk_widget_show(label);

    #ifdef HAVE_PANEL_PIXEL_SIZE
            /*we have to bind change_pixel_size before we do applet_widget_add
              since we need to get an initial change_pixel_size signal to set our
              initial size, and we get that during the _add call*/
            gtk_signal_connect(GTK_OBJECT(applet),"change_pixel_size",
                               GTK_SIGNAL_FUNC(applet_change_pixel_size),
                               NULL);
    #endif

            /* add the widget to the applet-widget, and thereby actually
               putting it "onto" the panel */
            applet_widget_add (APPLET_WIDGET (applet), label);
            gtk_widget_show (applet);

            /* special corba main loop */
            applet_widget_gtk_main ();

            return 0;
    }

Notice the "#ifdef HAVE_PANEL_PIXEL_SIZE" line, this will make sure your applet compiles correctly even on a panel from gnome-core 1.0 which doesn't have support for multiple sizes. Note that in gnome-core 1.1.0 release there was another implementation of panel sizes which is now deprecated, so you should use the method above.

If you want to say compare to the standard sizes (You shouldn't assume that they are the only ones that exist!), you can use the PIXEL_SIZE_TINY, PIXEL_SIZE_STANDARD, PIXEL_SIZE_LARGE and PIXEL_SIZE_HUGE constants as in:

#### Using Standard Pixel Sizes

    if(size < PIXEL_SIZE_STANDARD) {
            ... do something for very small applet ...
    } else {
            ... do something else for standard size applet ...
    }

### Rebinding Events

(Note: this is not included in versions of the panel prior to 1.2)

Sometimes you want to change the way the applet looks after it has already been added to the panel, and you want the right and middle mouse button clicks to still work. In this case you need to notify the panel that it should try to rebind the events. You do this with:

#### Rebinding

    #ifdef HAVE_APPLET_BIND_EVENTS
    applet_widget_bind_events(APPLET_WIDGET(applet),GTK_WIDGET(widget));
    #endif

which will bind mouseclicks (2nd and 3rd button) on the "widget".

Note that this is NOT in the panel in gnome-core 1.0, so if you use this feature, make sure to put the "#ifdef HAVE_APPLET_BIND_EVENTS" around the code.

### Multiple Applet Support

Having one process per applet might be ok, but when you have many applets it can be quite a hit on the memory. There is an easy way to multiple applets from one process, even different types of applets.

For a simple example let's modify our original hello applet to make it possible to have multiple instances of it from just one executable. We will create a factory corba service that can create new instances of the applet.

#### Industrialization of hello_applet

    #include <config.h>
    #include <applet-widget.h>

    /*when we get a command to start a new widget*/
    static GtkWidget *
    applet_start_new_applet(const gchar *goad_id, const gchar **params, gint nparams)
    {
            GtkWidget *applet;
            GtkWidget *label;

            /*if we weren't asked to start hello_applet, just return*/
            if(strcmp(goad_id, quot;hello_appletquot;)!=0) return NULL;

            /*now we do the same exact thing as we do in the main function for
              creating the applet*/

            /* create a new applet_widget */
            applet = applet_widget_new(quot;hello_appletquot;);
            if (!applet)
                    g_error(quot;Can't create applet!\nquot;);

            /* create the widget we are going to put on the applet */
            label = gtk_label_new(_(quot;Hello There!quot;));
            gtk_widget_show(label);

            /* add the widget to the applet-widget, and thereby actually
               putting it quot;ontoquot; the panel */
            applet_widget_add (APPLET_WIDGET (applet), label);
            gtk_widget_show (applet);

            /* return the applet widget from this function */
            return applet;
    }

    int
    main(int argc, char **argv)
    {
            GtkWidget *applet;
            GtkWidget *label;
            gchar *goad_id;

            /* Initialize the i18n stuff */
            bindtextdomain (PACKAGE, GNOMELOCALEDIR);
            textdomain (PACKAGE);

            /* intialize, this will basically set up the applet, corba and
               call gnome_init */
            applet_widget_init(quot;hello_appletquot;, NULL, argc, argv, NULL, 0, NULL);

            /*make new factory and get us the goad_id that was used to start us*/
            applet_factory_new(quot;hello_applet_factoryquot;, NULL, applet_start_new_applet);
            goad_id = (gchar *)goad_server_activation_id();

            /*if the goad_id was hello_applet, we create a new instance of
              our applet, otherwise don't do anything*/
            if(goad_id && strcmp(goad_id, quot;hello_appletquot;)==0) {
                    /* create a new applet_widget */
                    applet = applet_widget_new(quot;hello_appletquot;);
                    if (!applet)
                            g_error(quot;Can't create applet!\nquot;);

                    /* create the widget we are going to put on the applet */
                    label = gtk_label_new(_(quot;Hello There!quot;));
                    gtk_widget_show(label);

                    /* add the widget to the applet-widget, and thereby actually
                       putting it quot;ontoquot; the panel */
                    applet_widget_add (APPLET_WIDGET (applet), label);
                    gtk_widget_show (applet);

            }

            /* special corba main loop */
            applet_widget_gtk_main ();

            return 0;
    }

What you will notice is that what we do is just make a factory service with applet_factory_new, to which we pass a function pointer to a function that just creates new applets for us.

Now we need to create a .gnorba and .desktop files for an applet of this type. The .desktop file is the exact same as for normal applets. The .gnorba file however must now describe the factory as well.

#### Industrialization of hello.gnorba

    [hello_applet_factory]
    type=exe
    repo_id=IDL:GNOME/GenericFactory:1.0
    description=Hello Applet
    location_info=hello_applet

    [hello_applet]
    type=factory
    repo_id=IDL:GNOME/Applet:1.0
    description=Hello Applet
    location_info=hello_applet_factory

Sometimes you may want to have two applets that have very similiar functionality, but that appear to the user as two different applets, and you want to manage them from the same process. This is extremely simple. Just take the above example and add more types into the .gnorba file, then wherever we check the goad_id, just add another "else if" to check for another goad_id. Then in your desktops on the Exec line, you would have in one .desktop:

    Exec=hello_version_1_applet

and in a second:

    Exec=hello_version_2_applet

### Shared Library Applets

It is possible to make applets which will not be separate processes, but will be loaded directly into the panel. This saves on memory, but can make the panel less stable if the applet is less stable.

Here is an example, George will comment on stuff unless I sleep:

#### piCommander

    #include <config.>
    #include <applet-widget.h>

    typedef struct {
    	GtkWidget *applet;
    	GtkWidget *fentry;
    	GtkWidget *gentry;
    	GtkWidget *entry;
    } PicoData;

    static void
    entry_activate (GtkWidget *w, PicoData *pico)
    {
    	char *s;
    	s = gtk_editable_get_chars (GTK_EDITABLE (pico->entry), 0, -1);
    	gtk_editable_delete_text (GTK_EDITABLE (pico->entry), 0, -1);

    	if (strlen (s) == 0) {
    		g_free (s);
    		return;
    	}

    	gnome_execute_shell (NULL, s);

    	gnome_entry_save_history (GNOME_ENTRY (pico->gentry));
    	gnome_config_sync ();

    	g_free (s);
    }

    static int
    applet_save_session (GtkWidget *applet,
    		     const char *privcfgpath,
    		     const char *globcfgpath,
    		     PicoData *pico)
    {
    	gnome_entry_save_history (GNOME_ENTRY (pico->gentry));
    	gnome_config_sync ();
    	return FALSE;
    }

    static void
    applet_destroy (GtkWidget *applet, PicoData *pico)
    {
    	g_free (pico);
    }

    static CORBA_Object
    pico_begin (PortableServer_POA poa,
    	    const char *goad_id,
    	    const char **params,
    	    gpointer *impl_ptr,
    	    CORBA_Environment *ev)
    {
    	PicoData *pico = g_new0 (PicoData, 1);

    	pico->applet = applet_widget_new (goad_id);
    	pico->fentry = gnome_file_entry_new ("pico-commander", _("Run..."));
    	pico->gentry = gnome_file_entry_gnome_entry (GNOME_FILE_ENTRY (pico->fentry));
    	pico->entry = gnome_file_entry_gtk_entry (GNOME_FILE_ENTRY (pico->fentry));

    	gnome_entry_set_max_saved (GNOME_ENTRY (pico->gentry), 50);
    	gnome_entry_prepend_history (GNOME_ENTRY (pico->gentry), FALSE, "");
    	gtk_combo_set_use_arrows_always (GTK_COMBO (pico->gentry), TRUE);

    	applet_widget_add (APPLET_WIDGET (pico->applet), pico->fentry);

    	gtk_signal_connect (GTK_OBJECT (pico->entry), "activate",
    			    GTK_SIGNAL_FUNC (entry_activate), pico);
    	gtk_signal_connect (GTK_OBJECT (pico->applet), "save_session",
    			    GTK_SIGNAL_FUNC (applet_save_session), pico);
    	gtk_signal_connect (GTK_OBJECT (pico->applet), "destroy",
    			    GTK_SIGNAL_FUNC (applet_destroy), pico);

    	gtk_widget_show_all (pico->applet);

    	return applet_widget_corba_activate (APPLET_WIDGET (pico->applet),
    					     poa, goad_id, params,
    					     impl_ptr, ev);
    }

    static void
    pico_end (PortableServer_POA poa,
    	  const char *goad_id,
    	  gpointer impl_ptr,
    	  CORBA_Environment *ev)
    {
    	applet_widget_corba_deactivate (poa, goad_id, impl_ptr, ev);
    }

    static const char *repo_id[] = { "IDL:GNOME/Applet:1.0", NULL };
    static GnomePluginObject applets_list[] = {
    	{ repo_id, "pico-commander_applet", NULL, "50 line launcher",
    	  &pico_begin, &pico_end },
    	{ NULL }
    };

    GnomePlugin GNOME_Plugin_info = { applets_list, NULL };

### Building the Applets

Here's a simple makefile you can use (this one is for the fish applet) if you want to compile applets outside of the gnome-core source tree. It was sent to me by John Ellis <johne@bellatlantic.net>

FIXME! More up-to-date example.

## GNOME Panel Applet Library

This describes the API for the GNOME panel applet library, as of the 1.2.0 release.

&panel-init; &panel-interact; &panel-menu; &panel-misc;

## GNOME Status Docklet Library

This describes the API for the GNOME status docklet library, as of the 1.2.0 release of gnome-core.

&panel-status-docklet;