source: trunk/third/gnome-core/panel/APPLET_WRITING @ 15328

Revision 15328, 21.5 KB checked in by ghudson, 24 years ago (diff)
This commit was generated by cvs2svn to compensate for changes in r15327, which included commits to RCS files with non-trunk default branches.
Line 
1This is a little document that describes the "art" of applet writing.
2
3Introduction:
4=============
5
6Applets are basically gtk applications with one major difference, they
7have a window which sits inside the panel. Also the panel "takes care"
8of the applets by providing them with stuff such as sessions saving, and
9taking care of their space on the panel, taking care of restarting them,
10etc ... etc ... etc ...
11
12Changes:
13========
14
1510/24/99) Took out the old panel size stuff and replaced it with the new
16         pixel_size stuff
17
185/30/99) Took out the old mico example, added .gnorba,.desktop examples
19         and explanation, and fixed the multiple applet support
20         documentation. Also fixed all the applet_widget_init and
21         applet_widget_new calls for the correct arguments. Hopefully it's
22         now correct.
23
247/23/98) The applets should call applet_widget_sync_config, so that their
25         changes can be noticed by the panel and synced to disk immediately
26         it's not completely neccessary as everything will be saved when
27         logging out, but it makes it nice for crashes, etc ...
28
297/3/98) the session_save signal is now being phased out, you need to use
30        save_session signal which has basically the same interface, but
31        uses privcfgpath instead of cfgpath. cfgpath variable is also
32        being phased out and should not be used, you should use privcfgpath
33        instead. The change is basically that for privcfgpath and
34        save_session you add "section/key" to the path instead of just "key".
35        The old stuff is still in for compatibility reasons but will disappear
36        soon.
37
38
39The Hello World of applets:
40===========================
41
42The simplest applet one can write would be along the lines of:
43
44#include <config.h>
45#include <gnome.h>
46#include <applet-widget.h>
47
48int
49main(int argc, char **argv)
50{
51        GtkWidget *applet;
52        GtkWidget *label;
53
54        /* Initialize the i18n stuff */
55        bindtextdomain (PACKAGE, GNOMELOCALEDIR);
56        textdomain (PACKAGE);
57
58        /* intialize, this will basically set up the applet, corba and
59           call gnome_init */
60        applet_widget_init("hello_applet", NULL, argc, argv, NULL,0,NULL);
61
62        /* create a new applet_widget */
63        applet = applet_widget_new("hello_applet");
64        /* in the rare case that the communication with the panel
65           failed, error out */
66        if (!applet)
67                g_error("Can't create applet!\n");
68
69        /* create the widget we are going to put on the applet */
70        label = gtk_label_new(_("Hello There!"));
71        gtk_widget_show(label);
72
73        /* add the widget to the applet-widget, and thereby actually
74           putting it "onto" the panel */
75        applet_widget_add (APPLET_WIDGET (applet), label);
76        gtk_widget_show (applet);
77
78        /* special corba main loop */
79        applet_widget_gtk_main ();
80
81        return 0;
82}
83
84This creates an applet which just sits on the panel, not really doing
85anything, in real life the label would be substituted by something which
86actually does something useful. as you can see the applet doesn't really
87take care of restarting itself.
88
89The .gnorba and .desktop files:
90===============================
91
92For the applet to be added to the menus, you need to install two files.
93Your x.gnorba file goes into <prefix>/etc/CORBA/servers/ and the
94x.desktop file goes into <prefix>/share/applets/<category>/.
95
96Example files are
97
98hello.desktop:
99
100        [Desktop Entry]
101        Name=Hello Applet
102        Comment=An example Hello World type Applet
103        Exec=hello_applet --activate-goad-server=hello_applet
104        Icon=
105        Terminal=0
106        Type=Application
107
108hello.gnorba:
109
110        [hello_applet]
111        type=exe
112        repo_id=IDL:GNOME/Applet:1.0
113        description=Hello Applet
114        location_info=hello_applet
115
116One thing to keep in mind is that the Exec line doesn't actually get
117executed really. The panel will parse out the goad-server name and use
118the standard gnome activation service to run the applet. For a simple
119applet all you need to do is replace the hello_applet with the name of
120your applet executable.
121
122Now to the more interesting stuff.
123
124Applet Menu:
125============
126
127When the user right clicks on the applet, a menu appears, this is all
128handeled by the panel, so in order to add items to it you sue a special
129interface to "add callbacks" to the menu. A very simple example would
130be (making our hello applet even more feature full):
131
132#include <config.h>
133#include <gnome.h>
134#include <applet-widget.h>
135
136static void
137hello_there(AppletWidget *applet, gpointer data)
138{
139        g_print(_("Hello There"));
140}
141
142int
143main(int argc, char **argv)
144{
145        GtkWidget *applet;
146        GtkWidget *label;
147
148        /* Initialize the i18n stuff */
149        bindtextdomain (PACKAGE, GNOMELOCALEDIR);
150        textdomain (PACKAGE);
151
152        /* intialize, this will basically set up the applet, corba and
153           call gnome_init */
154        applet_widget_init("hello_applet", NULL, argc, argv, NULL, 0, NULL);
155
156        /* create a new applet_widget */
157        applet = applet_widget_new("hello_applet");
158        /* in the rare case that the communication with the panel
159           failed, error out */
160        if (!applet)
161                g_error("Can't create applet!\n");
162
163        /* create the widget we are going to put on the applet */
164        label = gtk_label_new(_("Hello There!"));
165        gtk_widget_show(label);
166
167        /* add the widget to the applet-widget, and thereby actually
168           putting it "onto" the panel */
169        applet_widget_add (APPLET_WIDGET (applet), label);
170        gtk_widget_show (applet);
171
172        /* add an item to the applet menu */
173        applet_widget_register_callback(APPLET_WIDGET(applet),
174                                        "hello",
175                                        _("Hello There"),
176                                        hello_there,
177                                        NULL);
178
179        /* special corba main loop */
180        applet_widget_gtk_main ();
181
182        return 0;
183}
184
185Now the user will see a "Hello There" menu item on the applet menu, and
186when selected, the applet will print "Hello There". Useful huh?
187
188Note that the second argument to the register_callback is just a string
189identifier of this callback, and can really be whatever you want. But it
190should NOT be translated as the label (the 3rd argument) should be.
191
192Advanced Menu Stuff:
193====================
194
195It is also possible to have submenus, remove menus and use
196gnome-stock icons on the menus.
197
198Submenus:
199
200To do submenus, you have to first call applet_widget_register_callback_dir,
201which only takes the callback name and the menu text. The callback name should
202end with '/'.  The callback name works as a "path" for the submenus.
203
204So to add a submenu "Foo" and in item "Bar" (into the submenu "Foo")
205you would do
206
207        applet_widget_register_callback_dir(APPLET_WIDGET(applet),
208                                            "foo/",
209                                            _("Foo"));
210        applet_widget_register_callback(APPLET_WIDGET(applet),
211                                        "foo/bar",
212                                        _("Bar"),
213                                        bar_callback,
214                                        NULL);
215
216Deleting:
217
218To delete a menu item, just call applet_widget_unregister_callback or
219applet_widget_unregister_callback_dir, with the proper callback name.
220
221Stock Icons:
222
223You use the _stock derivatives of the callback functions and pass an
224extra argument with the GNOME_STOCK_MENU_* type. For example to add an
225about menu item:
226
227        applet_widget_register_stock_callback(APPLET_WIDGET(applet),
228                                              "about",
229                                              GNOME_STOCK_MENU_ABOUT,
230                                              _("About..."),
231                                              about_cb,
232                                              NULL);
233
234
235Session Saving:
236===============
237
238The panel is session manager aware but the applets don't have to be,
239they can depend on the panel to save their information in a proper
240place. Basically session saving has two parts, loading the info, and
241saving the info. Loading is pretty simple, after you do
242applet_widget_new, you can get the correct paths to load your properties
243from the widget's structure. For example:
244
245gnome_config_push_prefix(APPLET_WIDGET(applet)->privcfgpath);
246hello = gnome_config_get_bool("section/hello=true");
247gnome_config_pop_prefix();
248
249will do the trick.
250
251For saving it's a little bit more complicated but not by much, let's
252make our original example save a global variable hello.
253
254#include <config.h>
255#include <gnome.h>
256#include <applet-widget.h>
257
258/* useless variable that we want to save the state of*/
259gint hello = TRUE;
260
261/* sesion save signal handler*/
262static gint
263applet_save_session(GtkWidget *w,
264                    const char *privcfgpath,
265                    const char *globcfgpath)
266{
267        gnome_config_push_prefix(privcfgpath);
268        gnome_config_set_string("section/hello",hello);
269        gnome_config_pop_prefix();
270
271        gnome_config_sync();
272        /* you need to use the drop_all here since we're all writing to
273           one file, without it, things might not work too well */
274        gnome_config_drop_all();
275
276        /* make sure you return FALSE, otherwise your applet might not
277           work compeltely, there are very few circumstances where you
278           want to return TRUE. This behaves similiar to GTK events, in
279           that if you return FALSE it means that you haven't done
280           everything yourself, meaning you want the panel to save your
281           other state such as the panel you are on, position,
282           parameter, etc ... */
283        return FALSE;
284}
285
286
287int
288main(int argc, char **argv)
289{
290        GtkWidget *applet;
291        GtkWidget *label;
292
293        /* Initialize the i18n stuff */
294        bindtextdomain (PACKAGE, GNOMELOCALEDIR);
295        textdomain (PACKAGE);
296
297        /* intialize, this will basically set up the applet, corba and
298           call gnome_init */
299        applet_widget_init("hello_applet", NULL, argc, argv, NULL, 0, NULL);
300
301        /* create a new applet_widget */
302        applet = applet_widget_new("hello_applet");
303        /* in the rare case that the communication with the panel
304           failed, error out */
305        if (!applet)
306                g_error("Can't create applet!\n");
307
308        /* read the contents of the stored value of hello from the
309           config file */
310        gnome_config_push_prefix(APPLET_WIDGET(applet)->privcfgpath);
311        hello = gnome_config_get_bool("section/hello=true");
312        gnome_config_pop_prefix();
313
314        /* create the widget we are going to put on the applet */
315        label = gtk_label_new(_("Hello There!"));
316        gtk_widget_show(label);
317
318        /* bind the session save signal */
319        gtk_signal_connect(GTK_OBJECT(applet),"save_session",
320                           GTK_SIGNAL_FUNC(applet_save_session),
321                           NULL);
322
323        /* add the widget to the applet-widget, and thereby actually
324           putting it "onto" the panel */
325        applet_widget_add (APPLET_WIDGET (applet), label);
326        gtk_widget_show (applet);
327
328        /* special corba main loop */
329        applet_widget_gtk_main ();
330
331        return 0;
332}
333
334That's basically it. Make sure you return FALSE from the save_session
335handler, else the panel will not remember your applet next time. Also
336note the presence of gnome_config_drop_all, that needs to be done,
337especially for multi applets (discussed below), or your info might get
338lost.
339
340If you need to store information global to all applets you can use the
341globcfgpath counterpart of privcfgpath, which gives you a path to a file
342which is the same for all applets.
343
344IMPORTANT!
345Make sure you only use two levels of config path below
346privcfgpath/globcfgpath. Which means you only tack on "section/key".
347Also don't just use "key". You need to tack on both the section and
348the key, no more, no less.
349
350
351gnome_config_push_prefix(APPLET_WIDGET(applet)->globcfgpath);
352hello = gnome_config_get_bool("all_hello_applets/hello=true");
353gnome_config_pop_prefix();
354
355Similiarly for the save_session.
356
357NOTE:
358When you update your configuration in some properties dialog, or however
359else, you should call applet_widget_sync_config(AppletWidget *applet),
360it will tell the panel to send a session save signal to the applet with
361the correct paths etc. This is not 100% neccessary, but makes it nice so
362that configuration is not lost during crashes (when panel couldn't do it's
363complete save during shutdown)
364
365Panel Orientation:
366==================
367
368How to tell which way the panel on which your applet sits is oriented,
369fairly simply. You bind the "change_orient" signal to the applet, so to
370modify our original hello applet, we'd do:
371
372#include <config.h>
373#include <gnome.h>
374#include <applet-widget.h>
375
376/*this is when the panel orientation changes*/
377static void
378applet_change_orient(GtkWidget *w, PanelOrientType o, gpointer data)
379{
380        switch(o) {
381                case ORIENT_UP: puts("ORIENT UP"); break;
382                case ORIENT_DOWN: puts("ORIENT DOWN"); break;
383                case ORIENT_LEFT: puts("ORIENT LEFT"); break;
384                case ORIENT_RIGHT: puts("ORIENT RIGHT"); break;
385        }
386}
387
388int
389main(int argc, char **argv)
390{
391        GtkWidget *applet;
392        GtkWidget *label;
393
394        /* Initialize the i18n stuff */
395        bindtextdomain (PACKAGE, GNOMELOCALEDIR);
396        textdomain (PACKAGE);
397
398        /* intialize, this will basically set up the applet, corba and
399           call gnome_init */
400        applet_widget_init("hello_applet", NULL, argc, argv, NULL, 0, NULL);
401
402        /* create a new applet_widget */
403        applet = applet_widget_new("hello_applet");
404        /* in the rare case that the communication with the panel
405           failed, error out */
406        if (!applet)
407                g_error("Can't create applet!\n");
408
409        /* create the widget we are going to put on the applet */
410        label = gtk_label_new(_("Hello There!"));
411        gtk_widget_show(label);
412
413        /*we have to bind change_orient before we do applet_widget_add
414          since we need to get an initial change_orient signal to set our
415          initial oriantation, and we get that during the _add call*/
416        gtk_signal_connect(GTK_OBJECT(applet),"change_orient",
417                           GTK_SIGNAL_FUNC(applet_change_orient),
418                           NULL);
419
420        /* add the widget to the applet-widget, and thereby actually
421           putting it "onto" the panel */
422        applet_widget_add (APPLET_WIDGET (applet), label);
423        gtk_widget_show (applet);
424
425        /* special corba main loop */
426        applet_widget_gtk_main ();
427
428        return 0;
429}
430
431Now we get a signal every time the panel changes it's orientation and we
432can change ours as well. The different values represent the orientation
433a menu/drawer would take were it on the panel, not the actual position
434of the panel. If the Panel "sits" on the bottom edge of the screen you
435will get ORIENT_UP, if it sits on the left edge, you get ORIENT_RIGHT,
436and so on, if the panel is a vertical drawer you get ORIENT_RIGHT or
437ORIENT_LEFT, if it's a horizontal drawer you get ORIENT_UP or ORIENT_DOWN.
438
439Also note that you should bind the event before you do applet_widget_add,
440as the event will be triggered during the add, so that you can set your
441initial orientation.
442
443Panel Size:
444===========
445(not in 1.0)
446
447One things that is very new in the panel now is the Size support. The
448panel supports the following sizes:
449
450Tiny: 24 pixels
451Standard: 48 pixels
452Large: 64 pixels
453Huge: 80 pixels
454
455It would be nice to let your applet pick it's layout so that it doesn't
456stretch the panel out of it's preffered size (the panel is always as
457thick as the thickest applet)
458
459The way this works is very similiar to the way orientation works so
460here is an example:
461
462How to what size the panel on which your applet sits is, fairly simply.
463You bind the "change_pixel_size" signal to the applet, so to modify our
464original hello applet, we'd do:
465
466#include <config.h>
467#include <gnome.h>
468#include <applet-widget.h>
469
470#ifdef HAVE_PANEL_PIXEL_SIZE
471/*this is when the panel size changes*/
472static void
473applet_change_pixel_size(GtkWidget *w, int size, gpointer data)
474{
475        printf("Got size of %d pixels\n",size);
476}
477#endif
478
479int
480main(int argc, char **argv)
481{
482        GtkWidget *applet;
483        GtkWidget *label;
484
485        /* Initialize the i18n stuff */
486        bindtextdomain (PACKAGE, GNOMELOCALEDIR);
487        textdomain (PACKAGE);
488
489        /* intialize, this will basically set up the applet, corba and
490           call gnome_init */
491        applet_widget_init("hello_applet", NULL, argc, argv, NULL, 0, NULL);
492
493        /* create a new applet_widget */
494        applet = applet_widget_new("hello_applet");
495        /* in the rare case that the communication with the panel
496           failed, error out */
497        if (!applet)
498                g_error("Can't create applet!\n");
499
500        /* create the widget we are going to put on the applet */
501        label = gtk_label_new(_("Hello There!"));
502        gtk_widget_show(label);
503
504#ifdef HAVE_PANEL_PIXEL_SIZE
505        /*we have to bind change_pixel_size before we do applet_widget_add
506          since we need to get an initial change_pixel_size signal to set our
507          initial size, and we get that during the _add call*/
508        gtk_signal_connect(GTK_OBJECT(applet),"change_pixel_size",
509                           GTK_SIGNAL_FUNC(applet_change_pixel_size),
510                           NULL);
511#endif
512
513        /* add the widget to the applet-widget, and thereby actually
514           putting it "onto" the panel */
515        applet_widget_add (APPLET_WIDGET (applet), label);
516        gtk_widget_show (applet);
517
518        /* special corba main loop */
519        applet_widget_gtk_main ();
520
521        return 0;
522}
523
524Notice the "#ifdef HAVE_PANEL_PIXEL_SIZE" line, this will make sure your applet
525compiles correctly even on a panel from gnome-core 1.0 which doesn't have
526support for multiple sizes.  Note that in gnome-core 1.1 release there was
527another implementation of panel sizes which is now deprecated, so you should
528use the method above.
529
530If you want to say compare to the standard sizes (You shouldn't assume that
531they are the only ones that exist!), you can use the PIXEL_SIZE_TINY,
532PIXEL_SIZE_STANDARD, PIXEL_SIZE_LARGE and PIXEL_SIZE_HUGE constants as in
533
534if(size < PIXEL_SIZE_STANDARD) {
535        ... do something for very small applet ...
536} else {
537        ... do something else for standard size applet ...
538}
539
540Rebinding events (Rebuilding the widget structure on the applet):
541=================================================================
542(not in 1.0)
543
544Sometimes you want to change the way the applet looks after it has already
545been added to the panel, and you want the right and middle mouse button
546clicks to still work. In this case you need to notify the panel that it
547should try to rebind the events. You do this with:
548
549#ifdef HAVE_APPLET_BIND_EVENTS
550applet_widget_bind_events(APPLET_WIDGET(applet),GTK_WIDGET(widget));
551#endif
552
553which will bind mouseclicks (2nd and 3rd button) on the "widget".
554
555Note that this is NOT in the panel in gnome-core 1.0, so if you use this
556feature, make sure to put the "#ifdef HAVE_APPLET_BIND_EVENTS" around the
557code.
558
559Multiple Applet Support:
560========================
561
562Having one process per applet might be ok, but when you have many applets
563it can be quite a hit on the memory. so why not manage multiple applets
564from one process, even different types of applets. Ok here's how it's
565done. For a simple example let's modify our original hello applet to
566make it possible to have multiple instances of it from just one
567executable. We will create a factory corba service that can create new
568instances of the applet.
569
570#include <config.h>
571#include <gnome.h>
572#include <applet-widget.h>
573
574
575/*when we get a command to start a new widget*/
576static GtkWidget *
577applet_start_new_applet(const gchar *goad_id, const gchar **params, gint nparams)
578{
579        GtkWidget *applet;
580        GtkWidget *label;
581
582        /*if we weren't asked to start hello_applet, just return*/
583        if(strcmp(goad_id, "hello_applet")!=0) return NULL;
584
585        /*now we do the same exact thing as we do in the main function for
586          creating the applet*/
587
588        /* create a new applet_widget */
589        applet = applet_widget_new("hello_applet");
590        if (!applet)
591                g_error("Can't create applet!\n");
592
593        /* create the widget we are going to put on the applet */
594        label = gtk_label_new(_("Hello There!"));
595        gtk_widget_show(label);
596
597        /* add the widget to the applet-widget, and thereby actually
598           putting it "onto" the panel */
599        applet_widget_add (APPLET_WIDGET (applet), label);
600        gtk_widget_show (applet);
601
602        /* return the applet widget from this function */
603        return applet;
604}
605
606int
607main(int argc, char **argv)
608{
609        GtkWidget *applet;
610        GtkWidget *label;
611        gchar *goad_id;
612
613        /* Initialize the i18n stuff */
614        bindtextdomain (PACKAGE, GNOMELOCALEDIR);
615        textdomain (PACKAGE);
616
617        /* intialize, this will basically set up the applet, corba and
618           call gnome_init */
619        applet_widget_init("hello_applet", NULL, argc, argv, NULL, 0, NULL);
620
621        /*make new factory and get us the goad_id that was used to start us*/
622        applet_factory_new("hello_applet_factory", NULL, applet_start_new_applet);
623        goad_id = (gchar *)goad_server_activation_id();
624
625        /*if the goad_id was hello_applet, we create a new instance of
626          our applet, otherwise don't do anything*/
627        if(goad_id && strcmp(goad_id, "hello_applet")==0) {
628                /* create a new applet_widget */
629                applet = applet_widget_new("hello_applet");
630                if (!applet)
631                        g_error("Can't create applet!\n");
632
633                /* create the widget we are going to put on the applet */
634                label = gtk_label_new(_("Hello There!"));
635                gtk_widget_show(label);
636
637                /* add the widget to the applet-widget, and thereby actually
638                   putting it "onto" the panel */
639                applet_widget_add (APPLET_WIDGET (applet), label);
640                gtk_widget_show (applet);
641
642        }
643
644        /* special corba main loop */
645        applet_widget_gtk_main ();
646
647        return 0;
648}
649
650What you will notice is that what we do is just make a factory service
651with applet_factory_new, to which we pass a function pointer to a function
652that just creates new applets for us.
653
654Now we need to create a .gnorba and .desktop files for an applet of this
655type. The .desktop file is the exact same as for normal applets. The
656.gnorba file however must now describe the factory as well.
657
658hello.gnorba:
659
660        [hello_applet_factory]
661        type=exe
662        repo_id=IDL:GNOME/GenericFactory:1.0
663        description=Hello Applet
664        location_info=hello_applet
665
666        [hello_applet]
667        type=factory
668        repo_id=IDL:GNOME/Applet:1.0
669        description=Hello Applet
670        location_info=hello_applet_factory
671
672That's it.
673
674Now sometimes you may want to have two applets that have very similiar
675functionality, but that appear to the user as two different applets, and
676you want to manage them from the same process. This is extremely simple.
677Just take the above example and add more types into the .gnorba file, then
678wherever we check the goad_id, just add another "else if" to check for
679another goad_id. Then in your desktops on the Exec line, you would have say
680
681In one .desktop:
682
683Exec=hello_applet --activate-goad-server=hello_version_1_applet
684
685In another .desktop
686
687Exec=hello_applet --activate-goad-server=hello_version_2_applet
688
689Shared library applets:
690=======================
691
692It is possible to make applets which will not be separate processes, but
693will be loaded directly into the panel. This makes the panel less stable
694if the applet is less stable.
695
696FIXME! Need explanation, examples.
697
698Building the applets:
699=====================
700
701Here's a simple makefile you can use (this one is for the fish applet) if
702you want to compile applets outside of the gnome-core source tree. It was
703sent to me by John Ellis <johne@bellatlantic.net>.
704
705FIXME! We need a more up to date example.
706
707It's all quite simple isn't it?
708
709George <jirka@5z.com>
Note: See TracBrowser for help on using the repository browser.