This is a little document that describes the "art" of applet writing.

Introduction:
=============

Applets are basically gtk applications with one major difference, they
have a window which sits inside the panel. Also the panel "takes care"
of the applets by providing them with stuff such as sessions saving, and
taking care of their space on the panel, taking care of restarting them,
etc ... etc ... etc ...

Changes:
========

10/24/99) Took out the old panel size stuff and replaced it with the new
	 pixel_size stuff

5/30/99) Took out the old mico example, added .gnorba,.desktop examples
	 and explanation, and fixed the multiple applet support
	 documentation. Also fixed all the applet_widget_init and
	 applet_widget_new calls for the correct arguments. Hopefully it's
	 now correct.

7/23/98) The applets should call applet_widget_sync_config, so that their
	 changes can be noticed by the panel and synced to disk immediately
	 it's not completely neccessary as everything will be saved when
	 logging out, but it makes it nice for crashes, etc ...

7/3/98) the session_save signal is now being phased out, you need to use
	save_session signal which has basically the same interface, but
	uses privcfgpath instead of cfgpath. cfgpath variable is also
	being phased out and should not be used, you should use privcfgpath
	instead. The change is basically that for privcfgpath and
	save_session you add "section/key" to the path instead of just "key".
	The old stuff is still in for compatibility reasons but will disappear
	soon.


The Hello World of applets:
===========================

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

#include <config.h>
#include <gnome.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 <prefix>/etc/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
	Exec=hello_applet --activate-goad-server=hello_applet
	Icon=
	Terminal=0
	Type=Application

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 doesn't actually get
executed really. The panel will parse out the goad-server name and use
the standard gnome activation service to run the applet. For a simple
applet all you need to do is replace the hello_applet with the name of
your applet executable.

Now to the more interesting stuff.

Applet 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 sue a special
interface to "add callbacks" to the menu. A very simple example would
be (making our hello applet even more feature full):

#include <config.h>
#include <gnome.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

	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:

	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:

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.

#include <config.h>
#include <gnome.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.


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

Similiarly for the save_session.

NOTE:
When you update your configuration in some properties dialog, or however
else, 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% neccessary, but makes it nice so
that configuration is not lost during crashes (when panel couldn't do it's
complete save during shutdown)

Panel Orientation:
==================

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

#include <config.h>
#include <gnome.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;
}

Now we get a signal every time the panel changes it's orientation and we
can change ours as well. The different values represent the orientation
a menu/drawer would take were it on the panel, not the actual position
of the panel. If the Panel "sits" on the bottom edge of the screen you
will get ORIENT_UP, if it sits on the left edge, you get ORIENT_RIGHT,
and so on, if the panel is a vertical drawer you get ORIENT_RIGHT or
ORIENT_LEFT, if it's a horizontal drawer you get ORIENT_UP or ORIENT_DOWN.

Also note that you should bind the event before you do applet_widget_add,
as the event will be triggered during the add, so that you can set your
initial orientation.

Panel Size:
===========
(not in 1.0)

One things that is very new in the panel now is the Size support. The
panel supports the following sizes:

Tiny: 24 pixels
Standard: 48 pixels
Large: 64 pixels
Huge: 80 pixels

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 so
here is an example:

How to what size the panel on which your applet sits is, fairly simply.
You bind the "change_pixel_size" signal to the applet, so to modify our
original hello applet, we'd do:

#include <config.h>
#include <gnome.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 %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 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

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

Rebinding events (Rebuilding the widget structure on the applet):
=================================================================
(not in 1.0)

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:

#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. so why not manage multiple applets
from one process, even different types of applets. Ok here's how it's
done. 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.

#include <config.h>
#include <gnome.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, "hello_applet")!=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("hello_applet");
	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);

	/* 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("hello_applet", NULL, argc, argv, NULL, 0, NULL);

	/*make new factory and get us the goad_id that was used to start us*/
	applet_factory_new("hello_applet_factory", 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, "hello_applet")==0) {
		/* create a new applet_widget */
		applet = applet_widget_new("hello_applet");
		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;
}

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.

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

That's it.

Now 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 say

In one .desktop:

Exec=hello_applet --activate-goad-server=hello_version_1_applet

In another .desktop

Exec=hello_applet --activate-goad-server=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 makes the panel less stable
if the applet is less stable.

FIXME! Need explanation, examples.

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! We need a more up to date example.

It's all quite simple isn't it?

George <jirka@5z.com>
