[Xfce4-commits] <xfce4-panel:devel> Some recent work on the tasklist.

Nick Schermer noreply at xfce.org
Mon Feb 15 20:42:01 CET 2010


Updating branch refs/heads/devel
         to 326dc35ea2f9b20f3322467a7de303a23ec880c1 (commit)
       from 2a6e4ee4e16862914c88c9adf16b02336b41f686 (commit)

commit 326dc35ea2f9b20f3322467a7de303a23ec880c1
Author: Nick Schermer <nick at xfce.org>
Date:   Wed Jan 20 14:48:55 2010 +0100

    Some recent work on the tasklist.
    
    Adds internal support for group buttons. Working support
    for the overflow menu when the buttons don't fit. Ctrl
    click to access the panel menu on tasklist buttons. And
    a lot of other stuff I can't remember.

 common/panel-debug.h               |    1 +
 common/panel-private.h             |    5 +
 docs/README.gtkrc-2.0              |    9 +-
 plugins/tasklist/tasklist-widget.c | 1953 +++++++++++++++++++++++++++---------
 plugins/tasklist/tasklist-widget.h |    2 -
 plugins/tasklist/tasklist.c        |   20 +-
 6 files changed, 1495 insertions(+), 495 deletions(-)

diff --git a/common/panel-debug.h b/common/panel-debug.h
index dc7fc61..349033e 100644
--- a/common/panel-debug.h
+++ b/common/panel-debug.h
@@ -24,6 +24,7 @@
 #define PANEL_DEBUG_DOMAIN_APPLICATION "application"
 #define PANEL_DEBUG_DOMAIN_EXTERNAL    "external"
 #define PANEL_DEBUG_DOMAIN_EXTERNAL46  "external46"
+#define PANEL_DEBUG_DOMAIN_TASKLIST    "tasklist"
 
 #define PANEL_DEBUG_BOOL(bool) ((bool) ? "true" : "false")
 
diff --git a/common/panel-private.h b/common/panel-private.h
index a0830f0..f361371 100644
--- a/common/panel-private.h
+++ b/common/panel-private.h
@@ -64,6 +64,11 @@
  * without asking the user what to do */
 #define PANEL_PLUGIN_AUTO_RESTART (60)
 
+/* integer swap functions */
+#define SWAP_INTEGER(a,b) G_STMT_START { gint swp = a; a = b; b = swp; } G_STMT_END
+#define TRANSPOSE_AREA(area) G_STMT_START { SWAP_INTEGER (area.width, area.height); \
+                                            SWAP_INTEGER (area.x, area.y); } G_STMT_END
+
 /* quick GList and GSList counting without traversing */
 #define LIST_HAS_ONE_ENTRY(l)           ((l) != NULL && (l)->next == NULL)
 #define LIST_HAS_ONE_OR_NO_ENTRIES(l)   ((l) == NULL || (l)->next == NULL)
diff --git a/docs/README.gtkrc-2.0 b/docs/README.gtkrc-2.0
index 04af684..0be16e6 100644
--- a/docs/README.gtkrc-2.0
+++ b/docs/README.gtkrc-2.0
@@ -36,6 +36,9 @@ Special widget name in this plugin is showdesktop-button.
 
 XfceTasklist
 ------------
+You can set a custom icon size in gtk-icon-sizes with the name
+panel-tasklist-menu. The default icon size is 16px.
+
 style "xfce-tasklist-style"
 {
   # The maximum length of a button before the label ellipsizes.
@@ -48,12 +51,16 @@ style "xfce-tasklist-style"
   # rows will be possible.
   XfceTasklist::max-button-size = 20
 
-  # Ellipsizing used in the task list button label.
+  # Ellipsizing used in the task list and overflow menu labels.
   XfceTasklist::ellipsize-mode = PANGO_ELLIPSIZE_END
 
   # Lucency of minimized icons. Valid values are between 0 (completely
   # hide the icon) and 100 (don't lighten the icon).
   XfceTasklist::minimized-icon-lucency = 50
+
+  # Maximum number of characters in the menu label before it will
+  # be ellipsized.
+  XfceTasklist::menu-max-width-chars = 24
 }
 class "XfceTasklist" style "xfce-tasklist-style"
 
diff --git a/plugins/tasklist/tasklist-widget.c b/plugins/tasklist/tasklist-widget.c
index c331dc2..08ca394 100644
--- a/plugins/tasklist/tasklist-widget.c
+++ b/plugins/tasklist/tasklist-widget.c
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2008-2009 Nick Schermer <nick at xfce.org>
+ * Copyright (C) 2008-2010 Nick Schermer <nick at xfce.org>
  *
  * This library 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
@@ -23,12 +23,16 @@
 #ifdef HAVE_STRING_H
 #include <string.h>
 #endif
+#ifdef HAVE_MATH_H
+#include <math.h>
+#endif
 
 #include <gtk/gtk.h>
 #include <exo/exo.h>
 #include <libwnck/libwnck.h>
 #include <libxfce4panel/libxfce4panel.h>
 #include <common/panel-private.h>
+#include <common/panel-debug.h>
 
 #ifdef GDK_WINDOWING_X11
 #include <X11/Xlib.h>
@@ -39,13 +43,34 @@
 #include "tasklist-widget.h"
 
 
-#define DEFAULT_BUTTON_SIZE      (25)
-#define DEFAULT_BUTTON_LENGTH    (200)
-#define DEFAULT_ICON_LUCENCY     (50)
-#define WIREFRAME_SIZE           (5)
-#define xfce_taskbar_lock()      G_BEGIN_DECLS { locked++; } G_END_DECLS;
-#define xfce_taskbar_unlock()    G_BEGIN_DECLS { if (locked > 0) locked--; else g_assert_not_reached (); } G_END_DECLS;
-#define xfce_taskbar_is_locked() (locked > 0)
+
+#define DEFAULT_BUTTON_SIZE          (25)
+#define DEFAULT_MAX_BUTTON_LENGTH    (200)
+#define DEFAULT_MENU_ICON_SIZE       (16)
+#define DEFAULT_MIN_BUTTON_LENGTH    (DEFAULT_MAX_BUTTON_LENGTH / 2)
+#define DEFAULT_ICON_LUCENCY         (50)
+#define DEFAULT_ELLIPSIZE_MODE       (PANGO_ELLIPSIZE_END)
+#define DEFAULT_MENU_MAX_WIDTH_CHARS (24)
+#define ARROW_BUTTON_SIZE            (20)
+#define WIREFRAME_SIZE               (5) /* same as xfwm4 */
+
+
+
+/* locking helpers for tasklist->locked */
+#define xfce_taskbar_lock(tasklist)      G_STMT_START { XFCE_TASKLIST (tasklist)->locked++; } G_STMT_END
+#define xfce_taskbar_unlock(tasklist)    G_STMT_START { \
+                                           if (XFCE_TASKLIST (tasklist)->locked > 0) \
+                                             XFCE_TASKLIST (tasklist)->locked--; \
+                                           else \
+                                             panel_assert_not_reached (); \
+                                         } G_STMT_END
+#define xfce_taskbar_is_locked(tasklist) (XFCE_TASKLIST (tasklist)->locked > 0)
+
+#define xfce_tasklist_button_visible(child,tasklist,active_ws) \
+  ((!tasklist->only_minimized || wnck_window_is_minimized (child->window)) \
+   && (tasklist->all_workspaces \
+       || wnck_window_is_pinned (child->window) \
+       || wnck_window_get_workspace (child->window) == active_ws))
 
 
 
@@ -72,14 +97,20 @@ struct _XfceTasklist
 {
   GtkContainer __parent__;
 
+  /* lock counter */
+  gint                  locked;
+
   /* the screen of this tasklist */
   WnckScreen           *screen;
 
-  /* all the applications in the tasklist */
-  GSList               *children;
+  /* window children in the tasklist */
+  GSList               *windows;
+
+  /* windows we monitor, but that are excluded from the tasklist */
+  GSList               *skipped_windows;
 
-  /* number of visible buttons, we cache this to avoid a loop */
-  guint                 n_visible_children;
+  /* arrow button of the overflow menu */
+  GtkWidget            *arrow_button;
 
   /* classgroups of all the windows in the taskbar */
   GSList               *class_groups;
@@ -96,15 +127,15 @@ struct _XfceTasklist
   /* relief of the tasklist buttons */
   GtkReliefStyle        button_relief;
 
-  /* whether we show application from all workspaces or
+  /* whether we show windows from all workspaces or
    * only the active workspace */
   guint                 all_workspaces : 1;
 
   /* whether we switch to another workspace when we try to
-   * unminimize an application on another workspace */
+   * unminimize a window on another workspace */
   guint                 switch_workspace : 1;
 
-  /* whether we only show monimized applications in the
+  /* whether we only show monimized windows in the
    * tasklist */
   guint                 only_minimized : 1;
 
@@ -128,35 +159,53 @@ struct _XfceTasklist
 
   /* gtk style properties */
   gint                  max_button_length;
+  gint                  min_button_length;
   gint                  max_button_size;
   PangoEllipsizeMode    ellipsize_mode;
   gint                  minimized_icon_lucency;
+  gint                  menu_icon_size;
+  gint                  menu_max_width_chars;
+
+  gint n_windows;
 };
 
+typedef enum
+{
+  XFCE_TASKLIST_BUTTON_TYPE_WINDOW,
+  XFCE_TASKLIST_BUTTON_TYPE_GROUP,
+  XFCE_TASKLIST_BUTTON_TYPE_MENU,
+}
+XfceTasklistChildType;
+
+typedef struct _XfceTasklistChild XfceTasklistChild;
 struct _XfceTasklistChild
 {
+  /* type of this button */
+  XfceTasklistChildType  type;
+
   /* pointer to the tasklist */
-  XfceTasklist      *tasklist;
+  XfceTasklist           *tasklist;
 
   /* button widgets */
-  GtkWidget         *button;
-  GtkWidget         *box;
-  GtkWidget         *icon;
-  GtkWidget         *label;
+  GtkWidget              *button;
+  GtkWidget              *box;
+  GtkWidget              *icon;
+  GtkWidget              *label;
 
-  /* this window of this button */
-  WnckWindow        *window;
+  /* unique id for sorting by insert time,
+   * simply increased for each new button */
+  guint                   unique_id;
 
-  /* class group of this window */
-  WnckClassGroup    *class_group;
-
-  /* unique id for sorting by insert time */
-  guint              unique_id;
-};
+  /* last time this window was focused */
+  GTimeVal                last_focused;
 
+  /* list of windows in case of a group button */
+  GSList                 *windows;
 
-
-static gint locked = 0;
+  /* wnck information */
+  WnckWindow             *window;
+  WnckClassGroup         *class_group;
+};
 
 
 
@@ -171,6 +220,7 @@ static void xfce_tasklist_unrealize (GtkWidget *widget);
 static void xfce_tasklist_remove (GtkContainer *container, GtkWidget *widget);
 static void xfce_tasklist_forall (GtkContainer *container, gboolean include_internals, GtkCallback callback, gpointer callback_data);
 static GType xfce_tasklist_child_type (GtkContainer *container);
+static void xfce_tasklist_arrow_button_toggled (GtkWidget *button, XfceTasklist *tasklist);
 static void xfce_tasklist_connect_screen (XfceTasklist *tasklist);
 static void xfce_tasklist_disconnect_screen (XfceTasklist *tasklist);
 static void xfce_tasklist_active_window_changed (WnckScreen *screen, WnckWindow  *previous_window, XfceTasklist *tasklist);
@@ -178,14 +228,27 @@ static void xfce_tasklist_active_workspace_changed (WnckScreen *screen, WnckWork
 static void xfce_tasklist_window_added (WnckScreen *screen, WnckWindow *window, XfceTasklist *tasklist);
 static void xfce_tasklist_window_removed (WnckScreen *screen, WnckWindow *window, XfceTasklist *tasklist);
 static void xfce_tasklist_viewports_changed (WnckScreen *screen, XfceTasklist *tasklist);
+static void xfce_tasklist_skipped_windows_state_changed (WnckWindow *window, WnckWindowState changed_state, WnckWindowState new_state, XfceTasklist *tasklist);
+static void xfce_tasklist_sort (XfceTasklist *tasklist);
+static GtkWidget *xfce_tasklist_get_panel_plugin (XfceTasklist *tasklist);
+
+/* wireframe */
 #ifdef GDK_WINDOWING_X11
 static void xfce_tasklist_wireframe_hide (XfceTasklist *tasklist);
 static void xfce_tasklist_wireframe_destroy (XfceTasklist *tasklist);
 static void xfce_tasklist_wireframe_update (XfceTasklist *tasklist, XfceTasklistChild *child);
 #endif
-static void xfce_tasklist_sort (XfceTasklist *tasklist);
-static gint xfce_tasklist_button_compare (gconstpointer a, gconstpointer b, gpointer user_data);
-static void xfce_tasklist_button_new (XfceTasklistChild *child);
+
+/* tasklist buttons */
+static gint xfce_tasklist_button_compare (gconstpointer child_a, gconstpointer child_b, gpointer user_data);
+static GtkWidget *xfce_tasklist_button_proxy_menu_item (XfceTasklistChild *child);
+static XfceTasklistChild *xfce_tasklist_button_new (WnckWindow *window, XfceTasklist *tasklist);
+
+/* tasklist group buttons */
+static void xfce_tasklist_group_button_remove (XfceTasklist *tasklist, WnckClassGroup *class_group);
+static XfceTasklistChild *xfce_tasklist_group_button_new (WnckClassGroup *class_group, XfceTasklist *tasklist);
+
+/* potential public functions */
 static void xfce_tasklist_set_include_all_workspaces (XfceTasklist *tasklist, gboolean all_workspaces);
 static void xfce_tasklist_set_button_relief (XfceTasklist *tasklist, GtkReliefStyle button_relief);
 static void xfce_tasklist_set_show_labels (XfceTasklist *tasklist, gboolean show_labels);
@@ -198,6 +261,10 @@ G_DEFINE_TYPE (XfceTasklist, xfce_tasklist, GTK_TYPE_CONTAINER)
 
 
 
+static GtkIconSize menu_icon_size = GTK_ICON_SIZE_INVALID;
+
+
+
 static void
 xfce_tasklist_class_init (XfceTasklistClass *klass)
 {
@@ -295,7 +362,15 @@ xfce_tasklist_class_init (XfceTasklistClass *klass)
                                                              NULL,
                                                              "The maximum length of a window button",
                                                              -1, G_MAXINT,
-                                                             DEFAULT_BUTTON_LENGTH,
+                                                             DEFAULT_MAX_BUTTON_LENGTH,
+                                                             EXO_PARAM_READABLE));
+
+  gtk_widget_class_install_style_property (gtkwidget_class,
+                                           g_param_spec_int ("min-button-length",
+                                                             NULL,
+                                                             "The minumum length of a window button",
+                                                             1, G_MAXINT,
+                                                             DEFAULT_MIN_BUTTON_LENGTH,
                                                              EXO_PARAM_READABLE));
 
   gtk_widget_class_install_style_property (gtkwidget_class,
@@ -311,7 +386,7 @@ xfce_tasklist_class_init (XfceTasklistClass *klass)
                                                               NULL,
                                                               "The ellipsize mode used for the button label",
                                                               PANGO_TYPE_ELLIPSIZE_MODE,
-                                                              PANGO_ELLIPSIZE_END,
+                                                              DEFAULT_ELLIPSIZE_MODE,
                                                               EXO_PARAM_READABLE));
 
   gtk_widget_class_install_style_property (gtkwidget_class,
@@ -321,6 +396,17 @@ xfce_tasklist_class_init (XfceTasklistClass *klass)
                                                              0, 100,
                                                              DEFAULT_ICON_LUCENCY,
                                                              EXO_PARAM_READABLE));
+  gtk_widget_class_install_style_property (gtkwidget_class,
+                                           g_param_spec_int ("menu-max-width-chars",
+                                                             NULL,
+                                                             "Maximum chars in the overflow menu labels",
+                                                             0, G_MAXINT,
+                                                             DEFAULT_MENU_MAX_WIDTH_CHARS,
+                                                             EXO_PARAM_READABLE));
+
+  menu_icon_size = gtk_icon_size_register ("panel-tasklist-menu",
+                                           DEFAULT_MENU_ICON_SIZE,
+                                           DEFAULT_MENU_ICON_SIZE);
 }
 
 
@@ -328,15 +414,14 @@ xfce_tasklist_class_init (XfceTasklistClass *klass)
 static void
 xfce_tasklist_init (XfceTasklist *tasklist)
 {
-  /* setup widget */
   GTK_WIDGET_SET_FLAGS (tasklist, GTK_NO_WINDOW);
 
-  /* initialize */
+  tasklist->locked = 0;
   tasklist->screen = NULL;
-  tasklist->children = NULL;
+  tasklist->windows = NULL;
+  tasklist->skipped_windows = NULL;
   tasklist->horizontal = TRUE;
   tasklist->all_workspaces = FALSE;
-  tasklist->n_visible_children = 0;
   tasklist->button_relief = GTK_RELIEF_NORMAL;
   tasklist->switch_workspace = TRUE;
   tasklist->only_minimized = FALSE;
@@ -347,12 +432,23 @@ xfce_tasklist_init (XfceTasklist *tasklist)
 #ifdef GDK_WINDOWING_X11
   tasklist->wireframe_window = 0;
 #endif
-  tasklist->max_button_length = DEFAULT_BUTTON_LENGTH;
+  tasklist->max_button_length = DEFAULT_MAX_BUTTON_LENGTH;
+  tasklist->min_button_length = DEFAULT_MIN_BUTTON_LENGTH;
   tasklist->max_button_size = DEFAULT_BUTTON_SIZE;
   tasklist->minimized_icon_lucency = DEFAULT_ICON_LUCENCY;
-  tasklist->ellipsize_mode = PANGO_ELLIPSIZE_END;
+  tasklist->ellipsize_mode = DEFAULT_ELLIPSIZE_MODE;
   tasklist->grouping = XFCE_TASKLIST_GROUPING_DEFAULT;
   tasklist->sort_order = XFCE_TASKLIST_SORT_ORDER_DEFAULT;
+  tasklist->menu_icon_size = DEFAULT_MENU_ICON_SIZE;
+  tasklist->menu_max_width_chars = DEFAULT_MENU_MAX_WIDTH_CHARS;
+
+  /* widgets for the overflow menu */
+  tasklist->arrow_button = xfce_arrow_button_new (GTK_ARROW_DOWN);
+  gtk_widget_set_parent (tasklist->arrow_button, GTK_WIDGET (tasklist));
+  gtk_button_set_relief (GTK_BUTTON (tasklist->arrow_button), tasklist->button_relief);
+  g_signal_connect (G_OBJECT (tasklist->arrow_button), "toggled",
+      G_CALLBACK (xfce_tasklist_arrow_button_toggled), tasklist);
+  gtk_widget_show (tasklist->arrow_button);
 }
 
 
@@ -367,45 +463,45 @@ xfce_tasklist_get_property (GObject    *object,
 
   switch (prop_id)
     {
-      case PROP_GROUPING:
-        g_value_set_uint (value, tasklist->grouping);
-        break;
+    case PROP_GROUPING:
+      g_value_set_uint (value, tasklist->grouping);
+      break;
 
-      case PROP_INCLUDE_ALL_WORKSPACES:
-        g_value_set_boolean (value, tasklist->all_workspaces);
-        break;
+    case PROP_INCLUDE_ALL_WORKSPACES:
+      g_value_set_boolean (value, tasklist->all_workspaces);
+      break;
 
-      case PROP_FLAT_BUTTONS:
-        g_value_set_boolean (value, !!(tasklist->button_relief == GTK_RELIEF_NONE));
-        break;
+    case PROP_FLAT_BUTTONS:
+      g_value_set_boolean (value, !!(tasklist->button_relief == GTK_RELIEF_NONE));
+      break;
 
-      case PROP_SWITCH_WORKSPACE_ON_UNMINIMIZE:
-        g_value_set_boolean (value, tasklist->switch_workspace);
-        break;
+    case PROP_SWITCH_WORKSPACE_ON_UNMINIMIZE:
+      g_value_set_boolean (value, tasklist->switch_workspace);
+      break;
 
-      case PROP_SHOW_LABELS:
-        g_value_set_boolean (value, tasklist->show_labels);
-        break;
+    case PROP_SHOW_LABELS:
+      g_value_set_boolean (value, tasklist->show_labels);
+      break;
 
-      case PROP_SHOW_ONLY_MINIMIZED:
-        g_value_set_boolean (value, tasklist->only_minimized);
-        break;
+    case PROP_SHOW_ONLY_MINIMIZED:
+      g_value_set_boolean (value, tasklist->only_minimized);
+      break;
 
-      case PROP_SHOW_WIREFRAMES:
-        g_value_set_boolean (value, tasklist->show_wireframes);
-        break;
+    case PROP_SHOW_WIREFRAMES:
+      g_value_set_boolean (value, tasklist->show_wireframes);
+      break;
 
-      case PROP_SHOW_HANDLE:
-        g_value_set_boolean (value, tasklist->show_handle);
-        break;
+    case PROP_SHOW_HANDLE:
+      g_value_set_boolean (value, tasklist->show_handle);
+      break;
 
-      case PROP_SORT_ORDER:
-        g_value_set_uint (value, tasklist->sort_order);
-        break;
+    case PROP_SORT_ORDER:
+      g_value_set_uint (value, tasklist->sort_order);
+      break;
 
-      default:
-        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
-        break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
     }
 }
 
@@ -422,58 +518,52 @@ xfce_tasklist_set_property (GObject      *object,
 
   switch (prop_id)
     {
-      case PROP_GROUPING:
-        tasklist->grouping = g_value_get_uint (value);
-        break;
-
-      case PROP_INCLUDE_ALL_WORKSPACES:
-        /* set include all workspaces */
-        xfce_tasklist_set_include_all_workspaces (tasklist, g_value_get_boolean (value));
-        break;
-
-      case PROP_FLAT_BUTTONS:
-        /* set the tasklist relief */
-        xfce_tasklist_set_button_relief (tasklist,
-                                         g_value_get_boolean (value) ?
-                                           GTK_RELIEF_NONE : GTK_RELIEF_NORMAL);
-        break;
-
-      case PROP_SHOW_LABELS:
-        /* set the new value */
-        xfce_tasklist_set_show_labels (tasklist, g_value_get_boolean (value));
-        break;
-
-      case PROP_SWITCH_WORKSPACE_ON_UNMINIMIZE:
-        /* set the new value */
-        tasklist->switch_workspace = g_value_get_boolean (value);
-        break;
-
-      case PROP_SHOW_ONLY_MINIMIZED:
-        /* whether the tasklist shows only minimized applications */
-        xfce_tasklist_set_show_only_minimized (tasklist, g_value_get_boolean (value));
-        break;
-
-      case PROP_SHOW_WIREFRAMES:
-        /* set the new value */
-        xfce_tasklist_set_show_wireframes (tasklist, g_value_get_boolean (value));
-        break;
-
-      case PROP_SHOW_HANDLE:
-        tasklist->show_handle = g_value_get_boolean (value);
-        break;
-
-      case PROP_SORT_ORDER:
-        sort_order = g_value_get_uint (value);
-        if (tasklist->sort_order != sort_order)
-          {
-            tasklist->sort_order = sort_order;
-            xfce_tasklist_sort (tasklist);
-          }
-        break;
-
-      default:
-        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
-        break;
+    case PROP_GROUPING:
+      tasklist->grouping = g_value_get_uint (value);
+      break;
+
+    case PROP_INCLUDE_ALL_WORKSPACES:
+      xfce_tasklist_set_include_all_workspaces (tasklist, g_value_get_boolean (value));
+      break;
+
+    case PROP_FLAT_BUTTONS:
+      xfce_tasklist_set_button_relief (tasklist,
+                                       g_value_get_boolean (value) ?
+                                         GTK_RELIEF_NONE : GTK_RELIEF_NORMAL);
+      break;
+
+    case PROP_SHOW_LABELS:
+      xfce_tasklist_set_show_labels (tasklist, g_value_get_boolean (value));
+      break;
+
+    case PROP_SWITCH_WORKSPACE_ON_UNMINIMIZE:
+      tasklist->switch_workspace = g_value_get_boolean (value);
+      break;
+
+    case PROP_SHOW_ONLY_MINIMIZED:
+      xfce_tasklist_set_show_only_minimized (tasklist, g_value_get_boolean (value));
+      break;
+
+    case PROP_SHOW_WIREFRAMES:
+      xfce_tasklist_set_show_wireframes (tasklist, g_value_get_boolean (value));
+      break;
+
+    case PROP_SHOW_HANDLE:
+      tasklist->show_handle = g_value_get_boolean (value);
+      break;
+
+    case PROP_SORT_ORDER:
+      sort_order = g_value_get_uint (value);
+      if (tasklist->sort_order != sort_order)
+        {
+          tasklist->sort_order = sort_order;
+          xfce_tasklist_sort (tasklist);
+        }
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
     }
 }
 
@@ -484,17 +574,19 @@ xfce_tasklist_finalize (GObject *object)
 {
   XfceTasklist *tasklist = XFCE_TASKLIST (object);
 
-  panel_return_if_fail (tasklist->children == NULL);
+  /* data that should already be freed when disconnecting the screen */
+  panel_return_if_fail (tasklist->windows == NULL);
+  panel_return_if_fail (tasklist->skipped_windows == NULL);
+  panel_return_if_fail (tasklist->screen == NULL);
 
   /* free the class group list, this is not empty because the
-   * windows are still openened */
+   * windows are (probably) still openened */
   if (tasklist->class_groups != NULL)
     g_slist_free (tasklist->class_groups);
 
 #ifdef GDK_WINDOWING_X11
   /* destroy the wireframe window */
-  if (tasklist->wireframe_window != 0)
-    xfce_tasklist_wireframe_destroy (tasklist);
+  xfce_tasklist_wireframe_destroy (tasklist);
 #endif
 
   (*G_OBJECT_CLASS (xfce_tasklist_parent_class)->finalize) (object);
@@ -506,57 +598,52 @@ static void
 xfce_tasklist_size_request (GtkWidget      *widget,
                             GtkRequisition *requisition)
 {
-  XfceTasklist      *tasklist = XFCE_TASKLIST (widget);
-  GSList            *li;
-  GtkRequisition     child_requisition;
+  XfceTasklist   *tasklist = XFCE_TASKLIST (widget);
+  gint            rows, cols;
+  gint            n_windows;
+  GtkRequisition  child_req;
+  gint            length;
+  GSList *li;
   XfceTasklistChild *child;
-  gint               n;
-  gint               rows, cols, length;
 
-  /* walk all the children */
-  for (li = tasklist->children, n = 0; li != NULL; li = li->next)
+  for (li = tasklist->windows, n_windows = 0; li != NULL; li = li->next)
     {
       child = li->data;
 
-      if (GTK_WIDGET_VISIBLE (child->button) == FALSE)
-        continue;
-
-      /* we don't use the child requisition, but the button needs it
-       * to do proper allocation */
-      gtk_widget_size_request (child->button, &child_requisition);
+      if (GTK_WIDGET_VISIBLE (child->button))
+        {
+          gtk_widget_size_request (child->button, &child_req);
 
-      n++;
+          if (child->type == XFCE_TASKLIST_BUTTON_TYPE_WINDOW
+              || child->type == XFCE_TASKLIST_BUTTON_TYPE_MENU)
+            {
+              child->type = XFCE_TASKLIST_BUTTON_TYPE_WINDOW;
+              n_windows++;
+            }
+        }
     }
 
-  if (G_UNLIKELY (n == 0))
+  tasklist->n_windows = n_windows;
+
+  if (n_windows == 0)
     {
-      /* don't request any size if there are no visible children */
       length = 0;
     }
   else
     {
-      /* calculate the number of rows */
       rows = tasklist->size / tasklist->max_button_size;
-      rows = CLAMP (rows, 1, n);
+      rows = CLAMP (rows, 1, n_windows);
 
-      /* calculate the number of columns */
-      cols = n / rows;
-      cols = MAX (cols, 1);
-      if (cols * rows < n)
+      cols = n_windows / rows;
+      if (cols * rows < n_windows)
         cols++;
 
-      /* get the requested length of the tasklist */
-      if (tasklist->show_labels)
-        {
-          if (tasklist->max_button_length != -1)
-            length = cols * tasklist->max_button_length;
-          else
-            length = cols * DEFAULT_BUTTON_LENGTH;
-        }
+      if (!tasklist->show_labels)
+        length = (tasklist->size / rows) * cols;
+      else if (tasklist->max_button_length != -1)
+        length = cols * tasklist->max_button_length;
       else
-        {
-          length = (tasklist->size / rows) * cols;
-        }
+        length = cols * DEFAULT_MAX_BUTTON_LENGTH;
     }
 
   /* set the requested sizes */
@@ -570,9 +657,123 @@ xfce_tasklist_size_request (GtkWidget      *widget,
       requisition->width = tasklist->size;
       requisition->height = length;
     }
+}
+
+
+
+static gint
+xfce_tasklist_size_sort_window (gconstpointer a,
+                                gconstpointer b)
+{
+  const XfceTasklistChild *child_a = a;
+  const XfceTasklistChild *child_b = b;
+  glong                    diff;
+
+  diff = child_a->last_focused.tv_sec - child_b->last_focused.tv_sec;
+  if (diff != 0)
+    return CLAMP (diff, -1, 1);
+
+  diff = child_a->last_focused.tv_usec - child_b->last_focused.tv_usec;
+  return CLAMP (diff, -1, 1);
+}
+
+
+
+static void
+xfce_tasklist_size_layout (XfceTasklist *tasklist,
+                           GtkAllocation *alloc,
+                           gint *n_rows,
+                           gint *n_cols,
+                           gboolean *arrow_visible)
+{
+  gint rows;
+  gint min_button_length;
+  gint cols;
+  GSList *windows_scored = NULL, *li;
+  XfceTasklistChild *child;
+  gint max_button_length;
+  gint n_buttons;
+  gint n_buttons_target;
+
+  rows = alloc->height / tasklist->max_button_size;
+  if (rows < 1)
+    rows = 1;
+
+  cols = tasklist->n_windows / rows;
+  if (cols * rows < tasklist->n_windows)
+    cols++;
+
+  if (tasklist->show_labels)
+    min_button_length = tasklist->min_button_length;
+  else
+    min_button_length = alloc->height / rows;
+
+  *arrow_visible = FALSE;
+
+  if (min_button_length * cols <= alloc->width)
+    {
+      /* all the windows seem to fit */
+      *n_rows = rows;
+      *n_cols = cols;
+    }
+  else
+    {
+      /* we need to group something, first create a list with the
+       * windows most suitable for grouping at the beginning that are
+       * (should be) currently visible */
+      for (li = tasklist->windows; li != NULL; li = li->next)
+        {
+          child = li->data;
+          if (GTK_WIDGET_VISIBLE (child->button))
+            windows_scored = g_slist_insert_sorted (windows_scored, child,
+                                                    xfce_tasklist_size_sort_window);
+        }
+
+      panel_return_if_fail (g_slist_length (windows_scored) == (guint) tasklist->n_windows);
+
+      if (!tasklist->show_labels)
+        max_button_length = min_button_length;
+      else if (tasklist->max_button_length != -1)
+        max_button_length = tasklist->max_button_length;
+      else
+        max_button_length = DEFAULT_MAX_BUTTON_LENGTH;
+
+      n_buttons = tasklist->n_windows;
+      n_buttons_target = ((alloc->width / max_button_length) + 1) * rows;
+
+      if (tasklist->grouping == XFCE_TASKLIST_GROUPING_AUTO)
+        {
+          /* try creating group buttons */
+        }
+
+      /* we now push the windows with the lowest score in the
+       * overflow menu */
+      if (n_buttons > n_buttons_target)
+        {
+          panel_debug (PANEL_DEBUG_DOMAIN_TASKLIST,
+                       "Putting %d windows in overflow menu",
+                       n_buttons - n_buttons_target);
+
+          for (li = windows_scored;
+               n_buttons > n_buttons_target && li != NULL;
+               li = li->next, n_buttons--)
+            {
+              child = li->data;
+              child->type = XFCE_TASKLIST_BUTTON_TYPE_MENU;
+            }
 
-  /* update the count */
-  tasklist->n_visible_children = n;
+          *arrow_visible = TRUE;
+        }
+
+      g_slist_free (windows_scored);
+
+      cols = n_buttons / rows;
+      if (cols * rows < n_buttons)
+        cols++;
+
+      *n_rows = rows;
+      *n_cols = cols;
+    }
 }
 
 
@@ -582,106 +783,121 @@ xfce_tasklist_size_allocate (GtkWidget     *widget,
                              GtkAllocation *allocation)
 {
   XfceTasklist      *tasklist = XFCE_TASKLIST (widget);
-  guint              rows, cols;
-  gint               width, height;
+  gint               rows, cols;
+  gint               row;
+  GtkAllocation      area = *allocation;
   GSList            *li;
-  XfceTasklistChild *child = NULL;
-  guint              i;
-  GtkAllocation      child_allocation;
-  gboolean           direction_rtl;
+  XfceTasklistChild *child;
+  gint               i;
+  GtkAllocation      child_alloc;
+  gboolean           direction_rtl = gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL;
+  gint               w, x, y, h;
+  gint               area_x, area_width;
+  gboolean           arrow_visible;
+  GtkRequisition     child_req;
+
+  panel_return_if_fail (GTK_WIDGET_VISIBLE (tasklist->arrow_button));
 
   /* set widget allocation */
   widget->allocation = *allocation;
 
-  /* leave when the tasklist is empty */
-  if (tasklist->n_visible_children == 0)
-    return;
+  /* swap integers with vertical orientation */
+  if (!tasklist->horizontal)
+    TRANSPOSE_AREA (area);
+  panel_return_if_fail (area.height == tasklist->size);
 
-  if (tasklist->horizontal)
-    {
-      /* get the number of rows and columns */
-      rows = MAX (allocation->height / tasklist->max_button_size, 1);
-      cols = MAX (tasklist->n_visible_children / rows, 1);
+  /* TODO if we compare the allocation with the requisition we can
+   * do a fast path to the child allocation, i think */
 
-      /* make sure the children fit and use all the space */
-      if (cols * rows < tasklist->n_visible_children)
-        cols++;
-      else if (rows > tasklist->n_visible_children)
-        rows = tasklist->n_visible_children;
+  xfce_tasklist_size_layout (tasklist, &area, &rows, &cols, &arrow_visible);
 
-      /* calculate the size of the buttons */
-      width = allocation->width / cols;
-      if (tasklist->max_button_length != -1)
-        width = MIN (width, tasklist->max_button_length);
-      height = allocation->height / rows;
+  /* allocate the arrow button for the overflow menu */
+  child_alloc.width = ARROW_BUTTON_SIZE;
+  child_alloc.height = area.height;
 
-      if (tasklist->show_labels == FALSE && height < width)
-        width = height;
-    }
-  else
+  if (arrow_visible)
     {
-      /* get the number of rows and columns */
-      rows = MAX (allocation->width / tasklist->max_button_size, 1);
-      cols = MAX (tasklist->n_visible_children / rows, 1);
+      child_alloc.x = area.x;
+      child_alloc.y = area.y;
 
-      /* make sure the children fit and use all the space */
-      if (cols * rows < tasklist->n_visible_children)
-        cols++;
-      else if (rows > tasklist->n_visible_children)
-        rows = tasklist->n_visible_children;
+      if (!direction_rtl)
+        child_alloc.x += area.width - ARROW_BUTTON_SIZE;
+      else
+        area.x += ARROW_BUTTON_SIZE;
 
-      /* calculate the size of the buttons */
-      height = allocation->height / cols;
-      if (tasklist->max_button_length != -1)
-        height = MIN (height, tasklist->max_button_length);
-      width = allocation->width / rows;
+      area.width -= ARROW_BUTTON_SIZE;
 
-      if (tasklist->show_labels == FALSE && width < height)
-        height = width;
+      /* position the arrow in the correct position */
+      if (!tasklist->horizontal)
+        TRANSPOSE_AREA (child_alloc);
+    }
+  else
+    {
+      child_alloc.x = child_alloc.y = -9999;
     }
 
-  /* initialize the child allocation */
-  child_allocation.width = width;
-  child_allocation.height = height;
+  gtk_widget_size_allocate (tasklist->arrow_button, &child_alloc);
 
-  /* check if this is a rtl widget */
-  direction_rtl = gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL;
+  area_x = area.x;
+  area_width = area.width;
 
   /* allocate all the children */
-  for (li = tasklist->children, i = 0; li != NULL; li = li->next)
+  for (li = tasklist->windows, i = 0; li != NULL; li = li->next)
     {
       child = li->data;
 
       /* skip hidden buttons */
-      if (GTK_WIDGET_VISIBLE (child->button) == FALSE)
+      if (!GTK_WIDGET_VISIBLE (child->button))
         continue;
 
-      /* calculate the child position */
-      if (tasklist->horizontal)
+      if (G_LIKELY (child->type == XFCE_TASKLIST_BUTTON_TYPE_WINDOW))
         {
-          child_allocation.x = (i / rows) * width + allocation->x;
-          child_allocation.y = (i % rows) * height + allocation->y;
+          row = (i % rows);
+          if (row == 0)
+            {
+              w = area_width / cols--;
+              if (tasklist->max_button_length > 0
+                  && w > tasklist->max_button_length)
+               w = tasklist->max_button_length;
+              x = area_x;
+
+              area_width -= w;
+              area_x += w;
+
+              y = area.y;
+              h = area.height;
+            }
+
+          child_alloc.y = y;
+          child_alloc.x = x;
+          child_alloc.width = MAX (w, 1); /* TODO this is a workaround */
+          child_alloc.height = h / (rows - row);
 
-          /* move to the other end of the panel if the direction is rtl */
-          if (G_UNLIKELY (direction_rtl))
-            child_allocation.x = allocation->x + allocation->width
-                - (child_allocation.x - allocation->x) - child_allocation.width;
+          h -= child_alloc.height;
+          y += child_alloc.height;
+
+          if (direction_rtl)
+            child_alloc.x = area.x + area.width - (child_alloc.x - area.x) - child_alloc.width;
+
+          /* allocate the child */
+          if (!tasklist->horizontal)
+            TRANSPOSE_AREA (child_alloc);
+
+          /* increase the position counter */
+          i++;
         }
       else
         {
-          child_allocation.x = (i % rows) * width + allocation->x;
-          child_allocation.y = (i / rows) * height + allocation->y;
-        }
+          gtk_widget_get_child_requisition (child->button, &child_req);
 
-      /* allocate the child */
-      gtk_widget_size_allocate (child->button, &child_allocation);
+          /* move the button offscreen */
+          child_alloc.y = child_alloc.x = -9999;
+          child_alloc.width = child_req.width;
+          child_alloc.height = child_req.height;
+        }
 
-      /* increase the position counter */
-      i++;
+      gtk_widget_size_allocate (child->button, &child_alloc);
     }
-
-  /* check if those two are the same, they should... */
-  g_assert (i == tasklist->n_visible_children);
 }
 
 
@@ -693,6 +909,8 @@ xfce_tasklist_style_set (GtkWidget *widget,
   XfceTasklist *tasklist = XFCE_TASKLIST (widget);
   gint          max_button_length;
   gint          max_button_size;
+  gint          min_button_length;
+  gint          w, h;
 
   /* let gtk update the widget style */
   (*GTK_WIDGET_CLASS (xfce_tasklist_parent_class)->style_set) (widget, previous_style);
@@ -700,16 +918,33 @@ xfce_tasklist_style_set (GtkWidget *widget,
   /* read the style properties */
   gtk_widget_style_get (GTK_WIDGET (tasklist),
                         "max-button-length", &max_button_length,
+                        "min-button-length", &min_button_length,
                         "ellipsize-mode", &tasklist->ellipsize_mode,
                         "max-button-size", &max_button_size,
                         "minimized-icon-lucency", &tasklist->minimized_icon_lucency,
+                        "menu-max-width-chars", &tasklist->menu_max_width_chars,
                         NULL);
 
+  if (gtk_icon_size_lookup (menu_icon_size, &w, &h))
+    tasklist->menu_icon_size = MIN (w, h);
+
   /* update the widget */
   if (tasklist->max_button_length != max_button_length
-      || tasklist->max_button_size != max_button_size)
+      || tasklist->max_button_size != max_button_size
+      || tasklist->min_button_length != min_button_length)
     {
-      tasklist->max_button_length = max_button_length;
+      if (max_button_length > 0)
+        {
+          /* prevent abuse of the min/max button length */
+          tasklist->max_button_length = MAX (min_button_length, max_button_length);
+          tasklist->min_button_length = MIN (min_button_length, max_button_length);
+        }
+      else
+        {
+          tasklist->max_button_length = max_button_length;
+          tasklist->min_button_length = min_button_length;
+        }
+
       tasklist->max_button_size = max_button_size;
 
       gtk_widget_queue_resize (widget);
@@ -723,10 +958,9 @@ xfce_tasklist_realize (GtkWidget *widget)
 {
   XfceTasklist *tasklist = XFCE_TASKLIST (widget);
 
-  /* let gtk realize the widget */
   (*GTK_WIDGET_CLASS (xfce_tasklist_parent_class)->realize) (widget);
 
-  /* connect to the screen */
+  /* we now have a screen */
   xfce_tasklist_connect_screen (tasklist);
 }
 
@@ -737,10 +971,9 @@ xfce_tasklist_unrealize (GtkWidget *widget)
 {
   XfceTasklist *tasklist = XFCE_TASKLIST (widget);
 
-  /* disconnect from the screen */
+  /* we're going to loose the screen */
   xfce_tasklist_disconnect_screen (tasklist);
 
-  /* let gtk unrealize the widget */
   (*GTK_WIDGET_CLASS (xfce_tasklist_parent_class)->unrealize) (widget);
 }
 
@@ -755,29 +988,24 @@ xfce_tasklist_remove (GtkContainer *container,
   XfceTasklistChild *child;
   GSList            *li;
 
-  for (li = tasklist->children; li != NULL; li = li->next)
+  for (li = tasklist->windows; li != NULL; li = li->next)
     {
       child = li->data;
 
       if (child->button == widget)
         {
-          /* remove the child from the list */
-          tasklist->children = g_slist_delete_link (tasklist->children, li);
+          tasklist->windows = g_slist_delete_link (tasklist->windows, li);
 
-          /* whether the widget is currently visible */
           was_visible = GTK_WIDGET_VISIBLE (widget);
 
-          /* remove from the itembar */
           gtk_widget_unparent (child->button);
 
-          /* cleanup the slice */
           g_slice_free (XfceTasklistChild, child);
 
           /* queue a resize if needed */
           if (G_LIKELY (was_visible))
             gtk_widget_queue_resize (GTK_WIDGET (container));
 
-          /* done */
           break;
         }
     }
@@ -792,9 +1020,12 @@ xfce_tasklist_forall (GtkContainer *container,
                       gpointer      callback_data)
 {
   XfceTasklist      *tasklist = XFCE_TASKLIST (container);
-  GSList            *children = tasklist->children;
+  GSList            *children = tasklist->windows;
   XfceTasklistChild *child;
 
+  if (include_internals)
+    (* callback) (tasklist->arrow_button, callback_data);
+
   while (children != NULL)
     {
       child = children->data;
@@ -815,6 +1046,67 @@ xfce_tasklist_child_type (GtkContainer *container)
 
 
 static void
+xfce_tasklist_arrow_button_menu_destroy (GtkWidget    *menu,
+                                         XfceTasklist *tasklist)
+{
+  panel_return_if_fail (XFCE_IS_TASKLIST (tasklist));
+  panel_return_if_fail (GTK_IS_TOGGLE_BUTTON (tasklist->arrow_button));
+  panel_return_if_fail (GTK_IS_WIDGET (menu));
+
+  gtk_widget_destroy (menu);
+
+  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (tasklist->arrow_button), FALSE);
+
+#ifdef GDK_WINDOWING_X11
+  /* make sure the wireframe is hidden */
+  xfce_tasklist_wireframe_hide (tasklist);
+#endif
+}
+
+
+
+static void
+xfce_tasklist_arrow_button_toggled (GtkWidget    *button,
+                                    XfceTasklist *tasklist)
+{
+  GSList            *li;
+  XfceTasklistChild *child;
+  GtkWidget         *mi;
+  GtkWidget         *menu;
+
+  panel_return_if_fail (XFCE_IS_TASKLIST (tasklist));
+  panel_return_if_fail (GTK_IS_TOGGLE_BUTTON (button));
+  panel_return_if_fail (tasklist->arrow_button == button);
+
+  if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button)))
+    {
+      menu = gtk_menu_new ();
+      g_signal_connect (G_OBJECT (menu), "selection-done",
+          G_CALLBACK (xfce_tasklist_arrow_button_menu_destroy), tasklist);
+
+      for (li = tasklist->windows; li != NULL; li = li->next)
+        {
+          child = li->data;
+
+          if (child->type != XFCE_TASKLIST_BUTTON_TYPE_MENU)
+            continue;
+
+          mi = xfce_tasklist_button_proxy_menu_item (child);
+          gtk_menu_shell_append (GTK_MENU_SHELL (menu), mi);
+          gtk_widget_show (mi);
+        }
+
+      gtk_menu_attach_to_widget (GTK_MENU (menu), button, NULL);
+      gtk_menu_popup (GTK_MENU (menu), NULL, NULL,
+                      xfce_panel_plugin_position_menu,
+                      xfce_tasklist_get_panel_plugin (tasklist),
+                      1, gtk_get_current_event_time ());
+    }
+}
+
+
+
+static void
 xfce_tasklist_connect_screen (XfceTasklist *tasklist)
 {
   GdkScreen *screen;
@@ -822,23 +1114,21 @@ xfce_tasklist_connect_screen (XfceTasklist *tasklist)
   panel_return_if_fail (XFCE_IS_TASKLIST (tasklist));
   panel_return_if_fail (tasklist->screen == NULL);
 
-  /* get the widget screen */
+  /* set the new screen */
   screen = gtk_widget_get_screen (GTK_WIDGET (tasklist));
-
-  /* set the wnck screen */
   tasklist->screen = wnck_screen_get (gdk_screen_get_number (screen));
 
   /* monitor screen changes */
   g_signal_connect (G_OBJECT (tasklist->screen), "active-window-changed",
-                    G_CALLBACK (xfce_tasklist_active_window_changed), tasklist);
+      G_CALLBACK (xfce_tasklist_active_window_changed), tasklist);
   g_signal_connect (G_OBJECT (tasklist->screen), "active-workspace-changed",
-                    G_CALLBACK (xfce_tasklist_active_workspace_changed), tasklist);
+      G_CALLBACK (xfce_tasklist_active_workspace_changed), tasklist);
   g_signal_connect (G_OBJECT (tasklist->screen), "window-opened",
-                    G_CALLBACK (xfce_tasklist_window_added), tasklist);
+      G_CALLBACK (xfce_tasklist_window_added), tasklist);
   g_signal_connect (G_OBJECT (tasklist->screen), "window-closed",
-                    G_CALLBACK (xfce_tasklist_window_removed), tasklist);
+      G_CALLBACK (xfce_tasklist_window_removed), tasklist);
   g_signal_connect (G_OBJECT (tasklist->screen), "viewports-changed",
-                    G_CALLBACK (xfce_tasklist_viewports_changed), tasklist);
+      G_CALLBACK (xfce_tasklist_viewports_changed), tasklist);
 }
 
 
@@ -848,33 +1138,40 @@ xfce_tasklist_disconnect_screen (XfceTasklist *tasklist)
 {
   GSList            *li, *lnext;
   XfceTasklistChild *child;
+  guint              n;
 
   panel_return_if_fail (XFCE_IS_TASKLIST (tasklist));
   panel_return_if_fail (WNCK_IS_SCREEN (tasklist->screen));
 
   /* disconnect monitor signals */
-  g_signal_handlers_disconnect_by_func (G_OBJECT (tasklist->screen),
-      G_CALLBACK (xfce_tasklist_active_window_changed), tasklist);
-  g_signal_handlers_disconnect_by_func (G_OBJECT (tasklist->screen),
-      G_CALLBACK (xfce_tasklist_active_workspace_changed), tasklist);
-  g_signal_handlers_disconnect_by_func (G_OBJECT (tasklist->screen),
-      G_CALLBACK (xfce_tasklist_window_added), tasklist);
-  g_signal_handlers_disconnect_by_func (G_OBJECT (tasklist->screen),
-      G_CALLBACK (xfce_tasklist_window_removed), tasklist);
-  g_signal_handlers_disconnect_by_func (G_OBJECT (tasklist->screen),
-      G_CALLBACK (xfce_tasklist_viewports_changed), tasklist);
+  n = g_signal_handlers_disconnect_matched (G_OBJECT (tasklist->screen),
+      G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, tasklist);
+  panel_return_if_fail (n == 5);
+
+  /* disconnect from all skipped windows */
+  for (li = tasklist->skipped_windows; li != NULL; li = lnext)
+    {
+      lnext = li->next;
+      panel_return_if_fail (wnck_window_is_skip_tasklist (WNCK_WINDOW (li->data)));
+      xfce_tasklist_window_removed (tasklist->screen, li->data, tasklist);
+    }
 
   /* remove all the windows */
-  for (li = tasklist->children; li != NULL; li = lnext)
+  for (li = tasklist->windows; li != NULL; li = lnext)
     {
       lnext = li->next;
       child = li->data;
 
       /* do a fake window remove */
-      xfce_tasklist_window_removed (tasklist->screen, child->window, tasklist);
+      if (G_LIKELY (child->type != XFCE_TASKLIST_BUTTON_TYPE_GROUP))
+        xfce_tasklist_window_removed (tasklist->screen, child->window, tasklist);
+      else
+        xfce_tasklist_group_button_remove (tasklist, child->class_group);
     }
 
-  /* unset the screen */
+  panel_assert (tasklist->windows == NULL);
+  panel_assert (tasklist->skipped_windows == NULL);
+
   tasklist->screen = NULL;
 }
 
@@ -898,24 +1195,30 @@ xfce_tasklist_active_window_changed (WnckScreen   *screen,
   active_window = wnck_screen_get_active_window (screen);
 
   /* lock the taskbar */
-  xfce_taskbar_lock ();
+  xfce_taskbar_lock (tasklist);
 
-  for (li = tasklist->children; li != NULL; li = li->next)
+  for (li = tasklist->windows; li != NULL; li = li->next)
     {
       child = li->data;
 
       /* skip hidden buttons */
-      if (GTK_WIDGET_VISIBLE (child->button) == FALSE
-          || !(child->window == previous_window || child->window == active_window))
+      /* TODO the visible check probably breaks with grouping */
+      if (!GTK_WIDGET_VISIBLE (child->button)
+          || !(child->window == previous_window
+               || child->window == active_window))
         continue;
 
+      /* update timestamp for window */
+      if (child->window == active_window)
+        g_get_current_time (&child->last_focused);
+
       /* set the toggle button state */
       gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (child->button),
                                     !!(child->window == active_window));
     }
 
   /* release the lock */
-  xfce_taskbar_unlock ();
+  xfce_taskbar_unlock (tasklist);
 }
 
 
@@ -935,19 +1238,18 @@ xfce_tasklist_active_workspace_changed (WnckScreen    *screen,
   panel_return_if_fail (tasklist->screen == screen);
 
   /* leave when we show tasks from all workspaces or are locked */
-  if (xfce_taskbar_is_locked () || tasklist->all_workspaces == TRUE)
+  if (xfce_taskbar_is_locked (tasklist)
+      || tasklist->all_workspaces)
     return;
 
-  /* get the new active workspace */
-  active_ws = wnck_screen_get_active_workspace (screen);
-
   /* walk all the children and hide buttons on other workspaces */
-  for (li = tasklist->children; li != NULL; li = li->next)
+  active_ws = wnck_screen_get_active_workspace (screen);
+  for (li = tasklist->windows; li != NULL; li = li->next)
     {
       child = li->data;
 
-      if (wnck_window_get_workspace (child->window) == active_ws
-          && (!tasklist->only_minimized || wnck_window_is_minimized (child->window)))
+      if (child->type == XFCE_TASKLIST_BUTTON_TYPE_GROUP
+          || xfce_tasklist_button_visible (child, tasklist, active_ws))
         gtk_widget_show (child->button);
       else
         gtk_widget_hide (child->button);
@@ -962,62 +1264,45 @@ xfce_tasklist_window_added (WnckScreen   *screen,
                             XfceTasklist *tasklist)
 {
   XfceTasklistChild *child;
-  static guint       unique_id_counter = 0;
-  WnckClassGroup    *class_group;
-  WnckWorkspace     *active_ws;
 
   panel_return_if_fail (WNCK_IS_SCREEN (screen));
   panel_return_if_fail (WNCK_IS_WINDOW (window));
   panel_return_if_fail (XFCE_IS_TASKLIST (tasklist));
   panel_return_if_fail (tasklist->screen == screen);
+  panel_return_if_fail (wnck_window_get_screen (window) == screen);
 
-  /* TODO we should monitor those windows too, for state changes */
+  /* ignore this window, but watch it for state changes */
   if (wnck_window_is_skip_tasklist (window))
-    return;
-
-  /* allocate new child */
-  child = g_slice_new0 (XfceTasklistChild);
-  child->tasklist = tasklist;
-  child->window = window;
-  child->unique_id = unique_id_counter++;
-
-  /* get the class group of the new window */
-  class_group = wnck_window_get_class_group (window);
-  if (G_LIKELY (class_group != NULL))
     {
-      /* prepend the class group if it's new */
-      if (g_slist_find (tasklist->class_groups, class_group) == NULL)
-        tasklist->class_groups = g_slist_prepend (tasklist->class_groups, class_group);
-
-      /* set the class group */
-      child->class_group = g_object_ref (G_OBJECT (class_group));
+      tasklist->skipped_windows = g_slist_prepend (tasklist->skipped_windows, window);
+      g_signal_connect (G_OBJECT (window), "state-changed",
+          G_CALLBACK (xfce_tasklist_skipped_windows_state_changed), tasklist);
 
-    }
-  else
-    {
-      child->class_group = NULL;
+      return;
     }
 
-  /* create the application button */
-  xfce_tasklist_button_new (child);
+  /* create new window button */
+  child = xfce_tasklist_button_new (window, tasklist);
 
-  /* insert in the internal list */
-  tasklist->children = g_slist_insert_sorted_with_data (tasklist->children, child,
-                                                        xfce_tasklist_button_compare,
-                                                        tasklist);
-
-  /* set the parent */
-  gtk_widget_set_parent (child->button, GTK_WIDGET (tasklist));
+  /* initial visibility of the function */
+  if (xfce_tasklist_button_visible (child, tasklist, wnck_screen_get_active_workspace (screen)))
+    gtk_widget_show (child->button);
 
-  /* get the new active workspace */
-  active_ws = wnck_screen_get_active_workspace (screen);
+  if (G_LIKELY (child->class_group != NULL))
+    {
+      /* we need to ref the class group else the value returned from
+       * wnck_window_get_class_group() is null */
+      panel_return_if_fail (WNCK_IS_CLASS_GROUP (child->class_group));
+      g_object_ref (G_OBJECT (child->class_group));
 
-  /* show the button */
-  if (wnck_window_get_workspace (window) == active_ws
-      && (!tasklist->only_minimized || wnck_window_is_minimized (window)))
-    gtk_widget_show (child->button);
+      /* prepend the class group if it's new */
+      if (g_slist_find (tasklist->class_groups, child->class_group) == NULL)
+        {
+          tasklist->class_groups = g_slist_prepend (tasklist->class_groups,
+                                                    child->class_group);
+        }
+    }
 
-  /* resize the itembar */
   gtk_widget_queue_resize (GTK_WIDGET (tasklist));
 }
 
@@ -1030,14 +1315,28 @@ xfce_tasklist_window_removed (WnckScreen   *screen,
 {
   GSList            *li;
   XfceTasklistChild *child;
+  GList             *windows, *lp;
+  gboolean           remove_class_group = TRUE;
+  guint              n;
 
   panel_return_if_fail (WNCK_IS_SCREEN (screen));
   panel_return_if_fail (WNCK_IS_WINDOW (window));
   panel_return_if_fail (XFCE_IS_TASKLIST (tasklist));
   panel_return_if_fail (tasklist->screen == screen);
 
+  /* check if the window is in our skipped window list */
+  if (wnck_window_is_skip_tasklist (window)
+      && (li = g_slist_find (tasklist->skipped_windows, window)) != NULL)
+    {
+      tasklist->skipped_windows = g_slist_delete_link (tasklist->skipped_windows, li);
+      g_signal_handlers_disconnect_by_func (G_OBJECT (window),
+          G_CALLBACK (xfce_tasklist_skipped_windows_state_changed), tasklist);
+
+      return;
+    }
+
   /* remove the child from the taskbar */
-  for (li = tasklist->children; li != NULL; li = li->next)
+  for (li = tasklist->windows; li != NULL; li = li->next)
     {
       child = li->data;
 
@@ -1047,15 +1346,29 @@ xfce_tasklist_window_removed (WnckScreen   *screen,
             {
               /* remove the class group from the internal list if this
                * was the last window in the group */
-              if (wnck_class_group_get_windows (child->class_group) == NULL)
-                tasklist->class_groups = g_slist_remove (tasklist->class_groups,
-                                                         child->class_group);
-
-              /* release the class group */
+              windows = wnck_class_group_get_windows (child->class_group);
+              for (lp = windows; remove_class_group && lp != NULL; lp = lp->next)
+                if (!wnck_window_is_skip_tasklist (WNCK_WINDOW (lp->data)))
+                  remove_class_group = FALSE;
+
+              if (remove_class_group)
+                {
+                  tasklist->class_groups = g_slist_remove (tasklist->class_groups,
+                                                           child->class_group);
+                }
+
+              panel_return_if_fail (WNCK_IS_CLASS_GROUP (child->class_group));
               g_object_unref (G_OBJECT (child->class_group));
             }
 
-          /* destroy the button */
+          /* disconnect from all the window watch functions */
+          panel_return_if_fail (WNCK_IS_WINDOW (window));
+          n = g_signal_handlers_disconnect_matched (G_OBJECT (window),
+              G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, child);
+          panel_return_if_fail (n == 4);
+
+          /* destroy the button, this will free the child data in the
+           * container remove function */
           gtk_widget_destroy (child->button);
 
           break;
@@ -1072,19 +1385,85 @@ xfce_tasklist_viewports_changed (WnckScreen   *screen,
   panel_return_if_fail (WNCK_IS_SCREEN (screen));
   panel_return_if_fail (XFCE_IS_TASKLIST (tasklist));
   panel_return_if_fail (tasklist->screen == screen);
+
+  /* TODO, rebuild the tasklist (only when we filter windows on
+   * this monitor?) */
 }
 
 
 
+static void
+xfce_tasklist_skipped_windows_state_changed (WnckWindow      *window,
+                                             WnckWindowState  changed_state,
+                                             WnckWindowState  new_state,
+                                             XfceTasklist    *tasklist)
+{
+  panel_return_if_fail (XFCE_IS_TASKLIST (tasklist));
+  panel_return_if_fail (WNCK_IS_WINDOW (window));
+  panel_return_if_fail (g_slist_find (tasklist->skipped_windows, window) != NULL);
+
+  if (PANEL_HAS_FLAG (changed_state, WNCK_WINDOW_STATE_SKIP_TASKLIST))
+    {
+      /* remove from list */
+      tasklist->skipped_windows = g_slist_remove (tasklist->skipped_windows, window);
+      g_signal_handlers_disconnect_by_func (G_OBJECT (window),
+          G_CALLBACK (xfce_tasklist_skipped_windows_state_changed), tasklist);
+
+      /* pretend a normal window insert */
+      xfce_tasklist_window_added (wnck_window_get_screen (window), window, tasklist);
+    }
+}
+
+
+
+static void
+xfce_tasklist_sort (XfceTasklist *tasklist)
+{
+  panel_return_if_fail (XFCE_IS_TASKLIST (tasklist));
+
+  tasklist->windows = g_slist_sort_with_data (tasklist->windows,
+                                               xfce_tasklist_button_compare,
+                                               tasklist);
+
+  gtk_widget_queue_resize (GTK_WIDGET (tasklist));
+}
+
+
+
+static GtkWidget *
+xfce_tasklist_get_panel_plugin (XfceTasklist *tasklist)
+{
+  GtkWidget *p;
+
+  panel_return_val_if_fail (XFCE_IS_TASKLIST (tasklist), NULL);
+
+  /* look in the parents for the panel plugin */
+  for (p = GTK_WIDGET (tasklist); p != NULL; p = gtk_widget_get_parent (p))
+    if (g_type_is_a (G_OBJECT_TYPE (p), XFCE_TYPE_PANEL_PLUGIN))
+      return p;
+
+  return NULL;
+}
+
+
+
+/**
+ * Wire Frame
+ **/
 #ifdef GDK_WINDOWING_X11
 static void
 xfce_tasklist_wireframe_hide (XfceTasklist *tasklist)
 {
+  GdkDisplay *dpy;
+
   panel_return_if_fail (XFCE_IS_TASKLIST (tasklist));
-  panel_return_if_fail (tasklist->wireframe_window != 0);
 
-  /* unmap the window */
-  XUnmapWindow (GDK_DISPLAY (), tasklist->wireframe_window);
+  if (tasklist->wireframe_window != 0)
+    {
+      /* unmap the window */
+      dpy = gtk_widget_get_display (GTK_WIDGET (tasklist));
+      XUnmapWindow (GDK_DISPLAY_XDISPLAY (dpy), tasklist->wireframe_window);
+    }
 }
 
 
@@ -1092,17 +1471,19 @@ xfce_tasklist_wireframe_hide (XfceTasklist *tasklist)
 static void
 xfce_tasklist_wireframe_destroy (XfceTasklist *tasklist)
 {
-  Display *dpy = GDK_DISPLAY ();
+  GdkDisplay *dpy;
 
   panel_return_if_fail (XFCE_IS_TASKLIST (tasklist));
-  panel_return_if_fail (tasklist->wireframe_window != 0);
 
-  /* unmap and destroy the window */
-  XUnmapWindow (dpy, tasklist->wireframe_window);
-  XDestroyWindow (dpy, tasklist->wireframe_window);
+  if (tasklist->wireframe_window != 0)
+    {
+      /* unmap and destroy the window */
+      dpy = gtk_widget_get_display (GTK_WIDGET (tasklist));
+      XUnmapWindow (GDK_DISPLAY_XDISPLAY (dpy), tasklist->wireframe_window);
+      XDestroyWindow (GDK_DISPLAY_XDISPLAY (dpy), tasklist->wireframe_window);
 
-  /* set to nul */
-  tasklist->wireframe_window = 0;
+      tasklist->wireframe_window = 0;
+    }
 }
 
 
@@ -1111,7 +1492,8 @@ static void
 xfce_tasklist_wireframe_update (XfceTasklist      *tasklist,
                                 XfceTasklistChild *child)
 {
-  Display              *dpy = GDK_DISPLAY ();
+  Display              *dpy;
+  GdkDisplay           *gdpy;
   gint                  x, y, width, height;
   XSetWindowAttributes  attrs;
   GC                    gc;
@@ -1124,6 +1506,9 @@ xfce_tasklist_wireframe_update (XfceTasklist      *tasklist,
   /* get the window geometry */
   wnck_window_get_geometry (child->window, &x, &y, &width, &height);
 
+  gdpy = gtk_widget_get_display (GTK_WIDGET (tasklist));
+  dpy = GDK_DISPLAY_XDISPLAY (gdpy);
+
   if (G_LIKELY (tasklist->wireframe_window != 0))
     {
       /* reposition the wireframe */
@@ -1181,29 +1566,15 @@ xfce_tasklist_wireframe_update (XfceTasklist      *tasklist,
                   width - 2 * (WIREFRAME_SIZE - 1) - 1,
                   height - 2 * (WIREFRAME_SIZE - 1) - 1);
 
-  /* free the gc */
   XFreeGC (dpy, gc);
 }
 #endif
 
 
-static void
-xfce_tasklist_sort (XfceTasklist *tasklist)
-{
-  panel_return_if_fail (XFCE_IS_TASKLIST (tasklist));
-
-  tasklist->children = g_slist_sort_with_data (tasklist->children,
-                                               xfce_tasklist_button_compare,
-                                               tasklist);
-
-  gtk_widget_queue_resize (GTK_WIDGET (tasklist));
-}
-
 
-
-/**************************************************************
- **************************************************************
- **************************************************************/
+/**
+ * Tasklist Buttons
+ **/
 static gint
 xfce_tasklist_button_compare (gconstpointer child_a,
                               gconstpointer child_b,
@@ -1212,25 +1583,73 @@ xfce_tasklist_button_compare (gconstpointer child_a,
   const XfceTasklistChild *a = child_a, *b = child_b;
   XfceTasklist            *tasklist = XFCE_TASKLIST (user_data);
   gint                     retval;
+  WnckClassGroup          *class_group_a, *class_group_b;
+  const gchar             *name_a = NULL, *name_b = NULL;
+  WnckWorkspace           *workspace_a, *workspace_b;
+  gint                     num_a, num_b;
 
-  panel_return_val_if_fail (WNCK_IS_WINDOW (a->window), 0);
-  panel_return_val_if_fail (WNCK_IS_WINDOW (b->window), 0);
+  panel_return_val_if_fail (a->type == XFCE_TASKLIST_BUTTON_TYPE_GROUP
+                            || WNCK_IS_WINDOW (a->window), 0);
+  panel_return_val_if_fail (b->type == XFCE_TASKLIST_BUTTON_TYPE_GROUP
+                            || WNCK_IS_WINDOW (b->window), 0);
 
   if (tasklist->all_workspaces)
     {
-      /* todo, keep workspaces together */
+      /* get workspace (this is slightly inefficient because the WnckWindow
+       * also stores the workspace number, not the structure, and we use that
+       * for comparing too */
+      workspace_a = a->window != NULL ? wnck_window_get_workspace (a->window) : NULL;
+      workspace_b = b->window != NULL ? wnck_window_get_workspace (b->window) : NULL;
+
+      /* skip this if windows are in same worspace, or both pinned (== NULL) */
+      if (workspace_a != workspace_b)
+        {
+          /* NULL means the window is pinned */
+          if (workspace_a == NULL)
+            workspace_a = wnck_screen_get_active_workspace (tasklist->screen);
+          if (workspace_b == NULL)
+            workspace_b = wnck_screen_get_active_workspace (tasklist->screen);
+
+          /* compare by workspace number */
+          num_a = wnck_workspace_get_number (workspace_a);
+          num_b = wnck_workspace_get_number (workspace_b);
+          if (num_a != num_b)
+            return num_a - num_b;
+        }
     }
 
   if (tasklist->sort_order == XFCE_TASKLIST_SORT_ORDER_GROUP_TITLE
       || tasklist->sort_order == XFCE_TASKLIST_SORT_ORDER_GROUP_TIMESTAMP)
     {
-      panel_return_val_if_fail (WNCK_IS_CLASS_GROUP (a->class_group), 0);
-      panel_return_val_if_fail (WNCK_IS_CLASS_GROUP (b->class_group), 0);
+      /* compare by class group names */
+      class_group_a = a->class_group;
+      class_group_b = b->class_group;
 
-      retval = strcasecmp (wnck_class_group_get_name (a->class_group),
-                           wnck_class_group_get_name (b->class_group));
-      if (retval != 0)
-        return retval;
+      /* skip this if windows are in same group (or both NULL) */
+      if (class_group_a != class_group_b)
+        {
+          /* get the group name if available */
+          if (G_LIKELY (class_group_a != NULL))
+            name_a = wnck_class_group_get_name (class_group_a);
+          if (G_LIKELY (class_group_b != NULL))
+            name_b = wnck_class_group_get_name (class_group_b);
+
+          /* if there is no class group name, use the window name */
+          if (G_UNLIKELY (exo_str_is_empty (name_a)))
+            name_a = wnck_window_get_name (a->window);
+          if (G_UNLIKELY (exo_str_is_empty (name_b)))
+            name_b = wnck_window_get_name (b->window);
+
+          retval = strcasecmp (name_a, name_b);
+          if (retval != 0)
+            return retval;
+        }
+      else if (a->type != b->type)
+        {
+          /* put the group in front of the other window buttons
+           * with the same group */
+          return b->type - a->type;
+        }
     }
 
   if (tasklist->sort_order == XFCE_TASKLIST_SORT_ORDER_TIMESTAMP
@@ -1240,107 +1659,134 @@ xfce_tasklist_button_compare (gconstpointer child_a,
     }
   else
     {
-      return strcasecmp (wnck_window_get_name (a->window),
-                         wnck_window_get_name (b->window));
+      if (a->window != NULL)
+        name_a = wnck_window_get_name (a->window);
+      else if (a->class_group != NULL)
+        name_a = wnck_class_group_get_name (a->class_group);
+      else
+        name_a = "";
+
+      if (b->window != NULL)
+        name_b = wnck_window_get_name (b->window);
+      else if (b->class_group != NULL)
+        name_b = wnck_class_group_get_name (b->class_group);
+      else
+        name_b = "";
+
+      return strcasecmp (name_a, name_b);
     }
 }
 
 
 
 static void
-tasklist_button_icon_changed (WnckWindow        *window,
-                              XfceTasklistChild *child)
+xfce_tasklist_button_icon_changed (WnckWindow        *window,
+                                   XfceTasklistChild *child)
 {
-  GdkPixbuf *pixbuf;
-  GdkPixbuf *lucent;
+  GdkPixbuf    *pixbuf;
+  GdkPixbuf    *lucent = NULL;
+  XfceTasklist *tasklist = child->tasklist;
 
-  /* leave when lucency is set to 0 */
-  if (child->tasklist->minimized_icon_lucency == 0)
+  panel_return_if_fail (XFCE_IS_TASKLIST (tasklist));
+  panel_return_if_fail (XFCE_IS_PANEL_IMAGE (child->icon));
+  panel_return_if_fail (WNCK_IS_WINDOW (window));
+  panel_return_if_fail (child->window == window);
+
+  /* 0 means icons are disabled */
+  if (tasklist->minimized_icon_lucency == 0)
     return;
 
-  /* get the application icon */
-  if (child->tasklist->show_labels)
+  /* get the window icon */
+  if (tasklist->show_labels)
     pixbuf = wnck_window_get_mini_icon (window);
   else
     pixbuf = wnck_window_get_icon (window);
 
   /* leave when there is no valid pixbuf */
   if (G_UNLIKELY (pixbuf == NULL))
-    return;
+    {
+      xfce_panel_image_clear (XFCE_PANEL_IMAGE (child->icon));
+      return;
+    }
 
   /* create a spotlight version of the icon when minimized */
-  if (child->tasklist->only_minimized == FALSE
-      && child->tasklist->minimized_icon_lucency < 100
+  if (!tasklist->only_minimized
+      && tasklist->minimized_icon_lucency < 100
       && wnck_window_is_minimized (window))
     {
-      /* create lightened version */
-      lucent = exo_gdk_pixbuf_lucent (pixbuf, child->tasklist->minimized_icon_lucency);
-      if (G_LIKELY (lucent != NULL))
-        {
-          /* set the button icon */
-          xfce_panel_image_set_from_pixbuf (XFCE_PANEL_IMAGE (child->icon), lucent);
-
-          /* release the pixbuf */
-          g_object_unref (G_OBJECT (lucent));
-        }
-    }
-  else
-    {
-      /* set the button icon */
-      xfce_panel_image_set_from_pixbuf (XFCE_PANEL_IMAGE (child->icon), pixbuf);
+      lucent = exo_gdk_pixbuf_lucent (pixbuf, tasklist->minimized_icon_lucency);
+      if (G_UNLIKELY (lucent != NULL))
+        pixbuf = lucent;
     }
+
+  xfce_panel_image_set_from_pixbuf (XFCE_PANEL_IMAGE (child->icon), pixbuf);
+
+  if (lucent != NULL && lucent != pixbuf)
+    g_object_unref (G_OBJECT (lucent));
 }
 
 
 
 static void
-tasklist_button_name_changed (WnckWindow        *window,
-                              XfceTasklistChild *child)
+xfce_tasklist_button_name_changed (WnckWindow        *window,
+                                   XfceTasklistChild *child)
 {
   const gchar *name;
   gchar       *label = NULL;
 
-  panel_return_if_fail (child->window == window);
-
-  /* get the window name */
-  name = wnck_window_get_name (window);
+  panel_return_if_fail (window == NULL || child->window == window);
+  panel_return_if_fail (WNCK_IS_WINDOW (child->window));
+  panel_return_if_fail (XFCE_IS_TASKLIST (child->tasklist));
 
-  /* set the tooltip */
+  name = wnck_window_get_name (child->window);
   gtk_widget_set_tooltip_text (GTK_WIDGET (child->button), name);
 
   /* create the button label */
   if (!child->tasklist->only_minimized
-      && wnck_window_is_minimized (window))
+      && wnck_window_is_minimized (child->window))
     name = label = g_strdup_printf ("[%s]", name);
-  else if (wnck_window_is_shaded (window))
+  else if (wnck_window_is_shaded (child->window))
     name = label = g_strdup_printf ("=%s=", name);
 
-  /* set the label */
   gtk_label_set_text (GTK_LABEL (child->label), name);
 
-  /* cleanup */
   g_free (label);
 
-  /* sort the tasklist */
-  xfce_tasklist_sort (child->tasklist);
+  /* if window is null, we have not inserted the button the in
+   * tasklist, so no need to sort, because we insert with sorting */
+  if (window != NULL)
+    xfce_tasklist_sort (child->tasklist);
 }
 
 
 
 static void
-tasklist_button_state_changed (WnckWindow        *window,
-                               WnckWindowState    changed_state,
-                               WnckWindowState    new_state,
-                               XfceTasklistChild *child)
+xfce_tasklist_button_state_changed (WnckWindow        *window,
+                                    WnckWindowState    changed_state,
+                                    WnckWindowState    new_state,
+                                    XfceTasklistChild *child)
 {
-  gboolean blink;
+  gboolean    blink;
+  WnckScreen *screen;
 
+  panel_return_if_fail (WNCK_IS_WINDOW (window));
   panel_return_if_fail (child->window == window);
+  panel_return_if_fail (XFCE_IS_TASKLIST (child->tasklist));
+
+  /* remove if the new state is hidding the window from the tasklist */
+  if (PANEL_HAS_FLAG (changed_state, WNCK_WINDOW_STATE_SKIP_TASKLIST))
+    {
+      screen = wnck_window_get_screen (window);
+      xfce_tasklist_window_removed (screen, window, child->tasklist);
+      xfce_tasklist_window_added (screen, window, child->tasklist);
+
+      return;
+    }
 
   /* update the button name */
   if (PANEL_HAS_FLAG (changed_state, WNCK_WINDOW_STATE_SHADED | WNCK_WINDOW_STATE_MINIMIZED)
       && !child->tasklist->only_minimized)
-    tasklist_button_name_changed (window, child);
+    xfce_tasklist_button_name_changed (window, child);
 
   /* update the button icon if needed */
   if (PANEL_HAS_FLAG (changed_state, WNCK_WINDOW_STATE_MINIMIZED))
@@ -1355,7 +1801,7 @@ tasklist_button_state_changed (WnckWindow        *window,
       else
         {
           /* update the icon (lucent) */
-          tasklist_button_icon_changed (window, child);
+          xfce_tasklist_button_icon_changed (window, child);
         }
     }
 
@@ -1374,44 +1820,47 @@ tasklist_button_state_changed (WnckWindow        *window,
 
 
 static void
-tasklist_button_workspace_changed (WnckWindow        *window,
-                                   XfceTasklistChild *child)
+xfce_tasklist_button_workspace_changed (WnckWindow        *window,
+                                        XfceTasklistChild *child)
 {
-  /* leave when we show application from all workspaces */
+  panel_return_if_fail (child->window == window);
+  panel_return_if_fail (XFCE_IS_TASKLIST (child->tasklist));
+
   if (child->tasklist->all_workspaces)
-    {
-      /* sort the tasklist */
-      xfce_tasklist_sort (child->tasklist);
-    }
-  else
-    {
-      /* hide the button */
-      gtk_widget_hide (child->button);
-    }
+    xfce_tasklist_sort (child->tasklist);
+  else if (!wnck_window_is_pinned (child->window))
+    gtk_widget_hide (child->button);
 }
 
 
+
 #ifdef GDK_WINDOWING_X11
 static void
-tasklist_button_geometry_changed (WnckWindow        *window,
-                                  XfceTasklistChild *child)
+xfce_tasklist_button_geometry_changed (WnckWindow        *window,
+                                       XfceTasklistChild *child)
 {
-  /* update the wireframe */
+  panel_return_if_fail (child->window == window);
+  panel_return_if_fail (XFCE_IS_TASKLIST (child->tasklist));
+
   xfce_tasklist_wireframe_update (child->tasklist, child);
 }
 
 
 
 static gboolean
-tasklist_button_leave_notify_event (GtkWidget *button,
-                                    GdkEventCrossing *event,
-                                    XfceTasklistChild *child)
+xfce_tasklist_button_leave_notify_event (GtkWidget         *button,
+                                         GdkEventCrossing  *event,
+                                         XfceTasklistChild *child)
 {
+  panel_return_val_if_fail (XFCE_IS_TASKLIST (child->tasklist), FALSE);
+  panel_return_val_if_fail (child->type == XFCE_TASKLIST_BUTTON_TYPE_WINDOW
+                            || child->type == XFCE_TASKLIST_BUTTON_TYPE_MENU, FALSE);
+
   /* disconnect signals */
   g_signal_handlers_disconnect_by_func (button,
-      tasklist_button_leave_notify_event, child);
+      xfce_tasklist_button_leave_notify_event, child);
   g_signal_handlers_disconnect_by_func (child->window,
-      tasklist_button_geometry_changed, child);
+      xfce_tasklist_button_geometry_changed, child);
 
   /* unmap and destroy the wireframe window */
   xfce_tasklist_wireframe_hide (child->tasklist);
@@ -1423,25 +1872,31 @@ tasklist_button_leave_notify_event (GtkWidget *button,
 
 
 static gboolean
-tasklist_button_enter_notify_event (GtkWidget         *button,
-                                    GdkEventCrossing  *event,
-                                    XfceTasklistChild *child)
+xfce_tasklist_button_enter_notify_event (GtkWidget         *button,
+                                         GdkEventCrossing  *event,
+                                         XfceTasklistChild *child)
 {
+  panel_return_val_if_fail (XFCE_IS_TASKLIST (child->tasklist), FALSE);
+  panel_return_val_if_fail (child->type == XFCE_TASKLIST_BUTTON_TYPE_WINDOW
+                            || child->type == XFCE_TASKLIST_BUTTON_TYPE_MENU, FALSE);
+  panel_return_val_if_fail (GTK_IS_WIDGET (button), FALSE);
+  panel_return_val_if_fail (WNCK_IS_WINDOW (child->window), FALSE);
+
 #ifdef GDK_WINDOWING_X11
   /* leave when there is nothing to do */
-  if (child->tasklist->show_wireframes == FALSE)
+  if (!child->tasklist->show_wireframes)
     return FALSE;
 
-  /* create wireframe */
+  /* show wireframe for the child */
   xfce_tasklist_wireframe_update (child->tasklist, child);
 
   /* connect signal to destroy the window when the user leaves the button */
   g_signal_connect (G_OBJECT (button), "leave-notify-event",
-      G_CALLBACK (tasklist_button_leave_notify_event), child);
+      G_CALLBACK (xfce_tasklist_button_leave_notify_event), child);
 
-  /* monitor geometry changes */
+  /* watch geometry changes */
   g_signal_connect (G_OBJECT (child->window), "geometry-changed",
-      G_CALLBACK (tasklist_button_geometry_changed), child);
+      G_CALLBACK (xfce_tasklist_button_geometry_changed), child);
 #endif
 
   return FALSE;
@@ -1450,22 +1905,36 @@ tasklist_button_enter_notify_event (GtkWidget         *button,
 
 
 static gboolean
-tasklist_button_button_press_event (GtkWidget         *button,
-                                    GdkEventButton    *event,
-                                    XfceTasklistChild *child)
+xfce_tasklist_button_button_press_event (GtkWidget         *button,
+                                         GdkEventButton    *event,
+                                         XfceTasklistChild *child)
 {
   GtkWidget     *menu, *panel_plugin;
   WnckWorkspace *workspace;
 
-  if (event->type == GDK_BUTTON_PRESS
-      && xfce_taskbar_is_locked ())
+  panel_return_val_if_fail (XFCE_IS_TASKLIST (child->tasklist), FALSE);
+  panel_return_val_if_fail (child->type == XFCE_TASKLIST_BUTTON_TYPE_WINDOW
+
+                            || child->type == XFCE_TASKLIST_BUTTON_TYPE_MENU, FALSE);
+  if (event->type != GDK_BUTTON_PRESS
+      || xfce_taskbar_is_locked (child->tasklist))
     return FALSE;
 
+  /* send the event to the panel plugin if control is pressed */
+  if (PANEL_HAS_FLAG (event->state, GDK_CONTROL_MASK))
+    {
+      /* send the event to the panel plugin */
+      panel_plugin = xfce_tasklist_get_panel_plugin (child->tasklist);
+      if (G_LIKELY (panel_plugin != NULL))
+        gtk_widget_event (panel_plugin, (GdkEvent *) event);
+
+      return TRUE;
+    }
+
   if (event->button == 1)
     {
       if (wnck_window_is_active (child->window))
         {
-          /* minimize the window */
           wnck_window_minimize (child->window);
         }
       else
@@ -1486,7 +1955,6 @@ tasklist_button_button_press_event (GtkWidget         *button,
                 wnck_workspace_activate (workspace, event->time - 1);
             }
 
-          /* active the window */
           wnck_window_activate_transient (child->window, event->time);
         }
 
@@ -1494,17 +1962,15 @@ tasklist_button_button_press_event (GtkWidget         *button,
     }
   else if (event->button == 3)
     {
-      panel_plugin = gtk_widget_get_parent (GTK_WIDGET (child->tasklist));
-      panel_plugin = gtk_widget_get_parent (panel_plugin);
-      panel_return_val_if_fail (XFCE_IS_PANEL_PLUGIN (panel_plugin), FALSE);
-
       menu = wnck_action_menu_new (child->window);
-      gtk_menu_attach_to_widget (GTK_MENU (menu), button, NULL);
       g_signal_connect (G_OBJECT (menu), "selection-done",
-                        G_CALLBACK (gtk_widget_destroy), NULL);
+          G_CALLBACK (gtk_widget_destroy), NULL);
+
+      gtk_menu_attach_to_widget (GTK_MENU (menu), button, NULL);
       gtk_menu_popup (GTK_MENU (menu), NULL, NULL,
                       xfce_panel_plugin_position_menu,
-                      panel_plugin, event->button,
+                      xfce_tasklist_get_panel_plugin (child->tasklist),
+                      event->button,
                       event->time);
 
       return TRUE;
@@ -1516,22 +1982,98 @@ tasklist_button_button_press_event (GtkWidget         *button,
 
 
 static void
-xfce_tasklist_button_new (XfceTasklistChild *child)
+xfce_tasklist_button_enter_notify_event_disconnected (gpointer  data,
+                                                      GClosure *closure)
 {
-  WnckWindow *window = child->window;
+  XfceTasklistChild *child = data;
 
-  panel_return_if_fail (XFCE_IS_TASKLIST (child->tasklist));
   panel_return_if_fail (WNCK_IS_WINDOW (child->window));
-  panel_return_if_fail (child->button == NULL);
 
-  /* create the application button */
+  /* we need to detach the geometry watch because that is connected
+   * to the window we proxy and thus not disconnected when the
+   * proxy dies */
+  g_signal_handlers_disconnect_by_func (child->window,
+      xfce_tasklist_button_geometry_changed, child);
+}
+
+
+
+static GtkWidget *
+xfce_tasklist_button_proxy_menu_item (XfceTasklistChild *child)
+{
+  GtkWidget    *mi;
+  GtkWidget    *image;
+  GtkWidget    *label;
+  XfceTasklist *tasklist = child->tasklist;
+
+  panel_return_val_if_fail (XFCE_IS_TASKLIST (child->tasklist), NULL);
+  panel_return_val_if_fail (child->type == XFCE_TASKLIST_BUTTON_TYPE_MENU, NULL);
+  panel_return_val_if_fail (GTK_IS_LABEL (child->label), NULL);
+
+  mi = gtk_image_menu_item_new ();
+  exo_binding_new (G_OBJECT (child->label), "label", G_OBJECT (mi), "label");
+  exo_binding_new (G_OBJECT (child->label), "label", G_OBJECT (mi), "tooltip-text");
+
+  label = gtk_bin_get_child (GTK_BIN (mi));
+  panel_return_val_if_fail (GTK_IS_LABEL (label), NULL);
+  gtk_label_set_max_width_chars (GTK_LABEL (label), tasklist->menu_max_width_chars);
+  gtk_label_set_ellipsize (GTK_LABEL (label), tasklist->ellipsize_mode);
+
+  if (G_LIKELY (tasklist->menu_icon_size > 0))
+    {
+      image = xfce_panel_image_new ();
+      gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (mi), image);
+      g_object_set (G_OBJECT (image), "size", tasklist->menu_icon_size, NULL);
+      exo_binding_new (G_OBJECT (child->icon), "pixbuf", G_OBJECT (image), "pixbuf");
+      gtk_widget_show (image);
+    }
+
+  g_signal_connect_data (G_OBJECT (mi), "enter-notify-event",
+      G_CALLBACK (xfce_tasklist_button_enter_notify_event), child,
+      xfce_tasklist_button_enter_notify_event_disconnected, 0);
+  g_signal_connect (G_OBJECT (mi), "button-press-event",
+      G_CALLBACK (xfce_tasklist_button_button_press_event), child);
+
+  /* TODO bold labels for urgent windows */
+  /* TODO item dnd */
+
+  return mi;
+}
+
+
+
+static XfceTasklistChild *
+xfce_tasklist_button_new (WnckWindow   *window,
+                          XfceTasklist *tasklist)
+{
+  XfceTasklistChild *child;
+  static guint       unique_id_counter = 0;
+
+  panel_return_val_if_fail (XFCE_IS_TASKLIST (tasklist), NULL);
+  panel_return_val_if_fail (WNCK_IS_WINDOW (window), NULL);
+
+  /* avoid integer overflows */
+  if (G_UNLIKELY (unique_id_counter >= G_MAXUINT))
+    unique_id_counter = 0;
+
+  child = g_slice_new0 (XfceTasklistChild);
+  child->type = XFCE_TASKLIST_BUTTON_TYPE_WINDOW;
+  child->tasklist = tasklist;
+  child->window = window;
+  child->unique_id = unique_id_counter++;
+  child->class_group = wnck_window_get_class_group (window);
+
+  /* create the window button */
   child->button = xfce_arrow_button_new (GTK_ARROW_NONE);
+  gtk_widget_set_parent (child->button, GTK_WIDGET (tasklist));
   gtk_button_set_relief (GTK_BUTTON (child->button),
                          child->tasklist->button_relief);
+
+  /* note that the same signals should be in the proxy menu item too */
   g_signal_connect (G_OBJECT (child->button), "enter-notify-event",
-      G_CALLBACK (tasklist_button_enter_notify_event), child);
+      G_CALLBACK (xfce_tasklist_button_enter_notify_event), child);
   g_signal_connect (G_OBJECT (child->button), "button-press-event",
-      G_CALLBACK (tasklist_button_button_press_event), child);
+      G_CALLBACK (xfce_tasklist_button_button_press_event), child);
 
   child->box = xfce_hvbox_new (child->tasklist->horizontal ?
       GTK_ORIENTATION_HORIZONTAL : GTK_ORIENTATION_VERTICAL, FALSE, 6);
@@ -1557,29 +2099,469 @@ xfce_tasklist_button_new (XfceTasklistChild *child)
     {
       gtk_label_set_angle (GTK_LABEL (child->label), 270);
       gtk_misc_set_alignment (GTK_MISC (child->label), 0.50, 0.00);
+      /* TODO can we already ellipsize here? */
     }
 
-  /* don't show the icon if we're in iconbox style */
+  /* don't show the label if we're in iconbox style */
   if (child->tasklist->show_labels)
     gtk_widget_show (child->label);
 
   /* monitor window changes */
   g_signal_connect (G_OBJECT (window), "icon-changed",
-      G_CALLBACK (tasklist_button_icon_changed), child);
+      G_CALLBACK (xfce_tasklist_button_icon_changed), child);
   g_signal_connect (G_OBJECT (window), "name-changed",
-      G_CALLBACK (tasklist_button_name_changed), child);
+      G_CALLBACK (xfce_tasklist_button_name_changed), child);
   g_signal_connect (G_OBJECT (window), "state-changed",
-      G_CALLBACK (tasklist_button_state_changed), child);
+      G_CALLBACK (xfce_tasklist_button_state_changed), child);
   g_signal_connect (G_OBJECT (window), "workspace-changed",
-      G_CALLBACK (tasklist_button_workspace_changed), child);
+      G_CALLBACK (xfce_tasklist_button_workspace_changed), child);
+
+  /* poke functions */
+  xfce_tasklist_button_icon_changed (window, child);
+  xfce_tasklist_button_name_changed (NULL, child);
+
+  /* insert */
+  tasklist->windows = g_slist_insert_sorted_with_data (tasklist->windows, child,
+                                                        xfce_tasklist_button_compare,
+                                                        tasklist);
+
+  return child;
+}
+
+
+
+/**
+ * Group Buttons
+ **/
+static void
+xfce_tasklist_group_button_menu_minimize_all (XfceTasklistChild *child)
+{
+  GSList            *li;
+  XfceTasklistChild *win_child;
+
+  panel_return_if_fail (child->type == XFCE_TASKLIST_BUTTON_TYPE_GROUP);
+  panel_return_if_fail (WNCK_IS_CLASS_GROUP (child->class_group));
+
+  for (li = child->windows; li != NULL; li = li->next)
+    {
+      win_child = li->data;
+      panel_return_if_fail (win_child->type == XFCE_TASKLIST_BUTTON_TYPE_WINDOW);
+      panel_return_if_fail (WNCK_IS_WINDOW (win_child->window));
+      wnck_window_minimize (win_child->window);
+    }
+}
+
+
+
+static void
+xfce_tasklist_group_button_menu_unminimize_all (XfceTasklistChild *child)
+{
+  GSList            *li;
+  XfceTasklistChild *win_child;
+
+  panel_return_if_fail (child->type == XFCE_TASKLIST_BUTTON_TYPE_GROUP);
+  panel_return_if_fail (WNCK_IS_CLASS_GROUP (child->class_group));
+
+  for (li = child->windows; li != NULL; li = li->next)
+    {
+      win_child = li->data;
+      panel_return_if_fail (win_child->type == XFCE_TASKLIST_BUTTON_TYPE_WINDOW);
+      panel_return_if_fail (WNCK_IS_WINDOW (win_child->window));
+      wnck_window_unminimize (win_child->window, gtk_get_current_event_time ());
+    }
+}
+
+
+
+static void
+xfce_tasklist_group_button_menu_maximize_all (XfceTasklistChild *child)
+{
+  GSList            *li;
+  XfceTasklistChild *win_child;
+
+  panel_return_if_fail (child->type == XFCE_TASKLIST_BUTTON_TYPE_GROUP);
+  panel_return_if_fail (WNCK_IS_CLASS_GROUP (child->class_group));
+
+  for (li = child->windows; li != NULL; li = li->next)
+    {
+      win_child = li->data;
+      panel_return_if_fail (win_child->type == XFCE_TASKLIST_BUTTON_TYPE_WINDOW);
+      panel_return_if_fail (WNCK_IS_WINDOW (win_child->window));
+      wnck_window_maximize (win_child->window);
+    }
+}
+
+
+
+static void
+xfce_tasklist_group_button_menu_unmaximize_all (XfceTasklistChild *child)
+{
+  GSList            *li;
+  XfceTasklistChild *win_child;
+
+  panel_return_if_fail (child->type == XFCE_TASKLIST_BUTTON_TYPE_GROUP);
+  panel_return_if_fail (WNCK_IS_CLASS_GROUP (child->class_group));
+
+  for (li = child->windows; li != NULL; li = li->next)
+    {
+      win_child = li->data;
+      panel_return_if_fail (win_child->type == XFCE_TASKLIST_BUTTON_TYPE_WINDOW);
+      panel_return_if_fail (WNCK_IS_WINDOW (win_child->window));
+      wnck_window_unmaximize (win_child->window);
+    }
+}
+
+
+
+static void
+xfce_tasklist_group_button_menu_close_all (XfceTasklistChild *child)
+{
+  GSList            *li;
+  XfceTasklistChild *win_child;
+
+  panel_return_if_fail (WNCK_IS_CLASS_GROUP (child->class_group));
+
+  for (li = child->windows; li != NULL; li = li->next)
+    {
+      win_child = li->data;
+      panel_return_if_fail (win_child->type == XFCE_TASKLIST_BUTTON_TYPE_WINDOW);
+      panel_return_if_fail (WNCK_IS_WINDOW (win_child->window));
+      wnck_window_close (win_child->window, gtk_get_current_event_time ());
+    }
+}
+
+
+
+static GtkWidget *
+xfce_tasklist_group_button_menu (XfceTasklistChild *child,
+                                 gboolean           action_menu_entries)
+{
+  GSList            *li;
+  XfceTasklistChild *win_child;
+  GtkWidget         *mi;
+  GtkWidget         *menu;
+  GtkWidget         *image;
+
+  panel_return_val_if_fail (XFCE_IS_TASKLIST (child->tasklist), NULL);
+  panel_return_val_if_fail (WNCK_IS_CLASS_GROUP (child->class_group), NULL);
+
+  menu = gtk_menu_new ();
+
+  for (li = child->windows; li != NULL; li = li->next)
+    {
+      win_child = li->data;
+      panel_return_val_if_fail (win_child->type == XFCE_TASKLIST_BUTTON_TYPE_WINDOW, NULL);
+
+      mi = xfce_tasklist_button_proxy_menu_item (win_child);
+      gtk_menu_shell_append (GTK_MENU_SHELL (menu), mi);
+      gtk_widget_show (mi);
+
+      if (action_menu_entries)
+        gtk_menu_item_set_submenu (GTK_MENU_ITEM (mi),
+            wnck_action_menu_new (win_child->window));
+    }
+
+  if (action_menu_entries)
+    {
+      mi = gtk_separator_menu_item_new ();
+      gtk_menu_shell_append (GTK_MENU_SHELL (menu), mi);
+      gtk_widget_show (mi);
+
+      mi = gtk_image_menu_item_new_with_mnemonic (_("Mi_nimize All"));
+      gtk_menu_shell_append (GTK_MENU_SHELL (menu), mi);
+      g_signal_connect_swapped (G_OBJECT (mi), "activate",
+          G_CALLBACK (xfce_tasklist_group_button_menu_minimize_all), child);
+      gtk_widget_show (mi);
+      image = gtk_image_new_from_stock ("wnck-stock-minimize", GTK_ICON_SIZE_MENU);
+      gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (mi), image);
+      gtk_widget_show (image);
+
+      mi =  gtk_image_menu_item_new_with_mnemonic (_("Un_minimize All"));
+      gtk_menu_shell_append (GTK_MENU_SHELL (menu), mi);
+      g_signal_connect_swapped (G_OBJECT (mi), "activate",
+          G_CALLBACK (xfce_tasklist_group_button_menu_unminimize_all), child);
+      gtk_widget_show (mi);
+
+      mi = gtk_image_menu_item_new_with_mnemonic (_("Ma_ximize All"));
+      gtk_menu_shell_append (GTK_MENU_SHELL (menu), mi);
+      g_signal_connect_swapped (G_OBJECT (mi), "activate",
+          G_CALLBACK (xfce_tasklist_group_button_menu_maximize_all), child);
+      gtk_widget_show (mi);
+      image = gtk_image_new_from_stock ("wnck-stock-maximize", GTK_ICON_SIZE_MENU);
+      gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (mi), image);
+      gtk_widget_show (image);
+
+      mi =  gtk_image_menu_item_new_with_mnemonic (_("_Unmaximize All"));
+      gtk_menu_shell_append (GTK_MENU_SHELL (menu), mi);
+      g_signal_connect_swapped (G_OBJECT (mi), "activate",
+          G_CALLBACK (xfce_tasklist_group_button_menu_unmaximize_all), child);
+      gtk_widget_show (mi);
+
+      mi = gtk_separator_menu_item_new ();
+      gtk_menu_shell_append (GTK_MENU_SHELL (menu), mi);
+      gtk_widget_show (mi);
+
+      mi = gtk_image_menu_item_new_with_mnemonic(_("_Close All"));
+      gtk_menu_shell_append (GTK_MENU_SHELL (menu), mi);
+      g_signal_connect_swapped (G_OBJECT (mi), "activate",
+          G_CALLBACK (xfce_tasklist_group_button_menu_close_all), child);
+      gtk_widget_show (mi);
+
+      image = gtk_image_new_from_stock ("wnck-stock-delete", GTK_ICON_SIZE_MENU);
+      gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (mi), image);
+      gtk_widget_show (image);
+    }
+
+  return menu;
+}
+
+
+
+static void
+xfce_tasklist_group_button_menu_destroy (GtkWidget         *menu,
+                                         XfceTasklistChild *child)
+{
+  panel_return_if_fail (XFCE_IS_TASKLIST (child->tasklist));
+  panel_return_if_fail (GTK_IS_TOGGLE_BUTTON (child->button));
+  panel_return_if_fail (GTK_IS_WIDGET (menu));
+
+  gtk_widget_destroy (menu);
+
+  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (child->button), FALSE);
+
+#ifdef GDK_WINDOWING_X11
+  /* make sure the wireframe is hidden */
+  xfce_tasklist_wireframe_hide (child->tasklist);
+#endif
+}
+
+
+
+static gboolean
+xfce_tasklist_group_button_button_press_event (GtkWidget         *button,
+                                               GdkEventButton    *event,
+                                               XfceTasklistChild *child)
+{
+  GtkWidget *panel_plugin;
+  GtkWidget *menu;
+
+  panel_return_val_if_fail (XFCE_IS_TASKLIST (child->tasklist), FALSE);
+  panel_return_val_if_fail (child->type == XFCE_TASKLIST_BUTTON_TYPE_GROUP, FALSE);
+
+  if (event->type != GDK_BUTTON_PRESS
+      || xfce_taskbar_is_locked (child->tasklist))
+    return FALSE;
+
+  /* send the event to the panel plugin if control is pressed */
+  if (PANEL_HAS_FLAG (event->state, GDK_CONTROL_MASK))
+    {
+      /* send the event to the panel plugin */
+      panel_plugin = xfce_tasklist_get_panel_plugin (child->tasklist);
+      if (G_LIKELY (panel_plugin != NULL))
+        gtk_widget_event (panel_plugin, (GdkEvent *) event);
+
+      return TRUE;
+    }
+
+  if (event->button == 1 || event->button == 3)
+    {
+      menu = xfce_tasklist_group_button_menu (child, event->button == 3);
+      g_signal_connect (G_OBJECT (menu), "selection-done",
+          G_CALLBACK (xfce_tasklist_group_button_menu_destroy), child);
+
+      gtk_menu_attach_to_widget (GTK_MENU (menu), button, NULL);
+      gtk_menu_popup (GTK_MENU (menu), NULL, NULL,
+                      xfce_panel_plugin_position_menu,
+                      xfce_tasklist_get_panel_plugin (child->tasklist),
+                      event->button,
+                      event->time);
+
+      return TRUE;
+    }
+
+  return FALSE;
+}
+
+
+
+static void
+xfce_tasklist_group_button_name_changed (WnckClassGroup    *class_group,
+                                         XfceTasklistChild *child)
+{
+  const gchar *name;
+  gchar       *label;
+
+  panel_return_if_fail (class_group == NULL || child->class_group == class_group);
+  panel_return_if_fail (XFCE_IS_TASKLIST (child->tasklist));
+  panel_return_if_fail (WNCK_IS_CLASS_GROUP (child->class_group));
+
+  /* create the button label */
+  name = wnck_class_group_get_name (child->class_group);
+  if (!exo_str_is_empty (name))
+    label = g_strdup_printf ("%s (%d)", name, 0);
+  else
+    label = g_strdup_printf ("(%d)", 0);
+
+  gtk_label_set_text (GTK_LABEL (child->label), label);
+
+  g_free (label);
+
+  /* don't sort if there is no need to update the sorting (ie. only number
+   * of windows is changed or button is not inserted in the tasklist yet */
+  if (class_group != NULL)
+    xfce_tasklist_sort (child->tasklist);
+}
+
+
+
+static void
+xfce_tasklist_group_button_icon_changed (WnckClassGroup    *class_group,
+                                         XfceTasklistChild *child)
+{
+  GdkPixbuf *pixbuf;
+
+  panel_return_if_fail (XFCE_IS_TASKLIST (child->tasklist));
+  panel_return_if_fail (WNCK_IS_CLASS_GROUP (class_group));
+  panel_return_if_fail (child->class_group == class_group);
+  panel_return_if_fail (XFCE_IS_PANEL_IMAGE (child->icon));
+
+  /* 0 means icons are disabled, athough the grouping button does
+   * not use lucient icons */
+  if (child->tasklist->minimized_icon_lucency == 0)
+    return;
+
+  /* get the class group icon */
+  if (child->tasklist->show_labels)
+    pixbuf = wnck_class_group_get_mini_icon (class_group);
+  else
+    pixbuf = wnck_class_group_get_icon (class_group);
+
+  if (G_LIKELY (pixbuf != NULL))
+    xfce_panel_image_set_from_pixbuf (XFCE_PANEL_IMAGE (child->icon), pixbuf);
+  else
+    xfce_panel_image_clear (XFCE_PANEL_IMAGE (child->icon));
+}
+
+
+
+static void
+xfce_tasklist_group_button_remove (XfceTasklist   *tasklist,
+                                   WnckClassGroup *class_group)
+{
+  XfceTasklistChild *child;
+  GSList            *li;
+  guint              n;
+
+  panel_return_if_fail (XFCE_IS_TASKLIST (tasklist));
+  panel_return_if_fail (WNCK_IS_CLASS_GROUP (class_group));
+
+  for (li = tasklist->windows; li != NULL; li = li->next)
+    {
+      child = li->data;
+
+      if (child->type == XFCE_TASKLIST_BUTTON_TYPE_GROUP
+          && child->class_group == class_group)
+        {
+          /* disconnect from all the group watch functions */
+          n = g_signal_handlers_disconnect_matched (G_OBJECT (class_group),
+              G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, child);
+          panel_return_if_fail (n == 2);
+
+          /* destroy the button, this will free the child data in the
+           * container remove function */
+          gtk_widget_destroy (child->button);
+
+          break;
+        }
+    }
+
+  return;
+  /* TODO remove */
+  xfce_tasklist_group_button_new(class_group, tasklist);
+}
+
+
+
+static XfceTasklistChild *
+xfce_tasklist_group_button_new (WnckClassGroup *class_group,
+                                XfceTasklist   *tasklist)
+{
+  XfceTasklistChild *child;
+
+  panel_return_val_if_fail (XFCE_IS_TASKLIST (tasklist), NULL);
+  panel_return_val_if_fail (WNCK_IS_CLASS_GROUP (class_group), NULL);
+
+  child = g_slice_new0 (XfceTasklistChild);
+  child->type = XFCE_TASKLIST_BUTTON_TYPE_GROUP;
+  child->tasklist = tasklist;
+  child->class_group = class_group;
+
+  /* create the window button */
+  child->button = xfce_arrow_button_new (GTK_ARROW_UP);
+  gtk_widget_set_parent (child->button, GTK_WIDGET (tasklist));
+  gtk_button_set_relief (GTK_BUTTON (child->button),
+                         child->tasklist->button_relief);
+
+  /* note that the same signals should be in the proxy menu item too */
+  g_signal_connect (G_OBJECT (child->button), "button-press-event",
+      G_CALLBACK (xfce_tasklist_group_button_button_press_event), child);
+
+  child->box = xfce_hvbox_new (child->tasklist->horizontal ?
+      GTK_ORIENTATION_HORIZONTAL : GTK_ORIENTATION_VERTICAL, FALSE, 6);
+  gtk_container_add (GTK_CONTAINER (child->button), child->box);
+  gtk_widget_show (child->box);
+
+  child->icon = xfce_panel_image_new ();
+  if (child->tasklist->show_labels)
+    gtk_box_pack_start (GTK_BOX (child->box), child->icon, FALSE, TRUE, 0);
+  else
+    gtk_box_pack_start (GTK_BOX (child->box), child->icon, TRUE, TRUE, 0);
+  if (child->tasklist->minimized_icon_lucency > 0)
+    gtk_widget_show (child->icon);
+
+  child->label = gtk_label_new (NULL);
+  gtk_box_pack_start (GTK_BOX (child->box), child->label, TRUE, TRUE, 0);
+  if (child->tasklist->horizontal)
+    {
+      gtk_misc_set_alignment (GTK_MISC (child->label), 0.0, 0.5);
+      gtk_label_set_ellipsize (GTK_LABEL (child->label), child->tasklist->ellipsize_mode);
+    }
+  else
+    {
+      gtk_label_set_angle (GTK_LABEL (child->label), 270);
+      gtk_misc_set_alignment (GTK_MISC (child->label), 0.50, 0.00);
+      /* TODO can we already ellipsize here? */
+    }
+
+  /* don't show the label if we're in iconbox style */
+  if (child->tasklist->show_labels)
+    gtk_widget_show (child->label);
+
+  /* monitor class group changes */
+  g_signal_connect (G_OBJECT (class_group), "icon-changed",
+      G_CALLBACK (xfce_tasklist_group_button_icon_changed), child);
+  g_signal_connect (G_OBJECT (class_group), "name-changed",
+      G_CALLBACK (xfce_tasklist_group_button_name_changed), child);
 
   /* poke functions */
-  tasklist_button_icon_changed (window, child);
-  tasklist_button_name_changed (window, child);
+  xfce_tasklist_group_button_icon_changed (class_group, child);
+  xfce_tasklist_group_button_name_changed (NULL, child);
+
+  /* insert */
+  tasklist->windows = g_slist_insert_sorted_with_data (tasklist->windows, child,
+                                                        xfce_tasklist_button_compare,
+                                                        tasklist);
+
+  return child;
+  /* TODO remove */
+  xfce_tasklist_group_button_remove(tasklist,class_group);
 }
 
 
 
+/**
+ * Potential Public Functions
+ **/
 static void
 xfce_tasklist_set_include_all_workspaces (XfceTasklist *tasklist,
                                           gboolean      all_workspaces)
@@ -1589,18 +2571,15 @@ xfce_tasklist_set_include_all_workspaces (XfceTasklist *tasklist,
 
   panel_return_if_fail (XFCE_IS_TASKLIST (tasklist));
 
-  /* normalize the value */
   all_workspaces = !!all_workspaces;
-
   if (tasklist->all_workspaces != all_workspaces)
     {
-      /* set new value */
       tasklist->all_workspaces = all_workspaces;
 
       if (all_workspaces)
         {
           /* make sure all buttons are visible */
-          for (li = tasklist->children; li != NULL; li = li->next)
+          for (li = tasklist->windows; li != NULL; li = li->next)
             {
               child = li->data;
               gtk_widget_show (child->button);
@@ -1628,16 +2607,19 @@ xfce_tasklist_set_button_relief (XfceTasklist   *tasklist,
 
   if (tasklist->button_relief != button_relief)
     {
-      /* set the new value */
       tasklist->button_relief = button_relief;
 
       /* change the relief of all buttons in the list */
-      for (li = tasklist->children; li != NULL; li = li->next)
+      for (li = tasklist->windows; li != NULL; li = li->next)
         {
           child = li->data;
           gtk_button_set_relief (GTK_BUTTON (child->button),
                                  button_relief);
         }
+
+      /* arrow button for overflow menu */
+      gtk_button_set_relief (GTK_BUTTON (tasklist->arrow_button),
+                             button_relief);
     }
 }
 
@@ -1654,11 +2636,10 @@ xfce_tasklist_set_show_labels (XfceTasklist *tasklist,
 
   if (tasklist->show_labels != show_labels)
     {
-      /* set new value */
       tasklist->show_labels = show_labels;
 
       /* change the mode of all the buttons */
-      for (li = tasklist->children; li != NULL; li = li->next)
+      for (li = tasklist->windows; li != NULL; li = li->next)
         {
           child = li->data;
 
@@ -1680,8 +2661,9 @@ xfce_tasklist_set_show_labels (XfceTasklist *tasklist,
                                          GTK_PACK_START);
             }
 
-          /* update the icon */
-          tasklist_button_icon_changed (child->window, child);
+          /* update the icon (we use another size for
+           * icon box mode) */
+          xfce_tasklist_button_icon_changed (child->window, child);
         }
     }
 }
@@ -1699,24 +2681,23 @@ xfce_tasklist_set_show_only_minimized (XfceTasklist *tasklist,
 
   if (tasklist->only_minimized != only_minimized)
     {
-      /* set the new value */
       tasklist->only_minimized = !!only_minimized;
 
       /* update the tasklist */
-      for (li = tasklist->children; li != NULL; li = li->next)
+      for (li = tasklist->windows; li != NULL; li = li->next)
         {
           child = li->data;
 
           /* update the icons of the minimized windows */
           if (wnck_window_is_minimized (child->window))
             {
-              tasklist_button_icon_changed (child->window, child);
-              tasklist_button_name_changed (child->window, child);
+              xfce_tasklist_button_icon_changed (child->window, child);
+              xfce_tasklist_button_name_changed (child->window, child);
             }
 
           /* if we show all workspaces, update the visibility here */
-          if (tasklist->all_workspaces == TRUE
-              && wnck_window_is_minimized (child->window) == FALSE)
+          if (tasklist->all_workspaces
+              && !wnck_window_is_minimized (child->window))
             {
               if (only_minimized)
                 gtk_widget_hide (child->button);
@@ -1726,7 +2707,7 @@ xfce_tasklist_set_show_only_minimized (XfceTasklist *tasklist,
         }
 
       /* update the buttons when we show only the active workspace */
-      if (tasklist->all_workspaces == FALSE)
+      if (!tasklist->all_workspaces)
         xfce_tasklist_active_workspace_changed (tasklist->screen,
                                                 NULL, tasklist);
     }
@@ -1740,13 +2721,11 @@ xfce_tasklist_set_show_wireframes (XfceTasklist *tasklist,
 {
   panel_return_if_fail (XFCE_IS_TASKLIST (tasklist));
 
-  /* set new value */
   tasklist->show_wireframes = !!show_wireframes;
 
 #ifdef GDK_WINDOWING_X11
   /* destroy the window if needed */
-  if (show_wireframes == FALSE && tasklist->wireframe_window != 0)
-    xfce_tasklist_wireframe_destroy (tasklist);
+  xfce_tasklist_wireframe_destroy (tasklist);
 #endif
 }
 
@@ -1769,7 +2748,7 @@ xfce_tasklist_set_orientation (XfceTasklist   *tasklist,
       tasklist->horizontal = horizontal;
 
       /* update the tasklist */
-      for (li = tasklist->children; li != NULL; li = li->next)
+      for (li = tasklist->windows; li != NULL; li = li->next)
         {
           child = li->data;
 
diff --git a/plugins/tasklist/tasklist-widget.h b/plugins/tasklist/tasklist-widget.h
index 48da478..c903260 100644
--- a/plugins/tasklist/tasklist-widget.h
+++ b/plugins/tasklist/tasklist-widget.h
@@ -25,8 +25,6 @@ G_BEGIN_DECLS
 
 typedef struct _XfceTasklistClass     XfceTasklistClass;
 typedef struct _XfceTasklist          XfceTasklist;
-typedef struct _XfceTasklistChild     XfceTasklistChild;
-typedef enum   _XfceTasklistStyle     XfceTasklistStyle;
 typedef enum   _XfceTasklistGrouping  XfceTasklistGrouping;
 typedef enum   _XfceTasklistSortOrder XfceTasklistSortOrder;
 
diff --git a/plugins/tasklist/tasklist.c b/plugins/tasklist/tasklist.c
index c785817..f338552 100644
--- a/plugins/tasklist/tasklist.c
+++ b/plugins/tasklist/tasklist.c
@@ -59,11 +59,15 @@ struct _TasklistPlugin
 
 
 
-static void tasklist_plugin_construct (XfcePanelPlugin *panel_plugin);
-static void tasklist_plugin_orientation_changed (XfcePanelPlugin *panel_plugin, GtkOrientation orientation);
-static gboolean tasklist_plugin_size_changed (XfcePanelPlugin *panel_plugin, gint size);
-static void tasklist_plugin_configure_plugin (XfcePanelPlugin *panel_plugin);
-static gboolean tasklist_plugin_handle_expose_event (GtkWidget *widget, GdkEventExpose *event, TasklistPlugin *plugin);
+static void     tasklist_plugin_construct           (XfcePanelPlugin *panel_plugin);
+static void     tasklist_plugin_orientation_changed (XfcePanelPlugin *panel_plugin,
+                                                     GtkOrientation   orientation);
+static gboolean tasklist_plugin_size_changed        (XfcePanelPlugin *panel_plugin,
+                                                     gint             size);
+static void     tasklist_plugin_configure_plugin    (XfcePanelPlugin *panel_plugin);
+static gboolean tasklist_plugin_handle_expose_event (GtkWidget       *widget,
+                                                     GdkEventExpose  *event,
+                                                     TasklistPlugin  *plugin);
 
 
 
@@ -212,6 +216,12 @@ tasklist_plugin_configure_plugin (XfcePanelPlugin *panel_plugin)
   TASKLIST_DIALOG_BIND ("show-handle", "active")
   TASKLIST_DIALOG_BIND ("sort-order", "active")
 
+#ifndef GDK_WINDOWING_X11
+  /* not functional in x11, so avoid confusion */
+  object = gtk_builder_get_object (builder, "show-wireframes");
+  gtk_widget_hide (GTK_WIDGET (object));
+#endif
+
   gtk_widget_show (GTK_WIDGET (dialog));
 }
 



More information about the Xfce4-commits mailing list