NOTE: FIXME:!!!!!!!!!!!!! This is broken now, needs fixing!!!!

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

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_defaults("hello_applet", NULL, argc, argv, 0,
				    NULL, argv[0]);

	/* create a new applet_widget */
	applet = applet_widget_new();
	/* 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, it just passes the argv[0] parameter to 
the panel, in the applet_widget_init_defaults call and that's the way
the panel will restart it next time.

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_defaults("hello_applet", NULL, argc, argv, 0,
				    NULL, argv[0]);

	/* create a new applet_widget */
	applet = applet_widget_new();
	/* 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_defaults("hello_applet", NULL, argc, argv, 0,
				    NULL, argv[0]);

	/* create a new applet_widget */
	applet = applet_widget_new();
	/* 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_config_sync(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_defaults("hello_applet", NULL, argc, argv, 0,
				    NULL, argv[0]);

	/* create a new applet_widget */
	applet = applet_widget_new();
	/* 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.

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.

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

/*when we get a command to start a new widget*/
static void
applet_start_new_applet(const gchar *param, gpointer data)
{
	GtkWidget *applet;

	applet = applet_widget_new_with_param(param);
	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);
}

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, 0, NULL, argv[0],
			   TRUE,TRUE,applet_start_new_applet,NULL);

	/* create a new applet_widget */
	applet = applet_widget_new();
	/* 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;
}

You will note in the above that all that is changed is that we use the
full syntax for the applet_widget_init function, and added a callback
function for starting a new applet. That's all that's needed really.
Of course the applet can't do things globally, but most likely you will
want to store the widget data in a structure which you store in the
user_data of the applet widget. See the clock applet for an example of
this.

If you want to put more applet types into one executable, you will need
to use a command line argument. This might change in the future since I
think the current way is kind of hackish, but I need to figure out a
clean interface for this, and I haven't thought of one that would work
well yet.

Anyway it's fairly simple to do. Here's a simple hello applet (again),
but it handeles a "Good Bye" applet as well.

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

/* These are the arguments that our application supports.  */
static struct argp_option arguments[] =
{
  { "hello", -1, NULL, 0, N_("Start in hello mode"), 1 },
  { "goodbye", -1, NULL, 0, N_("Start in goodbye mode"), 1 },
  { NULL, 0, NULL, 0, NULL, 0 }
};

/* Forward declaration of the function that gets called when one of
   our arguments is recognized.  */
/* we ignore the arguments */
static error_t
parse_an_arg (int key, char *arg, struct argp_state *state)
{
	return 0;
}

/* This structure defines our parser.  It can be used to specify some
   options for how our parsing function should be called.  */
static struct argp parser =
{
	arguments,			/* Options.  */
	parse_an_arg,			/* The parser function.  */
	NULL,				/* Some docs.  */
	NULL,				/* Some more docs.  */
	NULL,				/* Child arguments -- gnome_init fills
				           this in for us.  */
	NULL,				/* Help filter.  */
	NULL				/* Translation domain; for the app it
				           can always be NULL.  */
};

/* start a new instance of the hello applet */
static void
make_hello_applet(gchar *param)
{
	GtkWidget *applet;
	GtkWidget *label;

	/* create a new applet_widget */
	applet = applet_widget_new_with_param(param);
	/* 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);
}

/* start a new instance of the goodbye applet */
static void
make_goodbye_applet(gchar *param)
{
	GtkWidget *applet;
	GtkWidget *label;

	/* create a new applet_widget */
	applet = applet_widget_new_with_param(param);
	/* 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(_("GoodBye!"));
	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);
}


/* start a new applet depending on the param string */
static void
make_new_applet(const gchar *param)
{
	if(strstr(param,"--hello"))
		make_hello_applet(param);
	else /* --goodbye */
		make_goodbye_applet(param);
}

/*when we get a command to start a new widget*/
static void
applet_start_new_applet(const gchar *param, gpointer data)
{
	make_new_applet(param);
}

int
main(int argc, char **argv)
{
	gchar *param;

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

	/*we make a param string, instead of first parsing the params with
	  argp, we will allways use a string, since the panel will only
	  give us a string */
	param = make_param_string(argc,argv);

	applet_widget_init("hello_goodbye_applet", &parser, argc, argv, 0,
			   NULL, argv[0],TRUE,TRUE,applet_start_new_applet,
			   NULL);

	/* start a new applet using param */
	make_new_applet(param);
	g_free(param);

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

	return 0;
}

Not as simple as the last one, but still simple enough. We have to set
up the the argp argument stuff, even though we ignore it. The
make_hello_applet and make_goodbye_applet could really be merged
together. But the way I envision such a setup is that those functions
are in separate files along with all the functionality of that particular
applet.

After that you only create two .desktop entries, one with:
"hello_goodbye_applet --hello"
and one with:
"hello_goodbye_applet --goodbye"

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>.

------------------8<-------------------
#set PREFIX to gnome's location:
PREFIX = /usr
BINARY = fish_applet
DESKTOP_FILE = fish_applet.desktop
DESKTOP_DIR = Amusements
# i18n does not like this, but:
PACKAGE = gnome-core

include $(PREFIX)/lib/gnomeConf.sh

#Parse out those nasty quotes.
GNOME_INCLUDEDIR_PARSED = $(subst ",,$(GNOME_INCLUDEDIR))
GNOME_LIBS_PARSED = $(subst ",,$(GNOME_LIBS))
GNOMEUI_LIBS_PARSED = $(subst ",,$(GNOMEUI_LIBS))

GTK_INCLUDE = `gtk-config --cflags`
GTK_LIB = `gtk-config --libs`
CFLAGS = $(GTK_INCLUDE) $(GNOME_INCLUDEDIR_PARSED) -I.
LDFLAGS = $(GTK_LIB) $(GNOMEUI_LIBS_PARSED) $(PREFIX)/lib/libpanel_applet.so    \
          -lmicocoss2.0.5 -lmicoaux2.0.5 -lmico2.0.5
CC = gcc -Wall $(CFLAGS)
CCPLUS = c++ -Wall

OBJS = fish.o

$(BINARY): $(OBJS)
        $(CCPLUS) $(OBJS) -o $(BINARY) $(LDFLAGS)

fish.o : fish.c
        echo "#define PACKAGE \"$(PACKAGE)\"" > config.h
        $(CC) -c fish.c

install:
        install -c $(BINARY) $(PREFIX)/bin/$(BINARY)
        install -c $(DESKTOP_FILE) $(PREFIX)/share/applets/$(DESKTOP_DIR)/$(DESK
TOP_FILE)

uninstall:
        rm -f $(PREFIX)/bin/$(BINARY)
        rm -f $(PREFIX)/share/applets/$(DESKTOP_DIR)/$(DESKTOP_FILE)

clean:
        rm -f *.o *~ $(BINARY) core

------------------8<-------------------

It's all quite simple isn't it?

George <jirka@5z.com>
