Heavy rewriting in many areas
authormartyj19 <martyj19@comcast.net>
Wed, 8 Jul 2009 19:43:43 +0000 (19:43 +0000)
committermartyj19 <martyj19@comcast.net>
Wed, 8 Jul 2009 19:43:43 +0000 (19:43 +0000)
Reference release notes in mailing list lxde-list and on forum
Bump release ID to 0.4.990
New wincmd icon from http://www.oxygen-icons.org/ (licensed under LGPL).
- Given proper attribution in AUTHORS
Updated LINGUAS, oversight on one language

46 files changed:
AUTHORS
configure.ac
data/Makefile.am
data/images/window-manager.png [new file with mode: 0644]
data/ui/panel-pref.glade
man/lxpanel.xml
man/lxpanelctl.xml
po/LINGUAS
po/POTFILES.in
src/Makefile.am
src/configurator.c
src/gtk-run.c
src/icon-grid.c [new file with mode: 0644]
src/icon-grid.h [new file with mode: 0644]
src/lxpanelctl.c
src/menu-policy.c [new file with mode: 0644]
src/menu-policy.h [new file with mode: 0644]
src/misc.c
src/misc.h
src/panel.c
src/panel.h
src/plugin.c
src/plugin.h
src/plugins/Makefile.am
src/plugins/batt/batt.c
src/plugins/cpu/cpu.c
src/plugins/dclock.c
src/plugins/deskno/deskno.c
src/plugins/dirmenu.c
src/plugins/kbled/kbled.c
src/plugins/launchbar.c
src/plugins/menu.c
src/plugins/netstat/netstat.c
src/plugins/netstatus/netstatus.c
src/plugins/pager.c
src/plugins/separator.c
src/plugins/space.c
src/plugins/taskbar.c
src/plugins/thermal/thermal.c
src/plugins/tray.c [new file with mode: 0644]
src/plugins/volume/volume.c
src/plugins/volumealsa/volumealsa.c
src/plugins/wincmd.c
src/plugins/xkb/xkb-plugin.c
src/plugins/xkb/xkb.c
src/plugins/xkb/xkb.h

diff --git a/AUTHORS b/AUTHORS
index a8bab6d..a07869b 100644 (file)
--- a/AUTHORS
+++ b/AUTHORS
@@ -8,6 +8,7 @@ LXPanel - Lightweight X11 desktop panel
     Fred Chien <cfsghost@gmail.com>
     Daniel Kesler <kesler.daniel@gmail.com>
     Juergen Hoetzel <juergen@archlinux.org>
+    Marty Jack <martyj19@comcast.net>
 
 [History]
 LXPanel is a derivative work from fbpanel [1] written by Anatoly Asviyan,
@@ -32,6 +33,7 @@ modify existing projects to serve our needs. :-)
   gtk+ package. It's originally developed by gtk+ developers, but it contains
   some bugs. So a fixed version is included here.
 
+Window commands plugin icon from http://www.oxygen-icons.org/ (licensed under LGPL)
+
 [1] fbpanel, http://fbpanel.sourceforge.net/
 [2] fspanel, http://freshmeat.net/projects/fspanel/
-
index 34bb26b..26c2cc1 100644 (file)
@@ -1,5 +1,5 @@
 AC_PREREQ(2.53)
-AC_INIT(lxpanel, 0.4.1, http://lxde.org/)
+AC_INIT(lxpanel, 0.4.990, http://lxde.org/)
 AM_INIT_AUTOMAKE
 AC_CONFIG_SRCDIR([src/bg.c])
 AC_CONFIG_HEADER([config.h])
@@ -16,7 +16,7 @@ AC_PROG_INTLTOOL(, [no-xml])
 AM_PROG_CC_C_O
 
 # Checks for libraries.
-pkg_modules="gtk+-2.0 >= 2.12.0 \
+pkg_modules="gtk+-2.0 >= 2.14.0 \
              gthread-2.0"
 #             libstartup-notification-1.0"
 PKG_CHECK_MODULES(PACKAGE, [$pkg_modules])
index 9399429..c7c501f 100644 (file)
@@ -50,7 +50,8 @@ lxpanel_images_DATA = \
        images/numlock-on.png \
        images/numlock-off.png \
        images/scrllock-on.png \
-       images/scrllock-off.png
+       images/scrllock-off.png \
+       images/window-manager.png
 
 
 lxpanel_imagesdir = $(datadir)/lxpanel/images
diff --git a/data/images/window-manager.png b/data/images/window-manager.png
new file mode 100644 (file)
index 0000000..bf8714e
Binary files /dev/null and b/data/images/window-manager.png differ
index 785dc77..9fa0198 100644 (file)
@@ -4,7 +4,7 @@
   <!-- interface-naming-policy toplevel-contextual -->
   <object class="GtkListStore" id="liststore1">
     <columns>
-      <!-- column-name item text -->
+      <!-- column-name item -->
       <column type="gchararray"/>
     </columns>
     <data>
@@ -21,7 +21,7 @@
   </object>
   <object class="GtkListStore" id="liststore2">
     <columns>
-      <!-- column-name item text -->
+      <!-- column-name item -->
       <column type="gchararray"/>
     </columns>
     <data>
                             <property name="spacing">2</property>
                             <child>
                               <object class="GtkRadioButton" id="bg_none">
-                                <property name="label" translatable="yes">None (Use system theme)</property>
+                                <property name="label" translatable="yes">System theme</property>
                                 <property name="visible">True</property>
                                 <property name="can_focus">True</property>
                                 <property name="receives_default">False</property>
                             <child>
                               <object class="GtkHBox" id="hbox2">
                                 <property name="visible">True</property>
-                                <property name="spacing">12</property>
+                                <property name="spacing">10</property>
                                 <child>
                                   <object class="GtkRadioButton" id="bg_transparency">
-                                    <property name="label" translatable="yes">Enable Transparency</property>
+                                    <property name="label" translatable="yes">Solid color (with opacity)</property>
                                     <property name="visible">True</property>
                                     <property name="can_focus">True</property>
                                     <property name="receives_default">False</property>
                                   </packing>
                                 </child>
                                 <child>
-                                  <object class="GtkHBox" id="hbox3">
+                                  <object class="GtkColorButton" id="tint_clr">
                                     <property name="visible">True</property>
-                                    <property name="spacing">2</property>
-                                    <child>
-                                      <object class="GtkLabel" id="label20">
-                                        <property name="visible">True</property>
-                                        <property name="label" translatable="yes">Tint color:</property>
-                                      </object>
-                                      <packing>
-                                        <property name="expand">False</property>
-                                        <property name="fill">False</property>
-                                        <property name="position">0</property>
-                                      </packing>
-                                    </child>
-                                    <child>
-                                      <object class="GtkColorButton" id="tint_clr">
-                                        <property name="visible">True</property>
-                                        <property name="can_focus">True</property>
-                                        <property name="receives_default">False</property>
-                                        <property name="use_alpha">True</property>
-                                      </object>
-                                      <packing>
-                                        <property name="expand">False</property>
-                                        <property name="fill">False</property>
-                                        <property name="position">1</property>
-                                      </packing>
-                                    </child>
+                                    <property name="can_focus">True</property>
+                                    <property name="receives_default">True</property>
+                                    <property name="use_alpha">True</property>
+                                    <property name="color">#000000000000</property>
                                   </object>
                                   <packing>
                                     <property name="expand">False</property>
+                                    <property name="fill">False</property>
                                     <property name="position">1</property>
                                   </packing>
                                 </child>
                             <child>
                               <object class="GtkHBox" id="hbox4">
                                 <property name="visible">True</property>
+                                <property name="spacing">40</property>
                                 <child>
                                   <object class="GtkRadioButton" id="bg_image">
-                                    <property name="label" translatable="yes">Enable Image:</property>
+                                    <property name="label" translatable="yes">Image</property>
                                     <property name="visible">True</property>
                                     <property name="can_focus">True</property>
                                     <property name="receives_default">False</property>
                         <child>
                           <object class="GtkHBox" id="hbox1">
                             <property name="visible">True</property>
-                            <property name="spacing">2</property>
+                            <property name="spacing">10</property>
                             <child>
                               <object class="GtkCheckButton" id="use_font_clr">
-                                <property name="label" translatable="yes">Custom Color</property>
+                                <property name="label" translatable="yes">Custom color</property>
                                 <property name="visible">True</property>
                                 <property name="can_focus">True</property>
                                 <property name="receives_default">False</property>
                     </child>
                     <child>
                       <object class="GtkButton" id="moveup_btn">
+                        <property name="label">gtk-go-up</property>
                         <property name="visible">True</property>
                         <property name="can_focus">True</property>
                         <property name="receives_default">False</property>
-                        <child>
-                          <object class="GtkImage" id="image1">
-                            <property name="visible">True</property>
-                            <property name="stock">gtk-go-up</property>
-                          </object>
-                        </child>
+                        <property name="use_stock">True</property>
                       </object>
                       <packing>
                         <property name="expand">False</property>
                     </child>
                     <child>
                       <object class="GtkButton" id="movedown_btn">
+                        <property name="label">gtk-go-down</property>
                         <property name="visible">True</property>
                         <property name="can_focus">True</property>
                         <property name="receives_default">False</property>
-                        <child>
-                          <object class="GtkImage" id="image2">
-                            <property name="visible">True</property>
-                            <property name="stock">gtk-go-down</property>
-                          </object>
-                        </child>
+                        <property name="use_stock">True</property>
                       </object>
                       <packing>
                         <property name="expand">False</property>
index 14207a0..0936c51 100644 (file)
@@ -33,7 +33,7 @@
 >lxpanel</refname>
 
     <refpurpose
->a lightweight GTK2-based panel for LXDE desktop.</refpurpose>
+>a lightweight GTK2-based panel for the LXDE desktop.</refpurpose>
   </refnamediv>
   <refsynopsisdiv
 >    <cmdsynopsis
@@ -53,7 +53,7 @@
     <para
 ><command
 >lxpanel</command> is a program that provides a panel
-    for desktop, usually for LXDE. It is lightweight GTK+ 2.x based desktop 
+    for the desktop, usually for LXDE. It is a lightweight GTK+ 2.x based desktop 
     panel.
     </para>
    
@@ -97,7 +97,7 @@ mark="bullet"
 >Volume control plug-in (optional)</para></listitem>
        <listitem
 ><para
->lxpanelctl, an external controller let you control lxpanel in
+>lxpanelctl, an external controller lets you control lxpanel in
                other programs. For example, "lxpanelctl run" will show the Run
                dialog in lxpanel, and "lxpanelctl menu" will show the application
                menu. This is useful in key bindings provided by window managers.</para>
index 56351d2..a5c439d 100644 (file)
@@ -85,16 +85,6 @@ choice="req"
       <varlistentry
 >        <term
 ><command
->config</command>
-        </term>
-        <listitem
->          <para
->show config dialog</para>
-        </listitem>
-      </varlistentry>
-      <varlistentry
->        <term
-><command
 >restart</command>
         </term>
         <listitem
index a2b3190..ae40ad0 100644 (file)
@@ -4,6 +4,7 @@ ar
 cs
 da
 de
+el
 es
 et
 eu
index 2b88ff0..8b5b046 100644 (file)
@@ -9,8 +9,6 @@ src/panel.c
 src/plugin.c
 src/gtk-run.c
 
-src/systray/tray.c
-
 # Putting translation of plugins in main catalog is not a good idea...
 src/plugins/cpu/cpu.c
 src/plugins/deskno/deskno.c
@@ -28,6 +26,7 @@ src/plugins/netstatus/netstatus-util.c
 src/plugins/separator.c
 src/plugins/pager.c
 src/plugins/space.c
+src/plugins/tray.c
 src/plugins/xkb/xkb-plugin.c
 src/plugins/wincmd.c
 src/plugins/dirmenu.c
index 12a8c42..831323e 100644 (file)
@@ -17,12 +17,6 @@ INCLUDES = \
 
 BUILTIN_PLUGINS = $(top_builddir)/src/plugins/libbuiltin_plugins.a
 
-TRAY_SOURCES= \
-       systray/eggmarshalers.c systray/eggmarshalers.h \
-       systray/eggtraymanager.c systray/eggtraymanager.h \
-       systray/fixedtip.c systray/fixedtip.h \
-       systray/tray.c
-
 if ENABLE_MENU_CACHE
 MENU_SOURCES = gtk-run.c
 endif
@@ -30,12 +24,13 @@ endif
 lxpanel_SOURCES = \
        glib-mem.h \
        misc.c misc.h \
-       $(TRAY_SOURCES) \
        bg.c bg.h  \
        configurator.c \
        dbg.c dbg.h \
        ev.c ev.h \
        gtkbar.h gtkbar.c \
+       icon-grid.h icon-grid.c \
+       menu-policy.h menu-policy.c \
        panel.c panel.h \
        plugin.c plugin.h \
        $(MENU_SOURCES)
index dd9d5e8..bc51404 100644 (file)
@@ -74,53 +74,6 @@ static void update_toggle_button(GtkWidget *w, gboolean n);
 static void modify_plugin( GtkTreeView* view );
 static gboolean on_entry_focus_out( GtkWidget* edit, GdkEventFocus *evt, gpointer user_data );
 
-/* older versions of glib don't provde these API */
-#if ! GLIB_CHECK_VERSION(2, 8, 0)
-#include <errno.h>
-
-int g_mkdir_with_parents(const gchar *pathname, int mode)
-{
-    struct stat statbuf;
-    char *dir, *sep;
-    dir = g_strdup( pathname );
-    sep = dir[0] == '/' ? dir + 1 : dir;
-    do {
-        sep = strchr( sep, '/' );
-        if( G_LIKELY( sep ) )
-            *sep = '\0';
-
-        if( stat( dir, &statbuf) == 0 )
-        {
-            if( ! S_ISDIR(statbuf.st_mode) )    /* parent not dir */
-                goto err;
-        }
-        else    /* stat failed */
-        {
-            if( errno == ENOENT )   /* not exists */
-            {
-                if( mkdir( dir, mode ) == -1 )
-                    goto err;
-            }
-            else
-                goto err;   /* unknown error */
-        }
-
-        if( G_LIKELY( sep ) )
-        {
-            *sep = '/';
-            ++sep;
-        }
-        else
-            break;
-    }while( sep );
-    g_free( dir );
-    return 0;
-err:
-    g_free( dir );
-    return -1;
-}
-#endif
-
 static void
 response_event(GtkDialog *widget, gint arg1, Panel* panel )
 {
@@ -145,8 +98,9 @@ update_panel_geometry( Panel* p )
     calculate_position(p);
     gtk_widget_set_size_request(p->topgwin, p->aw, p->ah);
     gdk_window_move(p->topgwin->window, p->ax, p->ay);
+    panel_update_background(p);
     panel_establish_autohide(p);
-    panel_set_wm_strut( p );
+    panel_set_wm_strut(p);
 }
 
 static gboolean edge_selector(Panel* p, int edge)
@@ -170,7 +124,7 @@ gboolean panel_edge_available(Panel* p, int edge)
 static void set_edge(Panel* p, int edge)
 {
     p->edge = edge;
-    panel_set_orientation(p);
+    panel_set_panel_configuration_changed(p);
     update_panel_geometry(p);
     panel_update_background(p);
 }
@@ -257,12 +211,23 @@ static void set_width_type( GtkWidget *item, Panel* p )
     spin = (GtkWidget*)g_object_get_data(G_OBJECT(item), "width_spin" );
     t = (widthtype != WIDTH_REQUEST);
     gtk_widget_set_sensitive( spin, t );
-    if (widthtype == WIDTH_PERCENT) {
+    if (widthtype == WIDTH_PERCENT)
+    {
         gtk_spin_button_set_range( (GtkSpinButton*)spin, 0, 100 );
         gtk_spin_button_set_value( (GtkSpinButton*)spin, 100 );
-    } else if  (widthtype == WIDTH_PIXEL) {
-        gtk_spin_button_set_range( (GtkSpinButton*)spin, 0, gdk_screen_width() );
-        gtk_spin_button_set_value( (GtkSpinButton*)spin, gdk_screen_width() );
+    }
+    else if (widthtype == WIDTH_PIXEL)
+    {
+        if ((p->edge == EDGE_TOP) || (p->edge == EDGE_BOTTOM))
+        {
+            gtk_spin_button_set_range( (GtkSpinButton*)spin, 0, gdk_screen_width() );
+            gtk_spin_button_set_value( (GtkSpinButton*)spin, gdk_screen_width() );
+        }
+        else
+        {
+            gtk_spin_button_set_range( (GtkSpinButton*)spin, 0, gdk_screen_height() );
+            gtk_spin_button_set_value( (GtkSpinButton*)spin, gdk_screen_height() );
+        }
     } else
         return;
 
@@ -291,44 +256,37 @@ static void transparency_toggle( GtkWidget *b, Panel* p)
     RET();
 }
 
-static void background_toggle( GtkWidget *b, Panel* p)
+static void background_file_helper(Panel * p, GtkWidget * toggle, GtkFileChooser * file_chooser)
 {
-    GtkWidget* fc = (GtkWidget*)g_object_get_data(G_OBJECT(b), "img_file" );
-    gtk_widget_set_sensitive( fc, gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(b)));
-    if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(b))) {
-        if (!p->background) {
-            p->transparent = 0;
-            p->background = 1;
-            /* Update background immediately. */
-            panel_update_background( p );
-        }
+    char * file = g_strdup(gtk_file_chooser_get_filename(file_chooser));
+    if (file != NULL)
+    {
+        g_free(p->background_file);
+        p->background_file = file;
     }
-}
-
-static void background_changed(GtkFileChooser *file_chooser,  Panel* p )
-{
-    GtkWidget* btn = (GtkWidget*)g_object_get_data( G_OBJECT(file_chooser), "bg_image" );
-    char* file;
 
-    file = g_strdup(gtk_file_chooser_get_filename(file_chooser));
-    if( ! file )
-        return;
-
-    if( p->background_file && 0 == strcmp( p->background_file, file ) )
+    if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(toggle)))
     {
-        g_free( file );
-        return;
+        if ( ! p->background)
+        {
+            p->transparent = FALSE;
+            p->background = TRUE;
+            panel_update_background(p);
+        }
     }
+}
 
-    p->background_file = file;
+static void background_toggle( GtkWidget *b, Panel* p)
+{
+    GtkWidget * fc = (GtkWidget*) g_object_get_data(G_OBJECT(b), "img_file");
+    gtk_widget_set_sensitive(fc, gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(b)));
+    background_file_helper(p, b, GTK_FILE_CHOOSER(fc));
+}
 
-    if( gtk_toggle_button_get_active( (GtkToggleButton*)btn ) )
-    {
-        p->transparent = 0;
-        p->background = 1;
-        /* Update background immediately. */
-        panel_update_background( p );
-    }
+static void background_changed(GtkFileChooser *file_chooser,  Panel* p )
+{
+    GtkWidget * btn = GTK_WIDGET(g_object_get_data(G_OBJECT(file_chooser), "bg_image"));
+    background_file_helper(p, btn, file_chooser);
 }
 
 static void
@@ -341,7 +299,6 @@ background_disable_toggle( GtkWidget *b, Panel* p )
             p->transparent = 0;
             /* Update background immediately. */
             panel_update_background( p );
-            //restart();
         }
     }
 
@@ -352,9 +309,7 @@ static void
 on_font_color_set( GtkColorButton* clr,  Panel* p )
 {
     gtk_color_button_get_color( clr, &p->gfontcolor );
-    /* FIXME: need some better mechanism to update the panel */
-    if( p->usefontcolor )
-        gtk_widget_queue_draw( p->topgwin );
+    panel_set_panel_configuration_changed(p);
 }
 
 static void
@@ -363,7 +318,6 @@ on_tint_color_set( GtkColorButton* clr,  Panel* p )
     gtk_color_button_get_color( clr, &p->gtintcolor );
     p->tintcolor = gcolor2rgb24(&p->gtintcolor);
     p->alpha = gtk_color_button_get_alpha( clr ) / 256;
-    /* FIXME: need some better mechanism to update the panel */
     panel_update_background( p );
 }
 
@@ -376,8 +330,7 @@ on_use_font_color_toggled( GtkToggleButton* btn,   Panel* p )
     else
         gtk_widget_set_sensitive( clr, FALSE );
     p->usefontcolor = gtk_toggle_button_get_active( btn );
-    /* FIXME: need some better mechanism to update the panel */
-    gtk_widget_queue_draw( p->topgwin );
+    panel_set_panel_configuration_changed(p);
 }
 
 static void
@@ -386,8 +339,6 @@ set_dock_type(GtkToggleButton* toggle,  Panel* p )
     p->setdocktype = gtk_toggle_button_get_active(toggle) ? 1 : 0;
     panel_set_dock_type( p );
     update_panel_geometry(p);
-    /* FIXME: apparently, this doesn't work,
-              but we don't know the reason yet! */
 }
 
 static void
@@ -444,18 +395,33 @@ on_plugin_expand_toggled(GtkCellRendererToggle* render, char* path, GtkTreeView*
 
         gtk_tree_model_get( model, &it, COL_DATA, &pl, COL_EXPAND, &expand, -1 );
 
-        /* query the old packing of the plugin widget */
-        gtk_box_query_child_packing( GTK_BOX(pl->panel->box), pl->pwid, &old_expand, &fill, &padding, &pack_type );
-
-        expand = ! expand;
-        pl->expand = expand;
-        gtk_list_store_set( (GtkListStore*)model, &it, COL_EXPAND, expand, -1 );
-        /* apply the new packing with only "expand" modified. */
-        gtk_box_set_child_packing( GTK_BOX(pl->panel->box), pl->pwid, expand, fill, padding, pack_type );
+        if (pl->class->expand_available)
+        {
+            /* Only honor "stretch" if allowed by the plugin. */
+            expand = ! expand;
+            pl->expand = expand;
+            gtk_list_store_set( (GtkListStore*)model, &it, COL_EXPAND, expand, -1 );
+
+            /* Query the old packing of the plugin widget.
+             * Apply the new packing with only "expand" modified. */
+            gtk_box_query_child_packing( GTK_BOX(pl->panel->box), pl->pwid, &old_expand, &fill, &padding, &pack_type );
+            gtk_box_set_child_packing( GTK_BOX(pl->panel->box), pl->pwid, expand, fill, padding, pack_type );
+        }
     }
     gtk_tree_path_free( tp );
 }
 
+static void on_stretch_render(GtkTreeViewColumn * column, GtkCellRenderer * renderer, GtkTreeModel * model, GtkTreeIter * iter, gpointer data)
+{
+    /* Set the control visible depending on whether stretch is available for the plugin.
+     * The g_object_set method is touchy about its parameter, so we can't pass the boolean directly. */
+    Plugin * pl;
+    gtk_tree_model_get(model, iter, COL_DATA, &pl, -1);
+    g_object_set(renderer,
+        "visible", ((pl->class->expand_available) ? TRUE : FALSE),
+        NULL);
+}
+
 static void init_plugin_list( Panel* p, GtkTreeView* view, GtkWidget* label )
 {
     GtkListStore* list;
@@ -481,6 +447,7 @@ static void init_plugin_list( Panel* p, GtkTreeView* view, GtkWidget* label )
             _("Stretch"),
             render, "active", COL_EXPAND, NULL );
     gtk_tree_view_column_set_expand( col, FALSE );
+    gtk_tree_view_column_set_cell_data_func(col, render, on_stretch_render, NULL, NULL);
     gtk_tree_view_append_column( view, col );
 
     list = gtk_list_store_new( N_COLS, G_TYPE_STRING, G_TYPE_BOOLEAN, G_TYPE_POINTER );
@@ -492,7 +459,8 @@ static void init_plugin_list( Panel* p, GtkTreeView* view, GtkWidget* label )
         gtk_list_store_set( list, &it,
                             COL_NAME, _(pl->class->name),
                             COL_EXPAND, pl->expand,
-                            COL_DATA, pl, -1);
+                            COL_DATA, pl,
+                            -1);
     }
     gtk_tree_view_set_model( view, GTK_TREE_MODEL( list ) );
     g_signal_connect( view, "row-activated",
@@ -529,18 +497,23 @@ static void on_add_plugin_response( GtkDialog* dlg,
                 GtkTreePath* tree_path;
 
                 pl->panel = p;
+                if (pl->class->expand_default) pl->expand = TRUE;
                 plugin_start( pl, NULL );
                 p->plugins = g_list_append(p->plugins, pl);
                 /* FIXME: will show all cause problems? */
-                gtk_widget_show_all( pl->pwid );
+                if (pl->pwid)
+                {
+                    gtk_widget_show_all( pl->pwid );
 
-                /* update background of the newly added plugin */
-                plugin_widget_set_background( pl->pwid, pl->panel );
+                    /* update background of the newly added plugin */
+                    plugin_widget_set_background( pl->pwid, pl->panel );
+                }
 
                 model = gtk_tree_view_get_model( _view );
                 gtk_list_store_append( (GtkListStore*)model, &it );
                 gtk_list_store_set( (GtkListStore*)model, &it,
                                     COL_NAME, _(pl->class->name),
+                                    COL_EXPAND, pl->expand,
                                     COL_DATA, pl, -1 );
                 tree_sel = gtk_tree_view_get_selection( _view );
                 gtk_tree_selection_select_iter( tree_sel, &it );
@@ -622,7 +595,8 @@ static void on_add_plugin( GtkButton* btn, GtkTreeView* _view )
             gtk_list_store_append( list, &it );
             gtk_list_store_set( list, &it,
                                 0, _(pc->name),
-                                1, pc->type, -1 );
+                                1, pc->type,
+                                -1 );
             /* g_debug( "%s (%s)", pc->type, _(pc->name) ); */
         }
     }
@@ -657,8 +631,7 @@ static void on_remove_plugin( GtkButton* btn, GtkTreeView* view )
             gtk_tree_path_prev( tree_path );
         gtk_list_store_remove( GTK_LIST_STORE(model), &it );
         p->plugins = g_list_remove( p->plugins, pl );
-        plugin_stop( pl ); /* free the plugin widget & its data */
-        plugin_put( pl ); /* free the lib if necessary */
+        plugin_delete(pl);
 
         gtk_tree_selection_select_path( tree_sel, tree_path );
         gtk_tree_path_free( tree_path );
@@ -809,7 +782,7 @@ update_toggle_button(GtkWidget *w, gboolean n)
 void panel_configure( Panel* p, int sel_page )
 {
     GtkBuilder* builder;
-    GtkWidget *w, *w2, *width, *tint_clr, *img_file;
+    GtkWidget *w, *w2, *width, *tint_clr;
 
     if( p->pref_dialog )
     {
@@ -850,15 +823,15 @@ void panel_configure( Panel* p, int sel_page )
     g_signal_connect(w, "toggled", G_CALLBACK(edge_right_toggle), p);
 
     /* alignment */
-    w = (GtkWidget*)gtk_builder_get_object( builder, "alignment_left" );
+    p->alignment_left_label = w = (GtkWidget*)gtk_builder_get_object( builder, "alignment_left" );
     gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(w), (p->allign == ALLIGN_LEFT));
     g_signal_connect(w, "toggled", G_CALLBACK(align_left_toggle), p);
     w = (GtkWidget*)gtk_builder_get_object( builder, "alignment_center" );
     gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(w), (p->allign == ALLIGN_CENTER));
     g_signal_connect(w, "toggled", G_CALLBACK(align_center_toggle), p);
-    w = (GtkWidget*)gtk_builder_get_object( builder, "alignment_right" );
+    p->alignment_right_label = w = (GtkWidget*)gtk_builder_get_object( builder, "alignment_right" );
     gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(w), (p->allign == ALLIGN_RIGHT));
-    g_signal_connect(w, "toggled", G_CALLBACK(align_left_toggle), p);
+    g_signal_connect(w, "toggled", G_CALLBACK(align_right_toggle), p);
 
     /* margin */
     p->margin_control = w = (GtkWidget*)gtk_builder_get_object( builder, "margin" );
@@ -869,21 +842,20 @@ void panel_configure( Panel* p, int sel_page )
 
     /* size */
     p->width_label = (GtkWidget*)gtk_builder_get_object( builder, "width_label");
-    width = w = (GtkWidget*)gtk_builder_get_object( builder, "width" );
+    p->width_control = w = (GtkWidget*)gtk_builder_get_object( builder, "width" );
     gtk_widget_set_sensitive( w, p->widthtype != WIDTH_REQUEST );
     gint upper = 0;
-    if( p->widthtype == WIDTH_PERCENT) {
+    if( p->widthtype == WIDTH_PERCENT)
         upper = 100;
-    } else if( p->widthtype == WIDTH_PIXEL) {
-        upper = gdk_screen_width();
-    }
+    else if( p->widthtype == WIDTH_PIXEL)
+        upper = (((p->edge == EDGE_TOP) || (p->edge == EDGE_BOTTOM)) ? gdk_screen_width() : gdk_screen_height());
     gtk_spin_button_set_range( (GtkSpinButton*)w, 0, upper );
     gtk_spin_button_set_value( (GtkSpinButton*)w, p->width );
     g_signal_connect( w, "value-changed", G_CALLBACK(set_width), p );
 
     w = (GtkWidget*)gtk_builder_get_object( builder, "width_unit" );
     update_opt_menu( w, p->widthtype - 1 );
-    g_object_set_data(G_OBJECT(w), "width_spin", width );
+    g_object_set_data(G_OBJECT(w), "width_spin", p->width_control );
     g_signal_connect( w, "changed",
                      G_CALLBACK(set_width_type), p);
 
@@ -964,11 +936,10 @@ void panel_configure( Panel* p, int sel_page )
         g_signal_connect(trans, "toggled", G_CALLBACK(transparency_toggle), p);
         g_signal_connect(img, "toggled", G_CALLBACK(background_toggle), p);
 
-        img_file = w = (GtkWidget*)gtk_builder_get_object( builder, "img_file" );
-        g_object_set_data(G_OBJECT(img), "img_file", img_file);
-        gtk_file_chooser_set_current_folder( (GtkFileChooser*)w, PACKAGE_DATA_DIR "/lxpanel/images");
-        if (p->background_file)
-            gtk_file_chooser_set_filename( (GtkFileChooser*)w, p->background_file);
+        w = (GtkWidget*)gtk_builder_get_object( builder, "img_file" );
+        g_object_set_data(G_OBJECT(img), "img_file", w);
+        gtk_file_chooser_set_filename(GTK_FILE_CHOOSER(w),
+            ((p->background_file != NULL) ? p->background_file : PACKAGE_DATA_DIR "/lxpanel/images/background.png"));
 
         if (!p->background)
             gtk_widget_set_sensitive( w, FALSE);
@@ -1239,21 +1210,27 @@ inline void generic_config_dlg_save(gpointer panel_gpointer,GObject *where_the_o
     panel_config_save(panel);
 }
 
+/* Handler for "response" signal from standard configuration dialog. */
+static void generic_config_dlg_response(GtkWidget * widget, int response, Plugin * plugin)
+{
+    plugin->panel->plugin_pref_dialog = NULL;
+    gtk_widget_destroy(widget);
+}
+
 /* Parameters: const char* name, gpointer ret_value, GType type, ....NULL */
 GtkWidget* create_generic_config_dlg( const char* title, GtkWidget* parent,
-                                      GSourceFunc apply_func, gpointer plugin,
+                                      GSourceFunc apply_func, Plugin * plugin,
                                       const char* name, ... )
 {
     va_list args;
-    Panel* p = ((Plugin*)plugin)->panel;
+    Panel* p = plugin->panel;
     GtkWidget* dlg = gtk_dialog_new_with_buttons( title, NULL, 0,
                                                   GTK_STOCK_CLOSE,
                                                   GTK_RESPONSE_CLOSE,
                                                   NULL );
     panel_apply_icon(GTK_WINDOW(dlg));
 
-    /* this is a dirty hack.  We need to check if this response is GTK_RESPONSE_CLOSE or not. */
-    g_signal_connect( dlg, "response", G_CALLBACK(gtk_widget_destroy), NULL );
+    g_signal_connect( dlg, "response", G_CALLBACK(generic_config_dlg_response), plugin);
     g_object_weak_ref(G_OBJECT(dlg), generic_config_dlg_save, p);
     if( apply_func )
         g_object_set_data( G_OBJECT(dlg), "apply_func", apply_func );
@@ -1339,10 +1316,14 @@ GtkWidget* create_generic_config_dlg( const char* title, GtkWidget* parent,
     }
     va_end( args );
 
-    /* weird... why this doesn't work? */
-    /* gtk_container_set_border_width( GTK_CONTAINER(GTK_DIALOG(dlg)->vbox), 12 ); */
     gtk_container_set_border_width( GTK_CONTAINER(dlg), 8 );
 
+    /* If there is already a plugin configuration dialog open, close it.
+     * Then record this one in case the panel or plugin is deleted. */
+    if (p->plugin_pref_dialog != NULL)
+        gtk_widget_destroy(p->plugin_pref_dialog);
+    p->plugin_pref_dialog = dlg;
+
     gtk_widget_show_all( dlg );
     return dlg;
 }
@@ -1424,4 +1405,3 @@ lxpanel_get_terminal()
 {
     return terminal_cmd ? terminal_cmd : "lxterminal -e %s";
 }
-
index 9de9289..d34ba30 100644 (file)
@@ -226,7 +226,7 @@ static void on_entry_changed( GtkEntry* entry, GtkImage* img )
         int w, h;
         GdkPixbuf* pix;
         gtk_icon_size_lookup(GTK_ICON_SIZE_DIALOG, &w, &h);
-        pix = lxpanel_load_icon(menu_cache_item_get_icon(MENU_CACHE_ITEM(app)), MAX(w, h), TRUE);
+        pix = lxpanel_load_icon(menu_cache_item_get_icon(MENU_CACHE_ITEM(app)), w, h, TRUE);
         gtk_image_set_from_pixbuf(img, pix);
         g_object_unref(pix);
     }
diff --git a/src/icon-grid.c b/src/icon-grid.c
new file mode 100644 (file)
index 0000000..f353731
--- /dev/null
@@ -0,0 +1,379 @@
+/**
+ * Copyright (c) 2009 LxDE Developers, see the file AUTHORS for details.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <gtk/gtk.h>
+#include <gtk/gtkprivate.h>
+#include <string.h>
+
+#include "icon-grid.h"
+#include "panel.h"
+
+static gboolean icon_grid_placement(IconGrid * ig);
+static void icon_grid_geometry(IconGrid * ig, gboolean layout);
+static void icon_grid_element_size_request(GtkWidget * widget, GtkRequisition * requisition, IconGridElement * ige);
+static void icon_grid_size_request(GtkWidget * widget, GtkRequisition * requisition, IconGrid * ig);
+static void icon_grid_size_allocate(GtkWidget * widget, GtkAllocation * allocation, IconGrid * ig);
+static void icon_grid_demand_resize(IconGrid * ig);
+
+/* Establish the widget placement of an icon grid. */
+static gboolean icon_grid_placement(IconGrid * ig)
+{
+    gtk_widget_show(ig->container);
+
+    GdkWindow * bin_window = gtk_layout_get_bin_window(GTK_LAYOUT(ig->widget));
+    if (bin_window != NULL)
+    {
+        panel_determine_background_pixmap(ig->panel, ig->widget, bin_window);
+       gdk_window_clear(bin_window);
+    }
+
+    /* Reposition each visible child. */
+    int x = ig->border;
+    int y = ig->border;
+    int limit = ig->border + ((ig->orientation == GTK_ORIENTATION_HORIZONTAL)
+        ?  (ig->rows * (ig->child_height + ig->spacing))
+        :  (ig->columns * (ig->child_width + ig->spacing)));
+    IconGridElement * ige;
+    for (ige = ig->child_list; ige != NULL; ige = ige->flink)
+    {
+        if (ige->visible)
+        {
+            gtk_widget_show(ige->widget);
+            gtk_layout_move(GTK_LAYOUT(ig->widget), ige->widget, x, y);
+            gtk_widget_queue_draw(ige->widget);
+            if (ig->orientation == GTK_ORIENTATION_HORIZONTAL)
+            {
+                y += ig->child_height + ig->spacing;
+                if (y >= limit)
+                {
+                    y = ig->border;
+                    x += ig->child_width + ig->spacing;
+                }
+            }
+            else
+            {
+                x += ig->child_width + ig->spacing;
+                if (x >= limit)
+                {
+                    x = ig->border;
+                    y += ig->child_height + ig->spacing;
+                }
+            }
+        }
+    }
+
+    /* Redraw the container. */
+    if (bin_window != NULL)
+       gdk_window_invalidate_rect(bin_window, NULL, TRUE);
+    gtk_widget_queue_draw(ig->container);
+    return FALSE;
+}
+
+/* Establish the geometry of an icon grid. */
+static void icon_grid_geometry(IconGrid * ig, gboolean layout)
+{
+    /* Count visible children. */
+    int visible_children = 0;
+    IconGridElement * ige;
+    for (ige = ig->child_list; ige != NULL; ige = ige->flink)
+        if (ige->visible)
+            visible_children += 1;
+
+   int original_rows = ig->rows;
+   int original_columns = ig->columns;
+   int target_dimension = ig->target_dimension;
+   if (ig->orientation == GTK_ORIENTATION_HORIZONTAL)
+    {
+        /* In horizontal orientation, fit as many rows into the available height as possible.
+         * Then allocate as many columns as necessary.  Guard against zerodivides. */
+        if (ig->container->allocation.height > 1)
+            target_dimension = ig->container->allocation.height;
+        ig->rows = 0;
+        if ((ig->child_height + ig->spacing) != 0)
+            ig->rows = (target_dimension + ig->spacing - ig->border * 2) / (ig->child_height + ig->spacing);
+        if (ig->rows == 0)
+            ig->rows = 1;
+        ig->columns = (visible_children + (ig->rows - 1)) / ig->rows;
+        if ((ig->columns == 1) && (ig->rows > visible_children))
+            ig->rows = visible_children;
+    }
+    else
+    {
+        /* In vertical orientation, fit as many columns into the available width as possible.
+         * Then allocate as many rows as necessary.  Guard against zerodivides. */
+        if (ig->container->allocation.width > 1)
+            target_dimension = ig->container->allocation.width;
+        ig->columns = 0;
+        if ((ig->child_width + ig->spacing) != 0)
+            ig->columns = (target_dimension + ig->spacing - ig->border * 2) / (ig->child_width + ig->spacing);
+        if (ig->columns == 0)
+            ig->columns = 1;
+        ig->rows = (visible_children + (ig->columns - 1)) / ig->columns;
+        if ((ig->rows == 1) && (ig->columns > visible_children))
+            ig->columns = visible_children;
+    }
+
+    /* If the table geometry or child composition changed, redo the placement of children in table cells.
+     * This is gated by having a valid table allocation and by the "layout" parameter, which prevents a recursive loop.
+     * We do the placement later, also to prevent a recursive loop. */
+    if ((layout)
+    && (( ! ig->actual_dimension)
+      || (ig->rows != original_rows) || (ig->columns != original_columns)
+      || (ig->children_changed)))
+        {
+        ig->actual_dimension = TRUE;
+        ig->children_changed = FALSE;
+        g_idle_add((GSourceFunc) icon_grid_placement, ig);
+        }
+}
+
+/* Handler for "size-request" event on the icon grid element. */
+static void icon_grid_element_size_request(GtkWidget * widget, GtkRequisition * requisition, IconGridElement * ige)
+{
+    /* This is our opportunity to request space for the element. */
+    IconGrid * ig = ige->ig;
+    requisition->width = ig->child_width;
+    requisition->height = ig->child_height;
+}
+
+/* Handler for "size-request" event on the icon grid's container. */
+static void icon_grid_size_request(GtkWidget * widget, GtkRequisition * requisition, IconGrid * ig)
+{
+    /* This is our opportunity to request space for the layout container.
+     * Compute the geometry.  Do not attach children at this time to avoid a recursive loop. */
+    icon_grid_geometry(ig, FALSE);
+
+    /* Compute the requisition. */
+    if ((ig->columns == 0) || (ig->rows == 0))
+    {
+        requisition->width = 1;
+        requisition->height = 1;
+        gtk_widget_hide(ig->widget);   /* Necessary to get the plugin to disappear */
+    }
+    else
+    {
+        int column_spaces = ig->columns - 1;
+        int row_spaces = ig->rows - 1;
+        if (column_spaces < 0) column_spaces = 0;
+        if (row_spaces < 0) row_spaces = 0;
+        requisition->width = ig->child_width * ig->columns + column_spaces * ig->spacing + 2 * ig->border;
+        requisition->height = ig->child_height * ig->rows + row_spaces * ig->spacing + 2 * ig->border;
+        gtk_widget_show(ig->widget);
+    }
+}
+
+/* Handler for "size-allocate" event on the icon grid's container. */
+static void icon_grid_size_allocate(GtkWidget * widget, GtkAllocation * allocation, IconGrid * ig)
+{
+    /* This is our notification that there is a resize of the entire panel.
+     * Compute the geometry and recompute layout if the geometry changed. */
+    icon_grid_geometry(ig, TRUE);
+}
+
+/* Initiate a resize. */
+static void icon_grid_demand_resize(IconGrid * ig)
+{
+    ig->children_changed = TRUE;
+    GtkRequisition req;
+    icon_grid_size_request(NULL, &req, ig);
+    gtk_layout_set_size(GTK_LAYOUT(ig->widget), req.width, req.height);
+
+    if ((ig->rows != 0) || (ig->columns != 0))
+        icon_grid_placement(ig);
+}
+
+/* Establish an icon grid in a specified container widget.
+ * The icon grid manages the contents of the container.
+ * The orientation, geometry of the elements, and spacing can be varied.  All elements are the same size. */
+IconGrid * icon_grid_new(
+    Panel * panel, GtkWidget * container, GtkOrientation orientation,
+    gint child_width, gint child_height, gint spacing, gint border, gint target_dimension)
+{
+    /* Create a structure representing the icon grid and collect the parameters. */
+    IconGrid * ig = g_new0(IconGrid, 1);
+    ig->panel = panel;
+    ig->container = container;
+    ig->orientation = orientation;
+    ig->child_width = child_width;
+    ig->child_height = child_height;
+    ig->spacing = spacing;
+    ig->border = border;
+    ig->target_dimension = target_dimension;
+
+    /* Create a layout container. */
+    ig->widget = gtk_layout_new(NULL, NULL);
+    GTK_WIDGET_SET_FLAGS(ig->widget, GTK_NO_WINDOW);
+    gtk_container_add(GTK_CONTAINER(ig->container), ig->widget);
+    gtk_widget_show(ig->widget);
+
+    /* Connect signals. */
+    g_signal_connect(G_OBJECT(ig->widget), "size-request", G_CALLBACK(icon_grid_size_request), (gpointer) ig);
+    g_signal_connect(G_OBJECT(container), "size-request", G_CALLBACK(icon_grid_size_request), (gpointer) ig);
+    g_signal_connect(G_OBJECT(container), "size-allocate", G_CALLBACK(icon_grid_size_allocate), (gpointer) ig);
+    return ig;
+}
+
+/* Add an icon grid element and establish its initial visibility. */
+void icon_grid_add(IconGrid * ig, GtkWidget * child, gboolean visible)
+{
+    /* Create and initialize a structure representing the child. */
+    IconGridElement * ige = g_new0(IconGridElement, 1);
+    ige->ig = ig;
+    ige->widget = child;
+    ige->visible = visible;
+
+    /* Insert at the tail of the child list.  This keeps the graphics in the order they were added. */
+    if (ig->child_list == NULL)
+        ig->child_list = ige;
+    else
+    {
+        IconGridElement * ige_cursor;
+        for (ige_cursor = ig->child_list; ige_cursor->flink != NULL; ige_cursor = ige_cursor->flink) ;
+        ige_cursor->flink = ige;
+    }
+
+    /* Add the widget to the layout container. */
+    gtk_widget_show(ige->widget);
+    gtk_layout_put(GTK_LAYOUT(ig->widget), ige->widget, 0, 0);
+    g_signal_connect(G_OBJECT(child), "size-request", G_CALLBACK(icon_grid_element_size_request), (gpointer) ige);
+
+    /* Do a relayout. */
+    icon_grid_demand_resize(ig);
+}
+
+/* Remove an icon grid element. */
+void icon_grid_remove(IconGrid * ig, GtkWidget * child)
+{
+    IconGridElement * ige_pred = NULL;
+    IconGridElement * ige;
+    for (ige = ig->child_list; ige != NULL; ige_pred = ige, ige = ige->flink)
+    {
+        if (ige->widget == child)
+        {
+            /* The child is found.
+             * Layout containers apparently do not like having their children removed.
+             * Set the child invisible. */
+            icon_grid_set_visible(ig, child, FALSE);
+            icon_grid_demand_resize(ig);
+            break;
+        }
+    }
+}
+
+/* Reorder an icon grid element. */
+extern void icon_grid_reorder_child(IconGrid * ig, GtkWidget * child, gint position)
+{
+    /* Remove the child from its current position. */
+    IconGridElement * ige_pred = NULL;
+    IconGridElement * ige;
+    for (ige = ig->child_list; ige != NULL; ige_pred = ige, ige = ige->flink)
+    {
+        if (ige->widget == child)
+        {
+            if (ige_pred == NULL)
+                ig->child_list = ige->flink;
+            else
+                ige_pred->flink = ige->flink;
+            break;
+        }
+    }
+
+    /* If the child was found, insert it at the new position. */
+    if (ige != NULL)
+    {
+        if (ig->child_list == NULL)
+        {
+            ige->flink = NULL;
+            ig->child_list = ige;
+        }
+        else if (position == 0)
+        {
+            ige->flink = ig->child_list;
+            ig->child_list = ige;
+        }
+        else
+            {
+            gint local_position = position - 1;
+            IconGridElement * ige_cursor;
+            for (
+              ige_cursor = ig->child_list;
+              ((ige_cursor->flink != NULL) && (local_position > 0));
+              local_position -= 1, ige_cursor = ige_cursor->flink) ;
+            ige->flink = ige_cursor->flink;
+            ige_cursor->flink = ige;
+            }
+
+        /* Do a relayout. */
+        if (ige->visible)
+            icon_grid_demand_resize(ig);
+    }
+}
+
+/* Change the orientation of an icon grid. */
+void icon_grid_set_orientation(IconGrid * ig, GtkOrientation orientation, gint target_dimension)
+{
+    ig->orientation = orientation;
+    ig->target_dimension = target_dimension;
+    icon_grid_demand_resize(ig);
+}
+
+/* Change the visibility of an icon grid element. */
+void icon_grid_set_visible(IconGrid * ig, GtkWidget * child, gboolean visible)
+{
+    IconGridElement * ige;
+    for (ige = ig->child_list; ige != NULL; ige = ige->flink)
+    {
+        if (ige->widget == child)
+        {
+            if (ige->visible != visible)
+            {
+                /* Found, and the visibility changed.  Do a relayout. */
+                ige->visible = visible;
+                if ( ! ige->visible)
+                    gtk_widget_hide(ige->widget);
+                icon_grid_demand_resize(ig);
+            }
+            break;
+        }
+    }
+}
+
+/* Deallocate the icon grid structures. */
+void icon_grid_free(IconGrid * ig)
+{
+    if (ig->widget != NULL)
+        gtk_widget_hide(ig->widget);
+
+    /* Remove all child elements. */
+    IconGridElement * ige;
+    for (ige = ig->child_list; ige != NULL; ige = ige->flink)
+        icon_grid_remove(ig, ige->widget);
+
+    /* Get the empty widget redrawn. */
+    icon_grid_demand_resize(ig);
+    while (gtk_events_pending()) gtk_main_iteration();
+
+    /* Free all memory. */
+    while (ig->child_list != NULL)
+    {
+        IconGridElement * ige_succ = ig->child_list->flink;
+        g_free(ig->child_list);
+        ig->child_list = ige_succ;
+    }
+    g_free(ig);
+}
diff --git a/src/icon-grid.h b/src/icon-grid.h
new file mode 100644 (file)
index 0000000..4aef4c2
--- /dev/null
@@ -0,0 +1,71 @@
+/**
+ * Copyright (c) 2009 LxDE Developers, see the file AUTHORS for details.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef ICON_GRID_H
+#define ICON_GRID_H
+
+#include <gtk/gtk.h>
+
+#include "panel.h"
+
+struct _icon_grid_element;
+struct _icon_grid;
+
+/* Representative of an icon grid element.  This is a widget to be packed into a rectangular grid whose size adapts to conditions. */
+typedef struct _icon_grid_element {
+    struct _icon_grid_element * flink;         /* Forward link */
+    struct _icon_grid * ig;                    /* Back pointer to IconGrid */
+    GtkWidget * widget;                                /* Customer's widget */
+    gboolean visible;                          /* True if widget is visible */
+} IconGridElement;
+
+/* Representative of an icon grid.  This is a manager that packs widgets into a rectangular grid whose size adapts to conditions. */
+typedef struct _icon_grid {
+    IconGridElement * child_list;              /* List of icon grid elements */
+    Panel * panel;                             /* Back pointer to panel */
+    GtkWidget * container;                     /* Container widget */
+    GtkOrientation orientation;                        /* Desired orientation */
+    gint child_width;                          /* Desired child width */
+    gint child_height;                         /* Desired child height */
+    gint spacing;                              /* Desired spacing between grid elements */
+    gint border;                               /* Desired border around grid elements */
+    gint target_dimension;                     /* Desired dimension perpendicular to orientation */
+    gboolean actual_dimension;                 /* True if container has been allocated space */
+    gboolean children_changed;                 /* True if icon grid element list changed */
+    GtkWidget * widget;                                /* Layout widget we use for packing */
+    int rows;                                  /* Computed layout rows */
+    int columns;                               /* Computed layout columns */
+} IconGrid;
+
+extern IconGrid * icon_grid_new(
+    Panel * panel, GtkWidget * container, GtkOrientation orientation,
+    gint child_width, gint child_height, gint spacing, gint border, gint target_dimension);
+                                               /* Create an icon grid */
+extern void icon_grid_set_orientation(IconGrid * ig, GtkOrientation orientation, gint target_dimension);
+                                               /* Change the orientation of an icon grid */
+extern void icon_grid_add(IconGrid * ig, GtkWidget * child, gboolean visible);
+                                               /* Add a child to the icon grid */
+extern void icon_grid_remove(IconGrid * ig, GtkWidget * child);
+                                               /* Remove a child from the icon grid */
+extern void icon_grid_reorder_child(IconGrid * ig, GtkWidget * child, gint position);
+                                               /* Reorder the position of a child in the icon grid */
+extern void icon_grid_set_visible(IconGrid * ig, GtkWidget * child, gboolean visible);
+                                               /* Set the visibility of a child in the icon grid */
+extern void icon_grid_free(IconGrid * ig);     /* Free the icon grid */
+
+#endif
index 16fde81..d8f5ad9 100644 (file)
@@ -34,7 +34,7 @@ static const char usage[] =
         "Available commands:\n"
         "menu\tshow system menu\n"
         "run\tshow run dialog\n"
-        "config\tshow config dialog\n"
+//       "config\tshow config dialog\n"
         "restart\trestart lxpanel\n"
         "exit\texit lxpanel\n\n";
 
@@ -44,8 +44,8 @@ static int get_cmd( const char* cmd )
         return LXPANEL_CMD_SYS_MENU;
     else if( ! strcmp( cmd, "run") )
         return LXPANEL_CMD_RUN;
-    else if( ! strcmp( cmd, "config") )
-        return LXPANEL_CMD_CONFIG;
+//    else if( ! strcmp( cmd, "config") )
+//        return LXPANEL_CMD_CONFIG;
     else if( ! strcmp( cmd, "restart") )
         return LXPANEL_CMD_RESTART;
     else if( ! strcmp( cmd, "exit") )
diff --git a/src/menu-policy.c b/src/menu-policy.c
new file mode 100644 (file)
index 0000000..43bbccf
--- /dev/null
@@ -0,0 +1,39 @@
+/**
+ * Copyright (c) 2009 LxDE Developers, see the file AUTHORS for details.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <menu-cache.h>
+#include <glib.h>
+
+#include "menu-policy.h"
+#include "panel.h"
+
+/* Allocate a menu cache. */
+MenuCache * panel_menu_cache_new(void)
+{
+    if (g_getenv("XDG_MENU_PREFIX") == NULL)
+        g_setenv("XDG_MENU_PREFIX", "lxde-", TRUE);
+    return menu_cache_lookup("applications.menu");
+}
+
+/* Evaluate the visibility of a menu item. */
+gboolean panel_menu_item_evaluate_visibility(MenuCacheItem * item)
+{
+    return menu_cache_app_get_is_visible(
+        MENU_CACHE_APP(item),
+        ((is_in_lxde) ? SHOW_IN_LXDE : SHOW_IN_LXDE | SHOW_IN_GNOME | SHOW_IN_KDE | SHOW_IN_XFCE));
+}
diff --git a/src/menu-policy.h b/src/menu-policy.h
new file mode 100644 (file)
index 0000000..996643d
--- /dev/null
@@ -0,0 +1,27 @@
+/**
+ * Copyright (c) 2009 LxDE Developers, see the file AUTHORS for details.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MENU_POLICY_H
+#define MENU_POLICY_H
+
+#include <menu-cache.h>
+
+extern MenuCache * panel_menu_cache_new(void);                                 /* Allocate a menu cache */
+extern gboolean panel_menu_item_evaluate_visibility(MenuCacheItem * item);     /* Evaluate the visibility of a menu item */
+
+#endif
index f415598..8d24695 100644 (file)
@@ -35,8 +35,7 @@
 #include "dbg.h"
 
 /* data used by themed images buttons */
-typedef struct
-{
+typedef struct {
     char* fname;
     guint theme_changed_handler;
     GdkPixbuf* pixbuf;
@@ -44,14 +43,12 @@ typedef struct
     gulong hicolor;
     int dw, dh; /* desired size */
     gboolean keep_ratio;
-}ImgData;
+} ImgData;
 
 static GQuark img_data_id = 0;
 
 static void on_theme_changed(GtkIconTheme* theme, GtkWidget* img);
-void
-_gtk_image_set_from_file_scaled( GtkWidget* img, const gchar *file, gint width,
-        gint height, gboolean keep_ratio);
+static void _gtk_image_set_from_file_scaled( GtkWidget* img, const gchar *file, gint width, gint height, gboolean keep_ratio);
 
 /* X11 data types */
 Atom a_UTF8_STRING;
@@ -98,6 +95,11 @@ Atom a_NET_WM_STRUT_PARTIAL;
 Atom a_NET_WM_ICON;
 Atom a_KDE_NET_WM_SYSTEM_TRAY_WINDOW_FOR;
 
+/* SYSTEM TRAY spec */
+Atom a_NET_SYSTEM_TRAY_OPCODE;
+Atom a_NET_SYSTEM_TRAY_ORIENTATION;
+Atom a_MANAGER;
+
 Atom a_LXPANEL_CMD; /* for private client message */
 
 enum{
@@ -112,6 +114,7 @@ enum{
     I_NET_CLIENT_LIST_STACKING,
     I_NET_NUMBER_OF_DESKTOPS,
     I_NET_CURRENT_DESKTOP,
+    I_NET_DESKTOP_VIEWPORT,
     I_NET_DESKTOP_NAMES,
     I_NET_ACTIVE_WINDOW,
     I_NET_SUPPORTED,
@@ -141,6 +144,10 @@ enum{
     I_NET_WM_ICON,
     I_KDE_NET_WM_SYSTEM_TRAY_WINDOW_FOR,
 
+    I_NET_SYSTEM_TRAY_OPCODE,
+    I_NET_SYSTEM_TRAY_ORIENTATION,
+    I_MANAGER,
+
     I_LXPANEL_CMD,
     N_ATOMS
 };
@@ -352,6 +359,7 @@ void resolve_atoms()
     atom_names[ I_NET_CLIENT_LIST_STACKING ] = "_NET_CLIENT_LIST_STACKING";
     atom_names[ I_NET_NUMBER_OF_DESKTOPS ] = "_NET_NUMBER_OF_DESKTOPS";
     atom_names[ I_NET_CURRENT_DESKTOP ] = "_NET_CURRENT_DESKTOP";
+    atom_names[ I_NET_DESKTOP_VIEWPORT ] = "_NET_DESKTOP_VIEWPORT";
     atom_names[ I_NET_DESKTOP_NAMES ] = "_NET_DESKTOP_NAMES";
     atom_names[ I_NET_ACTIVE_WINDOW ] = "_NET_ACTIVE_WINDOW";
     atom_names[ I_NET_SUPPORTED ] = "_NET_SUPPORTED";
@@ -380,6 +388,11 @@ void resolve_atoms()
     atom_names[ I_NET_WM_STRUT_PARTIAL ] = "_NET_WM_STRUT_PARTIAL";
     atom_names[ I_NET_WM_ICON ] = "_NET_WM_ICON";
     atom_names[ I_KDE_NET_WM_SYSTEM_TRAY_WINDOW_FOR ] = "_KDE_NET_WM_SYSTEM_TRAY_WINDOW_FOR";
+
+    atom_names[ I_NET_SYSTEM_TRAY_OPCODE ] = "_NET_SYSTEM_TRAY_OPCODE";
+    atom_names[ I_NET_SYSTEM_TRAY_ORIENTATION ] = "_NET_SYSTEM_TRAY_ORIENTATION";
+    atom_names[ I_MANAGER ] = "MANAGER";
+
     atom_names[ I_LXPANEL_CMD ] = "_LXPANEL_CMD";
 
     Atom atoms[ N_ATOMS ];
@@ -404,6 +417,7 @@ void resolve_atoms()
     a_NET_CLIENT_LIST_STACKING = atoms[ I_NET_CLIENT_LIST_STACKING ];
     a_NET_NUMBER_OF_DESKTOPS = atoms[ I_NET_NUMBER_OF_DESKTOPS ];
     a_NET_CURRENT_DESKTOP = atoms[ I_NET_CURRENT_DESKTOP ];
+    a_NET_DESKTOP_VIEWPORT = atoms[ I_NET_DESKTOP_VIEWPORT ];
     a_NET_DESKTOP_NAMES = atoms[ I_NET_DESKTOP_NAMES ];
     a_NET_ACTIVE_WINDOW = atoms[ I_NET_ACTIVE_WINDOW ];
     a_NET_SUPPORTED = atoms[ I_NET_SUPPORTED ];
@@ -431,6 +445,10 @@ void resolve_atoms()
     a_NET_WM_ICON = atoms[ I_NET_WM_ICON ];
     a_KDE_NET_WM_SYSTEM_TRAY_WINDOW_FOR = atoms[ I_KDE_NET_WM_SYSTEM_TRAY_WINDOW_FOR ];
 
+    a_NET_SYSTEM_TRAY_OPCODE = atoms[ I_NET_SYSTEM_TRAY_OPCODE ];
+    a_NET_SYSTEM_TRAY_ORIENTATION = atoms[ I_NET_SYSTEM_TRAY_ORIENTATION ];
+    a_MANAGER = atoms[ I_MANAGER ];
+
     a_LXPANEL_CMD = atoms[ I_LXPANEL_CMD ];
 
     RET();
@@ -438,10 +456,9 @@ void resolve_atoms()
 
 
 void
-Xclimsg(Window win, long type, long l0, long l1, long l2, long l3, long l4)
+Xclimsg(Window win, Atom type, long l0, long l1, long l2, long l3, long l4)
 {
     XClientMessageEvent xev;
-
     xev.type = ClientMessage;
     xev.window = win;
     xev.message_type = type;
@@ -453,7 +470,7 @@ Xclimsg(Window win, long type, long l0, long l1, long l2, long l3, long l4)
     xev.data.l[4] = l4;
     XSendEvent(GDK_DISPLAY(), GDK_ROOT_WINDOW(), False,
           (SubstructureNotifyMask | SubstructureRedirectMask),
-          (XEvent *) & xev);
+          (XEvent *) &xev);
 }
 
 void
@@ -870,54 +887,6 @@ expand_tilda(gchar *file)
 
 }
 
-#if 0
-Window
-Select_Window(Display *dpy)
-{
-    int status;
-    Cursor cursor;
-    XEvent event;
-    Window target_win = None, root = RootWindow(dpy,DefaultScreen(dpy));
-    int buttons = 0;
-
-    ENTER;
-    /* Make the target cursor */
-    cursor = XCreateFontCursor(dpy, XC_crosshair);
-
-    /* Grab the pointer using target cursor, letting it room all over */
-    status = XGrabPointer(dpy, root, False,
-          ButtonPressMask|ButtonReleaseMask, GrabModeSync,
-          GrabModeAsync, root, cursor, CurrentTime);
-    if (status != GrabSuccess) {
-        ERR("Can't grab the mouse.");
-        RET(None);
-    }
-    /* Let the user select a window... */
-    while ((target_win == None) || (buttons != 0)) {
-        /* allow one more event */
-        XAllowEvents(dpy, SyncPointer, CurrentTime);
-        XWindowEvent(dpy, root, ButtonPressMask|ButtonReleaseMask, &event);
-        switch (event.type) {
-        case ButtonPress:
-            if (target_win == None) {
-                target_win = event.xbutton.subwindow; /* window selected */
-                DBG("target win = 0x%x\n", target_win);
-                if (target_win == None) target_win = root;
-            }
-            buttons++;
-            break;
-        case ButtonRelease:
-            if (buttons > 0) /* there may have been some down before we started */
-                buttons--;
-            break;
-        }
-    }
-
-    XUngrabPointer(dpy, CurrentTime);      /* Done with pointer */
-    RET(target_win);
-}
-#endif
-
 /*
  * SuxPanel version 0.1
  * Copyright (c) 2003 Leandro Pereira <leandro@linuxmag.com.br>
@@ -930,206 +899,128 @@ Select_Window(Display *dpy)
  *
  */
 
-GdkPixbuf *
-gdk_pixbuf_scale_ratio(GdkPixbuf *p, int width, int height, GdkInterpType itype, gboolean keep_ratio)
-{
-    gfloat w, h, rw, rh;
-
-    if (keep_ratio) {
-        w = gdk_pixbuf_get_width(p);
-        h = gdk_pixbuf_get_height(p);
-        rw = w / width;
-        rh = h / height;
-        if (rw > rh)
-            height = h / rw;
-        else
-            width =  w / rh;
-    }
-    return  gdk_pixbuf_scale_simple(p, width, height, itype);
-
-}
-
-void img_data_free( ImgData* data )
+/* DestroyNotify handler for image data in _gtk_image_new_from_file_scaled. */
+static void img_data_free(ImgData * data)
 {
-    g_free( data->fname );
-    if( data->theme_changed_handler )
-        g_signal_handler_disconnect( gtk_icon_theme_get_default(), data->theme_changed_handler );
-    if( data->pixbuf )
-        g_object_unref( data->pixbuf );
-    if( data->hilight )
-        g_object_unref( data->hilight );
-    g_slice_free( ImgData, data );
+    g_free(data->fname);
+    if (data->theme_changed_handler != 0)
+        g_signal_handler_disconnect(gtk_icon_theme_get_default(), data->theme_changed_handler);
+    if (data->pixbuf != NULL)
+        g_object_unref(data->pixbuf);
+    if (data->hilight != NULL)
+        g_object_unref(data->hilight);
+    g_free(data);
 }
 
-static void on_theme_changed(GtkIconTheme* theme, GtkWidget* img)
+/* Handler for "changed" signal in _gtk_image_new_from_file_scaled. */
+static void on_theme_changed(GtkIconTheme * theme, GtkWidget * img)
 {
-    ImgData* data = (ImgData*)g_object_get_qdata( G_OBJECT(img), img_data_id );
-    /* g_debug("reload icon: %s", data->fname); */
-    _gtk_image_set_from_file_scaled(img, data->fname,
-                    data->dw, data->dh, data->keep_ratio );
+    ImgData * data = (ImgData *) g_object_get_qdata(G_OBJECT(img), img_data_id);
+    _gtk_image_set_from_file_scaled(img, data->fname, data->dw, data->dh, data->keep_ratio);
 }
 
-void fb_button_set_from_file(GtkWidget* btn, const char* img_file)
+void fb_button_set_from_file(GtkWidget * btn, const char * img_file)
 {
-    GtkWidget* child = gtk_bin_get_child(GTK_BIN(btn));
-    GtkWidget* img = NULL;
-
-    if( GTK_IS_IMAGE(child) )
+    /* Locate the image within the button. */
+    GtkWidget * child = gtk_bin_get_child(GTK_BIN(btn));
+    GtkWidget * img = NULL;
+    if (GTK_IS_IMAGE(child))
         img = child;
-    else if( GTK_IS_BOX(child) )
+    else if (GTK_IS_BOX(child))
     {
-        GList* children = gtk_container_get_children(GTK_CONTAINER(child));
+        GList * children = gtk_container_get_children(GTK_CONTAINER(child));
         img = GTK_WIDGET(GTK_IMAGE(children->data));
-        g_list_free( children );
+        g_list_free(children);
     }
 
-    if(G_LIKELY(img))
+    if (img != NULL)
     {
-        ImgData* data = (ImgData*)g_object_get_qdata( G_OBJECT(img), img_data_id );
+        ImgData * data = (ImgData *) g_object_get_qdata(G_OBJECT(img), img_data_id);
         g_free(data->fname);
         data->fname = g_strdup(img_file);
-        _gtk_image_set_from_file_scaled(img, data->fname,
-                        data->dw, data->dh, data->keep_ratio );
-    }
-}
-
-/* FIXME: currently, the size of those images cannot be changed dynamically */
-static void on_img_size_allocated(GtkWidget* img, GtkAllocation *allocation, ImgData* data)
-{
-    int size;
-
-    /* enlarge */
-    if( allocation->width > data->dw ||
-        allocation->height > data->dh )
-    {
-        size = MAX(allocation->width, allocation->height);
-    }
-    /* shrinkage */
-    else if( allocation->width < data->dw ||
-        allocation->height < data->dh )
-    {
-        size = MIN(allocation->width, allocation->height);
-    }
-    else
-        return;
-    data->dw = data->dh = size;
-/*
-    g_debug("size = %d, pix: %d, %d, alloc:%d, %d", size,
-       gdk_pixbuf_get_width(data->pixbuf), gdk_pixbuf_get_height(data->pixbuf),
-       allocation->width, allocation->height );
-*/
-    g_signal_handlers_block_by_func( img, on_img_size_allocated, data );
-
-    if (gdk_pixbuf_get_width(data->pixbuf)!=gdk_pixbuf_get_height(data->pixbuf)) {
-        gtk_widget_set_size_request(img, allocation->width, allocation->height);
-        _gtk_image_set_from_file_scaled( img, data->fname,
-                        allocation->width, allocation->height, data->keep_ratio );
-    } else {
-        gtk_widget_set_size_request(img, size, size);
-        _gtk_image_set_from_file_scaled( img, data->fname,
-                        size, size, data->keep_ratio );
+        _gtk_image_set_from_file_scaled(img, data->fname, data->dw, data->dh, data->keep_ratio);
     }
-//    g_debug("size-allocated: %d, %d", allocation->width, allocation->height);
-
-#if 0
-    /* FIXME: This is a very bad dirty hack! */
-    if( gtk_events_pending() )
-        gtk_main_iteration();
-#endif
-    g_signal_handlers_unblock_by_func( img, on_img_size_allocated, data );
 }
 
-void
-_gtk_image_set_from_file_scaled( GtkWidget* img, const gchar *file, gint width,
-        gint height, gboolean keep_ratio)
+static void _gtk_image_set_from_file_scaled(GtkWidget * img, const gchar * file, gint width, gint height, gboolean keep_ratio)
 {
-    GdkPixbuf *pb_scaled;
-    gboolean themed = TRUE;
-    ImgData* data = (ImgData*)g_object_get_qdata( G_OBJECT(img), img_data_id );
-
+    ImgData * data = (ImgData *) g_object_get_qdata(G_OBJECT(img), img_data_id);
     data->dw = width;
     data->dh = height;
+    data->keep_ratio = keep_ratio;
 
-    if( data->pixbuf )
+    if (data->pixbuf != NULL)
     {
-        g_object_unref( data->pixbuf );
+        g_object_unref(data->pixbuf);
         data->pixbuf = NULL;
     }
+
     /* if there is a cached hilighted version of this pixbuf, free it */
-    if( data->hilight )
+    if (data->hilight != NULL)
     {
-        g_object_unref( data->hilight );
+        g_object_unref(data->hilight);
         data->hilight = NULL;
     }
 
     /* if they are the same string, eliminate unnecessary copy. */
-    if( data->fname != file )
+    gboolean themed = FALSE;
+    if (file != NULL)
     {
-        g_free( data->fname );
-        data->fname = g_strdup(file);
-    }
-    data->keep_ratio = TRUE;
-
-    if( G_UNLIKELY( ! file ) )
-        goto err;
+        if (data->fname != file)
+        {
+            g_free(data->fname);
+            data->fname = g_strdup(file);
+        }
 
-    if( g_file_test(file, G_FILE_TEST_EXISTS) )
-    {
-        pb_scaled = gdk_pixbuf_new_from_file_at_scale( file, width, height,
-                                                       keep_ratio, NULL );
-        if( !pb_scaled )
-            goto err;
-        data->pixbuf = pb_scaled;
-        themed = FALSE;
-    }
-    else
-    {
-        data->pixbuf = lxpanel_load_icon(file, MAX(width, height),TRUE);
-        if( ! data->pixbuf )
-            goto err;
+        if (g_file_test(file, G_FILE_TEST_EXISTS))
+        {
+            GdkPixbuf * pb_scaled = gdk_pixbuf_new_from_file_at_scale(file, width, height, keep_ratio, NULL);
+            if (pb_scaled != NULL)
+                data->pixbuf = pb_scaled;
+        }
+        else
+        {
+            data->pixbuf = lxpanel_load_icon(file, width, height, keep_ratio);
+            themed = TRUE;
+        }
     }
-    gtk_image_set_from_pixbuf((GtkImage *)img, data->pixbuf);
 
-    if( themed ) /* This image is loaded from icon theme */
+    if (data->pixbuf != NULL)
     {
-        /* update the image when icon theme get changed */
-        if( ! data->theme_changed_handler )
+        /* Set the pixbuf into the image widget. */
+        gtk_image_set_from_pixbuf((GtkImage *)img, data->pixbuf);
+        if (themed)
         {
-            data->theme_changed_handler = g_signal_connect( gtk_icon_theme_get_default(), "changed",
-                                            G_CALLBACK(on_theme_changed), img );
+            /* This image is loaded from icon theme.  Update the image if the icon theme is changed. */
+            if (data->theme_changed_handler == 0)
+                data->theme_changed_handler = g_signal_connect(gtk_icon_theme_get_default(), "changed", G_CALLBACK(on_theme_changed), img);
         }
-    }
-    else /* this is not loaded from icon theme */
-    {
-        if( data->theme_changed_handler )
+        else
         {
-            g_signal_handler_disconnect( gtk_icon_theme_get_default(), data->theme_changed_handler );
-            data->theme_changed_handler = 0;
+            /* This is not loaded from icon theme.  Disconnect the signal handler. */
+            if (data->theme_changed_handler != 0)
+            {
+                g_signal_handler_disconnect(gtk_icon_theme_get_default(), data->theme_changed_handler);
+                data->theme_changed_handler = 0;
+            }
         }
     }
+    else
+    {
+        /* No pixbuf available.  Set the "missing image" icon. */
+        gtk_image_set_from_stock(GTK_IMAGE(img), GTK_STOCK_MISSING_IMAGE, GTK_ICON_SIZE_BUTTON);
+    }
     return;
-
- err:
-    gtk_image_set_from_stock((GtkImage *)img, GTK_STOCK_MISSING_IMAGE,
-                                   GTK_ICON_SIZE_BUTTON);
 }
 
-GtkWidget *
-_gtk_image_new_from_file_scaled(const gchar *file, gint width,
-        gint height, gboolean keep_ratio)
+GtkWidget * _gtk_image_new_from_file_scaled(const gchar * file, gint width, gint height, gboolean keep_ratio)
 {
-    GtkWidget *img;
-    ImgData* data;
-
-    img = gtk_image_new();
-    data = g_slice_new0( ImgData );
-    if( G_UNLIKELY( 0 == img_data_id ) )
+    GtkWidget * img = gtk_image_new();
+    ImgData * data = g_new0(ImgData, 1);
+    if (img_data_id == 0)
         img_data_id = g_quark_from_static_string("ImgData");
-    g_object_set_qdata_full( G_OBJECT(img), img_data_id, data, 
-            (GDestroyNotify)img_data_free );
-    _gtk_image_set_from_file_scaled( img, file, width, height, keep_ratio );
-//    gtk_widget_set_size_request(img, width, height);
-    g_signal_connect( img, "size-allocate", G_CALLBACK(on_img_size_allocated), data );
+    g_object_set_qdata_full(G_OBJECT(img), img_data_id, data, (GDestroyNotify) img_data_free);
+    _gtk_image_set_from_file_scaled(img, file, width, height, keep_ratio);
     return img;
 }
 
@@ -1180,181 +1071,112 @@ guint32 gcolor2rgb24(GdkColor *color)
     RET(i);
 }
 
-
-static gboolean
-fb_button_enter (GtkImage *widget, GdkEventCrossing *event)
+/* Handler for "enter-notify-event" signal on image that has highlighting requested. */
+static gboolean fb_button_enter(GtkImage * widget, GdkEventCrossing * event)
 {
-    GdkPixbuf *dark, *light;
-    int i, height, rowstride;
-    gulong hicolor;
-    guchar *src, *up, extra[3];
-    ImgData* data;
-
-    if (gtk_image_get_storage_type(widget) != GTK_IMAGE_PIXBUF)
-        return TRUE;
-
-    data = (ImgData*)g_object_get_qdata( G_OBJECT(widget), img_data_id );
-    if( G_UNLIKELY( ! data ) )
-        return TRUE;
-
-    if( ! data->hilight )
+    if (gtk_image_get_storage_type(widget) == GTK_IMAGE_PIXBUF)
     {
-        dark = data->pixbuf;
-        height = gdk_pixbuf_get_height( dark );
-        rowstride = gdk_pixbuf_get_rowstride( dark );
-        hicolor = data->hicolor;
-
-        light = gdk_pixbuf_add_alpha(dark, FALSE, 0, 0, 0);
-        if( !light )
-            return TRUE;
-        src = gdk_pixbuf_get_pixels(light);
-        for (i = 2; i >= 0; i--, hicolor >>= 8)
-            extra[i] = hicolor & 0xFF;
-        for( up = src + height * rowstride; src < up; src+=4 )
+        ImgData * data = (ImgData *) g_object_get_qdata(G_OBJECT(widget), img_data_id);
+        if (data != NULL)
         {
-            if (src[3] == 0)
-                continue;
-            for (i = 0; i < 3; i++)
+            if (data->hilight == NULL)
             {
-                if (src[i] + extra[i] >= 255)
-                    src[i] = 255;
-                else
-                    src[i] += extra[i];
+                GdkPixbuf * dark = data->pixbuf;
+                int height = gdk_pixbuf_get_height(dark);
+                int rowstride = gdk_pixbuf_get_rowstride(dark);
+                gulong hicolor = data->hicolor;
+
+                GdkPixbuf * light = gdk_pixbuf_add_alpha(dark, FALSE, 0, 0, 0);
+                if (light != NULL)
+                {
+                    guchar extra[3];
+                    int i;
+                    for (i = 2; i >= 0; i--, hicolor >>= 8)
+                        extra[i] = hicolor & 0xFF;
+
+                    guchar * src = gdk_pixbuf_get_pixels(light);
+                    guchar * up;
+                    for (up = src + height * rowstride; src < up; src += 4)
+                    {
+                        if (src[3] != 0)
+                        {
+                            for (i = 0; i < 3; i++)
+                            {
+                            int value = src[i] + extra[i];
+                            if (value > 255) value = 255;
+                            src[i] = value;
+                            }
+                        }
+                    }
+                data->hilight = light;
+                }
             }
+
+        if (data->hilight != NULL)
+            gtk_image_set_from_pixbuf(widget, data->hilight);
         }
-        data->hilight = light;
     }
-
-    if( G_LIKELY( data->hilight ) )
-        gtk_image_set_from_pixbuf(widget, data->hilight);
     return TRUE;
 }
 
-static gboolean
-fb_button_leave (GtkImage *widget, GdkEventCrossing *event, gpointer user_data)
+/* Handler for "leave-notify-event" signal on image that has highlighting requested. */
+static gboolean fb_button_leave(GtkImage * widget, GdkEventCrossing * event, gpointer user_data)
 {
-    ImgData* data;
-
-    if (gtk_image_get_storage_type(widget) != GTK_IMAGE_PIXBUF)
-        return TRUE;
-
-    data = (ImgData*)g_object_get_qdata( G_OBJECT(widget), img_data_id );
-    if( data && data->pixbuf )
-        gtk_image_set_from_pixbuf(widget, data->pixbuf);
-
+    if (gtk_image_get_storage_type(widget) == GTK_IMAGE_PIXBUF)
+    {
+        ImgData * data = (ImgData *) g_object_get_qdata(G_OBJECT(widget), img_data_id);
+        if ((data != NULL) && (data->pixbuf != NULL))
+            gtk_image_set_from_pixbuf(widget, data->pixbuf);
+    }
     return TRUE;
 }
 
 
-GtkWidget *
-fb_button_new_from_file(gchar *fname, int width, int height, gulong hicolor, gboolean keep_ratio)
+GtkWidget * fb_button_new_from_file(
+    gchar * image_file, int width, int height, gulong highlight_color, gboolean keep_ratio)
 {
-    GtkWidget *b, *image;
-    ENTER;
-    b = gtk_event_box_new();
-    gtk_container_set_border_width(GTK_CONTAINER(b), 0);
-    GTK_WIDGET_UNSET_FLAGS (b, GTK_CAN_FOCUS);
-
-    image = _gtk_image_new_from_file_scaled(fname, width, height, keep_ratio);
-    gtk_misc_set_padding (GTK_MISC(image), 0, 0);
-    gtk_misc_set_alignment( (GtkMisc*)image, 0, 0 );
-
-    if(hicolor > 0)
-    {
-        ImgData* data = (ImgData*)g_object_get_qdata( G_OBJECT(image), img_data_id );
-        data->hicolor = hicolor;
-
-        gtk_widget_add_events(b, GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK);
-        g_signal_connect_swapped (G_OBJECT (b), "enter-notify-event",
-              G_CALLBACK (fb_button_enter), image);
-        g_signal_connect_swapped (G_OBJECT (b), "leave-notify-event",
-              G_CALLBACK (fb_button_leave), image);
-    }
-    gtk_container_add(GTK_CONTAINER(b), image);
-    gtk_widget_show(image);
-    gtk_widget_show(b);
-    RET(b);
+    return fb_button_new_from_file_with_label(image_file, width, height, highlight_color, keep_ratio, NULL, NULL);
 }
 
-GtkWidget *
-fb_button_new_from_file_with_colorlabel(gchar *fname, int width, int height,
-      gulong hicolor, gulong fcolor, gboolean keep_ratio, gchar *name)
+GtkWidget * fb_button_new_from_file_with_label(
+    gchar * image_file, int width, int height, gulong highlight_color, gboolean keep_ratio, Panel * panel, gchar * label)
 {
-    GtkWidget *b, *image, *box, *label;
-
-    ENTER;
-    b = gtk_event_box_new();
-    gtk_container_set_border_width(GTK_CONTAINER(b), 0);
-    GTK_WIDGET_UNSET_FLAGS (b, GTK_CAN_FOCUS);
-
-    box = gtk_hbox_new(FALSE, 0);
-    gtk_container_set_border_width(GTK_CONTAINER(box), 0);
-    GTK_WIDGET_UNSET_FLAGS (box, GTK_CAN_FOCUS);
-    gtk_container_add(GTK_CONTAINER(b), box);
-
-    image = _gtk_image_new_from_file_scaled(fname, width, height, keep_ratio);
-    gtk_misc_set_padding (GTK_MISC(image), 0, 0);
-    if(hicolor > 0)
+    GtkWidget * event_box = gtk_event_box_new();
+    gtk_container_set_border_width(GTK_CONTAINER(event_box), 0);
+    GTK_WIDGET_UNSET_FLAGS(event_box, GTK_CAN_FOCUS);
+
+    GtkWidget * image = _gtk_image_new_from_file_scaled(image_file, width, height, keep_ratio);
+    gtk_misc_set_padding(GTK_MISC(image), 0, 0);
+    gtk_misc_set_alignment(GTK_MISC(image), 0, 0);
+    if (highlight_color != 0)
     {
-        ImgData* data = (ImgData*)g_object_get_qdata( G_OBJECT(image), img_data_id );
-        data->hicolor = hicolor;
-
-        gtk_widget_add_events(b, GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK);
-        g_signal_connect_swapped (G_OBJECT (b), "enter-notify-event",
-              G_CALLBACK (fb_button_enter), image);
-        g_signal_connect_swapped (G_OBJECT (b), "leave-notify-event",
-              G_CALLBACK (fb_button_leave), image);
-    }
-    gtk_box_pack_start(GTK_BOX(box), image, FALSE, FALSE, 0);
-    if (name) {
-        label =  gtk_label_new("");
-        char *lname = g_strdup_printf("<span color=\"#%06x\">%s</span>", fcolor, name);
-       gtk_label_set_markup(GTK_LABEL(label), lname);
-        gtk_misc_set_padding(GTK_MISC(label), 2, 0);
-       g_free(lname);
-        gtk_box_pack_end(GTK_BOX(box), label, FALSE, FALSE, 0);
-    }
-    gtk_widget_show_all(b);
-    RET(b);
-}
+        ImgData * data = (ImgData *) g_object_get_qdata(G_OBJECT(image), img_data_id);
+        data->hicolor = highlight_color;
 
-GtkWidget *
-fb_button_new_from_file_with_label(gchar *fname, int width, int height,
-      gulong hicolor, gboolean keep_ratio, gchar *name)
-{
-    GtkWidget *b, *image, *box, *label;
+        gtk_widget_add_events(event_box, GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK);
+        g_signal_connect_swapped(G_OBJECT(event_box), "enter-notify-event", G_CALLBACK(fb_button_enter), image);
+        g_signal_connect_swapped(G_OBJECT(event_box), "leave-notify-event", G_CALLBACK(fb_button_leave), image);
+    }
 
-    ENTER;
-    b = gtk_event_box_new();
-    gtk_container_set_border_width(GTK_CONTAINER(b), 0);
-    GTK_WIDGET_UNSET_FLAGS (b, GTK_CAN_FOCUS);
+    if (label == NULL)
+        gtk_container_add(GTK_CONTAINER(event_box), image);
+    else
+    {
+        GtkWidget * inner = gtk_hbox_new(FALSE, 0);
+        gtk_container_set_border_width(GTK_CONTAINER(inner), 0);
+        GTK_WIDGET_UNSET_FLAGS (inner, GTK_CAN_FOCUS);
+        gtk_container_add(GTK_CONTAINER(event_box), inner);
 
-    box = gtk_hbox_new(FALSE, 0);
-    gtk_container_set_border_width(GTK_CONTAINER(box), 0);
-    GTK_WIDGET_UNSET_FLAGS (box, GTK_CAN_FOCUS);
-    gtk_container_add(GTK_CONTAINER(b), box);
+        gtk_box_pack_start(GTK_BOX(inner), image, FALSE, FALSE, 0);
 
-    image = _gtk_image_new_from_file_scaled(fname, width, height, keep_ratio);
-    gtk_misc_set_padding (GTK_MISC(image), 0, 0);
-    if(hicolor > 0)
-    {
-        ImgData* data = (ImgData*)g_object_get_qdata( G_OBJECT(image), img_data_id );
-        data->hicolor = hicolor;
-
-        gtk_widget_add_events(b, GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK);
-        g_signal_connect_swapped (G_OBJECT (b), "enter-notify-event",
-              G_CALLBACK (fb_button_enter), image);
-        g_signal_connect_swapped (G_OBJECT (b), "leave-notify-event",
-              G_CALLBACK (fb_button_leave), image);
-    }
-    gtk_box_pack_start(GTK_BOX(box), image, FALSE, FALSE, 0);
-    if (name) {
-        label =  gtk_label_new(name);
-        gtk_misc_set_padding(GTK_MISC(label), 2, 0);
-        gtk_box_pack_end(GTK_BOX(box), label, FALSE, FALSE, 0);
+        GtkWidget * lbl = gtk_label_new("");
+        panel_draw_label_text(panel, lbl, label, FALSE);
+        gtk_misc_set_padding(GTK_MISC(lbl), 2, 0);
+        gtk_box_pack_end(GTK_BOX(inner), lbl, FALSE, FALSE, 0);
     }
-    gtk_widget_show_all(b);
-    RET(b);
+
+    gtk_widget_show_all(event_box);
+    return event_box;
 }
 
 char* translate_exec_to_cmd( const char* exec, const char* icon,
@@ -1455,116 +1277,106 @@ void show_error( GtkWindow* parent_win, const char* msg )
     gtk_widget_destroy( dlg );
 }
 
-static GdkPixbuf* load_icon_file( const char* file_name, int size )
+/* Try to load an icon from a named file via the freedesktop.org data directories path.
+ * http://standards.freedesktop.org/basedir-spec/basedir-spec-0.6.html */
+static GdkPixbuf * load_icon_file(const char * file_name, int height, int width)
 {
-    GdkPixbuf* icon = NULL;
-    char* file_path;
-    const gchar** dirs = (const gchar**) g_get_system_data_dirs();
-    const gchar** dir;
-    for( dir = dirs; *dir; ++dir )
+    GdkPixbuf * icon = NULL;
+    const gchar ** dirs = (const gchar **) g_get_system_data_dirs();
+    const gchar ** dir;
+    for (dir = dirs; ((*dir != NULL) && (icon == NULL)); dir++)
     {
-        file_path = g_build_filename( *dir, "pixmaps", file_name, NULL );
-        icon = gdk_pixbuf_new_from_file_at_scale( file_path, size, size, TRUE, NULL );
-        g_free( file_path );
-        if( icon )
-            break;
+        char * file_path = g_build_filename(*dir, "pixmaps", file_name, NULL);
+        icon = gdk_pixbuf_new_from_file_at_scale(file_path, height, width, TRUE, NULL);
+        g_free(file_path);
     }
     return icon;
 }
 
-static GdkPixbuf* vfs_load_icon( GtkIconTheme* theme, const char* icon_name, int size )
+/* Try to load an icon from the current theme. */
+static GdkPixbuf * load_icon_from_theme(GtkIconTheme * theme, const char * icon_name, int height, int width)
 {
-    GdkPixbuf* icon = NULL;
-    const char* file;
-    GtkIconInfo* inf = gtk_icon_theme_lookup_icon( theme, icon_name, size,
-                                             GTK_ICON_LOOKUP_USE_BUILTIN );
-    if( G_UNLIKELY( ! inf ) )
-        return NULL;
-    file = gtk_icon_info_get_filename( inf );
-    if( G_LIKELY( file ) )
-    {
-        /* icon = gdk_pixbuf_new_from_file_at_scale( file, size, size, TRUE, NULL ); */
-        icon = gdk_pixbuf_new_from_file( file, NULL );
-    }
-    else
-        icon = gtk_icon_info_get_builtin_pixbuf( inf );
-    gtk_icon_info_free( inf );
+    GdkPixbuf * icon = NULL;
 
-    if( G_LIKELY( icon ) )  /* scale down the icon if it's too big */
+    /* Look up the icon in the current theme. */
+    GtkIconInfo * icon_info = gtk_icon_theme_lookup_icon(theme, icon_name, height, GTK_ICON_LOOKUP_USE_BUILTIN);
+    if (icon_info != NULL)
     {
-        int width, height;
-        height = gdk_pixbuf_get_height(icon);
-        width = gdk_pixbuf_get_width(icon);
-        if( G_UNLIKELY( height > size || width > size ) )
+        /* If that succeeded, get the filename of the icon.
+         * If that succeeds, load the icon from the specified file.
+         * Otherwise, try to get the builtin icon. */
+        const char * file = gtk_icon_info_get_filename(icon_info);
+        if (file != NULL)
+            icon = gdk_pixbuf_new_from_file(file, NULL);
+        else
         {
-            GdkPixbuf* scaled;
-            if( height > width )
-            {
-                width = size * height / width;
-                height = size;
-            }
-            else if( height < width )
+            icon = gtk_icon_info_get_builtin_pixbuf(icon_info);
+            g_object_ref(icon);
+        }
+        gtk_icon_info_free(icon_info);
+
+        /* If the icon is not sized properly, take a trip through the scaler.
+         * The lookup above takes the desired size, so we get the closest result possible. */
+        if (icon != NULL)
+        {
+            if ((height != gdk_pixbuf_get_height(icon)) || (width != gdk_pixbuf_get_width(icon)))
             {
-                height = size * width / height;
-                width = size;
+                GdkPixbuf * scaled = gdk_pixbuf_scale_simple(icon, width, height, GDK_INTERP_BILINEAR);
+                g_object_unref(icon);
+                icon = scaled;
             }
-            else
-                height = width = size;
-            scaled = gdk_pixbuf_scale_simple( icon, width, height, GDK_INTERP_BILINEAR );
-            g_object_unref( icon );
-            icon = scaled;
         }
     }
     return icon;
 }
 
-GdkPixbuf* lxpanel_load_icon( const char* name, int size, gboolean use_fallback )
+GdkPixbuf * lxpanel_load_icon(const char * name, int width, int height, gboolean use_fallback)
 {
-    GtkIconTheme* theme;
-    char *icon_name = NULL, *suffix;
-    GdkPixbuf* icon = NULL;
-    if( name )
+    GdkPixbuf * icon = NULL;
+
+    if (name != NULL)
     {
-        if( g_path_is_absolute( name) )
+        if (g_path_is_absolute(name))
         {
-            icon = gdk_pixbuf_new_from_file_at_scale( name,
-                                                     size, size, TRUE, NULL );
+            /* Absolute path. */
+            icon = gdk_pixbuf_new_from_file_at_scale(name, width, height, TRUE, NULL);
         }
         else
         {
-            theme = gtk_icon_theme_get_default();
-            suffix = strrchr( name, '.' );
-            if( suffix
-                && (0 == g_strcasecmp(++suffix, "png")
-                || 0 == g_strcasecmp(suffix, "svg")
-                || 0 == g_strcasecmp(suffix, "xpm")) ) /* has file extension, it's a basename of icon file */
+            /* Relative path. */
+            GtkIconTheme * theme = gtk_icon_theme_get_default();
+            char * suffix = strrchr(name, '.');
+            if ((suffix != NULL)
+            && ((g_strcasecmp(&suffix[1], "png") == 0)
+              || (g_strcasecmp(&suffix[1], "svg") == 0)
+              || (g_strcasecmp(&suffix[1], "xpm") == 0)))
             {
-                /* try to find it in pixmaps dirs */
-                icon = load_icon_file( name, size );
-                if( G_UNLIKELY( ! icon ) )  /* unfortunately, not found */
+                /* The file extension indicates it could be in the system pixmap directories. */
+                icon = load_icon_file(name, width, height);
+                if (icon == NULL)
                 {
-                    /* Let's remove the suffix, and see if this name can match an icon
-                         in current icon theme */
-                    icon_name = g_strndup( name,
-                                           (suffix - name - 1) );
-                    icon = vfs_load_icon( theme, icon_name, size );
-                    g_free( icon_name );
+                    /* Not found.
+                     * Let's remove the suffix, and see if this name can match an icon in the current icon theme. */
+                    char * icon_name = g_strndup(name, suffix - name - 1);
+                    icon = load_icon_from_theme(theme, icon_name, width, height);
+                    g_free(icon_name);
                 }
             }
-            else  /* no file extension, it could be an icon name in the icon theme */
+            else
             {
-                icon = vfs_load_icon( theme, name, size );
+                 /* No file extension.  It could be an icon name in the icon theme. */
+                 icon = load_icon_from_theme(theme, name, width, height);
             }
         }
     }
-    if( G_UNLIKELY( ! icon ) && use_fallback )  /* fallback to generic icon */
+
+    /* Fall back to generic icons. */
+    if ((icon == NULL) && (use_fallback))
     {
-        theme = gtk_icon_theme_get_default();
-        icon = vfs_load_icon( theme, "application-x-executable", size );
-        if( G_UNLIKELY( ! icon ) )  /* fallback to generic icon */
-        {
-            icon = vfs_load_icon( theme, "gnome-mime-application-x-executable", size );
-        }
+        GtkIconTheme * theme = gtk_icon_theme_get_default();
+        icon = load_icon_from_theme(theme, "application-x-executable", width, height);
+        if (icon == NULL)
+            icon = load_icon_from_theme(theme, "gnome-mime-application-x-executable", width, height);
     }
     return icon;
 }
@@ -1741,4 +1553,3 @@ gboolean lxpanel_launch_app(const char* exec, GList* files, gboolean in_terminal
 
     return (error == NULL);
 }
-
index f6a9814..2f95aa4 100644 (file)
@@ -72,7 +72,7 @@ extern int lxpanel_put_line(FILE* fp, const char* format, ...);
 //extern int lxpanel_put_int( FILE* fp, const char* name, int val );
 int get_line_as_is(char **fp, line *s);
 
-void Xclimsg(Window win, long type, long l0, long l1, long l2, long l3, long l4);
+void Xclimsg(Window win, Atom type, long l0, long l1, long l2, long l3, long l4);
 void Xclimsgwm(Window win, Atom type, Atom arg);
 void *get_xaproperty (Window win, Atom prop, Atom type, int *nitems);
 char *get_textproperty(Window win, Atom prop);
@@ -91,22 +91,15 @@ GPid get_net_wm_pid(Window win);
 
 void calculate_position(Panel *np);
 gchar *expand_tilda(gchar *file);
-GdkPixbuf *gdk_pixbuf_scale_ratio(GdkPixbuf *p, int width, int height, GdkInterpType itype,
-                                  gboolean keep_ratio);
-GtkWidget *_gtk_image_new_from_file_scaled(const gchar *file, gint width, gint height,
-                                          gboolean keep_ratio);
+
 GtkWidget *_gtk_image_new_from_file_scaled(const gchar *file, gint width,
                                            gint height, gboolean keep_ratio);
 void get_button_spacing(GtkRequisition *req, GtkContainer *parent, gchar *name);
 guint32 gcolor2rgb24(GdkColor *color);
-GtkWidget *fb_button_new_from_file(gchar *fname, int width, int height, gulong hicolor,
-      gboolean keep_ratio);
-GtkWidget *fb_button_new_from_file_with_label(gchar *fname, int width, int height,
-      gulong hicolor, gboolean keep_ratio, gchar *label);
-GtkWidget *fb_button_new_from_file_with_colorlabel(gchar *fname, int width, int height,
-      gulong hicolor, gulong fcolor, gboolean keep_ratio, gchar *name);
-void _gtk_image_set_from_file_scaled( GtkWidget* img, const gchar *file, gint width,
-       gint height, gboolean keep_ratio);
+GtkWidget * fb_button_new_from_file(
+    gchar * image_file, int width, int height, gulong highlight_color, gboolean keep_ratio);
+GtkWidget * fb_button_new_from_file_with_label(
+    gchar * image_file, int width, int height, gulong highlight_color, gboolean keep_ratio, Panel * panel, gchar * label);
 
 char* translate_exec_to_cmd( const char* exec, const char* icon,
                              const char* title, const char* fpath );
@@ -125,7 +118,7 @@ void show_error( GtkWindow* parent_win, const char* msg );
 
 /* Parameters: const char* name, gpointer ret_value, GType type, ....NULL */
 GtkWidget* create_generic_config_dlg( const char* title, GtkWidget* parent,
-                              GSourceFunc apply_func, gpointer plugin,
+                              GSourceFunc apply_func, Plugin * plugin,
                       const char* name, ... );
 
 
@@ -133,7 +126,7 @@ char* get_config_file( const char* profile, const char* file_name, gboolean is_g
 
 extern GtkMenu* lxpanel_get_panel_menu( Panel* panel, Plugin* plugin, gboolean use_sub_menu );
 
-extern GdkPixbuf* lxpanel_load_icon( const char* name, int size, gboolean use_fallback );
+extern GdkPixbuf* lxpanel_load_icon( const char* name, int width, int height, gboolean use_fallback );
 
 void fb_button_set_from_file(GtkWidget* btn, const char* img_file);
 
index 032b0ff..685b554 100644 (file)
@@ -36,7 +36,6 @@
 #include "misc.h"
 #include "bg.h"
 
-#include "glib-mem.h"
 #include "lxpanelctl.h"
 #include "dbg.h"
 
@@ -62,7 +61,7 @@ gboolean is_in_lxde = FALSE;
 /* Allocate and initialize new Panel structure. */
 static Panel* panel_allocate(void)
 {
-    Panel* p = g_slice_new0(Panel);
+    Panel* p = g_new0(Panel, 1);
     p->allign = ALLIGN_CENTER;
     p->edge = EDGE_NONE;
     p->widthtype = WIDTH_PERCENT;
@@ -87,7 +86,7 @@ static Panel* panel_allocate(void)
 /* Normalize panel configuration after load from file or reconfiguration. */
 static void panel_normalize_configuration(Panel* p)
 {
-    panel_set_orientation( p );
+    panel_set_panel_configuration_changed( p );
     if (p->width < 0)
         p->width = 100;
     if (p->widthtype == WIDTH_PERCENT && p->width > 100)
@@ -340,65 +339,66 @@ on_root_bg_changed(FbBg *bg, Panel* p)
     panel_update_background( p );
 }
 
-/* This function should only be called after the panel has been realized */
-void panel_update_background( Panel* p )
+void panel_determine_background_pixmap(Panel * p, GtkWidget * widget, GdkWindow * window)
 {
-    GList* l;
-    GdkPixmap* pixmap = NULL;
-
-    /* handle background image of panel */
-    gtk_widget_set_app_paintable(p->topgwin, TRUE);
+    GdkPixmap * pixmap = NULL;
 
-    if (p->background) {
-        pixmap = fb_bg_get_pix_from_file(p->topgwin, p->background_file);
-        if( p->bg )
-        {
-            g_object_unref( p->bg );
-            p->bg = NULL;
-        }
-    } else if (p->transparent) {
-        if( ! p->bg )
-        {
-            p->bg = fb_bg_get_for_display();
-            g_signal_connect(G_OBJECT(p->bg), "changed", G_CALLBACK(on_root_bg_changed), p);
-        }
-        pixmap = fb_bg_get_xroot_pix_for_win( p->bg, p->topgwin );
+    /* Free p->bg if it is not going to be used. */
+    if (( ! p->transparent) && (p->bg != NULL))
+    {
+        g_signal_handlers_disconnect_by_func(G_OBJECT(p->bg), on_root_bg_changed, p);
+        g_object_unref(p->bg);
+        p->bg = NULL;
+    }
 
-        if (pixmap && pixmap !=  GDK_NO_BG) {
-            if (p->alpha)
-                fb_bg_composite( pixmap, p->topgwin->style->black_gc, p->tintcolor, p->alpha );
-        }
+    if (p->background)
+    {
+        /* User specified background pixmap. */
+        if (p->background_file != NULL)
+            pixmap = fb_bg_get_pix_from_file(widget, p->background_file);
     }
-    else
+
+    else if (p->transparent)
     {
-        if( p->bg )
+        /* Transparent.  Determine the appropriate value from the root pixmap. */
+        if (p->bg == NULL)
         {
-            g_object_unref( p->bg );
-            p->bg = NULL;
+            p->bg = fb_bg_get_for_display();
+            g_signal_connect(G_OBJECT(p->bg), "changed", G_CALLBACK(on_root_bg_changed), p);
         }
+        pixmap = fb_bg_get_xroot_pix_for_win(p->bg, widget);
+        if ((pixmap != NULL) && (pixmap != GDK_NO_BG) && (p->alpha != 0))
+            fb_bg_composite(pixmap, widget->style->black_gc, p->tintcolor, p->alpha);
     }
 
-    if( pixmap )
+    if (pixmap != NULL)
     {
-        gtk_widget_set_app_paintable( p->topgwin, TRUE );
-        gdk_window_set_back_pixmap( p->topgwin->window, pixmap, FALSE );
-        g_object_unref( pixmap );
+        gtk_widget_set_app_paintable(widget, TRUE );
+        gdk_window_set_back_pixmap(window, pixmap, FALSE);
+        g_object_unref(pixmap);
     }
     else
-    {
-//        gdk_window_set_back_pixmap( p->topgwin->window, p->topgwin->style->bg_pixmap[0], FALSE );
-        gtk_widget_set_app_paintable( p->topgwin, FALSE );
-//        gdk_window_set_background( p->topgwin->window, &p->topgwin->style->bg[0] );
-    }
+        gtk_widget_set_app_paintable(widget, FALSE);
+}
 
-    for( l = p->plugins; l; l = l->next )
+/* Update the background of the entire panel.
+ * This function should only be called after the panel has been realized. */
+void panel_update_background(Panel * p)
+{
+    /* Redraw the top level widget. */
+    panel_determine_background_pixmap(p, p->topgwin, p->topgwin->window);
+    gdk_window_clear(p->topgwin->window);
+    gtk_widget_queue_draw(p->topgwin);
+    if (gtk_events_pending()) gtk_main_iteration();
+
+    /* Loop over all plugins redrawing each plugin. */
+    GList * l;
+    for (l = p->plugins; l != NULL; l = l->next)
     {
-        Plugin* pl = (Plugin*)l->data;
-        plugin_set_background( pl, p );
+        Plugin * pl = (Plugin *) l->data;
+        plugin_set_background(pl, p);
     }
 
-    gdk_window_clear( p->topgwin->window );
-    gtk_widget_queue_draw( p->topgwin );
 }
 
 static gboolean delay_update_background( Panel* p )
@@ -517,9 +517,7 @@ static void panel_popupmenu_remove_item( GtkMenuItem* item, Plugin* plugin )
 {
     Panel* panel = plugin->panel;
     panel->plugins = g_list_remove( panel->plugins, plugin );
-    plugin_stop( plugin ); /* free the plugin widget & its data */
-    plugin_put( plugin ); /* free the lib if necessary */
-
+    plugin_delete(plugin);
     panel_config_save( plugin->panel );
 }
 
@@ -617,6 +615,7 @@ static void panel_popupmenu_about( GtkMenuItem* item, Panel* panel )
         "Fred Chien <cfsghost@gmail.com>",
         "Daniel Kesler <kesler.daniel@gmail.com>",
         "Juergen Hoetzel <juergen@archlinux.org>",
+        "Marty Jack <martyj19@comcast.net>",
         NULL
     };
     /* TRANSLATORS: Replace this string with your names, one name per line. */
@@ -923,7 +922,8 @@ panel_start_gui(Panel *p)
     RET();
 }
 
-void panel_adjust_geometry_terminology(Panel *p)
+/* Exchange the "width" and "height" terminology for vertical and horizontal panels. */
+void panel_adjust_geometry_terminology(Panel * p)
 {
     if ((p->height_label != NULL) && (p->width_label != NULL))
     {
@@ -931,16 +931,84 @@ void panel_adjust_geometry_terminology(Panel *p)
         {
             gtk_label_set_text(GTK_LABEL(p->height_label), _("Height:"));
             gtk_label_set_text(GTK_LABEL(p->width_label), _("Width:"));
+            gtk_button_set_label(GTK_BUTTON(p->alignment_left_label), _("Left"));
+            gtk_button_set_label(GTK_BUTTON(p->alignment_right_label), _("Right"));
         }
         else
         {
             gtk_label_set_text(GTK_LABEL(p->height_label), _("Width:"));
             gtk_label_set_text(GTK_LABEL(p->width_label), _("Height:"));
+            gtk_button_set_label(GTK_BUTTON(p->alignment_left_label), _("Top"));
+            gtk_button_set_label(GTK_BUTTON(p->alignment_right_label), _("Bottom"));
         }
     }
 }
 
-void panel_set_orientation(Panel *p)
+/* Draw an integer value into a label, with the user preference color and optionally bold. */
+void panel_draw_label_integer(Panel * p, GtkWidget * label, int value, gboolean bold)
+{
+    char buffer[12];
+    sprintf(buffer, "%d", value);
+    panel_draw_label_text(p, label, buffer, bold);
+
+}
+
+/* Draw text into a label, with the user preference color and optionally bold. */
+void panel_draw_label_text(Panel * p, GtkWidget * label, char * text, gboolean bold)
+{
+    char buffer[512];
+
+    if (text == NULL)
+    {
+        /* Null string. */
+        gtk_label_set_text(GTK_LABEL(label), NULL);
+    }
+
+    else if ((p->usefontcolor) || (bold))
+    {
+        char * valid_markup = text;
+        char * escaped_text = NULL;
+
+        /* Check the string for characters that need to be escaped.
+         * If any are found, create the properly escaped string and use it instead. */
+        char * q;
+        for (q = text; *q != '\0'; q += 1)
+        {
+            if ((*q == '<') || (*q == '>') || (*q == '&'))
+            {
+                escaped_text = g_markup_escape_text(text, -1);
+                valid_markup = escaped_text;
+                break;
+            }
+        }
+
+        if (p->usefontcolor)
+        {
+            /* Color, optionally bold. */
+            g_snprintf(buffer, sizeof(buffer), "<span color=\"#%06x\">%s%s%s</span>",
+                gcolor2rgb24(&p->gfontcolor),
+                ((bold) ? "<b>" : ""),
+                valid_markup,
+                ((bold) ? "</b>" : ""));
+            gtk_label_set_markup(GTK_LABEL(label), buffer);
+        }
+        else if (bold)
+        {
+            /* No color, bold. */
+            g_snprintf(buffer, sizeof(buffer), "<span><b>%s</b></span>", valid_markup);
+            gtk_label_set_markup(GTK_LABEL(label), buffer);
+        }
+        g_free(escaped_text);
+    }
+
+    else
+    {
+        /* No color, no bold. */
+        gtk_label_set_text(GTK_LABEL(label), text);
+    }
+}
+
+void panel_set_panel_configuration_changed(Panel *p)
 {
     GList* l;
 
@@ -954,6 +1022,13 @@ void panel_set_orientation(Panel *p)
         p->height = ((p->orientation == ORIENT_HORIZ) ? PANEL_HEIGHT_DEFAULT : PANEL_WIDTH_DEFAULT);
         if (p->height_control != NULL)
             gtk_spin_button_set_value(GTK_SPIN_BUTTON(p->height_control), p->height);
+        if ((p->widthtype == WIDTH_PIXEL) && (p->width_control != NULL))
+        {
+            int value = ((p->orientation == ORIENT_HORIZ) ? gdk_screen_width() : gdk_screen_height());
+            gtk_spin_button_set_range(GTK_SPIN_BUTTON(p->width_control), 0, value);
+            gtk_spin_button_set_value(GTK_SPIN_BUTTON(p->width_control), value);
+        }
+
     }
 
     if (p->orientation == ORIENT_HORIZ) {
@@ -980,8 +1055,8 @@ void panel_set_orientation(Panel *p)
     */
     for( l = p->plugins; l; l = l->next ) {
         Plugin* pl = (Plugin*)l->data;
-        if( pl->class->orientation ) {
-            pl->class->orientation( pl );
+        if( pl->class->panel_configuration_changed ) {
+            pl->class->panel_configuration_changed( pl );
         }
     }
 }
@@ -1046,7 +1121,6 @@ panel_parse_global(Panel *p, char **fp)
                     p->background_file = g_strdup( s.t[1] );
                 } else {
                     ERR( "lxpanel: %s - unknown var in Global section\n", s.t[0]);
-                    RET(0);
                 }
             } else if (s.type == LINE_BLOCK_END) {
                 break;
@@ -1091,7 +1165,6 @@ panel_parse_plugin(Panel *p, char **fp)
                 border = atoi(s.t[1]);
             else {
                 ERR( "lxpanel: unknown var %s\n", s.t[0]);
-                goto error;
             }
         } else if (s.type == LINE_BLOCK_START) {
             if (!g_ascii_strcasecmp(s.t[0], "Config")) {
@@ -1124,7 +1197,7 @@ panel_parse_plugin(Panel *p, char **fp)
     }
 
     plug->panel = p;
-    plug->expand = expand;
+    if (plug->class->expand_available) plug->expand = expand;
     plug->padding = padding;
     plug->border = border;
     DBG("starting\n");
@@ -1139,9 +1212,9 @@ panel_parse_plugin(Panel *p, char **fp)
     RET(1);
 
  error:
+    if (plug != NULL)
+        plugin_unload(plug);
     g_free(type);
-    if (plug)
-          plugin_put(plug);
     RET(0);
 }
 
@@ -1178,16 +1251,27 @@ int panel_start( Panel *p, char **fp )
 static void
 delete_plugin(gpointer data, gpointer udata)
 {
-    ENTER;
-    plugin_stop((Plugin *)data);
-    plugin_put((Plugin *)data);
-    RET();
+    plugin_delete((Plugin *)data);
 }
 
 void panel_destroy(Panel *p)
 {
     ENTER;
 
+    if (p->pref_dialog != NULL)
+        gtk_widget_destroy(p->pref_dialog);
+    if (p->plugin_pref_dialog != NULL)
+    {
+        gtk_widget_destroy(p->plugin_pref_dialog);
+        p->plugin_pref_dialog = NULL;
+    }
+
+    if (p->bg != NULL)
+    {
+        g_signal_handlers_disconnect_by_func(G_OBJECT(p->bg), on_root_bg_changed, p);
+        g_object_unref(p->bg);
+    }
+
     if( p->config_changed )
         panel_config_save( p );
 
@@ -1212,7 +1296,7 @@ void panel_destroy(Panel *p)
     XSync(GDK_DISPLAY(), True);
 
     g_free( p->name );
-    g_slice_free( Panel, p );
+    g_free(p);
     RET();
 }
 
@@ -1250,27 +1334,32 @@ usage()
     g_print(_(" --help      -- print this help and exit\n"));
     g_print(_(" --version   -- print version and exit\n"));
     g_print(_(" --log <number> -- set log level 0-5. 0 - none 5 - chatty\n"));
-    g_print(_(" --configure -- launch configuration utility\n"));
+//    g_print(_(" --configure -- launch configuration utility\n"));
     g_print(_(" --profile name -- use specified profile\n"));
     g_print("\n");
     g_print(_(" -h  -- same as --help\n"));
     g_print(_(" -p  -- same as --profile\n"));
     g_print(_(" -v  -- same as --version\n"));
-    g_print(_(" -C  -- same as --configure\n"));
//   g_print(_(" -C  -- same as --configure\n"));
     g_print(_("\nVisit http://lxde.org/ for detail.\n\n"));
 }
 
-static void
-handle_error(Display * d, XErrorEvent * ev)
+int panel_handle_x_error(Display * d, XErrorEvent * ev)
 {
     char buf[256];
 
-    ENTER;
     if (log_level >= LOG_WARN) {
         XGetErrorText(GDK_DISPLAY(), ev->error_code, buf, 256);
         LOG(LOG_WARN, "lxpanel : X error: %s\n", buf);
     }
-    RET();
+    return 0;  /* Ignored */
+}
+
+int panel_handle_x_error_swallow_BadWindow_BadDrawable(Display * d, XErrorEvent * ev)
+{
+    if ((ev->error_code != BadWindow) && (ev->error_code != BadDrawable))
+        panel_handle_x_error(d, ev);
+    return 0;  /* Ignored */
 }
 
 /* Lightweight lock related functions - X clipboard hacks */
@@ -1352,9 +1441,12 @@ static gboolean start_all_panels( )
         while( name = g_dir_read_name( dir ) )
         {
             char* panel_config = g_build_filename( panel_dir, name, NULL );
-            Panel* panel = panel_new( panel_config, name );
-            if( panel )
-                all_panels = g_slist_prepend( all_panels, panel );
+            if (strchr(panel_config, '~') == NULL)     /* Skip editor backup files in case user has hand edited in this directory */
+            {
+                Panel* panel = panel_new( panel_config, name );
+                if( panel )
+                    all_panels = g_slist_prepend( all_panels, panel );
+            }
             g_free( panel_config );
         }
         g_dir_close( dir );
@@ -1382,7 +1474,7 @@ int main(int argc, char *argv[], char *env[])
 #endif
 
     XSetLocaleModifiers("");
-    XSetErrorHandler((XErrorHandler) handle_error);
+    XSetErrorHandler((XErrorHandler) panel_handle_x_error);
 
     resolve_atoms();
 
index 07dc920..7ec0bdc 100644 (file)
@@ -41,22 +41,23 @@ enum { POS_NONE, POS_START, POS_END };
 
 #define PANEL_ICON_SIZE               24       /* Default size of panel icons */
 #define PANEL_HEIGHT_DEFAULT          26       /* Default height of horizontal panel */
-#define PANEL_WIDTH_DEFAULT           26       /* Default "height" of vertical panel: not yet finished, will be wide */
+#define PANEL_WIDTH_DEFAULT           150      /* Default "height" of vertical panel */
 #define PANEL_HEIGHT_MAX              200      /* Maximum height of panel */
 #define PANEL_HEIGHT_MIN              16       /* Minimum height of panel */
+#define PANEL_ICON_HIGHLIGHT          0x202020 /* Constant to pass to icon loader */
 
 /* to check if we are in LXDE */
 extern gboolean is_in_lxde;
 
-typedef struct _Panel Panel;
-
-struct _Panel{
+/* Context of a panel on a given edge. */
+typedef struct _Panel {
     char* name;
-    GtkWidget *topgwin;           /* main panel window */
-    Window topxwin;               /* and it X window   */
-    GdkDisplay *display;               /* Main panel's GdkDisplay */
-    GtkStyle *defstyle;
-    GtkWidget *box;              /* primary layout box which contains all plugins */
+    GtkWidget * topgwin;               /* Main panel window */
+    Window topxwin;                    /* Main panel's X window   */
+    GdkDisplay * display;              /* Main panel's GdkDisplay */
+    GtkStyle * defstyle;
+
+    GtkWidget * box;                   /* Top level widget */
 
     GtkRequisition requisition;
     GtkWidget *(*my_box_new) (gboolean, gint);
@@ -101,19 +102,21 @@ struct _Panel{
 
     char* background_file;
 
-    int plug_num;
-    GList *plugins;
-
-    GSList* system_menus;
+    GList * plugins;                   /* List of all plugins */
+    GSList * system_menus;             /* List of plugins having menus */
 
-    GtkWidget* pref_dialog; /* preference dialog */
+    GtkWidget* plugin_pref_dialog;     /* Plugin preference dialog */
+    GtkWidget* pref_dialog;            /* preference dialog */
     GtkWidget* margin_control;         /* Margin control in preference dialog */
     GtkWidget* height_label;           /* Label of height control */
     GtkWidget* width_label;            /* Label of width control */
+    GtkWidget* alignment_left_label;   /* Label of alignment: left control */
+    GtkWidget* alignment_right_label;  /* Label of alignment: right control */
     GtkWidget* height_control;         /* Height control in preference dialog */
-};
-
+    GtkWidget* width_control;          /* Width control in preference dialog */
+} Panel;
 
+/* Decoded value of WM_STATE property. */
 typedef struct {
     unsigned int modal : 1;
     unsigned int sticky : 1;
@@ -128,6 +131,7 @@ typedef struct {
     unsigned int below : 1;
 } NetWMState;
 
+/* Decoded value of _NET_WM_WINDOW_TYPE property. */
 typedef struct {
     unsigned int desktop : 1;
     unsigned int dock : 1;
@@ -195,6 +199,10 @@ extern Atom a_NET_WM_STRUT_PARTIAL;
 extern Atom a_NET_WM_ICON;
 extern Atom a_KDE_NET_WM_SYSTEM_TRAY_WINDOW_FOR;
 
+extern Atom a_NET_SYSTEM_TRAY_OPCODE;
+extern Atom a_NET_SYSTEM_TRAY_ORIENTATION;
+extern Atom a_MANAGER;
+
 extern Atom a_LXPANEL_CMD; /* for private client message */
 
 extern int verbose;
@@ -203,14 +211,20 @@ extern FbEv *fbev;
 
 #define FBPANEL_WIN(win)  gdk_window_lookup(win)
 
-void panel_apply_icon(GtkWindow *w);
-void panel_destroy(Panel *p);
-void panel_adjust_geometry_terminology(Panel *p);
-void panel_establish_autohide(Panel *p);
-void panel_set_wm_strut(Panel *p);
-void panel_set_dock_type(Panel *p);
-void panel_set_orientation(Panel *p);
-void panel_update_background( Panel* p );
+extern void panel_apply_icon(GtkWindow *w);
+extern void panel_destroy(Panel *p);
+extern void panel_adjust_geometry_terminology(Panel *p);
+extern void panel_determine_background_pixmap(Panel * p, GtkWidget * widget, GdkWindow * window);
+extern void panel_draw_label_integer(Panel * p, GtkWidget * label, int value, gboolean bold);
+extern void panel_draw_label_text(Panel * p, GtkWidget * label, char * text, gboolean bold);
+extern void panel_establish_autohide(Panel *p);
+extern void panel_set_wm_strut(Panel *p);
+extern void panel_set_dock_type(Panel *p);
+extern void panel_set_panel_configuration_changed(Panel *p);
+extern void panel_update_background( Panel* p );
+
+extern int panel_handle_x_error(Display * d, XErrorEvent * ev);
+extern int panel_handle_x_error_swallow_BadWindow_BadDrawable(Display * d, XErrorEvent * ev);
 
 extern const char* lxpanel_get_file_manager();
 extern const char* lxpanel_get_terminal();
index 48b9fcc..78a9e07 100644 (file)
 //#define DEBUG
 #include "dbg.h"
 
-static GList *pcl = NULL;
+static GList * pcl = NULL;                     /* List of PluginClass structures */
 
-#if 0
-/* dummy type plugin for dynamic plugins registering new types */
-static void
-lx_type_plugin_iface_init(gpointer g_iface, gpointer g_iface_data);
+static void register_plugin_class(PluginClass * pc, gboolean dynamic);
+static void init_plugin_class_list(void);
+static PluginClass * plugin_find_class(const char * type);
+static PluginClass * plugin_load_dynamic(const char * type, const gchar * path);
+static void plugin_class_unref(PluginClass * pc);
 
-G_DEFINE_TYPE_EXTENDED ( LXTypePlugin,
-                         lx_type_plugin,
-                         G_TYPE_OBJECT,
-                         0,
-                         G_IMPLEMENT_INTERFACE (G_TYPE_TYPE_PLUGIN,
-                                         lx_type_plugin_iface_init))
-
-void lx_type_plugin_init( LXTypePlugin* tp )
-{
-}
-
-void lx_type_plugin_class_init(LXTypePluginClass* klass)
-{
-}
-
-void lx_type_plugin_iface_init(gpointer g_iface, gpointer g_iface_data)
-{
-}
-
-GTypePlugin* lx_type_plugin_get(const char* plugin_name)
-{
-    LXTypePlugin* tp = g_object_new(LX_TYPE_TYPE_PLUGIN, NULL);
-    return tp;
-}
-#endif
-
-/* Dynamic parameter for static (built-in) plugins must be FALSE
- * so lxpanel will not try to unload them */
+/* Dynamic parameter for static (built-in) plugins must be FALSE so we will not try to unload them */
 #define REGISTER_STATIC_PLUGIN_CLASS(pc) \
 do {\
     extern PluginClass pc;\
     register_plugin_class(&pc, FALSE);\
 } while (0)
 
-
-static void
-register_plugin_class(PluginClass *pc, int dynamic)
+/* Register a PluginClass. */
+static void register_plugin_class(PluginClass * pc, gboolean dynamic)
 {
     pcl = g_list_append(pcl, pc);
     pc->dynamic = dynamic;
 }
 
-static void
-init_plugin_class_list()
+/* Initialize the static plugins. */
+static void init_plugin_class_list(void)
 {
 #ifdef STATIC_SEPARATOR
     REGISTER_STATIC_PLUGIN_CLASS(separator_plugin_class);
@@ -131,264 +104,272 @@ init_plugin_class_list()
 #endif
 }
 
-GList* plugin_find_class( const char* type )
+/* Look up a plugin class by name. */
+static PluginClass * plugin_find_class(const char * type)
 {
-    GList *tmp;
-    PluginClass *pc = NULL;
-    for (tmp = pcl; tmp; tmp = g_list_next(tmp)) {
-        pc = (PluginClass *) tmp->data;
-        if (!g_ascii_strcasecmp(type, pc->type)) {
-            LOG(LOG_INFO, "   already have it\n");
-            break;
-        }
+    GList * tmp;
+    for (tmp = pcl; tmp != NULL; tmp = g_list_next(tmp))
+    {
+        PluginClass * pc = (PluginClass *) tmp->data;
+        if (g_ascii_strcasecmp(type, pc->type) == 0)
+            return pc;
     }
-    return tmp;
+    return NULL;
 }
 
-static PluginClass*
-plugin_load_dynamic( const char* type, const gchar* path )
+/* Load a dynamic plugin. */
+static PluginClass * plugin_load_dynamic(const char * type, const gchar * path)
 {
-    PluginClass *pc = NULL;
-    GModule *m;
-    gpointer tmpsym;
-    char class_name[ 128 ];
-    m = g_module_open(path, G_MODULE_BIND_LAZY);
-    if (!m) {
-        /* ERR("error is %s\n", g_module_error()); */
-        return NULL;
-    }
-    g_snprintf( class_name, 128, "%s_plugin_class", type );
-
-    if (!g_module_symbol(m, class_name, &tmpsym)
-         || (pc = tmpsym) == NULL
-         || strcmp(type, pc->type)) {
-        g_module_close(m);
-        ERR("%s.so is not a lxpanel plugin\n", type);
-        RET(NULL);
+    PluginClass * pc = NULL;
+
+    /* Load the external module. */
+    GModule * m = g_module_open(path, G_MODULE_BIND_LAZY);
+    if (m != NULL)
+    {
+        /* Formulate the name of the expected external variable of type PluginClass. */
+        char class_name[128];
+        g_snprintf(class_name, sizeof(class_name), "%s_plugin_class", type);
+
+        /* Validate that the external variable is of type PluginClass. */
+        gpointer tmpsym;
+        if (( ! g_module_symbol(m, class_name, &tmpsym))       /* Ensure symbol is present */
+        || ((pc = tmpsym) == NULL)
+        || (pc->structure_size != sizeof(PluginClass))         /* Then check versioning information */
+        || (pc->structure_version != PLUGINCLASS_VERSION)
+        || (strcmp(type, pc->type) != 0))                      /* Then and only then access other fields; check name */
+        {
+            g_module_close(m);
+            ERR("%s.so is not a lxpanel plugin\n", type);
+            return NULL;
+        }
+
+        /* Register the newly loaded and valid plugin. */
+        pc->gmodule = m;
+        register_plugin_class(pc, TRUE);
     }
-    pc->gmodule = m;
-    register_plugin_class(pc, TRUE);
     return pc;
 }
 
-Plugin *
-plugin_load(char *type)
+/* Create an instance of a plugin with a specified name, loading it if external. */
+Plugin * plugin_load(char * type)
 {
-    GList *tmp;
-    PluginClass *pc = NULL;
-    Plugin *plug = NULL;
-
-    if (!pcl)
+    /* Initialize static plugins on first call. */
+    if (pcl == NULL)
         init_plugin_class_list();
 
-    tmp = plugin_find_class( type );
+    /* Look up the PluginClass. */
+    PluginClass * pc = plugin_find_class(type);
 
-    if( tmp ) {
-        pc = (PluginClass *) tmp->data;
-    }
 #ifndef DISABLE_PLUGINS_LOADING
-    else if ( g_module_supported() ) {
-        gchar path[ PATH_MAX ];
-        
-        if( !pc ) {
-            g_snprintf(path, PATH_MAX, PACKAGE_LIB_DIR "/lxpanel/plugins/%s.so", type);
-            pc = plugin_load_dynamic( type, path );
-        }
+    /* If not found and dynamic loading is available, try to locate an external plugin. */
+    if ((pc == NULL) && (g_module_supported()))
+    {
+        gchar path[PATH_MAX];
+        g_snprintf(path, sizeof(path), PACKAGE_LIB_DIR "/lxpanel/plugins/%s.so", type);
+        pc = plugin_load_dynamic(type, path);
     }
 #endif  /* DISABLE_PLUGINS_LOADING */
 
-    /* nothing was found */
-    if (!pc)
+    /* If not found, return failure. */
+    if (pc == NULL)
         return NULL;
 
-    plug = g_new0(Plugin, 1);
-    g_return_val_if_fail (plug != NULL, NULL);
+    /* Instantiate the plugin */
+    Plugin * plug = g_new0(Plugin, 1);
     plug->class = pc;
-    pc->count++;
+    pc->count += 1;
     return plug;
 }
 
-
-void plugin_put(Plugin *this)
+/* Configure and start a plugin by calling its constructor. */
+int plugin_start(Plugin * pl, char ** fp)
 {
-    PluginClass *pc = this->class;
-    plugin_class_unref( pc );
-    g_free(this);
-}
-
-int
-plugin_start(Plugin *this, char** fp)
-{
-
-    DBG("%s\n", this->class->type);
-
-    if (!this->class->constructor(this, fp)) {
+    /* Call the constructor.
+     * It is responsible for parsing the parameters, and setting "pwid" to the top level widget. */
+    if ( ! pl->class->constructor(pl, fp))
         return 0;
-    }
 
-    if (this->class->one_per_system)
-        this->class->one_per_system_instantiated = TRUE;
+    /* If this plugin can only be instantiated once, count the instantiation.
+     * This causes the configuration system to avoid displaying the plugin as one that can be added. */
+    if (pl->class->one_per_system)
+        pl->class->one_per_system_instantiated = TRUE;
 
-    if (this->pwid ) {
-        /* this->pwid is created by the plugin */
-        gtk_widget_set_name(this->pwid, this->class->type);
-        gtk_box_pack_start(GTK_BOX(this->panel->box), this->pwid, this->expand, TRUE,
-              this->padding);
-        gtk_container_set_border_width(GTK_CONTAINER(this->pwid), this->border);
-
-        gtk_widget_show(this->pwid);
+    /* If the plugin has a top level widget, add it to the panel's container. */
+    if (pl->pwid != NULL)
+    {
+        gtk_widget_set_name(pl->pwid, pl->class->type);
+        gtk_box_pack_start(GTK_BOX(pl->panel->box), pl->pwid, pl->expand, TRUE, pl->padding);
+        gtk_container_set_border_width(GTK_CONTAINER(pl->pwid), pl->border);
+        gtk_widget_show(pl->pwid);
     }
     return 1;
 }
 
+/* Unload a plugin if initialization fails. */
+void plugin_unload(Plugin * pl)
+{
+    plugin_class_unref(pl->class);
+    g_free(pl);
+}
 
-void plugin_stop(Plugin *this)
+/* Delete a plugin. */
+void plugin_delete(Plugin * pl)
 {
-    if (/*!this->class->invisible &&*/ this->pwid )
+    Panel * p = pl->panel;
+    PluginClass * pc = pl->class;
+
+    /* If a plugin configuration dialog is open, close it. */
+    if (p->plugin_pref_dialog != NULL)
     {
-        gtk_widget_destroy(this->pwid);
-        this->pwid = NULL;
+        gtk_widget_destroy(p->plugin_pref_dialog);
+        p->plugin_pref_dialog = NULL;
     }
-    this->class->destructor(this);
-    this->panel->plug_num--;
-    this->class->one_per_system_instantiated = FALSE;
+
+    /* Run the destructor and then destroy the top level widget.
+     * This prevents problems with the plugin destroying child widgets. */
+    pc->destructor(pl);
+    if (pl->pwid != NULL)
+        gtk_widget_destroy(pl->pwid);
+
+    /* Data structure bookkeeping. */
+    pc->one_per_system_instantiated = FALSE;
+    plugin_class_unref(pc);
+
+    /* Free the Plugin structure. */
+    g_free(pl);
 }
 
-void plugin_class_unref( PluginClass* pc )
+/* Unreference a dynamic plugin. */
+static void plugin_class_unref(PluginClass * pc)
 {
-    --pc->count;
-    if (pc->count == 0 && pc->dynamic && ( ! pc->not_unloadable)) {
+    pc->count -= 1;
+
+    /* If the reference count drops to zero, unload the plugin if it is dynamic and has declared itself unloadable. */
+    if ((pc->count == 0)
+    && (pc->dynamic)
+    && ( ! pc->not_unloadable))
+    {
         pcl = g_list_remove(pcl, pc);
         g_module_close(pc->gmodule);
     }
 }
 
-/*
-   Get a list of all available plugin classes
-   Return a newly allocated GList which should be freed with
-   plugin_class_list_free( list );
-*/
-GList* plugin_get_available_classes()
+/* Get a list of all available plugin classes.
+ * Returns a newly allocated GList which should be freed with plugin_class_list_free(list). */
+GList * plugin_get_available_classes(void)
 {
-    GList* classes = NULL;
-    gchar *path;
-    char *dir_path;
-    const char* file;
-    GDir* dir;
-    GList* l;
-    PluginClass *pc;
-
-    if (!pcl)
+    /* Initialize static plugins on first call. */
+    if (pcl == NULL)
         init_plugin_class_list();
 
-    for( l = pcl; l; l = l->next ) {
-        pc = (PluginClass*)l->data;
-        classes = g_list_prepend( classes, pc );
-        ++pc->count;
+    /* Loop over all classes to formulate the result.
+     * Increase the reference count; it will be decreased in plugin_class_list_free. */
+    GList * classes = NULL;
+    GList * l;
+    for (l = pcl; l != NULL; l = l->next)
+    {
+        PluginClass * pc = (PluginClass *) l->data;
+        classes = g_list_prepend(classes, pc);
+        pc->count += 1;
     }
 
 #ifndef DISABLE_PLUGINS_LOADING
-    if( dir = g_dir_open( PACKAGE_LIB_DIR "/lxpanel/plugins", 0, NULL ) ) {
-        while( file = g_dir_read_name( dir ) ) {
-            GModule *m;
-            char* type;
-            if( ! g_str_has_suffix( file, ".so" ) )
-                  continue;
-            type = g_strndup( file, strlen(file) - 3 );
-            l = plugin_find_class( type );
-            if( l == NULL ) { /* If it has not been loaded */
-                path = g_build_filename( PACKAGE_LIB_DIR "/lxpanel/plugins", file, NULL );
-                if( pc = plugin_load_dynamic( type, path ) ) {
-                    ++pc->count;
-                    classes = g_list_prepend( classes, pc );
+    GDir * dir = g_dir_open(PACKAGE_LIB_DIR "/lxpanel/plugins", 0, NULL);
+    if (dir != NULL)
+    {
+        const char * file;
+        while ((file = g_dir_read_name(dir)) != NULL)
+        {
+            if (g_str_has_suffix(file, ".so"))
+            {
+                char * type = g_strndup(file, strlen(file) - 3);
+                if (plugin_find_class(type) == NULL)
+                {
+                    /* If it has not been loaded, do it.  If successful, add it to the result. */
+                    char * path = g_build_filename(PACKAGE_LIB_DIR "/lxpanel/plugins", file, NULL );
+                    PluginClass * pc = plugin_load_dynamic(type, path);
+                    if (pc != NULL)
+                    {
+                        pc->count += 1;
+                        classes = g_list_prepend(classes, pc);
+                    }
+                    g_free(path);
                 }
-                g_free( path );
+                g_free(type);
             }
-            g_free( type );
         }
-        g_dir_close( dir );
+        g_dir_close(dir);
     }
 #endif
-    /* classes = g_list_reverse( classes ); */
     return classes;
 }
 
-void plugin_class_list_free( GList* classes )
+/* Free the list allocated by plugin_get_available_classes. */
+void plugin_class_list_free(GList * list)
 {
-   g_list_foreach( classes, (GFunc)plugin_class_unref, NULL );
-   g_list_free( classes );
+   g_list_foreach(list, (GFunc) plugin_class_unref, NULL);
+   g_list_free(list);
 }
 
-void
-plugin_widget_set_background( GtkWidget* w, Panel* p )
+/* Recursively set the background of all widgets on a panel background configuration change. */
+void plugin_widget_set_background(GtkWidget * w, Panel * p)
 {
-       static gboolean in_tray = FALSE;
-       gboolean is_tray;
-
-    if( ! w )
-        return;
-
-    is_tray = ( GTK_IS_CONTAINER(w) && strcmp( gtk_widget_get_name( w ), "tray" ) == 0 );
-
-    if( ! GTK_WIDGET_NO_WINDOW( w ) )
+    if (w != NULL)
     {
-        if( p->background || p->transparent )
+        if ( ! GTK_WIDGET_NO_WINDOW(w))
         {
-            gtk_widget_set_app_paintable( w, TRUE );
-            if( GTK_WIDGET_REALIZED(w) )
-                gdk_window_set_back_pixmap( w->window, NULL, TRUE );
+            if ((p->background) || (p->transparent))
+            {
+                gtk_widget_set_app_paintable(w, TRUE);
+                if (GTK_WIDGET_REALIZED(w))
+                    gdk_window_set_back_pixmap(w->window, NULL, TRUE);
+            }
+            else
+            {
+                /* Set background according to the current GTK style. */
+                gtk_widget_set_app_paintable(w, FALSE);
+                if (GTK_WIDGET_REALIZED(w))
+                    gtk_style_set_background(w->style, w->window, GTK_STATE_NORMAL);
+            }
         }
-        else
+
+        /* Special handling for layout widget, used in icon grid.
+         * The widget has a "bin window", which is where the actual drawing is done. */
+        if (GTK_IS_LAYOUT(w))
         {
-            gtk_widget_set_app_paintable( w, FALSE );
-            if( GTK_WIDGET_REALIZED(w) )
+            GdkWindow * bin_window = gtk_layout_get_bin_window(GTK_LAYOUT(w));
+            if (bin_window != NULL)
             {
-                gdk_window_set_back_pixmap( w->window, NULL, FALSE );
-
-                               /* dirty hack to fix the background color of tray icons :-( */
-                if( in_tray )
-                                       gdk_window_set_background( w->window, &w->style->bg[ GTK_STATE_NORMAL ] );
-                       }
+                if ((p->background) || (p->transparent))
+                    panel_determine_background_pixmap(p, w, bin_window);
+                else
+                    gtk_style_set_background(w->style, bin_window, GTK_STATE_NORMAL);
+                    
+               gdk_window_clear(bin_window);
+                gdk_window_invalidate_rect(bin_window, NULL, TRUE);
+            }
         }
-        // g_debug("%s has window (%s)", gtk_widget_get_name(w), G_OBJECT_TYPE_NAME(w) );
-    }
-    /*
-    else
-        g_debug("%s has NO window (%s)", gtk_widget_get_name(w), G_OBJECT_TYPE_NAME(w) );
-    */
-    if( GTK_IS_CONTAINER( w ) )
-    {
-       if( is_tray )
-               in_tray = TRUE;
-
-        gtk_container_foreach( (GtkContainer*)w, 
-                (GtkCallback)plugin_widget_set_background, p );
 
-        if( is_tray )
-                       in_tray = FALSE;
-    }
+        /* Special handling to get tray icons redrawn.  This is the only known working technique to date. */
+        if (GTK_IS_SOCKET(w))
+        {
+            gtk_widget_hide(w);
+            if (gtk_events_pending()) gtk_main_iteration();
+            gtk_widget_show(w);
+            if (gtk_events_pending()) gtk_main_iteration();
+        }
 
-    /* Dirty hack: Force repaint of tray icons!!
-     * Normal gtk+ repaint cannot work across different processes.
-     * So, we need some dirty tricks here.
-     */
-    if( is_tray )
-    {
-        gtk_widget_set_size_request( w, w->allocation.width, w->allocation.height );
-        gtk_widget_hide (gtk_bin_get_child((GtkBin*)w));
-        if( gtk_events_pending() )
-            gtk_main_iteration();
-        gtk_widget_show (gtk_bin_get_child((GtkBin*)w));
-        if( gtk_events_pending() )
-            gtk_main_iteration();
-        gtk_widget_set_size_request( w, -1, -1 );
+        /* Recursively process all children of a container. */
+        if (GTK_IS_CONTAINER(w))
+            gtk_container_foreach(GTK_CONTAINER(w), (GtkCallback) plugin_widget_set_background, p);
     }
 }
 
-void plugin_set_background( Plugin* pl, Panel* p )
+/* Set the background of a plugin. */
+void plugin_set_background(Plugin * pl, Panel * p)
 {
     if (pl->pwid != NULL)
-        plugin_widget_set_background( pl->pwid, p );
+        plugin_widget_set_background(pl->pwid, p);
 }
 
 /* Handler for "button_press_event" signal with Plugin as parameter.
@@ -403,3 +384,25 @@ gboolean plugin_button_press_event(GtkWidget *widget, GdkEventButton *event, Plu
     }    
     return FALSE;
 }
+
+/* Helper for position-calculation callback for popup menus. */
+void plugin_popup_set_position_helper(Plugin * p, GtkWidget * near, GtkWidget * popup, GtkRequisition * popup_req, gint * px, gint * py)
+{
+    /* Get the origin of the requested-near widget in screen coordinates. */
+    gint x, y;
+    gdk_window_get_origin(GDK_WINDOW(near->window), &x, &y);
+    x += near->allocation.x;
+    y += near->allocation.y;
+
+    /* Dispatch on edge to lay out the popup menu with respect to the button.
+     * Also set "push-in" to avoid any case where it might flow off screen. */
+    switch (p->panel->edge)
+    {
+        case EDGE_TOP:          y += near->allocation.height;         break;
+        case EDGE_BOTTOM:       y -= popup_req->height;                break;
+        case EDGE_LEFT:         x += near->allocation.width;          break;
+        case EDGE_RIGHT:        x -= popup_req->width;                 break;
+    }
+    *px = x;
+    *py = y;
+}
index 5ca99fd..758c10c 100644 (file)
 
 struct _Plugin;
 
+/* Support for external plugin versioning.
+ * Plugins must invoke PLUGINCLASS_VERSIONING when they instantiate PluginClass. */
+#define PLUGINCLASS_VERSION 1
+#define PLUGINCLASS_VERSIONING \
+    structure_size : sizeof(PluginClass), \
+    structure_version : PLUGINCLASS_VERSION
+
+/* Representative of an available plugin. */
 typedef struct {
-    /* common */
-    char *fname;
-    int count;
-    GModule *gmodule;
 
-    int dynamic : 1;
-    int invisible : 1;
+    /* Keep these first.  Do not make unnecessary changes in structure layout. */
+    unsigned short structure_size;             /* Structure size, for versioning support */
+    unsigned short structure_version;          /* Structure version, for versioning support */
+
+    char * fname;                              /* Plugin file pathname */
+    int count;                                 /* Reference count */
+    GModule * gmodule;                         /* Associated GModule structure */
+
+    int dynamic : 1;                           /* True if dynamically loaded */
+    int unused_invisible : 1;                  /* Unused; reserved bit */
     int not_unloadable : 1;                    /* Not unloadable due to GModule restriction */
     int one_per_system : 1;                    /* Special: only one possible per system, such as system tray */
     int one_per_system_instantiated : 1;       /* True if one instance exists */
-
-    /* these fields are pointers to the data within loaded dll */
-    char *type;
-    char *name;
-    char *version;
-    char *description;
-
-    int (*constructor)(struct _Plugin *this, char **fp);
-    void (*destructor)(struct _Plugin *this);
-    void (*config)(struct _Plugin *this, GtkWindow* parent); /* config UI */
-    void (*save)(struct _Plugin *this, FILE* fp);
-    void (*orientation)(struct _Plugin *this);
+    int expand_available : 1;                  /* True if "stretch" option is available */
+    int expand_default : 1;                    /* True if "stretch" option is default */
+
+    /* These fields point within the plugin image. */
+    char * type;                               /* Internal name of plugin, to match external filename */
+    char * name;                               /* Display name of plugin for selection UI */
+    char * version;                            /* Version of plugin */
+    char * description;                                /* Brief textual description of plugin for selection UI */
+
+    int (*constructor)(struct _Plugin * plugin, char ** fp);           /* Create an instance of the plugin */
+    void (*destructor)(struct _Plugin * plugin);                       /* Destroy an instance of the plugin */
+    void (*config)(struct _Plugin * plugin, GtkWindow * parent);       /* Request the plugin to display its configuration dialog */
+    void (*save)(struct _Plugin * plugin, FILE * fp);                  /* Request the plugin to save its configuration to a file */
+    void (*panel_configuration_changed)(struct _Plugin * plugin);      /* Request the plugin to do a full redraw after a panel configuration change */
 } PluginClass;
 
-typedef struct _Plugin{
-    PluginClass *class;
-    Panel        *panel;
-    GtkWidget    *pwid;
-    int           expand;
-    int           padding;
-    int           border;
-    gpointer      priv;
+/* Representative of a loaded and active plugin attached to a panel. */
+typedef struct _Plugin {
+    PluginClass * class;                       /* Back pointer to PluginClass */
+    Panel * panel;                             /* Back pointer to Panel */
+    GtkWidget * pwid;                          /* Top level widget; plugin allocates, but plugin mechanism, not plugin itself, destroys this */
+    int expand;                                        /* Expand ("stretch") setting for container */
+    int padding;                               /* Padding setting for container */
+    int border;                                        /* Border setting for container */
+    gpointer priv;                             /* Private context for plugin; plugin frees this in its destructor */
 } Plugin;
 
-/* if plugin is external it will load its dll */
-Plugin * plugin_load(char *type);
-void plugin_put(Plugin *this);
-int plugin_start(Plugin *this, char **fp);
-void plugin_stop(Plugin *this);
-
-void plugin_class_unref( PluginClass* pc );
-
-/*
-   Get a list of all available plugin classes
-   Return a newly allocated GList which should be freed with
-   plugin_class_list_free( list );
-*/
-GList* plugin_get_available_classes();
-void plugin_class_list_free( GList* classes );
-
-void plugin_set_background( Plugin* pl, Panel* p );
-void plugin_widget_set_background( GtkWidget* w, Panel* p );
-gboolean plugin_button_press_event(GtkWidget *widget, GdkEventButton *event, Plugin *plugin);
+extern Plugin * plugin_load(char * type);              /* Create an instance of a plugin, loading it if necessary */
+extern int plugin_start(Plugin * this, char ** fp);    /* Configure and start a plugin by calling its constructor */
+extern void plugin_unload(Plugin * pl);                        /* Delete an instance of a plugin if initialization fails */
+extern void plugin_delete(Plugin * pl);                        /* Delete an instance of a plugin */
+extern GList * plugin_get_available_classes(void);     /* Get a list of all plugin classes; free with plugin_class_list_free */
+extern void plugin_class_list_free(GList * list);      /* Free the list allocated by plugin_get_available_classes */
+extern void plugin_widget_set_background(GtkWidget * w, Panel * p);
+                                                       /* Recursively set the background of all widgets on a panel background configuration change. */
+extern void plugin_set_background( Plugin* pl, Panel* p );  /* Set the background of a plugin. */
+extern gboolean plugin_button_press_event(GtkWidget *widget, GdkEventButton *event, Plugin *plugin);
+                                                        /* Handler for "button_press_event" signal with Plugin as parameter. */
+extern void plugin_popup_set_position_helper(Plugin * p, GtkWidget * near, GtkWidget * popup, GtkRequisition * popup_req, gint * px, gint * py);
+                                                       /* Helper for position-calculation callback for popup menus. */
 
 /* FIXME: optional definitions */
 #define STATIC_SEPARATOR
@@ -96,25 +105,4 @@ gboolean plugin_button_press_event(GtkWidget *widget, GdkEventButton *event, Plu
 #define STATIC_SPACE
 #define STATIC_ICONS
 
-#if 0
-/* Try to handle GTypePlugin problems, but in vain. :-( */
-#define LX_TYPE_TYPE_PLUGIN           (lx_type_plugin_get_type ())
-#define LX_TYPE_PLUGIN(obj)           (G_TYPE_CHECK_INSTANCE_CAST ((obj), LX_TYPE_TYPE_PLUGIN, LXTypePlugin))
-#define LX_TYPE_PLUGIN_CLASS(klass)   (G_TYPE_CHECK_CLASS_CAST ((klass), LX_TYPE_TYPE_PLUGIN, LXTypePluginClass))
-#define LX_IS_TYPE_PLUGIN(obj)        (G_TYPE_CHECK_INSTANCE_TYPE ((obj), LX_TYPE_TYPE_PLUGIN))
-
-typedef struct _LXTypePlugin LXTypePlugin;
-typedef struct _LXTypePluginClass LXTypePluginClass;
-
-struct _LXTypePlugin {
-    GObject parent;
-};
-
-struct _LXTypePluginClass {
-    GObjectClass parent;
-};
-
-GTypePlugin* lx_type_plugin_get( const char* plugin_name );
-#endif
-
 #endif
index 34cc9ae..6999c5c 100644 (file)
@@ -34,6 +34,7 @@ PLUGINS_SOURCES = \
        separator.c \
        pager.c \
        space.c \
+    tray.c \
        wincmd.c \
        dirmenu.c
 
index f77882a..04d3587 100644 (file)
@@ -48,7 +48,6 @@
 #include "misc.h" /* used for the line struct */
 #include "panel.h" /* used to determine panel orientation */
 #include "plugin.h"
-#include "glib-mem.h" /* compatibility macros for g_slice* */
 
 /* The last MAX_SAMPLES samples are averaged when charge rates are evaluated.
    This helps prevent spikes in the "time left" values the user sees. */
@@ -614,8 +613,8 @@ static void save(Plugin* p, FILE* fp) {
 
 
 PluginClass batt_plugin_class = {
-    fname       : NULL,
-    count       : 0,
+    
+    PLUGINCLASS_VERSIONING,
 
     type        : "batt",
     name        : N_("Battery Monitor"),
@@ -626,5 +625,5 @@ PluginClass batt_plugin_class = {
     destructor  : destructor,
     config      : config,
     save        : save,
-    orientation : orientation
+    panel_configuration_changed : orientation
 };
index a7ceec9..0ebdb6e 100644 (file)
@@ -50,7 +50,7 @@ typedef struct {
     GtkWidget * da;                            /* Drawing area */
     GdkPixmap * pixmap;                                /* Pixmap to be drawn on drawing area */
 
-    int timer;                                 /* Timer for periodic update */
+    guint timer;                               /* Timer for periodic update */
     CPUSample * stats_cpu;                     /* Ring buffer of CPU utilization values */
     unsigned int ring_cursor;                  /* Cursor for ring buffer */
     int pixmap_width;                          /* Width of drawing area pixmap; also size of ring buffer; does not include border size */
@@ -262,6 +262,8 @@ static void cpu_destructor(Plugin * p)
 /* Plugin descriptor. */
 PluginClass cpu_plugin_class = {
 
+    PLUGINCLASS_VERSIONING,
+
     type : "cpu",
     name : N_("CPU Usage Monitor"),
     version: "1.0",
index 594afc1..c41868e 100644 (file)
 #include <stdlib.h>
 #include <string.h>
 #include <glib/gi18n.h>
-#include <pthread.h>
 
 #include "panel.h"
 #include "misc.h"
 #include "plugin.h"
-#include "glib-mem.h"
 
 #include "dbg.h"
 
 #define DEFAULT_TIP_FORMAT    "%A %x"
 #define DEFAULT_CLOCK_FORMAT  "%R"
 
+/* Private context for digital clock plugin. */
 typedef struct {
-    Panel* panel;
-    GtkWidget *eb;
-    GtkWidget *main;
-    GtkWidget *clockw;
-    GtkWidget *calwin;
-    char *tfmt;
-    char *cfmt;
-    char *action;
-    short lastDay;
-    int bold;
-    int timer;
-    gboolean cal_show;
-    gchar *prev_str;
-} dclock;
-
-static void
-update_label_orient( Plugin* p );
-
-static GtkWidget *create_calendar()
+    Plugin * plugin;                           /* Back pointer to Plugin */
+    GtkWidget * clock_label;                   /* Label containing clock value */
+    GtkWidget * calendar_window;               /* Calendar window, if it is being displayed */
+    char * clock_format;                       /* Format string for clock value */
+    char * tooltip_format;                     /* Format string for tooltip value */
+    char * action;                             /* Command to execute on a click */
+    gboolean bold;                             /* True if bold font */
+    guint timer;                               /* Timer for periodic update */
+    char * prev_output;                                /* Previous value of clock */
+} DClockPlugin;
+
+static GtkWidget * dclock_create_calendar(void);
+static gboolean dclock_button_press_event(GtkWidget * widget, GdkEventButton * evt, Plugin * plugin);
+static gboolean dclock_update_display(DClockPlugin * dc);
+static int dclock_constructor(Plugin * p, char ** fp);
+static void dclock_destructor(Plugin * p);
+static void dclock_apply_configuration(Plugin * p);
+static void dclock_configure(Plugin * p, GtkWindow * parent);
+static void dclock_save_configuration(Plugin * p, FILE * fp);
+static void dclock_panel_configuration_changed(Plugin * p);
+
+/* Display a window containing the standard calendar widget. */
+static GtkWidget * dclock_create_calendar(void)
 {
-    GtkWidget *win;
-    GtkWidget *calendar;
-
-    /* create a new window */
-    win = gtk_window_new(GTK_WINDOW_TOPLEVEL);
+    /* Create a new window. */
+    GtkWidget * win = gtk_window_new(GTK_WINDOW_TOPLEVEL);
     gtk_window_set_default_size(GTK_WINDOW(win), 180, 180);
     gtk_window_set_decorated(GTK_WINDOW(win), FALSE);
-    gtk_window_set_resizable (GTK_WINDOW(win), FALSE);
+    gtk_window_set_resizable(GTK_WINDOW(win), FALSE);
     gtk_container_set_border_width(GTK_CONTAINER(win), 5);
     gtk_window_set_skip_taskbar_hint(GTK_WINDOW(win), TRUE);
     gtk_window_set_skip_pager_hint(GTK_WINDOW(win), TRUE);
-//    gtk_window_set_type_hint(GTK_WINDOW(win), GDK_WINDOW_TYPE_HINT_DOCK);
     gtk_window_set_position(GTK_WINDOW(win), GTK_WIN_POS_MOUSE);
-    gtk_window_stick (GTK_WINDOW(win));
-
-    GtkVBox* box = (GtkVBox*)gtk_vbox_new(FALSE, 0);
-
-    /* calendar */
-    calendar = gtk_calendar_new();
-    gtk_calendar_display_options(GTK_CALENDAR(calendar),
-                                 GTK_CALENDAR_SHOW_WEEK_NUMBERS |
-                                 GTK_CALENDAR_SHOW_DAY_NAMES |
-                                 GTK_CALENDAR_SHOW_HEADING);
-//    gtk_container_add(GTK_CONTAINER(win), calendar);
-    gtk_box_pack_start_defaults( GTK_BOX(box), calendar );
+    gtk_window_stick(GTK_WINDOW(win));
+
+    /* Create a vertical box as a child of the window. */
+    GtkWidget * box = gtk_vbox_new(FALSE, 0);
     gtk_container_add(GTK_CONTAINER(win), GTK_WIDGET(box));
 
-    gtk_widget_show_all(win);
+    /* Create a standard calendar widget as a child of the vertical box. */
+    GtkWidget * calendar = gtk_calendar_new();
+    gtk_calendar_display_options(
+        GTK_CALENDAR(calendar),
+        GTK_CALENDAR_SHOW_WEEK_NUMBERS | GTK_CALENDAR_SHOW_DAY_NAMES | GTK_CALENDAR_SHOW_HEADING);
+    gtk_box_pack_start_defaults(GTK_BOX(box), calendar);
 
+    /* Return the widget. */
     return win;
 }
 
-static void *
-actionProcess( void *arg )
-{
-    return ((void *)system((char *) arg));
-}
-
-static  gboolean
-clicked( GtkWidget *widget, GdkEventButton* evt, Plugin* plugin)
+/* Handler for "button-press-event" event from main widget. */
+static gboolean dclock_button_press_event(GtkWidget * widget, GdkEventButton * evt, Plugin * plugin)
 {
-    dclock *dc = (dclock*)plugin->priv;
+    DClockPlugin * dc = (DClockPlugin *) plugin->priv;
 
     /* Standard right-click handling. */
     if (plugin_button_press_event(widget, evt, plugin))
         return TRUE;
 
-    if( dc->action ) {
-        pthread_t actionThread;
-        pthread_create(&actionThread, NULL, actionProcess, dc->action);
-    } else {
-        if (!dc->cal_show) {
-            dc->cal_show = TRUE;
-            dc->calwin = create_calendar();
-        } else {
-            dc->cal_show = FALSE;
-            gtk_widget_destroy(dc->calwin);
-            dc->calwin = NULL;
+    /* If an action is set, execute it. */
+    if (dc->action != NULL)
+        g_spawn_command_line_async(dc->action, NULL);
+
+    /* If no action is set, toggle the presentation of the calendar. */
+    else
+    {
+        if (dc->calendar_window == NULL)
+        {
+            dc->calendar_window = dclock_create_calendar();
+            gtk_widget_show_all(dc->calendar_window);
+        }
+        else
+        {
+            gtk_widget_destroy(dc->calendar_window);
+            dc->calendar_window = NULL;
         }
     }
-    return FALSE;
+    return TRUE;
 }
 
-static gint
-clock_update(gpointer data )
+/* Periodic timer callback.
+ * Also used during initialization and configuration change to do a redraw. */
+static gboolean dclock_update_display(DClockPlugin * dc)
 {
-    char output[64], str[64];
+    /* Determine the current time. */
     time_t now;
-    struct tm * detail;
-    dclock *dc;
-    gchar *utf8;
-
-    g_assert(data != NULL);
-    dc = (dclock *)data;
-
     time(&now);
-    detail = localtime(&now);
+    struct tm * detail = localtime(&now);
+
+    /* Determine the content of the clock label. */
+    char output[64];
     strftime(output, sizeof(output),
-             (dc->cfmt ? dc->cfmt : DEFAULT_CLOCK_FORMAT), detail);
-
-    if (dc->bold&& dc->panel->usefontcolor)
-        g_snprintf(str, 64, "<span color=\"#%06x\"><b>%s</b></span>", gcolor2rgb24( &dc->panel->gfontcolor ), output);
-    else if (dc->bold)
-        g_snprintf(str, 64, "<b>%s</b>", output);
-    else if ( dc->panel->usefontcolor)
-        g_snprintf(str, 64, "<span color=\"#%06x\">%s</span>", gcolor2rgb24(&dc->panel->gfontcolor), output);
-    else
-        g_snprintf(str, 64, "%s", output);
+             ((dc->clock_format != NULL) ? dc->clock_format : DEFAULT_CLOCK_FORMAT), detail);
 
     /* When we write the clock value, it causes the panel to do a full relayout.
      * Since this function is called once per second, we take the trouble to check if the string actually changed first. */
-    if ((dc->prev_str == NULL) || (strcmp(dc->prev_str, str) != 0))
+    if ((dc->prev_output == NULL) || (strcmp(dc->prev_output, output) != 0))
     {
-        g_free(dc->prev_str);
-        dc->prev_str = g_strdup(str);
-        gtk_label_set_markup (GTK_LABEL(dc->clockw), str);
+        g_free(dc->prev_output);
+        dc->prev_output = g_strdup(output);
+
+        gchar * utf8 = g_locale_to_utf8(output, -1, NULL, NULL, NULL);
+        if (utf8 != NULL)
+        {
+            panel_draw_label_text(dc->plugin->panel, dc->clock_label, output, dc->bold);
+            g_free(utf8);
+        }
     }
 
-    if (detail->tm_mday != dc->lastDay) {
-        dc->lastDay = detail->tm_mday ;
-        strftime (output, sizeof(output),
-                  (dc->tfmt ? dc->tfmt : DEFAULT_TIP_FORMAT), detail);
-            if ((utf8 = g_locale_to_utf8(output, -1, NULL, NULL, NULL))) {
-                gtk_widget_set_tooltip_text(dc->main, utf8);
-                g_free(utf8);
-            }
+    /* Determine the content of the tooltip. */
+    strftime(output, sizeof(output),
+        ((dc->tooltip_format != NULL) ? dc->tooltip_format : DEFAULT_TIP_FORMAT), detail);
+    gchar * utf8 = g_locale_to_utf8(output, -1, NULL, NULL, NULL);
+    if (utf8 != NULL)
+    {
+        gtk_widget_set_tooltip_text(dc->plugin->pwid, utf8);
+        g_free(utf8);
     }
-
     return TRUE;
 }
 
-
-static int
-dclock_constructor(Plugin *p, char** fp)
+/* Plugin constructor. */
+static int dclock_constructor(Plugin * p, char ** fp)
 {
-    line s;
-    dclock *dc;
-
-    dc = g_slice_new0(dclock);
-    g_return_val_if_fail(dc != NULL, 0);
+    /* Allocate and initialize plugin context and set into Plugin private data pointer. */
+    DClockPlugin * dc = g_new0(DClockPlugin, 1);
     p->priv = dc;
+    dc->plugin = p;
 
-    dc->panel = p->panel;
-
+    /* Load parameters from the configuration file. */
+    line s;
     s.len = 256;
-    dc->cfmt = dc->tfmt = dc->action = NULL;
-    dc->bold = 0;
-    dc->bold = TRUE;
-    dc->cal_show = FALSE;
-    if( fp )
+    if (fp != NULL)
     {
-        while (lxpanel_get_line(fp, &s) != LINE_BLOCK_END) {
-            if (s.type == LINE_NONE) {
+        while (lxpanel_get_line(fp, &s) != LINE_BLOCK_END)
+        {
+            if (s.type == LINE_NONE)
+            {
                 ERR( "dclock: illegal token %s\n", s.str);
-                goto error;
+                return 0;
             }
-            if (s.type == LINE_VAR) {
-                if (!g_ascii_strcasecmp(s.t[0], "ClockFmt"))
-                    dc->cfmt = g_strdup(s.t[1]);
-                else if (!g_ascii_strcasecmp(s.t[0], "TooltipFmt"))
-                    dc->tfmt = g_strdup(s.t[1]);
-                else if (!g_ascii_strcasecmp(s.t[0], "Action"))
+            if (s.type == LINE_VAR)
+            {
+                if (g_ascii_strcasecmp(s.t[0], "ClockFmt") == 0)
+                    dc->clock_format = g_strdup(s.t[1]);
+                else if (g_ascii_strcasecmp(s.t[0], "TooltipFmt") == 0)
+                    dc->tooltip_format = g_strdup(s.t[1]);
+                else if (g_ascii_strcasecmp(s.t[0], "Action") == 0)
                     dc->action = g_strdup(s.t[1]);
-                else if (!g_ascii_strcasecmp(s.t[0], "BoldFont"))
+                else if (g_ascii_strcasecmp(s.t[0], "BoldFont") == 0)
                     dc->bold = str2num(bool_pair, s.t[1], 0);
-                else {
+                else
                     ERR( "dclock: unknown var %s\n", s.t[0]);
-                    goto error;
-                }
-            } else {
+            }
+            else
+            {
                 ERR( "dclock: illegal in this context %s\n", s.str);
-                goto error;
+                return 0;
             }
         }
     }
 
-    p->pwid = dc->main = gtk_event_box_new();
+    /* Allocate top level widget and set into Plugin widget pointer. */
+    p->pwid = gtk_event_box_new();
 
-    g_signal_connect (G_OBJECT (dc->main), "button_press_event",
-          G_CALLBACK (clicked), (gpointer) p);
-    dc->clockw = gtk_label_new(NULL);
-    gtk_misc_set_alignment(GTK_MISC(dc->clockw), 0.5, 0.5);
-    gtk_misc_set_padding(GTK_MISC(dc->clockw), 4, 0);
-    update_label_orient( p );
-    gtk_container_add(GTK_CONTAINER(dc->main), dc->clockw);
-    gtk_widget_show_all(dc->main);
+    /* Allocate a label as the child of the top level. */
+    dc->clock_label = gtk_label_new(NULL);
+    gtk_misc_set_alignment(GTK_MISC(dc->clock_label), 0.5, 0.5);
+    gtk_misc_set_padding(GTK_MISC(dc->clock_label), 4, 0);
+    gtk_container_add(GTK_CONTAINER(p->pwid), dc->clock_label);
 
-    dc->timer = g_timeout_add(1000, (GSourceFunc) clock_update, (gpointer)dc);
+    /* Connect signals. */
+    g_signal_connect(G_OBJECT (p->pwid), "button_press_event", G_CALLBACK(dclock_button_press_event), (gpointer) p);
 
-    clock_update( dc );
+    /* Initialize the clock display */
+    dclock_apply_configuration(p);
 
-    return 1;
+    /* Start a timer to refresh the clock display. */
+    dc->timer = g_timeout_add(1000, (GSourceFunc) dclock_update_display, (gpointer) dc);
 
- error:
-    g_free(dc->cfmt);
-    g_free(dc->tfmt);
-    g_free(dc->action);
-    g_free(dc->prev_str);
-    g_slice_free(dclock, dc);
-    return 0;
+    /* Show the widget and return. */
+    gtk_widget_show_all(p->pwid);
+    return 1;
 }
 
-
-static void
-dclock_destructor(Plugin *p)
+/* Plugin destructor. */
+static void dclock_destructor(Plugin * p)
 {
-    dclock *dc = (dclock *)p->priv;
+    DClockPlugin * dc = (DClockPlugin *) p->priv;
 
-    if (dc->timer)
+    /* Remove the timer. */
+    if (dc->timer != 0)
         g_source_remove(dc->timer);
 
-    gtk_widget_destroy(dc->clockw);
+    /* Ensure that the calendar is dismissed. */
+    if (dc->calendar_window != NULL)
+        gtk_widget_destroy(dc->calendar_window);
 
-    g_free(dc->cfmt);
-    g_free(dc->tfmt);
+    /* Deallocate all memory. */
+    g_free(dc->clock_format);
+    g_free(dc->tooltip_format);
     g_free(dc->action);
-    g_slice_free(dclock, dc);
+    g_free(dc->prev_output);
+    g_free(dc);
 }
 
-static void apply_config( Plugin* p )
+/* Callback when the configuration dialog has recorded a configuration change. */
+static void dclock_apply_configuration(Plugin * p)
 {
-    /* NOTE: This dirty hack is used to force the update of tooltip
-       because tooltip will be updated when dc->lastDay != today.
-    */
-    dclock* dc = (dclock*)p->priv;
-    --dc->lastDay;
-    clock_update( dc );
+    DClockPlugin * dc = (DClockPlugin *) p->priv;
+    g_free(dc->prev_output);   /* Force the update of the clock display */
+    dc->prev_output = NULL;
+    dclock_update_display(dc);
 }
 
-static void dclock_config( Plugin *p, GtkWindow* parent )
+/* Callback when the configuration dialog is to be shown. */
+static void dclock_configure(Plugin * p, GtkWindow * parent)
 {
-    GtkWidget* dlg;
-    dclock *dc = (dclock *)p->priv;
-    dlg = create_generic_config_dlg( _(p->class->name),
-                                     GTK_WIDGET(parent),
-                                    (GSourceFunc) apply_config, (gpointer) p,
-                                     _("Clock Format"), &dc->cfmt, CONF_TYPE_STR,
-                                     _("Tooltip Format"), &dc->tfmt, CONF_TYPE_STR,
-                                     _("Format codes: man 3 strftime"), NULL, CONF_TYPE_TRIM,
-                                     _("Action"), &dc->action, CONF_TYPE_STR,
-                                     _("Bold font"), &dc->bold, CONF_TYPE_BOOL,
-                                     NULL );
-    gtk_window_present( GTK_WINDOW(dlg) );
+    DClockPlugin * dc = (DClockPlugin *) p->priv;
+    GtkWidget * dlg = create_generic_config_dlg(
+        p->class->name,
+        GTK_WIDGET(parent),
+        (GSourceFunc) dclock_apply_configuration, (gpointer) p,
+        _("Clock Format"), &dc->clock_format, CONF_TYPE_STR,
+        _("Tooltip Format"), &dc->tooltip_format, CONF_TYPE_STR,
+        _("Format codes: man 3 strftime"), NULL, CONF_TYPE_TRIM,
+        _("Action when clicked (default: display calendar)"), &dc->action, CONF_TYPE_STR,
+        _("Bold font"), &dc->bold, CONF_TYPE_BOOL,
+        NULL);
+    gtk_window_present(GTK_WINDOW(dlg));
 }
 
-static void save_config( Plugin* p, FILE* fp )
+/* Callback when the configuration is to be saved. */
+static void dclock_save_configuration(Plugin * p, FILE * fp)
 {
-    dclock *dc = (dclock *)p->priv;
-    lxpanel_put_str( fp, "ClockFmt", dc->cfmt );
-    lxpanel_put_str( fp, "TooltipFmt", dc->tfmt );
-    lxpanel_put_str( fp, "Action", dc->action );
-    lxpanel_put_int( fp, "BoldFont", dc->bold );
+    DClockPlugin * dc = (DClockPlugin *) p->priv;
+    lxpanel_put_str(fp, "ClockFmt", dc->clock_format);
+    lxpanel_put_str(fp, "TooltipFmt", dc->tooltip_format);
+    lxpanel_put_str(fp, "Action", dc->action);
+    lxpanel_put_int(fp, "BoldFont", dc->bold);
 }
 
-static void
-update_label_orient( Plugin* p )
+/* Callback when panel configuration changes. */
+static void dclock_panel_configuration_changed(Plugin * p)
 {
-    dclock *dc = (dclock *)p->priv;
-    GtkLabel* label = GTK_LABEL(dc->clockw);
-    /* FIXME: gtk+ has only limited support for this, sigh! */
-    gdouble angle;
-    if( p->panel->edge == EDGE_LEFT )
-        angle = 90.0;
-    else if( p->panel->edge == EDGE_RIGHT )
-        angle = 270.0;
-    else
-        angle = 0.0;
-    gtk_label_set_angle( GTK_LABEL(label), angle );
+    dclock_apply_configuration(p);
 }
 
+/* Plugin descriptor. */
 PluginClass dclock_plugin_class = {
 
+    PLUGINCLASS_VERSIONING,
+
     type : "dclock",
     name : N_("Digital Clock"),
     version: "1.0",
-    description : N_("Display Digital clock and Tooltip"),
+    description : N_("Display digital clock and tooltip"),
 
     constructor : dclock_constructor,
     destructor  : dclock_destructor,
-    config : dclock_config,
-    save : save_config,
-    orientation : update_label_orient
+    config : dclock_configure,
+    save : dclock_save_configuration,
+    panel_configuration_changed : dclock_panel_configuration_changed
 };
index 7df1aa1..e6014d3 100644 (file)
 
 /* Private context for desktop number plugin. */
 typedef struct {
-    Panel *panel;                      /* Back pointer to Panel */
-    GtkWidget *label;                  /* Label */
+    Panel * panel;                     /* Back pointer to Panel */
+    GtkWidget * label;                 /* The label */
+    int number_of_desktops;            /* Number of desktops */
+    char * * desktop_labels;           /* Vector of desktop labels */
 } DesknoPlugin;
 
 static gboolean name_update(GtkWidget * widget, DesknoPlugin * dc);
 static gboolean clicked(GtkWidget * widget, GdkEventButton * event, Plugin * p);
 static int deskno_constructor(Plugin * p, char ** fp);
 static void deskno_destructor(Plugin * p);
+static void deskno_panel_configuration_changed(Plugin * p);
 
 /* Handler for current_desktop event from window manager. */
 static gboolean name_update(GtkWidget * widget, DesknoPlugin * dc)
 {
     /* Compute and redraw the desktop number. */
-    char buffer[128];
-    g_snprintf(buffer, sizeof(buffer), "<span color=\"#%06x\"><b>%d</b></span>",
-        ((dc->panel->usefontcolor) ? gcolor2rgb24(&dc->panel->gfontcolor) : 0), get_net_current_desktop() + 1);
-    gtk_label_set_markup(GTK_LABEL(dc->label), buffer);
+    int desktop_number = get_net_current_desktop();
+    if (desktop_number < dc->number_of_desktops)
+        panel_draw_label_text(dc->panel, dc->label, dc->desktop_labels[desktop_number], TRUE);
     return TRUE;
 }
 
+/* Handler for desktop_name and number_of_desktops events from window manager. */
+static void update_all(GtkWidget * widget, DesknoPlugin * dc)
+{
+    /* Get the NET_DESKTOP_NAMES property. */
+    dc->number_of_desktops = get_net_number_of_desktops();
+    int number_of_desktop_names;
+    char * * desktop_names;
+    desktop_names = get_utf8_property_list(GDK_ROOT_WINDOW(), a_NET_DESKTOP_NAMES, &number_of_desktop_names);
+
+    /* Reallocate the vector of labels. */
+    if (dc->desktop_labels != NULL)
+        g_strfreev(dc->desktop_labels);
+    dc->desktop_labels = g_new0(gchar *, dc->number_of_desktops + 1);
+
+    /* Loop to copy the desktop names to the vector of labels.
+     * If there are more desktops than labels, label the extras with a decimal number. */
+    int i;
+    for (i = 0; ((desktop_names != NULL) && (i < MIN(dc->number_of_desktops, number_of_desktop_names))); i++)
+        dc->desktop_labels[i] = g_strdup(desktop_names[i]);
+    for ( ; i < dc->number_of_desktops; i++)
+        dc->desktop_labels[i] = g_strdup_printf("%d", i + 1);
+
+    /* Free the property. */
+    if (desktop_names != NULL)
+        g_strfreev(desktop_names);
+
+    /* Redraw the label. */
+    name_update(widget, dc);
+}
+
 /* Handler for button-press-event on top level widget. */
 static gboolean clicked(GtkWidget * widget, GdkEventButton * event, Plugin * p)
 {
@@ -94,9 +126,11 @@ static int deskno_constructor(Plugin * p, char ** fp)
     /* Connect signals.  Note use of window manager event object. */
     g_signal_connect(p->pwid, "button_press_event", G_CALLBACK(clicked), p);
     g_signal_connect(G_OBJECT(fbev), "current_desktop", G_CALLBACK(name_update), (gpointer) dc);
+    g_signal_connect(G_OBJECT(fbev), "desktop_names", G_CALLBACK(update_all), (gpointer) dc);
+    g_signal_connect(G_OBJECT(fbev), "number_of_desktops", G_CALLBACK(update_all), (gpointer) dc);
 
     /* Initialize value and show the widget. */
-    name_update(NULL, dc);
+    update_all(NULL, dc);
     gtk_widget_show_all(p->pwid);
     return 1;
 }
@@ -108,19 +142,33 @@ static void deskno_destructor(Plugin * p)
 
     /* Disconnect signal from window manager event object. */
     g_signal_handlers_disconnect_by_func(G_OBJECT(fbev), name_update, dc);
+
+    /* Deallocate all memory. */
+    if (dc->desktop_labels != NULL)
+        g_strfreev(dc->desktop_labels);
     g_free(dc);
 }
 
+/* Callback when panel configuration changes. */
+static void deskno_panel_configuration_changed(Plugin * p)
+{
+    DesknoPlugin * dc = (DesknoPlugin *) p->priv;
+    name_update(NULL, dc);
+}
+
 /* Plugin descriptor. */
 PluginClass deskno_plugin_class = {
 
+    PLUGINCLASS_VERSIONING,
+
     type : "deskno",
-    name : N_("Desktop No / Workspace Name"),
+    name : N_("Desktop Number / Workspace Name"),
     version: "0.6",
     description : N_("Display workspace number, by cmeury@users.sf.net"),
 
     constructor : deskno_constructor,
     destructor  : deskno_destructor,
     config : NULL,
-    save : NULL
+    save : NULL,
+    panel_configuration_changed : deskno_panel_configuration_changed
 };
index afba97f..f514a28 100644 (file)
@@ -284,7 +284,6 @@ dirmenu_constructor(Plugin *p, char **fp)
     line s;
     gchar *fname;
     dirmenu *dm;
-    int w, h;
 
     s.len = 256;
     dm = g_new0(dirmenu, 1);
@@ -313,7 +312,6 @@ dirmenu_constructor(Plugin *p, char **fp)
                 }
                 else {
                     ERR( "dirmenu: unknown var %s\n", s.t[0]);
-                    goto error;
                 }
             } else {
                 ERR( "dirmenu: illegal in this context %s\n", s.str);
@@ -321,13 +319,6 @@ dirmenu_constructor(Plugin *p, char **fp)
             }
         }
     }
-    if (p->panel->orientation == ORIENT_HORIZ) {
-        w = 10000;
-        h = p->panel->ah;
-    } else {
-        w = p->panel->aw;
-        h = 10000;
-    }
 
     if (! fname)
         fname = strdup("file-manager");
@@ -335,9 +326,8 @@ dirmenu_constructor(Plugin *p, char **fp)
     /* Create button.
      * It is not known why, but the button text will not draw if it is edited from empty to non-empty
      * unless this strategy of initializing it with a non-empty value first is followed. */
-    p->pwid = dm->button = fb_button_new_from_file_with_colorlabel(fname, w, h,
-        0x202020, ((p->panel->usefontcolor) ? gcolor2rgb24(&p->panel->gfontcolor) : 0), TRUE,
-        "Temp");
+    p->pwid = dm->button = fb_button_new_from_file_with_label(fname, PANEL_ICON_SIZE, PANEL_ICON_SIZE,
+        PANEL_ICON_HIGHLIGHT, TRUE, p->panel, "Temp");
     dirmenu_apply_config_to_children(dm->button, dm);
     gtk_container_set_border_width( GTK_CONTAINER(dm->button), 0 );
     g_signal_connect( dm->button, "button_press_event",
@@ -355,8 +345,6 @@ dirmenu_constructor(Plugin *p, char **fp)
 
  error:
     g_free(fname);
-    dirmenu_destructor(p);
-    ERR( "%s - exit\n", __FUNCTION__);
     return 0;
 }
 
@@ -377,12 +365,7 @@ static void dirmenu_apply_config_to_children(GtkWidget *w, dirmenu* dm)
         if (dm->name == NULL)
            gtk_label_set_text(GTK_LABEL(w), NULL);
         else
-        {
-            gchar str[512];
-            g_snprintf(str, sizeof(str), "<span color=\"#%06x\">%s</span>",
-                ((dm->panel->usefontcolor) ? gcolor2rgb24(&dm->panel->gfontcolor) : 0), dm->name);
-            gtk_label_set_markup(GTK_LABEL(w), str);
-        }
+            panel_draw_label_text(dm->panel, w, dm->name, FALSE);
     }
 }
 
@@ -407,8 +390,16 @@ static void dirmenu_configure( Plugin *p, GtkWindow* parent )
     gtk_window_present( GTK_WINDOW(dlg) );
 }
 
+/* Callback when panel configuration changes. */
+static void dirmenu_panel_configuration_changed(Plugin * p)
+{
+    dirmenu_apply_config(p);
+}
+
 PluginClass dirmenu_plugin_class = {
 
+    PLUGINCLASS_VERSIONING,
+
     type : "dirmenu",
     name : N_("Directory Menu"),
     version: "1.0",
@@ -417,5 +408,7 @@ PluginClass dirmenu_plugin_class = {
     constructor : dirmenu_constructor,
     destructor  : dirmenu_destructor,
     config : dirmenu_configure,
-    save : save_config
+    save : save_config,
+    panel_configuration_changed : dirmenu_panel_configuration_changed
+
 };
index 10c036f..3a6ad55 100644 (file)
 #include "panel.h"
 #include "misc.h"
 #include "plugin.h"
-/*
-enum {
-    CapsLock = 0,
-    NumLock,
-    ScrlLock
-};
-*/
-const char* on_icons[]={
+#include "icon-grid.h"
+
+static const char * on_icons[] = {
     "capslock-on.png",
     "numlock-on.png",
     "scrllock-on.png"
 };
 
-const char* off_icons[]={
+static const char * off_icons[] = {
     "capslock-off.png",
     "numlock-off.png",
     "scrllock-off.png"
@@ -54,226 +49,211 @@ const char* off_icons[]={
 static int xkb_event_base = 0;
 static int xkb_error_base = 0;
 
-typedef struct _KbLed{
-    GtkWidget *mainw;
-    GtkWidget *img[3];
-    int old_state;
-    gboolean visible[3];
-} KbLed;
-
-static void apply_config( Plugin* p );
+/* Private context for keyboard LED plugin. */
+typedef struct {
+    IconGrid * icon_grid;                      /* Icon grid manager */
+    GtkWidget *indicator_image[3];             /* Image for each indicator */
+    int current_state;                         /* Current LED state, bit encoded */
+    gboolean visible[3];                       /* True if control is visible (per user configuration) */
+} KeyboardLEDPlugin;
+
+static void kbled_update_image(KeyboardLEDPlugin * kl, int i, int state);
+static void kbled_update_display(Plugin * p, unsigned int state);
+static GdkFilterReturn kbled_event_filter(GdkXEvent * gdkxevent, GdkEvent * event, Plugin * p);
+static int kbled_constructor(Plugin * p, char ** fp);
+static void kbled_destructor(Plugin * p);
+static void kbled_apply_configuration(Plugin * p);
+static void kbled_configure(Plugin * p, GtkWindow * parent);
+static void kbled_save_configuration(Plugin * p, FILE * fp);
+static void kbled_panel_configuration_changed(Plugin * p);
+
+/* Update image to correspond to current state. */
+static void kbled_update_image(KeyboardLEDPlugin * kl, int i, int state)
+{
+    char * file = g_build_filename(
+        PACKAGE_DATA_DIR "/lxpanel/images",
+        ((state) ? on_icons[i] : off_icons[i]),
+        NULL);
+    gtk_image_set_from_file(GTK_IMAGE(kl->indicator_image[i]), file);
+    g_free(file);
+}
 
-static void update_display( Plugin* p, unsigned int state )
+/* Redraw after Xkb event or initialization. */
+static void kbled_update_display(Plugin * p, unsigned int new_state)
 {
+    KeyboardLEDPlugin * kl = (KeyboardLEDPlugin *) p->priv;
     int i;
-    KbLed* kl = (KbLed*)p->priv;
-
-    for( i = 0; i < 3; ++i )
+    for (i = 0; i < 3; i++)
     {
-        if (!kl->visible[i])
-            continue;
-
-        gboolean old = kl->old_state & (1 << i);
-        gboolean cur = state & (1 << i);
-        if( old != cur )
-        {
-            char* file = g_build_filename( PACKAGE_DATA_DIR "/lxpanel/images",
-                                                    cur ? on_icons[i] : off_icons[i], NULL );
-            gtk_image_set_from_file( (GtkImage *)kl->img[ i ], file );
-            g_free( file );
-        }
+        /* If the control changed state, redraw it. */
+        int current_is_lit = kl->current_state & (1 << i);
+        int new_is_lit = new_state & (1 << i);
+        if (current_is_lit != new_is_lit)
+            kbled_update_image(kl, i, new_is_lit);
     }
-    kl->old_state = state;
+
+    /* Save new state. */
+    kl->current_state = new_state;
 }
 
-GdkFilterReturn
-event_filter(GdkXEvent *gdkxevent, GdkEvent *event, Plugin* p)
+/* GDK event filter. */
+static GdkFilterReturn kbled_event_filter(GdkXEvent * gdkxevent, GdkEvent * event, Plugin * p)
 {
-    XEvent* xev = (XEvent*)gdkxevent;
-    if( xev->xany.type == xkb_event_base + XkbEventCode )
+    /* Look for XkbIndicatorStateNotify events and update the display. */
+    XEvent * xev = (XEvent *) gdkxevent;
+    if (xev->xany.type == xkb_event_base + XkbEventCode)
     {
-        XkbEvent* xkbev = (XkbEvent*)xev;
+        XkbEvent * xkbev = (XkbEvent *) xev;
         if (xkbev->any.xkb_type == XkbIndicatorStateNotify)
-            update_display(p, xkbev->indicators.state);
-    }
-/*
-    XkbEvent ev;
-    memcpy(&ev.core, gdkxevent, sizeof(ev.core));
-
-    if (ev.core.type == applet->xkbev + XkbEventCode)
-    {
-        if (ev.any.xkb_type == XkbIndicatorStateNotify)
-            ledstates_changed(applet, ev.indicators.state);
+            kbled_update_display(p, xkbev->indicators.state);
     }
-*/
     return GDK_FILTER_CONTINUE;
 }
 
-static void kbled_orientation( Plugin* p )
-{
-    KbLed* kl = (KbLed*)p->priv;
-    GtkWidget* newbox;
-    newbox = recreate_box( (GtkBox*)kl->mainw, p->panel->orientation);
-    if( newbox != kl->mainw ) {
-        /* Since the old box has been destroyed,
-        we need to re-add the new box to the container */
-        kl->mainw = newbox;
-        gtk_container_add( GTK_CONTAINER(p->pwid), kl->mainw );
-    }
-}
-
-static void
-kbled_destructor(Plugin *p)
-{
-    KbLed *kl = (KbLed*)p->priv;
-
-    gdk_window_remove_filter(NULL, (GdkFilterFunc)event_filter, p);
-    g_free( kl );
-}
-
-/* Initialize the xkb extension */
-static gboolean init_xkb()
-{
-    int opcode;
-    int maj = XkbMajorVersion;
-    int min = XkbMinorVersion;
-
-    if (!XkbLibraryVersion(&maj, &min))
-        return FALSE;
-    if (!XkbQueryExtension( GDK_DISPLAY(), &opcode, &xkb_event_base, &xkb_error_base, &maj, &min))
-        return FALSE;
-    return TRUE;
-}
-
-static int kbled_constructor(Plugin *p, char **fp)
+/* Plugin constructor. */
+static int kbled_constructor(Plugin * p, char ** fp)
 {
-    KbLed *kl;
-    GtkWidget *image;
-    line s;
-/*
-    GdkPixbuf *icon;
-    GtkIconTheme* theme;
-    GtkIconInfo* info;
-*/
-    int i, state;
-
-    if( ! xkb_event_base )  /* if xkb extension is not initialized */
-    {
-        if( G_UNLIKELY( ! init_xkb() ) )
-            return FALSE;
-    }
-    if (!XkbSelectEvents(GDK_DISPLAY(), XkbUseCoreKbd, XkbIndicatorStateNotifyMask, XkbIndicatorStateNotifyMask))
-        return FALSE;
-
-    kl = g_new0( KbLed, 1);
-    g_return_val_if_fail(kl != NULL, 0);
+    /* Allocate and initialize plugin context and set into Plugin private data pointer. */
+    KeyboardLEDPlugin * kl = g_new0(KeyboardLEDPlugin, 1);
     kl->visible[0] = FALSE;
     kl->visible[1] = TRUE;
     kl->visible[2] = TRUE;
     p->priv = kl;
+
+    /* Load parameters from the configuration file. */
+    line s;
     s.len = 256;
-    if (fp) {
-        while (lxpanel_get_line(fp, &s) != LINE_BLOCK_END) {
-            if (s.type == LINE_NONE) {
+    if (fp != NULL)
+    {
+        while (lxpanel_get_line(fp, &s) != LINE_BLOCK_END)
+        {
+            if (s.type == LINE_NONE)
+            {
                 ERR( "kbled: illegal token %s\n", s.str);
-                return FALSE;
+                return 0;
             }
-            if (s.type == LINE_VAR) {
-                if (!g_ascii_strcasecmp(s.t[0], "ShowCapsLock"))
+            if (s.type == LINE_VAR)
+            {
+                if (g_ascii_strcasecmp(s.t[0], "ShowCapsLock") == 0)
                     kl->visible[0] = str2num(bool_pair, s.t[1], 0);
-                else if (!g_ascii_strcasecmp(s.t[0], "ShowNumLock"))
+                else if (g_ascii_strcasecmp(s.t[0], "ShowNumLock") == 0)
                     kl->visible[1] = str2num(bool_pair, s.t[1], 0);
-                else if (!g_ascii_strcasecmp(s.t[0], "ShowScrollLock"))
+                else if (g_ascii_strcasecmp(s.t[0], "ShowScrollLock") == 0)
                     kl->visible[2] = str2num(bool_pair, s.t[1], 0);
-                else {
-                    ERR( "kbled: unknown var %s\n", s.t[0]);
-                    continue;
-                }
+                else
+                    ERR("kbled: unknown var %s\n", s.t[0]);
             }
-            else {
-                ERR( "kbled: illegal in this context %s\n", s.str);
-                return FALSE;
+            else
+            {
+                ERR("kbled: illegal in this context %s\n", s.str);
+                return 0;
             }
         }
     }
 
-    /* create a container */
+    /* Allocate top level widget and set into Plugin widget pointer. */
     p->pwid = gtk_event_box_new();
-    gtk_widget_add_events( p->pwid, GDK_BUTTON_PRESS_MASK );
-    g_signal_connect( p->pwid, "button-press-event",
-            G_CALLBACK(plugin_button_press_event), p );
+    gtk_widget_add_events(p->pwid, GDK_BUTTON_PRESS_MASK);
+    g_signal_connect(p->pwid, "button-press-event", G_CALLBACK(plugin_button_press_event), p);
 
-    /* create a box */
-    kl->mainw = p->panel->my_box_new( FALSE, 0 );
-    for( i =0; i < 3; ++i ) {
-        kl->img[i] = gtk_image_new();
-        //gtk_widget_set_size_request( kl->img[i], 22, 22 );
-        gtk_box_pack_start( (GtkBox*)kl->mainw, kl->img[i], FALSE, FALSE, 0 );
-        if (kl->visible[i]) {
-            gtk_widget_show(kl->img[i]);
-        } else {
-            gtk_widget_hide(kl->img[i]);
-       }
+    /* Allocate an icon grid manager to manage the container.
+     * Then allocate three images for the three indications, but make them visible only when the configuration requests. */
+    GtkOrientation bo = (p->panel->orientation == ORIENT_HORIZ) ? GTK_ORIENTATION_HORIZONTAL : GTK_ORIENTATION_VERTICAL;
+    kl->icon_grid = icon_grid_new(p->panel, p->pwid, bo, PANEL_ICON_SIZE, PANEL_ICON_SIZE, 0, 0, p->panel->height); 
+    int i;
+    for (i = 0; i < 3; i++)
+    {
+        kl->indicator_image[i] = gtk_image_new();
+        icon_grid_add(kl->icon_grid, kl->indicator_image[i], kl->visible[i]);
     }
-    gtk_container_add( (GtkContainer*)p->pwid, kl->mainw );
 
-    XkbGetIndicatorState(GDK_DISPLAY(), XkbUseCoreKbd, &state);
-    kl->old_state = ~state;
-    update_display( p, state );
+    /* Initialize Xkb extension if not yet done. */
+    if (xkb_event_base == 0)
+    {
+        int opcode;
+        int maj = XkbMajorVersion;
+        int min = XkbMinorVersion;
+        if ( ! XkbLibraryVersion(&maj, &min))
+            return 0;
+        if ( ! XkbQueryExtension(GDK_DISPLAY(), &opcode, &xkb_event_base, &xkb_error_base, &maj, &min))
+            return 0;
+    }
 
-    /* add event filter to monitor xkb events */
-    gdk_window_add_filter(NULL, (GdkFilterFunc)event_filter, p );
+    /* Add GDK event filter and enable XkbIndicatorStateNotify events. */
+    gdk_window_add_filter(NULL, (GdkFilterFunc) kbled_event_filter, p);
+    if ( ! XkbSelectEvents(GDK_DISPLAY(), XkbUseCoreKbd, XkbIndicatorStateNotifyMask, XkbIndicatorStateNotifyMask))
+        return 0;
+
+    /* Get current indicator state and update display.
+     * Force current state to differ in all bits so a full redraw will occur. */
+    int current_state;
+    XkbGetIndicatorState(GDK_DISPLAY(), XkbUseCoreKbd, &current_state);
+    kl->current_state = ~ current_state;
+    kbled_update_display(p, current_state);
+
+    /* Show the widget. */
+    gtk_widget_show(p->pwid);
+    return 1;
+}
 
-    gtk_widget_show(kl->mainw);
-    //gtk_tooltips_set_tip (vol->tooltips, vol->mainw, _("Volume control"), NULL);
+/* Plugin destructor. */
+static void kbled_destructor(Plugin * p)
+{
+    KeyboardLEDPlugin * kl = (KeyboardLEDPlugin *) p->priv;
 
-    return TRUE;
+    /* Remove GDK event filter. */
+    gdk_window_remove_filter(NULL, (GdkFilterFunc) kbled_event_filter, p);
+    icon_grid_free(kl->icon_grid);
+    g_free(kl);
 }
 
-static void apply_config( Plugin* p )
+/* Callback when the configuration dialog has recorded a configuration change. */
+static void kbled_apply_configuration(Plugin * p)
 {
+    KeyboardLEDPlugin * kl = (KeyboardLEDPlugin *) p->priv;
     int i;
-    KbLed *kl = (KbLed *)p->priv;
+    for (i = 0; i < 3; i++)
+        icon_grid_set_visible(kl->icon_grid, kl->indicator_image[i], kl->visible[i]);
+}
 
-    for( i =0; i < 3; i++ ) {
-        if (kl->visible[i]) {
-            char* file = g_build_filename( PACKAGE_DATA_DIR "/lxpanel/images",
-                                                    kl->old_state ? on_icons[i] : off_icons[i], NULL );
-            gtk_image_set_from_file((GtkImage *)kl->img[ i ], file);
-            g_free(file);
-            gtk_widget_show(kl->img[i]);
-        } else {
-            gtk_widget_hide(kl->img[i]);
-       }
-    }
+/* Callback when the configuration dialog is to be shown. */
+static void kbled_configure(Plugin * p, GtkWindow * parent)
+{
+    KeyboardLEDPlugin * kl = (KeyboardLEDPlugin *) p->priv;
+    GtkWidget * dlg = create_generic_config_dlg(
+        p->class->name,
+        GTK_WIDGET(parent),
+        (GSourceFunc) kbled_apply_configuration, (gpointer) p,
+        _("Show CapsLock"), &kl->visible[0], CONF_TYPE_BOOL,
+        _("Show NumLock"), &kl->visible[1], CONF_TYPE_BOOL,
+        _("Show ScrollLock"), &kl->visible[2], CONF_TYPE_BOOL,
+        NULL);
+    gtk_widget_set_size_request(GTK_WIDGET(dlg), 200, -1);     /* Improve geometry */
+    gtk_window_present(GTK_WINDOW(dlg));
 }
 
-static void save_config( Plugin* p, FILE* fp )
+/* Callback when the configuration is to be saved. */
+static void kbled_save_configuration(Plugin * p, FILE * fp)
 {
-    KbLed *kl = (KbLed *)p->priv;
-    lxpanel_put_int( fp, "ShowCapsLock", kl->visible[0] );
-    lxpanel_put_int( fp, "ShowNumLock", kl->visible[1] );
-    lxpanel_put_int( fp, "ShowScrollLock", kl->visible[2] );
+    KeyboardLEDPlugin * kl = (KeyboardLEDPlugin *) p->priv;
+    lxpanel_put_int(fp, "ShowCapsLock", kl->visible[0]);
+    lxpanel_put_int(fp, "ShowNumLock", kl->visible[1]);
+    lxpanel_put_int(fp, "ShowScrollLock", kl->visible[2]);
 }
 
-static void kbled_config( Plugin *p, GtkWindow* parent )
+/* Callback when panel configuration changes. */
+static void kbled_panel_configuration_changed(Plugin * p)
 {
-    GtkWidget* dlg;
-    KbLed *kl = (KbLed *)p->priv;
-    dlg = create_generic_config_dlg( _(p->class->name),
-                                     GTK_WIDGET(parent),
-                                    (GSourceFunc) apply_config, (gpointer) p,
-                                     _("Show CapsLock"), &kl->visible[0], CONF_TYPE_BOOL,
-                                     _("Show NumLock"), &kl->visible[1], CONF_TYPE_BOOL,
-                                     _("Show ScrollLock"), &kl->visible[2], CONF_TYPE_BOOL,
-                                     NULL );
-    gtk_widget_set_size_request(GTK_WIDGET(dlg), 200, -1);
-    gtk_window_present( GTK_WINDOW(dlg) );
+    /* Set orientation into the icon grid. */
+    KeyboardLEDPlugin * kl = (KeyboardLEDPlugin *) p->priv;
+    GtkOrientation bo = (p->panel->orientation == ORIENT_HORIZ) ? GTK_ORIENTATION_HORIZONTAL : GTK_ORIENTATION_VERTICAL;
+    icon_grid_set_orientation(kl->icon_grid, bo, p->panel->height);
 }
 
+/* Plugin descriptor. */
 PluginClass kbled_plugin_class = {
-    fname: NULL,
-    count: 0,
+
+    PLUGINCLASS_VERSIONING,
 
     type : "kbled",
     name : N_("Keyboard LED"),
@@ -282,7 +262,7 @@ PluginClass kbled_plugin_class = {
 
     constructor : kbled_constructor,
     destructor  : kbled_destructor,
-    config : kbled_config,
-    save : save_config,
-    orientation : kbled_orientation
+    config : kbled_configure,
+    save : kbled_save_configuration,
+    panel_configuration_changed : kbled_panel_configuration_changed
 };
index 755a039..b787a14 100644 (file)
 #include <gdk-pixbuf/gdk-pixbuf.h>
 #include <glib/gi18n.h>
 
+#include <menu-cache.h>
+
 #include "panel.h"
 #include "misc.h"
 #include "plugin.h"
+#include "icon-grid.h"
+#include "menu-policy.h"
 
 #include "dbg.h"
 
 #include "glib-mem.h"
 
-typedef enum {
-  CURSOR_STANDARD,
-  CURSOR_DND
-} CursorType;
-
+/* Drag and drop target info. */
 enum {
   TARGET_URILIST,
   TARGET_UTF8_STRING,
@@ -49,13 +49,6 @@ enum {
   TARGET_COMPOUND_TEXT
 };
 
-enum {
-    COL_ICON = 0,
-    COL_TITLE,
-    COL_BTN,
-    N_COLS
-};
-
 static const GtkTargetEntry target_table[] = {
     { "text/uri-list", 0, TARGET_URILIST},
     { "UTF8_STRING", 0, TARGET_UTF8_STRING },
@@ -64,340 +57,315 @@ static const GtkTargetEntry target_table[] = {
     { "STRING",        0, 0 }
 };
 
+/* Column definitions for configuration dialogs. */
+enum {
+    COL_ICON,
+    COL_TITLE,
+    COL_BTN,
+    N_COLS
+};
+
 static const char desktop_ent[] = "Desktop Entry";
 
-typedef struct btn_t {
-    Plugin* plugin;
-    GtkWidget* widget;
-    gchar *desktop_id;
-    gchar *image;
-    gchar *action;
-    gchar *tooltip;
-/*  NOTE: Users can override the values specified in desktop file,
-          and we should process these special cases. */
-    guchar use_terminal : 1;
-    guchar customize_image : 1;
-    guchar customize_action : 1;
-    guchar customize_tooltip : 1;
-} btn_t;
-
-typedef struct launchbar {
-    GtkBox *box;
-    GtkTooltips *tips;
-    GSList* btns;
-    int iconsize;
-    GtkWidget* config_dlg;
-} launchbar;
-
-void panel_config_save(Panel* panel);
-
-#if 0
-/* used in menu.c to find the launchbar with most buttons */
-int launchbar_get_n_btns( Plugin* pl )
+/* Representative of one launch button.
+ * Note that the launch parameters come from the specified desktop file, or from the configuration file.
+ * This structure is also used during the "add to launchbar" dialog to hold menu items. */
+typedef struct {
+    Plugin * plugin;                   /* Back pointer to plugin */
+    GtkWidget * widget;                        /* Pointer to button */
+    gchar * desktop_id;                        /* Name of application (desktop file name less the .desktop) */
+    gchar * image;                     /* Image icon (from Icon entry) */
+    gchar * action;                    /* Action (from Exec entry) */
+    gchar * tooltip;                   /* Tooltip (from Name entry) */
+    guchar use_terminal : 1;           /* True if Terminal=true or from configuration file */
+    guchar customize_image : 1;                /* True if image icon from configuration file */
+    guchar customize_action : 1;       /* True if action from configuration file */
+    guchar customize_tooltip : 1;      /* True if tooltip from configuration file */
+} LaunchButton;
+
+/* Private context for launchbar plugin. */
+typedef struct {
+    IconGrid * icon_grid;              /* Icon grid managing the container */
+    GSList * buttons;                  /* Launchbar buttons */
+    GtkWidget * config_dlg;            /* Configuration dialog */
+    LaunchButton * bootstrap_button;   /* Bootstrapping button for empty launchbar */
+} LaunchbarPlugin;
+
+void panel_config_save(Panel * panel);  /* defined in configurator.c */
+
+static void launchbutton_free(LaunchButton * btn);
+static gboolean launchbutton_press_event(GtkWidget * widget, GdkEventButton * event, LaunchButton * b);
+static void launchbutton_drag_data_received_event(
+    GtkWidget * widget,
+    GdkDragContext * context,
+    gint x,
+    gint y,
+    GtkSelectionData * sd,
+    guint info,
+    guint time,
+    LaunchButton * b);
+static void launchbutton_build_bootstrap(Plugin * p);
+static void launchbutton_build_gui(Plugin * p, LaunchButton * btn);
+static int launchbutton_constructor(Plugin * p, char ** fp);
+static int launchbar_constructor(Plugin * p, char ** fp);
+static void launchbar_destructor(Plugin * p);
+static void launchbar_configure_menu_item_add_button(GtkButton * widget, Plugin * p);
+static void launchbar_configure_add_button(GtkButton * widget, Plugin * p);
+static void launchbar_configure_remove_button(GtkButton * widget, Plugin * p);
+static void launchbar_configure_move_up_button(GtkButton * widget, Plugin * p);
+static void launchbar_configure_move_down_button(GtkButton * widget, Plugin * p);
+static void launchbar_configure_response(GtkDialog * dlg, int response, Plugin * p);
+static void launchbar_configure_initialize_list(Plugin * p, GtkWidget * dlg, GtkTreeView * view, gboolean from_menu);
+static void launchbar_configure(Plugin * p, GtkWindow * parent);
+static void launchbar_save_configuration(Plugin * p, FILE * fp);
+static void launchbar_panel_configuration_changed(Plugin * p);
+
+/* Deallocate a LaunchButton. */
+static void launchbutton_free(LaunchButton * btn)
 {
-    return g_slist_length(((launchbar*)pl->priv)->btns);
+    g_free(btn->desktop_id);
+    g_free(btn->image);
+    g_free(btn->action);
+    g_free(btn->tooltip);
+    g_free(btn);
 }
-#endif
 
-void btn_free( btn_t* btn )
+/* Handler for "button-press-event" event from launchbar button. */
+static gboolean launchbutton_press_event(GtkWidget * widget, GdkEventButton * event, LaunchButton * b)
 {
-    g_free( btn->desktop_id );
-    g_free( btn->image );
-    g_free( btn->action );
-    g_free( btn->tooltip );
-    g_slice_free( btn_t, btn );
+    GtkWidget *image;
+
+    /* Standard right-click handling. */
+    if (plugin_button_press_event(widget, event, b->plugin))
+        return TRUE;
+
+    if (event->button == 1)    /* left button */
+    {
+        if (b->desktop_id == NULL)     /* The bootstrap button */
+            launchbar_configure(b->plugin, NULL);
+        else if (b->action != NULL)
+            lxpanel_launch_app(b->action, NULL, b->use_terminal);
+    }
+    return TRUE;
 }
 
-static gboolean
-on_button_event(GtkWidget *widget, GdkEventButton *event, btn_t *b )
+/* Handler for "drag-data-received" event from launchbar button. */
+static void launchbutton_drag_data_received_event(
+    GtkWidget * widget,
+    GdkDragContext * context,
+    gint x,
+    gint y,
+    GtkSelectionData * sd,
+    guint info,
+    guint time,
+    LaunchButton * b)
 {
-    GtkWidget *image;
-    if( event->button == 1 )    /* left button */
+    if (sd->length > 0)
     {
-        image = gtk_bin_get_child(GTK_BIN(widget));
-        g_assert(b != NULL);
-        if (event->type == GDK_BUTTON_RELEASE)
+        if (info == TARGET_URILIST)
         {
-            if ((event->x >=0 && event->x < widget->allocation.width)
-                  && (event->y >=0 && event->y < widget->allocation.height))
+            gchar * s = (gchar *) sd->data;
+            gchar * end = s + sd->length;
+            gchar * str = g_strdup(b->action);
+            while (s < end)
             {
-                if( b->action )
-                    lxpanel_launch_app(b->action, NULL, b->use_terminal);
+                while (s < end && g_ascii_isspace(*s))
+                    s++;
+                gchar * e = s;
+                while (e < end && !g_ascii_isspace(*e))
+                    e++;
+                if (s != e)
+                {
+                    *e = 0;
+                    s = g_filename_from_uri(s, NULL, NULL);
+                    if (s)
+                    {
+                        gchar * tmp = g_strconcat(str, " '", s, "'", NULL);
+                        g_free(str);
+                        g_free(s);
+                        str = tmp;
+                    }
+                }
+                s = e+1;
             }
-            gtk_misc_set_padding (GTK_MISC(image), 0, 0);
 
-            //system(b->action);
-        }
-        else if (event->type == GDK_BUTTON_PRESS)
-        {
-            gtk_misc_set_padding (GTK_MISC(image), 0, 3);
-            //ERR("here\n");
+            g_spawn_command_line_async(str, NULL);
+            g_free(str);
         }
-        return TRUE;
     }
-    else if(event->button == 3) /* right click */
+}
+
+/* Build the graphic elements for the bootstrap launchbar button. */
+static void launchbutton_build_bootstrap(Plugin * p)
+{
+    LaunchbarPlugin * lb = (LaunchbarPlugin *) p->priv;
+
+    if (lb->bootstrap_button == NULL)
     {
-        GtkMenu* popup = lxpanel_get_panel_menu
-                ( b->plugin->panel, b->plugin, TRUE );
-        GtkWidget* item;
-        char* title;
-
-#if 0
-        item = gtk_image_menu_item_new_with_label( _("Add Button") );
-        gtk_menu_shell_append( popup, item );
-        item = gtk_image_menu_item_new_with_label( _("Button Properties") );
-        gtk_menu_shell_append( popup, item );
-        /*
-        title = g_strdup_printf( _("Remove \"%s\""), b-> );
-        item = gtk_image_menu_item_new_with_label( _("Remove ") );
-        */
-        item = gtk_image_menu_item_new_with_label( _("Remove Button") );
-        gtk_menu_shell_append( popup, item );
-#endif
-        gtk_widget_show_all( (GtkWidget*)popup );
-
-        gtk_menu_popup( popup, NULL, NULL, NULL, NULL, event->button, event->time );
-        return TRUE;
+        /* Build a button that has the stock "Add" icon.
+         * The "desktop-id" being NULL is the marker that this is the bootstrap button. */
+        lb->bootstrap_button = g_new0(LaunchButton, 1);
+        lb->bootstrap_button->plugin = p;
+        lb->bootstrap_button->widget = gtk_button_new();
+        gtk_button_set_image(GTK_BUTTON(lb->bootstrap_button->widget), gtk_image_new_from_stock(GTK_STOCK_ADD, GTK_ICON_SIZE_MENU));
+        g_signal_connect(lb->bootstrap_button->widget, "button-press-event", G_CALLBACK(launchbutton_press_event), lb->bootstrap_button);
+
+        /* Add the bootstrap button to the icon grid.  By policy it is empty at this point. */
+        icon_grid_add(lb->icon_grid, lb->bootstrap_button->widget, TRUE); 
     }
-    return FALSE;
+    else
+        icon_grid_set_visible(lb->icon_grid, lb->bootstrap_button->widget, TRUE);
 }
 
-static void
-launchbar_destructor(Plugin *p)
+/* Build the graphic elements for a launchbar button.  The desktop_id field is already established. */
+static void launchbutton_build_gui(Plugin * p, LaunchButton * btn)
 {
-    launchbar *lb = (launchbar *)p->priv;
+    LaunchbarPlugin * lb = (LaunchbarPlugin *) p->priv;
 
-    gtk_widget_destroy(GTK_WIDGET(lb->box));
-    g_slist_foreach( lb->btns, (GFunc)btn_free, NULL );
-    g_slice_free(launchbar, lb);
-}
+    if (btn->desktop_id != NULL)
+    {
+        /* There is a valid desktop file name.  Try to open it. */
+        GKeyFile * desktop = g_key_file_new();
+        
+       gchar * desktop_file = NULL;
+        gboolean loaded;       
+       if (g_path_is_absolute(btn->desktop_id))
+        {
+            desktop_file = g_strdup(btn->desktop_id);
+            loaded = g_key_file_load_from_file(desktop, desktop_file, G_KEY_FILE_NONE, NULL );
+       }
+       else 
+       {
+            /* Load from the freedesktop.org specified data directories. */
+            gchar * full_id = g_strconcat("applications/", btn->desktop_id, NULL);
+            loaded = g_key_file_load_from_data_dirs(
+                desktop, full_id, &desktop_file, G_KEY_FILE_NONE, NULL);
+            g_free(full_id);
+        }
 
+       if (loaded)
+        {
+            /* Desktop file located.  Get Icon, Name, Exec, and Terminal parameters. */
+            gchar * icon = g_key_file_get_string(desktop, desktop_ent, "Icon", NULL);
+            gchar * title = g_key_file_get_locale_string(desktop, desktop_ent, "Name", NULL, NULL);
+            if ((btn->image == NULL) && (icon != NULL))
+                btn->image = icon;
 
-static void
-drag_data_received_cb (GtkWidget        *widget,
-      GdkDragContext   *context,
-      gint              x,
-      gint              y,
-      GtkSelectionData *sd,
-      guint             info,
-      guint             time,
-      btn_t              *b)
-{
-    gchar *s, *e, *end, *str, *tmp;
-
-    ENTER;
-    if (sd->length <= 0)
-        RET();
-    if (info == TARGET_URILIST) {
-        DBG("uri drag received: info=%d len=%d data=%s\n", info, sd->length, sd->data);
-        s = (gchar *)sd->data;
-        end = s + sd->length;
-        str = g_strdup(b->action);
-        while (s < end) {
-            while (s < end && g_ascii_isspace(*s))
-                s++;
-            e = s;
-            while (e < end && !g_ascii_isspace(*e))
-                e++;
-            if (s != e) {
-                *e = 0;
-                s = g_filename_from_uri(s, NULL, NULL);
-                if (s) {
-                    //strlen(s);
-                    //strlen(str);
-                    tmp = g_strconcat(str, " '", s, "'", NULL);
-                    g_free(str);
-                    g_free(s);
-                    str = tmp;
-                }
+            if ( ! btn->customize_action )
+            {
+                gchar * exec = g_key_file_get_string(desktop, desktop_ent, "Exec", NULL);
+                btn->action = translate_exec_to_cmd(exec, icon, title, desktop_file);
+                g_free(exec);
             }
-            s = e+1;
+
+            btn->use_terminal = g_key_file_get_boolean(desktop, desktop_ent, "Terminal", NULL);
+
+            if ( ! btn->customize_tooltip)
+                btn->tooltip = title;
+            if (btn->image != icon)
+                g_free(icon);
+            if (btn->tooltip != title)
+                g_free(title);
         }
-        DBG("cmd=<%s>\n", str);
-        g_spawn_command_line_async(str, NULL);
-        g_free(str);
 
-        //gtk_drag_finish (context, TRUE, FALSE, time);
+        g_free(desktop_file);
+        g_key_file_free(desktop);
     }
-    RET();
+
+    /* Create a button with the specified icon. */
+    GtkWidget * button = fb_button_new_from_file(btn->image, PANEL_ICON_SIZE, PANEL_ICON_SIZE, PANEL_ICON_HIGHLIGHT, TRUE);
+    btn->widget = button;
+    GTK_WIDGET_UNSET_FLAGS(button, GTK_CAN_FOCUS);
+    if (btn->tooltip != NULL)
+        gtk_widget_set_tooltip_text(button, btn->tooltip);
+
+    /* Add the button to the icon grid. */
+    icon_grid_add(lb->icon_grid, button, TRUE);
+
+    /* Drag and drop support. */
+    gtk_drag_dest_set(GTK_WIDGET(button),
+        GTK_DEST_DEFAULT_ALL,
+        target_table, G_N_ELEMENTS(target_table),
+        GDK_ACTION_COPY);
+
+    /* Connect signals. */
+    g_signal_connect(button, "button-press-event", G_CALLBACK(launchbutton_press_event), (gpointer) btn);
+    g_signal_connect(button, "drag_data_received", G_CALLBACK(launchbutton_drag_data_received_event), (gpointer) btn);
+
+    /* If the list goes from null to non-null, remove the bootstrap button. */
+    if ((lb->buttons == NULL) && (lb->bootstrap_button != NULL))
+        icon_grid_set_visible(lb->icon_grid, lb->bootstrap_button->widget, FALSE);
+
+    /* Append at end of list to preserve configured order. */
+    lb->buttons = g_slist_append(lb->buttons, btn);
+
+    /* Show the widget and return. */
+    gtk_widget_show(button);
+    plugin_widget_set_background(button, p->panel);
 }
 
-static int
-read_button(Plugin *p, char** fp)
+/* Read the configuration file entry for a launchbar button and create it. */
+static int launchbutton_constructor(Plugin * p, char ** fp)
 {
-    launchbar *lb = (launchbar *)p->priv;
-    gchar *fname;
-    GtkWidget *button;
-    line s;
-    int w, h;
-    btn_t* btn;
-
-    ENTER;
+    LaunchbarPlugin * lb = (LaunchbarPlugin *) p->priv;
 
-    btn = g_slice_new0( btn_t );
+    /* Allocate the LaunchButton structure. */
+    LaunchButton * btn = g_new0(LaunchButton, 1);
     btn->plugin = p;
 
+    /* Read parameters from the configuration file. */
+    line s;
     s.len = 256;
-    fname= NULL;
-
-    if( fp )
+    if (fp != NULL)
     {
-        while (lxpanel_get_line(fp, &s) != LINE_BLOCK_END) {
-            if (s.type == LINE_NONE) {
+        while (lxpanel_get_line(fp, &s) != LINE_BLOCK_END)
+        {
+            if (s.type == LINE_NONE)
+            {
                 ERR( "launchbar: illegal token %s\n", s.str);
-                RET(0);
+                launchbutton_free(btn);
+                return 0;
             }
             if (s.type == LINE_VAR)
             {
-                if( !g_ascii_strcasecmp(s.t[0], "id") )
+                if (g_ascii_strcasecmp(s.t[0], "id") == 0)
                     btn->desktop_id = g_strdup(s.t[1]);
-                else if (!g_ascii_strcasecmp(s.t[0], "image"))
+                else if (g_ascii_strcasecmp(s.t[0], "image") == 0)
                 {
-                    btn->customize_image = 1;
-                    btn->image = g_strdup(s.t[1]);
-                    fname = expand_tilda(s.t[1]);
+                    btn->customize_image = TRUE;
+                    btn->image = expand_tilda(g_strdup(s.t[1]));
                 }
-                else if (!g_ascii_strcasecmp(s.t[0], "tooltip"))
+                else if (g_ascii_strcasecmp(s.t[0], "tooltip") == 0)
                 {
-                    btn->customize_tooltip = 1;
+                    btn->customize_tooltip = TRUE;
                     btn->tooltip = g_strdup(s.t[1]);
                 }
-                else if (!g_ascii_strcasecmp(s.t[0], "action"))
+                else if (g_ascii_strcasecmp(s.t[0], "action") == 0)
                 {
-                    btn->customize_action = 1;
+                    btn->customize_action = TRUE;
                     btn->action = g_strdup(s.t[1]);
                 }
                 else
-                {
                     ERR( "launchbar: unknown var %s\n", s.t[0]);
-                    goto error;
-
-                }
-                DBG("action=%s\n", action);
-            } else {
-                ERR( "launchbar: illegal in this context %s\n", s.str);
-                goto error;
             }
-        }
-    }
-
-    if( btn->desktop_id )
-    {
-        gchar *desktop_file = NULL;
-        gchar *full_id = NULL;
-        GKeyFile* desktop = g_key_file_new();
-       gboolean loaded;
-       
-       if ( g_path_is_absolute( btn->desktop_id ) ) 
-       {
-           desktop_file = g_strdup( btn->desktop_id );
-           loaded =  g_key_file_load_from_file( desktop, desktop_file,
-                                                G_KEY_FILE_NONE, NULL );
-       }
-       else 
-       {
-           full_id = g_strconcat( "applications/", btn->desktop_id, NULL );
-           loaded = g_key_file_load_from_data_dirs( desktop, full_id, &desktop_file,
-                                                    G_KEY_FILE_NONE, NULL );
-           g_free( full_id );
-       }
-
-       /* key file located */
-       if ( loaded )
-        {
-            gchar *icon = NULL, *title = NULL;
-            icon = g_key_file_get_string( desktop, desktop_ent, "Icon", NULL);
-            title = g_key_file_get_locale_string( desktop, desktop_ent,
-                                                "Name", NULL, NULL);
-            if( !fname && icon )
-                fname = icon;
-
-            if( ! btn->customize_action )
+            else
             {
-                gchar* exec;
-                exec = g_key_file_get_string( desktop, desktop_ent, "Exec", NULL);
-                btn->action = translate_exec_to_cmd( exec, icon, title, desktop_file );
-                g_free( exec );
+                ERR( "launchbar: illegal in this context %s\n", s.str);
+                launchbutton_free(btn);
+                return 0;
             }
-
-            btn->use_terminal = g_key_file_get_boolean(desktop, desktop_ent, "Terminal", NULL);
-
-            if( ! btn->customize_tooltip )
-                btn->tooltip = title;
-            if( fname != icon )
-                g_free( icon );
-            if( btn->tooltip != title )
-                g_free( title );
         }
-        g_free( desktop_file );
-        g_key_file_free( desktop );
-    }
-
-    // button
-    if (p->panel->orientation == ORIENT_HORIZ) {
-        h = p->panel->ah;
-        w = h;
-    } else {
-        w = p->panel->aw;
-        h = w;
     }
-    button = fb_button_new_from_file( fname, w, h, 0x202020, TRUE );
-    btn->widget = button;
-
-    //gtk_container_set_border_width(GTK_CONTAINER(button), 0);
-    g_signal_connect ( button, "button-release-event",
-          G_CALLBACK (on_button_event), (gpointer) btn );
-    g_signal_connect ( button, "button-press-event",
-          G_CALLBACK (on_button_event), (gpointer) btn );
 
-    GTK_WIDGET_UNSET_FLAGS (button, GTK_CAN_FOCUS);
-
-    // DnD support
-    gtk_drag_dest_set (GTK_WIDGET(button),
-          GTK_DEST_DEFAULT_ALL, //GTK_DEST_DEFAULT_HIGHLIGHT,
-          target_table, G_N_ELEMENTS (target_table),
-          GDK_ACTION_COPY);
-    g_signal_connect ( button, "drag_data_received",
-          G_CALLBACK (drag_data_received_cb),  (gpointer) btn );
-
-    gtk_box_pack_start(GTK_BOX(lb->box), button, FALSE, TRUE, 0);
-
-    /* append is more time-consuming, but we really care about the order. */
-    lb->btns = g_slist_append( lb->btns, btn );
-
-    gtk_widget_show(button);
-    plugin_widget_set_background( button, p->panel );
-
-    g_free(fname);
-
-    /* tooltip */
-    if ( btn->tooltip ) {
-        gtk_widget_set_tooltip_text(button, btn->tooltip);
-    }
-    RET(1);
-
- error:
-    g_free(fname);
-    btn_free( btn );
-    RET(0);
+    /* Build the structures and return. */
+    launchbutton_build_gui(p, btn);
+    return 1;
 }
 
-static int
-launchbar_constructor(Plugin *p, char **fp)
+/* Plugin constructor. */
+static int launchbar_constructor(Plugin * p, char ** fp)
 {
-    launchbar *lb;
-    line s;
-    GtkRequisition req;
-    static char default_config[] =
-        "button {\n"
-            "id=pcmanfm.desktop\n"
-        "}\n"
-        "button {\n"
-            "id=firefox.desktop\n"
-        "}\n"
-        "}\n";
-    char *config_default = default_config;
-    static gchar *launchbar_rc = "style 'launchbar-style'\n"
+    static gchar * launchbar_rc = "style 'launchbar-style'\n"
         "{\n"
         "GtkWidget::focus-line-width = 0\n"
         "GtkWidget::focus-padding = 0\n"
@@ -408,393 +376,441 @@ launchbar_constructor(Plugin *p, char **fp)
 
     gtk_rc_parse_string(launchbar_rc);
 
-    p->pwid = gtk_event_box_new();
-    GTK_WIDGET_SET_FLAGS( p->pwid, GTK_NO_WINDOW );
-    gtk_widget_set_name(p->pwid, "launchbar");
-    get_button_spacing(&req, GTK_CONTAINER(p->pwid), "");
-
-    lb = g_slice_new0(launchbar);
-    g_return_val_if_fail(lb != NULL, 0);
+    /* Allocate plugin context and set into Plugin private data pointer. */
+    LaunchbarPlugin * lb = g_new0(LaunchbarPlugin, 1);
     p->priv = lb;
-    lb->box = GTK_BOX(p->panel->my_box_new(FALSE, 0));
 
-    gtk_container_add( (GtkContainer*)p->pwid, GTK_WIDGET(lb->box) );
-
-    gtk_container_set_border_width (GTK_CONTAINER (lb->box), 0);
-    gtk_widget_show(GTK_WIDGET(lb->box));
-
-    if  (p->panel->orientation == ORIENT_HORIZ)
-        lb->iconsize = GTK_WIDGET(p->panel->box)->allocation.height;
-    else
-        lb->iconsize = GTK_WIDGET(p->panel->box)->allocation.width;
+    /* Allocate top level widget and set into Plugin widget pointer. */
+    p->pwid = gtk_event_box_new();
+    GTK_WIDGET_SET_FLAGS(p->pwid, GTK_NO_WINDOW);
+    gtk_widget_set_name(p->pwid, "launchbar");
 
-    if( ! fp )
-        fp = &config_default;
+    /* Allocate an icon grid manager to manage the container. */
+    GtkOrientation bo = (p->panel->orientation == ORIENT_HORIZ) ? GTK_ORIENTATION_HORIZONTAL : GTK_ORIENTATION_VERTICAL;
+    lb->icon_grid = icon_grid_new(p->panel, p->pwid, bo, PANEL_ICON_SIZE, PANEL_ICON_SIZE, 3, 0, p->panel->height);
 
-    s.len = 256;
-    while (lxpanel_get_line(fp, &s) != LINE_BLOCK_END) {
-        if (s.type == LINE_NONE) {
-            ERR( "launchbar: illegal token %s\n", s.str);
-            goto error;
-        }
-        if (s.type == LINE_BLOCK_START) {
-            if (!g_ascii_strcasecmp(s.t[0], "button")) {
-                if (!read_button(p, fp)) {
-                    ERR( "launchbar: can't init button\n");
-                    goto error;
+    /* Read parameters from the configuration file. */
+    if (fp != NULL)
+    {
+        line s;
+        s.len = 256;
+        while (lxpanel_get_line(fp, &s) != LINE_BLOCK_END)
+        {
+            if (s.type == LINE_NONE)
+            {
+                ERR( "launchbar: illegal token %s\n", s.str);
+                return FALSE;
+            }
+            if (s.type == LINE_BLOCK_START)
+            {
+                if (g_ascii_strcasecmp(s.t[0], "button") == 0)
+                {
+                    if ( ! launchbutton_constructor(p, fp))
+                    {
+                        ERR( "launchbar: can't init button\n");
+                        return FALSE;
+                    }
+                }
+                else
+                {
+                    ERR( "launchbar: unknown var %s\n", s.t[0]);
+                    return FALSE;
                 }
-            } else {
-                ERR( "launchbar: unknown var %s\n", s.t[0]);
-                goto error;
             }
-        } else {
-            ERR( "launchbar: illegal in this context %s\n", s.str);
-            goto error;
+        else
+            {
+                ERR( "launchbar: illegal in this context %s\n", s.str);
+                return FALSE;
+            }
         }
     }
 
+    if (lb->buttons == NULL)
+        launchbutton_build_bootstrap(p);
     return TRUE;
-
- error:
-    launchbar_destructor(p);
-    return FALSE;
 }
 
-static void save_config( Plugin* p, FILE* fp )
+/* Plugin destructor. */
+static void launchbar_destructor(Plugin * p)
 {
-    launchbar *lb = (launchbar *)p->priv;
-    GSList* l;
-    for( l = lb->btns; l; l = l->next ) {
-        btn_t* btn = (btn_t*)l->data;
-        lxpanel_put_line( fp, "Button {" );
-        if( btn->desktop_id )
-            lxpanel_put_str( fp, "id", btn->desktop_id );
-        if( btn->customize_image )
-            lxpanel_put_str( fp, "image", btn->image );
-        if( btn->customize_tooltip )
-            lxpanel_put_str( fp, "tooltip", btn->tooltip );
-        if( btn->customize_action )
-            lxpanel_put_str( fp, "action", btn->action );
-        lxpanel_put_line( fp, "}" );
-    }
+    LaunchbarPlugin * lb = (LaunchbarPlugin *) p->priv;
+
+    g_slist_foreach(lb->buttons, (GFunc) launchbutton_free, NULL);
+    icon_grid_free(lb->icon_grid);
+    if (lb->bootstrap_button != NULL)
+        {
+        g_free(lb->bootstrap_button);
+        }
+    g_free(lb);
 }
 
-static void orientation_changed( Plugin* p )
+/* Handler for "clicked" action on launchbar configuration dialog "Add" button. */
+static void launchbar_configure_add_button(GtkButton * widget, Plugin * p)
 {
-    launchbar *lb = (launchbar *)p->priv;
-    GtkBox* newbox;
-    newbox = GTK_BOX(recreate_box( lb->box, p->panel->orientation ));
-    if( newbox != lb->box ) {
-        /* Since the old box has been destroyed,
-        we need to re-add the new box to the container */
-        lb->box = newbox;
-        gtk_container_add(GTK_CONTAINER(p->pwid), GTK_WIDGET(lb->box));
+    LaunchbarPlugin * lb = (LaunchbarPlugin *) p->priv;
+    GtkTreeView * menu_view = GTK_TREE_VIEW(g_object_get_data(G_OBJECT(lb->config_dlg), "menu_view"));
+    GtkTreeView * defined_view = GTK_TREE_VIEW(g_object_get_data(G_OBJECT(lb->config_dlg), "defined_view"));
+    GtkTreeModel * list;
+    GtkTreeIter it;
+    if (gtk_tree_selection_get_selected(gtk_tree_view_get_selection(menu_view), &list, &it))
+    {
+        LaunchButton * btn;
+        gtk_tree_model_get(list, &it, COL_BTN, &btn, -1);
+
+        /* We have located a selected button.
+         * Add a launch button to the launchbar and refresh the view in the configuration dialog. */
+        LaunchButton * defined_button = g_new0(LaunchButton, 1);
+        defined_button->plugin = p;
+        defined_button->desktop_id = g_strdup(btn->desktop_id);
+        launchbutton_build_gui(p, defined_button);
+        GtkListStore * list = GTK_LIST_STORE(gtk_tree_view_get_model(defined_view));
+        GtkTreeIter it;
+        gtk_list_store_append(list, &it);
+        gtk_list_store_set(list, &it,
+            COL_ICON, lxpanel_load_icon(btn->image, PANEL_ICON_SIZE, PANEL_ICON_SIZE, TRUE),
+            COL_TITLE, ((btn->tooltip != NULL) ? btn->tooltip : btn->action),
+            COL_BTN, defined_button,
+            -1);
     }
 }
 
-static void
-on_response( GtkDialog* dlg, int response, Plugin* p )
+/* Handler for "clicked" action on launchbar configuration dialog "Remove" button. */
+static void launchbar_configure_remove_button(GtkButton * widget, Plugin * p)
 {
-    launchbar *lb = (launchbar *)p->priv;
-    gtk_widget_destroy( GTK_WIDGET(dlg) );
-    lb->config_dlg = NULL;
+    LaunchbarPlugin * lb = (LaunchbarPlugin *) p->priv;
+    GtkTreeView * defined_view = GTK_TREE_VIEW(g_object_get_data(G_OBJECT(lb->config_dlg), "defined_view"));
+    GtkTreeModel * list;
+    GtkTreeIter it;
+    if (gtk_tree_selection_get_selected(gtk_tree_view_get_selection(defined_view), &list, &it))
+    {
+        LaunchButton * btn;
+        gtk_tree_model_get(list, &it, COL_BTN, &btn, -1);
+
+        /* We have found a selected button.
+         * Remove it from the icon grid, the data structure, and the view. */
+        gtk_list_store_remove(GTK_LIST_STORE(list), &it);
+        icon_grid_remove(lb->icon_grid, btn->widget);
+        lb->buttons = g_slist_remove(lb->buttons, btn);
+        launchbutton_free(btn);
+
+        /* Put the bootstrap button back if the list becomes empty. */
+        if (lb->buttons == NULL)
+            launchbutton_build_bootstrap(p);
+    }
 }
 
-static void on_add_btn_response( GtkDialog* dlg, int response, int* ret )
+/* Handler for "clicked" action on launchbar configuration dialog "Move Up" button. */
+static void launchbar_configure_move_up_button(GtkButton * widget, Plugin * p)
 {
-    *ret = response;
-    gtk_main_quit();
-}
+    LaunchbarPlugin * lb = (LaunchbarPlugin *) p->priv;
 
-static void on_add_btn( GtkButton* widget, Plugin* p )
-{
-    launchbar *lb = (launchbar *)p->priv;
-    GtkTreeView* view = (GtkTreeView*)g_object_get_data( (GObject *) lb->config_dlg, "view" );
-    GtkTreeSelection* tree_sel = gtk_tree_view_get_selection(view);
+    GtkTreeView * defined_view = GTK_TREE_VIEW(g_object_get_data(G_OBJECT(lb->config_dlg), "defined_view"));
+    GtkTreeModel * list;
     GtkTreeIter it;
-    GtkListStore* list;
-    GtkFileChooserDialog* dlg;
-    GtkFileFilter* filter;
-    int response;
-
-    /*
-    if( !gtk_tree_selection_get_selected( tree_sel, &list, &it ) )
-        return;
-    */
-    list = (GtkListStore*)gtk_tree_view_get_model( view );
-
-    /* FIXME: We should have a better interface for this in the fututure.
-              1. We can borrow the menu from menu plugin (PtkAppMenu).
-              2. We can borrow the app chooser from PCManFM.
-    */
-    dlg = (GtkFileChooserDialog *)gtk_file_chooser_dialog_new(_("Select Application"),
-                                       (GtkWindow *)lb->config_dlg,
-                                       GTK_FILE_CHOOSER_ACTION_OPEN,
-                                       GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
-                                       GTK_STOCK_ADD, GTK_RESPONSE_OK, NULL );
-    filter = gtk_file_filter_new();
-    gtk_file_filter_set_name( filter, "*.desktop" );
-    gtk_file_filter_add_pattern( filter, "*.desktop" );
-    gtk_file_chooser_add_filter( (GtkFileChooser *)dlg, filter );
-    g_object_unref( filter );
-    gtk_file_chooser_set_local_only( (GtkFileChooser *)dlg, TRUE );
-    gtk_file_chooser_set_current_folder( (GtkFileChooser *)dlg, "/usr/share/applications" );
-
-    gtk_widget_set_sensitive( lb->config_dlg, FALSE );
-    g_signal_connect( dlg, "response", G_CALLBACK(&on_add_btn_response),
-            &response );
-    gtk_window_present( (GtkWindow *)dlg );
-    gtk_main();
-    gtk_widget_set_sensitive( lb->config_dlg, TRUE );
-
-    if( response == GTK_RESPONSE_OK ) {
-        char* filename = gtk_file_chooser_get_filename( (GtkFileChooser *)dlg );
-        if( filename ) {
-            if( g_str_has_suffix( filename, ".desktop" ) ) {
-                char *config, *pconfig;
-                config = pconfig = g_strdup_printf( "id=%s\n}\n", filename );
-                /* Make a fake config entry, and let read_button() parst it. */
-                /* FIXME: This is a quick hack, which is dirty but easy and useful.
-                          Need to be re-written in the future.
-                */
-                if( read_button( p, &pconfig ) ) {
-                    GSList* l;
-                    btn_t* btn;
-                    l = g_slist_last( lb->btns );
-                    btn = (btn_t*)l->data;
-                    gtk_list_store_append( list, &it );
-                    gtk_list_store_set( list, &it,
-                                        COL_ICON, NULL, /* FIXME: need to be implemented */
-                                        COL_TITLE, (btn->tooltip ? btn->tooltip : btn->action),
-                                        COL_BTN, btn,
-                                        -1 );
-                }
-                g_free( config );
+    if (gtk_tree_selection_get_selected(gtk_tree_view_get_selection(defined_view), &list, &it))
+    {
+        LaunchButton *btn;
+        gtk_tree_model_get(GTK_TREE_MODEL(list), &it, COL_BTN, &btn, -1);
+        GtkTreePath * path = gtk_tree_model_get_path(GTK_TREE_MODEL(list), &it);
+        if ((gtk_tree_path_get_indices(path)[0] > 0)
+        && (gtk_tree_path_prev(path)))
+        {
+            GtkTreeIter it2;
+            if (gtk_tree_model_get_iter(list, &it2, path))
+            {
+                /* We have found a selected button that can be moved.
+                 * Reorder it in the icon grid, the data structure, and the view. */
+                int i = gtk_tree_path_get_indices(path)[0];
+                lb->buttons = g_slist_remove(lb->buttons, btn);
+                lb->buttons = g_slist_insert(lb->buttons, btn, i);
+                gtk_list_store_move_before(GTK_LIST_STORE(list), &it, &it2);
+                icon_grid_reorder_child(lb->icon_grid, btn->widget, i);
             }
-            g_free( filename );
         }
+        gtk_tree_path_free(path);
     }
-
-    gtk_widget_destroy( (GtkWidget *)dlg );
 }
 
-static void on_remove_btn( GtkButton* widget, Plugin* p )
+/* Handler for "clicked" action on launchbar configuration dialog "Move Down" button. */
+static void launchbar_configure_move_down_button(GtkButton * widget, Plugin * p)
 {
-    launchbar *lb = (launchbar *)p->priv;
-    GtkTreeView* view = (GtkTreeView*)g_object_get_data( 
-            (GObject*)lb->config_dlg, "view" );
-    GtkTreeSelection* tree_sel = gtk_tree_view_get_selection(view);
-    GtkTreeIter it;
-   // GtkListStore* list;
-    GtkTreeModel* list;
-    btn_t* btn;
-
-    if( !gtk_tree_selection_get_selected( tree_sel, &list, &it ) )
-        return;
-    gtk_tree_model_get( list, &it,COL_BTN, &btn, -1 );
-    gtk_list_store_remove( (GtkListStore *)list, &it );
-    if( btn ) {
-        lb->btns = g_slist_remove( lb->btns, btn );
-        gtk_widget_destroy( btn->widget );
-        btn_free( btn );
-    }
-}
+    LaunchbarPlugin * lb = (LaunchbarPlugin *) p->priv;
 
-static void on_up_btn( GtkButton* widget, Plugin* p )
-{
-    launchbar *lb = (launchbar *)p->priv;
-    btn_t *btn;
-    GtkTreeView* view = (GtkTreeView*)g_object_get_data(
-            (GObject*)lb->config_dlg, "view" );
-    GtkTreeSelection* tree_sel = gtk_tree_view_get_selection(view);
+    GtkTreeView * defined_view = GTK_TREE_VIEW(g_object_get_data(G_OBJECT(lb->config_dlg), "defined_view"));
+    GtkTreeModel * list;
     GtkTreeIter it;
-    GtkTreePath* path;
-    // GtkListStore* list;
-    GtkTreeModel* list;
-
-    if( !gtk_tree_selection_get_selected( tree_sel, &list, &it ) )
-        return;
-    gtk_tree_model_get( (GtkTreeModel*)list, &it, COL_BTN, &btn, -1 );
-    path = gtk_tree_model_get_path( (GtkTreeModel*)list, &it );
-    if( gtk_tree_path_get_indices(path)[0] > 0 ) {
-        if( gtk_tree_path_prev(path) ) {
+    if (gtk_tree_selection_get_selected(gtk_tree_view_get_selection(defined_view), &list, &it))
+    {
+        LaunchButton *btn;
+        gtk_tree_model_get(GTK_TREE_MODEL(list), &it, COL_BTN, &btn, -1);
+        GtkTreePath * path = gtk_tree_model_get_path(GTK_TREE_MODEL(list), &it);
+        int n = gtk_tree_model_iter_n_children(list, NULL);
+        if (gtk_tree_path_get_indices(path)[0] < (n - 1))
+        {
+            gtk_tree_path_next(path);
             GtkTreeIter it2;
-            if( gtk_tree_model_get_iter( list, &it2, path ) ) {
+            if (gtk_tree_model_get_iter( list, &it2, path))
+            {
+                /* We have found a selected button that can be moved.
+                 * Reorder it in the icon grid, the data structure, and the view. */
                 int i = gtk_tree_path_get_indices(path)[0];
-                lb->btns = g_slist_remove( lb->btns, btn );
-                lb->btns = g_slist_insert( lb->btns, btn, i );
-                gtk_list_store_move_before( (GtkListStore*)list, &it, &it2 );
-                gtk_box_reorder_child( lb->box, btn->widget, i );
+                lb->buttons = g_slist_remove(lb->buttons, btn);
+                lb->buttons = g_slist_insert(lb->buttons, btn, i + 1);
+                gtk_list_store_move_after(GTK_LIST_STORE(list), &it, &it2);
+                icon_grid_reorder_child( lb->icon_grid, btn->widget, i);
             }
         }
+        gtk_tree_path_free(path);
     }
-    gtk_tree_path_free( path );
 }
 
-static void on_down_btn( GtkButton* widget, Plugin* p )
+/* Handler for "response" signal from launchbar configuration dialog. */
+static void launchbar_configure_response(GtkDialog * dlg, int response, Plugin * p)
 {
-    launchbar *lb = (launchbar *)p->priv;
-    btn_t *btn;
-    GtkTreeView* view = g_object_get_data( G_OBJECT(lb->config_dlg), "view" );
-    GtkTreeSelection* tree_sel = gtk_tree_view_get_selection(view);
+    LaunchbarPlugin * lb = (LaunchbarPlugin *) p->priv;
+
+    /* Deallocate LaunchButtons that were loaded from the menu. */
+    GtkTreeView * menu_view = GTK_TREE_VIEW(g_object_get_data(G_OBJECT(lb->config_dlg), "menu_view"));
+    GtkTreeModel * model = gtk_tree_view_get_model(menu_view);
     GtkTreeIter it;
-    GtkTreePath* path;
-    // GtkListStore* list;
-    GtkTreeModel* list;
-    int n;
-
-    if( !gtk_tree_selection_get_selected( tree_sel, &list, &it ) )
-        return;
-    gtk_tree_model_get( list, &it, COL_BTN, &btn, -1 );
-    path = gtk_tree_model_get_path( list, &it );
-    n = gtk_tree_model_iter_n_children( list, NULL );
-    if( gtk_tree_path_get_indices(path)[0] < n - 1 ) {
-        GtkTreeIter it2;
-        gtk_tree_path_next(path);
-        if( gtk_tree_model_get_iter( list, &it2, path ) ) {
-            int i = gtk_tree_path_get_indices(path)[0];
-            lb->btns = g_slist_insert( lb->btns, btn, i + 1 );
-            lb->btns = g_slist_remove( lb->btns, btn );
-            gtk_list_store_move_after( (GtkListStore*)list, &it, &it2 );
-            gtk_box_reorder_child( lb->box, btn->widget, i );
+    if (gtk_tree_model_get_iter_first(model, &it))
+    {
+        do
+        {
+            LaunchButton * btn;
+            gtk_tree_model_get(model, &it, COL_BTN, &btn, -1);
+            launchbutton_free(btn);           
         }
+        while (gtk_tree_model_iter_next(model, &it));
     }
-    gtk_tree_path_free( path );
+
+    /* Deallocate the configuration dialog. */
+    lb->config_dlg = NULL;
+    gtk_widget_destroy(GTK_WIDGET(dlg));
 }
 
-static void init_btn_list( Plugin* p, GtkTreeView* view )
+static void launchbar_configure_add_menu_recursive(GtkListStore * list, MenuCacheDir * menu_dir)
 {
-    launchbar *lb = (launchbar *)p->priv;
-    GtkListStore *list;
-    GSList* l;
-    GtkTreeViewColumn* col;
-    GtkCellRenderer* render;
-    GtkTreeSelection* tree_sel = gtk_tree_view_get_selection(view);
-
-    gtk_tree_selection_set_mode(tree_sel, GTK_SELECTION_BROWSE);
-
-    list = gtk_list_store_new( N_COLS,
-                               GDK_TYPE_PIXBUF,
-                               G_TYPE_STRING,
-                               G_TYPE_POINTER,
-                               G_TYPE_POINTER );
-    col = gtk_tree_view_column_new();
-    gtk_tree_view_column_set_title( col, _("Buttons") );
-
-    render = gtk_cell_renderer_pixbuf_new();
-    gtk_tree_view_column_pack_start( col, render, FALSE );
-    gtk_tree_view_column_set_attributes( col, render, "pixbuf", COL_ICON, NULL );
-
-    render = gtk_cell_renderer_text_new();
-    gtk_tree_view_column_pack_start( col, render, TRUE );
-    gtk_tree_view_column_add_attribute( col, render, "text", COL_TITLE );
-
-    gtk_tree_view_append_column( view, col );
+    /* Iterate over all menu items in this directory. */
+    GSList * l;
+    for (l = menu_cache_dir_get_children(menu_dir); l != NULL; l = l->next)
+    {
+        /* Get the next menu item. */
+        MenuCacheItem * item = MENU_CACHE_ITEM(l->data);
+        switch (menu_cache_item_get_type(item))
+        {
+            case MENU_CACHE_TYPE_APP:
+                {
+                /* If an application, build a LaunchButton data structure so we can identify
+                 * the button in the handler.  In this application, the desktop_id is the
+                 * fully qualified desktop file path.  The image and tooltip are what is displayed in the view. */
+                LaunchButton * btn = g_new0(LaunchButton, 1);
+                btn->desktop_id = g_strdup(menu_cache_item_get_file_path(item));
+                btn->image = g_strdup(menu_cache_item_get_icon(item));
+                btn->tooltip = g_strdup(menu_cache_item_get_name(item));
+
+                /* Add the row to the view. */
+                GtkTreeIter it;
+                gtk_list_store_append(list, &it);
+                gtk_list_store_set(list, &it,
+                    COL_ICON, lxpanel_load_icon(btn->image, PANEL_ICON_SIZE, PANEL_ICON_SIZE, TRUE),
+                    COL_TITLE, ((btn->tooltip != NULL) ? btn->tooltip : btn->desktop_id),
+                    COL_BTN, btn,
+                    -1);
+                }
+                break;
 
-    for( l = lb->btns; l; l = l->next ) {
-        GtkTreeIter it;
-        GdkPixbuf* pix;
-        char* fname;
-
-        btn_t* btn = (btn_t*)l->data;
-#if 0
-        fname = expand_tilda( btn->image );
-        if( fname ) {
-            if( fname[0] == '/' ) /* file */
-                pix = gdk_pixbuf_new_from_file( fname, NULL );
-            else {
-                //pix =
-            }
+            case MENU_CACHE_TYPE_DIR:
+                /* If a directory, recursively add its menu items. */
+                launchbar_configure_add_menu_recursive(list, MENU_CACHE_DIR(item));
+                break;
         }
-        else
-            pix = NULL;
-        g_free( fname );
-#endif
-        gtk_list_store_append( list, &it );
-        gtk_list_store_set( list, &it,
-                            COL_ICON, NULL,
-                            COL_TITLE, (btn->tooltip ? btn->tooltip : btn->action),
-                            COL_BTN, btn, -1 );
-    }
-
-    gtk_tree_view_set_model( view, (GtkTreeModel*)list );
-    g_object_unref( list );
 
-    g_object_set_data( G_OBJECT(lb->config_dlg), "view", view );
+    }
 }
 
-static void launchbar_config( Plugin *p, GtkWindow* parent )
+/* Initialize the list of existing launchbar buttons when the configuration dialog is shown. */
+static void launchbar_configure_initialize_list(Plugin * p, GtkWidget * dlg, GtkTreeView * view, gboolean from_menu)
 {
-    GtkWidget *dlg, *hbox, *vbox, *scroll, *btn, *img;
-    GtkTreeView *view;
-    launchbar *lb = (launchbar *)p->priv;
-
-    if( !lb->config_dlg )
-    {
-        dlg = gtk_dialog_new_with_buttons( _(p->class->name),
-                                        parent, 0,
-                                        GTK_STOCK_CLOSE,
-                                        GTK_RESPONSE_CLOSE,
-                                        NULL );
-        lb->config_dlg = dlg;
-        panel_apply_icon(GTK_WINDOW(dlg));
-
-        hbox = gtk_hbox_new( FALSE, 4 );
-        gtk_box_pack_start( (GtkBox*)GTK_DIALOG(dlg)->vbox, hbox, TRUE, TRUE, 2 );
+    LaunchbarPlugin * lb = (LaunchbarPlugin *) p->priv;
 
-        scroll = gtk_scrolled_window_new( NULL, NULL );
-        gtk_scrolled_window_set_policy( (GtkScrolledWindow*)scroll,
-                                         GTK_POLICY_AUTOMATIC,
-                                         GTK_POLICY_AUTOMATIC );
-        gtk_scrolled_window_set_shadow_type( (GtkScrolledWindow*)scroll,
-                                             GTK_SHADOW_IN );
-        gtk_box_pack_start( GTK_BOX(hbox), scroll, TRUE, TRUE, 2 );
+    /* Set the selection mode. */
+    gtk_tree_selection_set_mode(gtk_tree_view_get_selection(view), GTK_SELECTION_BROWSE);
 
-        view = (GtkTreeView *)gtk_tree_view_new();
-        gtk_container_add( (GtkContainer*)scroll, GTK_WIDGET(view) );
+    /* Establish the column data types. */
+    GtkListStore * list = gtk_list_store_new(N_COLS,
+        GDK_TYPE_PIXBUF,
+        G_TYPE_STRING,
+        G_TYPE_POINTER);
 
-        vbox = gtk_vbox_new( FALSE, 2 );
-        gtk_box_pack_start( (GtkBox*)hbox, vbox, FALSE, FALSE, 2 );
+    /* Define a column. */
+    GtkTreeViewColumn * col = gtk_tree_view_column_new();
+    gtk_tree_view_column_set_title(col, ((from_menu) ? _("Available Applications") : _("Applications")));
 
-        btn = gtk_button_new_from_stock( GTK_STOCK_ADD );
-        g_signal_connect( btn, "clicked", G_CALLBACK( on_add_btn ), p );
-        gtk_box_pack_start( (GtkBox*)vbox, btn, FALSE, FALSE, 2 );
+    /* Establish the pixbuf column cell renderer. */
+    GtkCellRenderer * render = gtk_cell_renderer_pixbuf_new();
+    gtk_tree_view_column_pack_start(col, render, FALSE);
+    gtk_tree_view_column_set_attributes(col, render, "pixbuf", COL_ICON, NULL);
 
-        btn = gtk_button_new_from_stock( GTK_STOCK_REMOVE );
-        g_signal_connect( btn, "clicked", G_CALLBACK( on_remove_btn ), p );
-        gtk_box_pack_start( (GtkBox*)vbox, btn, FALSE, FALSE, 2 );
+    /* Establish the text column cell renderer. */
+    render = gtk_cell_renderer_text_new();
+    gtk_tree_view_column_pack_start(col, render, TRUE);
+    gtk_tree_view_column_add_attribute(col, render, "text", COL_TITLE);
 
-        btn = gtk_button_new();
-        gtk_container_add( GTK_CONTAINER(btn),
-                gtk_image_new_from_stock(GTK_STOCK_GO_UP,
-                                        GTK_ICON_SIZE_BUTTON) );
-        g_signal_connect( btn, "clicked", G_CALLBACK( on_up_btn ), p );
-        gtk_box_pack_start( (GtkBox*)vbox, btn, FALSE, FALSE, 2 );
+    /* Append the column to the view. */
+    gtk_tree_view_append_column(view, col);
 
-        btn = gtk_button_new();
-        gtk_container_add( GTK_CONTAINER(btn),
-                gtk_image_new_from_stock(GTK_STOCK_GO_DOWN,
-                                        GTK_ICON_SIZE_BUTTON) );
-        g_signal_connect( btn, "clicked", G_CALLBACK( on_down_btn ), p );
-        gtk_box_pack_start( (GtkBox*)vbox, btn, FALSE, FALSE, 2 );
+    if (from_menu)
+    {
+        /* Initialize from all menu items. */
+        MenuCache * menu_cache = panel_menu_cache_new();
+        if (menu_cache != NULL)
+        {
+            MenuCacheDir * dir = menu_cache_get_root_dir(menu_cache);
+            launchbar_configure_add_menu_recursive(list, dir);
+            menu_cache_unref(menu_cache);
+        }
+        g_object_set_data(G_OBJECT(dlg), "menu_view", view);
+    }
+    else
+    {
+        /* Initialize from defined launchbar buttons. */
+        GSList * l;
+        for (l = lb->buttons; l != NULL; l = l->next)
+        {
+            LaunchButton * btn = (LaunchButton *) l->data;
+            GtkTreeIter it;
+            gtk_list_store_append(list, &it);
+            gtk_list_store_set(list, &it,
+                COL_ICON, lxpanel_load_icon(btn->image, PANEL_ICON_SIZE, PANEL_ICON_SIZE, TRUE),
+                COL_TITLE, ((btn->tooltip != NULL) ? btn->tooltip : btn->action),
+                COL_BTN, btn,
+                -1);
+        }
+        g_object_set_data(G_OBJECT(dlg), "defined_view", view);
+    }
 
-        g_signal_connect( dlg, "response", G_CALLBACK(on_response), p );
+    /* Finish the setup and return. */
+    gtk_tree_view_set_model(view, GTK_TREE_MODEL(list));
+}
 
-        gtk_window_set_default_size( (GtkWindow*)dlg, 320, 400 );
+/* Callback when the configuration dialog is to be shown. */
+static void launchbar_configure(Plugin * p, GtkWindow * parent)
+{
+    LaunchbarPlugin * lb = (LaunchbarPlugin *) p->priv;
 
-        init_btn_list( p, view );
+    if (lb->config_dlg == NULL)
+    {
+        /* Create the configuration dialog. */
+        GtkWidget * dlg = gtk_dialog_new_with_buttons(
+            p->class->name,
+            parent,
+            0,
+            GTK_STOCK_CLOSE,
+            GTK_RESPONSE_CLOSE,
+            NULL);
+        gtk_window_set_default_size(GTK_WINDOW(dlg), 640, 400);
+        panel_apply_icon(GTK_WINDOW(dlg));
 
-        gtk_widget_show_all( dlg );
+        /* Create a horizontal box. */
+        GtkWidget * hbox = gtk_hbox_new(FALSE, 4);
+        gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dlg)->vbox), hbox, TRUE, TRUE, 2);
+
+        /* Create a scrollbar as the child of the horizontal box. */
+        GtkWidget * defined_scroll = gtk_scrolled_window_new(NULL, NULL);
+        gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(defined_scroll), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
+        gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(defined_scroll), GTK_SHADOW_IN);
+        gtk_box_pack_start(GTK_BOX(hbox), defined_scroll, TRUE, TRUE, 2);
+
+        /* Create a tree view as the child of the scrollbar. */
+        GtkWidget * defined_view = gtk_tree_view_new();
+        gtk_container_add(GTK_CONTAINER(defined_scroll), defined_view);
+
+        /* Create a vertical box as the child of the horizontal box. */
+        GtkWidget * vbox = gtk_vbox_new(FALSE, 2);
+        gtk_box_pack_start(GTK_BOX(hbox), vbox, FALSE, FALSE, 2);
+
+        /* Create a scrollbar as the child of the horizontal box. */
+        GtkWidget * menu_scroll = gtk_scrolled_window_new(NULL, NULL);
+        gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(menu_scroll), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
+        gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(menu_scroll), GTK_SHADOW_IN);
+        gtk_box_pack_start(GTK_BOX(hbox), menu_scroll, TRUE, TRUE, 2);
+
+        /* Create a tree view as the child of the scrollbar. */
+        GtkWidget * menu_view = gtk_tree_view_new();
+        gtk_container_add(GTK_CONTAINER(menu_scroll), menu_view);
+
+        /* Create an "Add" button as the child of the vertical box. */
+        GtkWidget * btn = gtk_button_new_from_stock(GTK_STOCK_ADD);
+        g_signal_connect(btn, "clicked", G_CALLBACK(launchbar_configure_add_button), p);
+        gtk_box_pack_start(GTK_BOX(vbox), btn, FALSE, FALSE, 2);
+
+        /* Create a "Remove" button as the child of the vertical box. */
+        btn = gtk_button_new_from_stock(GTK_STOCK_REMOVE);
+        g_signal_connect(btn, "clicked", G_CALLBACK(launchbar_configure_remove_button), p);
+        gtk_box_pack_start(GTK_BOX(vbox), btn, FALSE, FALSE, 2);
+
+        /* Create a "Move Up" button as the child of the vertical box. */
+        btn = gtk_button_new_from_stock(GTK_STOCK_GO_UP);
+        g_signal_connect(btn, "clicked", G_CALLBACK(launchbar_configure_move_up_button), p);
+        gtk_box_pack_start(GTK_BOX(vbox), btn, FALSE, FALSE, 2);
+
+        /* Create a "Move Down" button as the child of the vertical box. */
+        btn = gtk_button_new_from_stock(GTK_STOCK_GO_DOWN);
+        g_signal_connect(btn, "clicked", G_CALLBACK(launchbar_configure_move_down_button), p);
+        gtk_box_pack_start(GTK_BOX(vbox), btn, FALSE, FALSE, 2);
+
+        /* Connect signals. */
+        g_signal_connect(dlg, "response", G_CALLBACK(launchbar_configure_response), p);
+
+        /* Initialize the tree view contents. */
+        launchbar_configure_initialize_list(p, dlg, GTK_TREE_VIEW(defined_view), FALSE);
+        launchbar_configure_initialize_list(p, dlg, GTK_TREE_VIEW(menu_view), TRUE);
+
+        /* Show the dialog. */
+        gtk_widget_show_all(dlg);
+
+        /* Establish a callback when the dialog completes. */
+        g_object_weak_ref(G_OBJECT(dlg), (GWeakNotify) panel_config_save, p->panel);
+        gtk_window_present(GTK_WINDOW(dlg));
+        lb->config_dlg = dlg;
+    }
+}
 
-        g_object_weak_ref(G_OBJECT(dlg), 
-                (GWeakNotify)panel_config_save, p->panel );
+/* Callback when the configuration is to be saved. */
+static void launchbar_save_configuration(Plugin * p, FILE * fp)
+{
+    LaunchbarPlugin * lb = (LaunchbarPlugin *) p->priv;
+    GSList * l;
+    for (l = lb->buttons; l != NULL; l = l->next)
+    {
+        LaunchButton * btn = (LaunchButton *) l->data;
+        lxpanel_put_line(fp, "Button {");
+        if (btn->desktop_id != NULL)
+            lxpanel_put_str(fp, "id", btn->desktop_id);
+        if (btn->customize_image)
+            lxpanel_put_str(fp, "image", btn->image);
+        if(btn->customize_tooltip)
+            lxpanel_put_str(fp, "tooltip", btn->tooltip);
+        if (btn->customize_action)
+            lxpanel_put_str(fp, "action", btn->action);
+        lxpanel_put_line(fp, "}");
     }
-    gtk_window_present( GTK_WINDOW(lb->config_dlg) );
 }
 
+/* Callback when panel configuration changes. */
+static void launchbar_panel_configuration_changed(Plugin * p)
+{
+    /* Set orientation into the icon grid. */
+    LaunchbarPlugin * lb = (LaunchbarPlugin *) p->priv;
+    GtkOrientation bo = (p->panel->orientation == ORIENT_HORIZ) ? GTK_ORIENTATION_HORIZONTAL : GTK_ORIENTATION_VERTICAL;
+    icon_grid_set_orientation(lb->icon_grid, bo, p->panel->height);
+}
+
+/* Plugin descriptor. */
 PluginClass launchbar_plugin_class = {
-    fname: NULL,
-    count: 0,
+
+    PLUGINCLASS_VERSIONING,
 
     type : "launchbar",
     name : N_("Application Launch Bar"),
@@ -803,7 +819,7 @@ PluginClass launchbar_plugin_class = {
 
     constructor : launchbar_constructor,
     destructor  : launchbar_destructor,
-    config : launchbar_config,
-    save : save_config,
-    orientation : orientation_changed
+    config : launchbar_configure,
+    save : launchbar_save_configuration,
+    panel_configuration_changed : launchbar_panel_configuration_changed
 };
index 9acd7d9..146ce21 100644 (file)
 #include "misc.h"
 #include "plugin.h"
 #include "bg.h"
+#include "menu-policy.h"
 
 #include "dbg.h"
 
+#define DEFAULT_MENU_ICON PACKAGE_DATA_DIR "/lxpanel/images/my-computer.png"
 /*
  * SuxPanel version 0.1
  * Copyright (c) 2003 Leandro Pereira <leandro@linuxmag.com.br>
@@ -81,11 +83,9 @@ menu_destructor(Plugin *p)
 
     if( m->has_system_menu )
         p->panel->system_menus = g_slist_remove( p->panel->system_menus, p );
+
     g_signal_handler_disconnect(G_OBJECT(m->img), m->handler_id);
     gtk_widget_destroy(m->menu);
-    /* The widget is destroyed in plugin_stop().
-    gtk_widget_destroy(m->box);
-    */
 
     if( m->menu_cache )
     {
@@ -175,7 +175,7 @@ static void on_menu_item_map(GtkWidget* mi, MenuCacheItem* item)
             /* FIXME: this is inefficient */
             gtk_icon_size_lookup(GTK_ICON_SIZE_MENU, &w, &h);
             item = g_object_get_qdata(G_OBJECT(mi), SYS_MENU_ITEM_ID);
-            icon = lxpanel_load_icon(menu_cache_item_get_icon(item), MAX(w,h), TRUE);
+            icon = lxpanel_load_icon(menu_cache_item_get_icon(item), w, h, TRUE);
             if (icon)
             {
                 gtk_image_set_from_pixbuf(img, icon);
@@ -412,44 +412,33 @@ static GtkWidget* create_item( MenuCacheItem* item )
         g_signal_connect(mi, "button-press-event", G_CALLBACK(on_menu_button_press), item);
     }
     gtk_widget_show( mi );
-    /* g_debug("set_item_data"); */
     g_object_set_qdata_full( G_OBJECT(mi), SYS_MENU_ITEM_ID, menu_cache_item_ref(item), (GDestroyNotify) menu_cache_item_unref );
     return mi;
 }
 
 static void load_menu(MenuCacheDir* dir, GtkWidget* menu, int pos )
 {
-    GSList* l;
-    GtkWidget* mi;
+    GSList * l;
     for( l = menu_cache_dir_get_children(dir); l; l = l->next )
     {
         MenuCacheItem* item = MENU_CACHE_ITEM(l->data);
-        if( menu_cache_item_get_type(item) == MENU_CACHE_TYPE_APP )
+        if ((menu_cache_item_get_type(item) != MENU_CACHE_TYPE_APP)
+        || (panel_menu_item_evaluate_visibility(item)))
         {
-            if( is_in_lxde )
+            GtkWidget * mi = create_item(item);
+            if (mi != NULL)
             {
-               if( !menu_cache_app_get_is_visible(MENU_CACHE_APP(item), SHOW_IN_LXDE) )
-                    continue;
-            }
-            else
-            {
-                /* FIXME: showing apps from all desktops is not very pleasant. */
-               if( !menu_cache_app_get_is_visible(MENU_CACHE_APP(item), SHOW_IN_LXDE|SHOW_IN_GNOME|SHOW_IN_XFCE) )
-                    continue;
+                gtk_menu_shell_insert( (GtkMenuShell*)menu, mi, pos );
+                if( pos >= 0 )
+                    ++pos;
+                if (menu_cache_item_get_type(item) == MENU_CACHE_TYPE_DIR)
+                {
+                    GtkWidget* sub = gtk_menu_new();
+                    load_menu( MENU_CACHE_DIR(item), sub, -1 );    /* always pass -1 for position */
+                    gtk_menu_item_set_submenu( GTK_MENU_ITEM(mi), sub );
+                }
             }
         }
-        mi = create_item(item);
-        if( ! mi )
-            continue;
-        gtk_menu_shell_insert( (GtkMenuShell*)menu, mi, pos );
-        if( pos >= 0 )
-            ++pos;
-        if( menu_cache_item_get_type(item) == MENU_CACHE_TYPE_DIR )
-        {
-            GtkWidget* sub = gtk_menu_new();
-            load_menu( MENU_CACHE_DIR(item), sub, -1 );    /* always pass -1 for position */
-            gtk_menu_item_set_submenu( GTK_MENU_ITEM(mi), sub );
-        }
     }
 }
 
@@ -564,12 +553,9 @@ my_button_pressed(GtkWidget *widget, GdkEventButton *event, Plugin* plugin)
 {
     ENTER;
 
-    if( event->button == 3 )  /* right button */
-    {
-        GtkMenu* popup = lxpanel_get_panel_menu( plugin->panel, plugin, FALSE );
-        gtk_menu_popup( popup, NULL, NULL, NULL, NULL, event->button, event->time );
+    /* Standard right-click handling. */
+    if (plugin_button_press_event(widget, event, plugin))
         return TRUE;
-    }
 
     if ((event->type == GDK_BUTTON_PRESS)
           && (event->x >=0 && event->x < widget->allocation.width)
@@ -590,20 +576,13 @@ gboolean show_system_menu( gpointer system_menu )
 static GtkWidget *
 make_button(Plugin *p, gchar *fname, gchar *name, GdkColor* tint, GtkWidget *menu)
 {
-    int w, h;
+    int w = -1, h = PANEL_ICON_SIZE;
     char* title = NULL;
     menup *m;
 
     ENTER;
     m = (menup *)p->priv;
     m->menu = menu;
-    if (p->panel->orientation == ORIENT_HORIZ) {
-        h = p->panel->ah;
-        w = h * p->panel->aw / p->panel->ah;
-    } else {
-        w = p->panel->aw;
-        h = w * p->panel->ah / p->panel->aw;
-    }
 
     if( name )
     {
@@ -622,19 +601,14 @@ make_button(Plugin *p, gchar *fname, gchar *name, GdkColor* tint, GtkWidget *men
         else
             title = name;
 
-        /* FIXME: handle orientation problems */
-        if (p->panel->usefontcolor)
-            m->img = fb_button_new_from_file_with_colorlabel(fname, w, h, gcolor2rgb24(tint),
-                p->panel->fontcolor, TRUE, title);
-        else
-            m->img = fb_button_new_from_file_with_label(fname, w, h, gcolor2rgb24(tint), TRUE, title);
+        m->img = fb_button_new_from_file_with_label(fname, w, h, gcolor2rgb24(tint), TRUE, p->panel, title);
 
         if( title != name )
             g_free( title );
     }
     else
     {
-        m->img = fb_button_new_from_file(fname, w, h, gcolor2rgb24(tint), TRUE );
+        m->img = fb_button_new_from_file(fname, w, h, gcolor2rgb24(tint), TRUE);
     }
 
     gtk_widget_show(m->img);
@@ -749,12 +723,10 @@ read_system_menu(GtkMenu* menu, Plugin *p, char** fp)
     menup *m = (menup *)p->priv;
     GtkWidget* fake;
 
-    if(! m->menu_cache)
+    if (m->menu_cache == NULL)
     {
-        if( !g_getenv("XDG_MENU_PREFIX") )
-            g_setenv("XDG_MENU_PREFIX", "lxde-", TRUE);
-        m->menu_cache = menu_cache_lookup( "applications.menu" );
-        if( G_UNLIKELY(!m->menu_cache) )
+        m->menu_cache = panel_menu_cache_new();
+        if (m->menu_cache == NULL)
         {
             ERR("error loading applications menu");
             return;
@@ -894,7 +866,7 @@ read_submenu(Plugin *p, char** fp, gboolean as_item)
         gtk_menu_item_set_submenu (GTK_MENU_ITEM (mi), menu);
         RET(mi);
     } else {
-        m->fname = fname ? g_strdup(fname) : g_strdup( PACKAGE_DATA_DIR "/lxpanel/images/my-computer.png" );
+        m->fname = fname ? g_strdup(fname) : g_strdup( DEFAULT_MENU_ICON );
         m->caption = g_strdup(name);
         mi = make_button(p, fname, name, &color, menu);
         if (fname)
@@ -929,6 +901,7 @@ menu_constructor(Plugin *p, char **fp)
             "image=gnome-logout\n"
             "command=logout\n"
         "}\n"
+        "image=" DEFAULT_MENU_ICON "\n"
         "}\n";
     char *config_start, *config_end, *config_default = default_config;
     int iw, ih;
@@ -940,15 +913,6 @@ menu_constructor(Plugin *p, char **fp)
 
     p->priv = m;
 
-/*
-    if  (p->panel->orientation == ORIENT_HORIZ)
-        m->paneliconsize = p->panel->ah
-            - 2* GTK_WIDGET(p->panel->box)->style->ythickness;
-    else
-        m->paneliconsize = p->panel->aw
-            - 2* GTK_WIDGET(p->panel->box)->style->xthickness;
-    m->iconsize = 22;
-*/
     gtk_icon_size_lookup( GTK_ICON_SIZE_MENU, &iw, &ih );
     m->iconsize = MAX(iw, ih);
 
@@ -961,7 +925,7 @@ menu_constructor(Plugin *p, char **fp)
     m->config_start = start = *fp;
     if (!read_submenu(p, fp, FALSE)) {
         ERR("menu: plugin init failed\n");
-        goto error;
+        return 0;
     }
     m->config_end = *fp - 1;
     while( *m->config_end != '}' && m->config_end > m->config_start ) {
@@ -976,9 +940,6 @@ menu_constructor(Plugin *p, char **fp)
 
     RET(1);
 
- error:
-    menu_destructor(p);
-    RET(0);
 }
 
 static void save_config( Plugin* p, FILE* fp )
@@ -1033,13 +994,13 @@ static void menu_config( Plugin *p, GtkWindow* parent )
 }
 
 PluginClass menu_plugin_class = {
-    fname: NULL,
-    count: 0,
+
+    PLUGINCLASS_VERSIONING,
 
     type : "menu",
     name : N_("Menu"),
     version: "2.0",
-    description : N_("Provide Menu"),
+    description : N_("Application Menu"),
 
     constructor : menu_constructor,
     destructor  : menu_destructor,
index e1c5e33..da9ff43 100644 (file)
@@ -409,7 +409,6 @@ static int netstat_constructor(Plugin *p, char **fp)
                     ns->fixcmd = g_strdup(s.t[1]);
                 else {
                     ERR( "netstat: unknown var %s\n", s.t[0]);
-                    goto error;
                 }
             } else {
                 ERR( "netstat: illegal in this context %s\n", s.str);
@@ -464,8 +463,8 @@ static void orientation_changed(Plugin* p)
 }
 
 PluginClass netstat_plugin_class = {
-    fname: NULL,
-    count: 0,
+    
+    PLUGINCLASS_VERSIONING,
 
     type : "netstat",
     name : N_("Manage Networks"),
index b35be38..170fd7b 100644 (file)
@@ -73,11 +73,15 @@ static void on_response( GtkDialog* dlg, gint response, netstatus *ns )
     }
 }
 
-static void on_button_press( GtkWidget* widget, GdkEventButton* evt, Plugin* p )
+static gboolean on_button_press( GtkWidget* widget, GdkEventButton* evt, Plugin* p )
 {
     NetstatusIface* iface;
     netstatus *ns = (netstatus*)p->priv;
 
+    /* Standard right-click handling. */
+    if (plugin_button_press_event(widget, evt, p))
+        return TRUE;
+
     if( evt->button == 1 ) /*  Left click*/
     {
         if( ! ns->dlg )
@@ -93,6 +97,7 @@ static void on_button_press( GtkWidget* widget, GdkEventButton* evt, Plugin* p )
         }
         gtk_window_present( GTK_WINDOW(ns->dlg) );
     }
+    return TRUE;
 }
 
 static int
@@ -121,7 +126,6 @@ netstatus_constructor(Plugin *p, char** fp)
                     ns->config_tool = g_strdup(s.t[1]);
                 else {
                     ERR( "netstatus: unknown var %s\n", s.t[0]);
-                    goto error;
                 }
             } else {
                 ERR( "netstatus: illegal in this context %s\n", s.str);
@@ -151,7 +155,6 @@ netstatus_constructor(Plugin *p, char** fp)
     RET(1);
 
  error:
-    netstatus_destructor(p);
     RET(0);
 }
 
@@ -186,11 +189,11 @@ static void save_config( Plugin* p, FILE* fp )
 }
 
 PluginClass netstatus_plugin_class = {
-    fname: NULL,
-    count: 0,
+
+    PLUGINCLASS_VERSIONING,
 
     type : "netstatus",
-    name : N_("Net Status Monitor"),
+    name : N_("Network Status Monitor"),
     version: "1.0",
     description : N_("Monitor network status"),
 
index 54f399c..6be88df 100644 (file)
@@ -1,4 +1,4 @@
-/* pager.c -- pager module of lxpanel project
+/** pager.c -- pager module of lxpanel project
  *
  * Copyright (C) 2002-2003 Anatoly Asviyan <aanatoly@users.sf.net>
  *                         Joe MacDonald   <joe@deserted.net>
@@ -20,6 +20,7 @@
  * the Free Software Foundation, 51 Franklin Street, Fifth Floor,
  * Boston, MA 02110-1301 USA.
  */
+
 #include <stdlib.h>
 #include <stdio.h>
 #include <string.h>
 #include "panel.h"
 #include "misc.h"
 #include "plugin.h"
+#include "icon-grid.h"
 
 #include "dbg.h"
 
-/* managed window: all related info that wm holds about its managed windows */
-typedef struct task {
-    Window win;
-    int x, y;
-    guint w, h;
-    gint refcount;
-    guint stacking;
-    guint desktop;
-    char *name, *iname;
-    int ws;
-    NetWMState nws;
-    NetWMWindowType nwwt;
-    guint focused:1;
-} task;
-
-typedef struct _desk   desk;
-typedef struct _pager  pager;
-
-#define MAX_DESK_NUM   20
-/* map of a desktop */
-struct _desk {
-    GtkWidget *da;
-    GdkPixmap *pix;
-    int no, dirty, first;
-    gfloat scalew, scaleh;
-    pager *pg;
-};
-
-struct _pager {
-    Plugin* plugin;
-    GtkWidget *box, *eb;
-    desk *desks[MAX_DESK_NUM];
-    guint desknum;
-    guint curdesk;
-    int dw, dh;
-    gfloat scalex, scaley, ratio;
-    Window *wins;
-    int winnum, dirty;
-    GHashTable* htable;
-    task *focusedtask;
-};
-
-
-#define TASK_VISIBLE(tk)                            \
- (!( (tk)->nws.hidden || (tk)->nws.skip_pager ))
-//if (t->nws.skip_pager || t->nwwt.desktop /*|| t->nwwt.dock || t->nwwt.splash*/ )
-
-static void pager_rebuild_all(FbEv *ev, pager *pg);
-
-static inline void desk_set_dirty_by_win(pager *p, task *t);
-static inline void desk_set_dirty(desk *d);
-static inline void desk_set_dirty_all(pager *pg);
-/*
-static void desk_clear_pixmap(desk *d);
-static gboolean task_remove_stale(Window *win, task *t, pager *p);
-static gboolean task_remove_all(Window *win, task *t, pager *p);
-*/
-
-
-
+struct _task;
+struct _desk;
+struct _pager;
+
+#define ALL_DESKTOPS   (-1)
+#define BORDER_WIDTH   2
+
+/* Structure representing a "task", an open window. */
+typedef struct _task {
+    struct _task * task_flink;                 /* Forward link of task list */
+    Window win;                                        /* X window ID */
+    int x;                                     /* Geometry as reported by X server */
+    int y;
+    guint w;
+    guint h;
+    int stacking;                              /* Stacking order as reported by NET_WM_CLIENT_STACKING */
+    int desktop;                               /* Desktop that contains task */
+    int ws;                                    /* WM_STATE value */
+    NetWMState nws;                            /* NET_WM_STATE value */
+    NetWMWindowType nwwt;                      /* NET_WM_WINDOW_TYPE value */
+    guint focused : 1;                         /* True if window has focus */
+    guint present_in_client_list : 1;          /* State during WM_CLIENT_LIST processing to detect deletions */
+} PagerTask;
+
+/* Structure representing a desktop. */
+typedef struct _desk {
+    struct _pager * pg;                                /* Back pointer to plugin context */
+    GtkWidget * da;                            /* Drawing area */
+    GdkPixmap * pixmap;                                /* Pixmap to be drawn on drawing area */
+    int desktop_number;                                /* Desktop number */
+    gboolean dirty;                            /* True if needs to be recomputed */
+    gfloat scale_x;                            /* Horizontal scale factor */
+    gfloat scale_y;                            /* Vertical scale factor */
+} PagerDesk;
+
+/* Private context for pager plugin. */
+typedef struct _pager {
+    Plugin * plugin;                           /* Back pointer to plugin */
+    IconGrid * icon_grid;                      /* Container widget */
+    int desk_extent;                           /* Extent of desks vector */
+    PagerDesk * * desks;                       /* Vector of desktop structures */
+    guint number_of_desktops;                  /* Number of desktops, from NET_WM_NUMBER_OF_DESKTOPS */
+    guint current_desktop;                     /* Current desktop, from NET_WM_CURRENT_DESKTOP */
+    gfloat aspect_ratio;                       /* Aspect ratio of screen image */
+    int client_count;                          /* Count of tasks in stacking order */
+    PagerTask * * tasks_in_stacking_order;     /* Vector of tasks in stacking order */
+    PagerTask * task_list;                     /* Tasks in window ID order */
+    PagerTask * focused_task;                  /* Task that has focus */
+} PagerPlugin;
+
+static gboolean task_is_visible(PagerTask * tk);
+static PagerTask * task_lookup(PagerPlugin * pg, Window win);
+static void task_delete(PagerPlugin * pg, PagerTask * tk, gboolean unlink);
+static void task_get_geometry(PagerTask * tk);
+static void task_update_pixmap(PagerTask * tk, PagerDesk * d);
+static void desk_set_dirty(PagerDesk * d);
+static void desk_set_dirty_all(PagerPlugin * pg);
+static void desk_set_dirty_by_win(PagerPlugin * pg, PagerTask * tk);
+static gboolean desk_configure_event(GtkWidget * widget, GdkEventConfigure * event, PagerDesk * d);
+static gboolean desk_expose_event(GtkWidget * widget, GdkEventExpose * event, PagerDesk * d);
+static gboolean desk_scroll_event(GtkWidget * widget, GdkEventScroll * event, PagerDesk * d);
+static gboolean desk_button_press_event(GtkWidget * widget, GdkEventButton * event, PagerDesk * d);
+static void desk_new(PagerPlugin * pg, int desktop_number);
+static void desk_free(PagerPlugin * pg, int desktop_number);
+static void pager_property_notify_event(PagerPlugin * p, XEvent * ev);
+static void pager_configure_notify_event(PagerPlugin * pg, XEvent * ev);
+static GdkFilterReturn pager_event_filter(XEvent * xev, GdkEvent * event, PagerPlugin * pg);
+static void pager_net_active_window(FbEv * ev, PagerPlugin * pg);
+static void pager_net_desktop_names(FbEv * ev, PagerPlugin * pg);
+static void pager_net_number_of_desktops(FbEv * ev, PagerPlugin * pg);
+static void pager_net_client_list_stacking(FbEv * ev, PagerPlugin * pg);
+static int pager_constructor(Plugin * plug, char ** fp);
+static void pager_destructor(Plugin * p);
+static void pager_panel_configuration_changed(Plugin * p);
 
 /*****************************************************************
  * Task Management Routines                                      *
  *****************************************************************/
 
-
-/* tell to remove element with zero refcount */
-static gboolean
-task_remove_stale(Window *win, task *t, pager *p)
+/* Determine if a task is visible. */
+static gboolean task_is_visible(PagerTask * tk)
 {
-    if (t->refcount-- == 0) {
-        desk_set_dirty_by_win(p, t);
-        if (p->focusedtask == t)
-            p->focusedtask = NULL;
-        DBG("del %x\n", t->win);
-        g_free(t);
-        return TRUE;
-    }
-    return FALSE;
+    return ( ! ((tk->nws.hidden) || (tk->nws.skip_pager) || (tk->nwwt.dock)));
 }
 
-/* tell to remove element with zero refcount */
-static gboolean
-task_remove_all(Window *win, task *t, pager *p)
+/* Look up a task in the task list. */
+static PagerTask * task_lookup(PagerPlugin * pg, Window win)
 {
-    g_free(t);
-    return TRUE;
+    PagerTask * tk;
+    for (tk = pg->task_list; tk != NULL; tk = tk->task_flink)
+        {
+        if (tk->win == win)
+           return tk;
+        if (tk->win > win)
+            break;
+        }
+    return NULL;
 }
 
+/* Delete a task and optionally unlink it from the task list. */
+static void task_delete(PagerPlugin * pg, PagerTask * tk, gboolean unlink)
+{
+    /* If we think this task had focus, remove that. */
+    if (pg->focused_task == tk)
+        pg->focused_task = NULL;
+
+    /* If requested, unlink the task from the task list.
+     * If not requested, the caller will do this. */
+    if (unlink)
+    {
+        if (pg->task_list == tk)
+            pg->task_list = tk->task_flink;
+        else
+        {
+            /* Locate the task and its predecessor in the list and then remove it.  For safety, ensure it is found. */
+            PagerTask * tk_pred = NULL;
+            PagerTask * tk_cursor;
+            for (
+              tk_cursor = pg->task_list;
+              ((tk_cursor != NULL) && (tk_cursor != tk));
+              tk_pred = tk_cursor, tk_cursor = tk_cursor->task_flink) ;
+            if (tk_cursor == tk)
+                tk_pred->task_flink = tk->task_flink;
+        }
+    }
+
+    /* Deallocate the task structure. */
+    g_free(tk);
+}
 
-static void
-task_get_sizepos(task *t)
+/* Get the geometry of a task window in screen coordinates. */
+static void task_get_geometry(PagerTask * tk)
 {
-    Window root, junkwin;
-    int rx, ry;
-    guint dummy;
-    XWindowAttributes win_attributes;
+    /* Install an error handler that ignores BadWindow and BadDrawable.
+     * We frequently get a ConfigureNotify event on deleted windows. */
+    XErrorHandler previous_error_handler = XSetErrorHandler(panel_handle_x_error_swallow_BadWindow_BadDrawable);
 
-    ENTER;
-    if (!XGetWindowAttributes(GDK_DISPLAY(), t->win, &win_attributes)) {
-        if (!XGetGeometry (GDK_DISPLAY(), t->win, &root, &t->x, &t->y, &t->w, &t->h,
-                  &dummy, &dummy)) {
-            t->x = t->y = t->w = t->h = 2;
+    XWindowAttributes win_attributes;
+    if (XGetWindowAttributes(GDK_DISPLAY(), tk->win, &win_attributes))
+    {
+        Window unused_win;
+        int rx, ry;
+        XTranslateCoordinates(GDK_DISPLAY(), tk->win, win_attributes.root,
+              - win_attributes.border_width,
+              - win_attributes.border_width,
+              &rx, &ry, &unused_win);
+        tk->x = rx;
+        tk->y = ry;
+        tk->w = win_attributes.width;
+        tk->h = win_attributes.height;
+    }
+    else
+    {
+        Window unused_win;
+        guint unused;
+        if ( ! XGetGeometry(GDK_DISPLAY(), tk->win,
+            &unused_win, &tk->x, &tk->y, &tk->w, &tk->h, &unused, &unused))
+        {
+            tk->x = tk->y = tk->w = tk->h = 2;
         }
-
-    } else {
-        XTranslateCoordinates (GDK_DISPLAY(), t->win, win_attributes.root,
-              -win_attributes.border_width,
-              -win_attributes.border_width,
-              &rx, &ry, &junkwin);
-        t->x = rx;
-        t->y = ry;
-        t->w = win_attributes.width;
-        t->h = win_attributes.height;
-        DBG("win=0x%x WxH=%dx%d\n", t->win,t->w, t->h);
     }
-    RET();
-}
 
+    XSetErrorHandler(previous_error_handler);
+}
 
-static void
-task_update_pix(task *t, desk *d)
+/* Draw the representation of a task's window on the backing pixmap. */
+static void task_update_pixmap(PagerTask * tk, PagerDesk * d)
 {
-    int x, y, w, h;
-    GtkWidget *widget;
-    Panel* p;
-
-    ENTER;
-    g_return_if_fail(d->pix != NULL);
-    if (!TASK_VISIBLE(t))
-        RET();;
-
-    p = d->pg->plugin->panel;
-    if (t->desktop < p->desknum &&
-          t->desktop != d->no)
-        RET();
-
-    x = (gfloat)t->x * d->scalew;
-    y = (gfloat)t->y * d->scaleh;
-    w = (gfloat)t->w * d->scalew;
-    //h = (gfloat)t->h * d->scaleh;
-    h = (t->nws.shaded) ? 3 : (gfloat)t->h * d->scaleh;
-    if (w < 3 || h < 3)
-        RET();
-    widget = GTK_WIDGET(d->da);
-    gdk_draw_rectangle (d->pix,
-          (d->pg->focusedtask == t) ?
-          widget->style->bg_gc[GTK_STATE_SELECTED] :
-          widget->style->bg_gc[GTK_STATE_NORMAL],
-          TRUE,
-          x+1, y+1, w-1, h-1);
-    gdk_draw_rectangle (d->pix,
-          (d->pg->focusedtask == t) ?
-          widget->style->fg_gc[GTK_STATE_SELECTED] :
-          widget->style->fg_gc[GTK_STATE_NORMAL],
-          FALSE,
-          x, y, w, h);
-    RET();
+    if ((d->pixmap != NULL) && (task_is_visible(tk)))
+    {
+        Panel * p = d->pg->plugin->panel;
+        if ((tk->desktop == ALL_DESKTOPS) || (tk->desktop == d->desktop_number))
+        {
+            /* Scale the representation of the window to the drawing area. */
+            int x = (gfloat) tk->x * d->scale_x;
+            int y = (gfloat) tk->y * d->scale_y;
+            int w = (gfloat) tk->w * d->scale_x;
+            int h = ((tk->nws.shaded) ? 3 : (gfloat) tk->h * d->scale_y);
+            if ((w >= 3) && (h >= 3))
+            {
+                /* Draw the window representation and a border. */
+                GtkWidget * widget = GTK_WIDGET(d->da);
+                gdk_draw_rectangle(d->pixmap,
+                    (d->pg->focused_task == tk) ? widget->style->bg_gc[GTK_STATE_SELECTED] : widget->style->bg_gc[GTK_STATE_NORMAL],
+                    TRUE,
+                    x + 1, y + 1, w - 1, h - 1);
+                gdk_draw_rectangle(d->pixmap,
+                    (d->pg->focused_task == tk) ? widget->style->fg_gc[GTK_STATE_SELECTED] : widget->style->fg_gc[GTK_STATE_NORMAL],
+                    FALSE,
+                    x, y, w, h);
+            }
+        }
+    }
 }
 
-
 /*****************************************************************
  * Desk Functions                                                *
  *****************************************************************/
-static void
-desk_clear_pixmap(desk *d)
-{
-    GtkWidget *widget;
-
-    ENTER;
-    DBG("d->no=%d\n", d->no);
-    if (!d->pix)
-        RET();
-    widget = GTK_WIDGET(d->da);
-    gdk_draw_rectangle (d->pix,
-          ((d->no == d->pg->curdesk) ?
-                widget->style->dark_gc[GTK_STATE_SELECTED] :
-                widget->style->dark_gc[GTK_STATE_NORMAL]),
-          TRUE,
-          0, 0,
-          widget->allocation.width,
-          widget->allocation.height);
-
-    RET();
-}
 
-
-
-static inline void
-desk_set_dirty(desk *d)
+/* Mark a specified desktop for redraw. */
+static void desk_set_dirty(PagerDesk * d)
 {
-    ENTER;
-    d->dirty = 1;
+    d->dirty = TRUE;
     gtk_widget_queue_draw(d->da);
-    RET();
 }
 
-static inline void
-desk_set_dirty_all(pager *pg)
+/* Mark all desktops for redraw. */
+static void desk_set_dirty_all(PagerPlugin * pg)
 {
     int i;
-    ENTER;
-    for (i = 0; i < pg->desknum; i++)
+    for (i = 0; i < pg->number_of_desktops; i++)
         desk_set_dirty(pg->desks[i]);
-    RET();
 }
 
-static inline void
-desk_set_dirty_by_win(pager *p, task *t)
+/* Mark the desktop on which a specified window resides for redraw. */
+static void desk_set_dirty_by_win(PagerPlugin * pg, PagerTask * tk)
 {
-    ENTER;
-    if (t->nws.skip_pager || t->nwwt.desktop /*|| t->nwwt.dock || t->nwwt.splash*/ )
-        RET();
-    if (t->desktop < p->desknum)
-        desk_set_dirty(p->desks[t->desktop]);
-    else
-        desk_set_dirty_all(p);
-    RET();
+    if ( ! (tk->nws.skip_pager || tk->nwwt.desktop) || tk->nwwt.dock /*|| tk->nwwt.splash*/ )
+    {
+        if (tk->desktop < pg->number_of_desktops)
+            desk_set_dirty(pg->desks[tk->desktop]);
+        else
+            desk_set_dirty_all(pg);
+    }
 }
 
-/* Redraw the screen from the backing pixmap */
-static gint
-desk_expose_event (GtkWidget *widget, GdkEventExpose *event, desk *d)
+/* Handler for configure_event on drawing area. */
+static gboolean desk_configure_event(GtkWidget * widget, GdkEventConfigure * event, PagerDesk * d)
 {
-    ENTER;
-    DBG("d->no=%d\n", d->no);
-
-    if (d->dirty) {
-        pager *pg = d->pg;
-        task *t;
-        int j;
-
-        d->dirty = 0;
-        desk_clear_pixmap(d);
-        for (j = 0; j < pg->winnum; j++) {
-            if (!(t = g_hash_table_lookup(pg->htable, &pg->wins[j])))
-                continue;
-            task_update_pix(t, d);
+    /* Allocate pixmap and statistics buffer without border pixels. */
+    int new_pixmap_width = widget->allocation.width;
+    int new_pixmap_height = widget->allocation.height;
+    if ((new_pixmap_width > 0) && (new_pixmap_height > 0))
+    {
+        /* Allocate a new pixmap of the allocated size. */
+        if (d->pixmap != NULL)
+            g_object_unref(d->pixmap);
+        d->pixmap = gdk_pixmap_new(widget->window, new_pixmap_width, new_pixmap_height, -1);
+
+        /* Compute the horizontal and vertical scale factors, and mark the desktop for redraw. */
+        d->scale_y = (gfloat) widget->allocation.height / (gfloat) gdk_screen_height();
+        d->scale_x = (gfloat) widget->allocation.width  / (gfloat) gdk_screen_width();
+        desk_set_dirty(d);
+     }
+
+    /* Resize to optimal size. */
+    gtk_widget_set_size_request(widget,
+        (PANEL_HEIGHT_DEFAULT - BORDER_WIDTH * 2) * d->pg->aspect_ratio,
+        PANEL_HEIGHT_DEFAULT - BORDER_WIDTH * 2);
+    return FALSE;
+}
+
+/* Handler for expose_event on drawing area. */
+static gboolean desk_expose_event(GtkWidget * widget, GdkEventExpose * event, PagerDesk * d)
+{
+    if (d->pixmap != NULL)
+    {
+        /* Recompute the pixmap if needed. */
+        if (d->dirty)
+        {
+            d->dirty = FALSE;
+            PagerPlugin * pg = d->pg;
+
+            /* Erase the pixmap. */
+            if (d->pixmap != NULL)
+            {
+                GtkWidget * widget = GTK_WIDGET(d->da);
+                gdk_draw_rectangle(
+                    d->pixmap,
+                    ((d->desktop_number == d->pg->current_desktop)
+                        ? widget->style->dark_gc[GTK_STATE_SELECTED]
+                        : widget->style->dark_gc[GTK_STATE_NORMAL]),
+                    TRUE,
+                    0, 0, widget->allocation.width, widget->allocation.height);
+            }
+
+            /* Draw tasks onto the pixmap. */
+            int j;
+            for (j = 0; j < pg->client_count; j++)
+                task_update_pixmap(pg->tasks_in_stacking_order[j], d);
         }
+
+        /* Draw the requested part of the pixmap onto the drawing area. */
+        gdk_draw_drawable(widget->window,
+              widget->style->fg_gc[GTK_WIDGET_STATE(widget)],
+              d->pixmap,
+              event->area.x, event->area.y,
+              event->area.x, event->area.y,
+              event->area.width, event->area.height);
     }
-    gdk_draw_drawable(widget->window,
-          widget->style->fg_gc[GTK_WIDGET_STATE (widget)],
-          d->pix,
-          event->area.x, event->area.y,
-          event->area.x, event->area.y,
-          event->area.width, event->area.height);
-    RET(FALSE);
+    return FALSE;
 }
 
-/* Upon realize and every resize creates a new backing pixmap of the appropriate size */
-static gint
-desk_configure_event (GtkWidget *widget, GdkEventConfigure *event, desk *d)
+/* Handler for "scroll-event" on drawing area. */
+static gboolean desk_scroll_event(GtkWidget * widget, GdkEventScroll * event, PagerDesk * d)
 {
-    Panel* p;
-    int w, h;
-    ENTER;
-    DBG("d->no=%d %dx%d\n", d->no, widget->allocation.width, widget->allocation.height);
-    if (d->pix)
-        g_object_unref(d->pix);
-
-    d->pix = gdk_pixmap_new(widget->window,
-          widget->allocation.width,
-          widget->allocation.height,
-          -1);
-
-    d->scalew = (gfloat)widget->allocation.height / (gfloat)gdk_screen_height();
-    d->scaleh = (gfloat)widget->allocation.width  / (gfloat)gdk_screen_width();
-    desk_set_dirty(d);
-
-    p = d->pg->plugin->panel;
-    //request best size
-    if (p->orientation != ORIENT_HORIZ) {
-        w = widget->allocation.width;
-        h = (gfloat) w / d->pg->ratio;
-    } else {
-        h = widget->allocation.height;
-        w = (gfloat) h * d->pg->ratio;
+    /* Compute the new desktop from the scroll direction, wrapping at either extreme. */
+    int current_desktop = d->pg->current_desktop;
+    if ((event->direction == GDK_SCROLL_UP) || (event->direction == GDK_SCROLL_RIGHT))
+    {
+        current_desktop += 1;
+        if (current_desktop >= d->pg->number_of_desktops)
+            current_desktop = 0;
+    }
+    else
+    {
+        current_desktop -= 1;
+        if (current_desktop < 0)
+            current_desktop = d->pg->number_of_desktops - 1;
     }
-    DBG("requesting %dx%d\n", w, h);
-    gtk_widget_set_size_request(widget, w, h);
 
-    RET(FALSE);
+    /* Ask the window manager to make the new desktop current. */
+    Xclimsg(GDK_ROOT_WINDOW(), a_NET_CURRENT_DESKTOP, current_desktop, 0, 0, 0, 0);
+    return TRUE;
 }
 
-static gint
-desk_button_press_event(GtkWidget * widget, GdkEventButton * event, desk *d)
+/* Handler for "button-press-event" on drawing area. */
+static gboolean desk_button_press_event(GtkWidget * widget, GdkEventButton * event, PagerDesk * d)
 {
-    ENTER;
-    if( event->button == 3 ) { /* right button */
-        GtkMenu* popup =(GtkMenu*) lxpanel_get_panel_menu
-                ( d->pg->plugin->panel, d->pg->plugin, FALSE );
-        gtk_menu_popup( popup, NULL, NULL, NULL, NULL, event->button, event->time );
+    /* Standard right-click handling. */
+    if (plugin_button_press_event(widget, event, d->pg->plugin))
         return TRUE;
-    }
 
-    Xclimsg(GDK_ROOT_WINDOW(), a_NET_CURRENT_DESKTOP, d->no, 0, 0, 0, 0);
-    RET(TRUE);
+    /* Ask the window manager to make the new desktop current. */
+    Xclimsg(GDK_ROOT_WINDOW(), a_NET_CURRENT_DESKTOP, d->desktop_number, 0, 0, 0, 0);
+    return TRUE;
 }
 
-/*
-static gint
-desk_button_release_event(GtkWidget * widget, GdkEventButton * event, desk *d)
+/* Allocate the structure and the graphic elements representing a desktop. */
+static void desk_new(PagerPlugin * pg, int desktop_number)
 {
-    ENTER;
-    DBG("t=%d\n", d->no);
-    Xclimsg(GDK_ROOT_WINDOW(), a_NET_CURRENT_DESKTOP, d->no, 0, 0, 0, 0);
-    RET(TRUE);
-}
-*/
+    
+    /* Allocate and initialize structure. */
+    PagerDesk * d = pg->desks[desktop_number] = g_new0(PagerDesk, 1);
+    d->pg = pg;
+    d->desktop_number = desktop_number;
 
-static gint
-desk_scroll_event (GtkWidget *widget, GdkEventScroll *event, desk *d)
-{
-    int i;
+    /* Allocate drawing area. */
+    d->da = gtk_drawing_area_new();
 
-    ENTER;
-    DBG("scroll direction = %d\n", event->direction);
-    i = d->pg->curdesk;
-    if (event->direction == GDK_SCROLL_UP ||event->direction == GDK_SCROLL_LEFT) {
-        i--;
-        if (i < 0)
-            i = d->pg->desknum - 1;
-    } else {
-        i++;
-        if (i >= d->pg->desknum)
-            i = 0;
-    }
-    Xclimsg(GDK_ROOT_WINDOW(), a_NET_CURRENT_DESKTOP, i, 0, 0, 0, 0);
-    RET(TRUE);
+    icon_grid_add(pg->icon_grid, d->da, TRUE);
+    gtk_widget_add_events (d->da, GDK_EXPOSURE_MASK | GDK_BUTTON_PRESS_MASK);
+
+    /* Connect signals. */
+    g_signal_connect(G_OBJECT(d->da), "expose_event", G_CALLBACK(desk_expose_event), (gpointer) d);
+    g_signal_connect(G_OBJECT(d->da), "configure_event", G_CALLBACK(desk_configure_event), (gpointer) d);
+    g_signal_connect(G_OBJECT(d->da), "scroll-event", G_CALLBACK(desk_scroll_event), (gpointer) d);
+    g_signal_connect(G_OBJECT(d->da), "button_press_event", G_CALLBACK(desk_button_press_event), (gpointer) d);
+
+    /* Show the widget. */
+    gtk_widget_show(d->da);
 }
 
-static void
-desk_new(pager *pg, int i)
+/* Free the structure representing a desktop. */
+static void desk_free(PagerPlugin * pg, int desktop_number)
 {
-    desk *d;
+    PagerDesk * d = pg->desks[desktop_number];
 
-    ENTER;
-    g_assert(i < pg->desknum);
-    d = pg->desks[i] = g_new0(desk, 1);
-    d->pg = pg;
-    d->pix = NULL;
-    d->dirty = 0;
-    d->first = 1;
-    d->no = i;
+    g_signal_handlers_disconnect_by_func(G_OBJECT(d->da), desk_expose_event, d);
+    g_signal_handlers_disconnect_by_func(G_OBJECT(d->da), desk_configure_event, d);
+    g_signal_handlers_disconnect_by_func(G_OBJECT(d->da), desk_scroll_event, d);
+    g_signal_handlers_disconnect_by_func(G_OBJECT(d->da), desk_button_press_event, d);
 
-    d->da = gtk_drawing_area_new();
-    //gtk_widget_set_size_request(GTK_WIDGET(d->da), 10, 10);
-    gtk_box_pack_start(GTK_BOX(pg->box), d->da, TRUE, TRUE, 0);
-    gtk_widget_add_events (d->da, GDK_EXPOSURE_MASK
-          | GDK_BUTTON_PRESS_MASK
-          | GDK_BUTTON_RELEASE_MASK);
-    g_signal_connect (G_OBJECT (d->da), "expose_event",
-          (GCallback) desk_expose_event, (gpointer)d);
-    g_signal_connect (G_OBJECT (d->da), "configure_event",
-          (GCallback) desk_configure_event, (gpointer)d);
-    g_signal_connect (G_OBJECT (d->da), "scroll-event",
-          (GCallback) desk_scroll_event, (gpointer)d);
-    g_signal_connect (G_OBJECT (d->da), "button_press_event",
-         (GCallback) desk_button_press_event, (gpointer)d);
-    //g_signal_connect (G_OBJECT (d->da), "button_release_event",
-    //     (GCallback) desk_button_release_event, (gpointer)d);
-    gtk_widget_show(d->da);
-    DBG("before pack\n");
+    icon_grid_remove(pg->icon_grid, d->da);
 
-    DBG("after show\n");
-    RET();
-}
+    if (d->pixmap != NULL)
+        g_object_unref(d->pixmap);
 
-static void
-desk_free(pager *pg, int i)
-{
-    desk *d;
-
-    ENTER;
-    d = pg->desks[i];
-    DBG("i=%d d->no=%d d->da=%p d->pix=%p\n",
-          i, d->no, d->da, d->pix);
-    if (d->pix)
-        g_object_unref(d->pix);
-    gtk_widget_destroy(d->da);
     g_free(d);
-    RET();
 }
 
-
 /*****************************************************************
- * Netwm/WM Interclient Communication                            *
+ * Pager Functions                                               *
  *****************************************************************/
 
-static void
-do_net_active_window(FbEv *ev, pager *p)
+/* Handle PropertyNotify event.
+ * http://tronche.com/gui/x/icccm/
+ * http://standards.freedesktop.org/wm-spec/wm-spec-1.4.html */
+static void pager_property_notify_event(PagerPlugin * pg, XEvent * ev)
 {
-    Window *fwin;
-    task *t;
-
-    ENTER;
-    fwin = get_xaproperty(GDK_ROOT_WINDOW(), a_NET_ACTIVE_WINDOW, XA_WINDOW, 0);
-    DBG("win=%x\n", fwin ? *fwin : 0);
-    if (fwin) {
-        t = g_hash_table_lookup(p->htable, fwin);
-        if (t != p->focusedtask) {
-            if (p->focusedtask)
-                desk_set_dirty_by_win(p, p->focusedtask);
-            p->focusedtask = t;
-            if (t)
-                desk_set_dirty_by_win(p, t);
-        }
-        XFree(fwin);
-    } else {
-        if (p->focusedtask) {
-            desk_set_dirty_by_win(p, p->focusedtask);
-            p->focusedtask = NULL;
+    /* State may be PropertyNewValue, PropertyDeleted. */
+    if (((XPropertyEvent*) ev)->state == PropertyNewValue)
+    {
+        Atom at = ev->xproperty.atom;
+        Window win = ev->xproperty.window;
+        if (win != GDK_ROOT_WINDOW())
+        {
+            /* Look up task structure by X window handle. */
+            PagerTask * tk = task_lookup(pg, win);
+            if (tk != NULL)
+            {
+                /* Install an error handler that ignores BadWindow.
+                 * We frequently get a PropertyNotify event on deleted windows. */
+                XErrorHandler previous_error_handler = XSetErrorHandler(panel_handle_x_error_swallow_BadWindow_BadDrawable);
+
+                /* Dispatch on atom. */
+                if (at == a_WM_STATE)   
+                {
+                    /* Window changed state. */
+                    tk->ws = get_wm_state(tk->win);
+                    desk_set_dirty_by_win(pg, tk);
+                }
+                else if (at == a_NET_WM_STATE)
+                {
+                    /* Window changed EWMH state. */
+                    get_net_wm_state(tk->win, &tk->nws);
+                    desk_set_dirty_by_win(pg, tk);
+                }
+                else if (at == a_NET_WM_DESKTOP)
+                {
+                    /* Window changed desktop.
+                     * Mark both old and new desktops for redraw. */
+                    desk_set_dirty_by_win(pg, tk);
+                    tk->desktop = get_net_wm_desktop(tk->win);
+                    desk_set_dirty_by_win(pg, tk);
+
+                XSetErrorHandler(previous_error_handler);
+                }
+            }
         }
     }
-    RET();
 }
 
-static void
-do_net_current_desktop(FbEv *ev, pager *p)
+/* Handle ConfigureNotify event. */
+static void pager_configure_notify_event(PagerPlugin * pg, XEvent * ev)
 {
-    ENTER;
-    desk_set_dirty(p->desks[p->curdesk]);
-    p->curdesk =  get_net_current_desktop ();
-    if (p->curdesk >= p->desknum)
-        p->curdesk = 0;
-    desk_set_dirty(p->desks[p->curdesk]);
-    RET();
+    Window win = ev->xconfigure.window;
+    PagerTask * tk = task_lookup(pg, win);
+    if (tk != NULL)
+    {
+        task_get_geometry(tk);
+        desk_set_dirty_by_win(pg, tk);
+    }
 }
 
-
-static void
-do_net_client_list_stacking(FbEv *ev, pager *p)
+/* GDK event filter. */
+static GdkFilterReturn pager_event_filter(XEvent * xev, GdkEvent * event, PagerPlugin * pg)
 {
-    int i;
-    task *t;
-
-    ENTER;
-    if (p->wins)
-        XFree(p->wins);
-    p->wins = get_xaproperty (GDK_ROOT_WINDOW(), a_NET_CLIENT_LIST_STACKING,
-          XA_WINDOW, &p->winnum);
-    if (!p->wins || !p->winnum)
-        RET();
-
-    /* refresh existing tasks and add new */
-    for (i = 0; i < p->winnum; i++) {
-        if ((t = g_hash_table_lookup(p->htable, &p->wins[i]))) {
-            t->refcount++;
-            if (t->stacking != i) {
-                t->stacking = i;
-                desk_set_dirty_by_win(p, t);
-            }
-        } else {
-            t = g_new0(task, 1);
-            t->refcount++;
-            t->win = p->wins[i];
-            t->ws = get_wm_state (t->win);
-            t->desktop = get_net_wm_desktop(t->win);
-            get_net_wm_state(t->win, &t->nws);
-            get_net_wm_window_type(t->win, &t->nwwt);
-            task_get_sizepos(t);
-            if (!FBPANEL_WIN(t->win))
-                XSelectInput (GDK_DISPLAY(), t->win, PropertyChangeMask | StructureNotifyMask);
-            g_hash_table_insert(p->htable, &t->win, t);
-            DBG("add %x\n", t->win);
-            desk_set_dirty_by_win(p, t);
-        }
-    }
-    /* pass throu hash table and delete stale windows */
-    g_hash_table_foreach_remove(p->htable, (GHRFunc) task_remove_stale, (gpointer)p);
-    RET();
+    /* Look for PropertyNotify and ConfigureNotify events and update state. */
+    if (xev->type == PropertyNotify)
+        pager_property_notify_event(pg, xev);
+    else if (xev->type == ConfigureNotify)
+        pager_configure_notify_event(pg, xev);
+    return GDK_FILTER_CONTINUE;
 }
 
-
 /*****************************************************************
- * Pager Functions                                               *
+ * Netwm/WM Interclient Communication                            *
  *****************************************************************/
-/*
-static void
-pager_unmapnotify(pager *p, XEvent *ev)
+
+/* Handler for "active-window" event from root window listener. */
+static void pager_net_active_window(FbEv * ev, PagerPlugin * pg)
 {
-    Window win = ev->xunmap.window;
-    task *t;
-    if (!(t = g_hash_table_lookup(p->htable, &win)))
-        RET();
-    DBG("pager_unmapnotify: win=0x%x\n", win);
-    RET();
-    t->ws = WithdrawnState;
-    desk_set_dirty_by_win(p, t);
-    RET();
+    Window * focused_window = get_xaproperty(GDK_ROOT_WINDOW(), a_NET_ACTIVE_WINDOW, XA_WINDOW, 0);
+    if (focused_window != NULL)
+    {
+        PagerTask * tk = task_lookup(pg, *focused_window);
+        if (tk != pg->focused_task)
+        {
+            /* Focused task changed.  Redraw both old and new. */
+            if (pg->focused_task != NULL)
+                desk_set_dirty_by_win(pg, pg->focused_task);
+            pg->focused_task = tk;
+            if (tk != NULL)
+                desk_set_dirty_by_win(pg, tk);
+        }
+        XFree(focused_window);
+    }
+    else
+    {
+        /* Focused task disappeared.  Redraw old. */
+        if (pg->focused_task != NULL)
+        {
+            desk_set_dirty_by_win(pg, pg->focused_task);
+            pg->focused_task = NULL;
+        }
+    }
 }
-*/
-static void
-pager_configurenotify(pager *p, XEvent *ev)
+
+/* Handler for desktop_name event from window manager. */
+static void pager_net_desktop_names(FbEv * fbev, PagerPlugin * pg)
 {
-    Window win = ev->xconfigure.window;
-    task *t;
+    /* Get the NET_DESKTOP_NAMES property. */
+    int number_of_desktop_names;
+    char * * desktop_names;
+    desktop_names = get_utf8_property_list(GDK_ROOT_WINDOW(), a_NET_DESKTOP_NAMES, &number_of_desktop_names);
 
-    ENTER;
+    /* Loop to copy the desktop names to the vector of labels.
+     * If there are more desktops than labels, label the extras with a decimal number. */
+    int i;
+    for (i = 0; ((desktop_names != NULL) && (i < MIN(pg->number_of_desktops, number_of_desktop_names))); i++)
+        gtk_widget_set_tooltip_text(pg->desks[i]->da, desktop_names[i]);
+    for ( ; i < pg->number_of_desktops; i++)
+    {
+        char temp[10];
+        sprintf(temp, "%d", i + 1);
+        gtk_widget_set_tooltip_text(pg->desks[i]->da, temp);
+    }
 
-    if (!(t = g_hash_table_lookup(p->htable, &win)))
-        RET();
-    DBG("win=0x%x\n", win);
-    task_get_sizepos(t);
-    desk_set_dirty_by_win(p, t);
-    RET();
+    /* Free the property. */
+    if (desktop_names != NULL)
+        g_strfreev(desktop_names);
 }
 
-static void
-pager_propertynotify(pager *p, XEvent *ev)
+/* Handler for "current-desktop" event from root window listener. */
+static void pager_net_current_desktop(FbEv * ev, PagerPlugin * pg)
 {
-    Atom at = ev->xproperty.atom;
-    Window win = ev->xproperty.window;
-    task *t;
-
-    ENTER;
-    if ((win == GDK_ROOT_WINDOW()) || !(t = g_hash_table_lookup(p->htable, &win)))
-        RET();
-
-    /* The property is deleted */
-    if( ((XPropertyEvent*)ev)->state == 1 )
-        return;
-
-    DBG("window=0x%x\n", t->win);
-    if (at == a_WM_STATE)    {
-        DBG("event=WM_STATE\n");
-        t->ws = get_wm_state (t->win);
-    } else if (at == a_NET_WM_STATE) {
-        DBG("event=NET_WM_STATE\n");
-        get_net_wm_state(t->win, &t->nws);
-    } else if (at == a_NET_WM_DESKTOP) {
-        DBG("event=NET_WM_DESKTOP\n");
-        desk_set_dirty_by_win(p, t); // to clean up desks where this task was
-        t->desktop = get_net_wm_desktop(t->win);
-    } else {
-        RET();
-    }
-    desk_set_dirty_by_win(p, t);
-    RET();
+    desk_set_dirty(pg->desks[pg->current_desktop]);
+    pg->current_desktop = get_net_current_desktop();
+    if (pg->current_desktop >= pg->number_of_desktops)
+        pg->current_desktop = 0;
+    desk_set_dirty(pg->desks[pg->current_desktop]);
 }
 
-static GdkFilterReturn
-pager_event_filter( XEvent *xev, GdkEvent *event, pager *pg)
+
+/* Handler for "number-of-desktops" event from root window listener.
+ * Also used to initialize plugin. */
+static void pager_net_number_of_desktops(FbEv * ev, PagerPlugin * pg)
 {
-    ENTER;
-    if (xev->type == PropertyNotify )
-        pager_propertynotify(pg, xev);
-    else if (xev->type == ConfigureNotify )
-        pager_configurenotify(pg, xev);
-    RET(GDK_FILTER_CONTINUE);
-}
+    /* Get existing values. */
+    int number_of_desktops = pg->number_of_desktops;
+    int current_desktop = pg->current_desktop;
+
+    /* Get the correct number of desktops. */
+    pg->number_of_desktops = get_net_number_of_desktops();
+    if (pg->number_of_desktops < 1)
+        pg->number_of_desktops = 1;
+
+    /* Reallocate the structure if necessary. */
+    if (pg->number_of_desktops > pg->desk_extent)
+    {
+        PagerDesk * * new_desks = g_new(PagerDesk *, pg->number_of_desktops);
+        if (pg->desks != NULL)
+        {
+            memcpy(new_desks, pg->desks, pg->desk_extent * sizeof(PagerDesk *));
+            g_free(pg->desks);
+        }
+        pg->desks = new_desks;
+        pg->desk_extent = pg->number_of_desktops;
+    }
 
+    /* Reconcile the current desktop number. */
+    pg->current_desktop = get_net_current_desktop();
+    if (pg->current_desktop >= pg->number_of_desktops)
+        pg->current_desktop = 0;
+
+    /* Reconcile the old and new number of desktops. */
+    int difference = pg->number_of_desktops - number_of_desktops;
+    if (difference != 0)
+    {
+        if (difference < 0)
+        {
+            /* If desktops were deleted, then delete their maps also. */
+            int i;
+            for (i = pg->number_of_desktops; i < number_of_desktops; i++)
+                desk_free(pg, i);
+        }
+        else
+        {
+            /* If desktops were added, then create their maps also. */
+            int i;
+            for (i = number_of_desktops; i < pg->number_of_desktops; i++)
+                desk_new(pg, i);
+        }
+    }
 
+    /* Refresh the client list. */
+    pager_net_client_list_stacking(NULL, pg);
+}
 
+/* Handler for "net-client-list-stacking" event from root window listener. */
+static void pager_net_client_list_stacking(FbEv * ev, PagerPlugin * pg)
+{
+    /* Get the NET_CLIENT_LIST_STACKING property. */
+    Window * client_list = get_xaproperty(GDK_ROOT_WINDOW(), a_NET_CLIENT_LIST_STACKING, XA_WINDOW, &pg->client_count);
+    g_free(pg->tasks_in_stacking_order);
+    if (pg->client_count != 0)
+        pg->tasks_in_stacking_order = g_new(PagerTask *, pg->client_count);
+
+    if (client_list != NULL)
+    {
+        /* Loop over client list, correlating it with task list.
+         * Also generate a vector of task pointers in stacking order. */
+        int i;
+        for (i = 0; i < pg->client_count; i++)
+        {
+            /* Search for the window in the task list.  Set up context to do an insert right away if needed. */
+            PagerTask * tk_pred = NULL;
+            PagerTask * tk_cursor;
+            PagerTask * tk = NULL;
+            for (tk_cursor = pg->task_list; tk_cursor != NULL; tk_pred = tk_cursor, tk_cursor = tk_cursor->task_flink)
+            {
+                if (tk_cursor->win == client_list[i])
+                {
+                    tk = tk_cursor;
+                    break;
+                }
+                if (tk_cursor->win > client_list[i])
+                    break;
+            }
 
+            /* Task is already in task list. */
+            if (tk != NULL)
+            {
+                tk->present_in_client_list = TRUE;
+
+                /* If the stacking position changed, redraw the desktop. */
+                if (tk->stacking != i)
+                {
+                    tk->stacking = i;
+                    desk_set_dirty_by_win(pg, tk);
+                }
+            }
 
-static void
-pager_rebuild_all(FbEv *ev, pager *pg)
-{
-    int desknum, curdesk, dif, i;
-
-    ENTER;
-    desknum = pg->desknum;
-    curdesk = pg->curdesk;
-
-    pg->desknum = get_net_number_of_desktops();
-    if (pg->desknum < 1)
-        pg->desknum = 1;
-    else if (pg->desknum > MAX_DESK_NUM) {
-        pg->desknum = MAX_DESK_NUM;
-        ERR("pager: max number of supported desks is %d\n", MAX_DESK_NUM);
+            /* Task is not in task list. */
+            else
+            {
+                /* Allocate and initialize new task structure. */
+                tk = g_new0(PagerTask, 1);
+                tk->present_in_client_list = TRUE;
+                tk->win = client_list[i];
+                tk->ws = get_wm_state(tk->win);
+                tk->desktop = get_net_wm_desktop(tk->win);
+                get_net_wm_state(tk->win, &tk->nws);
+                get_net_wm_window_type(tk->win, &tk->nwwt);
+                task_get_geometry(tk);
+                if ( ! FBPANEL_WIN(tk->win))
+                    XSelectInput(GDK_DISPLAY(), tk->win, PropertyChangeMask | StructureNotifyMask);
+                desk_set_dirty_by_win(pg, tk);
+
+                /* Link the task structure into the task list. */
+                if (tk_pred == NULL)
+                {
+                    tk->task_flink = pg->task_list;
+                    pg->task_list = tk;
+                }
+                else
+                {
+                    tk->task_flink = tk_pred->task_flink;
+                    tk_pred->task_flink = tk;
+                }
+            }
+            pg->tasks_in_stacking_order[i] = tk;
+        }
+        Xfree(client_list);
     }
-    pg->curdesk = get_net_current_desktop();
-    if (pg->curdesk >= pg->desknum)
-        pg->curdesk = 0;
-    DBG("desknum=%d curdesk=%d\n", desknum, curdesk);
-    DBG("pg->desknum=%d pg->curdesk=%d\n", pg->desknum, pg->curdesk);
-    dif = pg->desknum - desknum;
-
-    if (dif == 0)
-        RET();
-
-    if (dif < 0) {
-        /* if desktops were deleted then delete their maps also */
-        for (i = pg->desknum; i < desknum; i++)
-            desk_free(pg, i);
-    } else {
-        for (i = desknum; i < pg->desknum; i++)
-            desk_new(pg, i);
+
+    /* Remove windows from the task list that are not present in the NET_CLIENT_LIST_STACKING. */
+    PagerTask * tk_pred = NULL;
+    PagerTask * tk = pg->task_list;
+    while (tk != NULL)
+    {
+        PagerTask * tk_succ = tk->task_flink;
+        if (tk->present_in_client_list)
+        {
+            tk->present_in_client_list = FALSE;
+            tk_pred = tk;
+        }
+        else
+        {
+            if (tk_pred == NULL)
+                pg->task_list = tk_succ;
+                else tk_pred->task_flink = tk_succ;
+            task_delete(pg, tk, FALSE);
+        }
+        tk = tk_succ;
     }
-    do_net_client_list_stacking(NULL, pg);
-    RET();
 }
 
-
-static int
-pager_constructor(Plugin *plug, char **fp)
+/* Plugin constructor. */
+static int pager_constructor(Plugin * plug, char ** fp)
 {
-    pager *pg;
-
-    ENTER;
-    pg = g_new0(pager, 1);
-    g_return_val_if_fail(pg != NULL, 0);
+    /* Allocate plugin context and set into Plugin private data pointer. */
+    PagerPlugin * pg = g_new0(PagerPlugin, 1);
     plug->priv = pg;
     pg->plugin = plug;
 
-    plug->pwid = gtk_event_box_new();
-    GTK_WIDGET_SET_FLAGS( plug->pwid, GTK_NO_WINDOW );
+    /* Compute aspect ratio of screen image. */
+    pg->aspect_ratio = (gfloat) gdk_screen_width() / (gfloat) gdk_screen_height();
 
-    pg->htable = g_hash_table_new (g_int_hash, g_int_equal);
+    /* Allocate top level widget and set into Plugin widget pointer. */
+    plug->pwid = gtk_event_box_new();
+    GTK_WIDGET_SET_FLAGS(plug->pwid, GTK_NO_WINDOW);
+    gtk_container_set_border_width(GTK_CONTAINER(plug->pwid), 0);
+
+    /* Create an icon grid manager to manage the drawing areas within the container. */
+    GtkOrientation bo = (plug->panel->orientation == ORIENT_HORIZ) ? GTK_ORIENTATION_HORIZONTAL : GTK_ORIENTATION_VERTICAL;
+    pg->icon_grid = icon_grid_new(plug->panel, plug->pwid, bo,
+        (PANEL_HEIGHT_DEFAULT - BORDER_WIDTH * 2) * pg->aspect_ratio,
+        PANEL_HEIGHT_DEFAULT - BORDER_WIDTH * 2,
+        1, BORDER_WIDTH,
+        plug->panel->height);
+
+    /* Add GDK event filter. */
+    gdk_window_add_filter(NULL, (GdkFilterFunc) pager_event_filter, pg);
+
+    /* Connect signals to receive root window events and initialize root window properties. */
+    g_signal_connect(G_OBJECT(fbev), "current_desktop", G_CALLBACK(pager_net_current_desktop), (gpointer) pg);
+    g_signal_connect(G_OBJECT(fbev), "active_window", G_CALLBACK(pager_net_active_window), (gpointer) pg);
+    g_signal_connect(G_OBJECT(fbev), "desktop_names", G_CALLBACK(pager_net_desktop_names), (gpointer) pg);
+    g_signal_connect(G_OBJECT(fbev), "number_of_desktops", G_CALLBACK(pager_net_number_of_desktops), (gpointer) pg);
+    g_signal_connect(G_OBJECT(fbev), "client_list_stacking", G_CALLBACK(pager_net_client_list_stacking), (gpointer) pg);
+
+    /* Allocate per-desktop structures. */
+    pager_net_number_of_desktops(fbev, pg);
+    pager_net_desktop_names(fbev, pg);
+    return 1;
+}
 
-    pg->box = plug->panel->my_box_new(TRUE, 1);
-    gtk_container_set_border_width (GTK_CONTAINER (pg->box), 2);
-    gtk_widget_show(pg->box);
+/* Plugin destructor. */
+static void pager_destructor(Plugin * p)
+{
+    PagerPlugin * pg = (PagerPlugin *) p->priv;
 
-    gtk_container_set_border_width (GTK_CONTAINER (plug->pwid), 1);
-    gtk_container_add(GTK_CONTAINER(plug->pwid), pg->box);
-    pg->eb = pg->box;
+    /* Remove GDK event filter. */
+    gdk_window_remove_filter(NULL, (GdkFilterFunc) pager_event_filter, pg);
 
-    pg->ratio = (gfloat)gdk_screen_width() / (gfloat)gdk_screen_height();
-    pg->scaley = (gfloat)pg->dh / (gfloat)gdk_screen_height();
-    pg->scalex = (gfloat)pg->dw / (gfloat)gdk_screen_width();
+    /* Remove root window signal handlers. */
+    g_signal_handlers_disconnect_by_func(G_OBJECT(fbev), pager_net_current_desktop, pg);
+    g_signal_handlers_disconnect_by_func(G_OBJECT(fbev), pager_net_active_window, pg);
+    g_signal_handlers_disconnect_by_func(G_OBJECT(fbev), pager_net_number_of_desktops, pg);
+    g_signal_handlers_disconnect_by_func(G_OBJECT(fbev), pager_net_client_list_stacking, pg);
 
-    pager_rebuild_all(fbev, pg);
-    //do_net_current_desktop(fbev, pg);
-    //do_net_client_list_stacking(fbev, pg);
+    /* Deallocate desktop structures. */
+    int i;
+    for (i = 0; i < pg->number_of_desktops; i += 1)
+        desk_free(pg, i);
 
-    gdk_window_add_filter(NULL, (GdkFilterFunc)pager_event_filter, pg );
+    /* Deallocate task list. */
+    while (pg->task_list != NULL)
+        task_delete(pg, pg->task_list, TRUE);
 
-    g_signal_connect (G_OBJECT (fbev), "current_desktop",
-          G_CALLBACK (do_net_current_desktop), (gpointer) pg);
-    g_signal_connect (G_OBJECT (fbev), "active_window",
-          G_CALLBACK (do_net_active_window), (gpointer) pg);
-    g_signal_connect (G_OBJECT (fbev), "number_of_desktops",
-          G_CALLBACK (pager_rebuild_all), (gpointer) pg);
-    g_signal_connect (G_OBJECT (fbev), "client_list_stacking",
-          G_CALLBACK (do_net_client_list_stacking), (gpointer) pg);
-    RET(1);
+    /* Deallocate all memory. */
+    icon_grid_free(pg->icon_grid);
+    g_free(pg->tasks_in_stacking_order);
+    g_free(pg);
 }
 
-static void
-pager_destructor(Plugin *p)
+/* Callback when panel configuration changes. */
+static void pager_panel_configuration_changed(Plugin * p)
 {
-    pager *pg = (pager *)p->priv;
-
-    ENTER;
-    g_signal_handlers_disconnect_by_func(G_OBJECT (fbev), do_net_current_desktop, pg);
-    g_signal_handlers_disconnect_by_func(G_OBJECT (fbev), do_net_active_window, pg);
-    g_signal_handlers_disconnect_by_func(G_OBJECT (fbev), pager_rebuild_all, pg);
-    g_signal_handlers_disconnect_by_func(G_OBJECT (fbev), do_net_client_list_stacking, pg);
-    gdk_window_remove_filter(NULL, (GdkFilterFunc)pager_event_filter, pg);
-    while (--pg->desknum) {
-        desk_free(pg, pg->desknum);
-    }
-    g_hash_table_foreach_remove(pg->htable, (GHRFunc) task_remove_all, (gpointer)pg);
-    g_hash_table_destroy(pg->htable);
-    gtk_widget_destroy(pg->eb);
-    g_free(pg);
-    RET();
+    /* Reset the icon grid orientation. */
+    PagerPlugin * pg = (PagerPlugin *) p->priv;
+    GtkOrientation bo = (p->panel->orientation == ORIENT_HORIZ) ? GTK_ORIENTATION_HORIZONTAL : GTK_ORIENTATION_VERTICAL;
+    icon_grid_set_orientation(pg->icon_grid, bo, p->panel->height); 
 }
 
-
+/* Plugin descriptor. */
 PluginClass pager_plugin_class = {
-    fname: NULL,
-    count: 0,
+
+    PLUGINCLASS_VERSIONING,
 
     type : "pager",
     name : N_("Desktop Pager"),
     version: "1.0",
     description : N_("Simple pager plugin"),
-    /* FIXME: orientation should be handled!! */
+
     constructor : pager_constructor,
     destructor  : pager_destructor,
     config : NULL,
-    save : NULL
+    save : NULL,
+    panel_configuration_changed : pager_panel_configuration_changed
 };
index 2a9309c..929d21a 100644 (file)
 
 #include "dbg.h"
 
-static int
-separator_constructor(Plugin *p, char **fp)
+static int separator_constructor(Plugin * p, char ** fp);
+static void separator_destructor(Plugin * p);
+static void separator_panel_configuration_changed(Plugin * p);
+
+/* Plugin constructor. */
+static int separator_constructor(Plugin * p, char ** fp)
 {
-    GtkWidget *sep, *eb;
+    /* Load parameters from the configuration file. */
     line s;
-
     s.len = 256;
-    if( fp )
+    if (fp != NULL)
     {
-        while (lxpanel_get_line(fp, &s) != LINE_BLOCK_END) {
+        while (lxpanel_get_line(fp, &s) != LINE_BLOCK_END)
+        {
             ERR( "separator: illegal in this context %s\n", s.str);
-            RET(0);
+            return 0;
         }
     }
 
-    p->pwid = eb = gtk_event_box_new();
-    GTK_WIDGET_SET_FLAGS( eb, GTK_NO_WINDOW );
-    gtk_widget_add_events( p->pwid, GDK_BUTTON_PRESS_MASK );
-    g_signal_connect( p->pwid, "button-press-event", G_CALLBACK(plugin_button_press_event), p );
+    /* Allocate top level widget and set into Plugin widget pointer. */
+    p->pwid = gtk_event_box_new();
+    GTK_WIDGET_SET_FLAGS(p->pwid, GTK_NO_WINDOW);
+    gtk_widget_add_events(p->pwid, GDK_BUTTON_PRESS_MASK);
+    gtk_container_set_border_width(GTK_CONTAINER(p->pwid), 1);
 
-    gtk_container_set_border_width(GTK_CONTAINER(eb), 1);
-    gtk_widget_show(eb);
+    /* Allocate separator as a child of top level. */
+    GtkWidget * sep = p->panel->my_separator_new();
+    gtk_container_add(GTK_CONTAINER(p->pwid), sep);
 
-    sep = p->panel->my_separator_new();
-    gtk_widget_show(sep);
-    gtk_container_add (GTK_CONTAINER (eb), sep);
+    /* Connect signals. */
+    g_signal_connect(p->pwid, "button-press-event", G_CALLBACK(plugin_button_press_event), p);
 
+    /* Show the widget and return. */
+    gtk_widget_show_all(p->pwid);
     return 1;
 }
 
-static void
-separator_destructor(Plugin *p)
+/* Plugin destructor. */
+static void separator_destructor(Plugin * p)
 {
 }
 
-static void orientation_changed( Plugin* p )
+/* Callback when panel configuration changes. */
+static void separator_panel_configuration_changed(Plugin * p)
 {
-    GtkWidget* eb = GTK_WIDGET((GtkEventBox*)p->pwid);
-    GtkWidget* sep = gtk_bin_get_child( GTK_BIN(eb) );
-    if( GTK_IS_VSEPARATOR(sep) ) {
-        if( p->panel->orientation == GTK_ORIENTATION_HORIZONTAL )
+    /* Determine if the orientation changed in a way that requires action. */
+    GtkWidget * sep = gtk_bin_get_child(GTK_BIN(p->pwid));
+    if (GTK_IS_VSEPARATOR(sep))
+    {
+        if (p->panel->orientation == GTK_ORIENTATION_HORIZONTAL)
             return;
     }
-    else {
-        if( p->panel->orientation == GTK_ORIENTATION_VERTICAL )
+    else
+    {
+        if (p->panel->orientation == GTK_ORIENTATION_VERTICAL)
             return;
     }
-    gtk_widget_destroy( sep );
+
+    /* If the orientation changed, recreate the separator. */
+    gtk_widget_destroy(sep);
     sep = p->panel->my_separator_new();
     gtk_widget_show(sep);
-    gtk_container_add (GTK_CONTAINER (eb), sep);
+    gtk_container_add(GTK_CONTAINER(p->pwid), sep);
 }
 
+/* Plugin descriptor. */
 PluginClass separator_plugin_class = {
 
+    PLUGINCLASS_VERSIONING,
+
     type : "separator",
     name : N_("Separator"),
     version: "1.0",
@@ -88,5 +103,5 @@ PluginClass separator_plugin_class = {
     destructor  : separator_destructor,
     config : NULL,
     save : NULL,
-    orientation : orientation_changed
+    panel_configuration_changed : separator_panel_configuration_changed
 };
index d7eaafb..874a43f 100644 (file)
 
 #include "dbg.h"
 
+/* Private context for space plugin. */
 typedef struct {
-    int size;
-} space;
+    int size;                          /* Size of spacer */
+} SpacePlugin;
 
-static void
-space_destructor(Plugin *p)
-{
-    space *sp = (space *)p->priv;
-
-    g_free(sp);
-}
+static int space_constructor(Plugin * p, char ** fp);
+static void space_destructor(Plugin * p);
+static void space_apply_configuration(Plugin * p);
+static void space_configure(Plugin * p, GtkWindow * parent);
+static void space_save_configuration(Plugin * p, FILE * fp);
 
-static gboolean on_btn_press( GtkWidget* w, GdkEventButton* evt, Plugin* plugin )
+/* Plugin constructor. */
+static int space_constructor(Plugin * p, char ** fp)
 {
-    if( evt->button == 3 )
-    {
-        GtkMenu* popup =(GtkMenu*)lxpanel_get_panel_menu
-                ( plugin->panel, plugin, FALSE );
-        gtk_menu_popup( popup, NULL, NULL, NULL, NULL, evt->button, evt->time );
-    }
-    return TRUE;
-}
+    /* Allocate plugin context and set into Plugin private data pointer. */
+    SpacePlugin * sp = g_new0(SpacePlugin, 1);
+    p->priv = sp;
 
-static int
-space_constructor(Plugin *p, char** fp)
-{
-    space *sp;
+    /* Load parameters from the configuration file. */
     line s;
-    int w, h;
-
-    ENTER;
     s.len = 256;
-    sp = g_new0(space, 1);
-    g_return_val_if_fail(sp != NULL, 0);
-    p->priv = sp;
-    if( fp )
+    if (fp != NULL)
     {
-        while (lxpanel_get_line(fp, &s) != LINE_BLOCK_END) {
-            if (s.type == LINE_NONE) {
+        while (lxpanel_get_line(fp, &s) != LINE_BLOCK_END)
+        {
+            if (s.type ==