/* sane - Scanner Access Now Easy.
   Copyright (C) 1997 David Mosberger-Tang and Tristan Tarrant
   This file is part of the SANE package.

   SANE 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.

   SANE 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 sane; see the file COPYING.  If not, write to the Free
   Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.  */
/*
  The preview strategy is as follows:

   1) A preview always acquires an image that covers the entire
      scan surface.  This is necessary so the user can see not
      only what is, but also what isn't selected.

   2) The preview must be zoomable so the user can precisely pick
      the selection area even for small scans on a large scan
      surface.  The user also should have the option of resizing
      the preview window.

   3) We let the user/backend pick whether a preview is in color,
      grayscale, lineart or what not.  The only options that the
      preview may (temporarily) modify are:

	- resolution (set so the preview fills the window)
	- scan area options (top-left corner, bottom-right corner)
	- preview option (to let the backend know we're doing a preview)

   4) The size of the scan surface is determined based on the constraints
      of the four corner coordinates.  Missing constraints are replaced
      by +/-INF as appropriate (-INF form top-left, +INF for bottom-right
      coords).

   5) The size of the preview window is determined based on the scan
      surface size:

          If the surface area is specified in pixels and if that size
	  fits on the screen, we use that size.  In all other cases,
	  we make the window of a size so that neither the width nor
	  the height is bigger than some fraction of the screen-size
	  while preserving the aspect ratio (a surface width or height
	  of INF implies an aspect ratio of 1).

   6) Given the preview window size and the scan surface size, we
      select the resolution so the acquired preview image just fits
      in the preview window.  The resulting resolution may be out
      of range in which case we pick the minum/maximum if there is
      a range or word-list constraint or a default value if there is
      no such constraint.

   7) Once a preview image has been acquired, we know the size of the
      preview image (in pixels).  An initial scale factor is chosen
      so the image fits into the preview window.

      */

#include <assert.h>
#include <math.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

#include <sys/param.h>

#include <gtkglue.h>
#include <preview.h>
#include <preferences.h>

#ifndef PATH_MAX
# define PATH_MAX	1024
#endif

/* Anything bigger than 2G will do, since SANE coordinates are 32 bit
   values.  */
#define INF		5.0e9

#define MM_PER_INCH	25.4

/* Cut fp conversion routines some slack: */
#define GROSSLY_DIFFERENT(f1,f2)	(fabs ((f1) - (f2)) > 1e-3)

#ifdef __alpha__
  /* This seems to be necessary for at least some XFree86 3.1.2
     servers.  It's known to be necessary for the XF86_TGA server for
     Linux/Alpha.  Fortunately, it's no great loss so we turn this on
     by default for now.  */
# define XSERVER_WITH_BUGGY_VISUALS
#endif

/* forward declarations */
static void scan_start (Preview *p);
static void scan_done (Preview *p);

static void
draw_rect (GdkWindow *win, GdkGC *gc, int coord[4])
{
  gint x, y, w, h;

  x = coord[0];
  y = coord[1];
  w = coord[2] - x;
  h = coord[3] - y;
  if (w < 0)
    {
      x = coord[2];
      w = -w;
    }
  if (h < 0)
    {
      y = coord[3];
      h = -h;
    }
  gdk_draw_rectangle (win, gc, FALSE, x, y, w + 1, h + 1);
}

static void
draw_selection (Preview *p)
{
  if (!p->gc)
    /* window isn't mapped yet */
    return;

  if (p->previous_selection.active)
    draw_rect (p->window->window, p->gc, p->previous_selection.coord);

  if (p->selection.active)
    draw_rect (p->window->window, p->gc, p->selection.coord);

  p->previous_selection = p->selection;
}

static void
update_selection (Preview *p)
{
  float min, max, normal, dev_selection[4];
  const SANE_Option_Descriptor *opt;
  SANE_Status status;
  SANE_Word val;
  int i, optnum;

  p->previous_selection = p->selection;

  memcpy (dev_selection, p->surface, sizeof (dev_selection));
  for (i = 0; i < 4; ++i)
    {
      optnum = p->dialog->well_known.coord[i];
      if (optnum > 0)
	{
	  opt = sane_get_option_descriptor (p->dialog->dev, optnum);
	  status = sane_control_option (p->dialog->dev, optnum,
					SANE_ACTION_GET_VALUE, &val, 0);
	  if (status != SANE_STATUS_GOOD)
	    continue;
	  if (opt->type == SANE_TYPE_FIXED)
	    dev_selection[i] = SANE_UNFIX (val);
	  else
	    dev_selection[i] = val;
	}
    }
  for (i = 0; i < 2; ++i)
    {
      min = p->surface[i];
      if (min <= -INF)
	min = 0.0;
      max = p->surface[i + 2];
      if (max >= INF)
	max = p->preview_width;

      normal = ((i == 0) ? p->preview_width : p->preview_height) - 1;
      normal /= (max - min);
      p->selection.active = TRUE;
      p->selection.coord[i]     = ((dev_selection[i] - min)*normal) + 0.5;
      p->selection.coord[i + 2] = ((dev_selection[i + 2] - min)*normal) + 0.5;
      if (p->selection.coord[i + 2] < p->selection.coord[i])
	p->selection.coord[i + 2] = p->selection.coord[i];
    }
  draw_selection (p);
}

static void
get_image_scale (Preview *p, float *xscalep, float *yscalep)
{
  float xscale, yscale;

  if (p->image_width == 0)
    xscale = 1.0;
  else
    {
      xscale = p->image_width/(float) p->preview_width;
      if (p->image_height > 0 && p->preview_height*xscale < p->image_height)
	xscale = p->image_height/(float) p->preview_height;
    }
  yscale = xscale;

  if (p->surface_unit == SANE_UNIT_PIXEL
      && p->image_width <= p->preview_width
      && p->image_height <= p->preview_height)
    {
      float swidth, sheight;

      assert (p->surface_type == SANE_TYPE_INT);
      swidth  = (p->surface[GSG_BR_X] - p->surface[GSG_TL_X] + 1);
      sheight = (p->surface[GSG_BR_Y] - p->surface[GSG_TL_Y] + 1);
      xscale = 1.0;
      yscale = 1.0;
      if (p->image_width > 0 && swidth < INF)
	xscale = p->image_width/swidth;
      if (p->image_height > 0 && sheight < INF)
	yscale = p->image_height/sheight;
    }
  *xscalep = xscale;
  *yscalep = yscale;
}

static void
paint_image (Preview *p)
{
  float xscale, yscale, src_x, src_y;
  int dst_x, dst_y, height, x, y, src_offset;
  gint gwidth, gheight;

  gwidth  = p->preview_width;
  gheight = p->preview_height;

  get_image_scale (p, &xscale, &yscale);

  memset (p->preview_row, 0xff, 3*gwidth);

  /* don't draw last line unless it's complete: */
  height = p->image_y;
  if (p->image_x == 0 && height < p->image_height)
    ++height;

  /* for now, use simple nearest-neighbor interpolation: */
  src_offset = 0;
  src_x = src_y = 0.0;
  for (dst_y = 0; dst_y < gheight; ++dst_y)
    {
      y = (int) (src_y + 0.5);
      if (y >= height)
	break;
      src_offset = y*3*p->image_width;

      if (p->image_data)
	for (dst_x = 0; dst_x < gwidth; ++dst_x)
	  {
	    x = (int) (src_x + 0.5);
	    if (x >= p->image_width)
	      break;

	    p->preview_row[3*dst_x + 0] = p->image_data[src_offset + 3*x + 0];
	    p->preview_row[3*dst_x + 1] = p->image_data[src_offset + 3*x + 1];
	    p->preview_row[3*dst_x + 2] = p->image_data[src_offset + 3*x + 2];
	    src_x += xscale;
	  }
      gtk_preview_draw_row (GTK_PREVIEW (p->window), p->preview_row,
			    0, dst_y, gwidth);
      src_x = 0.0;
      src_y += yscale;
    }
}

static void
display_partial_image (Preview *p)
{
  paint_image (p);

  if (GTK_WIDGET_DRAWABLE (p->window))
    {
      GtkPreview *preview = GTK_PREVIEW (p->window);
      int src_x, src_y;

      src_x = (p->window->allocation.width - preview->buffer_width)/2;
      src_y = (p->window->allocation.height - preview->buffer_height)/2;
      gtk_preview_put (preview, p->window->window, p->window->style->black_gc,
		       src_x, src_y,
		       0, 0, p->preview_width, p->preview_height);
    }
}

static void
display_maybe (Preview *p)
{
  time_t now;

  time (&now);
  if (now > p->image_last_time_updated)
    {
      p->image_last_time_updated = now;
      display_partial_image (p);
    }
}

static void
display_image (Preview *p)
{
  if (p->params.lines <= 0 && p->image_y < p->image_height)
    {
      p->image_height = p->image_y;
      p->image_data = realloc (p->image_data,
			       3*p->image_width*p->image_height);
      assert (p->image_data);
    }
  display_partial_image (p);
  scan_done (p);
}

static void
preview_area_resize (GtkWidget *widget)
{
  float min_x, max_x, min_y, max_y, xscale, yscale, f;
  Preview *p;

  p = gtk_object_get_data (GTK_OBJECT (widget), "PreviewPointer");

  p->preview_width  = widget->allocation.width;
  p->preview_height = widget->allocation.height;

  if (p->preview_row)
    p->preview_row = realloc (p->preview_row, 3*p->preview_width);
  else
    p->preview_row = malloc (3*p->preview_width);

  /* set the ruler ranges: */

  min_x = p->surface[GSG_TL_X];
  if (min_x <= -INF)
    min_x = 0.0;

  max_x = p->surface[GSG_BR_X];
  if (max_x >=  INF)
    max_x = p->preview_width - 1;

  min_y = p->surface[GSG_TL_Y];
  if (min_y <= -INF)
    min_y = 0.0;

  max_y = p->surface[GSG_BR_Y];
  if (max_y >=  INF)
    max_y = p->preview_height - 1;

  /* convert mm to inches if that's what the user wants: */

  if (p->surface_unit == SANE_UNIT_MM)
    {
      double factor = 1.0/preferences.length_unit;
      min_x *= factor; max_x *= factor; min_y *= factor; max_y *= factor;
    }

  get_image_scale (p, &xscale, &yscale);

  if (p->surface_unit == SANE_UNIT_PIXEL)
    f = 1.0/xscale;
  else if (p->image_width)
    f = xscale*p->preview_width/p->image_width;
  else
    f = 1.0;
  gtk_ruler_set_range (GTK_RULER (p->hruler), f*min_x, f*max_x, f*min_x,
		       /* max_size */ 20);

  if (p->surface_unit == SANE_UNIT_PIXEL)
    f = 1.0/yscale;
  else if (p->image_height)
    f = yscale*p->preview_height/p->image_height;
  else
    f = 1.0;
  gtk_ruler_set_range (GTK_RULER (p->vruler), f*min_y, f*max_y, f*min_y,
		       /* max_size */ 20);

  paint_image (p);
  update_selection (p);
}

static void
get_bounds (const SANE_Option_Descriptor *opt, float *minp, float *maxp)
{
  float min, max;
  int i;

  min = -INF;
  max =  INF;
  switch (opt->constraint_type)
    {
    case SANE_CONSTRAINT_RANGE:
      min = opt->constraint.range->min;
      max = opt->constraint.range->max;
      break;

    case SANE_CONSTRAINT_WORD_LIST:
      min =  INF;
      max = -INF;
      for (i = 1; i <= opt->constraint.word_list[0]; ++i)
	{
	  if (opt->constraint.word_list[i] < min)
	    min = opt->constraint.word_list[i];
	  if (opt->constraint.word_list[i] > max)
	    max = opt->constraint.word_list[i];
	}
      break;

    default:
      break;
    }
  if (opt->type == SANE_TYPE_FIXED)
    {
      if (min > -INF && min < INF)
	min = SANE_UNFIX (min);
      if (max > -INF && max < INF)
	max = SANE_UNFIX (max);
    }
  *minp = min;
  *maxp = max;
}

static void
save_option (Preview *p, int option, SANE_Word *save_loc, int *valid)
{
  SANE_Status status;

  if (option <= 0)
    {
      *valid = 0;
      return;
    }
  status = sane_control_option (p->dialog->dev, option, SANE_ACTION_GET_VALUE,
				save_loc, 0);
  *valid = (status == SANE_STATUS_GOOD);
}

static void
restore_option (Preview *p, int option, SANE_Word saved_value, int valid)
{
  const SANE_Option_Descriptor *opt;
  SANE_Status status;
  SANE_Handle dev;

  if (!valid)
    return;

  dev = p->dialog->dev;
  status = sane_control_option (dev, option, SANE_ACTION_SET_VALUE,
				&saved_value, 0);
  if (status != SANE_STATUS_GOOD)
    {
      char buf[256];
      opt = sane_get_option_descriptor (dev, option);
      snprintf (buf, sizeof (buf), "Failed restore value of option %s: %s.",
		opt->name, sane_strstatus (status));
      gsg_error (buf);
    }
}

static void
set_option_float (Preview *p, int option, float value)
{
  const SANE_Option_Descriptor *opt;
  SANE_Handle dev;
  SANE_Word word;

  if (option <= 0 || value <= -INF || value >= INF)
    return;

  dev = p->dialog->dev;
  opt = sane_get_option_descriptor (dev, option);
  if (opt->type == SANE_TYPE_FIXED)
    word = SANE_FIX (value) + 0.5;
  else
    word = value + 0.5;
  sane_control_option (dev, option, SANE_ACTION_SET_VALUE, &word, 0);
}

static void
set_option_bool (Preview *p, int option, SANE_Bool value)
{
  SANE_Handle dev;

  if (option <= 0)
    return;

  dev = p->dialog->dev;
  sane_control_option (dev, option, SANE_ACTION_SET_VALUE, &value, 0);
}

static int
increment_image_y (Preview *p)
{
  size_t extra_size, offset;
  char buf[256];

  p->image_x = 0;
  ++p->image_y;
  if (p->params.lines <= 0 && p->image_y >= p->image_height)
    {
      offset = 3*p->image_width*p->image_height;
      extra_size = 3*32*p->image_width;
      p->image_height += 32;
      p->image_data = realloc (p->image_data, offset + extra_size);
      if (!p->image_data)
	{
	  snprintf (buf, sizeof (buf),
		    "Failed to reallocate image memory: %s.",
		    strerror (errno));
	  gsg_error (buf);
	  scan_done (p);
	  return -1;
	}
      memset (p->image_data + offset, 0xff, extra_size);
    }
  return 0;
}

static void
input_available (gpointer data, gint source, GdkInputCondition cond)
{
  SANE_Status status;
  Preview *p = data;
  u_char buf[8192];
  SANE_Handle dev;
  SANE_Int len;
  int i, j;

  dev = p->dialog->dev;
  while (1)
    {
      status = sane_read (dev, buf, sizeof (buf), &len);
      if (status != SANE_STATUS_GOOD)
	{
	  if (status == SANE_STATUS_EOF)
	    {
	      if (p->params.last_frame)
		display_image (p);
	      else
		{
		  scan_start (p);
		  break;
		}
	    }
	  else
	    {
	      snprintf (buf, sizeof (buf), "Error during read: %s.",
			sane_strstatus (status));
	      gsg_error (buf);
	    }
	  scan_done (p);
	  return;
	}
      if (!len)
	break;			/* out of data for now */

      switch (p->params.format)
	{
	case SANE_FRAME_RGB:
	  if (p->params.depth != 8)
	    goto bad_depth;

	  for (i = 0; i < len; ++i)
	    {
	      p->image_data[p->image_offset++] = buf[i];
	      if (p->image_offset%3 == 0)
		{
		  if (++p->image_x >= p->image_width
		      && increment_image_y (p) < 0)
		    return;
		}
	    }
	  break;

	case SANE_FRAME_GRAY:
	  switch (p->params.depth)
	    {
	    case 1:
	      for (i = 0; i < len; ++i)
		{
		  u_char mask = buf[i];

		  for (j = 7; j >= 0; --j)
		    {
		      u_char gl = (mask & (1 << j)) ? 0x00 : 0xff;
		      p->image_data[p->image_offset++] = gl;
		      p->image_data[p->image_offset++] = gl;
		      p->image_data[p->image_offset++] = gl;
		      if (++p->image_x >= p->image_width)
			{
			  if (increment_image_y (p) < 0)
			    return;
			  break;	/* skip padding bits */
			}
		    }
		}
	      break;

	    case 8:
	      for (i = 0; i < len; ++i)
		{
		  u_char gl = buf[i];
		  p->image_data[p->image_offset++] = gl;
		  p->image_data[p->image_offset++] = gl;
		  p->image_data[p->image_offset++] = gl;
		  if (++p->image_x >= p->image_width
		      && increment_image_y (p) < 0)
		    return;
		}
	      break;

	    default:
	      goto bad_depth;
	    }
	  break;

	case SANE_FRAME_RED:
	case SANE_FRAME_GREEN:
	case SANE_FRAME_BLUE:
	  switch (p->params.depth)
	    {
	    case 1:
	      for (i = 0; i < len; ++i)
		{
		  u_char mask = buf[i];

		  for (j = 0; j < 8; ++j)
		    {
		      u_char gl = (mask & 1) ? 0xff : 0x00;
		      mask >>= 1;
		      p->image_data[p->image_offset++] = gl;
		      p->image_offset += 3;
		      if (++p->image_x >= p->image_width
			  && increment_image_y (p) < 0)
			return;
		    }
		}
	      break;

	    case 8:
	      for (i = 0; i < len; ++i)
		{
		  p->image_data[p->image_offset] = buf[i];
		  p->image_offset += 3;
		  if (++p->image_x >= p->image_width
		      && increment_image_y (p) < 0)
		    return;
		}
	      break;

	    default:
	      goto bad_depth;
	    }
	  break;

	default:
	  fprintf (stderr, "preview.input_available: bad frame format %d\n",
		   p->params.format);
	  scan_done (p);
	  return;
	}
      if (p->input_tag < 0)
	{
	  display_maybe (p);
	  while (gtk_events_pending ())
	    gtk_main_iteration ();
	}
    }
  display_maybe (p);
  return;

bad_depth:
  snprintf (buf, sizeof (buf), "Preview cannot handle depth %d.",
	    p->params.depth);
  gsg_error (buf);
  scan_done (p);
  return;
}

static void
scan_done (Preview *p)
{
  int i;

  p->scanning = FALSE;
  if (p->input_tag >= 0)
    {
      gdk_input_remove (p->input_tag);
      p->input_tag = -1;
    }
  sane_cancel (p->dialog->dev);

  restore_option (p, p->dialog->well_known.dpi,
		  p->saved_dpi, p->saved_dpi_valid);
  for (i = 0; i < 4; ++i)
    restore_option (p, p->dialog->well_known.coord[i],
		    p->saved_coord[i], p->saved_coord_valid[i]);
  set_option_bool (p, p->dialog->well_known.preview, SANE_FALSE);

  gtk_widget_set_sensitive (p->cancel, FALSE);
  gsg_set_sensitivity (p->dialog, TRUE);
}

static void
scan_start (Preview *p)
{
  SANE_Handle dev = p->dialog->dev;
  SANE_Status status;
  char buf[256];
  int fd, y;

  gtk_widget_set_sensitive (p->cancel, TRUE);
  gsg_set_sensitivity (p->dialog, FALSE);

  /* clear old preview: */
  memset (p->preview_row, 0xff, 3*p->preview_width);
  for (y = 0; y < p->preview_height; ++y)
    gtk_preview_draw_row (GTK_PREVIEW (p->window), p->preview_row,
			  0, y, p->preview_width);

  if (p->input_tag >= 0)
    {
      gdk_input_remove (p->input_tag);
      p->input_tag = -1;
    }

  gsg_sync (p->dialog);

  status = sane_start (dev);
  if (status != SANE_STATUS_GOOD)
    {
      snprintf (buf, sizeof (buf),
		"Failed to start scanner: %s.", sane_strstatus (status));
      gsg_error (buf);
      scan_done (p);
      return;
    }

  status = sane_get_parameters (dev, &p->params);
  if (status != SANE_STATUS_GOOD)
    {
      snprintf (buf, sizeof (buf),
		"Failed to obtain parameters: %s.", sane_strstatus (status));
      gsg_error (buf);
      scan_done (p);
      return;
    }

  p->image_offset = p->image_x = p->image_y = 0;

  if (p->params.format >= SANE_FRAME_RED
      && p->params.format <= SANE_FRAME_BLUE)
    p->image_offset = p->params.format - SANE_FRAME_RED;

  if (!p->image_data || p->params.pixels_per_line != p->image_width
      || (p->params.lines >= 0 && p->params.lines != p->image_height))
    {
      /* image size changed */
      if (p->image_data)
	free (p->image_data);

      p->image_width  = p->params.pixels_per_line;
      p->image_height = p->params.lines;
      if (p->image_height < 0)
	p->image_height = 32;	/* may have to adjust as we go... */

      p->image_data = malloc (3*p->image_width*p->image_height);
      if (!p->image_data)
	{
	  snprintf (buf, sizeof (buf),
		    "Failed to allocate image memory: %s.", strerror (errno));
	  gsg_error (buf);
	  scan_done (p);
	  return;
	}
      memset (p->image_data, 0xff, 3*p->image_width*p->image_height);
    }

  if (p->selection.active)
    {
      p->previous_selection = p->selection;
      p->selection.active = FALSE;
      draw_selection (p);
    }
  p->scanning = TRUE;

  if (sane_set_io_mode (dev, SANE_TRUE) == SANE_STATUS_GOOD
      && sane_get_select_fd (dev, &fd) == SANE_STATUS_GOOD)
    p->input_tag = gdk_input_add (fd, GDK_INPUT_READ | GDK_INPUT_EXCEPTION, input_available, p);
  else
    input_available (p, -1, GDK_INPUT_READ);
}

static void
establish_selection (Preview *p)
{
  float min, max, normal, dev_selection[4];
  int i;

  memcpy (dev_selection, p->surface, sizeof (dev_selection));
  if (p->selection.active)
    for (i = 0; i < 2; ++i)
      {
	min = p->surface[i];
	if (min <= -INF)
	  min = 0.0;
	max = p->surface[i + 2];
	if (max >= INF)
	  max = p->preview_width;

	normal = 1.0/(((i == 0) ? p->preview_width : p->preview_height) - 1);
	normal *= (max - min);
	dev_selection[i]     = p->selection.coord[i]*normal + min;
	dev_selection[i + 2] = p->selection.coord[i + 2]*normal + min;
      }
  for (i = 0; i < 4; ++i)
    set_option_float (p, p->dialog->well_known.coord[i], dev_selection[i]);
  gsg_update_scan_window (p->dialog);
  if (p->dialog->param_change_callback)
    (*p->dialog->param_change_callback) (p->dialog,
					 p->dialog->param_change_arg);
}

static int
make_preview_image_path (Preview *p, size_t filename_size, char *filename)
{
  return gsg_make_path (filename_size, filename, 0, "preview-",
			p->dialog->dev_name, ".ppm");
}

static void
restore_preview_image (Preview *p)
{
  u_int psurface_type, psurface_unit;
  char filename[PATH_MAX];
  int width, height;
  float psurface[4];
  size_t nread;
  FILE *in;

  /* See whether there is a saved preview and load it if present: */

  if (make_preview_image_path (p, sizeof (filename), filename) < 0)
    return;

  in = fopen (filename, "r");
  if (!in)
    return;

  /* Be careful about consuming too many bytes after the final newline
     (e.g., consider an image whose first image byte is 13 (`\r').  */
  if (fscanf (in, "P6\n# surface: %g %g %g %g %u %u\n%d %d\n255%*[\n]",
	      psurface + 0, psurface + 1, psurface + 2, psurface + 3,
	      &psurface_type, &psurface_unit,
	      &width, &height) != 8)
    return;

  if (GROSSLY_DIFFERENT (psurface[0], p->surface[0])
      || GROSSLY_DIFFERENT (psurface[1], p->surface[1])
      || GROSSLY_DIFFERENT (psurface[2], p->surface[2])
      || GROSSLY_DIFFERENT (psurface[3], p->surface[3])
      || psurface_type != p->surface_type || psurface_unit != p->surface_unit)
    /* ignore preview image that was acquired for/with a different surface */
    return;

  p->image_width = width;
  p->image_height = height;
  p->image_data = malloc (3*width*height);
  if (!p->image_data)
    return;

  nread = fread (p->image_data, 3, width*height, in);

  p->image_y = nread/width;
  p->image_x = nread%width;
}

/* This is executed _after_ the gtkpreview's expose routine.  */
static gint
expose_handler (GtkWidget *window, GdkEvent *event, gpointer data)
{
  Preview *p = data;

  p->selection.active = FALSE;
  update_selection (p);
  return FALSE;
}

static gint
event_handler (GtkWidget *window, GdkEvent *event, gpointer data)
{
  Preview *p = data;
  int i, tmp;

  if (event->type == GDK_EXPOSE)
    {
      if (!p->gc)
	{
	  p->gc = gdk_gc_new (p->window->window);
	  gdk_gc_set_function (p->gc, GDK_INVERT);
	  gdk_gc_set_line_attributes (p->gc, 1, GDK_LINE_ON_OFF_DASH,
				      GDK_CAP_BUTT, GDK_JOIN_MITER);
	  paint_image (p);
	}
      else
	{
	  p->selection.active = FALSE;
	  draw_selection (p);
	}
    }
  else if (!p->scanning)
    switch (event->type)
      {
      case GDK_UNMAP:
      case GDK_MAP:
	break;

      case GDK_BUTTON_PRESS:
	p->selection.coord[0] = event->button.x;
	p->selection.coord[1] = event->button.y;
	p->selection_drag = TRUE;
	break;

      case GDK_BUTTON_RELEASE:
	if (!p->selection_drag)
	  break;
	p->selection_drag = FALSE;

	p->selection.coord[2] = event->button.x;
	p->selection.coord[3] = event->button.y;
	p->selection.active =
	  (p->selection.coord[0] != p->selection.coord[2]
	   || p->selection.coord[1] != p->selection.coord[3]);

	if (p->selection.active)
	  {
	    for (i = 0; i < 2; i += 1)
	      if (p->selection.coord[i] > p->selection.coord[i + 2])
		{
		  tmp = p->selection.coord[i];
		  p->selection.coord[i] = p->selection.coord[i + 2];
		  p->selection.coord[i + 2] = tmp;
		}
	    if (p->selection.coord[0] < 0)
	      p->selection.coord[0] = 0;
	    if (p->selection.coord[1] < 0)
	      p->selection.coord[1] = 0;
	    if (p->selection.coord[2] >= p->preview_width)
	      p->selection.coord[2] = p->preview_width - 1;
	    if (p->selection.coord[3] >= p->preview_height)
	      p->selection.coord[3] = p->preview_height - 1;
	  }
	draw_selection (p);
	establish_selection (p);
	break;

      case GDK_MOTION_NOTIFY:
	if (p->selection_drag)
	  {
	    p->selection.active = TRUE;
	    p->selection.coord[2] = event->motion.x;
	    p->selection.coord[3] = event->motion.y;
	    draw_selection (p);
	  }
	break;

      default:
#if 0
	fprintf (stderr, "event_handler: unhandled event type %d\n",
		 event->type);
#endif
	break;
      }
  return FALSE;
}

static void
start_button_clicked (GtkWidget *widget, gpointer data)
{
  preview_scan (data);
}

static void
cancel_button_clicked (GtkWidget *widget, gpointer data)
{
  scan_done (data);
}

static void
top_destroyed (GtkWidget *widget, gpointer call_data)
{
  Preview *p = call_data;

  p->top = NULL;
}

Preview *
preview_new (GSGDialog *dialog)
{
  static int first_time = 1;
  GtkWidget *table, *frame, *button;
  GtkSignalFunc signal_func;
  GtkWidgetClass *class;
  GtkBox *vbox, *hbox;
  Preview *p;

  p = malloc (sizeof (*p));
  if (!p)
    return 0;
  memset (p, 0, sizeof (*p));

  p->dialog = dialog;
  p->input_tag = -1;

  if (first_time)
    {
      first_time = 0;
      gtk_preview_set_gamma (preferences.preview_gamma);
      gtk_preview_set_install_cmap (preferences.preview_own_cmap);
    }

#ifndef XSERVER_WITH_BUGGY_VISUALS
  gtk_widget_push_visual (gtk_preview_get_visual ());
#endif
  gtk_widget_push_colormap (gtk_preview_get_cmap ());

  p->top = gtk_dialog_new ();
  gtk_signal_connect (GTK_OBJECT (p->top), "destroy",
		      GTK_SIGNAL_FUNC (top_destroyed), p);
  gtk_window_set_title (GTK_WINDOW (p->top), "xscan preview");
  vbox = GTK_BOX (GTK_DIALOG (p->top)->vbox);
  hbox = GTK_BOX (GTK_DIALOG (p->top)->action_area);

  /* construct the preview area (table with sliders & preview window) */
  table = gtk_table_new (2, 2, /* homogeneous */ FALSE);
  gtk_table_set_col_spacing (GTK_TABLE (table), 0, 1);
  gtk_table_set_row_spacing (GTK_TABLE (table), 0, 1);
  gtk_container_border_width (GTK_CONTAINER (table), 2);
  gtk_box_pack_start (vbox, table, /* expand */ TRUE, /* fill */ TRUE,
		      /* padding */ 0);

  /* the empty box in the top-left corner */
  frame = gtk_frame_new (/* label */ 0);
  gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_OUT);
  gtk_table_attach (GTK_TABLE (table), frame, 0, 1, 0, 1,
		    GTK_FILL, GTK_FILL, 0, 0);

  /* the horizontal ruler */
  p->hruler = gtk_hruler_new ();
  gtk_table_attach (GTK_TABLE (table), p->hruler, 1, 2, 0, 1,
		    GTK_FILL, 0, 0, 0);

  /* the vertical ruler */
  p->vruler = gtk_vruler_new ();
  gtk_table_attach (GTK_TABLE (table), p->vruler, 0, 1, 1, 2, 0,
		    GTK_FILL, 0, 0);

  /* the preview area */

  p->window = gtk_preview_new (GTK_PREVIEW_COLOR);
  gtk_preview_set_expand (GTK_PREVIEW (p->window), TRUE);
  gtk_widget_set_events (p->window,
			 GDK_EXPOSURE_MASK |
			 GDK_POINTER_MOTION_MASK |
			 GDK_POINTER_MOTION_HINT_MASK |
			 GDK_BUTTON_PRESS_MASK |
			 GDK_BUTTON_RELEASE_MASK);
  gtk_signal_connect (GTK_OBJECT (p->window), "event",
		      (GtkSignalFunc) event_handler, p);
  gtk_signal_connect_after (GTK_OBJECT (p->window), "expose_event",
			    (GtkSignalFunc) expose_handler, p);
  gtk_signal_connect_after (GTK_OBJECT (p->window), "size_allocate",
			    (GtkSignalFunc) preview_area_resize, 0);
  gtk_object_set_data (GTK_OBJECT (p->window), "PreviewPointer", p);

  /* Connect the motion-notify events of the preview area with the
     rulers.  Nifty stuff!  */

  class = GTK_WIDGET_CLASS (GTK_OBJECT (p->hruler)->klass);
  signal_func = (GtkSignalFunc) class->motion_notify_event;
  gtk_signal_connect_object (GTK_OBJECT (p->window), "motion_notify_event",
			     signal_func, GTK_OBJECT (p->hruler));

  class = GTK_WIDGET_CLASS (GTK_OBJECT (p->vruler)->klass);
  signal_func = (GtkSignalFunc) class->motion_notify_event;
  gtk_signal_connect_object (GTK_OBJECT (p->window), "motion_notify_event",
			     signal_func, GTK_OBJECT (p->vruler));

  p->viewport = gtk_frame_new (/* label */ 0);
  gtk_frame_set_shadow_type (GTK_FRAME (p->viewport), GTK_SHADOW_IN);
  gtk_container_add (GTK_CONTAINER (p->viewport), p->window);

  gtk_table_attach (GTK_TABLE (table), p->viewport, 1, 2, 1, 2,
		    GTK_FILL | GTK_EXPAND | GTK_SHRINK,
		    GTK_FILL | GTK_EXPAND | GTK_SHRINK, 0, 0);

  preview_update (p);

  /* fill in action area: */

  /* Start button */
  button = gtk_button_new_with_label ("Acquire Preview");
  gtk_signal_connect (GTK_OBJECT (button), "clicked",
		      (GtkSignalFunc) start_button_clicked, p);
  gtk_box_pack_start (GTK_BOX (hbox), button, TRUE, TRUE, 0);
  gtk_widget_show (button);

  /* Cancel button */
  p->cancel = gtk_button_new_with_label ("Cancel Preview");
  gtk_signal_connect (GTK_OBJECT (p->cancel), "clicked",
		      (GtkSignalFunc) cancel_button_clicked, p);
  gtk_box_pack_start (GTK_BOX (hbox), p->cancel, TRUE, TRUE, 0);
  gtk_widget_set_sensitive (p->cancel, FALSE);

  gtk_widget_show (p->cancel);
  gtk_widget_show (p->viewport);
  gtk_widget_show (p->window);
  gtk_widget_show (p->hruler);
  gtk_widget_show (p->vruler);
  gtk_widget_show (frame);
  gtk_widget_show (table);
  gtk_widget_show (p->top);

  gtk_widget_pop_colormap ();
#ifndef XSERVER_WITH_BUGGY_VISUALS
  gtk_widget_pop_visual ();
#endif
  return p;
}

void
preview_update (Preview *p)
{
  float val, width, height, max_width, max_height;
  const SANE_Option_Descriptor *opt;
  int i, surface_changed;
  SANE_Value_Type type;
  SANE_Unit unit;
  float min, max;

  surface_changed = 0;
  unit = SANE_UNIT_PIXEL;
  type = SANE_TYPE_INT;
  for (i = 0; i < 4; ++i)
    {
      val = (i & 2) ? INF : -INF;
      if (p->dialog->well_known.coord[i] > 0)
	{
	  opt = sane_get_option_descriptor (p->dialog->dev,
					    p->dialog->well_known.coord[i]);
	  assert (opt->unit == SANE_UNIT_PIXEL || opt->unit == SANE_UNIT_MM);
	  unit = opt->unit;
	  type = opt->type;

	  get_bounds (opt, &min, &max);
	  if (i & 2)
	    val = max;
	  else
	    val = min;
	}
      if (p->surface[i] != val)
	{
	  surface_changed = 1;
	  p->surface[i] = val;
	}
    }
  if (p->surface_unit != unit)
    {
      surface_changed = 1;
      p->surface_unit = unit;
    }
  if (p->surface_type != type)
    {
      surface_changed = 1;
      p->surface_type = type;
    }
  if (surface_changed && p->image_data)
    {
      free (p->image_data);
      p->image_data = 0;
      p->image_width = 0;
      p->image_height = 0;
    }

  /* guess the initial preview window size: */

  width  = p->surface[GSG_BR_X] - p->surface[GSG_TL_X];
  height = p->surface[GSG_BR_Y] - p->surface[GSG_TL_Y];
  if (p->surface_type == SANE_TYPE_INT)
    {
      width  += 1.0;
      height += 1.0;
    }
  else
    {
      width  += SANE_UNFIX (1.0);
      height += SANE_UNFIX (1.0);
    }

  assert (width > 0.0 && height > 0.0);

  if (width >= INF || height >= INF)
    p->aspect = 1.0;
  else
    p->aspect = width/height;

  max_width  = 0.5*gdk_screen_width ();
  max_height = 0.5*gdk_screen_height ();

  if (p->surface_unit != SANE_UNIT_PIXEL)
    {
      width  = max_width;
      height = max_height;
    }
  else
    {
      if (width > max_width)
	width  = max_width;

      if (height > max_height)
	height = max_height;
    }

  /* re-adjust so we maintain aspect without exceeding max size: */
  if (width/height != p->aspect)
    {
      if (p->aspect > 1.0)
	height = width/p->aspect;
      else
	width  = height*p->aspect;
    }

  p->preview_width = width + 0.5;
  p->preview_height = height + 0.5;
  if (surface_changed)
    {
      gtk_widget_set_usize (GTK_WIDGET (p->window),
			    p->preview_width, p->preview_height);
      if (GTK_WIDGET_DRAWABLE (p->window))
	preview_area_resize (p->window);

      if (preferences.preserve_preview)
	restore_preview_image (p);
    }
  update_selection (p);
}

void
preview_scan (Preview *p)
{
  float min, max, swidth, sheight, width, height, dpi = 0;
  const SANE_Option_Descriptor *opt;
  gint gwidth, gheight;
  int i;

  save_option (p, p->dialog->well_known.dpi,
	       &p->saved_dpi, &p->saved_dpi_valid);
  for (i = 0; i < 4; ++i)
    save_option (p, p->dialog->well_known.coord[i],
		 &p->saved_coord[i], p->saved_coord_valid + i);

  /* determine dpi, if necessary: */

  if (p->dialog->well_known.dpi > 0)
    {
      opt = sane_get_option_descriptor (p->dialog->dev,
					p->dialog->well_known.dpi);

      gwidth  = p->preview_width;
      gheight = p->preview_height;

      height = gheight;
      width  = height*p->aspect;
      if (width > gwidth)
	{
	  width  = gwidth;
	  height = width/p->aspect;
	}

      swidth = (p->surface[GSG_BR_X] - p->surface[GSG_TL_X]);
      if (swidth < INF)
	dpi = MM_PER_INCH*width/swidth;
      else
	{
	  sheight = (p->surface[GSG_BR_Y] - p->surface[GSG_TL_Y]);
	  if (sheight < INF)
	    dpi = MM_PER_INCH*height/sheight;
	  else
	    dpi = 18.0;
	}
      get_bounds (opt, &min, &max);
      if (dpi < min)
	dpi = min;
      if (dpi > max)
	dpi = max;

      set_option_float (p, p->dialog->well_known.dpi, dpi);
    }

  /* set the scan window (necessary since backends may default to
     non-maximum size):  */
  for (i = 0; i < 4; ++i)
    set_option_float (p, p->dialog->well_known.coord[i], p->surface[i]);
  set_option_bool (p, p->dialog->well_known.preview, SANE_TRUE);

  /* OK, all set to go */
  scan_start (p);
}

void
preview_destroy (Preview *p)
{
  char filename[PATH_MAX];
  FILE *out;

  if (p->scanning)
    scan_done (p);		/* don't save partial window */
  else if (preferences.preserve_preview && p->image_data
	   && make_preview_image_path (p, sizeof (filename), filename) >= 0)
    {
      /* save preview image */
      out = fopen (filename, "w");
      if (out)
	{
	  /* always save it as a PPM image: */
	  fprintf (out, "P6\n# surface: %g %g %g %g %u %u\n%d %d\n255\n",
		   p->surface[0], p->surface[1], p->surface[2], p->surface[3],
		   p->surface_type, p->surface_unit,
		   p->image_width, p->image_height);
	  fwrite (p->image_data, 3, p->image_width*p->image_height, out);
	  fclose (out);
	}
    }
  if (p->image_data)
    free (p->image_data);
  if (p->preview_row)
    free (p->preview_row);
  if (p->gc)
    gdk_gc_destroy (p->gc);
  if (p->top)
    gtk_widget_destroy (p->top);
  free (p);
}
