/* tasklist object */

/*
 * Copyright (C) 2001 Havoc Pennington
 * Copyright (C) 2003 Kim Woelders
 * Copyright (C) 2003 Red Hat, Inc.
 * Includes urgency notification based on Matthew Eaton's patch for libwnck
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 */
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#ifdef HAVE_MEMORY_H
#include <memory.h>
#endif
#include <stdio.h>
#ifdef HAVE_STRING_H
#include <string.h>
#endif

#include "netk-tasklist.h"
#include "netk-window.h"
#include "netk-window-action-menu.h"
#include "netk-workspace.h"
#include "netk-class-group.h"
#include "netk-xutils.h"
#include "netk-private.h"

#include "xfce-gdk-extensions.h"

#define NETK_TYPE_TASK              (netk_task_get_type ())
#define NETK_TASK(object)           (G_TYPE_CHECK_INSTANCE_CAST ((object), NETK_TYPE_TASK, NetkTask))
#define NETK_TASK_CLASS(klass)      (G_TYPE_CHECK_CLASS_CAST ((klass), NETK_TYPE_TASK, NetkTaskClass))
#define NETK_IS_TASK(object)        (G_TYPE_CHECK_INSTANCE_TYPE ((object), NETK_TYPE_TASK))
#define NETK_IS_TASK_CLASS(klass)   (G_TYPE_CHECK_CLASS_TYPE ((klass), NETK_TYPE_TASK))
#define NETK_TASK_GET_CLASS(obj)    (G_TYPE_INSTANCE_GET_CLASS ((obj), NETK_TYPE_TASK, NetkTaskClass))

#define NETK_TASKLIST_GET_PRIVATE(o)    (G_TYPE_INSTANCE_GET_PRIVATE ((o), NETK_TYPE_TASKLIST, NetkTasklistPrivate))

typedef struct _NetkTask NetkTask;
typedef struct _NetkTaskClass NetkTaskClass;

#define MINI_ICON_SIZE 16
#define DEFAULT_GROUPING_LIMIT 80

#define DEFAULT_WIDTH 1
#define DEFAULT_HEIGHT 48
#define DEFAULT_BUTTON_WIDTH 200

#define TIMEOUT_ACTIVATE 1000

#define N_SCREEN_CONNECTIONS 5

#ifdef PANGO_ELLIPSIZE_END
#define NETK_ELLIPSIZE_END PANGO_ELLIPSIZE_END
#else
#define NETK_ELLIPSIZE_END 3
#endif

#define MAX_URGENT_BLINK_COUNT 18

typedef enum
{
    NETK_TASK_CLASS_GROUP,
    NETK_TASK_WINDOW
}
NetkTaskType;

struct _NetkTask
{
    GObject parent_instance;

    NetkTasklist *tasklist;

    GtkWidget *button;
    GtkWidget *image;
    GtkWidget *label;

    NetkTaskType type;

    NetkClassGroup *class_group;
    NetkWindow *window;

    gdouble grouping_score;

    GList *windows;             /* List of the NetkTask for the window,
                                   if this is a class group */
    gulong state_changed_tag;
    gulong icon_changed_tag;
    gulong name_changed_tag;
    gulong class_name_changed_tag;
    gulong class_icon_changed_tag;

    /* task menu */
    GtkWidget *menu;
    /* ops menu */
    GtkWidget *action_menu;

    guint really_toggling:1;    /* Set when tasklist really wants
                                 * to change the togglebutton state
                                 */
    guint was_active:1;         /* used to fixup activation behavior */

    guint button_activate;

    guint blink_timeout_id;
    gint blink_count;
};

struct _NetkTaskClass
{
    GObjectClass parent_class;
};

struct _NetkTasklistPrivate
{
    NetkScreen *screen;

    NetkTask *active_task;      /* NULL if active window not in tasklist */
    NetkTask *active_class_group; /* NULL if active window not in tasklist */

    gboolean include_all_workspaces;

    /* Calculated by update_lists */
    GList *windows;
    GList *class_groups;

    GHashTable *win_hash;
    GHashTable *class_group_hash;

    GtkTooltips *tooltips;

    gint max_button_width;
    gint max_button_height;

    gboolean switch_workspace_on_unminimize;

    NetkTasklistGroupingType grouping;
    gint grouping_limit;

    guint screen_connections[N_SCREEN_CONNECTIONS];
    guint idle_callback_tag;
    
    int *size_hints;
    int size_hints_len;

    gint minimum_width;
    gint minimum_height;

    NetkLoadIconFunction icon_loader;
    void *icon_loader_data;
    GDestroyNotify free_icon_loader_data;

    gboolean show_label;
    GtkReliefStyle relief;
};


static void netk_task_init (NetkTask * task);
static void netk_task_class_init (NetkTaskClass * klass);
static void netk_task_finalize (GObject * object);

static NetkTask *netk_task_new_from_window (NetkTasklist * tasklist,
                                            NetkWindow * window);
static NetkTask *netk_task_new_from_class_group (NetkTasklist * tasklist,
                                                 NetkClassGroup * class_group);

static gboolean netk_task_get_demands_attention (NetkTask * task);
static gboolean  urgent_timeout_callback(gpointer data);
static void  queue_urgent_timeout(gpointer data);
static void  unqueue_urgent_timeout(gpointer data);
static char *netk_task_get_text (NetkTask * task);
static GdkPixbuf *netk_task_get_icon (NetkTask * task);
static gint netk_task_compare (gconstpointer a, gconstpointer b);
static void netk_task_update_visible_state (NetkTask * task);


static void netk_tasklist_init (NetkTasklist * tasklist);
static void netk_tasklist_class_init (NetkTasklistClass * klass);
static void netk_tasklist_finalize (GObject * object);

static void netk_tasklist_size_request (GtkWidget * widget,
                                        GtkRequisition * requisition);
static void netk_tasklist_size_allocate (GtkWidget * widget,
                                         GtkAllocation * allocation);
static void netk_tasklist_realize (GtkWidget * widget);
static void netk_tasklist_unrealize (GtkWidget * widget);
static void netk_tasklist_forall (GtkContainer * container,
                                  gboolean include_internals,
                                  GtkCallback callback,
                                  gpointer callback_data);
static void netk_tasklist_remove (GtkContainer * container,
                                  GtkWidget * widget);
static void netk_tasklist_free_tasks (NetkTasklist * tasklist);
static void netk_tasklist_update_lists (NetkTasklist * tasklist);
static int netk_tasklist_layout (GtkAllocation * allocation, int max_width,
                                 int max_height, int n_buttons,
                                 int *n_cols_out, int *n_rows_out);

static void netk_tasklist_active_window_changed (NetkScreen * screen,
                                                 NetkTasklist * tasklist);
static void netk_tasklist_active_workspace_changed (NetkScreen * screen,
                                                    NetkTasklist * tasklist);
static void netk_tasklist_window_added (NetkScreen * screen, NetkWindow * win,
                                        NetkTasklist * tasklist);
static void netk_tasklist_window_removed (NetkScreen * screen,
                                          NetkWindow * win,
                                          NetkTasklist * tasklist);
static void netk_tasklist_viewports_changed (NetkScreen * screen,
                                             NetkTasklist * tasklist);
static void netk_tasklist_connect_window (NetkTasklist * tasklist,
                                          NetkWindow * window);

static void netk_tasklist_change_active_task (NetkTasklist * tasklist,
                                              NetkTask * active_task);

static void netk_tasklist_activate_task_window (NetkTask * task);

static void netk_tasklist_update_icon_geometries (NetkTasklist * tasklist);
static void netk_tasklist_connect_screen (NetkTasklist * tasklist,
                                          NetkScreen * screen);
static void netk_tasklist_disconnect_screen (NetkTasklist * tasklist);

static GObjectClass *task_parent_class;
static GObjectClass *tasklist_parent_class;

GType
netk_task_get_type (void)
{
    static GType type = G_TYPE_INVALID;

    if (G_UNLIKELY (type == G_TYPE_INVALID))
    {
        type = _netk_g_type_register_simple (G_TYPE_OBJECT,
                                             "NetkTask",
                                             sizeof (NetkTaskClass),
                                             netk_task_class_init,
                                             sizeof (NetkTask),
                                             netk_task_init,
                                             0);
    }

    return type;
}

static void
netk_task_init (NetkTask * task)
{
    
}

static void
netk_task_class_init (NetkTaskClass * klass)
{
    GObjectClass *object_class = G_OBJECT_CLASS (klass);

    task_parent_class = g_type_class_peek_parent (klass);

    object_class->finalize = netk_task_finalize;

    gtk_rc_parse_string ("\n" 
                         "   style \"tasklist-button-style\"\n" 
                         "   {\n"
                         "      GtkWidget::focus-line-width=0\n"
                         "      GtkWidget::focus-padding=0\n" 
                         "   }\n" "\n"
                         "    widget \"*.tasklist-button\" style \"tasklist-button-style\"\n"
                         "\n");
}

static void
netk_task_finalize (GObject * object)
{
    NetkTask *task;

    task = NETK_TASK (object);

    if (task->tasklist->priv->active_task == task)
        netk_tasklist_change_active_task (task->tasklist, NULL);

    if (task->blink_timeout_id)
    {
        g_source_remove (task->blink_timeout_id);
        task->blink_timeout_id = 0;
    }

    if (task->button)
    {
        gtk_widget_destroy (task->button);
        task->button = NULL;
    }

    g_list_free (task->windows);
    task->windows = NULL;

    if (task->state_changed_tag)
    {
        g_signal_handler_disconnect (task->window, task->state_changed_tag);
        task->state_changed_tag = 0;
    }

    if (task->icon_changed_tag)
    {
        g_signal_handler_disconnect (task->window, task->icon_changed_tag);
        task->icon_changed_tag = 0;
    }

    if (task->name_changed_tag)
    {
        g_signal_handler_disconnect (task->window, task->name_changed_tag);
        task->name_changed_tag = 0;
    }

    if (task->class_name_changed_tag)
    {
        g_signal_handler_disconnect (task->class_group,
                                     task->class_name_changed_tag);
        task->class_name_changed_tag = 0;
    }

    if (task->class_icon_changed_tag)
    {
        g_signal_handler_disconnect (task->class_group,
                                     task->class_icon_changed_tag);
        task->class_icon_changed_tag = 0;
    }

    if (task->menu)
    {
        gtk_widget_destroy (task->menu);
        task->menu = NULL;
    }

    if (task->action_menu)
    {
      g_object_unref (G_OBJECT (task->action_menu));
      task->action_menu = NULL;
    }

    if (task->window)
    {
        g_object_unref (G_OBJECT (task->window));
        task->window = NULL;
    }

    if (task->class_group)
    {
        g_object_unref (G_OBJECT (task->class_group));
        task->class_group = NULL;
    }

    if (task->button_activate != 0)
    {
        g_source_remove (task->button_activate);
        task->button_activate = 0;
    }

    G_OBJECT_CLASS (task_parent_class)->finalize (object);
}

GType
netk_tasklist_get_type (void)
{
    static GType type = G_TYPE_INVALID;

    if (G_UNLIKELY (type == G_TYPE_INVALID))
    {
        type = _netk_g_type_register_simple (GTK_TYPE_CONTAINER,
                                             "NetkTasklist",
                                             sizeof (NetkTasklistClass),
                                             netk_tasklist_class_init,
                                             sizeof (NetkTasklist),
                                             netk_tasklist_init,
                                             0);
    }

    return type;
}

static void
netk_tasklist_init (NetkTasklist * tasklist)
{
    GTK_WIDGET_SET_FLAGS (GTK_WIDGET (tasklist), GTK_NO_WINDOW);

    tasklist->priv = NETK_TASKLIST_GET_PRIVATE (tasklist);
    
    tasklist->priv->include_all_workspaces = FALSE;

    tasklist->priv->win_hash = g_hash_table_new (NULL, NULL);
    tasklist->priv->class_group_hash = g_hash_table_new (NULL, NULL);

    tasklist->priv->grouping = NETK_TASKLIST_AUTO_GROUP;
    tasklist->priv->grouping_limit = DEFAULT_GROUPING_LIMIT;

    tasklist->priv->minimum_width = DEFAULT_WIDTH;
    tasklist->priv->minimum_height = DEFAULT_HEIGHT;

    tasklist->priv->idle_callback_tag = 0;
}

static void
netk_tasklist_class_init (NetkTasklistClass * klass)
{
    GObjectClass *object_class = G_OBJECT_CLASS (klass);
    GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
    GtkContainerClass *container_class = GTK_CONTAINER_CLASS (klass);

    g_type_class_add_private (klass, sizeof (NetkTasklistPrivate));

    tasklist_parent_class = g_type_class_peek_parent (klass);

    object_class->finalize = netk_tasklist_finalize;

    widget_class->size_request = netk_tasklist_size_request;
    widget_class->size_allocate = netk_tasklist_size_allocate;
    widget_class->realize = netk_tasklist_realize;
    widget_class->unrealize = netk_tasklist_unrealize;

    container_class->forall = netk_tasklist_forall;
    container_class->remove = netk_tasklist_remove;
}

static void
netk_tasklist_finalize (GObject * object)
{
    NetkTasklist *tasklist;

    tasklist = NETK_TASKLIST (object);

    if (tasklist->priv->free_icon_loader_data != NULL)
        (*tasklist->priv->free_icon_loader_data) (tasklist->priv->
                                                  icon_loader_data);

    netk_tasklist_disconnect_screen (tasklist);

    /* Tasks should have gone away due to removing their
     * buttons in container destruction
     */
    g_assert (tasklist->priv->windows == NULL);
    g_assert (tasklist->priv->class_groups == NULL);
    /* netk_tasklist_free_tasks (tasklist); */

    g_hash_table_destroy (tasklist->priv->win_hash);
    tasklist->priv->win_hash = NULL;

    g_hash_table_destroy (tasklist->priv->class_group_hash);
    tasklist->priv->class_group_hash = NULL;

    if (tasklist->priv->idle_callback_tag != 0)
    {
        g_source_remove (tasklist->priv->idle_callback_tag);
    }

    if (tasklist->priv->tooltips)
    {
        g_object_unref (G_OBJECT (tasklist->priv->tooltips));
        tasklist->priv->tooltips = NULL;
    }

    g_free (tasklist->priv->size_hints);
    tasklist->priv->size_hints = NULL;
    tasklist->priv->size_hints_len = 0;

    G_OBJECT_CLASS (tasklist_parent_class)->finalize (object);
}

void
netk_tasklist_set_show_label (NetkTasklist * tasklist,
                              gboolean show_label)
{ 
    g_return_if_fail (NETK_IS_TASKLIST (tasklist));

    if (tasklist->priv->show_label == show_label)
        return;

    tasklist->priv->show_label = show_label;
    gtk_widget_queue_resize (GTK_WIDGET (tasklist));
    netk_tasklist_update_lists (tasklist);
}

void 
netk_tasklist_set_button_relief (NetkTasklist * tasklist, 
                                 GtkReliefStyle relief)
{
    g_return_if_fail (NETK_IS_TASKLIST (tasklist));

    if (tasklist->priv->relief == relief)
        return;

    tasklist->priv->relief = relief;
    netk_tasklist_update_lists (tasklist);
    gtk_widget_queue_resize (GTK_WIDGET (tasklist));
}

void
netk_tasklist_set_grouping (NetkTasklist * tasklist,
                            NetkTasklistGroupingType grouping)
{
    g_return_if_fail (NETK_IS_TASKLIST (tasklist));

    if (tasklist->priv->grouping == grouping)
        return;

    tasklist->priv->grouping = grouping;
    gtk_widget_queue_resize (GTK_WIDGET (tasklist));
}

void
netk_tasklist_set_switch_workspace_on_unminimize (NetkTasklist * tasklist,
                                                  gboolean
                                                  switch_workspace_on_unminimize)
{
    g_return_if_fail (NETK_IS_TASKLIST (tasklist));

    tasklist->priv->switch_workspace_on_unminimize =
        switch_workspace_on_unminimize;
}

void
netk_tasklist_set_include_all_workspaces (NetkTasklist * tasklist,
                                          gboolean include_all_workspaces)
{
    g_return_if_fail (NETK_IS_TASKLIST (tasklist));

    include_all_workspaces = (include_all_workspaces != 0);

    if (tasklist->priv->include_all_workspaces == include_all_workspaces)
        return;

    tasklist->priv->include_all_workspaces = include_all_workspaces;
    netk_tasklist_update_lists (tasklist);
    gtk_widget_queue_resize (GTK_WIDGET (tasklist));
}

void
netk_tasklist_set_grouping_limit (NetkTasklist * tasklist, gint limit)
{
    g_return_if_fail (NETK_IS_TASKLIST (tasklist));

    if (tasklist->priv->grouping_limit == limit)
        return;

    tasklist->priv->grouping_limit = limit;
    gtk_widget_queue_resize (GTK_WIDGET (tasklist));
}

/* set the minimum width 
 * use -1 to unset it (resulting in the default width */
void
netk_tasklist_set_minimum_width (NetkTasklist * tasklist, gint size)
{
    g_return_if_fail (NETK_IS_TASKLIST (tasklist));

    if (size == -1)
        size = DEFAULT_WIDTH;

    if (tasklist->priv->minimum_width == size)
        return;

    tasklist->priv->minimum_width = size;
    gtk_widget_queue_resize (GTK_WIDGET (tasklist));
}

/* get the minimum width */
gint
netk_tasklist_get_minimum_width (NetkTasklist * tasklist)
{
    g_return_val_if_fail (NETK_IS_TASKLIST (tasklist), 0);

    return tasklist->priv->minimum_width;
}

/* set the minimum height
 * use -1 to unset it (resulting in the default height */
void
netk_tasklist_set_minimum_height (NetkTasklist * tasklist, gint size)
{
    g_return_if_fail (NETK_IS_TASKLIST (tasklist));

    if (size == -1)
        size = DEFAULT_HEIGHT;

    if (tasklist->priv->minimum_height == size)
        return;

    tasklist->priv->minimum_height = size;
    gtk_widget_queue_resize (GTK_WIDGET (tasklist));
}

/* get the minimum height */
gint
netk_tasklist_get_minimum_height (NetkTasklist * tasklist)
{
    g_return_val_if_fail (NETK_IS_TASKLIST (tasklist), 0);

    return tasklist->priv->minimum_height;
}

/**
 * netk_tasklist_set_icon_loader:
 * @tasklist: a #NetkTasklist
 * @load_icon_func: icon loader function
 * @data: data for icon loader function
 * @free_data_func: function to free the data
 *
 * Sets a function to be used for loading icons. The icon
 * loader function takes an icon name as in the Icon field
 * in a .desktop file. The "flags" field for the function
 * is not defined to do anything yet.
 * 
 **/
void
netk_tasklist_set_icon_loader (NetkTasklist * tasklist,
                               NetkLoadIconFunction load_icon_func,
                               void *data, GDestroyNotify free_data_func)
{
    if (tasklist->priv->free_icon_loader_data != NULL)
        (*tasklist->priv->free_icon_loader_data) (tasklist->priv->
                                                  icon_loader_data);

    tasklist->priv->icon_loader = load_icon_func;
    tasklist->priv->icon_loader_data = data;
    tasklist->priv->free_icon_loader_data = free_data_func;
}

/* returns the maximal possible button width (i.e. if you
 * don't want to stretch the buttons to fill the alloctions
 * the width can be smaller) */
static int
netk_tasklist_layout (GtkAllocation * allocation, int max_width,
                      int max_height, int n_buttons, int *n_cols_out,
                      int *n_rows_out)
{
    int n_cols, n_rows;

    /* How many rows fit in the allocation */
    n_rows = allocation->height / max_height;

    /* Don't have more rows than buttons */
    n_rows = MIN (n_rows, n_buttons);

    /* At least one row */
    n_rows = MAX (n_rows, 1);

    /* We want to use as many rows as possible to limit the width */
    n_cols = (n_buttons + n_rows - 1) / n_rows;

    /* At least one column */
    n_cols = MAX (n_cols, 1);

    *n_cols_out = n_cols;
    *n_rows_out = n_rows;

    return allocation->width / n_cols;
}

static void
netk_tasklist_score_groups (NetkTasklist * tasklist, GList * ungrouped_class_groups)
{
    NetkTask *class_group_task;
    NetkTask *win_task;
    GList *l, *w;
    const char *first_name = NULL;
    int n_windows;
    int n_same_title;
    double same_window_ratio;

    l = ungrouped_class_groups;
    while (l != NULL)
    {
        class_group_task = NETK_TASK (l->data);

        n_windows = g_list_length (class_group_task->windows);

        n_same_title = 0;
        w = class_group_task->windows;
        while (w != NULL)
        {
            win_task = NETK_TASK (w->data);

            if (first_name == NULL)
            {
                first_name = netk_window_get_icon_name (win_task->window);
                n_same_title++;
            }
            else
            {
                if (strcmp
                    (netk_window_get_icon_name (win_task->window),
                     first_name) == 0)
                    n_same_title++;
            }

            w = w->next;
        }
        same_window_ratio = (double) n_same_title / (double) n_windows;

        /* FIXME: This is fairly bogus and should be researched more.
         *        XP groups by least used, so we probably want to add
         *        total focused time to this expression.
         */
        class_group_task->grouping_score = -same_window_ratio * 5 + n_windows;

        l = l->next;
    }
}

static GList *
netk_task_get_highest_scored (GList * ungrouped_class_groups,
                              NetkTask ** class_group_task_out)
{
    NetkTask *class_group_task;
    NetkTask *best_task = NULL;
    double max_score = -1000000000.0;   /* Large negative score */
    GList *l;

    l = ungrouped_class_groups;
    while (l != NULL)
    {
        class_group_task = NETK_TASK (l->data);

        if (class_group_task->grouping_score >= max_score)
        {
            max_score = class_group_task->grouping_score;
            best_task = class_group_task;
        }

        l = l->next;
    }

    *class_group_task_out = best_task;

    return g_list_remove (ungrouped_class_groups, best_task);
}

static void
netk_tasklist_size_request (GtkWidget * widget, GtkRequisition * requisition)
{
    NetkTasklist *tasklist;
    GtkRequisition child_req;
    GtkAllocation fake_allocation;
    int max_height = 1;
    int max_width = 1;
    /* int u_width, u_height; */
    GList *l;
    GArray *array;
    GList *ungrouped_class_groups;
    int n_windows;
    int n_rows;
    int n_cols, last_n_cols;
    int n_grouped_buttons;
    gboolean score_set;
    int val;
    NetkTask *class_group_task;
    int lowest_range;
    int grouping_limit;

    tasklist = NETK_TASKLIST (widget);

    /* Calculate max needed height and width of the buttons */
    l = tasklist->priv->windows;
    while (l != NULL)
    {
        NetkTask *task = NETK_TASK (l->data);

        gtk_widget_size_request (task->button, &child_req);

        max_height = MAX (child_req.height, max_height);
        max_width = MAX (child_req.width, max_width);

        l = l->next;
    }

    l = tasklist->priv->class_groups;
    while (l != NULL)
    {
        NetkTask *task = NETK_TASK (l->data);

        gtk_widget_size_request (task->button, &child_req);

        max_height = MAX (child_req.height, max_height);

        l = l->next;
    }

    if (tasklist->priv->show_label)
        max_width = DEFAULT_BUTTON_WIDTH;
    else
        max_width = max_height;
    
    tasklist->priv->max_button_width = max_width;
    tasklist->priv->max_button_height = max_height;
    requisition->width = tasklist->priv->minimum_width;
    requisition->height = tasklist->priv->minimum_height;

    fake_allocation.width = requisition->width;
    fake_allocation.height = requisition->height;

    array = g_array_new (FALSE, FALSE, sizeof (int));

    /* Calculate size_hints list */

    n_windows = g_list_length (tasklist->priv->windows);
    n_grouped_buttons = 0;
    ungrouped_class_groups = g_list_copy (tasklist->priv->class_groups);
    score_set = FALSE;

    grouping_limit =
        MIN (tasklist->priv->grouping_limit,
             tasklist->priv->max_button_width);

    /* Try ungrouped mode */
    netk_tasklist_layout (&fake_allocation, tasklist->priv->max_button_width,
                          tasklist->priv->max_button_height, n_windows,
                          &n_cols, &n_rows);

    last_n_cols = G_MAXINT;
    lowest_range = G_MAXINT;
    if (tasklist->priv->grouping != NETK_TASKLIST_ALWAYS_GROUP)
    {
        val = n_cols * tasklist->priv->max_button_width;
        g_array_insert_val (array, array->len, val);
        val = n_cols * grouping_limit;
        g_array_insert_val (array, array->len, val);

        last_n_cols = n_cols;
        lowest_range = val;
    }

    while (ungrouped_class_groups != NULL
           && tasklist->priv->grouping != NETK_TASKLIST_NEVER_GROUP)
    {
        if (!score_set)
        {
            netk_tasklist_score_groups (tasklist, ungrouped_class_groups);
            score_set = TRUE;
        }

        ungrouped_class_groups =
            netk_task_get_highest_scored (ungrouped_class_groups, &class_group_task);

        n_grouped_buttons += g_list_length (class_group_task->windows) - 1;

        netk_tasklist_layout (&fake_allocation,
                              tasklist->priv->max_button_width,
                              tasklist->priv->max_button_height,
                              n_windows - n_grouped_buttons, &n_cols,
                              &n_rows);
        if (n_cols != last_n_cols
            && (tasklist->priv->grouping == NETK_TASKLIST_AUTO_GROUP
                || ungrouped_class_groups == NULL))
        {
            val = n_cols * tasklist->priv->max_button_width;
            if (val >= lowest_range)
            {                   /* Overlaps old range */
                lowest_range = n_cols * grouping_limit;
                if (array->len > 0)
                    g_array_index (array, int, array->len - 1) = lowest_range;
                else
                    g_array_insert_val (array, 0, lowest_range);
            }
            else
            {
                /* Full new range */
                g_array_insert_val (array, array->len, val);
                val = n_cols * grouping_limit;
                g_array_insert_val (array, array->len, val);
                lowest_range = val;
            }

            last_n_cols = n_cols;
        }
    }

    g_list_free (ungrouped_class_groups);

    /* Always let you go down to a zero size: */
    if (array->len > 0)
        g_array_index (array, int, array->len - 1) = 0;
    else
    {
        val = 0;
        g_array_insert_val (array, 0, val);
    }

    if (tasklist->priv->size_hints)
        g_free (tasklist->priv->size_hints);

    tasklist->priv->size_hints_len = array->len;

    tasklist->priv->size_hints = (int *) g_array_free (array, FALSE);
}

const int *
netk_tasklist_get_size_hint_list (NetkTasklist * tasklist, int *n_elements)
{
    *n_elements = tasklist->priv->size_hints_len;
    return tasklist->priv->size_hints;
}

static void
netk_tasklist_size_allocate (GtkWidget * widget, GtkAllocation * allocation)
{
    GtkAllocation child_allocation;
    NetkTasklist *tasklist;
    NetkTask *class_group_task;
    int n_windows;
    GList *l;
    int button_width;
    int total_width;
    int n_rows;
    int n_cols;
    int n_grouped_buttons;
    int i;
    gboolean score_set;
    GList *ungrouped_class_groups;
    NetkTask *win_task;
    GList *visible_tasks = NULL;
    int grouping_limit;

    tasklist = NETK_TASKLIST (widget);

    n_windows = g_list_length (tasklist->priv->windows);
    n_grouped_buttons = 0;
    ungrouped_class_groups = g_list_copy (tasklist->priv->class_groups);
    score_set = FALSE;

    grouping_limit =
        MIN (tasklist->priv->grouping_limit,
             tasklist->priv->max_button_width);

    /* Try ungrouped mode */
    button_width =
        netk_tasklist_layout (allocation, tasklist->priv->max_button_width,
                              tasklist->priv->max_button_height, n_windows,
                              &n_cols, &n_rows);
    while (ungrouped_class_groups != NULL
           && ((tasklist->priv->grouping == NETK_TASKLIST_ALWAYS_GROUP)
               || ((tasklist->priv->grouping == NETK_TASKLIST_AUTO_GROUP)
                   && (button_width < grouping_limit))))
    {
        if (!score_set)
        {
            netk_tasklist_score_groups (tasklist, ungrouped_class_groups);
            score_set = TRUE;
        }

        ungrouped_class_groups =
            netk_task_get_highest_scored (ungrouped_class_groups, &class_group_task);

        n_grouped_buttons += g_list_length (class_group_task->windows) - 1;

        if (g_list_length (class_group_task->windows) > 1)
        {
            visible_tasks = g_list_prepend (visible_tasks, class_group_task);

            /* Hide all this apps windows */
            l = class_group_task->windows;
            while (l != NULL)
            {
                win_task = NETK_TASK (l->data);

                gtk_widget_set_child_visible (GTK_WIDGET (win_task->button),
                                              FALSE);
                l = l->next;
            }
        }
        else
        {
            visible_tasks =
                g_list_prepend (visible_tasks, class_group_task->windows->data);
            gtk_widget_set_child_visible (GTK_WIDGET (class_group_task->button),
                                          FALSE);
        }

        button_width =
            netk_tasklist_layout (allocation,
                                  tasklist->priv->max_button_width,
                                  tasklist->priv->max_button_height,
                                  n_windows - n_grouped_buttons, &n_cols,
                                  &n_rows);
    }

    /* Add all ungrouped windows to visible_tasks, and hide their apps */
    l = ungrouped_class_groups;
    while (l != NULL)
    {
        class_group_task = NETK_TASK (l->data);

        visible_tasks =
            g_list_concat (visible_tasks, g_list_copy (class_group_task->windows));
        gtk_widget_set_child_visible (GTK_WIDGET (class_group_task->button), FALSE);
        l = l->next;
    }

    visible_tasks = g_list_sort (visible_tasks, netk_task_compare);

    /* Allocate children */
    l = visible_tasks;
    i = 0;
    total_width = tasklist->priv->max_button_width * n_cols;
    total_width = MIN (total_width, allocation->width);
    /* total_width = allocation->width; */
    while (l != NULL)
    {
        NetkTask *task = NETK_TASK (l->data);
        int col = i % n_cols;
        int row = i / n_cols;

        child_allocation.x = total_width * col / n_cols;
        child_allocation.y = allocation->height * row / n_rows;
        child_allocation.width =
            total_width * (col + 1) / n_cols - child_allocation.x;
        child_allocation.height =
            allocation->height * (row + 1) / n_rows - child_allocation.y;
        child_allocation.x += allocation->x;
        child_allocation.y += allocation->y;

        gtk_widget_size_allocate (task->button, &child_allocation);
        gtk_widget_set_child_visible (GTK_WIDGET (task->button), TRUE);

        i++;
        l = l->next;
    }

    /* Update icon geometries. */
    netk_tasklist_update_icon_geometries (tasklist);

    g_list_free (visible_tasks);
    g_list_free (ungrouped_class_groups);

    GTK_WIDGET_CLASS (tasklist_parent_class)->size_allocate (widget,
                                                             allocation);
}

static void
netk_tasklist_realize (GtkWidget * widget)
{
    NetkTasklist *tasklist;

    tasklist = NETK_TASKLIST (widget);

    (*GTK_WIDGET_CLASS (tasklist_parent_class)->realize) (widget);
}

static void
netk_tasklist_unrealize (GtkWidget * widget)
{
    NetkTasklist *tasklist;

    tasklist = NETK_TASKLIST (widget);

    (*GTK_WIDGET_CLASS (tasklist_parent_class)->unrealize) (widget);
}

static void
netk_tasklist_forall (GtkContainer * container, gboolean include_internals,
                      GtkCallback callback, gpointer callback_data)
{
    NetkTasklist *tasklist;
    GList *tmp;

    tasklist = NETK_TASKLIST (container);

    tmp = tasklist->priv->windows;
    while (tmp != NULL)
    {
        NetkTask *task = NETK_TASK (tmp->data);
        tmp = tmp->next;

        (*callback) (task->button, callback_data);
    }

    tmp = tasklist->priv->class_groups;
    while (tmp != NULL)
    {
        NetkTask *task = NETK_TASK (tmp->data);
        tmp = tmp->next;

        (*callback) (task->button, callback_data);
    }
}

static void
netk_tasklist_remove (GtkContainer * container, GtkWidget * widget)
{
    NetkTasklist *tasklist;
    GList *tmp;

    g_return_if_fail (NETK_IS_TASKLIST (container));
    g_return_if_fail (widget != NULL);

    tasklist = NETK_TASKLIST (container);

    tmp = tasklist->priv->windows;
    while (tmp != NULL)
    {
        NetkTask *task = NETK_TASK (tmp->data);
        tmp = tmp->next;

        if (task->button == widget)
        {
            g_hash_table_remove (tasklist->priv->win_hash, task->window);
            tasklist->priv->windows =
                g_list_remove (tasklist->priv->windows, task);

            gtk_widget_unparent (widget);
            g_object_unref (G_OBJECT (task));
            break;
        }
    }

    tmp = tasklist->priv->class_groups;
    while (tmp != NULL)
    {
        NetkTask *task = NETK_TASK (tmp->data);
        tmp = tmp->next;

        if (task->button == widget)
        {
            g_hash_table_remove (tasklist->priv->class_group_hash, task->class_group);
            tasklist->priv->class_groups =
                g_list_remove (tasklist->priv->class_groups, task);

            gtk_widget_unparent (widget);
            g_object_unref (G_OBJECT (task));
            break;
        }
    }
    gtk_widget_queue_resize (GTK_WIDGET (container));
}

static void
netk_tasklist_connect_screen (NetkTasklist * tasklist, NetkScreen * screen)
{
    GList *windows;
    guint *c;
    int i;

    i = 0;
    c = tasklist->priv->screen_connections;

    c[i++] =
        g_signal_connect_object (G_OBJECT (screen), "active_window_changed",
                                 G_CALLBACK
                                 (netk_tasklist_active_window_changed),
                                 tasklist, 0);
    c[i++] =
        g_signal_connect_object (G_OBJECT (screen),
                                 "active_workspace_changed",
                                 G_CALLBACK
                                 (netk_tasklist_active_workspace_changed),
                                 tasklist, 0);
    c[i++] =
        g_signal_connect_object (G_OBJECT (screen), "window_opened",
                                 G_CALLBACK (netk_tasklist_window_added),
                                 tasklist, 0);
    c[i++] =
        g_signal_connect_object (G_OBJECT (screen), "window_closed",
                                 G_CALLBACK (netk_tasklist_window_removed),
                                 tasklist, 0);
    c[i++] =
        g_signal_connect_object (G_OBJECT (screen), "viewports_changed",
                                 G_CALLBACK (netk_tasklist_viewports_changed),
                                 tasklist, 0);


    g_assert (i == N_SCREEN_CONNECTIONS);

    windows = netk_screen_get_windows (screen);
    while (windows != NULL)
    {
        netk_tasklist_connect_window (tasklist, windows->data);
        windows = windows->next;
    }
}

static void
netk_tasklist_disconnect_screen (NetkTasklist * tasklist)
{
    int i;

    i = 0;
    while (i < N_SCREEN_CONNECTIONS)
    {
        if (tasklist->priv->screen_connections[i] != 0)
            g_signal_handler_disconnect (G_OBJECT (tasklist->priv->screen),
                                         tasklist->priv->
                                         screen_connections[i]);

        tasklist->priv->screen_connections[i] = 0;

        ++i;
    }
}

void
netk_tasklist_set_screen (NetkTasklist * tasklist, NetkScreen * screen)
{
    if (tasklist->priv->screen == screen)
        return;

    if (tasklist->priv->screen)
        netk_tasklist_disconnect_screen (tasklist);

    tasklist->priv->screen = screen;

    netk_tasklist_update_lists (tasklist);

    netk_tasklist_connect_screen (tasklist, screen);
}

GtkWidget *
netk_tasklist_new (NetkScreen * screen)
{
    NetkTasklist *tasklist;

    tasklist = g_object_new (NETK_TYPE_TASKLIST, NULL);

    tasklist->priv->tooltips = gtk_tooltips_new ();
    g_object_ref (G_OBJECT (tasklist->priv->tooltips));
    gtk_object_sink (GTK_OBJECT (tasklist->priv->tooltips));

    netk_tasklist_set_screen (tasklist, screen);

    tasklist->priv->show_label=TRUE;
    tasklist->priv->relief=GTK_RELIEF_NORMAL;

    return GTK_WIDGET (tasklist);
}

static void
netk_tasklist_free_tasks (NetkTasklist * tasklist)
{
    GList *l;

    tasklist->priv->active_task = NULL;
    tasklist->priv->active_class_group = NULL;

    if (tasklist->priv->windows)
    {
        l = tasklist->priv->windows;
        while (l != NULL)
        {
            NetkTask *task = NETK_TASK (l->data);
            l = l->next;
            
            if (task->blink_timeout_id)
            {
                g_source_remove (task->blink_timeout_id);
                task->blink_timeout_id = 0;
            }

            /* if we just unref the task it means we lose our ref to the
             * task before we unparent the button, which breaks stuff.
             */
            gtk_widget_destroy (task->button);
        }
    }
    g_assert (tasklist->priv->windows == NULL);
    g_assert (g_hash_table_size (tasklist->priv->win_hash) == 0);

    if (tasklist->priv->class_groups)
    {
        l = tasklist->priv->class_groups;
        while (l != NULL)
        {
            NetkTask *task = NETK_TASK (l->data);
            l = l->next;

            if (task->blink_timeout_id)
            {
                g_source_remove (task->blink_timeout_id);
                task->blink_timeout_id = 0;
            }

            /* if we just unref the task it means we lose our ref to the
             * task before we unparent the button, which breaks stuff.
             */
            gtk_widget_destroy (task->button);
        }
    }
    g_assert (tasklist->priv->class_groups == NULL);
    g_assert (g_hash_table_size (tasklist->priv->class_group_hash) == 0);
}

/*                                                                              
 * This function determines if a window should be included in the tasklist.     
 */                                                                             
static gboolean                                                                 
netk_tasklist_include_window (NetkTasklist *tasklist, NetkWindow *win)          
{                                                                               
    NetkWorkspace *active_workspace;                                              

    if (netk_window_get_state (win) & NETK_WINDOW_STATE_SKIP_TASKLIST)            
    {
        return FALSE;                                                               
    }

    if (tasklist->priv->include_all_workspaces)
    {
        return TRUE;
    }

    if (netk_window_is_pinned (win))
    {
        return TRUE;
    }

    active_workspace = netk_screen_get_active_workspace (tasklist->priv->screen); 

    if (active_workspace == NULL)
    {
        return TRUE;
    }

    if (active_workspace != netk_window_get_workspace (win))
    {
        return FALSE;
    }

    if (!netk_workspace_is_virtual (active_workspace))
    {
        return TRUE;
    }

    return netk_window_is_in_viewport (win, active_workspace);
}

static void
netk_tasklist_update_lists (NetkTasklist * tasklist)
{
    GList *windows;
    NetkWindow *win;
    NetkClassGroup *class_group;
    GList *l;
    NetkTask *class_group_task;
    NetkTask *win_task;

    netk_tasklist_free_tasks (tasklist);

    windows = netk_screen_get_windows (tasklist->priv->screen);

    l = windows;
    while (l != NULL)
    {
        win = NETK_WINDOW (l->data);
        if (netk_tasklist_include_window (tasklist, win))
        {
            win_task = netk_task_new_from_window (tasklist, win);
            tasklist->priv->windows = g_list_prepend (tasklist->priv->windows, win_task);
            g_hash_table_insert (tasklist->priv->win_hash, win, win_task);

            gtk_widget_set_parent (win_task->button, GTK_WIDGET (tasklist));
            gtk_widget_show (win_task->button);

            /* Class group */

            class_group = netk_window_get_class_group (win);
            class_group_task = g_hash_table_lookup (tasklist->priv->class_group_hash, class_group);

            if (class_group_task == NULL)
            {
                class_group_task = netk_task_new_from_class_group  (tasklist, class_group);
                gtk_widget_set_parent (class_group_task->button, GTK_WIDGET (tasklist));
                gtk_widget_show (class_group_task->button);

                tasklist->priv->class_groups = g_list_prepend (tasklist->priv->class_groups, class_group_task);
                g_hash_table_insert (tasklist->priv->class_group_hash, class_group, class_group_task);
            }

            class_group_task->windows = g_list_prepend (class_group_task->windows, win_task);
        }

        l = l->next;
    }

    /* Sort the class group lists */
    l = tasklist->priv->class_groups;
    while (l)
    {
        class_group_task = NETK_TASK (l->data);

        class_group_task->windows =
            g_list_sort (class_group_task->windows, netk_task_compare);

        /* so the number of windows in the task gets reset on the
         * task label
         */
        netk_task_update_visible_state (class_group_task);


        l = l->next;
    }


    /* since we cleared active_window we need to reset it */
    netk_tasklist_active_window_changed (tasklist->priv->screen, tasklist);

    gtk_widget_queue_resize (GTK_WIDGET (tasklist));
}

static void
netk_tasklist_change_active_task (NetkTasklist * tasklist,
                                  NetkTask * active_task)
{
    if (active_task && active_task == tasklist->priv->active_task)
        return;

    if (tasklist->priv->active_task)
    {
        tasklist->priv->active_task->really_toggling = TRUE;
        gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON
                                      (tasklist->priv->active_task->button),
                                      FALSE);
        tasklist->priv->active_task->really_toggling = FALSE;
    }

    tasklist->priv->active_task = active_task;

    if (tasklist->priv->active_task)
    {
        tasklist->priv->active_task->really_toggling = TRUE;
        gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON
                                      (tasklist->priv->active_task->button),
                                      TRUE);
        tasklist->priv->active_task->really_toggling = FALSE;
    }

    if (active_task)
    {
        active_task =
            g_hash_table_lookup (tasklist->priv->class_group_hash,
                                 active_task->class_group);

        if (active_task && active_task == tasklist->priv->active_class_group)
            return;

        if (tasklist->priv->active_class_group)
        {
            tasklist->priv->active_class_group->really_toggling = TRUE;
            gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON
                                          (tasklist->priv->active_class_group->
                                           button), FALSE);
            tasklist->priv->active_class_group->really_toggling = FALSE;
        }

        tasklist->priv->active_class_group = active_task;

        if (tasklist->priv->active_class_group)
        {
            tasklist->priv->active_class_group->really_toggling = TRUE;
            gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON
                                          (tasklist->priv->active_class_group->
                                           button), TRUE);
            tasklist->priv->active_class_group->really_toggling = FALSE;
        }
    }
}

static void
netk_tasklist_update_icon_geometries (NetkTasklist * tasklist)
{
    gint x, y, width, height;
    GList *list;

    for (list = tasklist->priv->windows; list; list = list->next)
    {
        NetkTask *task = NETK_TASK (list->data);

        if (!GTK_WIDGET_REALIZED (task->button))
            continue;

        gdk_window_get_origin (GTK_BUTTON (task->button)->event_window, &x, &y);
        width = task->button->allocation.width;
        height = task->button->allocation.height;

        netk_window_set_icon_geometry (task->window, x, y, width, height);
    }
}

static void
netk_tasklist_active_window_changed (NetkScreen * screen,
                                     NetkTasklist * tasklist)
{
    NetkWindow *active_window;
    NetkTask *active_task;
    
    active_window = netk_screen_get_active_window (screen);

    active_task =
        g_hash_table_lookup (tasklist->priv->win_hash, active_window);

    netk_tasklist_change_active_task (tasklist, active_task);
}

static void
netk_tasklist_active_workspace_changed (NetkScreen * screen,
                                        NetkTasklist * tasklist)
{
    netk_tasklist_update_lists (tasklist);
    gtk_widget_queue_resize (GTK_WIDGET (tasklist));
}

static void
netk_tasklist_window_changed_workspace (NetkWindow * window,
                                        NetkTasklist * tasklist)
{
    NetkWorkspace *active_ws;
    NetkWorkspace *window_ws;
    gboolean need_update;
    GList *l;

    active_ws = netk_screen_get_active_workspace (tasklist->priv->screen);
    window_ws = netk_window_get_workspace (window);

    if (!window_ws)
        return;

    need_update = FALSE;

    if (active_ws == window_ws)
        need_update = TRUE;

    l = tasklist->priv->windows;
    while (!need_update && l != NULL)
    {
        NetkTask *task = l->data;

        if (task->type == NETK_TASK_WINDOW && task->window == window)
            need_update = TRUE;

        l = l->next;
    }

    if (need_update)
    {
        netk_tasklist_update_lists (tasklist);
        gtk_widget_queue_resize (GTK_WIDGET (tasklist));
    }
}

static gboolean
do_netk_tasklist_update_lists (gpointer data)
{
    NetkTasklist *tasklist = NETK_TASKLIST (data);
    tasklist->priv->idle_callback_tag = 0;
    netk_tasklist_update_lists (tasklist);
    return FALSE;
}

static void
netk_tasklist_window_changed_geometry (NetkWindow   *window,
                                       NetkTasklist *tasklist)
{
    NetkTask *win_task;
    gboolean show;
    if (tasklist->priv->idle_callback_tag != 0)
    {
        return;
    }
    
    /*
     * We want to re-generate the task list if
     * the window is shown but shouldn't be or
     * the window isn't shown but should be.
     */

    win_task = g_hash_table_lookup (tasklist->priv->win_hash, window);
    show = netk_tasklist_include_window(tasklist, window);
    if ((win_task == NULL && !show) || (win_task != NULL && show))
    {
        return;
    }
    
    /* Don't keep any stale references */
    gtk_widget_queue_draw (GTK_WIDGET (tasklist));
    tasklist->priv->idle_callback_tag = 
                g_idle_add (do_netk_tasklist_update_lists, tasklist);
}

static void
netk_tasklist_connect_window (NetkTasklist * tasklist, NetkWindow * window)
{
    g_signal_connect_object (window, "workspace_changed",
                             G_CALLBACK
                             (netk_tasklist_window_changed_workspace),
                             tasklist, 0);
    g_signal_connect_object (window, "geometry_changed",
                             G_CALLBACK 
                             (netk_tasklist_window_changed_geometry),
                             tasklist, 0);
}

static void
netk_tasklist_window_added (NetkScreen * screen, NetkWindow * win,
                            NetkTasklist * tasklist)
{
    netk_tasklist_connect_window (tasklist, win);

    netk_tasklist_update_lists (tasklist);
    gtk_widget_queue_resize (GTK_WIDGET (tasklist));
}

static void
netk_tasklist_window_removed (NetkScreen * screen, NetkWindow * win,
                              NetkTasklist * tasklist)
{
    netk_tasklist_update_lists (tasklist);
    gtk_widget_queue_resize (GTK_WIDGET (tasklist));
}

static void
netk_tasklist_viewports_changed (NetkScreen * screen, NetkTasklist * tasklist)
{
    netk_tasklist_update_lists (tasklist);
    gtk_widget_queue_resize (GTK_WIDGET (tasklist));
}

static void
netk_task_position_menu (GtkMenu * menu, gint * x, gint * y,
                         gboolean * push_in, gpointer user_data)
{
    GtkWidget *widget = GTK_WIDGET (user_data);
    GtkRequisition requisition;
    gint menu_xpos;
    gint menu_ypos;

    gtk_widget_size_request (GTK_WIDGET (menu), &requisition);

    gdk_window_get_origin (widget->window, &menu_xpos, &menu_ypos);

    menu_xpos += widget->allocation.x;
    menu_ypos += widget->allocation.y;

    if (menu_ypos > gdk_screen_height () / 2)
        menu_ypos -= requisition.height;
    else
        menu_ypos += widget->allocation.height;

    /* *x = menu_xpos; */
    *y = menu_ypos;
    *push_in = TRUE;
}

static void
netk_task_menu_activated (GtkMenuItem * menu_item, gpointer data)
{
    NetkTask *task = NETK_TASK (data);

    netk_tasklist_activate_task_window (task);
}

static void
netk_tasklist_activate_task_window (NetkTask * task)
{
    NetkTasklist *tasklist;
    NetkWindowState state;

    tasklist = task->tasklist;

    if (task->window == NULL)
        return;

    state = netk_window_get_state (task->window);

    if (state & NETK_WINDOW_STATE_MINIMIZED)
    {
        NetkWorkspace *active_ws;
        NetkWorkspace *window_ws;

        active_ws = netk_screen_get_active_workspace (tasklist->priv->screen);
        window_ws = netk_window_get_workspace (task->window);
        if ((window_ws && active_ws != window_ws)
            && (!tasklist->priv->switch_workspace_on_unminimize))
        {
            netk_workspace_activate (window_ws);
        }
        netk_window_activate (task->window);
    }
    else
    {
        if (task->was_active)
        {
            task->was_active = FALSE;
            netk_window_minimize (task->window);
            return;
        }
        else
        {
            NetkWorkspace *window_ws;

            window_ws = netk_window_get_workspace (task->window);
            if (window_ws)
            {
                netk_workspace_activate (window_ws);
            }

            netk_window_activate (task->window);
        }
    }

    netk_tasklist_change_active_task (tasklist, task);
}

static void
netk_task_popup_menu (NetkTask * task, gboolean action_submenu)
{
    GtkWidget *menu;
    NetkTask *win_task;
    char *text;
    GdkPixbuf *pixbuf;
    GtkWidget *menu_item;
    GtkWidget *image;
    GList *l, *list;

    if (task->class_group == NULL)
        return;

    if (task->menu == NULL)
        task->menu = gtk_menu_new ();

    menu = task->menu;

    /* Remove old menu content */
    list = gtk_container_get_children (GTK_CONTAINER (menu));
    l = list;
    while (l)
    {
        GtkWidget *child = GTK_WIDGET (l->data);
        gtk_container_remove (GTK_CONTAINER (menu), child);
        l = l->next;
    }
    g_list_free (list);

    l = task->windows;
    while (l)
    {
        win_task = NETK_TASK (l->data);

        text = netk_task_get_text (win_task);
        menu_item = gtk_image_menu_item_new_with_label (text);
        if (netk_task_get_demands_attention (win_task))
            p_make_gtk_label_bold (GTK_LABEL (GTK_BIN (menu_item)->child));
        g_free (text);

        pixbuf = netk_task_get_icon (win_task);
        if (pixbuf)
        {
            image = gtk_image_new_from_pixbuf (pixbuf);
            gtk_widget_show (image);
            gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (menu_item),
                                           image);
            g_object_unref (G_OBJECT (pixbuf));
        }

        gtk_widget_show (menu_item);

       if (action_submenu)
         gtk_menu_item_set_submenu (GTK_MENU_ITEM (menu_item),
                                    netk_create_window_action_menu (win_task->window));
       else
        g_signal_connect_object (G_OBJECT (menu_item), "activate",
                                 G_CALLBACK (netk_task_menu_activated),
                                 G_OBJECT (win_task), 0);

        gtk_menu_shell_append (GTK_MENU_SHELL (menu), menu_item);

        l = l->next;
    }

    gtk_menu_set_screen (GTK_MENU (menu), xfce_gdk_display_locate_monitor_with_pointer (NULL, NULL));
    gtk_widget_show (menu);
    gtk_menu_popup (GTK_MENU (menu), NULL, NULL, netk_task_position_menu,
                    task->button, 1, gtk_get_current_event_time ());
}

static void
netk_task_button_toggled (GtkButton * button, NetkTask * task)
{
    /* Did we really want to change the state of the togglebutton? */
    if (task->really_toggling)
        return;

    /* Undo the toggle */
    task->really_toggling = TRUE;
    gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button),
                                  !gtk_toggle_button_get_active
                                  (GTK_TOGGLE_BUTTON (button)));
    task->really_toggling = FALSE;

    switch (task->type)
    {
        case NETK_TASK_CLASS_GROUP:
            netk_task_popup_menu (task, FALSE);
            break;
        case NETK_TASK_WINDOW:
            if (task->window == NULL)
                return;
            netk_tasklist_activate_task_window (task);
            break;
        default:
            break;
    }
}

static char *
netk_task_get_text (NetkTask * task)
{
    NetkWindowState state;
    const char *name;

    switch (task->type)
    {
        case NETK_TASK_CLASS_GROUP:
            name = netk_class_group_get_name (task->class_group);
            if (name[0] != 0)
                return g_strdup_printf ("%s (%d)", name, g_list_length (task->windows));
            else
                return g_strdup_printf ("(%d)", g_list_length (task->windows));
            break;
        case NETK_TASK_WINDOW:
            state = netk_window_get_state (task->window);
            name = netk_window_get_name (task->window);

            if (state & NETK_WINDOW_STATE_MINIMIZED)
                return g_strdup_printf ("[%s]", name);
            else
                return g_strdup (name);
            break;
        default:
            break;
    }

    return NULL;
}

static void
netk_dimm_icon (GdkPixbuf * pixbuf)
{
    int x, y, pixel_stride, row_stride;
    guchar *row, *pixels;
    int w, h;

    if (!pixbuf)
        return;
        
    w = gdk_pixbuf_get_width (pixbuf);
    h = gdk_pixbuf_get_height (pixbuf);

    g_assert (gdk_pixbuf_get_has_alpha (pixbuf));

    pixel_stride = 4;

    row = gdk_pixbuf_get_pixels (pixbuf);
    row_stride = gdk_pixbuf_get_rowstride (pixbuf);

    for (y = 0; y < h; y++)
    {
        pixels = row;

        for (x = 0; x < w; x++)
        {
            pixels[3] /= 2;

            pixels += pixel_stride;
        }

        row += row_stride;
    }
}

static GdkPixbuf *
netk_task_scale_icon (GdkPixbuf * orig, gboolean minimized)
{
    int w, h;
    GdkPixbuf *pixbuf;

    if (!orig)
        return NULL;

    w = gdk_pixbuf_get_width (orig);
    h = gdk_pixbuf_get_height (orig);
    

    if (h != MINI_ICON_SIZE || !gdk_pixbuf_get_has_alpha (orig))
    {
        double scale;

        pixbuf =
            gdk_pixbuf_new (GDK_COLORSPACE_RGB, TRUE, 8,
                            MINI_ICON_SIZE * w / (double) h, MINI_ICON_SIZE);

        scale = MINI_ICON_SIZE / (double) gdk_pixbuf_get_height (orig);

        gdk_pixbuf_scale (orig, pixbuf, 0, 0, gdk_pixbuf_get_width (pixbuf),
                          gdk_pixbuf_get_height (pixbuf), 0, 0, scale, scale,
                          GDK_INTERP_HYPER);
    }
    else
        pixbuf = orig;

    if (minimized)
    {
        if (orig == pixbuf)
            pixbuf = gdk_pixbuf_copy (orig);

        netk_dimm_icon (pixbuf);
    }

    if (orig == pixbuf)
        g_object_ref (G_OBJECT (pixbuf));

    return pixbuf;
}


static GdkPixbuf *
netk_task_get_icon (NetkTask * task)
{
    NetkWindowState state;
    GdkPixbuf *pixbuf;

    pixbuf = NULL;

    switch (task->type)
    {
        case NETK_TASK_CLASS_GROUP:
            pixbuf =
                netk_task_scale_icon (netk_class_group_get_mini_icon
                                      (task->class_group), FALSE);
            break;
        case NETK_TASK_WINDOW:
            state = netk_window_get_state (task->window);

            pixbuf =
                netk_task_scale_icon (netk_window_get_mini_icon
                                      (task->window),
                                      state & NETK_WINDOW_STATE_MINIMIZED);
            break;
        default:
            break;
    }

    return pixbuf;
}

static gboolean
netk_task_get_demands_attention (NetkTask * task)
{
    GList *l;
    NetkTask *win_task;
    gboolean demands_attention = FALSE;

    switch (task->type)
    {
        case NETK_TASK_CLASS_GROUP:
            l = task->windows;
            while (l)
            {
                win_task = NETK_TASK (l->data);

                if (netk_window_or_transient_demands_attention
                    (win_task->window))
                {
                    demands_attention = TRUE;
                    break;
                }

                l = l->next;
            }
            break;

        case NETK_TASK_WINDOW:
            demands_attention =
                netk_window_or_transient_demands_attention (task->window);
            break;
    }

    return demands_attention != FALSE;
}

static void
netk_task_update_visible_state (NetkTask * task)
{
    GdkPixbuf *pixbuf;
    char *text;
    gboolean urgent = netk_task_get_demands_attention (task);

    pixbuf = netk_task_get_icon (task);
    gtk_image_set_from_pixbuf (GTK_IMAGE (task->image), pixbuf);
    if (pixbuf)
        g_object_unref (G_OBJECT (pixbuf));
    
    text = netk_task_get_text (task);
    if (text != NULL)
    {
        if (task->tasklist->priv->show_label && task->label)
        {
            gtk_label_set_text (GTK_LABEL (task->label), text);
            if (urgent)
            {
                p_make_gtk_label_bold ((GTK_LABEL (task->label)));
            }
            else
            {
                p_make_gtk_label_normal ((GTK_LABEL (task->label)));
            }
        }
        gtk_tooltips_set_tip (task->tasklist->priv->tooltips, task->button,
                              text, NULL);
        g_free (text);
    }
    if (urgent)
        queue_urgent_timeout(task);
    else
        unqueue_urgent_timeout(task);

    gtk_widget_queue_resize (GTK_WIDGET (task->tasklist));
}

static void
netk_task_state_changed (NetkWindow * window, NetkWindowState changed_mask,
                         NetkWindowState new_state, gpointer data)
{
    NetkTasklist *tasklist = NETK_TASKLIST (data);
    
    if (changed_mask & NETK_WINDOW_STATE_SKIP_TASKLIST)
    {
        netk_tasklist_update_lists (tasklist);
        gtk_widget_queue_resize (GTK_WIDGET (tasklist));
        return;
    }

    if (changed_mask & NETK_WINDOW_STATE_MINIMIZED              ||
        changed_mask & NETK_WINDOW_STATE_URGENT                 || 
        changed_mask & NETK_WINDOW_STATE_DEMANDS_ATTENTION      )
    {
        NetkTask *win_task = NULL, *class_group_task = NULL;

        win_task = g_hash_table_lookup (tasklist->priv->win_hash, window);
    
        if (win_task)
        {
            class_group_task = 
                g_hash_table_lookup (tasklist->priv->class_group_hash, 
                                     win_task->class_group);
        }

        if (class_group_task)
        {
            netk_task_update_visible_state (class_group_task);
        }

        netk_task_update_visible_state (win_task);
    }
}

static void
netk_task_icon_changed (NetkWindow * window, gpointer data)
{
    NetkTask *task = NETK_TASK (data);

    if (task)
        netk_task_update_visible_state (task);
}

static void
netk_task_name_changed (NetkWindow * window, gpointer data)
{
    NetkTask *task = NETK_TASK (data);

    if (task)
        netk_task_update_visible_state (task);
}

static void
netk_task_class_name_changed (NetkClassGroup * class_group, gpointer data)
{
    NetkTask *task = NETK_TASK (data);

    if (task)
        netk_task_update_visible_state (task);
}

static void
netk_task_class_icon_changed (NetkClassGroup * class_group, gpointer data)
{
    NetkTask *task = NETK_TASK (data);

    if (task)
        netk_task_update_visible_state (task);
}

static gboolean
netk_task_motion_timeout (gpointer data)
{
    NetkTask *task = NETK_TASK (data);

    task->button_activate = 0;

    netk_window_activate (task->window);

    return FALSE;
}

static void
netk_task_drag_leave (GtkWidget * widget, GdkDragContext * context,
                      guint time, NetkTask * task)
{
    if (task->button_activate != 0)
    {
        g_source_remove (task->button_activate);
        task->button_activate = 0;
    }
}

static gboolean
netk_task_drag_motion (GtkWidget * widget, GdkDragContext * context, gint x,
                       gint y, guint time, NetkTask * task)
{

    if (task->button_activate == 0 && task->type == NETK_TASK_WINDOW)
        task->button_activate = g_timeout_add (TIMEOUT_ACTIVATE,
                                               netk_task_motion_timeout,
                                               task);
    gdk_drag_status (context, 0, time);

    return TRUE;
}

static gboolean
netk_task_button_press_event (GtkWidget * widget,
                              GdkEventButton * event, gpointer data)
{
    NetkTask *task = NETK_TASK (data);

    switch (task->type)
    {
        case NETK_TASK_CLASS_GROUP:
            if (event->button == 1)
            {
                netk_task_popup_menu (task, FALSE);
                return TRUE;
            }
            else if (event->button == 3)
            {
                netk_task_popup_menu (task, TRUE);
                return TRUE;
            }
            break;

        case NETK_TASK_WINDOW:
            if (event->button == 1)
            {
                if (netk_window_is_active (task->window))
                    task->was_active = TRUE;
                else
                    task->was_active = FALSE;

                return FALSE;
            }
            else if (event->button == 3)
            {
              if (task->action_menu)
                gtk_widget_destroy (task->action_menu);

              g_assert (task->action_menu == NULL);

              task->action_menu = netk_create_window_action_menu (task->window);

              g_object_add_weak_pointer (G_OBJECT (task->action_menu),
                                         (void**) &task->action_menu);

	      gtk_menu_set_screen (GTK_MENU (task->action_menu), 
				   xfce_gdk_display_locate_monitor_with_pointer (NULL, NULL));

              gtk_widget_show (task->action_menu);
              gtk_menu_popup (GTK_MENU (task->action_menu),
                              NULL, NULL,
                              netk_task_position_menu, task->button,
                              event->button,
                              gtk_get_current_event_time ());

              /* FIXME we should really arrange to destroy the menu on popdown */

              return TRUE;
            }
            break;
        default:
            break;
    }

    return FALSE;
}

static void
netk_task_create_widgets (NetkTask * task)
{
    GtkWidget *table;
    GdkPixbuf *pixbuf;
    char *text;

    task->button = gtk_toggle_button_new ();
    task->button_activate = 0;
    gtk_button_set_relief (GTK_BUTTON (task->button), task->tasklist->priv->relief);

    g_object_add_weak_pointer (G_OBJECT (task->button),
                               (void **) &task->button);

    gtk_widget_set_name (task->button, "tasklist-button");

    gtk_drag_dest_set (GTK_WIDGET (task->button), 0, NULL, 0, 0);

    table = gtk_table_new (1, 2, FALSE);

    pixbuf = netk_task_get_icon (task);
    if (pixbuf)
    {
        task->image = gtk_image_new_from_pixbuf (pixbuf);
        g_object_unref (G_OBJECT (pixbuf));
    }
    else
    {
        task->image = gtk_image_new ();
    }

    gtk_widget_show (task->image);

    text = netk_task_get_text (task);

    gtk_table_attach (GTK_TABLE (table),
                      task->image, 0, 1, 0, 1, 0, GTK_EXPAND, 0, 0);

    if (task->tasklist->priv->show_label) {
        gtk_table_set_col_spacings (GTK_TABLE (table), 4);
        task->label = gtk_label_new (text);
        gtk_misc_set_alignment (GTK_MISC (task->label), 0.5, 0.5);
        gtk_label_set_ellipsize (GTK_LABEL (task->label), PANGO_ELLIPSIZE_END);
        gtk_widget_show (task->label);
        gtk_table_attach (GTK_TABLE (table), task->label, 1, 2, 0, 1, 
                          GTK_EXPAND|GTK_FILL|GTK_SHRINK, GTK_EXPAND, 0, 0);
    }
    else
    {
        gtk_table_set_col_spacings (GTK_TABLE (table), 0);
        task->label = NULL;
    }

    gtk_container_add (GTK_CONTAINER (task->button), table);
    gtk_widget_show (table);

    gtk_tooltips_set_tip (task->tasklist->priv->tooltips, task->button, text,
                          NULL);
    g_free (text);

    /* Set up signals */
    if (GTK_IS_TOGGLE_BUTTON (task->button))
        g_signal_connect_object (G_OBJECT (task->button), "toggled",
                                 G_CALLBACK (netk_task_button_toggled),
                                 G_OBJECT (task), 0);


    g_signal_connect_object (G_OBJECT (task->button), "button_press_event",
                             G_CALLBACK (netk_task_button_press_event),
                             G_OBJECT (task), 0);

    g_signal_connect_object (G_OBJECT (task->button), "drag_motion",
                             G_CALLBACK (netk_task_drag_motion),
                             G_OBJECT (task), 0);

    g_signal_connect_object (G_OBJECT (task->button), "drag_leave",
                             G_CALLBACK (netk_task_drag_leave),
                             G_OBJECT (task), 0);

    switch (task->type)
    {
        case NETK_TASK_CLASS_GROUP:
            task->class_name_changed_tag =
                g_signal_connect (G_OBJECT (task->class_group),
                                  "name_changed",
                                  G_CALLBACK (netk_task_class_name_changed),
                                  task);
            task->class_icon_changed_tag =
                g_signal_connect (G_OBJECT (task->class_group),
                                  "icon_changed",
                                  G_CALLBACK (netk_task_class_icon_changed),
                                  task);
            break;
        case NETK_TASK_WINDOW:
            task->state_changed_tag =
                g_signal_connect (G_OBJECT (task->window), "state_changed",
                                  G_CALLBACK (netk_task_state_changed),
                                  task->tasklist);
            task->icon_changed_tag =
                g_signal_connect (G_OBJECT (task->window), "icon_changed",
                                  G_CALLBACK (netk_task_icon_changed), task);
            task->name_changed_tag =
                g_signal_connect (G_OBJECT (task->window), "name_changed",
                                  G_CALLBACK (netk_task_name_changed), task);
            break;
        default:
            break;
    }
}

static gboolean
urgent_timeout_callback (gpointer data)
{
    NetkTask *task= (NetkTask *) data; 
    GtkStyle *style;
    GtkRcStyle *mod;
    GdkColor c;

    g_return_val_if_fail (task, FALSE);

    if(netk_task_get_demands_attention (task))
    {
        if(task->blink_count > MAX_URGENT_BLINK_COUNT)
        {
            return TRUE;
        }
        else
        {
            ++task->blink_count;
        }
        
        style = gtk_widget_get_style (task->button);
        mod = gtk_widget_get_modifier_style (task->button);
        c = style->bg[GTK_STATE_SELECTED];
        if(mod->color_flags[GTK_STATE_NORMAL] & GTK_RC_BG)
        {
            gtk_button_set_relief (GTK_BUTTON (task->button), 
                                   task->tasklist->priv->relief);
            mod->color_flags[GTK_STATE_NORMAL] &= ~(GTK_RC_BG);
            gtk_widget_modify_style(task->button, mod);
        }
        else
        {
            gtk_button_set_relief (GTK_BUTTON (task->button), 
                                   GTK_RELIEF_NORMAL);
            mod->color_flags[GTK_STATE_NORMAL] |= GTK_RC_BG;
            mod->bg[GTK_STATE_NORMAL] = c;
            gtk_widget_modify_style(task->button, mod);
        }

        if(task->tasklist->priv->show_label && task->label)
        {
            style = gtk_widget_get_style (task->label);
            mod = gtk_widget_get_modifier_style (task->label);
            c = style->fg[GTK_STATE_SELECTED];
            
            if(mod->color_flags[GTK_STATE_NORMAL] & GTK_RC_FG)
            {
                mod->color_flags[GTK_STATE_NORMAL] &= ~(GTK_RC_FG);
                gtk_widget_modify_style(task->label, mod);
            }
            else
            {
                mod->color_flags[GTK_STATE_NORMAL] |= GTK_RC_FG;
                mod->fg[GTK_STATE_NORMAL] = c;
                gtk_widget_modify_style(task->label, mod);
            }

            p_make_gtk_label_bold (GTK_LABEL (task->label));
        }
    }
    else if(task->tasklist->priv->show_label && task->label)
    {
        unqueue_urgent_timeout (task);
    }

    return (netk_task_get_demands_attention (task));
}
 
static void  
queue_urgent_timeout(gpointer data)
{
    NetkTask *task = data;

    if (task->blink_timeout_id == 0)
    {
        task->blink_timeout_id = 
                    g_timeout_add (500, urgent_timeout_callback, task);
    }
}

static void  
unqueue_urgent_timeout(gpointer data)
{
    NetkTask *task = data;

    if (task->blink_timeout_id > 0)
    {
        GtkRcStyle *style; 

        g_source_remove (task->blink_timeout_id);

        gtk_button_set_relief (GTK_BUTTON (task->button), 
                               task->tasklist->priv->relief);
        style = gtk_widget_get_modifier_style (task->button);
        style->color_flags[GTK_STATE_NORMAL] = 0;
        gtk_widget_modify_style (task->button, style);
        if(task->tasklist->priv->show_label && task->label)
        {
            style = gtk_widget_get_modifier_style (task->label);
            style->color_flags[GTK_STATE_NORMAL] = 0;
            gtk_widget_modify_style (task->label, style);
            p_make_gtk_label_normal (GTK_LABEL (task->label));
        }
    }
    task->blink_timeout_id = 0;
    task->blink_count = 0;
}

static void
draw_dot (GdkWindow * window, GdkGC * lgc, GdkGC * dgc, int x, int y)
{
    gdk_draw_point (window, dgc, x, y);
    gdk_draw_point (window, lgc, x + 1, y + 1);
}


gboolean
netk_task_class_group_expose (GtkWidget * widget, GdkEventExpose * event,
                              gpointer data)
{
    GtkStyle *style;
    GdkGC *lgc, *dgc;
    int x, y, i, j;

    style = widget->style;

    lgc = style->light_gc[GTK_STATE_NORMAL];
    dgc = style->dark_gc[GTK_STATE_NORMAL];

    x = widget->allocation.x + widget->allocation.width -
        (GTK_CONTAINER (widget)->border_width + style->ythickness + 10);
    y = widget->allocation.y + style->xthickness + 2;

    for (i = 0; i < 3; i++)
    {
        for (j = i; j < 3; j++)
        {
            draw_dot (widget->window, lgc, dgc, x + j * 3, y + i * 3);
        }
    }
    return FALSE;
}

static gint
compare_class_group_tasks (NetkTask *task1, NetkTask *task2)
{
    const char *name1, *name2;

    name1 = netk_class_group_get_name (task1->class_group);
    name2 = netk_class_group_get_name (task2->class_group);

    return g_utf8_collate (name1, name2);
}


static gint
netk_task_compare (gconstpointer a, gconstpointer b)
{
    NetkTask *task1 = NETK_TASK (a);
    NetkTask *task2 = NETK_TASK (b);
    gulong xid1_1, xid1_2;
    gulong xid2_1, xid2_2;

    xid1_1 = xid1_2 = xid2_1 = xid2_2 = 0;      /* silence compiler */

    switch (task1->type)
    {
        case NETK_TASK_CLASS_GROUP:
            if (task2->type == NETK_TASK_CLASS_GROUP)
                return compare_class_group_tasks (task1, task2);
            else
                return -1; /* Sort groups before everything else */
            break;
        case NETK_TASK_WINDOW:
            xid1_1 = netk_window_get_group_leader (task1->window);
            xid1_2 = netk_window_get_xid (task1->window);
            break;
        default:
            break;
    }

    switch (task2->type)
    {
        case NETK_TASK_CLASS_GROUP:
            if (task1->type == NETK_TASK_CLASS_GROUP)
                return compare_class_group_tasks (task1, task2);
            else
                return 1; /* Sort groups before everything else */
            break;
        case NETK_TASK_WINDOW:
            xid2_1 = netk_window_get_group_leader (task2->window);
            xid2_2 = netk_window_get_xid (task2->window);
            break;
        default:
            break;
    }

    if ((xid1_1 < xid2_1) || ((xid1_1 == xid2_1) && (xid1_2 < xid2_2)))
        return -1;
    else if ((xid1_1 == xid2_1) && (xid1_2 == xid2_2))
        return 0;
    else
        return 1;
}

static NetkTask *
netk_task_new_from_window (NetkTasklist * tasklist, NetkWindow * window)
{
    NetkTask *task;

    task = g_object_new (NETK_TYPE_TASK, NULL);

    task->type = NETK_TASK_WINDOW;
    task->window = g_object_ref (G_OBJECT (window));
    task->class_group = g_object_ref (G_OBJECT (netk_window_get_class_group (window)));
    task->tasklist = tasklist;
    task->blink_timeout_id = 0;

    netk_task_create_widgets (task);
    
    if (netk_task_get_demands_attention (task))
    {
        queue_urgent_timeout (task);
    }

    return task;
}

static NetkTask *
netk_task_new_from_class_group (NetkTasklist * tasklist,
                                NetkClassGroup * class_group)
{
    NetkTask *task;

    task = g_object_new (NETK_TYPE_TASK, NULL);

    task->type = NETK_TASK_CLASS_GROUP;
    task->class_group = g_object_ref (G_OBJECT (class_group));
    task->window = NULL;
    task->tasklist = tasklist;
    task->blink_timeout_id = 0;

    netk_task_create_widgets (task);

    g_signal_connect_object (task->button, "expose_event",
                             G_CALLBACK (netk_task_class_group_expose),
                             G_OBJECT (task), G_CONNECT_AFTER);

    if (netk_task_get_demands_attention (task))
    {
        queue_urgent_timeout (task);
    }

    return task;
}

NetkScreen*
netk_tasklist_get_screen(NetkTasklist * tasklist)
{
    g_return_val_if_fail (NETK_IS_TASKLIST (tasklist), NULL);

    return tasklist->priv->screen;
}

NetkTasklistGroupingType
netk_tasklist_get_grouping(NetkTasklist * tasklist)
{
    g_return_val_if_fail (NETK_IS_TASKLIST (tasklist), NETK_TASKLIST_NEVER_GROUP);

    return tasklist->priv->grouping;
}

gboolean 
netk_tasklist_get_switch_workspace_on_unminimize(NetkTasklist * tasklist)
{
    g_return_val_if_fail (NETK_IS_TASKLIST (tasklist), FALSE);

    return tasklist->priv->switch_workspace_on_unminimize;
}

gint 
netk_tasklist_get_grouping_limit(NetkTasklist * tasklist)
{
    g_return_val_if_fail (NETK_IS_TASKLIST (tasklist), 0);

    return tasklist->priv->grouping_limit;
}

gboolean 
netk_tasklist_get_include_all_workspaces(NetkTasklist * tasklist)
{
    g_return_val_if_fail (NETK_IS_TASKLIST (tasklist), FALSE);

    return tasklist->priv->include_all_workspaces;
}

gboolean 
netk_tasklist_get_show_label(NetkTasklist * tasklist)
{
    g_return_val_if_fail (NETK_IS_TASKLIST (tasklist), FALSE);

    return tasklist->priv->show_label;
}

GtkReliefStyle 
netk_tasklist_get_button_relief(NetkTasklist * tasklist)
{
    g_return_val_if_fail (NETK_IS_TASKLIST (tasklist), GTK_RELIEF_NONE);
    
    return tasklist->priv->relief;
}

