/* GIMP - The GNU Image Manipulation Program
 * Copyright (C) 1995 Spencer Kimball and Peter Mattis
 *
 * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */
/*
 * Modified for viking by Quy Tonthat <qtonthat@gmail.com>
 */

#include <gtk/gtk.h>

#include "print-preview.h"
#include "vikviewport.h"
#include "config.h"

#define DRAWING_AREA_SIZE 200


enum
{
  OFFSETS_CHANGED,
  LAST_SIGNAL
};


static void      vik_print_preview_finalize         (GObject          *object);

static void      vik_print_preview_size_allocate    (GtkWidget        *widget,
                                                      GtkAllocation    *allocation,
                                                      VikPrintPreview *preview);
static void      vik_print_preview_realize          (GtkWidget        *widget);
static gboolean  vik_print_preview_event            (GtkWidget        *widget,
                                                      GdkEvent         *event,
                                                      VikPrintPreview *preview);

#if GTK_CHECK_VERSION (3,0,0)
static gboolean  vik_print_preview_draw             (GtkWidget       *widget,
                                                     cairo_t         *cr,
                                                     VikPrintPreview *preview);
#else
static gboolean  vik_print_preview_expose_event     (GtkWidget        *widget,
                                                      GdkEventExpose   *eevent,
                                                      VikPrintPreview *preview);
#endif

static gdouble   vik_print_preview_get_scale        (VikPrintPreview *preview);

static void      vik_print_preview_get_page_margins (VikPrintPreview *preview,
                                                      gdouble          *left_margin,
                                                      gdouble          *right_margin,
                                                      gdouble          *top_margin,
                                                      gdouble          *bottom_margin);

static void      print_preview_queue_draw            (VikPrintPreview *preview);


G_DEFINE_TYPE (VikPrintPreview, vik_print_preview, GTK_TYPE_ASPECT_FRAME)

#define parent_class vik_print_preview_parent_class

static guint vik_print_preview_signals[LAST_SIGNAL] = { 0 };


#define g_marshal_value_peek_double(v)   (v)->data[0].v_double

static void
marshal_VOID__DOUBLE_DOUBLE (GClosure     *closure,
                             GValue       *return_value,
                             guint         n_param_values,
                             const GValue *param_values,
                             gpointer      invocation_hint,
                             gpointer      marshal_data)
{
  typedef void (*GMarshalFunc_VOID__DOUBLE_DOUBLE) (gpointer     data1,
                                                    gdouble      arg_1,
                                                    gdouble      arg_2,
                                                    gpointer     data2);
  register GMarshalFunc_VOID__DOUBLE_DOUBLE callback;
  register GCClosure *cc = (GCClosure*) closure;
  register gpointer data1, data2;

  g_return_if_fail (n_param_values == 3);

  if (G_CCLOSURE_SWAP_DATA (closure))
    {
      data1 = closure->data;
      data2 = g_value_peek_pointer (param_values + 0);
    }
  else
    {
      data1 = g_value_peek_pointer (param_values + 0);
      data2 = closure->data;
    }

  callback = (GMarshalFunc_VOID__DOUBLE_DOUBLE) (marshal_data ?
                                                 marshal_data : cc->callback);

  callback (data1,
            g_marshal_value_peek_double (param_values + 1),
            g_marshal_value_peek_double (param_values + 2),
            data2);
}

static void
vik_print_preview_class_init (VikPrintPreviewClass *klass)
{
  GObjectClass *object_class = G_OBJECT_CLASS (klass);

  vik_print_preview_signals[OFFSETS_CHANGED] =
    g_signal_new ("offsets-changed",
                  G_TYPE_FROM_CLASS (klass),
                  G_SIGNAL_RUN_FIRST,
                  G_STRUCT_OFFSET (VikPrintPreviewClass, offsets_changed),
                  NULL, NULL,
                  marshal_VOID__DOUBLE_DOUBLE,
                  G_TYPE_NONE, 2,
                  G_TYPE_DOUBLE,
                  G_TYPE_DOUBLE);

  object_class->finalize = vik_print_preview_finalize;

  klass->offsets_changed = NULL;
}

static void
vik_print_preview_init (VikPrintPreview *preview)
{
  preview->page               = NULL;
  preview->pixbuf             = NULL;
  preview->dragging           = FALSE;
  preview->image_offset_x     = 0.0;
  preview->image_offset_y     = 0.0;
  preview->image_offset_x_max = 0.0;
  preview->image_offset_y_max = 0.0;
  preview->image_xres         = 230.0; // 1.0
  preview->image_yres         = 230.0;
  preview->use_full_page      = FALSE;

  preview->area = gtk_drawing_area_new();
  gtk_container_add (GTK_CONTAINER (preview), preview->area);
  gtk_widget_show (preview->area);

  gtk_widget_add_events (GTK_WIDGET (preview->area), GDK_BUTTON_PRESS_MASK);

  g_signal_connect (preview->area, "size-allocate",
                    G_CALLBACK (vik_print_preview_size_allocate),
                    preview);
  g_signal_connect (preview->area, "realize",
                    G_CALLBACK (vik_print_preview_realize),
                    NULL);
  g_signal_connect (preview->area, "event",
                    G_CALLBACK (vik_print_preview_event),
                    preview);
#if GTK_CHECK_VERSION (3,0,0)
  g_signal_connect (preview->area, "draw",
                    G_CALLBACK (vik_print_preview_draw),
                    preview);
#else
  g_signal_connect (preview->area, "expose-event",
                    G_CALLBACK (vik_print_preview_expose_event),
                    preview);
#endif
}


static void
vik_print_preview_finalize (GObject *object)
{
  VikPrintPreview *preview = VIK_PRINT_PREVIEW (object);

  if (preview->pixbuf)
    {
      g_object_unref (preview->pixbuf);
      preview->pixbuf = NULL;
    }

  if (preview->page)
    {
      g_object_unref (preview->page);
      preview->page = NULL;
    }

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

/**
 * vik_print_preview_new:
 * @page: page setup
 * @vvp: the viewport to print
 *
 * Creates a new #VikPrintPreview widget.
 *
 * Return value: the new #VikPrintPreview widget.
 **/
GtkWidget *
vik_print_preview_new (GtkPageSetup *page,
                        gpointer      vvp)
{
  VikPrintPreview *preview;
  gfloat            ratio;

  preview = g_object_new (VIK_TYPE_PRINT_PREVIEW, NULL);

  preview->viewport = VIK_VIEWPORT(vvp);

  if (page != NULL)
    preview->page = gtk_page_setup_copy (page);
  else
    preview->page = gtk_page_setup_new ();

  ratio = (gtk_page_setup_get_paper_width (preview->page, GTK_UNIT_POINTS) /
           gtk_page_setup_get_paper_height (preview->page, GTK_UNIT_POINTS));

  gtk_aspect_frame_set (GTK_ASPECT_FRAME (preview), 0.5, 0.5, ratio, FALSE);

  gtk_widget_set_size_request (preview->area,
                               DRAWING_AREA_SIZE, DRAWING_AREA_SIZE);

  return GTK_WIDGET (preview);
}

/**
 * vik_print_preview_set_image_dpi:
 * @preview: a #VikPrintPreview.
 * @xres: the X resolution
 * @yres: the Y resolution
 *
 * Sets the resolution of the image displayed by the
 * #VikPrintPreview.
 **/
void
vik_print_preview_set_image_dpi (VikPrintPreview *preview,
                                  gdouble           xres,
                                  gdouble           yres)
{
  g_return_if_fail (VIK_IS_PRINT_PREVIEW (preview));

  if (preview->image_xres != xres || preview->image_yres != yres)
    {
      preview->image_xres = xres;
      preview->image_yres = yres;

      print_preview_queue_draw (preview);
    }
}

/**
 * vik_print_preview_set_page_setup:
 * @preview: a #VikPrintPreview.
 * @page: the page setup to use
 *
 * Sets the page setup to use by the #VikPrintPreview.
 **/
void
vik_print_preview_set_page_setup (VikPrintPreview *preview,
                                   GtkPageSetup     *page)
{
  gfloat ratio;

  if (preview->page)
    g_object_unref (preview->page);

  preview->page = gtk_page_setup_copy (page);

  ratio = (gtk_page_setup_get_paper_width (page, GTK_UNIT_POINTS) /
           gtk_page_setup_get_paper_height (page, GTK_UNIT_POINTS));

  gtk_aspect_frame_set (GTK_ASPECT_FRAME (preview), 0.5, 0.5, ratio, FALSE);

  print_preview_queue_draw (preview);
}

/**
 * vik_print_preview_set_image_offsets:
 * @preview: a #VikPrintPreview.
 * @offset_x: the X offset
 * @offset_y: the Y offset
 *
 * Sets the offsets of the image displayed by the #VikPrintPreview.
 * It does not emit the "offsets-changed" signal.
 **/
void
vik_print_preview_set_image_offsets (VikPrintPreview *preview,
                                      gdouble           offset_x,
                                      gdouble           offset_y)
{
  g_return_if_fail (VIK_IS_PRINT_PREVIEW (preview));

  preview->image_offset_x = offset_x;
  preview->image_offset_y = offset_y;

  print_preview_queue_draw (preview);
}

/**
 * vik_print_preview_set_image_offsets_max:
 * @preview: a #VikPrintPreview.
 * @offset_x_max: the maximum X offset allowed
 * @offset_y_max: the maximum Y offset allowed
 *
 * Sets the maximum offsets of the image displayed by the
 * #VikPrintPreview.  It does not emit the "offsets-changed" signal.
 **/
void
vik_print_preview_set_image_offsets_max (VikPrintPreview *preview,
                                          gdouble           offset_x_max,
                                          gdouble           offset_y_max)
{
  g_return_if_fail (VIK_IS_PRINT_PREVIEW (preview));

  preview->image_offset_x_max = offset_x_max;
  preview->image_offset_y_max = offset_y_max;

  print_preview_queue_draw (preview);
}

/**
 * vik_print_preview_set_use_full_page:
 * @preview: a #VikPrintPreview.
 * @full_page: TRUE to ignore the page margins
 *
 * If @full_page is TRUE, the page margins are ignored and the full page
 * can be used to setup printing.
 **/
void
vik_print_preview_set_use_full_page (VikPrintPreview *preview,
                                      gboolean          full_page)
{
  g_return_if_fail (VIK_IS_PRINT_PREVIEW (preview));

  preview->use_full_page = full_page;

  print_preview_queue_draw (preview);
}

static void
vik_print_preview_realize (GtkWidget *widget)
{
  GdkCursor *cursor;

  cursor = gdk_cursor_new_for_display (gtk_widget_get_display (widget),
                                       GDK_FLEUR);
  gdk_window_set_cursor (gtk_widget_get_window(widget), cursor);
  gdk_cursor_unref (cursor);
}

static gboolean
vik_print_preview_event (GtkWidget        *widget,
                          GdkEvent         *event,
                          VikPrintPreview *preview)
{
  static gdouble orig_offset_x = 0.0;
  static gdouble orig_offset_y = 0.0;
  static gint    start_x       = 0;
  static gint    start_y       = 0;

  gdouble        offset_x;
  gdouble        offset_y;
  gdouble        scale;

  switch (event->type)
    {
    case GDK_BUTTON_PRESS:
      // Actually > 3.20.0, as set by configure.ac
      // GDK 3.0 .. 3.20 uses gdk_device_grab(), but we won't bother with that
#if GTK_CHECK_VERSION (3,0,0)
      {
        GdkDisplay *display = gdk_display_get_default ();
        GdkSeat *seat = gdk_display_get_default_seat ( display );
        if ( seat ) {
          (void)gdk_seat_grab ( seat,
                                gtk_widget_get_window(widget),
                                GDK_SEAT_CAPABILITY_POINTER,
                                FALSE,
                                NULL,
                                event,
                                NULL,
                                NULL );
        }
      }
#else
      (void)gdk_pointer_grab ( gtk_widget_get_window(widget),
                               FALSE,
                               (GDK_BUTTON1_MOTION_MASK | GDK_BUTTON_RELEASE_MASK),
                               NULL,
                               NULL,
                               event->button.time );
#endif
      orig_offset_x = preview->image_offset_x;
      orig_offset_y = preview->image_offset_y;

      start_x = event->button.x;
      start_y = event->button.y;

      preview->dragging = TRUE;
      break;

    case GDK_MOTION_NOTIFY:
      scale = vik_print_preview_get_scale (preview);

      offset_x = (orig_offset_x + (event->motion.x - start_x) / scale);
      offset_y = (orig_offset_y + (event->motion.y - start_y) / scale);

      offset_x = CLAMP (offset_x, 0, preview->image_offset_x_max);
      offset_y = CLAMP (offset_y, 0, preview->image_offset_y_max);

      if (preview->image_offset_x != offset_x ||
          preview->image_offset_y != offset_y)
        {
          vik_print_preview_set_image_offsets (preview, offset_x, offset_y);

          g_signal_emit (preview,
                         vik_print_preview_signals[OFFSETS_CHANGED], 0,
                         preview->image_offset_x, preview->image_offset_y);
        }
      break;

    case GDK_BUTTON_RELEASE:
#if GTK_CHECK_VERSION (3,0,0)
      {
        GdkDisplay *display = gdk_display_get_default ();
        GdkSeat *seat = gdk_display_get_default_seat ( display );
        if ( seat ) {
          gdk_seat_ungrab ( seat );
        }
      }
#else
      gdk_display_pointer_ungrab (gtk_widget_get_display (widget),
                                  event->button.time);
#endif
      start_x = start_y = 0;
      preview->dragging = FALSE;

      print_preview_queue_draw (preview);
      break;

    default:
      break;
    }

  return FALSE;
}

static GdkPixbuf *get_thumbnail(VikViewport *vvp, gint thumb_width, gint thumb_height)
{
  GdkPixbuf *pixbuf;
  GdkPixbuf *thumbnail;

  gint width = vik_viewport_get_width ( vvp );
  gint height = vik_viewport_get_height ( vvp );
#if GTK_CHECK_VERSION (3,0,0)
  pixbuf = vik_viewport_get_pixbuf (vvp, width, height);
#else
  GdkDrawable *drawable = GDK_DRAWABLE(vik_viewport_get_pixmap(vvp));
  pixbuf = gdk_pixbuf_get_from_drawable(NULL, drawable, NULL, 0, 0, 0, 0, width, height);
#endif

  if (!pixbuf) {
    g_warning("%s: failed to obtain viewport pixbuf for the thumbnail",
	      __FUNCTION__);
    return NULL;
  }

  thumbnail = gdk_pixbuf_scale_simple(pixbuf, thumb_width, thumb_height,
                                      GDK_INTERP_BILINEAR);
  g_object_unref(pixbuf);
  return thumbnail;
}

static gboolean
#if GTK_CHECK_VERSION (3,0,0)
vik_print_preview_draw (GtkWidget       *widget,
                        cairo_t         *cr,
                        VikPrintPreview *preview)
#else
vik_print_preview_expose_event (GtkWidget        *widget,
                                 GdkEventExpose   *eevent,
                                 VikPrintPreview *preview)
#endif
{
  gdouble  paper_width;
  gdouble  paper_height;
  gdouble  left_margin;
  gdouble  right_margin;
  gdouble  top_margin;
  gdouble  bottom_margin;
  gdouble  scale;
#if !GTK_CHECK_VERSION (3,0,0)
  cairo_t *cr = gdk_cairo_create (gtk_widget_get_window(widget));
#endif
  paper_width = gtk_page_setup_get_paper_width (preview->page,
                                                GTK_UNIT_POINTS);
  paper_height = gtk_page_setup_get_paper_height (preview->page,
                                                  GTK_UNIT_POINTS);
  vik_print_preview_get_page_margins (preview,
                                       &left_margin,
                                       &right_margin,
                                       &top_margin,
                                       &bottom_margin);

  scale = vik_print_preview_get_scale (preview);

  /* draw background */
  cairo_scale (cr, scale, scale);
  gdk_cairo_set_source_color (cr, &gtk_widget_get_style(widget)->white);
  cairo_rectangle (cr, 0, 0, paper_width, paper_height);
  cairo_fill (cr);

  /* draw page_margins */
  gdk_cairo_set_source_color (cr, &gtk_widget_get_style(widget)->black);
  cairo_rectangle (cr,
                   left_margin,
                   top_margin,
                   paper_width - left_margin - right_margin,
                   paper_height - top_margin - bottom_margin);
  cairo_stroke (cr);

  if (preview->dragging)
    {
      gint width = vik_viewport_get_width ( preview->viewport );
      gint height = vik_viewport_get_height ( preview->viewport );

      cairo_rectangle (cr,
                       left_margin + preview->image_offset_x,
                       top_margin  + preview->image_offset_y,
                       (gdouble) width  * 72.0 / preview->image_xres,
                       (gdouble) height * 72.0 / preview->image_yres);
      cairo_stroke (cr);
    }
  else
    {
      /* draw image */
      cairo_translate (cr,
                       left_margin + preview->image_offset_x,
                       top_margin  + preview->image_offset_y);

      if (preview->pixbuf == NULL)
        {
          GtkAllocation allocation;
          gtk_widget_get_allocation ( widget, &allocation );
          gint width  = MIN (allocation.width, 1024);
          gint height = MIN (allocation.height, 1024);

          preview->pixbuf = get_thumbnail(preview->viewport, width, height);
        }

      if (preview->pixbuf != NULL)
        {
          gint width = vik_viewport_get_width ( preview->viewport );
          gint height = vik_viewport_get_height ( preview->viewport );

          gdouble scale_x = ((gdouble) width /
                             gdk_pixbuf_get_width (preview->pixbuf));
          gdouble scale_y = ((gdouble) height /
                             gdk_pixbuf_get_height (preview->pixbuf));

          scale_x = scale_x * 72.0 / preview->image_xres;
          scale_y = scale_y * 72.0 / preview->image_yres;

          cairo_scale (cr, scale_x, scale_y);

          gdk_cairo_set_source_pixbuf (cr, preview->pixbuf, 0, 0);

          cairo_paint (cr);
        }
    }

#if !GTK_CHECK_VERSION (3,0,0)
  cairo_destroy (cr);
#endif

  return FALSE;
}

static gdouble
vik_print_preview_get_scale (VikPrintPreview* preview)
{
  gdouble scale_x;
  gdouble scale_y;

  GtkAllocation allocation;
  gtk_widget_get_allocation ( preview->area, &allocation );
  scale_x = ((gdouble) allocation.width /
             gtk_page_setup_get_paper_width (preview->page, GTK_UNIT_POINTS));

  scale_y = ((gdouble) allocation.height /
             gtk_page_setup_get_paper_height (preview->page, GTK_UNIT_POINTS));

  return MIN (scale_x, scale_y);
}

static void
vik_print_preview_size_allocate (GtkWidget        *widget,
                                  GtkAllocation    *allocation,
                                  VikPrintPreview *preview)
{
  if (preview->pixbuf != NULL)
    {
      g_object_unref (preview->pixbuf);
      preview->pixbuf = NULL;
    }
}

static void
vik_print_preview_get_page_margins (VikPrintPreview *preview,
                                     gdouble          *left_margin,
                                     gdouble          *right_margin,
                                     gdouble          *top_margin,
                                     gdouble          *bottom_margin)
{
  if (preview->use_full_page)
    {
      *left_margin   = 0.0;
      *right_margin  = 0.0;
      *top_margin    = 0.0;
      *bottom_margin = 0.0;
    }
  else
    {
      *left_margin   = gtk_page_setup_get_left_margin (preview->page,
                                                       GTK_UNIT_POINTS);
      *right_margin  = gtk_page_setup_get_right_margin (preview->page,
                                                        GTK_UNIT_POINTS);
      *top_margin    = gtk_page_setup_get_top_margin (preview->page,
                                                      GTK_UNIT_POINTS);
      *bottom_margin = gtk_page_setup_get_bottom_margin (preview->page,
                                                         GTK_UNIT_POINTS);
    }
}

static void
print_preview_queue_draw (VikPrintPreview *preview)
{
  gtk_widget_queue_draw (GTK_WIDGET (preview->area));
}
