/* pager object */

/*
 * Copyright (C) 2001 Havoc Pennington
 * Copyright (C) 2003 Kim Woelders
 * Copyright (C) 2003 Red Hat, Inc.
 *
 * 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 GDK_MULTIHEAD_SAFE
#undef GDK_MULTIHEAD_SAFE
#endif

#include "netk-private.h"
#include "netk-pager.h"
#include "netk-workspace.h"
#include "netk-window.h"
#include "netk-xutils.h"


#define N_SCREEN_CONNECTIONS 11

#define NETK_PAGER_GET_PRIVATE(o)    (G_TYPE_INSTANCE_GET_PRIVATE ((o), NETK_TYPE_PAGER, NetkPagerPrivate))

struct _NetkPagerPrivate
{
    NetkScreen *screen;

    int n_rows;                 /* really columns for vertical orientation */
    NetkPagerDisplayMode display_mode;
    gboolean show_all_workspaces;
    gboolean workspace_scrolling;
    GtkShadowType shadow_type;

    GtkOrientation orientation;
    int workspace_size;
    guint screen_connections[N_SCREEN_CONNECTIONS];
    int drag_start_x;
    int drag_start_y;
    int drag_start_x_workspace_relative;
    int drag_start_y_workspace_relative;
    NetkWindow *drag_window;
    int drag_window_x;
    int drag_window_y;
    NetkWindow *action_window;
    int action_click_x;
    int action_click_y;
    guint dragging:1;

    GdkPixbuf *bg_cache;

    int layout_manager_token;
    int ws_activate;
    NetkWorkspace *ws_to_activate;
};

#define POINT_IN_RECT(xcoord, ycoord, rect) \
 ((xcoord) >= (rect).x &&                   \
  (xcoord) <  ((rect).x + (rect).width) &&  \
  (ycoord) >= (rect).y &&                   \
  (ycoord) <  ((rect).y + (rect).height))

static void netk_pager_init (NetkPager * pager);
static void netk_pager_class_init (NetkPagerClass * klass);
static void netk_pager_finalize (GObject * object);

static void netk_pager_realize (GtkWidget * widget);
static void netk_pager_unrealize (GtkWidget * widget);
static void netk_pager_size_request (GtkWidget * widget,
                                     GtkRequisition * requisition);
static void netk_pager_size_allocate (GtkWidget * widget,
                                      GtkAllocation * allocation);
static gboolean netk_pager_expose_event (GtkWidget * widget,
                                         GdkEventExpose * event);
static gboolean netk_pager_button_press (GtkWidget * widget,
                                         GdkEventButton * event);
static gboolean netk_pager_motion (GtkWidget * widget,
                                   GdkEventMotion * event);
static gboolean netk_pager_scroll_event (GtkWidget * widget,
                                         GdkEventScroll * event);
static gboolean netk_pager_button_release (GtkWidget * widget,
                                           GdkEventButton * event);
static gboolean netk_pager_focus (GtkWidget * widget,
                                  GtkDirectionType direction);
static void workspace_name_changed_callback (NetkWorkspace * workspace,
                                             gpointer data);

static void netk_pager_connect_screen (NetkPager * pager,
                                       NetkScreen * screen);
static void netk_pager_connect_window (NetkPager * pager,
                                       NetkWindow * window);
static void netk_pager_disconnect_screen (NetkPager * pager);

static void netk_pager_set_layout_hint (NetkPager * pager);

static void netk_pager_clear_drag (NetkPager * pager);

static GdkPixbuf *netk_pager_get_background (NetkPager * pager, int width,
                                             int height);

static GObjectClass *parent_class;

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

    if (G_UNLIKELY (type == G_TYPE_INVALID))
    {
        type = _netk_g_type_register_simple (GTK_TYPE_WIDGET,
                                             "NetkPager",
                                             sizeof (NetkPagerClass),
                                             netk_pager_class_init,
                                             sizeof (NetkPager),
                                             netk_pager_init,
                                             0);                                    
    }

    return type;
}

static void
netk_pager_init (NetkPager * pager)
{
    pager->priv = NETK_PAGER_GET_PRIVATE (pager);
    
    pager->priv->n_rows = 1;
    pager->priv->display_mode = NETK_PAGER_DISPLAY_CONTENT;
    pager->priv->show_all_workspaces = TRUE;
    pager->priv->workspace_scrolling = TRUE;
    pager->priv->shadow_type = GTK_SHADOW_NONE;
    pager->priv->orientation = GTK_ORIENTATION_HORIZONTAL;
    pager->priv->workspace_size = 48;
    pager->priv->bg_cache = NULL;
    pager->priv->layout_manager_token = NETK_NO_MANAGER_TOKEN;

    GTK_WIDGET_SET_FLAGS (GTK_WIDGET (pager), GTK_CAN_FOCUS);
}

static void
netk_pager_class_init (NetkPagerClass * klass)
{
    GObjectClass *object_class = G_OBJECT_CLASS (klass);
    GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
    
    g_type_class_add_private (klass, sizeof (NetkPagerPrivate));

    parent_class = g_type_class_peek_parent (klass);

    object_class->finalize = netk_pager_finalize;

    widget_class->realize = netk_pager_realize;
    widget_class->unrealize = netk_pager_unrealize;
    widget_class->size_request = netk_pager_size_request;
    widget_class->size_allocate = netk_pager_size_allocate;
    widget_class->expose_event = netk_pager_expose_event;
    widget_class->button_press_event = netk_pager_button_press;
    widget_class->button_release_event = netk_pager_button_release;
    widget_class->scroll_event = netk_pager_scroll_event;
    widget_class->motion_notify_event = netk_pager_motion;
    widget_class->focus = netk_pager_focus;
}

static void
netk_pager_finalize (GObject * object)
{
    NetkPager *pager;

    pager = NETK_PAGER (object);

    netk_pager_disconnect_screen (pager);

    if (pager->priv->bg_cache)
    {
        g_object_unref (G_OBJECT (pager->priv->bg_cache));
        pager->priv->bg_cache = NULL;
    }

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

static void
netk_pager_realize (GtkWidget * widget)
{

    GdkWindowAttr attributes;
    gint attributes_mask;
    NetkPager *pager;

    pager = NETK_PAGER (widget);

    GTK_WIDGET_SET_FLAGS (widget, GTK_REALIZED);

    attributes.window_type = GDK_WINDOW_CHILD;
    attributes.x = widget->allocation.x;
    attributes.y = widget->allocation.y;
    attributes.width = widget->allocation.width;
    attributes.height = widget->allocation.height;
    attributes.wclass = GDK_INPUT_OUTPUT;
    attributes.visual = gtk_widget_get_visual (widget);
    attributes.colormap = gtk_widget_get_colormap (widget);
    attributes.event_mask =
        gtk_widget_get_events (widget) | GDK_EXPOSURE_MASK |
        GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK |
        GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK;

    attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP;

    widget->window =
        gdk_window_new (gtk_widget_get_parent_window (widget), &attributes,
                        attributes_mask);
    gdk_window_set_user_data (widget->window, widget);

    widget->style = gtk_style_attach (widget->style, widget->window);
    gtk_style_set_background (widget->style, widget->window,
                              GTK_STATE_NORMAL);

    netk_pager_set_layout_hint (pager);
}

static void
netk_pager_unrealize (GtkWidget * widget)
{
    NetkPager *pager;

    pager = NETK_PAGER (widget);

    netk_pager_clear_drag (pager);

    netk_screen_release_workspace_layout (pager->priv->screen,
                                          pager->priv->layout_manager_token);

    GTK_WIDGET_CLASS (parent_class)->unrealize (widget);
}

static void
netk_pager_size_request (GtkWidget * widget, GtkRequisition * requisition)
{
    NetkPager *pager;
    int n_spaces;
    int spaces_per_row;
    double screen_aspect;
    int other_dimension_size;
    int size;
    int n_rows;
    int focus_width;
    NetkWorkspace *space;

    pager = NETK_PAGER (widget);

    n_spaces = netk_screen_get_workspace_count (pager->priv->screen);

    g_assert (pager->priv->n_rows > 0);
    spaces_per_row =
        (n_spaces + pager->priv->n_rows - 1) / pager->priv->n_rows;
    space = netk_screen_get_workspace (pager->priv->screen, 0);

    if (pager->priv->orientation == GTK_ORIENTATION_VERTICAL)
    {
        if (space)
        {
            screen_aspect =
                  (double) netk_workspace_get_height (space) /
                  (double) netk_workspace_get_width (space);
        }
        else
        {
            screen_aspect =
                  (double) netk_screen_get_height (pager->priv->screen) /
                  (double) netk_screen_get_width (pager->priv->screen);
        }

        /* TODO: Handle NETK_PAGER_DISPLAY_NAME for this case */

        if (pager->priv->show_all_workspaces)
        {
            size = pager->priv->workspace_size;
            n_rows = pager->priv->n_rows;
        }
        else
        {
            size = pager->priv->workspace_size;
            n_rows = 1;
            spaces_per_row = 1;
        }

        other_dimension_size = screen_aspect * size;

        requisition->width = size * n_rows + (n_rows - 1);
        requisition->height =
            other_dimension_size * spaces_per_row + (spaces_per_row - 1);
    }
    else
    {
        if (space) 
        {
            screen_aspect =
                  (double) netk_workspace_get_width (space) /
                  (double) netk_workspace_get_height (space);
        } 
        else 
        {
            screen_aspect =
                  (double) netk_screen_get_width (pager->priv->screen) /
                  (double) netk_screen_get_height (pager->priv->screen);
        }

        if (pager->priv->show_all_workspaces)
        {
            size = pager->priv->workspace_size;
            n_rows = pager->priv->n_rows;
        }
        else
        {
            size = pager->priv->workspace_size;
            n_rows = 1;
            spaces_per_row = 1;
        }

        if (pager->priv->display_mode == NETK_PAGER_DISPLAY_CONTENT)
        {
            other_dimension_size = screen_aspect * size;
        }
        else
        {
            int n_spaces, i, w;
            NetkScreen *screen;
            PangoLayout *layout;

            n_spaces = netk_screen_get_workspace_count (pager->priv->screen);
            layout = gtk_widget_create_pango_layout (widget, NULL);
            screen = pager->priv->screen;
            other_dimension_size = 1;

            for (i = 0; i < n_spaces; i++)
            {
                pango_layout_set_text (layout,
                                       netk_workspace_get_name
                                       (netk_screen_get_workspace
                                        (screen, i)), -1);
                pango_layout_get_pixel_size (layout, &w, NULL);
                other_dimension_size = MAX (other_dimension_size, w);
            }

            g_object_unref (G_OBJECT (layout));

            other_dimension_size += 2;
        }

        requisition->width =
            other_dimension_size * spaces_per_row + (spaces_per_row - 1);
        requisition->height = size * n_rows + (n_rows - 1);
    }

    if (pager->priv->shadow_type != GTK_SHADOW_NONE)
    {
        requisition->width += 2 * widget->style->xthickness;
        requisition->height += 2 * widget->style->ythickness;
    }

    gtk_widget_style_get (widget, "focus-line-width", &focus_width, NULL);

    requisition->width += 2 * focus_width;
    requisition->height += 2 * focus_width;
}

static void
netk_pager_size_allocate (GtkWidget * widget, GtkAllocation * allocation)
{
    NetkPager *pager;
    int workspace_size;
    int focus_width;
    int width;
    int height;

    pager = NETK_PAGER (widget);

    gtk_widget_style_get (GTK_WIDGET (pager),
                          "focus-line-width", &focus_width, NULL);

    width = allocation->width - 2 * focus_width;
    height = allocation->height - 2 * focus_width;

    if (pager->priv->shadow_type != GTK_SHADOW_NONE)
    {
        width -= 2 * widget->style->xthickness;
        height -= 2 * widget->style->ythickness;
    }

    g_assert (pager->priv->n_rows > 0);

    if (pager->priv->orientation == GTK_ORIENTATION_VERTICAL)
    {
        if (pager->priv->show_all_workspaces)
            workspace_size = (width - (pager->priv->n_rows - 1)) / pager->priv->n_rows;
        else
            workspace_size = width;
    }
    else
    {
        if (pager->priv->show_all_workspaces)
            workspace_size = (height - (pager->priv->n_rows - 1)) / pager->priv->n_rows;
        else
            workspace_size = height;
    }

    if (workspace_size != pager->priv->workspace_size)
    {
        pager->priv->workspace_size = workspace_size;
        gtk_widget_queue_resize (GTK_WIDGET (widget));
        return;
    }

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

static void
get_workspace_rect (NetkPager * pager, int space, GdkRectangle * rect)
{
    int hsize, vsize;
    int n_spaces;
    int spaces_per_row;
    GtkWidget *widget;
    int col, row;
    int focus_width;

    gtk_widget_style_get (GTK_WIDGET (pager),
                          "focus-line-width", &focus_width, NULL);

    widget = GTK_WIDGET (pager);

    if (!pager->priv->show_all_workspaces)
    {
        NetkWorkspace *active_space;

        active_space = netk_screen_get_active_workspace (pager->priv->screen);

        if (active_space && space == netk_workspace_get_number (active_space))
        {
            rect->x = focus_width;
            rect->y = focus_width;
            rect->width = widget->allocation.width - 2 * focus_width;
            rect->height = widget->allocation.height - 2 * focus_width;

            if (pager->priv->shadow_type != GTK_SHADOW_NONE)
            {
                rect->x += widget->style->xthickness;
                rect->y += widget->style->ythickness;
                rect->width -= 2 * widget->style->xthickness;
                rect->height -= 2 * widget->style->ythickness;
            }
        }
        else
        {
            rect->x = 0;
            rect->y = 0;
            rect->width = 0;
            rect->height = 0;
        }

        return;
    }

    hsize = widget->allocation.width - 2 * focus_width;
    vsize = widget->allocation.height - 2 * focus_width;

    if (pager->priv->shadow_type != GTK_SHADOW_NONE)
    {
        hsize -= 2 * widget->style->xthickness;
        vsize -= 2 * widget->style->ythickness;
    }

    n_spaces = netk_screen_get_workspace_count (pager->priv->screen);

    g_assert (pager->priv->n_rows > 0);
    spaces_per_row =
        (n_spaces + pager->priv->n_rows - 1) / pager->priv->n_rows;

    if (pager->priv->orientation == GTK_ORIENTATION_VERTICAL)
    {
        rect->width =
            (hsize - (pager->priv->n_rows - 1)) / pager->priv->n_rows;
        rect->height = (vsize - (spaces_per_row - 1)) / spaces_per_row;

        col = space / spaces_per_row;
        row = space % spaces_per_row;
        rect->x = (rect->width + 1) * col;
        rect->y = (rect->height + 1) * row;

        if (col == pager->priv->n_rows - 1)
            rect->width = hsize - rect->x;

        if (row == spaces_per_row - 1)
            rect->height = vsize - rect->y;
    }
    else
    {
        rect->width = (hsize - (spaces_per_row - 1)) / spaces_per_row;
        rect->height =
            (vsize - (pager->priv->n_rows - 1)) / pager->priv->n_rows;

        col = space % spaces_per_row;
        row = space / spaces_per_row;
        rect->x = (rect->width + 1) * col;
        rect->y = (rect->height + 1) * row;

        if (col == spaces_per_row - 1)
            rect->width = hsize - rect->x;

        if (row == pager->priv->n_rows - 1)
            rect->height = vsize - rect->y;
    }

    rect->x += focus_width;
    rect->y += focus_width;

    if (pager->priv->shadow_type != GTK_SHADOW_NONE)
    {
        rect->x += widget->style->xthickness;
        rect->y += widget->style->ythickness;
    }
}

static GList *
get_windows_for_workspace_in_bottom_to_top (NetkScreen * screen,
                                            NetkWorkspace * workspace)
{
    GList *result;
    GList *windows;
    GList *tmp;
    gboolean is_active;

    result = NULL;
    is_active = workspace == netk_screen_get_active_workspace (screen);

    windows = netk_screen_get_windows_stacked (screen);
    for (tmp = windows; tmp != NULL; tmp = tmp->next)
    {
        NetkWindow *win = NETK_WINDOW (tmp->data);

        if (!netk_window_is_visible_on_workspace (win, workspace))
            continue;
        if (netk_window_get_state (win) & NETK_WINDOW_STATE_SKIP_PAGER)
            continue;

        result = g_list_prepend (result, win);
    }

    result = g_list_reverse (result);

    return result;
}

static void
get_window_rect (NetkWindow * window,
                 const GdkRectangle * workspace_rect, GdkRectangle * rect)
{
    double width_ratio, height_ratio;
    int x, y, width, height;
    NetkWorkspace *workspace;
    GdkRectangle unclipped_win_rect;

    workspace = netk_window_get_workspace (window);
    if (workspace == NULL)
        workspace =
            netk_screen_get_active_workspace (netk_window_get_screen
                                              (window));

    /* scale window down by same ratio we scaled workspace down */
    width_ratio =
        (double) workspace_rect->width /
        (double) netk_workspace_get_width (workspace);
    height_ratio =
        (double) workspace_rect->height /
        (double) netk_workspace_get_height (workspace);

    netk_window_get_geometry (window, &x, &y, &width, &height);

    x += netk_workspace_get_viewport_x (workspace);
    y += netk_workspace_get_viewport_y (workspace);
    x = x * width_ratio + 0.5 /* rounding */;
    y = y * height_ratio + 0.5 /* rounding */;
    width = width * width_ratio + 0.5 /* rounding */;
    height = height * height_ratio + 0.5 /* roudning */;

    x += workspace_rect->x;
    y += workspace_rect->y;

    if (width < 3)
        width = 3;
    if (height < 3)
        height = 3;

    unclipped_win_rect.x = x;
    unclipped_win_rect.y = y;
    unclipped_win_rect.width = width;
    unclipped_win_rect.height = height;

    gdk_rectangle_intersect ((GdkRectangle *) workspace_rect, &unclipped_win_rect, rect);
}

static void
draw_window (GdkDrawable * drawable, GtkWidget * widget, NetkWindow * win,
             const GdkRectangle * winrect)
{
    GdkPixbuf *icon;
    int icon_x, icon_y, icon_w, icon_h;
    gboolean is_active;

    is_active = netk_window_is_active (win);

    if ((winrect->width > 2) && (winrect->height > 2))
    {
        gdk_draw_rectangle (drawable,
                            is_active ?
                            widget->style->bg_gc[GTK_STATE_SELECTED] :
                            widget->style->bg_gc[GTK_STATE_NORMAL],
                            TRUE,
                            winrect->x + 1, winrect->y + 1,
                            winrect->width - 2, winrect->height - 2);
    }

    icon = netk_window_get_icon (win);
    icon_w = icon_h = 0;

    if (icon)
    {
        icon_w = gdk_pixbuf_get_width (icon);
        icon_h = gdk_pixbuf_get_height (icon);

        /* If the icon is too big, fall back to mini icon.
         * We don't arbitrarily scale the icon, because it's
         * just too slow on my Athlon 850.
         */
        if (icon_w > (winrect->width - 2) || icon_h > (winrect->height - 2))
        {
            icon = netk_window_get_mini_icon (win);
            if (icon)
            {
                icon_w = gdk_pixbuf_get_width (icon);
                icon_h = gdk_pixbuf_get_height (icon);

                /* Give up. */
                if (icon_w > (winrect->width - 2)
                    || icon_h > (winrect->height - 2))
                    icon = NULL;
            }
        }
    }

    if (icon)
    {
        icon_x = winrect->x + (winrect->width - icon_w) / 2;
        icon_y = winrect->y + (winrect->height - icon_h) / 2;

        {
            /* render_to_drawable should take a clip rect to save
             * us this mess...
             */
            GdkRectangle pixbuf_rect;
            GdkRectangle draw_rect;

            pixbuf_rect.x = icon_x;
            pixbuf_rect.y = icon_y;
            pixbuf_rect.width = icon_w;
            pixbuf_rect.height = icon_h;

            if (gdk_rectangle_intersect
                ((GdkRectangle *) winrect, &pixbuf_rect, &draw_rect))
            {
#if (GTK_MAJOR_VERSION >= 2) && (GTK_MINOR_VERSION >= 2)
                gdk_draw_pixbuf (drawable, NULL, icon,
                                 draw_rect.x - pixbuf_rect.x,
                                 draw_rect.y - pixbuf_rect.y, draw_rect.x,
                                 draw_rect.y, draw_rect.width,
                                 draw_rect.height, GDK_RGB_DITHER_NORMAL, 0,
                                 0);
#else
                gdk_pixbuf_render_to_drawable_alpha (icon, drawable,
                                                     draw_rect.x -
                                                     pixbuf_rect.x,
                                                     draw_rect.y -
                                                     pixbuf_rect.y,
                                                     draw_rect.x, draw_rect.y,
                                                     draw_rect.width,
                                                     draw_rect.height,
                                                     GDK_PIXBUF_ALPHA_FULL,
                                                     128,
                                                     GDK_RGB_DITHER_NORMAL, 0,
                                                     0);
#endif
            }
        }
    }

    if ((winrect->width > 0) && (winrect->height > 0))
    {
        gdk_draw_rectangle (drawable,
                            is_active ?
                            widget->style->fg_gc[GTK_STATE_SELECTED] :
                            widget->style->fg_gc[GTK_STATE_NORMAL],
                            FALSE,
                            winrect->x, winrect->y,
                            winrect->width - 1, winrect->height - 1);
    }
}

static int
workspace_at_point (NetkPager * pager,
                    int x, int y, int *viewport_x, int *viewport_y)
{
    GtkWidget *widget;
    int i;
    int n_spaces;
    int focus_width;
    int xthickness;
    int ythickness;

    widget = GTK_WIDGET (pager);

    gtk_widget_style_get (GTK_WIDGET (pager),
                          "focus-line-width", &focus_width, NULL);

    xthickness = focus_width + widget->style->xthickness;
    ythickness = focus_width + widget->style->ythickness;

    n_spaces = netk_screen_get_workspace_count (pager->priv->screen);

    i = 0;
    while (i < n_spaces)
    {
        GdkRectangle rect;

        get_workspace_rect (pager, i, &rect);

        if (pager->priv->shadow_type != GTK_SHADOW_NONE)
        {
            /* If workspace is on the edge, pretend points on the frame
             * belong to the workspace.
             */

            GtkWidget *widget = GTK_WIDGET (pager);

            if (rect.x == xthickness)
            {
                rect.x = 0;
                rect.width += xthickness;
            }
            if (rect.y == ythickness)
            {
                rect.y = 0;
                rect.height += ythickness;
            }
            if (rect.y + rect.height ==
                widget->allocation.height - ythickness)
            {
                rect.height += ythickness;
            }
            if (rect.x + rect.width == widget->allocation.width - xthickness)
            {
                rect.width += xthickness;
            }
        }

        if (POINT_IN_RECT (x, y, rect))
        {
            double width_ratio, height_ratio;
            NetkWorkspace *space;

            space = netk_screen_get_workspace (pager->priv->screen, i);
            g_assert (space != NULL);

            /* Scale x, y mouse coords to corresponding screenwide viewport coords */

            width_ratio =
                (double) netk_workspace_get_width (space) /
                (double) rect.width;
            height_ratio =
                (double) netk_workspace_get_height (space) /
                (double) rect.height;

            if (viewport_x)
                *viewport_x = width_ratio * (x - rect.x);
            if (viewport_y)
                *viewport_y = height_ratio * (y - rect.y);

            return i;
        }

        ++i;
    }

    return -1;
}


static void
netk_pager_draw_workspace (NetkPager * pager,
                           int workspace,
                           GdkRectangle * rect,
                           GdkPixbuf * bg_pixbuf, gboolean prelight)
{
    GList *windows;
    GList *tmp;
    gboolean is_current;
    NetkWorkspace *active_space;

    active_space = netk_screen_get_active_workspace (pager->priv->screen);

    is_current = active_space &&
        workspace == netk_workspace_get_number (active_space);

    /* FIXME in names mode, should probably draw things like a button.
     */

    if (is_current)
    {
        int vx, vy, vw, vh;     /* viewport */
        double width_ratio, height_ratio;

        width_ratio =
            rect->width / (double) netk_workspace_get_width (active_space);
        height_ratio =
            rect->height / (double) netk_workspace_get_height (active_space);
        vx = 0.5 /* rounding */ + rect->x +
            width_ratio * netk_workspace_get_viewport_x (active_space);
        vy = 0.5 /* rounding */ + rect->y +
            height_ratio * netk_workspace_get_viewport_y (active_space);
        vw = 0.5 /* rounding */ +
            width_ratio * netk_screen_get_width (pager->priv->screen);
        vh = 0.5 /* rounding */ +
            height_ratio * netk_screen_get_height (pager->priv->screen);

        gdk_draw_rectangle (GTK_WIDGET (pager)->window,
                            prelight ?
                            GTK_WIDGET (pager)->style->
                            mid_gc[GTK_STATE_SELECTED] : GTK_WIDGET (pager)->
                            style->dark_gc[GTK_STATE_SELECTED], TRUE, vx, vy,
                            vw, vh);
    }
    else if (prelight)
    {
        gdk_draw_rectangle (GTK_WIDGET (pager)->window,
                            GTK_WIDGET (pager)->style->
                            dark_gc[GTK_STATE_PRELIGHT], TRUE, rect->x,
                            rect->y, rect->width, rect->height);
    }
    else if (bg_pixbuf)
    {
#if (GTK_MAJOR_VERSION >= 2) && (GTK_MINOR_VERSION >= 2)
        gdk_draw_pixbuf (GTK_WIDGET (pager)->window,
                         GTK_WIDGET (pager)->style->
                         dark_gc[GTK_STATE_SELECTED], bg_pixbuf, 0, 0,
                         rect->x, rect->y, -1, -1, GDK_RGB_DITHER_MAX, 0, 0);
#else
        gdk_pixbuf_render_to_drawable (bg_pixbuf, GTK_WIDGET (pager)->window,
                                       GTK_WIDGET (pager)->style->
                                       dark_gc[GTK_STATE_SELECTED], 0, 0,
                                       rect->x, rect->y, -1, -1,
                                       GDK_RGB_DITHER_MAX, 0, 0);
#endif
    }
    else
    {
        gdk_draw_rectangle (GTK_WIDGET (pager)->window,
                            GTK_WIDGET (pager)->style->
                            dark_gc[GTK_STATE_NORMAL], TRUE, rect->x, rect->y,
                            rect->width, rect->height);

    }
    if (pager->priv->display_mode == NETK_PAGER_DISPLAY_CONTENT)
    {
        windows =
            get_windows_for_workspace_in_bottom_to_top (pager->priv->screen,
                                                        netk_screen_get_workspace
                                                        (pager->priv->screen,
                                                         workspace));

        tmp = windows;
        while (tmp != NULL)
        {
            NetkWindow *win = tmp->data;
            GdkRectangle winrect;

            if (pager->priv->dragging && win == pager->priv->drag_window)
            {
                tmp = tmp->next;
                continue;
            }

            get_window_rect (win, rect, &winrect);

            draw_window (GTK_WIDGET (pager)->window,
                         GTK_WIDGET (pager), win, &winrect);

            tmp = tmp->next;
        }

        g_list_free (windows);
    }
    else
    {
        /* Workspace name mode */
        const char *workspace_name;
        PangoLayout *layout;
        int w, h;

        workspace_name =
            netk_workspace_get_name (netk_screen_get_workspace
                                     (pager->priv->screen, workspace));
        layout =
            gtk_widget_create_pango_layout (GTK_WIDGET (pager),
                                            workspace_name);

        pango_layout_get_pixel_size (layout, &w, &h);

        gdk_draw_layout (GTK_WIDGET (pager)->window,
                         is_current ?
                         GTK_WIDGET (pager)->style->
                         fg_gc[GTK_STATE_SELECTED] : GTK_WIDGET (pager)->
                         style->fg_gc[GTK_STATE_NORMAL],
                         rect->x + (rect->width - w) / 2,
                         rect->y + (rect->height - h) / 2, layout);

        g_object_unref (G_OBJECT (layout));
    }
}

static gboolean
netk_pager_expose_event (GtkWidget * widget, GdkEventExpose * event)
{
    NetkPager *pager;
    int i;
    int n_spaces;
    NetkWorkspace *active_space;
    GdkPixbuf *bg_pixbuf;
    gboolean first;
    int drag_space;
    int focus_width;

    pager = NETK_PAGER (widget);

    n_spaces = netk_screen_get_workspace_count (pager->priv->screen);
    active_space = netk_screen_get_active_workspace (pager->priv->screen);
    bg_pixbuf = NULL;
    first = TRUE;

    gtk_widget_style_get (widget, "focus-line-width", &focus_width, NULL);

    if (GTK_WIDGET_HAS_FOCUS (widget))
    {
        gtk_paint_focus (widget->style,
                         widget->window,
                         GTK_WIDGET_STATE (widget),
                         NULL,
                         widget,
                         "pager",
                         0, 0,
                         widget->allocation.width, widget->allocation.height);
    }
    if (pager->priv->shadow_type != GTK_SHADOW_NONE)
    {
        gtk_paint_shadow (widget->style,
                          widget->window,
                          GTK_WIDGET_STATE (widget),
                          pager->priv->shadow_type,
                          NULL,
                          widget,
                          "pager",
                          focus_width,
                          focus_width,
                          widget->allocation.width - 2 * focus_width,
                          widget->allocation.height - 2 * focus_width);
    }

    if (pager->priv->dragging)
    {
        drag_space = workspace_at_point (pager,
                                         pager->priv->drag_window_x,
                                         pager->priv->drag_window_y,
                                         NULL, NULL);
    }
    else
    {
        drag_space = -1;
    }

    i = 0;
    while (i < n_spaces)
    {
        GdkRectangle rect;

        if (pager->priv->show_all_workspaces
            || (active_space
                && i == netk_workspace_get_number (active_space)))
        {
            get_workspace_rect (pager, i, &rect);

            /* We only want to do this once, even if w/h change,
             * for efficiency. width/height will only change by
             * one pixel at most.
             */
            if (first
                && pager->priv->display_mode == NETK_PAGER_DISPLAY_CONTENT)
            {
                bg_pixbuf =
                    netk_pager_get_background (pager, rect.width,
                                               rect.height);
                first = FALSE;
            }

            netk_pager_draw_workspace (pager, i, &rect, bg_pixbuf,
                                       drag_space == i);
        }

        ++i;
    }

    /* Draw the drag window */
    if (drag_space >= 0)
    {
        GdkRectangle rect;
        GdkRectangle winrect;
        int dx, dy;

        get_workspace_rect (pager, drag_space, &rect);
        get_window_rect (pager->priv->drag_window, &rect, &winrect);

        dx = (pager->priv->drag_window_x - rect.x) -
            pager->priv->drag_start_x_workspace_relative;
        dy = (pager->priv->drag_window_y - rect.y) -
            pager->priv->drag_start_y_workspace_relative;

        winrect.x += dx;
        winrect.y += dy;

        draw_window (widget->window, widget, pager->priv->drag_window,
                     &winrect);
    }

    return FALSE;
}

static gboolean
netk_pager_button_press (GtkWidget * widget, GdkEventButton * event)
{
    NetkPager *pager;
    gboolean handled = FALSE;
    int space_number;
    NetkWorkspace *space = NULL;
    GdkRectangle workspace_rect;

    pager = NETK_PAGER (widget);

    space_number = workspace_at_point (pager, event->x, event->y, NULL, NULL);

    if (space_number != -1)
    {
        if (event->button == 1)
            handled = TRUE;

        get_workspace_rect (pager, space_number, &workspace_rect);
        space = netk_screen_get_workspace (pager->priv->screen, space_number);
    }

    if (space)
    {
        GList *windows;
        GList *tmp;

        windows =
            get_windows_for_workspace_in_bottom_to_top (pager->priv->screen,
                                                        space);

        /* clicks on top windows first */
        windows = g_list_reverse (windows);

        tmp = windows;
        while (tmp != NULL)
        {
            NetkWindow *win = NETK_WINDOW (tmp->data);
            GdkRectangle winrect;

            get_window_rect (win, &workspace_rect, &winrect);

            if (POINT_IN_RECT (event->x, event->y, winrect))
            {
                if (event->button == 1)
                {
                    /* netk_window_activate (win); */
                    pager->priv->drag_window = win;
                    pager->priv->drag_start_x = event->x;
                    pager->priv->drag_start_y = event->y;
                    pager->priv->drag_start_x_workspace_relative =
                        event->x - workspace_rect.x;
                    pager->priv->drag_start_y_workspace_relative =
                        event->y - workspace_rect.y;
                }

                break;
            }

            tmp = tmp->next;
        }

        g_list_free (windows);
    }

    return handled;
}

static gboolean
netk_pager_motion (GtkWidget * widget, GdkEventMotion * event)
{
    NetkPager *pager;
    int x, y;

    pager = NETK_PAGER (widget);

    gdk_window_get_pointer (widget->window, &x, &y, NULL);

    if (!pager->priv->dragging &&
        pager->priv->drag_window != NULL &&
        gtk_drag_check_threshold (widget,
                                  pager->priv->drag_start_x,
                                  pager->priv->drag_start_y, x, y))
        pager->priv->dragging = TRUE;

    if (pager->priv->dragging)
    {
        gtk_widget_queue_draw (widget);
        pager->priv->drag_window_x = event->x;
        pager->priv->drag_window_y = event->y;
    }

    return TRUE;
}

static gboolean
netk_pager_button_release (GtkWidget * widget, GdkEventButton * event)
{
    NetkWorkspace *space;
    NetkPager *pager;
    int i;
    gboolean handled = FALSE;
    int viewport_x;
    int viewport_y;

    pager = NETK_PAGER (widget);

    if (event->button == 1 && pager->priv->dragging)
    {
        i = workspace_at_point (pager, event->x, event->y, NULL, NULL);

        if (i >= 0)
        {
            space = netk_screen_get_workspace (pager->priv->screen, i);

            if (space)
            {
                netk_window_move_to_workspace (pager->priv->drag_window, space);
                if (space == netk_screen_get_active_workspace (pager->priv->screen))
                {
                    netk_window_activate (pager->priv->drag_window);
                }
            }
        }

        netk_pager_clear_drag (pager);
        handled = TRUE;
    }
    else if (event->button == 1)
    {
        i = workspace_at_point (pager, event->x, event->y, &viewport_x,
                                &viewport_y);

        if (i >= 0 && (space = netk_screen_get_workspace (pager->priv->screen, i)))
        {
            int screen_width, screen_height;
            
            /* Don't switch the desktop if we're already there */               
            if (space != netk_screen_get_active_workspace (pager->priv->screen))
                netk_workspace_activate (space);
                
            /* EWMH only lets us move the viewport for the active workspace,
             * but we just go ahead and hackily assume that the activate
             * just above takes effect prior to moving the viewport
             */

            /* Transform the pointer location to viewport origin, assuming
             * that we want the nearest "regular" viewport containing the
             * pointer.
             */                                
            screen_width  = netk_screen_get_width  (pager->priv->screen);
            screen_height = netk_screen_get_height (pager->priv->screen);
            viewport_x = (viewport_x / screen_width)  * screen_width;
            viewport_y = (viewport_y / screen_height) * screen_height;

            if (netk_workspace_get_viewport_x (space) != viewport_x ||
                netk_workspace_get_viewport_y (space) != viewport_y)
              netk_screen_move_viewport (pager->priv->screen, viewport_x, viewport_y);

            if ((space == netk_screen_get_active_workspace (pager->priv->screen))
                 && (pager->priv->drag_window))
              netk_window_activate (pager->priv->drag_window);
        }

        if (pager->priv->drag_window)
            netk_pager_clear_drag (pager);

        handled = TRUE;
    }

    return FALSE;
}

static gboolean
netk_pager_scroll_event (GtkWidget * widget, GdkEventScroll * event)
{
    NetkPager *pager;
    gint n, active;
    NetkWorkspace *ws = NULL;

    g_return_val_if_fail (event != NULL, FALSE);
    g_return_val_if_fail (widget != NULL, FALSE);

    pager = NETK_PAGER (widget);
    
    if (!pager->priv->workspace_scrolling)
	return FALSE;
    
    n = netk_screen_get_workspace_count (pager->priv->screen);
    active =
        netk_workspace_get_number (netk_screen_get_active_workspace
                                   (pager->priv->screen));

    switch (event->direction)
    {
        case GDK_SCROLL_UP:
        case GDK_SCROLL_LEFT:
            if (active > 0)
            {
                ws = netk_screen_get_workspace (pager->priv->screen,
                                                active - 1);
            }
            else
            {
                ws = netk_screen_get_workspace (pager->priv->screen, n - 1);
            }
            netk_workspace_activate (ws);
            break;
        case GDK_SCROLL_DOWN:
        case GDK_SCROLL_RIGHT:
            if (active < n - 1)
            {
                ws = netk_screen_get_workspace (pager->priv->screen,
                                                active + 1);
            }
            else
            {
                ws = netk_screen_get_workspace (pager->priv->screen, 0);
            }
            netk_workspace_activate (ws);
            break;
        default:
            break;
    }

    return TRUE;
}

static gboolean
netk_pager_focus (GtkWidget * widget, GtkDirectionType direction)
{
    NetkPager *pager;

    pager = NETK_PAGER (widget);

    return GTK_WIDGET_CLASS (parent_class)->focus (widget, direction);
}

/* dragging something over the pager */
static gboolean
netk_pager_drag_timeout (NetkPager *pager)
{
    netk_workspace_activate (pager->priv->ws_to_activate);
    pager->priv->ws_activate = 0;
    pager->priv->ws_to_activate = NULL;
    return FALSE;
}

static void
netk_pager_drag_leave (GtkWidget * widget, GdkDragContext * context,
                       guint time, gpointer data)
{
    NetkPager *pager = NETK_PAGER (widget);

    if (pager->priv->ws_activate != 0)
    {
        g_source_remove (pager->priv->ws_activate);
        pager->priv->ws_activate = 0;
        pager->priv->ws_to_activate = NULL;
    }
}

static gboolean
netk_pager_drag_motion (GtkWidget * widget, GdkDragContext * context, gint x,
                        gint y, guint time, gpointer data)
{
    NetkPager *pager = NETK_PAGER (widget);
    NetkWorkspace *current_ws, *new_ws;
    int num;

    current_ws = netk_screen_get_active_workspace (pager->priv->screen);

    num = workspace_at_point (pager, x, y, NULL, NULL);
    
    if (num != -1) 
    {
        new_ws = netk_screen_get_workspace (pager->priv->screen, num);
        if (new_ws != current_ws && new_ws != pager->priv->ws_to_activate)
        {
            if (pager->priv->ws_activate != 0)
            {
                g_source_remove (pager->priv->ws_activate);
            }
            pager->priv->ws_to_activate = new_ws;
            pager->priv->ws_activate = 
                g_timeout_add (1000, (GSourceFunc)netk_pager_drag_timeout, pager);
        }
    }
    else
    {
        g_source_remove (pager->priv->ws_activate);
        pager->priv->ws_activate = 0;
        pager->priv->ws_to_activate = NULL;
    }
    gdk_drag_status (context, 0, time);

    return TRUE;
}

void
netk_pager_set_screen (NetkPager * pager, NetkScreen * screen)
{
    if (pager->priv->screen == screen)
        return;

    if (pager->priv->screen)
        netk_pager_disconnect_screen (pager);

    netk_pager_connect_screen (pager, screen);
}

GtkWidget *
netk_pager_new (NetkScreen * screen)
{
    NetkPager *pager;

    pager = g_object_new (NETK_TYPE_PAGER, NULL);

    gtk_drag_dest_set (GTK_WIDGET (pager), 0, NULL, 0, 0);
    g_signal_connect (pager, "drag_motion",
                      G_CALLBACK (netk_pager_drag_motion), NULL);
    g_signal_connect (pager, "drag_leave",
                      G_CALLBACK (netk_pager_drag_leave), NULL);

    netk_pager_connect_screen (pager, screen);

    return GTK_WIDGET (pager);
}

static void
netk_pager_set_layout_hint (NetkPager * pager)
{
    int layout_rows;
    int layout_cols;

    /* The visual representation of the pager doesn't
     * correspond to the layout of the workspaces
     * here. i.e. the user will not pay any attention
     * to the n_rows setting on this pager.
     */
    if (!pager->priv->show_all_workspaces)
        return;

    if (pager->priv->orientation == GTK_ORIENTATION_HORIZONTAL)
    {
        layout_rows = pager->priv->n_rows;
        layout_cols = 0;
    }
    else
    {
        layout_rows = 0;
        layout_cols = pager->priv->n_rows;
    }

    pager->priv->layout_manager_token =
        netk_screen_try_set_workspace_layout (pager->priv->screen,
                                              pager->priv->
                                              layout_manager_token,
                                              layout_rows, layout_cols);
}

void
netk_pager_set_orientation (NetkPager * pager, GtkOrientation orientation)
{
    g_return_if_fail (NETK_IS_PAGER (pager));

    if (pager->priv->orientation == orientation)
        return;

    pager->priv->orientation = orientation;
    gtk_widget_queue_resize (GTK_WIDGET (pager));

    netk_pager_set_layout_hint (pager);
}

void
netk_pager_set_n_rows (NetkPager * pager, int n_rows)
{
    g_return_if_fail (NETK_IS_PAGER (pager));

    if (pager->priv->n_rows == n_rows || n_rows <= 0)
        return;

    pager->priv->n_rows = n_rows;
    gtk_widget_queue_resize (GTK_WIDGET (pager));

    netk_pager_set_layout_hint (pager);
}

void
netk_pager_set_display_mode (NetkPager * pager, NetkPagerDisplayMode mode)
{
    g_return_if_fail (NETK_IS_PAGER (pager));

    if (pager->priv->display_mode == mode)
        return;

    pager->priv->display_mode = mode;
    gtk_widget_queue_resize (GTK_WIDGET (pager));
}

void
netk_pager_set_show_all (NetkPager * pager, gboolean show_all_workspaces)
{
    g_return_if_fail (NETK_IS_PAGER (pager));

    show_all_workspaces = (show_all_workspaces != 0);

    if (pager->priv->show_all_workspaces == show_all_workspaces)
        return;

    pager->priv->show_all_workspaces = show_all_workspaces;
    gtk_widget_queue_resize (GTK_WIDGET (pager));
}

void
netk_pager_set_workspace_scrolling (NetkPager * pager, gboolean workspace_scrolling)
{
    g_return_if_fail (NETK_IS_PAGER (pager));

    if (pager->priv->workspace_scrolling == workspace_scrolling)
        return;

    pager->priv->workspace_scrolling = workspace_scrolling;
    gtk_widget_queue_resize (GTK_WIDGET (pager));
}

void
netk_pager_set_shadow_type (NetkPager * pager, GtkShadowType shadow_type)
{
    g_return_if_fail (NETK_IS_PAGER (pager));

    if (pager->priv->shadow_type == shadow_type)
        return;

    pager->priv->shadow_type = shadow_type;
    gtk_widget_queue_resize (GTK_WIDGET (pager));
}

static void
active_window_changed_callback (NetkScreen * screen, gpointer data)
{
    NetkPager *pager = NETK_PAGER (data);
    gtk_widget_queue_draw (GTK_WIDGET (pager));
}

static void
active_workspace_changed_callback (NetkScreen * screen, gpointer data)
{
    NetkPager *pager = NETK_PAGER (data);
    gtk_widget_queue_draw (GTK_WIDGET (pager));
}

static void
window_stacking_changed_callback (NetkScreen * screen, gpointer data)
{
    NetkPager *pager = NETK_PAGER (data);
    gtk_widget_queue_draw (GTK_WIDGET (pager));
}

static void
window_opened_callback (NetkScreen * screen, NetkWindow * window,
                        gpointer data)
{
    NetkPager *pager = NETK_PAGER (data);

    netk_pager_connect_window (pager, window);
    gtk_widget_queue_draw (GTK_WIDGET (pager));
}

static void
window_closed_callback (NetkScreen * screen, NetkWindow * window,
                        gpointer data)
{
    NetkPager *pager = NETK_PAGER (data);

    if (pager->priv->drag_window == window)
        netk_pager_clear_drag (pager);

    gtk_widget_queue_draw (GTK_WIDGET (pager));
}

static void
workspace_created_callback (NetkScreen * screen, NetkWorkspace * space,
                            gpointer data)
{
    NetkPager *pager = NETK_PAGER (data);
    g_signal_connect (space, "name_changed",
                      G_CALLBACK (workspace_name_changed_callback), pager);
    gtk_widget_queue_resize (GTK_WIDGET (pager));
}

static void
workspace_destroyed_callback (NetkScreen * screen, NetkWorkspace * space,
                              gpointer data)
{
    NetkPager *pager = NETK_PAGER (data);
    g_signal_handlers_disconnect_by_func (space,
                                          G_CALLBACK
                                          (workspace_name_changed_callback),
                                          pager);
    gtk_widget_queue_resize (GTK_WIDGET (pager));
}

static void
application_opened_callback (NetkScreen * screen, NetkApplication * app,
                             gpointer data)
{
}

static void
application_closed_callback (NetkScreen * screen, NetkApplication * app,
                             gpointer data)
{
}

static void
window_name_changed_callback (NetkWindow * window, gpointer data)
{
    NetkPager *pager = NETK_PAGER (data);
    gtk_widget_queue_draw (GTK_WIDGET (pager));
}

static void
window_state_changed_callback (NetkWindow * window, NetkWindowState changed,
                               NetkWindowState new, gpointer data)
{
    NetkPager *pager = NETK_PAGER (data);
    gtk_widget_queue_draw (GTK_WIDGET (pager));
}

static void
window_workspace_changed_callback (NetkWindow * window, gpointer data)
{
    NetkPager *pager = NETK_PAGER (data);
    gtk_widget_queue_draw (GTK_WIDGET (pager));
}

static void
window_icon_changed_callback (NetkWindow * window, gpointer data)
{
    NetkPager *pager = NETK_PAGER (data);
    gtk_widget_queue_draw (GTK_WIDGET (pager));
}

static void
window_geometry_changed_callback (NetkWindow * window, gpointer data)
{
    NetkPager *pager = NETK_PAGER (data);
    gtk_widget_queue_draw (GTK_WIDGET (pager));
}

static void
background_changed_callback (NetkWindow * window, gpointer data)
{
    NetkPager *pager = NETK_PAGER (data);

    if (pager->priv->bg_cache)
    {
        g_object_unref (G_OBJECT (pager->priv->bg_cache));
        pager->priv->bg_cache = NULL;
    }

    gtk_widget_queue_draw (GTK_WIDGET (pager));
}

static void
workspace_name_changed_callback (NetkWorkspace * space, gpointer data)
{
    gtk_widget_queue_resize (GTK_WIDGET (data));
}

static void
viewports_changed_callback (NetkWorkspace * space, gpointer data)
{
    gtk_widget_queue_resize (GTK_WIDGET (data));
}

static void
netk_pager_connect_screen (NetkPager * pager, NetkScreen * screen)
{
    int i;
    guint *c;
    GList *tmp;

    g_return_if_fail (pager->priv->screen == NULL);

    pager->priv->screen = screen;

    tmp = netk_screen_get_windows (screen);
    while (tmp != NULL)
    {
        netk_pager_connect_window (pager, NETK_WINDOW (tmp->data));
        tmp = tmp->next;
    }

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

    c[i] = g_signal_connect (G_OBJECT (screen), "active_window_changed",
                             G_CALLBACK (active_window_changed_callback),
                             pager);
    ++i;

    c[i] = g_signal_connect (G_OBJECT (screen), "active_workspace_changed",
                             G_CALLBACK (active_workspace_changed_callback),
                             pager);
    ++i;

    c[i] = g_signal_connect (G_OBJECT (screen), "window_stacking_changed",
                             G_CALLBACK (window_stacking_changed_callback),
                             pager);
    ++i;

    c[i] = g_signal_connect (G_OBJECT (screen), "window_opened",
                             G_CALLBACK (window_opened_callback), pager);
    ++i;

    c[i] = g_signal_connect (G_OBJECT (screen), "window_closed",
                             G_CALLBACK (window_closed_callback), pager);
    ++i;

    c[i] = g_signal_connect (G_OBJECT (screen), "workspace_created",
                             G_CALLBACK (workspace_created_callback), pager);
    ++i;

    c[i] = g_signal_connect (G_OBJECT (screen), "workspace_destroyed",
                             G_CALLBACK (workspace_destroyed_callback),
                             pager);
    ++i;

    c[i] = g_signal_connect (G_OBJECT (screen), "application_opened",
                             G_CALLBACK (application_opened_callback), pager);
    ++i;

    c[i] = g_signal_connect (G_OBJECT (screen), "application_closed",
                             G_CALLBACK (application_closed_callback), pager);
    ++i;

    c[i] = g_signal_connect (G_OBJECT (screen), "background_changed",
                             G_CALLBACK (background_changed_callback), pager);
    ++i;

    c[i] = g_signal_connect (G_OBJECT (screen), "viewports_changed",
                             G_CALLBACK (viewports_changed_callback), pager);
    ++i;

    g_assert (i == N_SCREEN_CONNECTIONS);

    /* connect to name_changed on each workspace */
    for (i = 0; i < netk_screen_get_workspace_count (pager->priv->screen);
         i++)
    {
        NetkWorkspace *space;
        space = netk_screen_get_workspace (pager->priv->screen, i);
        g_signal_connect (space, "name_changed",
                          G_CALLBACK (workspace_name_changed_callback),
                          pager);
    }
}

static void
netk_pager_connect_window (NetkPager * pager, NetkWindow * window)
{
    g_signal_connect_object (G_OBJECT (window), "name_changed",
                             G_CALLBACK (window_name_changed_callback), pager, 0);
    g_signal_connect_object (G_OBJECT (window), "state_changed",
                             G_CALLBACK (window_state_changed_callback),
                             pager, 0);
    g_signal_connect_object (G_OBJECT (window), "workspace_changed",
                             G_CALLBACK (window_workspace_changed_callback),
                             pager, 0);
    g_signal_connect_object (G_OBJECT (window), "icon_changed",
                             G_CALLBACK (window_icon_changed_callback), pager, 0);
    g_signal_connect_object (G_OBJECT (window), "geometry_changed",
                             G_CALLBACK (window_geometry_changed_callback),
                             pager, 0);
}

static void
netk_pager_disconnect_screen (NetkPager * pager)
{
    int i;

    if (pager->priv->screen == NULL)
        return;

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

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

        ++i;
    }

    for (i = 0; i < netk_screen_get_workspace_count (pager->priv->screen);
         i++)
    {
        NetkWorkspace *space;
        space = netk_screen_get_workspace (pager->priv->screen, i);
        g_signal_handlers_disconnect_by_func (space,
                                              G_CALLBACK
                                              (workspace_name_changed_callback),
                                              pager);
    }

    pager->priv->screen = NULL;
}

static void
netk_pager_clear_drag (NetkPager * pager)
{
    if (pager->priv->dragging)
        gtk_widget_queue_draw (GTK_WIDGET (pager));

    pager->priv->dragging = FALSE;
    pager->priv->drag_window = NULL;
    pager->priv->drag_start_x = -1;
    pager->priv->drag_start_y = -1;
    pager->priv->drag_window_x = -1;
    pager->priv->drag_window_y = -1;
}

static GdkPixbuf *
netk_pager_get_background (NetkPager * pager, int width, int height)
{
    Pixmap p;
    GdkPixbuf *pix = NULL;

    /* We have to be careful not to keep alternating between
     * width/height values, otherwise this would get really slow.
     */
    if (pager->priv->bg_cache
        && gdk_pixbuf_get_width (pager->priv->bg_cache) == width
        && gdk_pixbuf_get_height (pager->priv->bg_cache) == height)
        return pager->priv->bg_cache;

    if (pager->priv->bg_cache)
    {
        g_object_unref (G_OBJECT (pager->priv->bg_cache));
        pager->priv->bg_cache = NULL;
    }

    if (pager->priv->screen == NULL)
        return NULL;

    /* FIXME this just globally disables the thumbnailing feature */
    return NULL;

#define MIN_BG_SIZE 10

    if (width < MIN_BG_SIZE || height < MIN_BG_SIZE)
        return NULL;

    p = netk_screen_get_background_pixmap (pager->priv->screen);

    if (p != None)
    {
        p_netk_error_trap_push ();
        pix = p_netk_gdk_pixbuf_get_from_pixmap (NULL, p, 0, 0, 0, 0, -1, -1);
        p_netk_error_trap_pop ();
    }

    if (pix)
    {
        pager->priv->bg_cache =
            gdk_pixbuf_scale_simple (pix, width, height, GDK_INTERP_BILINEAR);

        g_object_unref (G_OBJECT (pix));
    }

    return pager->priv->bg_cache;
}

int
p_netk_pager_get_n_workspaces (NetkPager * pager)
{
    return netk_screen_get_workspace_count (pager->priv->screen);
}

const char *
p_netk_pager_get_workspace_name (NetkPager * pager, int i)
{
    NetkWorkspace *space;

    space = netk_screen_get_workspace (pager->priv->screen, i);
    if (space)
        return netk_workspace_get_name (space);
    else
        return NULL;
}

NetkWorkspace *
p_netk_pager_get_active_workspace (NetkPager * pager)
{
    return netk_screen_get_active_workspace (pager->priv->screen);
}

NetkWorkspace *
p_netk_pager_get_workspace (NetkPager * pager, int i)
{
    return netk_screen_get_workspace (pager->priv->screen, i);
}

void
p_netk_pager_activate_workspace (NetkWorkspace * wspace)
{
    netk_workspace_activate (wspace);
}

void
p_netk_pager_get_workspace_rect (NetkPager * pager, int i,
                                 GdkRectangle * rect)
{
    get_workspace_rect (pager, i, rect);
}
