/*
 * xed-print-preview.c
 *
 * Copyright (C) 2008 Paolo Borelli
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */

/*
 * Modified by the xed Team, 1998-2006. See the AUTHORS file for a
 * list of people on the xed Team.
 * See the ChangeLog files for a list of changes.
 *
 * $Id: xed-commands-search.c 5931 2007-09-25 20:05:40Z pborelli $
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <math.h>
#include <stdlib.h>
#include <glib/gi18n.h>
#include <gtk/gtk.h>
#include <gdk/gdkkeysyms.h>
#include <cairo-pdf.h>

#include "xed-print-preview.h"

#define PRINTER_DPI (72.)

struct _XedPrintPreviewPrivate
{
    GtkPrintOperation *operation;
    GtkPrintContext *context;
    GtkPrintOperationPreview *gtk_preview;

    GtkWidget *layout;
    GtkWidget *scrolled_window;

    GtkWidget *next;
    GtkWidget *prev;
    GtkWidget   *page_entry;
    GtkWidget   *last;
    GtkWidget *multi;
    GtkWidget *zoom_one;
    GtkWidget *zoom_fit;
    GtkWidget *zoom_in;
    GtkWidget *zoom_out;

    /* real size of the page in inches */
    double paper_w;
    double paper_h;
    double dpi;

    double scale;

    /* size of the tile of a page (including padding
     * and drop shadow) in pixels */
    gint tile_w;
    gint tile_h;

    GtkPageOrientation orientation;

    /* multipage support */
    gint rows;
    gint cols;

    guint n_pages;
    guint cur_page;
};

G_DEFINE_TYPE (XedPrintPreview, xed_print_preview, GTK_TYPE_BOX)

static void
xed_print_preview_get_property (GObject    *object,
                                guint       prop_id,
                                GValue     *value,
                                GParamSpec *pspec)
{
    //XedPrintPreview *preview = XED_PRINT_PREVIEW (object);

    switch (prop_id)
    {
        default:
            G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
            break;
    }
}

static void
xed_print_preview_set_property (GObject      *object,
                                guint         prop_id,
                                const GValue *value,
                                GParamSpec   *pspec)
{
    //XedPrintPreview *preview = XED_PRINT_PREVIEW (object);

    switch (prop_id)
    {
        default:
            G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
            break;
    }
}

static void
xed_print_preview_finalize (GObject *object)
{
    //XedPrintPreview *preview = XED_PRINT_PREVIEW (object);

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

static void
xed_print_preview_grab_focus (GtkWidget *widget)
{
    XedPrintPreview *preview;

    preview = XED_PRINT_PREVIEW (widget);

    gtk_widget_grab_focus (GTK_WIDGET (preview->priv->layout));
}

static void
xed_print_preview_class_init (XedPrintPreviewClass *klass)
{
    GObjectClass *object_class;
    GtkWidgetClass *widget_class;

    object_class = G_OBJECT_CLASS (klass);
    widget_class = GTK_WIDGET_CLASS (klass);

    object_class->get_property = xed_print_preview_get_property;
    object_class->set_property = xed_print_preview_set_property;
    object_class->finalize = xed_print_preview_finalize;

    widget_class->grab_focus = xed_print_preview_grab_focus;

    g_type_class_add_private (object_class, sizeof(XedPrintPreviewPrivate));
}

static void
update_layout_size (XedPrintPreview *preview)
{
    XedPrintPreviewPrivate *priv;

    priv = preview->priv;

    /* force size of the drawing area to make the scrolled window work */
    gtk_layout_set_size (GTK_LAYOUT (priv->layout), priv->tile_w * priv->cols, priv->tile_h * priv->rows);

    gtk_widget_queue_draw (preview->priv->layout);
}

static void
set_rows_and_cols (XedPrintPreview *preview,
                   gint             rows,
                   gint             cols)
{
    /* TODO: set the zoom appropriately */

    preview->priv->rows = rows;
    preview->priv->cols = cols;
    update_layout_size (preview);
}

/* get the paper size in points: these must be used only
 * after the widget has been mapped and the dpi is known */

static double
get_paper_width (XedPrintPreview *preview)
{
    return preview->priv->paper_w * preview->priv->dpi;
}

static double
get_paper_height (XedPrintPreview *preview)
{
    return preview->priv->paper_h * preview->priv->dpi;
}

#define PAGE_PAD 12
#define PAGE_SHADOW_OFFSET 5

/* The tile size is the size of the area where a page
 * will be drawn including the padding and idependent
 * of the orientation */

/* updates the tile size to the current zoom and page size */
static void
update_tile_size (XedPrintPreview *preview)
{
    XedPrintPreviewPrivate *priv;
    gint w, h;

    priv = preview->priv;

    w = 2 * PAGE_PAD + floor (priv->scale * get_paper_width (preview) + 0.5);
    h = 2 * PAGE_PAD + floor (priv->scale * get_paper_height (preview) + 0.5);

    if ((priv->orientation == GTK_PAGE_ORIENTATION_LANDSCAPE) ||
        (priv->orientation == GTK_PAGE_ORIENTATION_REVERSE_LANDSCAPE))
    {
        priv->tile_w = h;
        priv->tile_h = w;
    }
    else
    {
        priv->tile_w = w;
        priv->tile_h = h;
    }
}

/* Zoom should always be set with one of these two function
 * so that the tile size is properly updated */

static void
set_zoom_factor (XedPrintPreview *preview,
                 double           zoom)
{
    XedPrintPreviewPrivate *priv;

    priv = preview->priv;

    priv->scale = zoom;

    update_tile_size (preview);
    update_layout_size (preview);
}

static void
set_zoom_fit_to_size (XedPrintPreview *preview)
{
    XedPrintPreviewPrivate *priv;
    double width, height;
    double p_width, p_height;
    double zoomx, zoomy;

    priv = preview->priv;

    g_object_get (gtk_scrollable_get_hadjustment (GTK_SCROLLABLE (priv->layout)), "page-size", &width, NULL);
    g_object_get (gtk_scrollable_get_vadjustment (GTK_SCROLLABLE (priv->layout)), "page-size", &height, NULL);

    width /= priv->cols;
    height /= priv->rows;

    if ((priv->orientation == GTK_PAGE_ORIENTATION_LANDSCAPE) ||
        (priv->orientation == GTK_PAGE_ORIENTATION_REVERSE_LANDSCAPE))
    {
        p_width = get_paper_height (preview);
        p_height = get_paper_width (preview);
    }
    else
    {
        p_width = get_paper_width (preview);
        p_height = get_paper_height (preview);
    }

    zoomx = MAX (1, width - 2 * PAGE_PAD) / p_width;
    zoomy = MAX (1, height - 2 * PAGE_PAD) / p_height;

    if (zoomx <= zoomy)
    {
        priv->tile_w = width;
        priv->tile_h = floor (0.5 + width * (p_height / p_width));
        priv->scale = zoomx;
    }
    else
    {
        priv->tile_w = floor (0.5 + height * (p_width / p_height));
        priv->tile_h = height;
        priv->scale = zoomy;
    }

    update_layout_size (preview);
}

#define ZOOM_IN_FACTOR (1.2)
#define ZOOM_OUT_FACTOR (1.0 / ZOOM_IN_FACTOR)

static void
zoom_in (XedPrintPreview *preview)
{
    set_zoom_factor (preview, preview->priv->scale * ZOOM_IN_FACTOR);
}

static void
zoom_out (XedPrintPreview *preview)
{
    set_zoom_factor (preview, preview->priv->scale * ZOOM_OUT_FACTOR);
}

static void
goto_page (XedPrintPreview *preview,
           gint             page)
{
    gchar c[32];

    g_snprintf (c, 32, "%d", page + 1);
    gtk_entry_set_text (GTK_ENTRY (preview->priv->page_entry), c);

    gtk_widget_set_sensitive (GTK_WIDGET (preview->priv->prev), (page > 0) && (preview->priv->n_pages > 1));
    gtk_widget_set_sensitive (GTK_WIDGET (preview->priv->next),
                              (page != (preview->priv->n_pages - 1)) &&
                              (preview->priv->n_pages > 1));

    if (page != preview->priv->cur_page)
    {
        preview->priv->cur_page = page;
        if (preview->priv->n_pages > 0)
        {
            gtk_widget_queue_draw (preview->priv->layout);
        }
    }
}

static void
prev_button_clicked (GtkWidget       *button,
                     XedPrintPreview *preview)
{
    GdkEvent *event;
    gint page;

    event = gtk_get_current_event ();

    if (event->button.state & GDK_SHIFT_MASK)
    {
        page = 0;
    }
    else
    {
        page = preview->priv->cur_page - preview->priv->rows * preview->priv->cols;
    }

    goto_page (preview, MAX (page, 0));

    gdk_event_free (event);
}

static void
next_button_clicked (GtkWidget       *button,
                     XedPrintPreview *preview)
{
    GdkEvent *event;
    gint page;

    event = gtk_get_current_event ();

    if (event->button.state & GDK_SHIFT_MASK)
    {
        page = preview->priv->n_pages - 1;
    }
    else
    {
        page = preview->priv->cur_page + preview->priv->rows * preview->priv->cols;
    }

    goto_page (preview, MIN (page, preview->priv->n_pages - 1));

    gdk_event_free (event);
}

static void
page_entry_activated (GtkEntry        *entry,
                      XedPrintPreview *preview)
{
    const gchar *text;
    gint page;

    text = gtk_entry_get_text (entry);

    page = CLAMP (atoi (text), 1, preview->priv->n_pages) - 1;
    goto_page (preview, page);

    gtk_widget_grab_focus (GTK_WIDGET (preview->priv->layout));
}

static void
page_entry_insert_text (GtkEditable *editable,
                        const gchar *text,
                        gint         length,
                        gint        *position)
{
    gunichar c;
    const gchar *p;
    const gchar *end;

    p = text;
    end = text + length;

    while (p != end)
    {
        const gchar *next;
        next = g_utf8_next_char (p);

        c = g_utf8_get_char (p);

        if (!g_unichar_isdigit (c))
        {
            g_signal_stop_emission_by_name (editable, "insert-text");
            break;
        }

        p = next;
    }
}

static gboolean
page_entry_focus_out (GtkWidget       *widget,
                      GdkEventFocus   *event,
                      XedPrintPreview *preview)
{
    const gchar *text;
    gint page;

    text = gtk_entry_get_text (GTK_ENTRY (widget));
    page = atoi (text) - 1;

    /* Reset the page number only if really needed */
    if (page != preview->priv->cur_page)
    {
        gchar *str;

        str = g_strdup_printf ("%d", preview->priv->cur_page + 1);
        gtk_entry_set_text (GTK_ENTRY (widget), str);
        g_free (str);
    }

    return FALSE;
}

static void
on_1x1_clicked (GtkMenuItem     *i,
                XedPrintPreview *preview)
{
    set_rows_and_cols (preview, 1, 1);
}

static void
on_1x2_clicked (GtkMenuItem     *i,
                XedPrintPreview *preview)
{
    set_rows_and_cols (preview, 1, 2);
}

static void
on_2x1_clicked (GtkMenuItem     *i,
                XedPrintPreview *preview)
{
    set_rows_and_cols (preview, 2, 1);
}

static void
on_2x2_clicked (GtkMenuItem     *i,
                XedPrintPreview *preview)
{
    set_rows_and_cols (preview, 2, 2);
}

static void
multi_button_clicked (GtkWidget       *button,
                      XedPrintPreview *preview)
{
    GtkWidget *m, *i;

    m = gtk_menu_new ();
    gtk_widget_show (m);
    g_signal_connect (m, "selection_done",
                      G_CALLBACK (gtk_widget_destroy), m);

    i = gtk_menu_item_new_with_label ("1x1");
    gtk_widget_show (i);
    gtk_menu_attach (GTK_MENU (m), i, 0, 1, 0, 1);
    g_signal_connect (i, "activate", G_CALLBACK (on_1x1_clicked), preview);

    i = gtk_menu_item_new_with_label ("2x1");
    gtk_widget_show (i);
    gtk_menu_attach (GTK_MENU (m), i, 0, 1, 1, 2);
    g_signal_connect (i, "activate", G_CALLBACK (on_2x1_clicked), preview);

    i = gtk_menu_item_new_with_label ("1x2");
    gtk_widget_show (i);
    gtk_menu_attach (GTK_MENU (m), i, 1, 2, 0, 1);
    g_signal_connect (i, "activate", G_CALLBACK (on_1x2_clicked), preview);

    i = gtk_menu_item_new_with_label ("2x2");
    gtk_widget_show (i);
    gtk_menu_attach (GTK_MENU (m), i, 1, 2, 1, 2);
    g_signal_connect (i, "activate", G_CALLBACK (on_2x2_clicked), preview);

    gtk_menu_popup (GTK_MENU (m), NULL, NULL, NULL, preview, 0, GDK_CURRENT_TIME);
}

static void
zoom_one_button_clicked (GtkWidget       *button,
                         XedPrintPreview *preview)
{
    set_zoom_factor (preview, 1);
}

static void
zoom_fit_button_clicked (GtkWidget       *button,
                         XedPrintPreview *preview)
{
    set_zoom_fit_to_size (preview);
}

static void
zoom_in_button_clicked (GtkWidget       *button,
                        XedPrintPreview *preview)
{
    zoom_in (preview);
}

static void
zoom_out_button_clicked (GtkWidget       *button,
                         XedPrintPreview *preview)
{
    zoom_out (preview);
}

static void
close_button_clicked (GtkWidget       *button,
                      XedPrintPreview *preview)
{
    gtk_widget_destroy (GTK_WIDGET (preview));
}

static void
create_bar (XedPrintPreview *preview)
{
    XedPrintPreviewPrivate *priv;
    GtkWidget *toolbar;
    GtkToolItem *i;
    AtkObject *atko;
    GtkWidget *status;
    GtkWidget *box;
    GtkWidget *close_button;

    priv = preview->priv;

    toolbar = gtk_toolbar_new ();
    gtk_style_context_add_class (gtk_widget_get_style_context (toolbar), "inline-toolbar");
    gtk_widget_show (toolbar);
    gtk_box_pack_start (GTK_BOX (preview), toolbar, FALSE, FALSE, 0);

    i = gtk_tool_item_new ();
    gtk_toolbar_insert (GTK_TOOLBAR (toolbar), i, -1);

    box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
    gtk_style_context_add_class (gtk_widget_get_style_context (box), "linked");
    gtk_container_add (GTK_CONTAINER (i), box);

    priv->prev = gtk_button_new_from_icon_name ("go-previous-symbolic", GTK_ICON_SIZE_MENU);
    gtk_box_pack_start (GTK_BOX (box), priv->prev, FALSE, FALSE, 0);
    gtk_widget_set_tooltip_text (priv->prev, _("Show the previous page"));
    g_signal_connect (priv->prev, "clicked",
                      G_CALLBACK (prev_button_clicked), preview);

    priv->next = gtk_button_new_from_icon_name ("go-next-symbolic", GTK_ICON_SIZE_MENU);
    gtk_box_pack_start (GTK_BOX (box), priv->next, FALSE, FALSE, 0);
    gtk_widget_set_tooltip_text (priv->next, _("Show the next page"));
    g_signal_connect (priv->next, "clicked",
                      G_CALLBACK (next_button_clicked), preview);

    gtk_widget_show_all (GTK_WIDGET (i));

    i = gtk_separator_tool_item_new ();
    gtk_widget_show (GTK_WIDGET (i));
    gtk_toolbar_insert (GTK_TOOLBAR (toolbar), i, -1);

    status = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 4);
    priv->page_entry = gtk_entry_new ();
    gtk_entry_set_width_chars (GTK_ENTRY (priv->page_entry), 3);
    gtk_entry_set_max_length (GTK_ENTRY (priv->page_entry), 6);
    gtk_widget_set_tooltip_text (priv->page_entry, _("Current page (Alt+P)"));

    g_signal_connect (priv->page_entry, "activate",
                      G_CALLBACK (page_entry_activated), preview);
    g_signal_connect (priv->page_entry, "insert-text",
                      G_CALLBACK (page_entry_insert_text), NULL);
    g_signal_connect (priv->page_entry, "focus-out-event",
                      G_CALLBACK (page_entry_focus_out), preview);

    gtk_box_pack_start (GTK_BOX (status), priv->page_entry, FALSE, FALSE, 0);
    /* gtk_label_set_mnemonic_widget ((GtkLabel *) l, mp->priv->page_entry); */

    /* We are displaying 'XXX of XXX'. */
    /* Translators: the "of" from "1 of 19" in print preview. */
    gtk_box_pack_start (GTK_BOX (status), gtk_label_new (_("of")), FALSE, FALSE, 0);

    priv->last = gtk_label_new ("");
    gtk_box_pack_start (GTK_BOX (status), priv->last, FALSE, FALSE, 0);
    atko = gtk_widget_get_accessible (priv->last);
    atk_object_set_name (atko, _("Page total"));
    atk_object_set_description (atko, _("The total number of pages in the document"));

    gtk_widget_show_all (status);

    i = gtk_tool_item_new ();
    gtk_container_add (GTK_CONTAINER (i), status);
    gtk_widget_show (GTK_WIDGET (i));
    gtk_toolbar_insert (GTK_TOOLBAR (toolbar), i, -1);

    i = gtk_separator_tool_item_new ();
    gtk_widget_show (GTK_WIDGET (i));
    gtk_toolbar_insert (GTK_TOOLBAR (toolbar), i, -1);

    i = gtk_tool_item_new ();
    gtk_toolbar_insert (GTK_TOOLBAR (toolbar), i, -1);
    box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
    gtk_container_add (GTK_CONTAINER (i), box);

    priv->multi = gtk_button_new_from_icon_name ("view-grid-symbolic", GTK_ICON_SIZE_MENU);
    gtk_box_pack_start (GTK_BOX (box), priv->multi, FALSE, FALSE, 0);
    gtk_widget_set_tooltip_text (priv->multi, _("Show multiple pages"));
    g_signal_connect (priv->multi, "clicked",
                      G_CALLBACK (multi_button_clicked), preview);

    gtk_widget_show_all (GTK_WIDGET (i));

    i = gtk_separator_tool_item_new ();
    gtk_widget_show (GTK_WIDGET (i));
    gtk_toolbar_insert (GTK_TOOLBAR (toolbar), i, -1);

    i = gtk_tool_item_new ();
    gtk_toolbar_insert (GTK_TOOLBAR (toolbar), i, -1);
    box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
    gtk_style_context_add_class (gtk_widget_get_style_context (box), "linked");
    gtk_container_add (GTK_CONTAINER (i), box);

    priv->zoom_one = gtk_button_new_from_icon_name ("zoom-original-symbolic", GTK_ICON_SIZE_MENU);
    gtk_box_pack_start (GTK_BOX (box), priv->zoom_one, FALSE, FALSE, 0);
    gtk_widget_set_tooltip_text (priv->zoom_one, _("Zoom 1:1"));
    g_signal_connect (priv->zoom_one, "clicked",
                      G_CALLBACK (zoom_one_button_clicked), preview);

    priv->zoom_fit = gtk_button_new_from_icon_name ("zoom-fit-best-symbolic", GTK_ICON_SIZE_MENU);
    gtk_box_pack_start (GTK_BOX (box), priv->zoom_fit, FALSE, FALSE, 0);
    gtk_widget_set_tooltip_text (priv->zoom_fit, _("Zoom to fit the whole page"));
    g_signal_connect (priv->zoom_fit, "clicked",
                      G_CALLBACK (zoom_fit_button_clicked), preview);

    priv->zoom_in = gtk_button_new_from_icon_name ("zoom-in-symbolic", GTK_ICON_SIZE_MENU);
    gtk_box_pack_start (GTK_BOX (box), priv->zoom_in, FALSE, FALSE, 0);
    gtk_widget_set_tooltip_text (priv->zoom_in, _("Zoom the page in"));
    g_signal_connect (priv->zoom_in, "clicked",
                      G_CALLBACK (zoom_in_button_clicked), preview);

    priv->zoom_out = gtk_button_new_from_icon_name ("zoom-out-symbolic", GTK_ICON_SIZE_MENU);
    gtk_box_pack_start (GTK_BOX (box), priv->zoom_out, FALSE, FALSE, 0);
    gtk_widget_set_tooltip_text (priv->zoom_out, _("Zoom the page out"));
    g_signal_connect (priv->zoom_out, "clicked",
                      G_CALLBACK (zoom_out_button_clicked), preview);

    gtk_widget_show_all (GTK_WIDGET (i));

    i = gtk_separator_tool_item_new ();
    gtk_widget_show (GTK_WIDGET (i));
    gtk_toolbar_insert (GTK_TOOLBAR (toolbar), i, -1);

    i = gtk_tool_item_new ();
    gtk_toolbar_insert (GTK_TOOLBAR (toolbar), i, -1);
    box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
    gtk_container_add (GTK_CONTAINER (i), box);

    close_button = gtk_button_new_with_mnemonic (_("_Close preview"));
    gtk_box_pack_start (GTK_BOX (box), close_button, FALSE, FALSE, 0);
    gtk_widget_set_tooltip_text (close_button, _("Close print preview"));
    g_signal_connect (close_button, "clicked",
                      G_CALLBACK (close_button_clicked), preview);

    gtk_widget_show_all (GTK_WIDGET (i));
}

static gint
get_first_page_displayed (XedPrintPreview *preview)
{
    XedPrintPreviewPrivate *priv;

    priv = preview->priv;

    return priv->cur_page - priv->cur_page % (priv->cols * priv->rows);
}

/* returns the page number (starting from 0) or -1 if no page */
static gint
get_page_at_coords (XedPrintPreview *preview,
                    gint             x,
                    gint             y)
{
    XedPrintPreviewPrivate *priv;
    GtkAdjustment *hadj, *vadj;
    gint r, c, pg;

    priv = preview->priv;

    if (priv->tile_h <= 0 || priv->tile_h <= 0)
    {
        return -1;
    }

    hadj = gtk_scrollable_get_hadjustment (GTK_SCROLLABLE (priv->layout));
    vadj = gtk_scrollable_get_vadjustment (GTK_SCROLLABLE (priv->layout));

    x += gtk_adjustment_get_value (hadj);
    y += gtk_adjustment_get_value (vadj);

    r = 1 + y / (priv->tile_h);
    c = 1 + x / (priv->tile_w);

    if (c > priv->cols)
    {
        return -1;
    }

    pg = get_first_page_displayed (preview) - 1;
    pg += (r - 1) * priv->cols + c;

    if (pg >= priv->n_pages)
    {
        return -1;
    }

    /* FIXME: we could try to be picky and check
     * if we actually are inside the page */
    return pg;
}

static gboolean
preview_layout_query_tooltip (GtkWidget       *widget,
                              gint             x,
                              gint             y,
                              gboolean         keyboard_tip,
                              GtkTooltip      *tooltip,
                              XedPrintPreview *preview)
{
    gint pg;
    gchar *tip;

    pg = get_page_at_coords (preview, x, y);
    if (pg < 0)
    {
        return FALSE;
    }

    tip = g_strdup_printf (_("Page %d of %d"), pg + 1, preview->priv->n_pages);
    gtk_tooltip_set_text (tooltip, tip);
    g_free (tip);

    return TRUE;
}

static gint
preview_layout_key_press (GtkWidget       *widget,
                          GdkEventKey     *event,
                          XedPrintPreview *preview)
{
    XedPrintPreviewPrivate *priv;
    GtkAdjustment *hadj, *vadj;
    double x, y;
    guint h, w;
    double hlower, hupper, vlower, vupper;
    double hpage, vpage;
    double hstep, vstep;
    gboolean domove = FALSE;
    gboolean ret = TRUE;

    priv = preview->priv;

    hadj = gtk_scrollable_get_hadjustment (GTK_SCROLLABLE (priv->layout));
    vadj = gtk_scrollable_get_vadjustment (GTK_SCROLLABLE (priv->layout));

    x = gtk_adjustment_get_value (hadj);
    y = gtk_adjustment_get_value (vadj);

    g_object_get (hadj,
                  "lower", &hlower,
                  "upper", &hupper,
                  "page-size", &hpage,
                  NULL);
    g_object_get (vadj,
                  "lower", &vlower,
                  "upper", &vupper,
                  "page-size", &vpage,
                  NULL);

    gtk_layout_get_size (GTK_LAYOUT (priv->layout), &w, &h);

    hstep = 10;
    vstep = 10;

    switch (event->keyval)
    {
        case '1':
            set_zoom_fit_to_size (preview);
            break;
        case '+':
        case '=':
        case GDK_KEY_KP_Add:
            zoom_in (preview);
            break;
        case '-':
        case '_':
        case GDK_KEY_KP_Subtract:
            zoom_out (preview);
            break;
        case GDK_KEY_KP_Right:
        case GDK_KEY_Right:
            if (event->state & GDK_SHIFT_MASK)
                x = hupper - hpage;
            else
                x = MIN (hupper - hpage, x + hstep);
            domove = TRUE;
            break;
        case GDK_KEY_KP_Left:
        case GDK_KEY_Left:
            if (event->state & GDK_SHIFT_MASK)
                x = hlower;
            else
                x = MAX (hlower, x - hstep);
            domove = TRUE;
            break;
        case GDK_KEY_KP_Up:
        case GDK_KEY_Up:
            if (event->state & GDK_SHIFT_MASK)
                goto page_up;
            y = MAX (vlower, y - vstep);
            domove = TRUE;
            break;
        case GDK_KEY_KP_Down:
        case GDK_KEY_Down:
            if (event->state & GDK_SHIFT_MASK)
                goto page_down;
            y = MIN (vupper - vpage, y + vstep);
            domove = TRUE;
            break;
        case GDK_KEY_KP_Page_Up:
        case GDK_KEY_Page_Up:
        case GDK_KEY_Delete:
        case GDK_KEY_KP_Delete:
        case GDK_KEY_BackSpace:
        page_up:
            if (y <= vlower)
            {
                if (preview->priv->cur_page > 0)
                {
                    goto_page (preview, preview->priv->cur_page - 1);
                    y = (vupper - vpage);
                }
            }
            else
            {
                y = vlower;
            }
            domove = TRUE;
            break;
        case GDK_KEY_KP_Page_Down:
        case GDK_KEY_Page_Down:
        case ' ':
        page_down:
            if (y >= (vupper - vpage))
            {
                if (preview->priv->cur_page < preview->priv->n_pages - 1)
                {
                    goto_page (preview, preview->priv->cur_page + 1);
                    y = vlower;
                }
            }
            else
            {
                y = (vupper - vpage);
            }
            domove = TRUE;
            break;
        case GDK_KEY_KP_Home:
        case GDK_KEY_Home:
            goto_page (preview, 0);
            y = 0;
            domove = TRUE;
            break;
        case GDK_KEY_KP_End:
        case GDK_KEY_End:
            goto_page (preview, preview->priv->n_pages - 1);
            y = 0;
            domove = TRUE;
            break;
        case GDK_KEY_Escape:
            gtk_widget_destroy (GTK_WIDGET (preview));
            break;
        case 'c':
            if (event->state & GDK_MOD1_MASK)
            {
                gtk_widget_destroy (GTK_WIDGET (preview));
            }
            break;
        case 'p':
            if (event->state & GDK_MOD1_MASK)
            {
                gtk_widget_grab_focus (preview->priv->page_entry);
            }
            break;
        default:
            /* by default do not stop the default handler */
            ret = FALSE;
    }

    if (domove)
    {
        gtk_adjustment_set_value (hadj, x);
        gtk_adjustment_set_value (vadj, y);

        gtk_adjustment_value_changed (hadj);
        gtk_adjustment_value_changed (vadj);
    }

    return ret;
}

static void
create_preview_layout (XedPrintPreview *preview)
{
    XedPrintPreviewPrivate *priv;
    AtkObject *atko;

    priv = preview->priv;

    priv->layout = gtk_layout_new (NULL, NULL);
//  gtk_widget_set_double_buffered (priv->layout, FALSE);

    atko = gtk_widget_get_accessible (GTK_WIDGET (priv->layout));
    atk_object_set_name (atko, _("Page Preview"));
    atk_object_set_description (atko, _("The preview of a page in the document to be printed"));

    gtk_widget_add_events (priv->layout,
                           GDK_POINTER_MOTION_MASK |
                           GDK_BUTTON_PRESS_MASK |
                           GDK_KEY_PRESS_MASK);

    gtk_widget_set_can_focus (priv->layout, TRUE);

    g_signal_connect (priv->layout, "key-press-event",
                      G_CALLBACK (preview_layout_key_press), preview);

    g_object_set (priv->layout, "has-tooltip", TRUE, NULL);
    g_signal_connect (priv->layout, "query-tooltip",
                      G_CALLBACK (preview_layout_query_tooltip), preview);

    priv->scrolled_window = gtk_scrolled_window_new (NULL, NULL);
    gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (priv->scrolled_window),
                                    GTK_POLICY_AUTOMATIC,
                                    GTK_POLICY_AUTOMATIC);

    gtk_container_add (GTK_CONTAINER (priv->scrolled_window), priv->layout);
    gtk_box_pack_end (GTK_BOX (preview), priv->scrolled_window, TRUE, TRUE, 0);

    gtk_widget_show_all (GTK_WIDGET (priv->scrolled_window));
    gtk_widget_grab_focus (GTK_WIDGET (priv->layout));
}

static void
xed_print_preview_init (XedPrintPreview *preview)
{
    XedPrintPreviewPrivate *priv;

    priv = G_TYPE_INSTANCE_GET_PRIVATE (preview, XED_TYPE_PRINT_PREVIEW, XedPrintPreviewPrivate);

    preview->priv = priv;

    priv->operation = NULL;
    priv->context = NULL;
    priv->gtk_preview = NULL;

    gtk_orientable_set_orientation (GTK_ORIENTABLE (preview), GTK_ORIENTATION_VERTICAL);

    create_bar (preview);
    create_preview_layout (preview);

    // FIXME
    priv->cur_page = 0;
    priv->paper_w = 0;
    priv->paper_h = 0;
    priv->dpi = PRINTER_DPI;
    priv->scale = 1.0;
    priv->rows = 1;
    priv->cols = 1;
}

static void
draw_page_content (cairo_t          *cr,
                   gint              page_number,
                   XedPrintPreview  *preview)
{
    /* scale to the desired size */
    cairo_scale (cr, preview->priv->scale, preview->priv->scale);

    /* rotate acording to page orientation if needed */
    if ((preview->priv->orientation == GTK_PAGE_ORIENTATION_LANDSCAPE) ||
        (preview->priv->orientation == GTK_PAGE_ORIENTATION_REVERSE_LANDSCAPE))
    {
        cairo_matrix_t matrix;

        cairo_matrix_init (&matrix,
                           0, -1,
                           1,  0,
                           0,  get_paper_width (preview));
        cairo_transform (cr, &matrix);
    }

    gtk_print_context_set_cairo_context (preview->priv->context,
                                         cr,
                                         preview->priv->dpi,
                                         preview->priv->dpi);

    gtk_print_operation_preview_render_page (preview->priv->gtk_preview, page_number);
}

/* For the frame, we scale and rotate manually, since
 * the line width should not depend on the zoom and
 * the drop shadow should be on the bottom right no matter
 * the orientation */
static void
draw_page_frame (cairo_t         *cr,
                 XedPrintPreview *preview)
{
    double w, h;

    w = get_paper_width (preview);
    h = get_paper_height (preview);

    if ((preview->priv->orientation == GTK_PAGE_ORIENTATION_LANDSCAPE) ||
        (preview->priv->orientation == GTK_PAGE_ORIENTATION_REVERSE_LANDSCAPE))
    {
        double tmp;

        tmp = w;
        w = h;
        h = tmp;
    }

    w *= preview->priv->scale;
    h *= preview->priv->scale;

    /* drop shadow */
    cairo_set_source_rgb (cr, 0, 0, 0);
    cairo_rectangle (cr, PAGE_SHADOW_OFFSET, PAGE_SHADOW_OFFSET, w, h);
    cairo_fill (cr);

    /* page frame */
    cairo_set_source_rgb (cr, 1, 1, 1);
    cairo_rectangle (cr, 0, 0, w, h);
    cairo_fill_preserve (cr);
    cairo_set_source_rgb (cr, 0, 0, 0);
    cairo_set_line_width (cr, 1);
    cairo_stroke (cr);
}

static void
draw_page (cairo_t         *cr,
           double           x,
           double           y,
           gint             page_number,
           XedPrintPreview *preview)
{
    cairo_save (cr);

    /* move to the page top left corner */
    cairo_translate (cr, x + PAGE_PAD, y + PAGE_PAD);

    draw_page_frame (cr, preview);
    draw_page_content (cr, page_number, preview);

    cairo_restore (cr);
}

static gboolean
preview_draw (GtkWidget       *widget,
              cairo_t         *cr,
              XedPrintPreview *preview)
{
    XedPrintPreviewPrivate *priv;
    GdkWindow *bin_window;
    gint pg;
    gint i, j;

    priv = preview->priv;

    bin_window = gtk_layout_get_bin_window (GTK_LAYOUT (priv->layout));

    if (!gtk_cairo_should_draw_window (cr, bin_window))
    {
        return TRUE;
    }

    cairo_save (cr);

    gtk_cairo_transform_to_window (cr, widget, bin_window);

    /* get the first page to display */
    pg = get_first_page_displayed (preview);

    for (i = 0; i < priv->cols; ++i)
    {
        for (j = 0; j < priv->rows; ++j)
        {
            if (!gtk_print_operation_preview_is_selected (priv->gtk_preview, pg))
            {
                continue;
            }

            if (pg == priv->n_pages)
            {
                break;
            }

            draw_page (cr, j * priv->tile_w, i * priv->tile_h, pg, preview);

            ++pg;
        }
    }

    cairo_restore (cr);

    return TRUE;
}

static double
get_screen_dpi (XedPrintPreview *preview)
{
    GdkScreen *screen;
    double dpi;

    screen = gtk_widget_get_screen (GTK_WIDGET (preview));

    dpi = gdk_screen_get_resolution (screen);
    if (dpi < 30. || 600. < dpi)
    {
        g_warning ("Invalid the x-resolution for the screen, assuming 96dpi");
        dpi = 96.;
    }

    return dpi;
}

static void
set_n_pages (XedPrintPreview *preview,
             gint             n_pages)
{
    gchar *str;

    preview->priv->n_pages = n_pages;

    // FIXME: count the visible pages

    str =  g_strdup_printf ("%d", n_pages);
    gtk_label_set_markup (GTK_LABEL (preview->priv->last), str);
    g_free (str);
}

static void
preview_ready (GtkPrintOperationPreview *gtk_preview,
               GtkPrintContext          *context,
               XedPrintPreview          *preview)
{
    gint n_pages;

    g_object_get (preview->priv->operation, "n-pages", &n_pages, NULL);
    set_n_pages (preview, n_pages);
    goto_page (preview, 0);

    /* figure out the dpi */
    preview->priv->dpi = get_screen_dpi (preview);

    set_zoom_factor (preview, 1.0);

    /* let the default gtklayout handler clear the background */
    g_signal_connect_after (preview->priv->layout, "draw",
                            G_CALLBACK (preview_draw), preview);

    gtk_widget_queue_draw (preview->priv->layout);
}

static void
update_paper_size (XedPrintPreview *preview,
                   GtkPageSetup    *page_setup)
{
    GtkPaperSize *paper_size;

    paper_size = gtk_page_setup_get_paper_size (page_setup);

    preview->priv->paper_w = gtk_paper_size_get_width (paper_size, GTK_UNIT_INCH);
    preview->priv->paper_h = gtk_paper_size_get_height (paper_size, GTK_UNIT_INCH);

    preview->priv->orientation = gtk_page_setup_get_orientation (page_setup);
}

static void
preview_got_page_size (GtkPrintOperationPreview *gtk_preview,
                       GtkPrintContext          *context,
                       GtkPageSetup             *page_setup,
                       XedPrintPreview          *preview)
{
    update_paper_size (preview, page_setup);
}

/* HACK: we need a dummy surface to paginate... can we use something simpler? */

static cairo_status_t
dummy_write_func (G_GNUC_UNUSED gpointer      closure,
                  G_GNUC_UNUSED const guchar *data,
                  G_GNUC_UNUSED guint         length)
{
    return CAIRO_STATUS_SUCCESS;
}

#define PRINTER_DPI (72.)

static cairo_surface_t *
create_preview_surface_platform (GtkPaperSize *paper_size,
                                 double       *dpi_x,
                                 double       *dpi_y)
{
    double width, height;
    cairo_surface_t *sf;

    width = gtk_paper_size_get_width (paper_size, GTK_UNIT_POINTS);
    height = gtk_paper_size_get_height (paper_size, GTK_UNIT_POINTS);

    *dpi_x = *dpi_y = PRINTER_DPI;

    sf = cairo_pdf_surface_create_for_stream (dummy_write_func, NULL, width, height);
    return sf;
}

static cairo_surface_t *
create_preview_surface (XedPrintPreview *preview,
                        double          *dpi_x,
                        double          *dpi_y)
{
    GtkPageSetup *page_setup;
    GtkPaperSize *paper_size;

    page_setup = gtk_print_context_get_page_setup (preview->priv->context);
    /* gtk_page_setup_get_paper_size swaps width and height for landscape */
    paper_size = gtk_page_setup_get_paper_size (page_setup);

    return create_preview_surface_platform (paper_size, dpi_x, dpi_y);
}

GtkWidget *
xed_print_preview_new (GtkPrintOperation        *op,
                       GtkPrintOperationPreview *gtk_preview,
                       GtkPrintContext          *context)
{
    XedPrintPreview *preview;
    GtkPageSetup *page_setup;
    cairo_surface_t *surface;
    cairo_t *cr;
    double dpi_x, dpi_y;

    g_return_val_if_fail (GTK_IS_PRINT_OPERATION (op), NULL);
    g_return_val_if_fail (GTK_IS_PRINT_OPERATION_PREVIEW (gtk_preview), NULL);

    preview = g_object_new (XED_TYPE_PRINT_PREVIEW, NULL);

    preview->priv->operation = g_object_ref (op);
    preview->priv->gtk_preview = g_object_ref (gtk_preview);
    preview->priv->context = g_object_ref (context);

    /* FIXME: is this legal?? */
    gtk_print_operation_set_unit (op, GTK_UNIT_POINTS);

    g_signal_connect (gtk_preview, "ready",
                      G_CALLBACK (preview_ready), preview);
    g_signal_connect (gtk_preview, "got-page-size",
                      G_CALLBACK (preview_got_page_size), preview);

    page_setup = gtk_print_context_get_page_setup (preview->priv->context);
    update_paper_size (preview, page_setup);

    /* FIXME: we need a cr to paginate... but we can't get the drawing
     * area surface because it's not there yet... for now I create
     * a dummy pdf surface */

    surface = create_preview_surface (preview, &dpi_x, &dpi_y);
    cr = cairo_create (surface);
    gtk_print_context_set_cairo_context (context, cr, dpi_x, dpi_y);
    cairo_destroy (cr);
    cairo_surface_destroy (surface);

    return GTK_WIDGET (preview);
}

