]> git.sur5r.net Git - glabels/blobdiff - src/mini-preview.c
Imported Upstream version 3.0.0
[glabels] / src / mini-preview.c
diff --git a/src/mini-preview.c b/src/mini-preview.c
new file mode 100644 (file)
index 0000000..dc92d6e
--- /dev/null
@@ -0,0 +1,1190 @@
+/*
+ *  mini-preview.c
+ *  Copyright (C) 2001-2009  Jim Evins <evins@snaught.com>.
+ *
+ *  This file is part of gLabels.
+ *
+ *  gLabels 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 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  gLabels 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 gLabels.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+
+#include "mini-preview.h"
+
+#include <math.h>
+#include <glib/gi18n.h>
+
+#include <libglabels.h>
+#include "cairo-label-path.h"
+#include "marshal.h"
+#include "color.h"
+#include "print.h"
+
+#include "debug.h"
+
+
+/*===========================================*/
+/* Private macros and constants.             */
+/*===========================================*/
+
+#define MARGIN 2
+#define SHADOW_OFFSET 3
+
+#define ARROW_SCALE 0.35
+#define UP_FONT_FAMILY "Sans"
+#define UP_SCALE 0.15
+
+/*===========================================*/
+/* Private types                             */
+/*===========================================*/
+
+enum {
+        CLICKED,
+        PRESSED,
+        RELEASED,
+        LAST_SIGNAL
+};
+
+typedef struct {
+        gdouble x;
+        gdouble y;
+} LabelCenter;
+
+struct _glMiniPreviewPrivate {
+
+        GtkWidget      *canvas;
+
+        lglTemplate    *template;
+        gint            labels_per_sheet;
+        LabelCenter    *centers;
+
+        gint            highlight_first;
+        gint            highlight_last;
+
+        gboolean        dragging;
+        gint            first_i;
+        gint            last_i;
+        gint            prev_i;
+
+        gboolean        draw_arrow_flag;
+        gboolean        rotate_flag;
+
+        gboolean        update_scheduled_flag;
+
+        glLabel        *label;
+        gint            page;
+        gint            n_sheets;
+        gint            n_copies;
+        gint            first;
+        gint            last;
+        gboolean        collate_flag;
+        gboolean        outline_flag;
+        gboolean        reverse_flag;
+        gboolean        crop_marks_flag;
+};
+
+
+/*===========================================*/
+/* Private globals                           */
+/*===========================================*/
+
+static gint mini_preview_signals[LAST_SIGNAL] = { 0 };
+
+
+/*===========================================*/
+/* Local function prototypes                 */
+/*===========================================*/
+
+static void     gl_mini_preview_finalize       (GObject                *object);
+
+static void     gl_mini_preview_construct      (glMiniPreview          *this,
+                                                gint                    height,
+                                                gint                    width);
+
+static gboolean button_press_event_cb          (GtkWidget              *widget,
+                                                GdkEventButton         *event);
+static gboolean motion_notify_event_cb         (GtkWidget              *widget,
+                                                GdkEventMotion         *event);
+static gboolean button_release_event_cb        (GtkWidget              *widget,
+                                                GdkEventButton         *event);
+
+
+static gboolean draw_cb                        (GtkWidget              *widget,
+                                                cairo_t                *cr,
+                                                glMiniPreview          *this);
+static void     style_set_cb                   (GtkWidget              *widget,
+                                                GtkStyle               *previous_style,
+                                                glMiniPreview          *this);
+
+static void     redraw                         (glMiniPreview          *this);
+static void     draw                           (glMiniPreview          *this,
+                                                cairo_t                *cr);
+
+static void     draw_shadow                    (glMiniPreview          *this,
+                                                cairo_t                *cr,
+                                                gdouble                 x,
+                                                gdouble                 y,
+                                                gdouble                 width,
+                                                gdouble                 height);
+static void     draw_paper                     (glMiniPreview          *this,
+                                                cairo_t                *cr,
+                                                gdouble                 width,
+                                                gdouble                 height,
+                                                gdouble                 line_width);
+static void     draw_labels                    (glMiniPreview          *this,
+                                                cairo_t                *cr,
+                                                lglTemplate            *template,
+                                                gdouble                 line_width);
+static void     draw_arrow                     (glMiniPreview          *this,
+                                                cairo_t                *cr);
+
+static void     draw_rich_preview              (glMiniPreview          *this,
+                                                cairo_t                *cr);
+
+
+static gint     find_closest_label             (glMiniPreview          *this,
+                                                gdouble                 x,
+                                                gdouble                 y);
+
+static gdouble  set_transform_and_get_scale    (glMiniPreview          *this,
+                                                cairo_t                *cr);
+
+
+/****************************************************************************/
+/* Object infrastructure.                                                   */
+/****************************************************************************/
+G_DEFINE_TYPE (glMiniPreview, gl_mini_preview, GTK_TYPE_EVENT_BOX)
+
+
+/*****************************************************************************/
+/* Class Init Function.                                                      */
+/*****************************************************************************/
+static void
+gl_mini_preview_class_init (glMiniPreviewClass *class)
+{
+        GObjectClass   *object_class = G_OBJECT_CLASS (class);
+        GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class);
+
+        gl_debug (DEBUG_MINI_PREVIEW, "START");
+
+        gl_mini_preview_parent_class = g_type_class_peek_parent (class);
+
+        object_class->finalize = gl_mini_preview_finalize;
+
+        widget_class->button_press_event   = button_press_event_cb;
+        widget_class->motion_notify_event  = motion_notify_event_cb;
+        widget_class->button_release_event = button_release_event_cb;
+
+        mini_preview_signals[CLICKED] =
+            g_signal_new ("clicked",
+                          G_OBJECT_CLASS_TYPE(object_class),
+                          G_SIGNAL_RUN_LAST,
+                          G_STRUCT_OFFSET (glMiniPreviewClass, clicked),
+                          NULL, NULL,
+                          gl_marshal_VOID__INT,
+                          G_TYPE_NONE, 1, G_TYPE_INT);
+
+        mini_preview_signals[PRESSED] =
+            g_signal_new ("pressed",
+                          G_OBJECT_CLASS_TYPE(object_class),
+                          G_SIGNAL_RUN_LAST,
+                          G_STRUCT_OFFSET (glMiniPreviewClass, pressed),
+                          NULL, NULL,
+                          gl_marshal_VOID__INT_INT,
+                          G_TYPE_NONE, 2, G_TYPE_INT, G_TYPE_INT);
+
+        mini_preview_signals[RELEASED] =
+            g_signal_new ("released",
+                          G_OBJECT_CLASS_TYPE(object_class),
+                          G_SIGNAL_RUN_LAST,
+                          G_STRUCT_OFFSET (glMiniPreviewClass, released),
+                          NULL, NULL,
+                          gl_marshal_VOID__INT_INT,
+                          G_TYPE_NONE, 2, G_TYPE_INT, G_TYPE_INT);
+
+        gl_debug (DEBUG_MINI_PREVIEW, "END");
+}
+
+
+/*****************************************************************************/
+/* Object Instance Init Function.                                            */
+/*****************************************************************************/
+static void
+gl_mini_preview_init (glMiniPreview *this)
+{
+        gl_debug (DEBUG_MINI_PREVIEW, "START");
+
+        this->priv = g_new0 (glMiniPreviewPrivate, 1);
+
+        gtk_widget_add_events (GTK_WIDGET (this),
+                               GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK |
+                               GDK_POINTER_MOTION_MASK);
+
+        gtk_event_box_set_visible_window (GTK_EVENT_BOX (this), FALSE);
+
+        this->priv->canvas = gtk_drawing_area_new ();
+        gtk_widget_set_has_window(this->priv->canvas, FALSE);
+        gtk_container_add (GTK_CONTAINER (this), this->priv->canvas);
+
+        g_signal_connect (G_OBJECT (this->priv->canvas), "draw",
+                          G_CALLBACK (draw_cb), this);
+        g_signal_connect (G_OBJECT (this->priv->canvas), "style-set",
+                          G_CALLBACK (style_set_cb), this);
+
+        gl_debug (DEBUG_MINI_PREVIEW, "END");
+}
+
+
+/*****************************************************************************/
+/* Finalize Method.                                                          */
+/*****************************************************************************/
+static void
+gl_mini_preview_finalize (GObject *object)
+{
+        glMiniPreview *this = GL_MINI_PREVIEW (object);
+
+        gl_debug (DEBUG_MINI_PREVIEW, "START");
+
+        g_return_if_fail (object != NULL);
+        g_return_if_fail (GL_IS_MINI_PREVIEW (object));
+
+        if (this->priv->label)
+        {
+                g_object_unref (this->priv->label);
+        }
+        lgl_template_free (this->priv->template);
+        g_free (this->priv->centers);
+        g_free (this->priv);
+
+        G_OBJECT_CLASS (gl_mini_preview_parent_class)->finalize (object);
+
+        gl_debug (DEBUG_MINI_PREVIEW, "END");
+}
+
+
+/*****************************************************************************/
+/** New Object Generator.                                                    */
+/*****************************************************************************/
+GtkWidget *
+gl_mini_preview_new (gint height,
+                     gint width)
+{
+        glMiniPreview *this;
+
+        gl_debug (DEBUG_MINI_PREVIEW, "START");
+
+        this = g_object_new (gl_mini_preview_get_type (), NULL);
+
+        gl_mini_preview_construct (this, height, width);
+
+        gl_debug (DEBUG_MINI_PREVIEW, "END");
+
+        return GTK_WIDGET (this);
+}
+
+
+/*--------------------------------------------------------------------------*/
+/* Construct composite widget.                                              */
+/*--------------------------------------------------------------------------*/
+static void
+gl_mini_preview_construct (glMiniPreview *this,
+                           gint           height,
+                           gint           width)
+{
+        gl_debug (DEBUG_MINI_PREVIEW, "START");
+
+        gtk_widget_set_size_request (GTK_WIDGET (this->priv->canvas), width, height);
+
+        gl_debug (DEBUG_MINI_PREVIEW, "END");
+}
+
+
+/****************************************************************************/
+/* Set template for mini-preview to determine geometry.                     */
+/****************************************************************************/
+void
+gl_mini_preview_set_by_name (glMiniPreview *this,
+                             const gchar   *name)
+{
+        lglTemplate *template;
+
+        gl_debug (DEBUG_MINI_PREVIEW, "START");
+
+        /* Fetch template */
+        template = lgl_db_lookup_template_from_name (name);
+
+        gl_mini_preview_set_template (this, template);
+
+        lgl_template_free (template);
+
+        gl_debug (DEBUG_MINI_PREVIEW, "END");
+}
+
+
+/****************************************************************************/
+/* Set template for mini-preview to determine geometry.                     */
+/****************************************************************************/
+void
+gl_mini_preview_set_template (glMiniPreview     *this,
+                              const lglTemplate *template)
+{
+        const lglTemplateFrame    *frame;
+        lglTemplateOrigin         *origins;
+        gdouble                    w, h;
+        gint                       i;
+
+        gl_debug (DEBUG_MINI_PREVIEW, "START");
+
+        frame = (lglTemplateFrame *)template->frames->data;
+
+        /*
+         * Set template
+         */
+        lgl_template_free (this->priv->template);
+        this->priv->template = lgl_template_dup (template);
+
+        /*
+         * Set labels per sheet
+         */
+        this->priv->labels_per_sheet = lgl_template_frame_get_n_labels (frame);
+
+        /*
+         * Initialize centers
+         */
+        g_free (this->priv->centers);
+        this->priv->centers = g_new0 (LabelCenter, this->priv->labels_per_sheet);
+        origins = lgl_template_frame_get_origins (frame);
+        lgl_template_frame_get_size (frame, &w, &h);
+        for ( i=0; i<this->priv->labels_per_sheet; i++ )
+        {
+                this->priv->centers[i].x = origins[i].x + w/2.0;
+                this->priv->centers[i].y = origins[i].y + h/2.0;
+        }
+        g_free (origins);
+
+        /*
+         * Redraw modified preview
+         */
+        redraw (this);
+
+        gl_debug (DEBUG_MINI_PREVIEW, "END");
+}
+
+
+/****************************************************************************/
+/* Highlight given label outlines.                                          */
+/****************************************************************************/
+void
+gl_mini_preview_highlight_range (glMiniPreview *this,
+                                 gint           first_label,
+                                 gint           last_label)
+{
+        gl_debug (DEBUG_MINI_PREVIEW, "START");
+
+        if ( (first_label != this->priv->highlight_first) ||
+             (last_label  != this->priv->highlight_last) )
+        {
+
+                this->priv->highlight_first = first_label;
+                this->priv->highlight_last =  last_label;
+
+                redraw (this);
+
+        }
+
+        gl_debug (DEBUG_MINI_PREVIEW, "END");
+}
+
+
+/****************************************************************************/
+/* Set draw arrow.                                                          */
+/****************************************************************************/
+void
+gl_mini_preview_set_draw_arrow (glMiniPreview     *this,
+                                gboolean           draw_arrow_flag)
+{
+        if ( draw_arrow_flag != this->priv->draw_arrow_flag )
+        {
+                this->priv->draw_arrow_flag = draw_arrow_flag;
+                redraw (this);
+        }
+}
+
+
+/****************************************************************************/
+/* Set rotate flag.                                                         */
+/****************************************************************************/
+void
+gl_mini_preview_set_rotate (glMiniPreview     *this,
+                            gboolean           rotate_flag)
+{
+        if ( rotate_flag != this->priv->rotate_flag )
+        {
+                this->priv->rotate_flag = rotate_flag;
+                redraw (this);
+        }
+}
+
+
+/****************************************************************************/
+/* Set label.                                                               */
+/****************************************************************************/
+void
+gl_mini_preview_set_label (glMiniPreview     *this,
+                           glLabel           *label)
+{
+        if ( this->priv->label )
+        {
+                g_object_unref (this->priv->label);
+        }
+        this->priv->label = g_object_ref (label);
+        redraw (this);
+}
+
+
+/****************************************************************************/
+/* Set page number.                                                         */
+/****************************************************************************/
+void
+gl_mini_preview_set_page (glMiniPreview     *this,
+                          gint               page)
+{
+        if ( page != this->priv->page )
+        {
+                this->priv->page = page;
+                redraw (this);
+        }
+}
+
+
+/****************************************************************************/
+/* Set number of sheets.                                                    */
+/****************************************************************************/
+void
+gl_mini_preview_set_n_sheets (glMiniPreview     *this,
+                              gint               n_sheets)
+{
+        if ( n_sheets != this->priv->n_sheets )
+        {
+                this->priv->n_sheets = n_sheets;
+                redraw (this);
+        }
+}
+
+
+/****************************************************************************/
+/* Set number of copies (merge only).                                       */
+/****************************************************************************/
+void
+gl_mini_preview_set_n_copies (glMiniPreview     *this,
+                              gint               n_copies)
+{
+        if ( n_copies != this->priv->n_copies )
+        {
+                this->priv->n_copies = n_copies;
+                redraw (this);
+        }
+}
+
+
+/****************************************************************************/
+/* Set first label number on first sheet.                                   */
+/****************************************************************************/
+void
+gl_mini_preview_set_first (glMiniPreview     *this,
+                           gint               first)
+{
+        if ( first != this->priv->first )
+        {
+                this->priv->first = first;
+                redraw (this);
+        }
+}
+
+
+/****************************************************************************/
+/* Set last label number on first sheet (non-merge only).                   */
+/****************************************************************************/
+void
+gl_mini_preview_set_last (glMiniPreview     *this,
+                          gint               last)
+{
+        if ( last != this->priv->last )
+        {
+                this->priv->last = last;
+                redraw (this);
+        }
+}
+
+
+/****************************************************************************/
+/* Set collate flag (merge only).                                           */
+/****************************************************************************/
+void
+gl_mini_preview_set_collate_flag (glMiniPreview     *this,
+                                  gboolean           collate_flag)
+{
+        if ( collate_flag != this->priv->collate_flag )
+        {
+                this->priv->collate_flag = collate_flag;
+                redraw (this);
+        }
+}
+
+
+/****************************************************************************/
+/* Set outline flag.                                                        */
+/****************************************************************************/
+void
+gl_mini_preview_set_outline_flag (glMiniPreview     *this,
+                                  gboolean           outline_flag)
+{
+        if ( outline_flag != this->priv->outline_flag )
+        {
+                this->priv->outline_flag = outline_flag;
+                redraw (this);
+        }
+}
+
+
+/****************************************************************************/
+/* Set reverse flag.                                                        */
+/****************************************************************************/
+void
+gl_mini_preview_set_reverse_flag (glMiniPreview     *this,
+                                  gboolean           reverse_flag)
+{
+        if ( reverse_flag != this->priv->reverse_flag )
+        {
+                this->priv->reverse_flag = reverse_flag;
+                redraw (this);
+        }
+}
+
+
+/****************************************************************************/
+/* Set crop marks flag.                                                     */
+/****************************************************************************/
+void
+gl_mini_preview_set_crop_marks_flag (glMiniPreview     *this,
+                                     gboolean           crop_marks_flag)
+{
+        if ( crop_marks_flag != this->priv->crop_marks_flag )
+        {
+                this->priv->crop_marks_flag = crop_marks_flag;
+                redraw (this);
+        }
+}
+
+
+/*--------------------------------------------------------------------------*/
+/* Set transformation and return scale.                                     */
+/*--------------------------------------------------------------------------*/
+static gdouble
+set_transform_and_get_scale (glMiniPreview *this,
+                             cairo_t       *cr)
+{
+        lglTemplate   *template = this->priv->template;
+        GtkAllocation  allocation;
+        gdouble        w, h;
+        gdouble        scale;
+        gdouble        offset_x, offset_y;
+
+        /* Establish scale and origin. */
+        gtk_widget_get_allocation (GTK_WIDGET (this), &allocation);
+        w = allocation.width;
+        h = allocation.height;
+
+        /* establish scale. */
+        scale = MIN( (w - 2*MARGIN - 2*SHADOW_OFFSET)/template->page_width,
+                     (h - 2*MARGIN - 2*SHADOW_OFFSET)/template->page_height );
+
+        /* Find offset to center preview. */
+        offset_x = (w/scale - template->page_width) / 2.0;
+        offset_y = (h/scale - template->page_height) / 2.0;
+
+        /* Set transformation. */
+        cairo_scale (cr, scale, scale);
+        cairo_translate (cr, offset_x, offset_y);
+
+        return scale;
+}
+
+
+/*--------------------------------------------------------------------------*/
+/* Button press event handler                                               */
+/*--------------------------------------------------------------------------*/
+static gboolean
+button_press_event_cb (GtkWidget      *widget,
+                       GdkEventButton *event)
+{
+        glMiniPreview     *this = GL_MINI_PREVIEW (widget);
+        GdkWindow         *window;
+        cairo_t           *cr;
+        gdouble            scale;
+        gdouble            x, y;
+        gint               i;
+
+        gl_debug (DEBUG_MINI_PREVIEW, "START");
+
+        if ( event->button == 1 )
+        {
+
+                window = gtk_widget_get_window (this->priv->canvas);
+
+                cr = gdk_cairo_create (window);
+
+                scale = set_transform_and_get_scale (this, cr);
+
+                x = event->x;
+                y = event->y;
+                cairo_device_to_user (cr, &x, &y);
+
+                i = find_closest_label (this, x, y);
+
+                g_signal_emit (G_OBJECT(this),
+                               mini_preview_signals[CLICKED],
+                               0, i);
+
+                this->priv->first_i = i;
+                this->priv->last_i  = i;
+                g_signal_emit (G_OBJECT(this),
+                               mini_preview_signals[PRESSED],
+                               0, this->priv->first_i, this->priv->last_i);
+
+                this->priv->dragging = TRUE;
+                this->priv->prev_i   = i;
+
+                cairo_destroy (cr);
+        }
+
+        gl_debug (DEBUG_MINI_PREVIEW, "END");
+        return FALSE;
+}
+
+
+/*--------------------------------------------------------------------------*/
+/* Motion notify event handler                                              */
+/*--------------------------------------------------------------------------*/
+static gboolean
+motion_notify_event_cb (GtkWidget      *widget,
+                        GdkEventMotion *event)
+{
+        glMiniPreview *this = GL_MINI_PREVIEW (widget);
+        GdkWindow     *window;
+        cairo_t       *cr;
+        gdouble        scale;
+        gdouble        x, y;
+        gint           i;
+
+        gl_debug (DEBUG_MINI_PREVIEW, "START");
+
+        if (this->priv->dragging)
+        {
+                window = gtk_widget_get_window (this->priv->canvas);
+
+                cr = gdk_cairo_create (window);
+
+                scale = set_transform_and_get_scale (this, cr);
+
+                x = event->x;
+                y = event->y;
+                cairo_device_to_user (cr, &x, &y);
+
+                i = find_closest_label (this, x, y);
+
+                if ( i != this->priv->prev_i )
+                {
+                        this->priv->last_i = i;
+
+                        g_signal_emit (G_OBJECT(this),
+                                       mini_preview_signals[PRESSED],
+                                       0,
+                                       MIN (this->priv->first_i, this->priv->last_i),
+                                       MAX (this->priv->first_i, this->priv->last_i));
+
+                        this->priv->prev_i = i;
+                }
+                cairo_destroy (cr);
+        }
+
+        gl_debug (DEBUG_MINI_PREVIEW, "END");
+        return FALSE;
+}
+
+
+/*--------------------------------------------------------------------------*/
+/* Button release event handler                                             */
+/*--------------------------------------------------------------------------*/
+static gboolean
+button_release_event_cb (GtkWidget      *widget,
+                         GdkEventButton *event)
+{
+        glMiniPreview *this = GL_MINI_PREVIEW (widget);
+
+        gl_debug (DEBUG_MINI_PREVIEW, "START");
+
+        if ( event->button == 1 )
+        {
+                this->priv->dragging = FALSE;
+
+        }
+
+        g_signal_emit (G_OBJECT(this),
+                       mini_preview_signals[RELEASED],
+                       0, this->priv->first_i, this->priv->last_i);
+
+        gl_debug (DEBUG_MINI_PREVIEW, "END");
+        return FALSE;
+}
+
+
+/*--------------------------------------------------------------------------*/
+/* Find index+1 of label closest to given coordinates.                      */
+/*--------------------------------------------------------------------------*/
+static gint
+find_closest_label (glMiniPreview      *this,
+                    gdouble             x,
+                    gdouble             y)
+{
+        gint    i;
+        gint    min_i;
+        gdouble dx, dy, d2, min_d2;
+
+        dx = x - this->priv->centers[0].x;
+        dy = y - this->priv->centers[0].y;
+        min_d2 = dx*dx + dy*dy;
+        min_i = 0;
+
+        for ( i=1; i<this->priv->labels_per_sheet; i++ )
+        {
+                dx = x - this->priv->centers[i].x;
+                dy = y - this->priv->centers[i].y;
+                d2 = dx*dx + dy*dy;
+
+                if ( d2 < min_d2 )
+                {
+                        min_d2 = d2;
+                        min_i  = i;
+                }
+        }
+
+        return min_i + 1;
+}
+
+
+/*--------------------------------------------------------------------------*/
+/* Expose event handler.                                                    */
+/*--------------------------------------------------------------------------*/
+static gboolean
+draw_cb (GtkWidget       *widget,
+         cairo_t         *cr,
+         glMiniPreview   *this)
+{
+        gl_debug (DEBUG_MINI_PREVIEW, "START");
+
+        this->priv->update_scheduled_flag = FALSE;
+        draw (this, cr);
+
+        gl_debug (DEBUG_MINI_PREVIEW, "END");
+        return FALSE;
+}
+
+
+/*--------------------------------------------------------------------------*/
+/* Style set handler (updates colors when style/theme changes).             */
+/*--------------------------------------------------------------------------*/
+static void
+style_set_cb (GtkWidget        *widget,
+              GtkStyle         *previous_style,
+              glMiniPreview    *this)
+{
+        gl_debug (DEBUG_MINI_PREVIEW, "START");
+
+        redraw (this);
+
+        gl_debug (DEBUG_MINI_PREVIEW, "END");
+}
+
+
+/*--------------------------------------------------------------------------*/
+/* Redraw.                                                                  */
+/*--------------------------------------------------------------------------*/
+static void
+redraw (glMiniPreview      *this)
+{
+        GdkWindow     *window;
+        GtkAllocation  allocation;
+
+        gl_debug (DEBUG_MINI_PREVIEW, "START");
+
+        window = gtk_widget_get_window (this->priv->canvas);
+
+        if (window)
+        {
+
+                if ( !this->priv->update_scheduled_flag )
+                {
+                        this->priv->update_scheduled_flag = TRUE;
+
+                        gtk_widget_get_allocation (GTK_WIDGET (this), &allocation);
+                        gdk_window_invalidate_rect (window, &allocation, FALSE);
+                }
+        }
+
+        gl_debug (DEBUG_MINI_PREVIEW, "END");
+}
+
+
+/*--------------------------------------------------------------------------*/
+/* Draw mini preview.                                                       */
+/*--------------------------------------------------------------------------*/
+static void
+draw (glMiniPreview  *this,
+      cairo_t        *cr)
+{
+        lglTemplate *template = this->priv->template;
+        gdouble      scale;
+        gdouble      shadow_x, shadow_y;
+
+
+        gl_debug (DEBUG_MINI_PREVIEW, "START");
+
+        if (template)
+        {
+
+                scale = set_transform_and_get_scale (this, cr);
+
+                /* update shadow */
+                shadow_x = SHADOW_OFFSET/scale;
+                shadow_y = SHADOW_OFFSET/scale;
+
+                draw_shadow (this, cr,
+                             shadow_x, shadow_y,
+                             template->page_width, template->page_height);
+
+                draw_paper (this, cr,
+                            template->page_width, template->page_height,
+                            1.0/scale);
+
+                draw_labels (this, cr, template, 2.0/scale);
+
+                if (this->priv->draw_arrow_flag)
+                {
+                        draw_arrow (this, cr);
+                }
+
+                if (this->priv->label)
+                {
+                        draw_rich_preview (this, cr);
+                }
+
+        }
+
+        gl_debug (DEBUG_MINI_PREVIEW, "END");
+
+}
+
+
+/*--------------------------------------------------------------------------*/
+/* Draw page shadow                                                         */
+/*--------------------------------------------------------------------------*/
+static void
+draw_shadow (glMiniPreview      *this,
+             cairo_t            *cr,
+             gdouble             x,
+             gdouble             y,
+             gdouble             width,
+             gdouble             height)
+{
+        GtkStyle *style;
+        guint     shadow_color;
+
+        gl_debug (DEBUG_MINI_PREVIEW, "START");
+
+        cairo_save (cr);
+
+        cairo_rectangle (cr, x, y, width, height);
+
+        style = gtk_widget_get_style (GTK_WIDGET(this));
+        shadow_color = gl_color_from_gdk_color (&style->dark[GTK_STATE_NORMAL]);
+        cairo_set_source_rgb (cr, GL_COLOR_RGB_ARGS (shadow_color));
+
+        cairo_fill (cr);
+
+        cairo_restore (cr);
+
+        gl_debug (DEBUG_MINI_PREVIEW, "END");
+}
+
+
+/*--------------------------------------------------------------------------*/
+/* Draw page                                                                */
+/*--------------------------------------------------------------------------*/
+static void
+draw_paper (glMiniPreview      *this,
+            cairo_t            *cr,
+            gdouble             width,
+            gdouble             height,
+            gdouble             line_width)
+{
+        GtkStyle                  *style;
+        guint                      paper_color, outline_color;
+
+        gl_debug (DEBUG_MINI_PREVIEW, "START");
+
+        cairo_save (cr);
+
+        style = gtk_widget_get_style (GTK_WIDGET(this));
+        paper_color   = gl_color_from_gdk_color (&style->light[GTK_STATE_NORMAL]);
+        outline_color = gl_color_from_gdk_color (&style->fg[GTK_STATE_NORMAL]);
+
+        cairo_rectangle (cr, 0.0, 0.0, width, height);
+
+        cairo_set_source_rgb (cr, GL_COLOR_RGB_ARGS (paper_color));
+        cairo_fill_preserve (cr);
+
+        cairo_set_source_rgb (cr, GL_COLOR_RGB_ARGS (outline_color));
+        cairo_set_line_width (cr, line_width);
+        cairo_stroke (cr);
+
+        cairo_restore (cr);
+
+        gl_debug (DEBUG_MINI_PREVIEW, "END");
+}
+
+
+/*--------------------------------------------------------------------------*/
+/* Draw labels                                                              */
+/*--------------------------------------------------------------------------*/
+static void
+draw_labels (glMiniPreview *this,
+             cairo_t       *cr,
+             lglTemplate   *template,
+             gdouble        line_width)
+{
+        const lglTemplateFrame    *frame;
+        gint                       i, n_labels;
+        lglTemplateOrigin         *origins;
+        GtkStyle                  *style;
+        guint                      base_color;
+        guint                      highlight_color, paper_color, outline_color;
+
+        gl_debug (DEBUG_MINI_PREVIEW, "START");
+
+        frame = (lglTemplateFrame *)template->frames->data;
+
+        n_labels = lgl_template_frame_get_n_labels (frame);
+        origins  = lgl_template_frame_get_origins (frame);
+
+        style = gtk_widget_get_style (GTK_WIDGET(this));
+        base_color      = gl_color_from_gdk_color (&style->base[GTK_STATE_SELECTED]);
+
+        paper_color     = gl_color_from_gdk_color (&style->light[GTK_STATE_NORMAL]);
+        highlight_color = gl_color_set_opacity (base_color, 0.10);
+        if (this->priv->label)
+        {
+                /* Outlines are more subtle when doing a rich preview. */
+                outline_color   = gl_color_set_opacity (base_color, 0.25);
+        }
+        else
+        {
+                outline_color   = gl_color_set_opacity (base_color, 1.00);
+        }
+
+        for ( i=0; i < n_labels; i++ ) {
+
+                cairo_save (cr);
+
+                cairo_translate (cr, origins[i].x, origins[i].y);
+                gl_cairo_label_path (cr, template, FALSE, FALSE);
+
+                if ( ((i+1) >= this->priv->highlight_first) &&
+                     ((i+1) <= this->priv->highlight_last) )
+                {
+                        cairo_set_source_rgba (cr, GL_COLOR_RGBA_ARGS (highlight_color));
+                        cairo_set_fill_rule (cr, CAIRO_FILL_RULE_EVEN_ODD);
+                        cairo_fill_preserve (cr);
+                }
+
+                cairo_set_line_width (cr, line_width);
+                cairo_set_source_rgba (cr, GL_COLOR_RGBA_ARGS (outline_color));
+                cairo_stroke (cr);
+
+                cairo_restore (cr);
+
+        }
+
+        g_free (origins);
+
+        gl_debug (DEBUG_MINI_PREVIEW, "END");
+}
+
+
+/*--------------------------------------------------------------------------*/
+/* Draw arrow to indicate top of labels.                                    */
+/*--------------------------------------------------------------------------*/
+static void
+draw_arrow  (glMiniPreview      *this,
+             cairo_t            *cr)
+{
+        lglTemplateFrame  *frame;
+        lglTemplateOrigin *origins;
+        gdouble            width, height, min;
+        gdouble            x0, y0;
+        GtkStyle          *style;
+        guint              base_color, arrow_color;
+
+        PangoLayout          *layout;
+        PangoFontDescription *desc;
+        PangoRectangle        rect;
+
+        /* Translators: "Up" refers to the direction towards the top of a label. */
+        const gchar          *up = _("Up");
+
+        frame = (lglTemplateFrame *)this->priv->template->frames->data;
+
+        lgl_template_frame_get_size (frame, &width, &height);
+
+        if ( width != height )
+        {
+
+                origins = lgl_template_frame_get_origins (frame);
+                x0 = origins[0].x;
+                y0 = origins[0].y;
+                min = MIN (width, height);
+                g_free (origins);
+
+                cairo_save (cr);
+
+                style       = gtk_widget_get_style (GTK_WIDGET(this));
+                base_color  = gl_color_from_gdk_color (&style->base[GTK_STATE_SELECTED]);
+                arrow_color = gl_color_set_opacity (base_color, 0.25);
+                cairo_set_source_rgba (cr, GL_COLOR_RGBA_ARGS (arrow_color));
+
+                cairo_translate (cr, x0 + width/2, y0 + height/2);
+                cairo_scale (cr, 1, -1);
+                if ( this->priv->rotate_flag )
+                {
+                        cairo_rotate (cr, -G_PI/2.0);
+                }
+
+                cairo_new_path (cr);
+                cairo_move_to (cr, 0, -min*ARROW_SCALE/3);
+                cairo_line_to (cr, 0, min*ARROW_SCALE);
+
+                cairo_new_sub_path (cr);
+                cairo_move_to (cr, -min*ARROW_SCALE/2, min*ARROW_SCALE/2);
+                cairo_line_to (cr, 0, min*ARROW_SCALE);
+                cairo_line_to (cr, min*ARROW_SCALE/2, min*ARROW_SCALE/2);
+
+                cairo_set_line_width (cr, 0.25*min*ARROW_SCALE);
+
+                cairo_stroke (cr);
+
+                layout = pango_cairo_create_layout (cr);
+
+                desc = pango_font_description_new ();
+                pango_font_description_set_family (desc, UP_FONT_FAMILY);
+                pango_font_description_set_weight (desc, PANGO_WEIGHT_BOLD);
+                pango_font_description_set_size (desc, min*UP_SCALE*PANGO_SCALE);
+                pango_layout_set_font_description (layout, desc);
+                pango_font_description_free (desc);
+
+                pango_layout_set_text (layout, up, -1);
+                pango_layout_set_width (layout, -1);
+                pango_layout_get_pixel_extents (layout, NULL, &rect);
+
+                cairo_move_to (cr, -rect.width/2, -min/4+rect.height/2);
+
+                cairo_scale (cr, 1, -1);
+                pango_cairo_show_layout (cr, layout);
+
+                cairo_restore (cr);
+
+        }
+}
+
+
+/*--------------------------------------------------------------------------*/
+/* Draw rich preview using print renderers.                                 */
+/*--------------------------------------------------------------------------*/
+static void
+draw_rich_preview (glMiniPreview          *this,
+                   cairo_t                *cr)
+{
+        glMerge      *merge;
+        glPrintState  state;
+
+        merge = gl_label_get_merge (this->priv->label);
+
+        if (!merge)
+        {
+                gl_print_simple_sheet (this->priv->label,
+                                       cr,
+                                       this->priv->page,
+                                       this->priv->n_sheets,
+                                       this->priv->first,
+                                       this->priv->last,
+                                       this->priv->outline_flag,
+                                       this->priv->reverse_flag,
+                                       this->priv->crop_marks_flag);
+        }
+        else
+        {
+                /* FIXME: maybe the renderers should be more self contained.
+                 *        This will only work for the first page, since
+                 *        previous pages must be rendered to establish
+                 *        state.
+                 */
+                state.i_copy = 0;
+                state.p_record = (GList *)gl_merge_get_record_list (merge);
+
+                if (this->priv->collate_flag)
+                {
+                        gl_print_collated_merge_sheet (this->priv->label,
+                                                       cr,
+                                                       this->priv->page,
+                                                       this->priv->n_copies,
+                                                       this->priv->first,
+                                                       this->priv->outline_flag,
+                                                       this->priv->reverse_flag,
+                                                       this->priv->crop_marks_flag,
+                                                       &state);
+                }
+                else
+                {
+                        gl_print_uncollated_merge_sheet (this->priv->label,
+                                                         cr,
+                                                         this->priv->page,
+                                                         this->priv->n_copies,
+                                                         this->priv->first,
+                                                         this->priv->outline_flag,
+                                                         this->priv->reverse_flag,
+                                                         this->priv->crop_marks_flag,
+                                                         &state);
+                }
+        }
+}
+
+
+
+/*
+ * Local Variables:       -- emacs
+ * mode: C                -- emacs
+ * c-basic-offset: 8      -- emacs
+ * tab-width: 8           -- emacs
+ * indent-tabs-mode: nil  -- emacs
+ * End:                   -- emacs
+ */