/*
 * "$Id: panel.c,v 1.11.8.2 2007/05/29 01:47:27 rlk Exp $"
 *
 *   Main window code for Print plug-in for the GIMP.
 *
 *   Copyright 1997-2003 Michael Sweet (mike@easysw.com),
 *	Robert Krawitz (rlk@alum.mit.edu), Steve Miller (smiller@rni.net)
 *      and Michael Natterer (mitch@gimp.org)
 *
 *   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.
 */

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

#define MAX_PREVIEW_PPI        (400)
#define INCH 72
#define FINCH ((gdouble) INCH)
#define ROUNDUP(x, y) (((x) + ((y) - 1)) / (y))
#define SCALE(x, y) (((x) + (1.0 / (2.0 * (y)))) * (y))

#include <gutenprint/gutenprint-intl-internal.h>
#include <gutenprintui2/gutenprintui.h>
#include "gutenprintui-internal.h"

#include <string.h>
#include <stdio.h>

#define MAXIMUM_PARAMETER_LEVEL STP_PARAMETER_LEVEL_ADVANCED4

/*
 * Constants for GUI.
 */
static int preview_size_vert = 360;
static int preview_size_horiz = 300;
static const int minimum_image_percent = 5.0;
static const int thumbnail_hintw = 128;
static const int thumbnail_hinth = 128;

#define MOVE_CONSTRAIN	   0
#define MOVE_HORIZONTAL	   1
#define MOVE_VERTICAL      2
#define MOVE_ANY           (MOVE_HORIZONTAL | MOVE_VERTICAL)
#define MOVE_GRID	   4

/*
 *  Main window widgets
 */

static GtkWidget *main_vbox;
static GtkWidget *main_hbox;
static GtkWidget *right_vbox;
static GtkWidget *notebook;

static GtkWidget *output_color_vbox;
static GtkWidget *cyan_button;
static GtkWidget *magenta_button;
static GtkWidget *yellow_button;
static GtkWidget *black_button;
static GtkWidget *red_button;
static GtkWidget *green_button;
static GtkWidget *blue_button;

static GtkWidget *print_dialog;           /* Print dialog window */

static GtkWidget *recenter_button;
static GtkWidget *recenter_vertical_button;
static GtkWidget *recenter_horizontal_button;

static GtkWidget *left_entry;
/*
static GtkWidget *right_entry;
*/
static GtkWidget *right_border_entry;
static GtkWidget *top_entry;
/*
static GtkWidget *bottom_entry;
*/
static GtkWidget *bottom_border_entry;
static GtkWidget *width_entry;
static GtkWidget *height_entry;
static GtkWidget *units_hbox;
static GtkWidget *units_label;

static GtkWidget *custom_size_width;
static GtkWidget *custom_size_height;
static GtkWidget *show_all_paper_sizes_button;
static GtkWidget *auto_paper_size_button;

static GtkWidget *orientation_menu;    /* Orientation menu */

static GtkWidget *scaling_percent;     /* Scale by percent */
static GtkWidget *scaling_ppi;         /* Scale by pixels-per-inch */
static GtkWidget *scaling_image;       /* Scale to the image */
static GtkObject *scaling_adjustment;  /* Adjustment object for scaling */

static GtkWidget *setup_dialog;        /* Setup dialog window */
static GtkWidget *printer_driver;      /* Printer driver widget */
static GtkWidget *printer_model_label; /* Printer model name */
static GtkWidget *printer_crawler;     /* Scrolled Window for menu */
static GtkWidget *printer_combo;       /* Combo for menu */
static GtkWidget *manufacturer_clist;  /* Manufacturer widget */
static GtkWidget *manufacturer_crawler;     /* Scrolled Window for menu */
static gint plist_callback_id = -1;
static GtkWidget *ppd_file;            /* PPD file entry */
static GtkWidget *ppd_box;
static GtkWidget *ppd_label;           /* PPD file entry */
static GtkWidget *ppd_button;          /* PPD file browse button */
static GtkWidget *ppd_browser;         /* File selection dialog for PPDs */
static GtkWidget *ppd_model_label;     /* PPD file entry */
static GtkWidget *ppd_model;           /* PPD file entry */
static GtkWidget *new_printer_dialog;  /* New printer dialog window */
static GtkWidget *new_printer_entry;   /* New printer text entry */
static GtkWidget *file_button;         /* PPD file browse button */
static GtkWidget *file_entry;          /* FSD for print files */
static GtkWidget *file_browser;        /* FSD for print files */
static GtkWidget *standard_cmd_entry;  /* FSD for print files */
static GtkWidget *custom_command_entry;   /* FSD for print files */
static GtkWidget *queue_combo;   /* FSD for print files */
static gint queue_callback_id = -1;

static GtkWidget *adjust_color_button;
static GtkWidget *about_dialog;

static GtkWidget *page_size_table;
static GtkWidget *printer_features_table;
static GtkWidget *color_adjustment_table;

static GtkWidget *copy_count_spin_button;

static gboolean preview_valid = FALSE;
static gboolean frame_valid = FALSE;
static gboolean need_exposure = FALSE;
static gboolean suppress_scaling_adjustment = FALSE;
static gboolean suppress_scaling_callback   = FALSE;
static gboolean thumbnail_update_pending    = FALSE;
static gboolean thumbnail_needs_rebuild     = FALSE;
/*
 * These are semaphores, not true booleans.
 */
static gint suppress_preview_update = 0;
static gint suppress_preview_reset = 0;

static GtkDrawingArea *preview = NULL;	/* Preview drawing area widget */
static GtkDrawingArea *swatch = NULL;
static gint mouse_x, mouse_y;		/* Last mouse position */
static gint orig_top, orig_left;	/* Original mouse position at start */
static gint buttons_pressed = 0;
static gint preview_active = 0;
static gint buttons_mask = 0;
static gint move_constraint = 0;
static gint mouse_button = -1;	/* Button being dragged with */
static gint preview_thumbnail_w, preview_thumbnail_h;
static gint preview_x, preview_y, preview_w, preview_h;

static gint physical_orientation = -2; /* Actual orientation */

static gint paper_width, paper_height; /* Physical width */
static gint printable_width, printable_height;	/* Size of printable area */
static gint print_width, print_height; /* Printed area of image */
static gint left, right, top, bottom; /* Imageable region */
static gint image_width, image_height; /* Image size (possibly rotated) */
static gint image_true_width, image_true_height; /* Original image */
static gdouble image_xres, image_yres; /* Original image resolution */
static gint do_update_thumbnail = 0;
static gint saveme = 0;		/* True if printrc should be saved */
static gint runme = 0;		/* True if print should proceed */
static gint exit_after_file_ok = 0; /* True if we should exit after file browser complete */
static gint auto_paper_size = 0; /* True if we're using auto paper size now */



static void scaling_update        (GtkAdjustment *adjustment);
static void scaling_callback      (GtkWidget *widget);
static void plist_callback        (GtkWidget *widget, gpointer data);
static void queue_callback        (GtkWidget *widget, gpointer data);
static void custom_media_size_callback(GtkWidget *widget, gpointer data);
static void show_all_paper_sizes_callback(GtkWidget *widget, gpointer data);
static void auto_paper_size_callback(GtkWidget *widget, gpointer data);
static void combo_callback        (GtkWidget *widget, gpointer data);
static void output_type_callback  (GtkWidget *widget, gpointer data);
static void unit_callback         (GtkWidget *widget, gpointer data);
static void orientation_callback  (GtkWidget *widget, gpointer data);
static void ppd_file_callback     (GtkWidget *widget, gpointer data);
static void printandsave_callback (void);
static void about_callback        (void);
static void print_callback        (void);
static void save_callback         (void);
static void setup_callback      (GtkWidget *widget);

static void setup_update          (void);
static void setup_open_callback   (void);
static void setup_ok_callback     (void);
static void setup_cancel_callback (void);
static void new_printer_open_callback   (void);
static void new_printer_ok_callback     (void);
static void ppd_browse_callback   (void);
static void ppd_ok_callback       (void);
static void file_browse_callback   (void);
static void file_ok_callback      (void);
static void file_cancel_callback  (void);
static void build_printer_driver_clist(void);
static void print_driver_callback (GtkWidget      *widget,
				   gint            row,
				   gint            column,
				   GdkEventButton *event,
				   gpointer        data);
static void manufacturer_callback (GtkWidget      *widget,
				   gint            row,
				   gint            column,
				   GdkEventButton *event,
				   gpointer        data);
static void command_type_callback  (GtkWidget *widget, gpointer data);

static void do_preview_thumbnail (void);
static void invalidate_preview_thumbnail (void);
static void invalidate_frame (void);

static GtkWidget *color_adjust_dialog;

static void preview_update              (void);
static void preview_expose              (void);
static void preview_button_callback     (GtkWidget      *widget,
					 GdkEventButton *bevent,
					 gpointer        data);
static void preview_motion_callback     (GtkWidget      *widget,
					 GdkEventMotion *mevent,
					 gpointer        data);
static void position_callback           (GtkWidget      *widget);
static void position_button_callback    (GtkWidget      *widget,
					 gpointer        data);
static void copy_count_callback         (GtkAdjustment  *widget,
					 gpointer        data);
static void plist_build_combo(GtkWidget *combo,
			      GtkWidget *label,
			      stp_string_list_t *items,
			      int active,
			      const gchar *cur_item,
			      const gchar *def_value,
			      GCallback callback,
			      gint *callback_id,
			      int (*check_func)(const char *string),
			      gpointer data);
static void initialize_thumbnail(void);
static void set_color_defaults (void);
static void redraw_color_swatch (void);
static void color_update (GtkAdjustment *adjustment);
static void dimension_update (GtkAdjustment *adjustment);
static void set_controls_active (GtkObject *checkbutton, gpointer optno);
static void update_adjusted_thumbnail (gboolean regenerate_image);

static void set_media_size(const gchar *new_media_size);
static const stp_printer_t *tmp_printer = NULL;

static option_t *current_options = NULL;
static int current_option_count = 0;

static unit_t units[] =
  {
    { N_("Inch"), N_("Set the base unit of measurement to inches"),
      72.0, NULL, "%.2f" },
    { N_("cm"), N_("Set the base unit of measurement to centimetres"),
      72.0 / 2.54, NULL, "%.2f" },
    { N_("Points"), N_("Set the base unit of measurement to points (1/72\")"),
      1.0, NULL, "%.0f" },
    { N_("mm"), N_("Set the base unit of measurement to millimetres"),
      72.0 / 25.4, NULL, "%.1f" },
    { N_("Pica"), N_("Set the base unit of measurement to picas (1/12\")"),
      72.0 / 12.0, NULL, "%.1f" },
  };
static const gint unit_count = sizeof(units) / sizeof(unit_t);

static radio_group_t output_types[] =
  {
    { N_("Color"), N_("Color output"), "Color", NULL },
    { N_("Grayscale"),
      N_("Print in shades of gray using black ink"), "BW", NULL }
  };

static const gint output_type_count = (sizeof(output_types) /
				       sizeof(radio_group_t));

/*
 * The order of these entries must match the order in command_t in
 * gutenprintui.h
 */
static radio_group_t command_options[] =
  {
    { N_("Standard Command"), N_("Use standard print command"), "Standard", NULL },
    { N_("Custom Command"), N_("Use custom print command"), "Custom", NULL },
    { N_("File"), N_("Print to a file"), "File", NULL }
  };

static const gint command_options_count = (sizeof(command_options) /
					   sizeof(radio_group_t));

static gdouble preview_ppi = 10;

static stp_string_list_t *printer_list = 0;
static stpui_plist_t *pv;
static const char *manufacturer = 0;

static gint thumbnail_w, thumbnail_h, thumbnail_bpp;
static guchar *thumbnail_data;
static guchar *adjusted_thumbnail_data;
static guchar *preview_thumbnail_data;

static void
set_stpui_curve_values(GtkWidget *gcurve, const stp_curve_t *seed)
{
  if (stp_curve_get_gamma(seed))
    {
      stpui_curve_set_gamma(STPUI_CURVE(gcurve), stp_curve_get_gamma(seed));
    }
  else
    {
      stp_curve_t *copy = stp_curve_create_copy(seed);
      const float *fdata;
      size_t count;
      stp_curve_resample(copy, 256);
      fdata = stp_curve_get_float_data(copy, &count);
      stpui_curve_set_vector(STPUI_CURVE(gcurve), count, fdata);
      stp_curve_destroy(copy);
    }
}

static void
set_stp_curve_values(GtkWidget *widget, option_t *opt)
{
  int i;
  double lo, hi;
  gfloat vector[256];
  GtkWidget *gcurve = GTK_WIDGET(widget);
  stp_curve_t *curve = stp_curve_create_copy(opt->info.curve.deflt);
  stpui_curve_get_vector(STPUI_CURVE(gcurve), 256, vector);
  stp_curve_get_bounds(opt->info.curve.deflt, &lo, &hi);
  for (i = 0; i < 256; i++)
    {
      if (vector[i] > hi)
	vector[i] = hi;
      else if (vector[i] < lo)
	vector[i] = lo;
    }
  switch (STPUI_CURVE(gcurve)->curve_type)
    {
    case STPUI_CURVE_TYPE_SPLINE:
      stp_curve_set_interpolation_type(curve, STP_CURVE_TYPE_SPLINE);
      break;
    default:
      stp_curve_set_interpolation_type(curve, STP_CURVE_TYPE_LINEAR);
      break;
    }
  stp_curve_set_float_data(curve, 256, vector);
  stp_set_curve_parameter(pv->v, opt->fast_desc->name, curve);
  stp_curve_destroy(curve);
}

static int
open_curve_editor(GtkObject *button, gpointer xopt)
{
  option_t *opt = (option_t *)xopt;
  if (opt->info.curve.is_visible == FALSE)
    {
      GtkWidget *gcurve =
	GTK_WIDGET(STPUI_GAMMA_CURVE(opt->info.curve.gamma_curve)->curve);
      const stp_curve_t *seed =
	stp_get_curve_parameter(pv->v, opt->fast_desc->name);
      stp_curve_t *nseed = NULL;
      if (!seed)
	seed = opt->info.curve.deflt;
      if (seed)
	nseed = stp_curve_create_copy(seed);
      gtk_widget_set_sensitive(GTK_WIDGET(opt->checkbox), FALSE);
      gtk_widget_show(GTK_WIDGET(opt->info.curve.dialog));
      set_stpui_curve_values(gcurve, seed);
      opt->info.curve.is_visible = TRUE;
      if (opt->info.curve.current)
	stp_curve_destroy(opt->info.curve.current);
      opt->info.curve.current = nseed;
      invalidate_preview_thumbnail();
      update_adjusted_thumbnail(FALSE);
    }
/*  gtk_window_activate_focus(GTK_WINDOW(opt->info.curve.dialog)); */
  return 1;
}

static int
set_default_curve_callback(GtkObject *button, gpointer xopt)
{
  option_t *opt = (option_t *)xopt;
  GtkWidget *gcurve =
    GTK_WIDGET(STPUI_GAMMA_CURVE(opt->info.curve.gamma_curve)->curve);
  const stp_curve_t *seed = opt->info.curve.deflt;
  set_stpui_curve_values(gcurve, seed);
  set_stp_curve_values(gcurve, opt);
  invalidate_preview_thumbnail();
  update_adjusted_thumbnail(TRUE);
  return 1;
}

static int
set_previous_curve_callback(GtkObject *button, gpointer xopt)
{
  option_t *opt = (option_t *)xopt;
  GtkWidget *gcurve =
    GTK_WIDGET(STPUI_GAMMA_CURVE(opt->info.curve.gamma_curve)->curve);
  const stp_curve_t *seed = opt->info.curve.current;
  if (!seed)
    seed = opt->info.curve.deflt;
  set_stpui_curve_values(gcurve, seed);
  set_stp_curve_values(gcurve, opt);
  invalidate_preview_thumbnail();
  update_adjusted_thumbnail(TRUE);
  return 1;
}

static int
set_curve_callback(GtkObject *button, gpointer xopt)
{
  option_t *opt = (option_t *)xopt;
  GtkWidget *gcurve =
    GTK_WIDGET(STPUI_GAMMA_CURVE(opt->info.curve.gamma_curve)->curve);
  gtk_widget_hide(opt->info.curve.dialog);
  gtk_widget_set_sensitive(GTK_WIDGET(opt->checkbox), TRUE);
  opt->info.curve.is_visible = FALSE;
  set_stp_curve_values(gcurve, opt);
  if (opt->info.curve.current)
    stp_curve_destroy(opt->info.curve.current);
  opt->info.curve.current = NULL;
  invalidate_preview_thumbnail();
  update_adjusted_thumbnail(TRUE);
  return 1;
}

static gint
curve_draw_callback(GtkWidget *widget, GdkEvent *event, gpointer xopt)
{
  option_t *opt = (option_t *)xopt;
  switch (event->type)
    {
    case GDK_BUTTON_RELEASE:
      set_stp_curve_values(widget, opt);
      invalidate_preview_thumbnail();
      update_adjusted_thumbnail(TRUE);
      break;
    default:
      break;
    }
  return 1;
}

static gint
curve_type_changed(GtkWidget *widget, gpointer xopt)
{
  option_t *opt = (option_t *)xopt;
  set_stp_curve_values(widget, opt);
  invalidate_preview_thumbnail();
  update_adjusted_thumbnail(TRUE);
  return 1;
}

static int
cancel_curve_callback(GtkObject *button, gpointer xopt)
{
  option_t *opt = (option_t *)xopt;
  if (opt->info.curve.is_visible)
    {
      stp_set_curve_parameter(pv->v, opt->fast_desc->name,
			      opt->info.curve.current);
      stp_curve_destroy(opt->info.curve.current);
      opt->info.curve.current = NULL;
      gtk_widget_hide(opt->info.curve.dialog);
      gtk_widget_set_sensitive(GTK_WIDGET(opt->checkbox), TRUE);
      opt->info.curve.is_visible = FALSE;
      invalidate_preview_thumbnail();
      update_adjusted_thumbnail(TRUE);
    }
  return 1;
}

static void
stpui_create_curve(option_t *opt,
		   GtkTable *table,
		   gint column,
		   gint row,
		   const gchar *text,
		   const stp_curve_t *deflt,
		   gboolean is_optional)
{
  double lower, upper;
  opt->checkbox = gtk_check_button_new();
  gtk_table_attach(GTK_TABLE(table), opt->checkbox,
		   column, column + 1, row, row + 1,
		   GTK_EXPAND|GTK_FILL, GTK_FILL, 0, 0);
  if (is_optional)
    gtk_widget_show(opt->checkbox);
  else
    gtk_widget_hide(opt->checkbox);

  opt->info.curve.label = gtk_label_new(text);
  gtk_misc_set_alignment (GTK_MISC (opt->info.curve.label), 0.0, 0.5);
  gtk_table_attach (GTK_TABLE (table), opt->info.curve.label,
                    column + 1, column + 2, row, row + 1,
                    GTK_FILL, GTK_FILL, 0, 0);
  gtk_widget_show (opt->info.curve.label);

  opt->info.curve.button = gtk_button_new_with_label(_("Edit Curve..."));
  g_signal_connect(G_OBJECT(opt->info.curve.button), "clicked",
		   G_CALLBACK(open_curve_editor), opt);
  gtk_table_attach (GTK_TABLE (table), opt->info.curve.button,
                    column + 2, column + 3, row, row + 1,
                    GTK_FILL, GTK_FILL, 0, 0);
  gtk_widget_show(opt->info.curve.button);

  opt->info.curve.dialog =
    stpui_dialog_new(gettext(opt->fast_desc->text),
		     GTK_WIN_POS_MOUSE, TRUE,
		     _("Set Default"), set_default_curve_callback,
		     opt, NULL, NULL, FALSE, FALSE,
		     _("Restore Previous"), set_previous_curve_callback,
		     opt, NULL, NULL, FALSE, FALSE,
		     _("OK"), set_curve_callback,
		     opt, NULL, NULL, FALSE, FALSE,
		     _("Cancel"), cancel_curve_callback,
		     opt, NULL, NULL, FALSE, FALSE,
		     NULL);
  opt->info.curve.gamma_curve = stpui_gamma_curve_new();
  gtk_box_pack_start(GTK_BOX(GTK_DIALOG(opt->info.curve.dialog)->vbox),
		     opt->info.curve.gamma_curve, TRUE, TRUE, 0);
  stp_curve_get_bounds(opt->info.curve.deflt, &lower, &upper);
  stpui_curve_set_range
    (STPUI_CURVE (STPUI_GAMMA_CURVE(opt->info.curve.gamma_curve)->curve),
     0.0, 1.0, lower, upper);
  gtk_widget_set_usize
    (GTK_WIDGET(STPUI_GAMMA_CURVE(opt->info.curve.gamma_curve)->curve), 256, 256);
  gtk_widget_show(opt->info.curve.gamma_curve);

  g_signal_connect
    (G_OBJECT(STPUI_GAMMA_CURVE(opt->info.curve.gamma_curve)->curve),
     "curve-type-changed", G_CALLBACK(curve_type_changed), opt);
  g_signal_connect
    (G_OBJECT(STPUI_GAMMA_CURVE(opt->info.curve.gamma_curve)->curve),
     "event", G_CALLBACK(curve_draw_callback), opt);

  if (opt->fast_desc->help)
    {
      stpui_set_help_data (opt->info.curve.label, opt->fast_desc->help);
      stpui_set_help_data (opt->info.curve.button, opt->fast_desc->help);
      stpui_set_help_data (opt->info.curve.gamma_curve, opt->fast_desc->help);
    }
}

static int
checkbox_callback(GtkObject *button, gpointer xopt)
{
  option_t *opt = (option_t *)xopt;
  GtkWidget *checkbox = GTK_WIDGET(opt->info.bool.checkbox);
  opt->info.bool.current =
    gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(checkbox));
  stp_set_boolean_parameter(pv->v, opt->fast_desc->name,
			    opt->info.bool.current);
  invalidate_frame();
  invalidate_preview_thumbnail();
  update_adjusted_thumbnail(TRUE);
  preview_update();
  return 1;
}

static int
print_mode_is_color(const stp_vars_t *v)
{
  const char *printing_mode = stp_get_string_parameter(v, "PrintingMode");
  if (!printing_mode)
    {
      int answer = 1;
      stp_parameter_t desc;
      stp_describe_parameter(v, "PrintingMode", &desc);
      if (desc.p_type == STP_PARAMETER_TYPE_STRING_LIST &&
	  strcmp(desc.deflt.str, "BW") == 0)
	answer = 0;
      stp_parameter_description_destroy(&desc);
      return answer;
    }
  if (strcmp(printing_mode, "BW") == 0)
    return 0;
  else
    return 1;
}

static void
set_current_printer(void)
{
  pv = &(stpui_plist[stpui_plist_current]);
  if (print_mode_is_color(pv->v))
    stp_set_string_parameter(pv->v, "PrintingMode", "Color");
  else
    stp_set_string_parameter(pv->v, "PrintingMode", "BW");
}


static void
stpui_create_boolean(option_t *opt,
		     GtkTable *table,
		     gint column,
		     gint row,
		     const gchar *text,
		     int deflt,
		     gboolean is_optional)
{
  opt->checkbox = gtk_check_button_new();
  gtk_table_attach(GTK_TABLE(table), opt->checkbox,
		   column, column + 1, row, row + 1,
		   GTK_EXPAND|GTK_FILL, GTK_FILL, 0, 0);
  if (is_optional)
    gtk_widget_show(opt->checkbox);
  else
    gtk_widget_hide(opt->checkbox);

  opt->info.bool.checkbox =
    gtk_toggle_button_new_with_label(gettext(opt->fast_desc->text));
  gtk_table_attach(GTK_TABLE(table), opt->info.bool.checkbox,
		   column + 1, column + 3, row, row + 1,
		   GTK_EXPAND|GTK_FILL, GTK_FILL, 0, 0);
  gtk_widget_show(opt->info.bool.checkbox);
  gtk_toggle_button_set_active
    (GTK_TOGGLE_BUTTON(opt->info.bool.checkbox),
     stp_get_boolean_parameter(pv->v, opt->fast_desc->name));
  g_signal_connect(G_OBJECT(opt->info.bool.checkbox), "toggled",
		   G_CALLBACK(checkbox_callback), opt);
}

static void
build_queue_combo(void)
{
  plist_build_combo(queue_combo,
		    NULL,
		    stpui_system_print_queues,
		    1,
		    stpui_plist_get_queue_name(pv),
		    NULL,
		    G_CALLBACK(queue_callback),
		    &queue_callback_id,
		    NULL,
		    NULL);
}

static void
build_printer_combo(void)
{
  int i;
  if (printer_list)
    stp_string_list_destroy(printer_list);
  printer_list = stp_string_list_create();
  for (i = 0; i < stpui_plist_count; i++)
    stp_string_list_add_string(printer_list,
			       stpui_plist[i].name, stpui_plist[i].name);
  plist_build_combo(printer_combo,
		    NULL,
		    printer_list,
		    1,
		    stp_string_list_param(printer_list, stpui_plist_current)->name,
		    NULL,
		    G_CALLBACK(plist_callback),
		    &plist_callback_id,
		    NULL,
		    NULL);
}

static int
check_page_size(const char *paper_size)
{
  const stp_papersize_t *ps = stp_get_papersize_by_name(paper_size);
  if (ps && (ps->paper_unit == PAPERSIZE_ENGLISH_STANDARD ||
	     ps->paper_unit == PAPERSIZE_METRIC_STANDARD))
    return 1;
  else
    return 0;
}

static void
build_page_size_combo(option_t *option)
{
  /*
   * Some printers don't support any "standard" page sizes.  If the number
   * of page sizes is small, just display all of them.
   */
  if (stpui_show_all_paper_sizes ||
      stp_string_list_count(option->info.list.params) < 10)
    plist_build_combo(option->info.list.combo, option->info.list.label,
		      option->info.list.params, option->is_active,
		      stp_get_string_parameter(pv->v, option->fast_desc->name),
		      option->info.list.default_val,G_CALLBACK(combo_callback),
		      &(option->info.list.callback_id), NULL, option);
  else
    plist_build_combo(option->info.list.combo, option->info.list.label,
		      option->info.list.params, option->is_active,
		      stp_get_string_parameter(pv->v, option->fast_desc->name),
		      option->info.list.default_val,G_CALLBACK(combo_callback),
		      &(option->info.list.callback_id),
		      check_page_size, option);
}

static void
build_a_combo(option_t *option)
{
  const gchar *new_value;
  stp_parameter_activity_t active;
  if (option->fast_desc &&
      option->fast_desc->p_type == STP_PARAMETER_TYPE_STRING_LIST)
    {
      const gchar *val = stp_get_string_parameter(pv->v,
						  option->fast_desc->name);
      if (option->info.list.params == NULL || ! option->is_active ||
	  stp_string_list_count(option->info.list.params) == 0)
	stp_set_string_parameter(pv->v, option->fast_desc->name, NULL);
      else if (!val || strlen(val) == 0 ||
	       ! stp_string_list_is_present(option->info.list.params, val))
	stp_set_string_parameter(pv->v, option->fast_desc->name,
				 option->info.list.default_val);
      if (strcmp(option->fast_desc->name, "PageSize") == 0)
	build_page_size_combo(option);
      else
	plist_build_combo(option->info.list.combo, option->info.list.label,
			  option->info.list.params,
			  option->is_active,
			  stp_get_string_parameter(pv->v,
						   option->fast_desc->name),
			  option->info.list.default_val, G_CALLBACK(combo_callback),
			  &(option->info.list.callback_id), NULL, option);
      if (strcmp(option->fast_desc->name, "PageSize") == 0)
	set_media_size
	  (stp_get_string_parameter(pv->v, option->fast_desc->name));
    }
  else
    plist_build_combo(option->info.list.combo, option->info.list.label,
		      NULL, 0, "", "", G_CALLBACK(combo_callback),
		      &(option->info.list.callback_id), NULL, option);
  new_value =
    stpui_combo_get_name(option->info.list.combo, option->info.list.params);
  active = stp_get_string_parameter_active(pv->v, option->fast_desc->name);
  stp_set_string_parameter(pv->v, option->fast_desc->name, new_value);
  stp_set_string_parameter_active(pv->v, option->fast_desc->name, active);
}

static void
populate_options(const stp_vars_t *v)
{
  stp_parameter_list_t params = stp_get_parameter_list(v);
  int i;
  int idx;
  if (current_options)
    {
      for (i = 0; i < current_option_count; i++)
	{
	  option_t *opt = &(current_options[i]);
	  switch (opt->fast_desc->p_type)
	    {
	    case STP_PARAMETER_TYPE_STRING_LIST:
	      if (opt->info.list.combo)
		{
		  gtk_widget_destroy(opt->info.list.combo);
		  gtk_widget_destroy(opt->info.list.label);
		  if (opt->info.list.params)
		    stp_string_list_destroy(opt->info.list.params);
		  g_free(opt->info.list.default_val);
		}
	      break;
	    case STP_PARAMETER_TYPE_DOUBLE:
	    case STP_PARAMETER_TYPE_DIMENSION:
	      if (opt->info.flt.adjustment)
		{
		  gtk_widget_destroy
		    (GTK_WIDGET
		     (SCALE_ENTRY_SCALE(opt->info.flt.adjustment)));
		  gtk_widget_destroy
		    (GTK_WIDGET
		     (SCALE_ENTRY_LABEL(opt->info.flt.adjustment)));
		  gtk_widget_destroy
		    (GTK_WIDGET
		     (SCALE_ENTRY_SPINBUTTON(opt->info.flt.adjustment)));
		}
	      break;
	    case STP_PARAMETER_TYPE_CURVE:
	      gtk_widget_destroy(GTK_WIDGET(opt->info.curve.label));
	      gtk_widget_destroy(GTK_WIDGET(opt->info.curve.button));
	      gtk_widget_destroy(GTK_WIDGET(opt->info.curve.dialog));
	      if (opt->info.curve.current)
		stp_curve_destroy(opt->info.curve.current);
	      break;
	    case STP_PARAMETER_TYPE_BOOLEAN:
	      gtk_widget_destroy(GTK_WIDGET(opt->info.bool.checkbox));
	      break;
	    default:
	      break;
	    }
	  if (opt->checkbox)
	    gtk_widget_destroy(GTK_WIDGET(opt->checkbox));
	}
      g_free(current_options);
    }
  current_option_count = stp_parameter_list_count(params);
  current_options = g_malloc(sizeof(option_t) * current_option_count);

  for (idx = 0, i = 0; i < current_option_count; i++)
    {
      stp_parameter_t desc;
      const stp_parameter_t *param = stp_parameter_list_param(params, i);
      if (!param->read_only &&
	  (param->p_class == STP_PARAMETER_CLASS_OUTPUT ||
	   param->p_class == STP_PARAMETER_CLASS_FEATURE ||
	   (param->p_class == STP_PARAMETER_CLASS_CORE &&
	    strcmp(param->name, "PageSize") == 0)))
	{
	  option_t *opt = &(current_options[idx]);
	  opt->fast_desc = stp_parameter_list_param(params, i);
	  stp_describe_parameter(v, opt->fast_desc->name, &desc);
	  opt->checkbox = NULL;
	  opt->is_active = 0;
	  opt->is_enabled = 0;
	  switch (opt->fast_desc->p_type)
	    {
	    case STP_PARAMETER_TYPE_STRING_LIST:
	      opt->info.list.callback_id = -1;
	      opt->info.list.default_val = g_strdup(desc.deflt.str);
	      if (desc.bounds.str)
		opt->info.list.params =
		  stp_string_list_create_copy(desc.bounds.str);
	      else
		opt->info.list.params = NULL;
	      opt->info.list.combo = NULL;
	      opt->info.list.label = NULL;
	      opt->is_active = desc.is_active;
	      break;
	    case STP_PARAMETER_TYPE_DOUBLE:
	      opt->info.flt.adjustment = NULL;
	      opt->info.flt.upper = desc.bounds.dbl.upper;
	      opt->info.flt.lower = desc.bounds.dbl.lower;
	      opt->info.flt.deflt = desc.deflt.dbl;
	      opt->info.flt.scale = 1.0;
	      opt->is_active = desc.is_active;
	      break;
	    case STP_PARAMETER_TYPE_DIMENSION:
	      opt->info.flt.adjustment = NULL;
	      opt->info.flt.upper = desc.bounds.dimension.upper;
	      opt->info.flt.lower = desc.bounds.dimension.lower;
	      opt->info.flt.deflt = desc.deflt.dimension;
	      opt->info.flt.scale = 1.0;
	      opt->is_active = desc.is_active;
	      break;
	    case STP_PARAMETER_TYPE_CURVE:
	      opt->info.curve.label = NULL;
	      opt->info.curve.button = NULL;
	      opt->info.curve.dialog = NULL;
	      opt->info.curve.gamma_curve = NULL;
	      opt->info.curve.current = NULL;
	      opt->info.curve.deflt = desc.deflt.curve;
	      opt->info.curve.is_visible = FALSE;
	      opt->is_active = desc.is_active;
	      break;
	    case STP_PARAMETER_TYPE_BOOLEAN:
	      opt->info.bool.checkbox = NULL;
	      opt->info.bool.current = 0;
	      opt->info.bool.deflt = desc.deflt.boolean;
	      opt->is_active = desc.is_active;
	    default:
	      break;
	    }
	  idx++;
	  stp_parameter_description_destroy(&desc);
	}
    }
  current_option_count = idx;
  stp_parameter_list_destroy(params);
}

static void
destroy_something(GtkWidget *widget, gpointer data)
{
  gtk_widget_destroy(widget);
}

static void
populate_option_table(GtkWidget *table, int p_class)
{
  int i, j;
  int current_pos = 0;
  GtkWidget *previous_sep = NULL;
  int counts[STP_PARAMETER_LEVEL_INVALID][STP_PARAMETER_TYPE_INVALID];
  int vpos[STP_PARAMETER_LEVEL_INVALID][STP_PARAMETER_TYPE_INVALID];
  for (i = 0; i < STP_PARAMETER_LEVEL_INVALID; i++)
    for (j = 0; j < STP_PARAMETER_TYPE_INVALID; j++)
      {
	vpos[i][j] = 0;
	counts[i][j] = 0;
      }


  gtk_container_foreach(GTK_CONTAINER(table), destroy_something, NULL);

  /* First scan the options to figure out where to start */
  for (i = 0; i < current_option_count; i++)
    {
      const stp_parameter_t *desc = current_options[i].fast_desc;
      /*
       * Specialize the core parameters (page size is the only one we want)
       * Yuck.
       */
      if (!desc->read_only && desc->p_class == p_class &&
	  (desc->p_class != STP_PARAMETER_CLASS_CORE ||
	   strcmp(desc->name, "PageSize") == 0))
	{
	  switch (desc->p_type)
	    {
	    case STP_PARAMETER_TYPE_STRING_LIST:
	    case STP_PARAMETER_TYPE_DIMENSION:
	    case STP_PARAMETER_TYPE_DOUBLE:
	    case STP_PARAMETER_TYPE_CURVE:
	    case STP_PARAMETER_TYPE_BOOLEAN:
	      counts[desc->p_level][desc->p_type]++;
	      break;
	    default:
	      break;
	    }
	}
    }

  /* Now, figure out where we're going to put the options */
  for (i = 0; i <= MAXIMUM_PARAMETER_LEVEL + 1; i++)
    {
      int level_count = 0;
      if (i <= MAXIMUM_PARAMETER_LEVEL)
	for (j = 0; j < STP_PARAMETER_TYPE_INVALID; j++)
	  level_count += counts[i][j];
      if (level_count > 0 && current_pos > 0)
	{
	  GtkWidget *sep = gtk_hseparator_new();
	  gtk_table_attach (GTK_TABLE(table), sep, 0, 4,
			    current_pos, current_pos + 1,
			    GTK_EXPAND|GTK_FILL, GTK_FILL, 0, 0);
	  if (previous_sep)
	    gtk_widget_show(previous_sep);
	  previous_sep = sep;
	  current_pos++;
	}
      if (i <= MAXIMUM_PARAMETER_LEVEL)
	for (j = 0; j < STP_PARAMETER_TYPE_INVALID; j++)
	  {
	    vpos[i][j] = current_pos;
	    current_pos += counts[i][j];
	  }
    }

  for (i = 0; i < current_option_count; i++)
    {
      option_t *opt = &(current_options[i]);
      const stp_curve_t *xcurve;
      const stp_parameter_t *desc = opt->fast_desc;
      if (!desc->read_only && desc->p_class == p_class &&
	  (desc->p_class != STP_PARAMETER_CLASS_CORE ||
	   strcmp(desc->name, "PageSize") == 0))
	{
	  gdouble unit_scaler;
	  gdouble minor_increment;
	  gint digits;
	  switch (desc->p_type)
	    {
	    case STP_PARAMETER_TYPE_STRING_LIST:
	      stpui_create_new_combo(opt, table, 0,
				     vpos[desc->p_level][desc->p_type]++,
				     !(desc->is_mandatory));
	      if (desc->p_level > MAXIMUM_PARAMETER_LEVEL)
		stp_set_string_parameter_active(pv->v, desc->name,
						STP_PARAMETER_INACTIVE);
	      break;
	    case STP_PARAMETER_TYPE_DOUBLE:
	      stpui_create_scale_entry(opt, GTK_TABLE(table), 0,
				       vpos[desc->p_level][desc->p_type]++,
				       gettext(desc->text), 200, 0,
				       opt->info.flt.deflt,
				       opt->info.flt.lower,
				       opt->info.flt.upper,
				       .001, .01, 3, TRUE, 0, 0, NULL,
				       !(desc->is_mandatory));
	      stpui_set_adjustment_tooltip(opt->info.flt.adjustment,
					   gettext(desc->help));
	      g_signal_connect(G_OBJECT(opt->info.flt.adjustment),
			       "value_changed",
			       G_CALLBACK(color_update), opt);
	      if (desc->p_level > MAXIMUM_PARAMETER_LEVEL)
		stp_set_float_parameter_active(pv->v, desc->name,
					       STP_PARAMETER_INACTIVE);
	      break;
	    case STP_PARAMETER_TYPE_DIMENSION:
	      unit_scaler = units[pv->unit].scale;
	      if (unit_scaler > 100)
		{
		  digits = 3;
		  minor_increment = .001;
		}
	      else if (unit_scaler > 10)
		{
		  digits = 2;
		  minor_increment = .01;
		}
	      else if (unit_scaler > 1)
		{
		  digits = 1;
		  minor_increment = .1;
		}
	      else
		{
		  digits = 0;
		  minor_increment = 1;
		}
	      stpui_create_scale_entry(opt, GTK_TABLE(table), 0,
				       vpos[desc->p_level][desc->p_type]++,
				       gettext(desc->text), 200, 0,
				       opt->info.flt.deflt / unit_scaler,
				       opt->info.flt.lower / unit_scaler,
				       opt->info.flt.upper / unit_scaler,
				       minor_increment, minor_increment * 10,
				       digits, TRUE, 0, 0, NULL,
				       !(desc->is_mandatory));
	      stpui_set_adjustment_tooltip(opt->info.flt.adjustment,
					   gettext(desc->help));
	      g_signal_connect(G_OBJECT(opt->info.flt.adjustment),
			       "value_changed",
			       G_CALLBACK(dimension_update), opt);
	      if (desc->p_level > MAXIMUM_PARAMETER_LEVEL)
		stp_set_dimension_parameter_active(pv->v, desc->name,
						   STP_PARAMETER_INACTIVE);
	      break;
	    case STP_PARAMETER_TYPE_CURVE:
	      xcurve = stp_get_curve_parameter(pv->v, opt->fast_desc->name);
	      if (xcurve)
		opt->info.curve.current = stp_curve_create_copy(xcurve);
	      else
		opt->info.curve.current = NULL;
	      stpui_create_curve(opt, GTK_TABLE(table), 0,
				 vpos[desc->p_level][desc->p_type]++,
				 gettext(desc->text), opt->info.curve.deflt,
				 !(desc->is_mandatory));
	      if (desc->p_level > MAXIMUM_PARAMETER_LEVEL)
		stp_set_curve_parameter_active(pv->v, desc->name,
					       STP_PARAMETER_INACTIVE);
	      break;
	    case STP_PARAMETER_TYPE_BOOLEAN:
	      opt->info.bool.current =
		stp_get_boolean_parameter(pv->v, opt->fast_desc->name);
	      stpui_create_boolean(opt, GTK_TABLE(table), 0,
				   vpos[desc->p_level][desc->p_type]++,
				   gettext(desc->text), opt->info.bool.deflt,
				   !(desc->is_mandatory));
	      if (desc->p_level > MAXIMUM_PARAMETER_LEVEL)
		stp_set_boolean_parameter_active(pv->v, desc->name,
						 STP_PARAMETER_INACTIVE);
	      break;
	    case STP_PARAMETER_TYPE_INT:
	      stp_set_int_parameter_active(pv->v, opt->fast_desc->name,
					   STP_PARAMETER_INACTIVE);
	      break;
	    case STP_PARAMETER_TYPE_RAW:
	      stp_set_raw_parameter_active(pv->v, opt->fast_desc->name,
					   STP_PARAMETER_INACTIVE);
	      break;
	    case STP_PARAMETER_TYPE_FILE:
	      if (strcmp(opt->fast_desc->name, "PPDFile") != 0)
		stp_set_file_parameter_active(pv->v, opt->fast_desc->name,
					      STP_PARAMETER_INACTIVE);
	      break;
	    default:
	      break;
	    }
	  if (opt->checkbox)
	    g_signal_connect
	      (G_OBJECT(opt->checkbox), "toggled",
	       G_CALLBACK(set_controls_active), opt);
	}
    }
}

static void
set_options_active(const char *omit)
{
  int i;
  for (i = 0; i < current_option_count; i++)
    {
      option_t *opt = &(current_options[i]);
      const stp_parameter_t *desc = opt->fast_desc;
      GtkObject *adj;
      if (omit && strcmp(omit, opt->fast_desc->name) == 0)
	continue;
      switch (desc->p_type)
	{
	case STP_PARAMETER_TYPE_STRING_LIST:
	  build_a_combo(opt);
	  break;
	case STP_PARAMETER_TYPE_DOUBLE:
	case STP_PARAMETER_TYPE_DIMENSION:
	  adj = opt->info.flt.adjustment;
	  if (adj)
	    {
	      if (opt->is_active && desc->p_level <= MAXIMUM_PARAMETER_LEVEL)
		{
		  gtk_widget_show(GTK_WIDGET(SCALE_ENTRY_LABEL(adj)));
		  gtk_widget_show(GTK_WIDGET(SCALE_ENTRY_SCALE(adj)));
		  gtk_widget_show(GTK_WIDGET(SCALE_ENTRY_SPINBUTTON(adj)));
		}
	      else
		{
		  gtk_widget_hide(GTK_WIDGET(SCALE_ENTRY_LABEL(adj)));
		  gtk_widget_hide(GTK_WIDGET(SCALE_ENTRY_SCALE(adj)));
		  gtk_widget_hide(GTK_WIDGET(SCALE_ENTRY_SPINBUTTON(adj)));
		}
	    }
	  break;
	case STP_PARAMETER_TYPE_CURVE:
	  if (opt->is_active && desc->p_level <= MAXIMUM_PARAMETER_LEVEL)
	    {
	      gtk_widget_show(GTK_WIDGET(opt->info.curve.label));
	      gtk_widget_show(GTK_WIDGET(opt->info.curve.button));
	    }
	  else
	    {
	      gtk_widget_hide(GTK_WIDGET(opt->info.curve.label));
	      gtk_widget_hide(GTK_WIDGET(opt->info.curve.button));
	      gtk_widget_hide(GTK_WIDGET(opt->info.curve.dialog));
	    }
	case STP_PARAMETER_TYPE_BOOLEAN:
	  if (opt->is_active && desc->p_level <= MAXIMUM_PARAMETER_LEVEL)
	    {
	      gtk_widget_show(GTK_WIDGET(opt->info.bool.checkbox));
	    }
	  else
	    {
	      gtk_widget_hide(GTK_WIDGET(opt->info.bool.checkbox));
	    }
	  break;
	default:
	  break;
	}
      if (opt->checkbox)
	{
	  if (!(opt->is_active) || desc->p_level > MAXIMUM_PARAMETER_LEVEL)
	    gtk_widget_hide(GTK_WIDGET(opt->checkbox));
	  else if (!(desc->is_mandatory))
	    gtk_widget_show(GTK_WIDGET(opt->checkbox));
	}
    }
}

static void
color_button_callback(GtkWidget *widget, gpointer data)
{
  invalidate_preview_thumbnail();
  update_adjusted_thumbnail(TRUE);
}

static void
create_top_level_structure(void)
{
  gchar *plug_in_name;
  /*
   * Create the main dialog
   */

  plug_in_name = g_strdup_printf (_("%s -- Print v%s"),
                                  stpui_get_image_filename(),
				  VERSION " - " RELEASE_DATE);

  print_dialog =
    stpui_dialog_new (plug_in_name,
		      GTK_WIN_POS_MOUSE,
		      TRUE,

		      _("About"), about_callback,
		      NULL, NULL, NULL, FALSE, FALSE,
		      _("Print and\nSave Settings"), printandsave_callback,
		      NULL, NULL, NULL, FALSE, FALSE,
		      _("Save\nSettings"), save_callback,
		      NULL, NULL, NULL, FALSE, FALSE,
		      _("Print"), print_callback,
		      NULL, NULL, NULL, FALSE, FALSE,
		      _("Cancel"), gtk_widget_destroy,
		      NULL, (GObject *) 1, NULL, FALSE, TRUE,

		      NULL);

  g_free (plug_in_name);

  g_signal_connect (G_OBJECT (print_dialog), "destroy",
		    G_CALLBACK (gtk_main_quit), NULL);

  /*
   * Top-level containers
   */

  main_vbox = gtk_vbox_new (FALSE, 2);
  gtk_container_set_border_width (GTK_CONTAINER (main_vbox), 6);
  gtk_box_pack_start (GTK_BOX (GTK_DIALOG (print_dialog)->vbox), main_vbox,
		      TRUE, TRUE, 0);
  gtk_widget_show (main_vbox);

  main_hbox = gtk_hbox_new (FALSE, 4);
  gtk_box_pack_start (GTK_BOX (main_vbox), main_hbox, TRUE, TRUE, 0);
  gtk_widget_show (main_hbox);

  right_vbox = gtk_vbox_new (FALSE, 2);
  gtk_box_pack_end (GTK_BOX (main_hbox), right_vbox, FALSE, FALSE, 0);
  gtk_widget_show (right_vbox);

  notebook = gtk_notebook_new ();
  gtk_box_pack_start (GTK_BOX (right_vbox), notebook, TRUE, TRUE, 0);
  gtk_widget_show (notebook);
}

static gint
drawing_area_resize_callback(GtkWidget *widget, GdkEventConfigure *event)
{
  preview_size_vert = event->height - 1;
  preview_size_horiz = event->width - 1;
  invalidate_preview_thumbnail();
  invalidate_frame();
  preview_update();
  return 1;
}

static void
create_preview (void)
{
  GtkWidget *frame;
  GtkWidget *event_box;

  frame = gtk_frame_new (_("Preview"));
  gtk_box_pack_start (GTK_BOX (main_hbox), frame, TRUE, TRUE, 0);
  gtk_widget_show (frame);

  preview = (GtkDrawingArea *) gtk_drawing_area_new ();
  gtk_drawing_area_size(preview, preview_size_horiz + 1, preview_size_vert +1);
  g_signal_connect(G_OBJECT(preview), "configure_event",
		   G_CALLBACK(drawing_area_resize_callback), NULL);
  event_box = gtk_event_box_new ();
  gtk_container_add (GTK_CONTAINER (event_box), GTK_WIDGET (preview));
  gtk_container_add (GTK_CONTAINER (frame), event_box);
  gtk_widget_show (event_box);

  g_signal_connect (G_OBJECT (preview), "expose_event",
		    G_CALLBACK (preview_expose), NULL);
  g_signal_connect (G_OBJECT (preview), "button_press_event",
		    G_CALLBACK (preview_button_callback), NULL);
  g_signal_connect (G_OBJECT (preview), "button_release_event",
		    G_CALLBACK (preview_button_callback), NULL);
  g_signal_connect (G_OBJECT (preview), "motion_notify_event",
		    G_CALLBACK (preview_motion_callback), NULL);
  gtk_widget_show (GTK_WIDGET (preview));

  stpui_set_help_data
    (event_box,
     _("Position the image on the page.\n"
       "Click and drag with the primary button to position the image.\n"
       "Click and drag with the second button to move the image with finer precision; "
       "each unit of motion moves the image one point (1/72\")\n"
       "Click and drag with the third (middle) button to move the image in units of "
       "the image size.\n"
       "Holding down the shift key while clicking and dragging constrains the image to "
       "only horizontal or vertical motion.\n"
       "If you click another button while dragging the mouse, the image will return "
       "to its original position."));

  gtk_widget_set_events (GTK_WIDGET (preview),
                         GDK_EXPOSURE_MASK | GDK_BUTTON_MOTION_MASK |
                         GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK);
}

static GtkWidget *
create_positioning_entry(GtkWidget *table, int hpos, int vpos,
			 const char *text, const char *help)
{
  return stpui_create_entry
    (table, hpos, vpos, text, help, G_CALLBACK(position_callback));
}

static GtkWidget *
create_positioning_button(GtkWidget *box, int invalid,
			  const char *text, const char *help)
{
  GtkWidget *button = gtk_button_new_with_label(gettext(text));
  gtk_box_pack_start(GTK_BOX(box), button, FALSE, TRUE, 0);
  gtk_widget_show(button);
  stpui_set_help_data(button, help);
  g_signal_connect(G_OBJECT(button), "clicked",
		     G_CALLBACK(position_button_callback),
		     (gpointer) invalid);
  return button;
}

static void
create_paper_size_frame(void)
{
  GtkWidget *frame;
  GtkWidget *vbox;
  GtkWidget *media_size_table;
  GtkWidget *table;
  int vpos = 0;

  frame = gtk_frame_new (_("Paper Size"));
  gtk_box_pack_start (GTK_BOX (right_vbox), frame, FALSE, TRUE, 0);
  gtk_widget_show (frame);

  vbox = gtk_vbox_new (FALSE, 2);
  gtk_container_set_border_width (GTK_CONTAINER (vbox), 4);
  gtk_container_add (GTK_CONTAINER (frame), vbox);
  gtk_widget_show (vbox);

  table = gtk_table_new (1, 1, FALSE);
  gtk_container_add (GTK_CONTAINER (vbox), table);
  gtk_widget_show (table);

  /*
   * Media size combo box.
   */

  page_size_table = gtk_table_new(1, 1, FALSE);
  gtk_widget_show (page_size_table);
  gtk_table_attach_defaults(GTK_TABLE(table), page_size_table,
			    0, 2, vpos, vpos + 1);
  vpos++;
  show_all_paper_sizes_button =
    gtk_check_button_new_with_label(_("Show All Paper Sizes"));
  gtk_table_attach_defaults
    (GTK_TABLE(table), show_all_paper_sizes_button, 0, 2, vpos, vpos + 1);
  gtk_toggle_button_set_active
    (GTK_TOGGLE_BUTTON(show_all_paper_sizes_button),
     stpui_show_all_paper_sizes);
  g_signal_connect(G_OBJECT(show_all_paper_sizes_button), "toggled",
		   G_CALLBACK(show_all_paper_sizes_callback), NULL);
  gtk_widget_show(show_all_paper_sizes_button);
  vpos++;

  /*
   * Custom media size entries
   */

  media_size_table = gtk_table_new (1, 1, FALSE);
  stpui_table_attach_aligned(GTK_TABLE (table), 0, vpos++, _("Dimensions:"),
			     0.0, 0.5, media_size_table, 1, TRUE);
  gtk_table_set_col_spacings (GTK_TABLE (media_size_table), 4);

  custom_size_width = stpui_create_entry
    (media_size_table, 0, 3, _("Width:"),
     _("Width of the paper that you wish to print to"),
     G_CALLBACK(custom_media_size_callback));

  custom_size_height = stpui_create_entry
    (media_size_table, 2, 3, _("Height:"),
     _("Height of the paper that you wish to print to"),
     G_CALLBACK(custom_media_size_callback));

  vpos++;
  auto_paper_size_button =
    gtk_check_button_new_with_label(_("Automatic Paper Size"));
  gtk_table_attach_defaults
    (GTK_TABLE(table), auto_paper_size_button, 0, 2, vpos, vpos + 1);
  gtk_toggle_button_set_active
    (GTK_TOGGLE_BUTTON(auto_paper_size_button), FALSE);
  g_signal_connect(G_OBJECT(auto_paper_size_button), "toggled",
		   G_CALLBACK(auto_paper_size_callback), NULL);
}

static void
create_copy_number_frame(void)
{
  GtkWidget *frame;
  GtkWidget *vbox;
  GtkWidget *event_box;
  GtkAdjustment *adj;

  frame = gtk_frame_new (_("Number of Copies"));
  gtk_box_pack_start (GTK_BOX (right_vbox), frame, FALSE, TRUE, 0);
  gtk_widget_show (frame);

  vbox = gtk_hbox_new (FALSE, 2);
  gtk_container_set_border_width (GTK_CONTAINER (vbox), 4);
  gtk_container_add (GTK_CONTAINER (frame), vbox);
  gtk_widget_show (vbox);

  event_box = gtk_event_box_new ();
  gtk_container_add (GTK_CONTAINER (vbox), event_box);
  stpui_set_help_data(event_box,
		      _("Select the number of copies to print; "
			"a value between 1 and 100"));
  gtk_widget_show (event_box);

  /*
   * Number of Copies Spin Button
   */

  adj = (GtkAdjustment *) gtk_adjustment_new (1.0f, 1.0f, 100.0f,
					      1.0f, 5.0f, 0.0f);
  copy_count_spin_button = gtk_spin_button_new (adj, 0, 0);
  gtk_spin_button_set_wrap (GTK_SPIN_BUTTON (copy_count_spin_button), FALSE);
  gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (copy_count_spin_button), TRUE);
  gtk_spin_button_set_update_policy (GTK_SPIN_BUTTON (copy_count_spin_button), 
				     GTK_UPDATE_IF_VALID);

  g_signal_connect(G_OBJECT (adj), "value_changed",
		   G_CALLBACK (copy_count_callback),
		   NULL);

  gtk_container_add (GTK_CONTAINER (event_box), copy_count_spin_button);
  gtk_widget_show(copy_count_spin_button);
}

static void
create_positioning_frame (void)
{
  GtkWidget *frame;
  GtkWidget *table;
  GtkWidget *box;
  GtkWidget *sep;

  frame = gtk_frame_new (_("Image Position"));
  gtk_box_pack_start (GTK_BOX (right_vbox), frame, FALSE, TRUE, 0);
  gtk_widget_show (frame);

  table = gtk_table_new (1, 1, FALSE);
  gtk_table_set_col_spacings (GTK_TABLE (table), 2);
  gtk_table_set_row_spacings (GTK_TABLE (table), 2);
  gtk_container_set_border_width (GTK_CONTAINER (table), 4);
  gtk_container_add (GTK_CONTAINER (frame), table);
  gtk_widget_show (table);

  /*
   * Orientation option menu.
   */

  orientation_menu =
    stpui_option_menu_new (FALSE,
			   _("Auto"), orientation_callback,
			   (gpointer) ORIENT_AUTO, NULL, NULL, 0,
			   _("Portrait"), orientation_callback,
			   (gpointer) ORIENT_PORTRAIT, NULL, NULL, 0,
			   _("Landscape"), orientation_callback,
			   (gpointer) ORIENT_LANDSCAPE, NULL, NULL, 0,
			   _("Upside down"), orientation_callback,
			   (gpointer) ORIENT_UPSIDEDOWN, NULL, NULL, 0,
			   _("Seascape"), orientation_callback,
			   (gpointer) ORIENT_SEASCAPE, NULL, NULL, 0,
			   NULL);
  stpui_set_help_data (orientation_menu,
		 _("Select the orientation: portrait, landscape, "
		   "upside down, or seascape (upside down landscape)"));
  stpui_table_attach_aligned (GTK_TABLE (table), 0, 0, _("Orientation:"),
			      1.0, 0.5, orientation_menu, 4, TRUE);
  sep = gtk_hseparator_new ();
  gtk_table_attach_defaults (GTK_TABLE (table), sep, 0, 6, 1, 2);
  gtk_widget_show (sep);

  /*
   * Position entries
   */

  left_entry = create_positioning_entry
    (table, 0, 2, _("Left:"),
     _("Distance from the left of the paper to the image"));
#if 0
  right_entry = create_positioning_entry
    (table, 0, 3, _("Right:"),
     _("Distance from the left of the paper to the right of the image"));
#endif
  right_border_entry = create_positioning_entry
    (table, 0, 4, _("Right:"),
     _("Distance from the right of the paper to the image"));
  top_entry = create_positioning_entry
    (table, 3, 2, _("Top:"),
     _("Distance from the top of the paper to the image"));
#if 0
  bottom_entry = create_positioning_entry
    (table, 3, 3, _("Bottom:"),
     _("Distance from the top of the paper to bottom of the image"));
#endif
  bottom_border_entry = create_positioning_entry
    (table, 3, 4, _("Bottom:"),
     _("Distance from the bottom of the paper to the image"));
  /*
   * Center options
   */

  sep = gtk_hseparator_new ();
  gtk_table_attach_defaults (GTK_TABLE (table), sep, 0, 6, 5, 6);
  gtk_widget_show (sep);

  box = gtk_hbox_new (TRUE, 4);
  stpui_table_attach_aligned (GTK_TABLE (table), 0, 7, _("Center:"), 0.5, 0.5,
			      box, 5, TRUE);
  recenter_horizontal_button = create_positioning_button
    (box, INVALID_LEFT, _("Horizontal"),
     _("Center the image horizontally on the paper"));
  recenter_button = create_positioning_button
    (box, INVALID_LEFT | INVALID_TOP, _("Both"),
     _("Center the image on the paper"));
  recenter_vertical_button = create_positioning_button
    (box, INVALID_TOP, _("Vertical"),
     _("Center the image vertically on the paper"));
}

static void
create_printer_dialog (void)
{
  GtkWidget *table;
  GtkWidget *label;
  GtkWidget *event_box;
  GSList    *group;
  gint       i;
  stp_string_list_t *manufacturer_list = stp_string_list_create();

  setup_dialog = stpui_dialog_new(_("Setup Printer"),
				  GTK_WIN_POS_MOUSE, TRUE,
				  _("OK"), setup_ok_callback,
				  NULL, NULL, NULL, TRUE, FALSE,
				  _("Cancel"), setup_cancel_callback,
				  NULL, (GObject *) 1, NULL, FALSE, TRUE,
				  NULL);

  /*
   * Top-level table for dialog.
   */

  table = gtk_table_new (4, 4, FALSE);
  gtk_container_set_border_width (GTK_CONTAINER (table), 6);
  gtk_table_set_col_spacings (GTK_TABLE (table), 4);
  gtk_table_set_row_spacing (GTK_TABLE (table), 0, 150);
  gtk_box_pack_start (GTK_BOX (GTK_DIALOG (setup_dialog)->vbox), table,
                      TRUE, TRUE, 0);
  gtk_widget_show (table);

  /*
   * Printer driver option menu.
   */

  label = gtk_label_new (_("Printer Make:"));
  gtk_misc_set_alignment (GTK_MISC (label), 1.0, 0.5);
  gtk_table_attach (GTK_TABLE (table), label, 1, 2, 0, 2,
		    GTK_FILL, GTK_FILL, 0, 0);
  gtk_widget_show (label);

  event_box = gtk_event_box_new ();
  gtk_table_attach (GTK_TABLE (table), event_box, 2, 4, 0, 2,
                    GTK_FILL | GTK_EXPAND, GTK_FILL | GTK_EXPAND, 0, 0);
  gtk_widget_show (event_box);

  stpui_set_help_data (event_box, _("Select the make of your printer"));

  manufacturer_crawler = gtk_scrolled_window_new (NULL, NULL);
  gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (manufacturer_crawler),
                                  GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
  gtk_container_add (GTK_CONTAINER (event_box), manufacturer_crawler);
  gtk_widget_show (manufacturer_crawler);

  manufacturer_clist = gtk_clist_new (1);
  gtk_widget_set_usize (manufacturer_clist, 200, 0);
  gtk_clist_set_selection_mode(GTK_CLIST(manufacturer_clist),GTK_SELECTION_SINGLE);
  gtk_container_add (GTK_CONTAINER (manufacturer_crawler), manufacturer_clist);
  gtk_widget_show (manufacturer_clist);

  g_signal_connect (G_OBJECT (manufacturer_clist), "select_row",
		    G_CALLBACK (manufacturer_callback), NULL);


  label = gtk_label_new (_("Printer Model:"));
  gtk_misc_set_alignment (GTK_MISC (label), 1.0, 0.5);
  gtk_table_attach (GTK_TABLE (table), label, 4, 5, 0, 2,
		    GTK_FILL, GTK_FILL, 0, 0);
  gtk_widget_show (label);

  event_box = gtk_event_box_new ();
  gtk_table_attach (GTK_TABLE (table), event_box, 5, 7, 0, 2,
                    GTK_FILL | GTK_EXPAND, GTK_FILL | GTK_EXPAND, 0, 0);
  gtk_widget_show (event_box);

  stpui_set_help_data (event_box, _("Select your printer model"));

  printer_crawler = gtk_scrolled_window_new (NULL, NULL);
  gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (printer_crawler),
                                  GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
  gtk_container_add (GTK_CONTAINER (event_box), printer_crawler);
  gtk_widget_show (printer_crawler);

  printer_driver = gtk_clist_new (1);
  gtk_widget_set_usize (printer_driver, 200, 0);
  gtk_clist_set_selection_mode(GTK_CLIST(printer_driver),GTK_SELECTION_SINGLE);
  gtk_container_add (GTK_CONTAINER (printer_crawler), printer_driver);
  gtk_widget_show (printer_driver);

  g_signal_connect (G_OBJECT (printer_driver), "select_row",
		    G_CALLBACK (print_driver_callback), NULL);


  for (i = 0; i < stp_printer_model_count (); i ++)
    {
      const stp_printer_t *the_printer = stp_get_printer_by_index (i);

      if (strcmp(stp_printer_get_long_name (the_printer), "") != 0 &&
	  strcmp(stp_printer_get_family(the_printer), "raw") != 0)
	{
	  const gchar *make = stp_printer_get_manufacturer(the_printer);
	  if (! stp_string_list_is_present(manufacturer_list, make))
	    stp_string_list_add_string(manufacturer_list, make, make);
	}
    }

  for (i = 0; i < stp_string_list_count(manufacturer_list); i++)
    {
      const stp_param_string_t *param =
	stp_string_list_param(manufacturer_list, i);
      gchar *xname = g_strdup(param->name);
      gtk_clist_insert(GTK_CLIST(manufacturer_clist), i, &xname);
      gtk_clist_set_row_data_full(GTK_CLIST(manufacturer_clist), i, xname,
				  g_free);
    }
  stp_string_list_destroy(manufacturer_list);
  gtk_clist_sort(GTK_CLIST(manufacturer_clist));
  build_printer_driver_clist();

  /*
   * PPD file.
   */

  ppd_label = gtk_label_new (_("PPD File:"));
  gtk_misc_set_alignment (GTK_MISC (ppd_label), 1.0, 0.5);
  gtk_table_attach (GTK_TABLE (table), ppd_label, 1, 2, 3, 4,
                    GTK_FILL, GTK_FILL, 0, 0);
  gtk_widget_show (ppd_label);

  ppd_box = gtk_hbox_new (FALSE, 8);
  gtk_table_attach (GTK_TABLE (table), ppd_box, 2, 7, 3, 4,
                    GTK_FILL, GTK_FILL, 0, 0);

  ppd_file = gtk_entry_new ();
  g_signal_connect(G_OBJECT(ppd_file), "activate",
		   G_CALLBACK(ppd_file_callback), NULL);
  gtk_box_pack_start (GTK_BOX (ppd_box), ppd_file, TRUE, TRUE, 0);
  gtk_widget_show (ppd_file);

  stpui_set_help_data(ppd_file,_("Enter the correct PPD filename for your printer"));

  ppd_button = gtk_button_new_with_label (_("Browse"));
  gtk_misc_set_padding (GTK_MISC (GTK_BIN (ppd_button)->child), 2, 0);
  gtk_box_pack_start (GTK_BOX (ppd_box), ppd_button, FALSE, FALSE, 0);
  gtk_widget_show (ppd_button);
  gtk_widget_show (ppd_box);

  stpui_set_help_data(ppd_button,
		_("Choose the correct PPD filename for your printer"));
  g_signal_connect (G_OBJECT (ppd_button), "clicked",
		    G_CALLBACK (ppd_browse_callback), NULL);

  ppd_model_label = gtk_label_new (_("Printer Model:"));
  gtk_misc_set_alignment (GTK_MISC (ppd_model_label), 1.0, 0.5);
  gtk_table_attach (GTK_TABLE (table), ppd_model_label, 1, 2, 4, 5,
                    GTK_FILL, GTK_FILL, 0, 0);
  gtk_widget_show (ppd_model_label);

  ppd_model = gtk_label_new ("");
  gtk_misc_set_alignment (GTK_MISC (ppd_model), 0.0, 0.5);
  gtk_table_attach (GTK_TABLE (table), ppd_model, 2, 7, 4, 5,
                    GTK_FILL, GTK_FILL, 0, 0);
  gtk_widget_show (ppd_model);


  /*
   * Print command.
   */

  group = NULL;
  for (i = 0; i < command_options_count; i++)
    group = stpui_create_radio_button(&(command_options[i]), group, table,
				      0, i > 0 ? i + 6 : i + 5,
				      G_CALLBACK(command_type_callback));

  standard_cmd_entry = gtk_entry_new();
  gtk_table_attach (GTK_TABLE (table), standard_cmd_entry, 2, 7, 6, 7,
                    GTK_FILL, GTK_FILL, 0, 0);
  gtk_entry_set_editable(GTK_ENTRY(standard_cmd_entry), FALSE);
  gtk_widget_set_sensitive(standard_cmd_entry, FALSE);
  gtk_widget_show (standard_cmd_entry);

  queue_combo = gtk_combo_new ();
  event_box = gtk_event_box_new ();
  gtk_container_add (GTK_CONTAINER (event_box), queue_combo);
  gtk_widget_show (queue_combo);
  gtk_widget_show (event_box);
  build_queue_combo();

  stpui_set_help_data(event_box,
		      _("Select the name of the output queue (not the type, "
			"or model, of printer) that you wish to print to"));
  label = gtk_label_new(_("Printer Queue:"));
  gtk_widget_show(label);
  gtk_table_attach (GTK_TABLE (table), label, 2, 3, 5, 6,
                    GTK_FILL, GTK_FILL, 0, 0);
  gtk_table_attach (GTK_TABLE (table), event_box, 3, 7, 5, 6,
                    GTK_FILL, GTK_FILL, 0, 0);

  custom_command_entry = gtk_entry_new ();
  gtk_table_attach (GTK_TABLE (table), custom_command_entry, 2, 7, 7, 8,
                    GTK_FILL, GTK_FILL, 0, 0);
  g_signal_connect(G_OBJECT(custom_command_entry), "activate",
		   G_CALLBACK(setup_callback), NULL);
  gtk_widget_set_sensitive(custom_command_entry, FALSE);
  gtk_widget_show (custom_command_entry);

  stpui_set_help_data
    (custom_command_entry, _("Enter the correct command to print to your printer. "));

  file_entry = gtk_entry_new ();
  gtk_table_attach (GTK_TABLE (table), file_entry, 2, 6, 8, 9,
                    GTK_FILL, GTK_FILL, 0, 0);
  g_signal_connect(G_OBJECT(file_entry), "activate",
		   G_CALLBACK(setup_callback), NULL);
  gtk_widget_show (file_entry);

  gtk_widget_set_sensitive(file_entry, FALSE);
  stpui_set_help_data
    (file_entry, _("Enter the file to print to. "));

  file_button = gtk_button_new_with_label (_("Browse"));

  gtk_table_attach (GTK_TABLE (table), file_button, 6, 7, 8, 9,
                    GTK_FILL, GTK_FILL, 0, 0);
  gtk_widget_show (file_button);

  stpui_set_help_data(file_button, _("File to print to"));
  g_signal_connect (G_OBJECT (file_button), "clicked",
		    G_CALLBACK (file_browse_callback), NULL);

  /*
   * Output file selection dialog.
   */

  file_browser = gtk_file_selection_new (_("Print To File"));

  g_signal_connect
    (G_OBJECT (GTK_FILE_SELECTION (file_browser)->ok_button), "clicked",
     G_CALLBACK (file_ok_callback), NULL);
  g_signal_connect
    (G_OBJECT (GTK_FILE_SELECTION (file_browser)->cancel_button), "clicked",
     G_CALLBACK (file_cancel_callback), NULL);

  /*
   * PPD file selection dialog.
   */

  ppd_browser = gtk_file_selection_new (_("PPD File"));
  gtk_file_selection_hide_fileop_buttons (GTK_FILE_SELECTION (ppd_browser));

  g_signal_connect
    (G_OBJECT (GTK_FILE_SELECTION (ppd_browser)->ok_button), "clicked",
     G_CALLBACK (ppd_ok_callback), NULL);
  g_signal_connect_object
    (G_OBJECT (GTK_FILE_SELECTION (ppd_browser)->cancel_button), "clicked",
     G_CALLBACK (gtk_widget_hide), G_OBJECT (ppd_browser), G_CONNECT_SWAPPED);
}

static void
create_new_printer_dialog (void)
{
  GtkWidget *table;

  new_printer_dialog =
    stpui_dialog_new (_("Define New Printer"),
		      GTK_WIN_POS_MOUSE, FALSE,
		      _("OK"), new_printer_ok_callback,
		      NULL, NULL, NULL, TRUE, FALSE,
		      _("Cancel"), gtk_widget_hide,
		      NULL, (GObject *) 1, NULL, FALSE, TRUE,
		      NULL);

  table = gtk_table_new (1, 1, FALSE);
  gtk_container_set_border_width (GTK_CONTAINER (table), 6);
  gtk_table_set_col_spacings (GTK_TABLE (table), 4);
  gtk_table_set_row_spacings (GTK_TABLE (table), 8);
  gtk_box_pack_start (GTK_BOX (GTK_DIALOG (new_printer_dialog)->vbox), table,
                      FALSE, FALSE, 0);
  gtk_widget_show (table);

  new_printer_entry = gtk_entry_new ();
  gtk_entry_set_max_length (GTK_ENTRY (new_printer_entry), 127);
  stpui_table_attach_aligned(GTK_TABLE (table), 0, 0, _("Printer Name:"), 1.0,
			     0.5, new_printer_entry, 1, TRUE);

  stpui_set_help_data(new_printer_entry,
		_("Enter the name you wish to give this logical printer"));
  g_signal_connect (G_OBJECT (new_printer_entry), "activate",
		    G_CALLBACK (new_printer_ok_callback), NULL);
}

static void
create_about_dialog (void)
{
  GtkWidget *label;
  about_dialog =
    stpui_dialog_new (_("About Gutenprint " PLUG_IN_VERSION),
		      GTK_WIN_POS_MOUSE, FALSE,
		      _("OK"), gtk_widget_hide,
		      NULL, (GObject *) 1, NULL, TRUE, TRUE,
		      NULL);

  label = gtk_label_new
    (_("Gutenprint Version " PLUG_IN_VERSION "\n"
       "\n"
       "Copyright (C) 1997-2003 Michael Sweet, Robert Krawitz,\n"
       "and the rest of the Gutenprint Development Team.\n"
       "\n"
       "Please visit our web site at http://gimp-print.sourceforge.net.\n"
       "\n"
       "This program is free software; you can redistribute it and/or modify\n"
       "it under the terms of the GNU General Public License as published by\n"
       "the Free Software Foundation; either version 2 of the License, or\n"
       "(at your option) any later version.\n"
       "\n"
       "This program is distributed in the hope that it will be useful,\n"
       "but WITHOUT ANY WARRANTY; without even the implied warranty of\n"
       "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n"
       "GNU General Public License for more details.\n"
       "\n"
       "You should have received a copy of the GNU General Public License\n"
       "along with this program; if not, write to the Free Software\n"
       "Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  "
       "USA\n"));

  gtk_misc_set_padding (GTK_MISC (label), 12, 4);
  gtk_box_pack_start (GTK_BOX (GTK_DIALOG (about_dialog)->vbox), label,
                      FALSE, FALSE, 0);
  gtk_widget_show (label);
}

static void
create_printer_settings_frame (void)
{
  GtkWidget *table;
  GtkWidget *sep;
  GtkWidget *printer_hbox;
  GtkWidget *button;
  GtkWidget *event_box;
  GtkWidget *scrolled_window;
  gint vpos = 0;

  create_printer_dialog ();
  create_about_dialog ();
  create_new_printer_dialog ();

  table = gtk_table_new (1, 1, FALSE);
  gtk_table_set_col_spacings (GTK_TABLE (table), 2);
  gtk_table_set_row_spacings (GTK_TABLE (table), 2);
  gtk_container_set_border_width (GTK_CONTAINER (table), 4);
  gtk_notebook_append_page (GTK_NOTEBOOK (notebook), table,
                            gtk_label_new (_("Printer Settings")));
  gtk_widget_show (table);

  /*
   * Printer option menu.
   */

  printer_combo = gtk_combo_new ();
  event_box = gtk_event_box_new ();
  gtk_container_add (GTK_CONTAINER (event_box), printer_combo);
  gtk_widget_show (printer_combo);

  stpui_set_help_data(event_box,
		_("Select the name of the printer (not the type, "
		  "or model, of printer) that you wish to print to"));
  stpui_table_attach_aligned(GTK_TABLE (table), 0, vpos++, _("Printer Name:"),
			     0.0, 0.5, event_box, 1, TRUE);
  printer_model_label = gtk_label_new ("");
  stpui_table_attach_aligned(GTK_TABLE (table), 0, vpos++, _("Printer Model:"),
			     0.0, 0.0, printer_model_label, 1, TRUE);
  printer_hbox = gtk_hbox_new (TRUE, 4);
  gtk_table_attach (GTK_TABLE (table), printer_hbox,
		    1, 4, vpos, vpos + 1, GTK_EXPAND|GTK_FILL, GTK_FILL, 0, 0);
  vpos += 2;
  gtk_widget_show (printer_hbox);

  /*
   * Setup printer button
   */

  button = gtk_button_new_with_label (_("Setup Printer..."));
  stpui_set_help_data(button,
		      _("Choose the printer model, PPD file, and command "
			"that is used to print to this printer"));
  gtk_misc_set_padding (GTK_MISC (GTK_BIN (button)->child), 2, 0);
  gtk_box_pack_start (GTK_BOX (printer_hbox), button, FALSE, TRUE, 0);
  gtk_widget_show (button);

  g_signal_connect (G_OBJECT (button), "clicked",
		    G_CALLBACK (setup_open_callback), NULL);

  /*
   * New printer button
   */

  button = gtk_button_new_with_label (_("New Printer..."));
  stpui_set_help_data (button, _("Define a new logical printer. This can be used to "
			   "name a collection of settings that you wish to "
			   "remember for future use."));
  gtk_box_pack_start (GTK_BOX (printer_hbox), button, FALSE, TRUE, 0);
  gtk_widget_show (button);

  g_signal_connect (G_OBJECT (button), "clicked",
		    G_CALLBACK (new_printer_open_callback), NULL);

  sep = gtk_hseparator_new ();
  gtk_table_attach (GTK_TABLE (table), sep, 0, 5, vpos, vpos + 1,
		    GTK_EXPAND|GTK_FILL, GTK_FILL, 0, 0);
  gtk_widget_show (sep);
  vpos++;

  printer_features_table = gtk_table_new(1, 1, FALSE);
  gtk_table_set_col_spacings (GTK_TABLE (printer_features_table), 2);
  gtk_table_set_row_spacings (GTK_TABLE (printer_features_table), 0);
  gtk_widget_show (printer_features_table);

  scrolled_window = gtk_scrolled_window_new(NULL, NULL);
  gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled_window),
				 GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
  gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(scrolled_window),
					printer_features_table);
  gtk_table_attach_defaults(GTK_TABLE(table), scrolled_window,
			    0, 6, vpos, vpos + 1);
  gtk_widget_show(scrolled_window);
}

static void
create_scaling_frame (void)
{
  GtkWidget *frame;
  GtkWidget *vbox;
  GtkWidget *hbox;
  GtkWidget *table;
  GtkWidget *box;
  GtkWidget *label;
  GtkWidget *event_box;
  GtkWidget *sep;
  GSList    *group;

  frame = gtk_frame_new (_("Image Size"));
  gtk_box_pack_start (GTK_BOX (main_vbox), frame, FALSE, FALSE, 0);
  gtk_widget_show (frame);

  hbox = gtk_hbox_new (FALSE, 2);
  gtk_container_set_border_width (GTK_CONTAINER (hbox), 4);
  gtk_container_add (GTK_CONTAINER (frame), hbox);
  gtk_widget_show (hbox);

  vbox = gtk_vbox_new (FALSE, 2);
/*  gtk_container_set_border_width (GTK_CONTAINER (vbox), 4); */
  gtk_container_add (GTK_CONTAINER (hbox), vbox);
  gtk_widget_show (vbox);

  table = gtk_table_new (1, 1, FALSE);
  gtk_table_set_col_spacings (GTK_TABLE (table), 4);
  gtk_box_pack_start (GTK_BOX (vbox), table, FALSE, FALSE, 0);
  gtk_widget_show (table);

  /*
   * Create the scaling adjustment using percent.  It doesn't really matter,
   * since as soon as we call plist_callback at the end of initialization
   * everything will be put right.
   */
  scaling_adjustment =
    stpui_scale_entry_new (GTK_TABLE (table), 0, 0, _("Scaling:"), 100, 75,
			   100.0, minimum_image_percent, 100.0,
			   1.0, 10.0, 1, TRUE, 0, 0, NULL);
  stpui_set_adjustment_tooltip(scaling_adjustment,
			       _("Set the scale (size) of the image"));
  g_signal_connect (G_OBJECT (scaling_adjustment), "value_changed",
		    G_CALLBACK (scaling_update), NULL);

  box = gtk_hbox_new (FALSE, 4);
  gtk_box_pack_start (GTK_BOX (vbox), box, TRUE, TRUE, 0);
  gtk_widget_show (box);

  /*
   * The scale by percent/ppi toggles
   */

  table = gtk_table_new (1, 1, FALSE);
  gtk_table_set_col_spacings (GTK_TABLE (table), 4);
  gtk_box_pack_start (GTK_BOX (box), table, FALSE, FALSE, 0);
  gtk_widget_show (table);

  event_box = gtk_event_box_new ();
  gtk_table_attach (GTK_TABLE (table), event_box, 0, 1, 0, 1,
		    GTK_FILL, GTK_FILL, 0, 0);
  gtk_widget_show (event_box);

  label = gtk_label_new (_("Scale by:"));
  gtk_container_add (GTK_CONTAINER (event_box), label);
  gtk_widget_show (label);

  stpui_set_help_data(event_box,
		_("Select whether scaling is measured as percent of "
		  "available page size or number of output dots per inch"));

  scaling_percent = gtk_radio_button_new_with_label (NULL, _("Percent"));
  group = gtk_radio_button_group (GTK_RADIO_BUTTON (scaling_percent));
  stpui_table_attach_aligned(GTK_TABLE (table), 0, 0, NULL, 0.5, 0.5,
			     scaling_percent, 2, TRUE);

  stpui_set_help_data(scaling_percent, _("Scale the print to the size of the page"));
  g_signal_connect (G_OBJECT (scaling_percent), "toggled",
		    G_CALLBACK (scaling_callback), NULL);

  scaling_ppi = gtk_radio_button_new_with_label (group, _("PPI"));
  stpui_table_attach_aligned(GTK_TABLE (table), 2, 0, NULL, 0.5, 0.5,
			     scaling_ppi, 1, TRUE);

  stpui_set_help_data(scaling_ppi,
		_("Scale the print to the number of dots per inch"));
  g_signal_connect (G_OBJECT (scaling_ppi), "toggled",
		    G_CALLBACK (scaling_callback), NULL);

  sep = gtk_vseparator_new ();
  gtk_box_pack_start (GTK_BOX (hbox), sep, FALSE, FALSE, 8);
  gtk_widget_show (sep);

  /*
   * The width/height enries
   */

  table = gtk_table_new (1, 1, FALSE);
  gtk_table_set_col_spacings (GTK_TABLE (table), 2);
  gtk_box_pack_start (GTK_BOX (hbox), table, FALSE, FALSE, 0);
  gtk_widget_show (table);

  width_entry = create_positioning_entry
    (table, 0, 0, _("Width:"), _("Set the width of the print"));
  height_entry = create_positioning_entry
    (table, 0, 1, _("Height:"), _("Set the height of the print"));

  /*
   * The "image size" button
   */

  scaling_image = gtk_button_new_with_label (_("Use Original\nImage Size"));
  gtk_misc_set_padding (GTK_MISC (GTK_BIN (scaling_image)->child), 2, 2);
  gtk_box_pack_end (GTK_BOX (hbox), scaling_image, FALSE, FALSE, 0);
  gtk_widget_show (scaling_image);

  stpui_set_help_data(scaling_image,
		_("Set the print size to the size of the image"));
  g_signal_connect (G_OBJECT (scaling_image), "clicked",
		    G_CALLBACK (scaling_callback), NULL);
}

/*
 * create_color_adjust_window (void)
 *
 * NOTES:
 *   creates the color adjuster popup, allowing the user to adjust brightness,
 *   contrast, saturation, etc.
 */
static void
create_color_adjust_window (void)
{
  GtkWidget *table;
  GtkWidget *label;
  GtkWidget *event_box;
  GtkWidget *scrolled_window;
  gint x, y; /* Window dimensions */

  initialize_thumbnail();

  color_adjust_dialog =
    stpui_dialog_new(_("Print Color Adjust"),
		     GTK_WIN_POS_MOUSE, TRUE,

		     _("Set Defaults"), set_color_defaults,
		     NULL, NULL, NULL, FALSE, FALSE,
		     _("Close"), gtk_widget_hide,
		     NULL, (GObject *) 1, NULL, TRUE, TRUE,
		     NULL);

  table = gtk_table_new (1, 1, FALSE);
  gtk_container_set_border_width (GTK_CONTAINER (table), 6);
  gtk_table_set_col_spacings (GTK_TABLE (table), 4);
  gtk_table_set_row_spacings (GTK_TABLE (table), 0);
/*  gtk_table_set_row_spacing (GTK_TABLE (table), 8, 6); */

  gtk_box_pack_start (GTK_BOX (GTK_DIALOG (color_adjust_dialog)->vbox),
		      table, TRUE, TRUE, 0);
  gtk_widget_show (table);

  /*
   * Drawing area for color swatch feedback display...
   */

  event_box = gtk_event_box_new ();
  gtk_widget_show (GTK_WIDGET (event_box));
  gtk_table_attach (GTK_TABLE (table), GTK_WIDGET (event_box),
                    0, 1, 0, 1, 0, 0, 0, 0);

  swatch = (GtkDrawingArea *) gtk_drawing_area_new ();
  gtk_widget_set_events (GTK_WIDGET (swatch), GDK_EXPOSURE_MASK);
  gtk_drawing_area_size (swatch, thumbnail_w, thumbnail_h);
  gtk_container_add (GTK_CONTAINER (event_box), GTK_WIDGET (swatch));
  gtk_widget_show (GTK_WIDGET (swatch));

  stpui_set_help_data (GTK_WIDGET (event_box), _("Image preview"));
  g_signal_connect (G_OBJECT (swatch), "expose_event",
		    G_CALLBACK (redraw_color_swatch),
		    NULL);

  event_box = gtk_event_box_new ();
  gtk_widget_show (GTK_WIDGET (event_box));
  gtk_table_attach (GTK_TABLE (table), GTK_WIDGET (event_box),
                    1, 2, 0, 1, 0, 0, 0, 0);

  output_color_vbox = gtk_vbox_new(TRUE, 0);
  gtk_container_add(GTK_CONTAINER(event_box), output_color_vbox);
  gtk_widget_show(GTK_WIDGET(output_color_vbox));

  label = gtk_label_new(_("View Output Channels:"));
  gtk_box_pack_start(GTK_BOX(output_color_vbox), label, TRUE, TRUE, 0);
  gtk_widget_show(GTK_WIDGET(label));

  cyan_button = gtk_toggle_button_new_with_label(_("Cyan"));
  gtk_box_pack_start(GTK_BOX(output_color_vbox), cyan_button, TRUE, TRUE, 0);
  gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(cyan_button), TRUE);
  gtk_widget_show(GTK_WIDGET(cyan_button));
  g_signal_connect (G_OBJECT (cyan_button), "toggled",
		    G_CALLBACK (color_button_callback), NULL);

  magenta_button = gtk_toggle_button_new_with_label(_("Magenta"));
  gtk_box_pack_start(GTK_BOX(output_color_vbox), magenta_button, TRUE, TRUE,0);
  gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(magenta_button), TRUE);
  gtk_widget_show(GTK_WIDGET(magenta_button));
  g_signal_connect (G_OBJECT (magenta_button), "toggled",
		    G_CALLBACK (color_button_callback), NULL);

  yellow_button = gtk_toggle_button_new_with_label(_("Yellow"));
  gtk_box_pack_start(GTK_BOX(output_color_vbox), yellow_button, TRUE, TRUE, 0);
  gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(yellow_button), TRUE);
  gtk_widget_show(GTK_WIDGET(yellow_button));
  g_signal_connect (G_OBJECT (yellow_button), "toggled",
		    G_CALLBACK (color_button_callback), NULL);

  black_button = gtk_toggle_button_new_with_label(_("Black"));
  gtk_box_pack_start(GTK_BOX(output_color_vbox), black_button, TRUE, TRUE, 0);
  gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(black_button), TRUE);
  gtk_widget_show(GTK_WIDGET(black_button));
  g_signal_connect (G_OBJECT (black_button), "toggled",
		    G_CALLBACK (color_button_callback), NULL);

  red_button = gtk_toggle_button_new_with_label(_("Red"));
  gtk_box_pack_start(GTK_BOX(output_color_vbox), red_button, TRUE, TRUE, 0);
  gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(red_button), TRUE);
  gtk_widget_show(GTK_WIDGET(red_button));
  g_signal_connect (G_OBJECT (red_button), "toggled",
		    G_CALLBACK (color_button_callback), NULL);

  green_button = gtk_toggle_button_new_with_label(_("Green"));
  gtk_box_pack_start(GTK_BOX(output_color_vbox), green_button, TRUE, TRUE,0);
  gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(green_button), TRUE);
  gtk_widget_show(GTK_WIDGET(green_button));
  g_signal_connect (G_OBJECT (green_button), "toggled",
		    G_CALLBACK (color_button_callback), NULL);

  blue_button = gtk_toggle_button_new_with_label(_("Blue"));
  gtk_box_pack_start(GTK_BOX(output_color_vbox), blue_button, TRUE, TRUE, 0);
  gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(blue_button), TRUE);
  gtk_widget_show(GTK_WIDGET(blue_button));
  g_signal_connect (G_OBJECT (blue_button), "toggled",
		    G_CALLBACK (color_button_callback), NULL);

  color_adjustment_table = gtk_table_new(1, 1, FALSE);
  gtk_table_set_col_spacings (GTK_TABLE (color_adjustment_table), 2);
  gtk_table_set_row_spacings (GTK_TABLE (color_adjustment_table), 0);
  gtk_container_set_border_width (GTK_CONTAINER (color_adjustment_table), 4);
  gtk_widget_show (color_adjustment_table);

  scrolled_window = gtk_scrolled_window_new(NULL, NULL);
  gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled_window),
				 GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
  gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(scrolled_window),
					color_adjustment_table);
  gtk_table_attach_defaults(GTK_TABLE(table), scrolled_window,
			    0, 2, 1, 2);
  gtk_widget_show(scrolled_window);

  /* The initial size request does not account for the
     GtkScrolledWindow. */
  gtk_window_get_size(GTK_WINDOW(color_adjust_dialog), &x, &y);
  gtk_window_set_default_size(GTK_WINDOW(color_adjust_dialog), x, y+300);
}

static void
create_image_settings_frame (void)
{
  GtkWidget *vbox;
  GtkWidget *hbox;
  GtkWidget *table;
  GtkWidget *label;
  GtkWidget *event_box;
  GtkWidget *sep;
  GSList    *group;
  gint i;

  create_color_adjust_window ();

  vbox = gtk_vbox_new (FALSE, 4);
  gtk_container_set_border_width (GTK_CONTAINER (vbox), 4);
  gtk_notebook_append_page (GTK_NOTEBOOK (notebook), vbox,
                            gtk_label_new (_("Output")));
  gtk_widget_show (vbox);

  table = gtk_table_new (1, 1, FALSE);
  gtk_table_set_col_spacings (GTK_TABLE (table), 4);
  gtk_box_pack_start (GTK_BOX (vbox), table, FALSE, FALSE, 0);
  gtk_widget_show (table);

  event_box = gtk_event_box_new ();
  gtk_table_attach (GTK_TABLE (table), event_box, 0, 1, 0, 1,
                    GTK_FILL, GTK_FILL, 0, 0);
  gtk_widget_show (event_box);

  /*
   * Output type toggles.
   */

  table = gtk_table_new (1, 1, FALSE);
  gtk_table_set_col_spacings (GTK_TABLE (table), 4);
/*  gtk_table_set_row_spacing (GTK_TABLE (table), 2, 4); */
  gtk_box_pack_start (GTK_BOX (vbox), table, FALSE, FALSE, 0);
  gtk_widget_show (table);

  event_box = gtk_event_box_new ();
  gtk_table_attach (GTK_TABLE (table), event_box, 0, 1, 0, 1,
                    GTK_FILL, GTK_FILL, 0, 0);
  gtk_widget_show (event_box);

  label = gtk_label_new (_("Output Type:"));
  gtk_container_add (GTK_CONTAINER (event_box), label);
  gtk_widget_show (label);

  stpui_set_help_data(event_box, _("Select the desired output type"));

  group = NULL;
  for (i = 0; i < output_type_count; i++)
    group = stpui_create_radio_button(&(output_types[i]), group, table, 0, i,
				      G_CALLBACK(output_type_callback));

  sep = gtk_hseparator_new ();
  gtk_box_pack_start (GTK_BOX (vbox), sep, FALSE, FALSE, 0);
  gtk_widget_show (sep);

  /*
   *  Color adjust button
   */
  hbox = gtk_hbox_new (FALSE, 4);
  gtk_box_pack_start (GTK_BOX (vbox), hbox, TRUE, TRUE, 0);
  gtk_widget_show(hbox);
  label = gtk_label_new("");
  gtk_box_pack_start(GTK_BOX(hbox), label, TRUE, TRUE, 0);
  gtk_widget_show(label);

  adjust_color_button = gtk_button_new_with_label (_("Adjust Output..."));
  gtk_misc_set_padding (GTK_MISC (GTK_BIN (adjust_color_button)->child), 4, 0);
  gtk_box_pack_start (GTK_BOX (hbox), adjust_color_button, FALSE, FALSE, 0);
  gtk_widget_show(adjust_color_button);
  label = gtk_label_new("");
  gtk_box_pack_end(GTK_BOX(hbox), label, TRUE, TRUE, 0);
  gtk_widget_show(label);

  stpui_set_help_data(adjust_color_button,
		_("Adjust color balance, brightness, contrast, "
		  "saturation, and dither algorithm"));
  g_signal_connect_object (G_OBJECT (adjust_color_button), "clicked",
			   G_CALLBACK (gtk_widget_show),
			   G_OBJECT (color_adjust_dialog),
			   G_CONNECT_SWAPPED);
}

static void
create_units_frame (void)
{
  GtkWidget *vbox;
  GtkWidget *table;
  GtkWidget *label;
  GtkWidget *event_box;
  GSList    *group;
  gint i;

  units_hbox = gtk_hbox_new(FALSE, 0);
  label = gtk_label_new(_("Size Units:"));
  gtk_widget_show(label);
  gtk_box_pack_start(GTK_BOX(units_hbox), label, TRUE, TRUE, 0);
  units_label = gtk_label_new(_(" "));
  gtk_widget_show(units_label);
  gtk_box_pack_start(GTK_BOX(units_hbox), units_label, TRUE, TRUE, 0);
  gtk_widget_show(units_hbox);

  vbox = gtk_vbox_new (FALSE, 4);
  gtk_container_set_border_width (GTK_CONTAINER (vbox), 4);
  gtk_notebook_append_page (GTK_NOTEBOOK (notebook), vbox, units_hbox);
  gtk_widget_show (vbox);

  /*
   * The units toggles
   */

  table = gtk_table_new (1, 1, FALSE);
  gtk_table_set_col_spacings (GTK_TABLE (table), 4);
  gtk_box_pack_start (GTK_BOX (vbox), table, FALSE, FALSE, 0);
  gtk_widget_show (table);

  event_box = gtk_event_box_new ();
  gtk_table_attach (GTK_TABLE (table), event_box, 0, 1, 0, 1,
                    GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, 0, 0);
  gtk_widget_show (event_box);

  label = gtk_label_new (_("Units:"));
  gtk_container_add (GTK_CONTAINER (event_box), label);
  gtk_widget_show (label);

  stpui_set_help_data(event_box,
		_("Select the base unit of measurement for printing"));

  group = NULL;
  for (i = 0; i < unit_count; i++)
    {
      unit_t *unit = &(units[i]);
      unit->checkbox = gtk_radio_button_new_with_label(group, gettext(unit->name));
      group = gtk_radio_button_group(GTK_RADIO_BUTTON(unit->checkbox));
      stpui_table_attach_aligned(GTK_TABLE(table), i / 2, i % 2, NULL, 0.5,
				 0.5, unit->checkbox, 1, TRUE);
      stpui_set_help_data(unit->checkbox, gettext(unit->help));
      g_signal_connect(G_OBJECT(unit->checkbox), "toggled",
		       G_CALLBACK(unit_callback), (gpointer) i);
    }
}

/*
 *  create_main_window()
 */
static void
create_main_window (void)
{
  gint x, y; /* Window dimensions */

  set_current_printer();
  manufacturer = stp_printer_get_manufacturer(stp_get_printer(pv->v));
  /*
   * Create the various dialog components.  Note that we're not
   * actually initializing the values at this point; that will be done after
   * the UI is fully created.
   */

  stpui_help_init();

  create_top_level_structure ();

  create_preview ();
  create_printer_settings_frame ();
  create_units_frame();
  create_paper_size_frame();
  create_copy_number_frame();
  create_positioning_frame ();
  create_scaling_frame ();
  create_image_settings_frame ();

  /*
   * Now actually set up the correct values in the dialog
   */

  do_update_thumbnail = 1;
  build_printer_combo ();
  plist_callback (NULL, (gpointer) stpui_plist_current);
  update_adjusted_thumbnail (TRUE);

  /* The initial size request does not account for the
     GtkScrolledWindow. */
  gtk_window_get_size(GTK_WINDOW(print_dialog), &x, &y);
  gtk_window_set_default_size(GTK_WINDOW(print_dialog), x, y+80);

  gtk_widget_show (print_dialog);
}

static void
set_entry_value(GtkWidget *entry, double value, int block)
{
  gchar s[255];
  gdouble unit_scaler = units[pv->unit].scale;
  const gchar *format = units[pv->unit].format;

  g_snprintf(s, sizeof(s), format, value / unit_scaler);
  if (block)
    g_signal_handlers_block_matched (G_OBJECT (entry),
				     G_SIGNAL_MATCH_DATA,
				     0,
				     0,
				     NULL,
				     NULL,
				     NULL);

  gtk_entry_set_text (GTK_ENTRY (entry), s);
  if (block)
    g_signal_handlers_unblock_matched (G_OBJECT (entry),
				       G_SIGNAL_MATCH_DATA,
				       0,
				       0,
				       NULL,
				       NULL,
				       NULL);
}

static void
reset_preview(void)
{
  if (!suppress_preview_reset)
    {
      stpui_enable_help();
      buttons_pressed = preview_active = 0;
    }
}

static void
invalidate_preview_thumbnail (void)
{
  preview_valid = FALSE;
}

static void
invalidate_frame (void)
{
  frame_valid = FALSE;
}

static void
compute_scaling_limits(gdouble *min_ppi_scaling, gdouble *max_ppi_scaling)
{
  if (auto_paper_size)
    {
      *min_ppi_scaling =
	FINCH * (gdouble) image_width / (gdouble) printable_width;
    }
  else
    {
      gdouble min_ppi_scaling1 =
	FINCH * (gdouble) image_width / (gdouble) printable_width;
      gdouble min_ppi_scaling2 =
	FINCH * (gdouble) image_height / (gdouble) printable_height;

      if (min_ppi_scaling1 > min_ppi_scaling2)
	*min_ppi_scaling = min_ppi_scaling1;
      else
	*min_ppi_scaling = min_ppi_scaling2;
    }

  *max_ppi_scaling = *min_ppi_scaling * 100 / minimum_image_percent;
}

/*
 *  scaling_update() - Update the scaling scale using the slider.
 */
static void
scaling_update (GtkAdjustment *adjustment)
{
  reset_preview ();

  if (pv->scaling != adjustment->value)
    {
      invalidate_preview_thumbnail ();
      if (GTK_TOGGLE_BUTTON (scaling_ppi)->active)
	pv->scaling = -adjustment->value;
      else
	pv->scaling = adjustment->value;

      suppress_scaling_adjustment = TRUE;
      preview_update ();
      suppress_scaling_adjustment = FALSE;
    }
}

/*
 *  scaling_callback() - Update the scaling scale using radio buttons.
 */
static void
scaling_callback (GtkWidget *widget)
{
  gdouble max_ppi_scaling;
  gdouble min_ppi_scaling;
  gdouble current_scale;

  reset_preview ();

  if (suppress_scaling_callback)
    return;

  compute_scaling_limits(&min_ppi_scaling, &max_ppi_scaling);

  if (widget == scaling_ppi)
    {
      if (! GTK_TOGGLE_BUTTON (scaling_ppi)->active)
	return;

      GTK_ADJUSTMENT (scaling_adjustment)->lower = min_ppi_scaling;
      GTK_ADJUSTMENT (scaling_adjustment)->upper = max_ppi_scaling;

      /*
       * Compute the correct PPI to create an image of the same size
       * as the one measured in percent
       */
      current_scale = GTK_ADJUSTMENT (scaling_adjustment)->value;
      GTK_ADJUSTMENT (scaling_adjustment)->value =
	min_ppi_scaling / (current_scale / 100);
      pv->scaling = 0.0;
    }
  else if (widget == scaling_percent)
    {
      gdouble new_percent;

      if (! GTK_TOGGLE_BUTTON (scaling_percent)->active)
	return;

      current_scale = GTK_ADJUSTMENT (scaling_adjustment)->value;
      GTK_ADJUSTMENT (scaling_adjustment)->lower = minimum_image_percent;
      GTK_ADJUSTMENT (scaling_adjustment)->upper = 100.0;

      new_percent = 100 * min_ppi_scaling / current_scale;

      if (new_percent > 100)
	new_percent = 100;
      if (new_percent < minimum_image_percent)
	new_percent = minimum_image_percent;

      GTK_ADJUSTMENT (scaling_adjustment)->value = new_percent;
      pv->scaling = 0.0;
    }
  else if (widget == scaling_image)
    {
      gdouble yres = image_yres;

      invalidate_preview_thumbnail ();

      GTK_ADJUSTMENT (scaling_adjustment)->lower = min_ppi_scaling;
      GTK_ADJUSTMENT (scaling_adjustment)->upper = max_ppi_scaling;

      if (yres < min_ppi_scaling)
	yres = min_ppi_scaling;
      if (yres > max_ppi_scaling)
	yres = max_ppi_scaling;

      gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (scaling_ppi), TRUE);
      GTK_ADJUSTMENT (scaling_adjustment)->value = yres;
      pv->scaling = 0.0;
    }

  if (widget == scaling_ppi || widget == scaling_percent)
    suppress_preview_update++;
  gtk_adjustment_changed (GTK_ADJUSTMENT (scaling_adjustment));
  gtk_adjustment_value_changed (GTK_ADJUSTMENT (scaling_adjustment));
  if (auto_paper_size)
    set_media_size(stp_get_string_parameter(pv->v, "PageSize"));
  if (widget == scaling_ppi || widget == scaling_percent)
    suppress_preview_update--;
}

/****************************************************************************
 *
 * plist_build_combo
 *
 ****************************************************************************/
static void
plist_build_combo (GtkWidget      *combo,       /* I - Combo widget */
		   GtkWidget      *label,
		   stp_string_list_t *items,    /* I - Menu items */
		   int		  active,
		   const gchar    *cur_item,    /* I - Current item */
		   const gchar    *def_value,   /* I - default item */
		   GCallback      callback,    /* I - Callback */
		   gint           *callback_id, /* IO - Callback ID (init to -1) */
		   int (*check_func)(const char *string),
		   gpointer        data)
{
  gint      i; /* Looping var */
  GList    *list = 0;
  gint num_items = 0;
  GtkEntry *entry = GTK_ENTRY (GTK_COMBO (combo)->entry);

  if (check_func && items)
    {
      stp_string_list_t *new_items = stp_string_list_create();
      num_items = stp_string_list_count(items);
      for (i = 0; i < num_items; i++)
	{
	  stp_param_string_t *param = stp_string_list_param(items, i);
	  if ((*check_func)(param->name))
	    stp_string_list_add_string(new_items, param->name, param->text);
	}
      items = new_items;
    }

  if (items)
    num_items = stp_string_list_count(items);

  if (*callback_id != -1)
    g_signal_handler_disconnect (G_OBJECT (entry), *callback_id);
  gtk_entry_set_editable (entry, FALSE);

  if (!active || num_items == 0)
    {
      list = g_list_append (list, _("Standard"));
      gtk_combo_set_popdown_strings (GTK_COMBO (combo), list);
      *callback_id = -1;
      gtk_widget_set_sensitive (combo, FALSE);
      gtk_widget_hide (combo);
      if (label)
	gtk_widget_hide(label);
      if (check_func && items)
	stp_string_list_destroy(items);
      return;
    }

  for (i = 0; i < num_items; i ++)
    list = g_list_append(list, g_strdup(stp_string_list_param(items, i)->text));

  gtk_combo_set_popdown_strings (GTK_COMBO (combo), list);

  if (cur_item)
    for (i = 0; i < num_items; i ++)
      if (strcmp(stp_string_list_param(items, i)->name, cur_item) == 0)
	break;

  if (i >= num_items && def_value)
    for (i = 0; i < num_items; i ++)
      if (strcmp(stp_string_list_param(items, i)->name, def_value) == 0)
	break;

  if (i >= num_items)
    i = 0;

  gtk_entry_set_text (entry, stp_string_list_param(items, i)->text);

  gtk_combo_set_value_in_list (GTK_COMBO (combo), TRUE, FALSE);
  gtk_widget_set_sensitive (combo, TRUE);
  gtk_widget_show (combo);
  if (label)
    gtk_widget_show(label);

  *callback_id = g_signal_connect (G_OBJECT (entry), "changed", callback,
				   data);
  if (check_func && items)
    stp_string_list_destroy(items);
}

void
stpui_set_image_dimensions(gint width, gint height)
{
  image_true_width = width;
  image_true_height = height;
}

void
stpui_set_image_resolution(gdouble xres, gdouble yres)
{
  image_xres = xres;
  image_yres = yres;
}

gint
stpui_compute_orientation(void)
{
  if (auto_paper_size ||
      (printable_width >= printable_height &&
       image_true_width >= image_true_height) ||
      (printable_height >= printable_width &&
       image_true_height >= image_true_width))
    return ORIENT_PORTRAIT;
  else
    return ORIENT_LANDSCAPE;
}

static void
compute_printable_region(void)
{
  stp_get_media_size(pv->v, &paper_width, &paper_height);
  stp_get_imageable_area(pv->v, &left, &right, &bottom, &top);
  printable_width  = right - left;
  printable_height = bottom - top;
}  

static void
set_orientation(int orientation)
{
  compute_printable_region();
  pv->orientation = orientation;
  if (orientation == ORIENT_AUTO)
    orientation = stpui_compute_orientation();
  physical_orientation = orientation;
  switch (orientation)
    {
    case ORIENT_PORTRAIT:
    case ORIENT_UPSIDEDOWN:
      image_height = image_true_height;
      image_width = image_true_width;
      preview_thumbnail_h = thumbnail_h;
      preview_thumbnail_w = thumbnail_w;
      break;
    case ORIENT_LANDSCAPE:
    case ORIENT_SEASCAPE:
      image_height = image_true_width;
      image_width = image_true_height;
      preview_thumbnail_h = thumbnail_w;
      preview_thumbnail_w = thumbnail_h;
      break;
    }
  update_adjusted_thumbnail(FALSE);
}

static void
position_button_callback(GtkWidget *widget, gpointer data)
{
  reset_preview();
  pv->invalid_mask |= (gint) data;
  preview_update ();
}

/*
 * position_callback() - callback for position entry widgets
 */
static void
position_callback (GtkWidget *widget)
{
  gdouble new_printed_value = atof (gtk_entry_get_text (GTK_ENTRY (widget)));
  gint new_value = SCALE(new_printed_value, units[pv->unit].scale);

  reset_preview ();
  suppress_preview_update++;

  if (widget == top_entry)
    stp_set_top(pv->v, new_value);
/*
  else if (widget == bottom_entry)
    stp_set_top(pv->v, new_value - print_height);
*/
  else if (widget == bottom_border_entry)
    stp_set_top (pv->v, paper_height - print_height - new_value);
  else if (widget == left_entry)
    stp_set_left (pv->v, new_value);
/*
  else if (widget == right_entry)
    stp_set_left(pv->v, new_value - print_width);
*/
  else if (widget == right_border_entry)
    stp_set_left (pv->v, paper_width - print_width - new_value);
  else if (widget == width_entry || widget == height_entry)
    {
      gboolean was_percent = (pv->scaling >= 0);
      if (pv->scaling >= 0)
	{
	  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (scaling_ppi), TRUE);
	  scaling_callback (scaling_ppi);
	}
      if  (widget == width_entry)
	GTK_ADJUSTMENT (scaling_adjustment)->value =
	  ((gdouble) image_width) / (new_value / FINCH);
      else
	GTK_ADJUSTMENT (scaling_adjustment)->value =
	  ((gdouble) image_height) / (new_value / FINCH);
      gtk_adjustment_value_changed (GTK_ADJUSTMENT (scaling_adjustment));
      if (was_percent)
	{
	  gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(scaling_percent),TRUE);
	  gtk_adjustment_value_changed(GTK_ADJUSTMENT (scaling_adjustment));
	}
    }

  suppress_preview_update--;
  preview_update ();
}

static void
set_adjustment_active(option_t *opt, gboolean active, gboolean do_toggle)
{
  GtkObject *adj = opt->info.flt.adjustment;
  if (do_toggle)
    gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(opt->checkbox), active);
  gtk_widget_set_sensitive
    (GTK_WIDGET (SCALE_ENTRY_LABEL (adj)), active);
  gtk_widget_set_sensitive
    (GTK_WIDGET (SCALE_ENTRY_SCALE (adj)), active);
  gtk_widget_set_sensitive
    (GTK_WIDGET (SCALE_ENTRY_SPINBUTTON (adj)), active);
}

static void
set_combo_active(option_t *opt, gboolean active, gboolean do_toggle)
{
  if (do_toggle)
    gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(opt->checkbox), active);
  gtk_widget_set_sensitive(GTK_WIDGET(opt->info.list.combo), active);
  gtk_widget_set_sensitive(GTK_WIDGET(opt->info.list.label), active);
}

static void
set_curve_active(option_t *opt, gboolean active, gboolean do_toggle)
{
  if (do_toggle)
    gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(opt->checkbox), active);
  gtk_widget_set_sensitive(GTK_WIDGET(opt->info.curve.button), active);
  gtk_widget_set_sensitive(GTK_WIDGET(opt->info.curve.label), active);
  if (active)
    {
      if (opt->info.curve.is_visible)
	gtk_widget_show(GTK_WIDGET(opt->info.curve.dialog));
    }
  else
    gtk_widget_hide(GTK_WIDGET(opt->info.curve.dialog));
}

static void
set_bool_active(option_t *opt, gboolean active, gboolean do_toggle)
{
  if (do_toggle)
    gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(opt->checkbox), active);
  gtk_widget_set_sensitive(GTK_WIDGET(opt->info.bool.checkbox), active);
}

static void
do_color_updates (void)
{
  int i;
  for (i = 0; i < current_option_count; i++)
    {
      option_t *opt = &(current_options[i]);
      if (opt->fast_desc->p_level <= MAXIMUM_PARAMETER_LEVEL)
	{
	  switch (opt->fast_desc->p_type)
	    {
	    case STP_PARAMETER_TYPE_DOUBLE:
	      if (stp_check_float_parameter(pv->v, opt->fast_desc->name,
					    STP_PARAMETER_INACTIVE))
		gtk_adjustment_set_value
		  (GTK_ADJUSTMENT(opt->info.flt.adjustment),
		   stp_get_float_parameter(pv->v, opt->fast_desc->name));
	      if (stp_check_float_parameter(pv->v, opt->fast_desc->name,
					    STP_PARAMETER_ACTIVE) ||
		  opt->fast_desc->is_mandatory)
		set_adjustment_active(opt, TRUE, TRUE);
	      else
		set_adjustment_active(opt, FALSE, TRUE);
	      break;
	    case STP_PARAMETER_TYPE_DIMENSION:
	      if (stp_check_dimension_parameter(pv->v, opt->fast_desc->name,
						STP_PARAMETER_INACTIVE))
		{
		  gdouble unit_scaler = units[pv->unit].scale;
		  gtk_adjustment_set_value
		  (GTK_ADJUSTMENT(opt->info.flt.adjustment),
		   (stp_get_dimension_parameter(pv->v, opt->fast_desc->name) /
		    unit_scaler));
		}
	      if (stp_check_dimension_parameter(pv->v, opt->fast_desc->name,
						STP_PARAMETER_ACTIVE) ||
		  opt->fast_desc->is_mandatory)
		set_adjustment_active(opt, TRUE, TRUE);
	      else
		set_adjustment_active(opt, FALSE, TRUE);
	      break;
	    case STP_PARAMETER_TYPE_CURVE:
	      if (stp_check_curve_parameter(pv->v, opt->fast_desc->name,
					    STP_PARAMETER_ACTIVE) ||
		  opt->fast_desc->is_mandatory)
		set_curve_active(opt, TRUE, TRUE);
	      else
		set_curve_active(opt, FALSE, TRUE);
	      break;
	    case STP_PARAMETER_TYPE_STRING_LIST:
	      if (strcmp(opt->fast_desc->name, "PageSize") == 0)
		build_page_size_combo(opt);
	      else if (stp_check_string_parameter(pv->v, opt->fast_desc->name,
						  STP_PARAMETER_INACTIVE))
		plist_build_combo(opt->info.list.combo, opt->info.list.label,
				  opt->info.list.params, opt->is_active,
				  (stp_get_string_parameter
				   (pv->v, opt->fast_desc->name)),
				  opt->info.list.default_val,
				  G_CALLBACK(combo_callback),
				  &(opt->info.list.callback_id),
				  NULL, opt);
	      if (stp_check_string_parameter(pv->v, opt->fast_desc->name,
					     STP_PARAMETER_ACTIVE) ||
		  opt->fast_desc->is_mandatory)
		set_combo_active(opt, TRUE, TRUE);
	      else
		set_combo_active(opt, FALSE, TRUE);
	      break;
	    case STP_PARAMETER_TYPE_BOOLEAN:
	      if (stp_check_boolean_parameter(pv->v, opt->fast_desc->name,
					      STP_PARAMETER_INACTIVE))
		gtk_toggle_button_set_active
		  (GTK_TOGGLE_BUTTON(opt->info.bool.checkbox),
		   stp_get_boolean_parameter(pv->v, opt->fast_desc->name));
	      if (stp_check_boolean_parameter(pv->v, opt->fast_desc->name,
					      STP_PARAMETER_ACTIVE) ||
		  opt->fast_desc->is_mandatory)
		set_bool_active(opt, TRUE, TRUE);
	      else
		set_bool_active(opt, FALSE, TRUE);
	      break;
	    default:
	      break;
	    }
	}
    }
  update_adjusted_thumbnail (TRUE);
}

static void
update_options(void)
{
  gtk_widget_hide(page_size_table);
  gtk_widget_hide(printer_features_table);
  gtk_widget_hide(color_adjustment_table);
  populate_options(pv->v);
  populate_option_table(page_size_table, STP_PARAMETER_CLASS_CORE);
  populate_option_table(printer_features_table, STP_PARAMETER_CLASS_FEATURE);
  populate_option_table(color_adjustment_table, STP_PARAMETER_CLASS_OUTPUT);
  gtk_widget_show(page_size_table);
  gtk_widget_show(printer_features_table);
  gtk_widget_show(color_adjustment_table);
  set_options_active(NULL);
}

static void
update_standard_print_command(void)
{
  char *label_text =
    stpui_build_standard_print_command(pv, stp_get_printer(pv->v));
  gtk_entry_set_text(GTK_ENTRY(standard_cmd_entry), label_text);
  g_free(label_text);
}

static void
set_color_options(void)
{
  stp_parameter_t desc;
  stp_describe_parameter(pv->v, "PrintingMode", &desc);
  if (desc.p_type == STP_PARAMETER_TYPE_STRING_LIST)
    {
      if (!stp_string_list_is_present(desc.bounds.str, "Color"))
	{
	  gtk_widget_set_sensitive (output_types[1].button, TRUE);
	  if (gtk_toggle_button_get_active
	      (GTK_TOGGLE_BUTTON (output_types[0].button)) == TRUE)
	    gtk_toggle_button_set_active
	      (GTK_TOGGLE_BUTTON (output_types[1].button), TRUE);
	  gtk_widget_set_sensitive (output_types[0].button, FALSE);
	}
      else if (!stp_string_list_is_present(desc.bounds.str, "BW"))
	{
	  gtk_widget_set_sensitive (output_types[0].button, TRUE);
	  if (gtk_toggle_button_get_active
	      (GTK_TOGGLE_BUTTON (output_types[1].button)) == TRUE)
	    gtk_toggle_button_set_active
	      (GTK_TOGGLE_BUTTON (output_types[0].button), TRUE);
	  gtk_widget_set_sensitive (output_types[1].button, FALSE);
	}
      else
	{
	  gtk_widget_set_sensitive (output_types[0].button, TRUE);
	  gtk_widget_set_sensitive (output_types[1].button, TRUE);
	}
    }
  stp_parameter_description_destroy(&desc);
}

static void
do_all_updates(void)
{
  gint i;
  suppress_preview_update++;
  set_orientation(pv->orientation);
  invalidate_preview_thumbnail ();
  preview_update ();
  update_standard_print_command();

  if (pv->scaling < 0)
    {
      gdouble tmp = -pv->scaling;
      gdouble max_ppi_scaling;
      gdouble min_ppi_scaling;

      compute_scaling_limits(&min_ppi_scaling, &max_ppi_scaling);

      gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (scaling_ppi), TRUE);
      GTK_ADJUSTMENT (scaling_adjustment)->lower = min_ppi_scaling;
      GTK_ADJUSTMENT (scaling_adjustment)->upper = max_ppi_scaling;
      GTK_ADJUSTMENT (scaling_adjustment)->value = tmp;
      gtk_adjustment_changed (GTK_ADJUSTMENT (scaling_adjustment));
      gtk_adjustment_value_changed (GTK_ADJUSTMENT (scaling_adjustment));
    }
  else
    {
      gdouble tmp = pv->scaling;

      gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (scaling_percent), TRUE);
      GTK_ADJUSTMENT (scaling_adjustment)->lower = minimum_image_percent;
      GTK_ADJUSTMENT (scaling_adjustment)->upper = 100.0;
      GTK_ADJUSTMENT (scaling_adjustment)->value = tmp;
      g_signal_emit_by_name (G_OBJECT(scaling_adjustment), "changed");
      g_signal_emit_by_name (G_OBJECT(scaling_adjustment), "value_changed");
    }

  set_color_options();
  for (i = 0; i < output_type_count; i++)
    {
      if (stp_get_string_parameter(pv->v, "PrintingMode") &&
	  strcmp(output_types[i].value,
		 stp_get_string_parameter(pv->v, "PrintingMode")) == 0)
	gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(output_types[i].button),
				     TRUE);
    }

  /*
   * Now get option parameters.
   */

  update_options();
  do_color_updates ();

  gtk_option_menu_set_history (GTK_OPTION_MENU (orientation_menu),
			       pv->orientation + 1);

  gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(units[pv->unit].checkbox),
			       TRUE);
  gtk_label_set_text(GTK_LABEL(units_label), units[pv->unit].name);
  suppress_preview_update--;
  preview_update ();
}

static void
copy_count_callback(GtkAdjustment *adjustment, gpointer data)
{
  gint copy_count = (gint) adjustment->value;
  stpui_plist_set_copy_count(pv, copy_count);
  update_standard_print_command();
}

static void
auto_paper_size_callback(GtkWidget *widget, gpointer data)
{
  auto_paper_size =
    gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(auto_paper_size_button));
  pv->auto_size_roll_feed_paper = auto_paper_size;
  set_orientation(pv->orientation);
  do_all_updates();
}

static void
setup_auto_paper_size(void)
{
  const stp_papersize_t *ps =
    stp_get_papersize_by_name(stp_get_string_parameter(pv->v, "PageSize"));
  if (ps->height == 0 && ps->width != 0)		/* Implies roll feed */
    {
      g_signal_handlers_block_matched (G_OBJECT(auto_paper_size_button),
				       G_SIGNAL_MATCH_DATA,
				       0,
				       0,
				       NULL,
				       NULL,
                                       NULL);

      gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(auto_paper_size_button),
				   pv->auto_size_roll_feed_paper);
      gtk_widget_show(auto_paper_size_button);
      g_signal_handlers_unblock_matched (G_OBJECT(auto_paper_size_button),
					 G_SIGNAL_MATCH_DATA,
					 0,
					 0,
					 NULL,
					 NULL,
					 NULL);
    }
  else
    {
      gtk_widget_hide(auto_paper_size_button);
      auto_paper_size = 0;
    }
}

static void
queue_callback (GtkWidget *widget,
		gpointer   data)
{
  int i;
  int count = stp_string_list_count(stpui_system_print_queues);
  const gchar *result =
    gtk_entry_get_text(GTK_ENTRY(GTK_COMBO(queue_combo)->entry));
  for (i = 0; i < count; i++)
    {
      const stp_param_string_t *s =
	stp_string_list_param(stpui_system_print_queues, i);
      if (!strcmp(result, s->text))
	{
	  stpui_plist_set_queue_name(pv, s->name);
	  do_all_updates();
	  return;
	}
    }
}

static void
setup_callback (GtkWidget *widget)
{
  const gchar *new_value = gtk_entry_get_text (GTK_ENTRY (widget));

  if (widget == custom_command_entry)
    stpui_plist_set_custom_command(pv, new_value);
  else if (widget == file_entry)
    {
      stpui_plist_set_output_filename(pv, new_value);
      gtk_file_selection_set_filename
	(GTK_FILE_SELECTION (file_browser),
	 gtk_entry_get_text (GTK_ENTRY (file_entry)));
    }
}

/*
 *  plist_callback() - Update the current system printer.
 */
static void
plist_callback (GtkWidget *widget,
		gpointer   data)
{
  gint         i;
  char *tmp;

  suppress_preview_update++;
  invalidate_frame ();
  invalidate_preview_thumbnail ();
  reset_preview ();

  if (widget)
    {
      const gchar *result =
	gtk_entry_get_text(GTK_ENTRY(GTK_COMBO(printer_combo)->entry));

      for (i = 0; i < stpui_plist_count; i++)
	{
	  if (! strcmp (result, stp_string_list_param(printer_list, i)->text))
	    {
	      stpui_plist_current = i;
	      break;
	    }
	}
    }
  else
    {
      stpui_plist_current = (gint) data;
    }

  set_current_printer();
  build_queue_combo();
  manufacturer = stp_printer_get_manufacturer(stp_get_printer(pv->v));
  build_printer_driver_clist();

  if (strcmp(stp_get_driver(pv->v), "") != 0)
    tmp_printer = stp_get_printer(pv->v);

  gtk_entry_set_text(GTK_ENTRY(file_entry),
		     stpui_plist_get_output_filename(pv));
  tmp = stpui_build_standard_print_command(pv, stp_get_printer(pv->v));
  gtk_entry_set_text(GTK_ENTRY(standard_cmd_entry), tmp);
  stp_free(tmp);
  gtk_entry_set_text(GTK_ENTRY(custom_command_entry),
		     stpui_plist_get_custom_command(pv));
  gtk_spin_button_set_value(GTK_SPIN_BUTTON(copy_count_spin_button),
			    (gfloat) stpui_plist_get_copy_count(pv));
  do_all_updates();

  setup_update ();
  do_all_updates();
  suppress_preview_update--;
  update_adjusted_thumbnail(TRUE);
  preview_update ();
}

static void
show_all_paper_sizes_callback(GtkWidget *widget, gpointer data)
{
  int i;
  stpui_show_all_paper_sizes =
    gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget));
  for (i = 0; i < current_option_count; i++)
    {
      option_t *option = &(current_options[i]);
      if (option->fast_desc &&
	  strcmp(option->fast_desc->name, "PageSize") == 0)
	{
	  build_a_combo(option);
	  break;
	}
    }
}

static void
custom_media_size_callback(GtkWidget *widget,
			   gpointer data)
{
  gint width_limit, height_limit;
  gint min_width_limit, min_height_limit;
  gdouble new_printed_value = atof(gtk_entry_get_text(GTK_ENTRY(widget)));
  gint new_value = SCALE(new_printed_value, units[pv->unit].scale);
  invalidate_frame ();
  invalidate_preview_thumbnail ();
  reset_preview ();

  stp_get_size_limit(pv->v, &width_limit, &height_limit,
		     &min_width_limit, &min_height_limit);
  if (widget == custom_size_width)
    {
      if (new_value < min_width_limit)
	new_value = min_width_limit;
      else if (new_value > width_limit)
	new_value = width_limit;
      stp_set_page_width (pv->v, new_value);
    }
  else
    {
      if (new_value < min_height_limit)
	new_value = min_height_limit;
      else if (new_value > height_limit)
	new_value = height_limit;
      stp_set_page_height (pv->v, new_value);
    }
  set_entry_value (widget, new_value, 0);
  preview_update ();
}


/*
 *  media_size_callback() - Update the current media size.
 */
static void
set_media_size(const gchar *new_media_size)
{
  static int setting_media_size = 0;
  const stp_papersize_t *pap = stp_get_papersize_by_name (new_media_size);

  if (setting_media_size)
    return;
  setting_media_size++;

  if (pap)
    {
      gint size;
      int old_width = stp_get_page_width(pv->v);
      int old_height = stp_get_page_height(pv->v);
      int need_preview_update = 0;

      if (! stpui_show_all_paper_sizes &&
	  (pap->paper_unit == PAPERSIZE_METRIC_EXTENDED ||
	   pap->paper_unit == PAPERSIZE_ENGLISH_EXTENDED))
	{
	  int i;
	  stp_parameter_t desc;
	  stp_describe_parameter(pv->v, "PageSize", &desc);
	  stp_set_string_parameter(pv->v, "PageSize", desc.deflt.str);
	  pap = stp_get_papersize_by_name(desc.deflt.str);
	  stp_parameter_description_destroy(&desc);
	  for (i = 0; i < current_option_count; i++)
	    {
	      option_t *option = &(current_options[i]);
	      if (option->fast_desc &&
		  strcmp(option->fast_desc->name, "PageSize") == 0)
		{
		  build_a_combo(option);
		  break;
		}
	    }
	}

      if (pap->width == 0)
	{
	  int max_w, max_h, min_w, min_h;
	  stp_get_size_limit(pv->v, &max_w, &max_h, &min_w, &min_h);
	  size = old_width;
	  if (size < min_w)
	    size = min_w;
	  else if (size > max_w)
	    size = max_w;
	  gtk_widget_set_sensitive (GTK_WIDGET (custom_size_width), TRUE);
	  gtk_entry_set_editable (GTK_ENTRY (custom_size_width), TRUE);
	}
      else
	{
	  size = pap->width;
	  gtk_widget_set_sensitive (GTK_WIDGET (custom_size_width), FALSE);
	  gtk_entry_set_editable (GTK_ENTRY (custom_size_width), FALSE);
	}
      if (size != old_width)
	{
	  need_preview_update = 1;
	  set_entry_value (custom_size_width, size, 0);
	  stp_set_page_width (pv->v, size);
	}

      setup_auto_paper_size();
      if (pap->height == 0)
	{
	  int max_w, max_h, min_w, min_h;
	  stp_get_size_limit(pv->v, &max_w, &max_h, &min_w, &min_h);
	  if (auto_paper_size)
	    {
	      int l, r, b, t;
	      stp_set_page_height(pv->v, 0);
	      old_height = 0;
	      stp_get_imageable_area(pv->v, &l, &r, &b, &t);
	      gtk_widget_set_sensitive(GTK_WIDGET(custom_size_height), FALSE);
	      gtk_entry_set_editable(GTK_ENTRY(custom_size_height), FALSE);
	      size = print_height;
	    }
	  else
	    {
	      gtk_widget_set_sensitive (GTK_WIDGET (custom_size_height), TRUE);
	      gtk_entry_set_editable (GTK_ENTRY (custom_size_height), TRUE);
	      size = old_height;
	    }
	  if (size < min_h)
	    size = min_h;
	  else if (size > max_h)
	    size = max_h;
	}
      else
	{
	  size = pap->height;
	  gtk_widget_set_sensitive(GTK_WIDGET (custom_size_height), FALSE);
	  gtk_entry_set_editable (GTK_ENTRY (custom_size_height), FALSE);
	}
      if (size != old_height)
	{
	  need_preview_update = 1;
	  set_entry_value (custom_size_height, size, 0);
	  stp_set_page_height (pv->v, size);
	}
      if (need_preview_update)
	{
	  invalidate_preview_thumbnail();
	  invalidate_frame();
	  preview_update();
	}
    }
  setting_media_size--;
}

static gboolean
refresh_all_options(gpointer data)
{
  do_all_updates();
  do_all_updates();		/* Update twice to pick up cascading changes */
  return FALSE;
}

static void
combo_callback(GtkWidget *widget, gpointer data)
{
  option_t *option = (option_t *)data;
  const gchar *new_value =
    stpui_combo_get_name(option->info.list.combo, option->info.list.params);
  const gchar *value =
    stp_get_string_parameter(pv->v, option->fast_desc->name);
  if (value && new_value)
    {
      reset_preview();
      if (!value || strcmp(value, new_value) != 0)
	{
	  invalidate_frame();
	  invalidate_preview_thumbnail();
	  stp_set_string_parameter(pv->v, option->fast_desc->name, new_value);
	  if (strcmp(option->fast_desc->name, "PageSize") == 0)
	    set_media_size(new_value);
	  g_idle_add(refresh_all_options, NULL);
	  if (option->fast_desc->p_class == STP_PARAMETER_CLASS_OUTPUT)
	    update_adjusted_thumbnail(TRUE);
	  preview_update();
	}
    }
}

/*
 *  orientation_callback() - Update the current media size.
 */
static void
orientation_callback (GtkWidget *widget,
		      gpointer   data)
{
  reset_preview ();

  if (pv->orientation != (gint) data)
    {
      invalidate_preview_thumbnail ();
      set_orientation((gint) data);
      update_adjusted_thumbnail(TRUE);
      preview_update ();
    }
}

/*
 *  output_type_callback() - Update the current output type.
 */
static void
output_type_callback (GtkWidget *widget,
		      gpointer   data)
{
  reset_preview ();

  if (GTK_TOGGLE_BUTTON (widget)->active)
    {
      if (strcmp((const char *) data, "BW") == 0)
	gtk_widget_hide(output_color_vbox);
      else
	gtk_widget_show(output_color_vbox);
      stp_set_string_parameter(pv->v, "PrintingMode", (const char *) data);
      invalidate_preview_thumbnail ();
      update_adjusted_thumbnail (TRUE);
      set_options_active(NULL);
      preview_update ();
      do_all_updates();
    }
}

static void
command_type_callback(GtkWidget *widget, gpointer data)
{
  if (strcmp((const char *) data, "Standard") == 0)
    {
      gtk_widget_set_sensitive(standard_cmd_entry, TRUE);
      gtk_widget_set_sensitive(queue_combo, TRUE);
      gtk_widget_set_sensitive(file_entry, FALSE);
      gtk_entry_set_editable(GTK_ENTRY(file_entry), FALSE);
      gtk_widget_set_sensitive(custom_command_entry, FALSE);
      gtk_entry_set_editable(GTK_ENTRY(custom_command_entry), FALSE);
      gtk_widget_hide(GTK_WIDGET(file_browser));
      gtk_widget_set_sensitive(file_button, FALSE);
      gtk_widget_set_sensitive(copy_count_spin_button, TRUE);
      stpui_plist_set_command_type(pv, COMMAND_TYPE_DEFAULT);
    }
  else if (strcmp((const char *) data, "Custom") == 0)
    {
      gtk_widget_set_sensitive(standard_cmd_entry, FALSE);
      gtk_widget_set_sensitive(queue_combo, FALSE);
      gtk_widget_set_sensitive(file_entry, FALSE);
      gtk_entry_set_editable(GTK_ENTRY(file_entry), FALSE);
      gtk_widget_set_sensitive(custom_command_entry, TRUE);
      gtk_entry_set_editable(GTK_ENTRY(custom_command_entry), TRUE);
      gtk_widget_hide(GTK_WIDGET(file_browser));
      gtk_widget_set_sensitive(file_button, FALSE);
      gtk_widget_set_sensitive(copy_count_spin_button, FALSE);
      stpui_plist_set_command_type(pv, COMMAND_TYPE_CUSTOM);
    }
  else if (strcmp((const char *) data, "File") == 0)
    {
      gtk_widget_set_sensitive(standard_cmd_entry, FALSE);
      gtk_widget_set_sensitive(queue_combo, FALSE);
      gtk_widget_set_sensitive(file_entry, TRUE);
      gtk_entry_set_editable(GTK_ENTRY(file_entry), TRUE);
      gtk_widget_set_sensitive(custom_command_entry, FALSE);
      gtk_entry_set_editable(GTK_ENTRY(custom_command_entry), FALSE);
      gtk_widget_set_sensitive(file_button, TRUE);
      gtk_widget_set_sensitive(copy_count_spin_button, FALSE);
      stpui_plist_set_command_type(pv, COMMAND_TYPE_FILE);
    }      
}

static void
set_all_entry_values(void)
{
  set_entry_value (top_entry, (stp_get_top (pv->v)), 1);
  set_entry_value (left_entry, (stp_get_left (pv->v)), 1);
/*
  set_entry_value (bottom_entry, (top + stp_get_top(pv->v) + print_height), 1);
*/
  set_entry_value (bottom_border_entry,
                   (paper_height - (stp_get_top (pv->v) + print_height)), 1);
/*
  set_entry_value (right_entry, (stp_get_left(pv->v) + print_width), 1);
*/
  set_entry_value (right_border_entry,
                   (paper_width - (stp_get_left (pv->v) + print_width)), 1);
  set_entry_value (width_entry, print_width, 1);
  set_entry_value (height_entry, print_height, 1);
  set_entry_value (custom_size_width, stp_get_page_width (pv->v), 1);
  set_entry_value (custom_size_height, stp_get_page_height (pv->v), 1);
}

/*
 *  unit_callback() - Update the current unit.
 */
static void
unit_callback (GtkWidget *widget,
	       gpointer   data)
{
  reset_preview ();

  if (GTK_TOGGLE_BUTTON (widget)->active)
    {
      pv->unit = (gint) data;
      gtk_label_set_text(GTK_LABEL(units_label), units[pv->unit].name);
      set_all_entry_values();
      update_options();
      do_color_updates();
    }
}

static void
destroy_dialogs (void)
{
  int i;
  gtk_widget_destroy (color_adjust_dialog);
  gtk_widget_destroy (setup_dialog);
  gtk_widget_destroy (print_dialog);
  gtk_widget_destroy (new_printer_dialog);
  gtk_widget_destroy (about_dialog);
  for (i = 0; i < current_option_count; i++)
    {
      if (current_options[i].fast_desc->p_type == STP_PARAMETER_TYPE_CURVE &&
	  current_options[i].info.curve.dialog)
	gtk_widget_destroy(current_options[i].info.curve.dialog);
    }
}

static void
dialogs_set_sensitive (gboolean sensitive)
{
  int i;
  gtk_widget_set_sensitive (color_adjust_dialog, sensitive);
  gtk_widget_set_sensitive (setup_dialog, sensitive);
  gtk_widget_set_sensitive (print_dialog, sensitive);
  gtk_widget_set_sensitive (new_printer_dialog, sensitive);
  gtk_widget_set_sensitive (about_dialog, sensitive);
  for (i = 0; i < current_option_count; i++)
    {
      if (current_options[i].fast_desc->p_type == STP_PARAMETER_TYPE_CURVE &&
	  current_options[i].info.curve.dialog)
	gtk_widget_set_sensitive(current_options[i].info.curve.dialog,
				 sensitive);
    }
}

/*
 * 'print_callback()' - Start the print.
 */
static void
print_callback (void)
{
  if (stpui_plist_get_command_type(pv) == COMMAND_TYPE_FILE &&
      strlen(stpui_plist_get_output_filename(pv)) == 0)
    {
      dialogs_set_sensitive (FALSE);
      exit_after_file_ok = 1;
      gtk_widget_show (file_browser);
    }
  else
    {
      runme = TRUE;
      destroy_dialogs ();
    }
}

/*
 *  printandsave_callback() -
 */
static void
printandsave_callback (void)
{
  saveme = TRUE;
  print_callback();
}

static void
about_callback (void)
{
  gtk_widget_show (about_dialog);
}

/*
 *  save_callback() - save settings, don't destroy dialog
 */
static void
save_callback (void)
{
  reset_preview ();
  stpui_printrc_save ();
}

/*
 *  setup_update() - update widgets in the setup dialog
 */
static void
setup_update (void)
{
  GtkAdjustment *adjustment;
  gint           idx = 0;
  gint i;
  gchar *tmp;
  stp_parameter_t desc;
  const char *ppd_file_name = stp_get_file_parameter(pv->v, "PPDFile");

  for (i = 0; i < GTK_CLIST(manufacturer_clist)->rows; i++)
    {
      (void) gtk_clist_get_text(GTK_CLIST(manufacturer_clist), i, 0, &tmp);
      if (tmp && strcmp(manufacturer, tmp) == 0)
	{
	  idx = i;
	  break;
	}
    }
  gtk_clist_select_row(GTK_CLIST(manufacturer_clist), idx, 0);

  idx = stp_get_printer_index_by_driver (stp_get_driver (pv->v));

  idx = gtk_clist_find_row_from_data(GTK_CLIST(printer_driver),
				     (gpointer) idx);
/*
  if (idx >= 0)
    idx = 0;
*/
  gtk_clist_select_row (GTK_CLIST (printer_driver), idx, 0);
  stp_describe_parameter(pv->v, "ModelName", &desc);
  if (desc.p_type == STP_PARAMETER_TYPE_STRING_LIST && desc.is_active &&
      desc.deflt.str)
    {
      const char *extra_printer_model = desc.deflt.str;
      char *label_text =
	g_malloc(strlen(gettext (stp_printer_get_long_name (tmp_printer))) +
		 2 +		/* " (" */
		 strlen(extra_printer_model) +
		 2);		/* ")" + null terminator */
      strcpy(label_text, extra_printer_model);
      strcat(label_text, " (");
      strcat(label_text, gettext (stp_printer_get_long_name (tmp_printer)));
      strcat(label_text, ")");
      gtk_label_set_text (GTK_LABEL (printer_model_label), label_text);
      g_free(label_text);
    }
  else
    {
      gtk_label_set_text (GTK_LABEL (printer_model_label),
			  gettext (stp_printer_get_long_name (tmp_printer)));
    }
  stp_parameter_description_destroy(&desc);

  if (ppd_file_name)
    gtk_entry_set_text (GTK_ENTRY (ppd_file), ppd_file_name);
  else
    gtk_entry_set_text (GTK_ENTRY (ppd_file), "");
  ppd_file_callback(ppd_file, NULL);

  if (stp_parameter_find_in_settings(pv->v, "PPDFile"))
    {
      gtk_widget_show (ppd_box);
      gtk_widget_show (ppd_label);
      gtk_widget_show (ppd_model_label);
      gtk_widget_show (ppd_model);
    }
  else
    {
      gtk_widget_hide (ppd_box);
      gtk_widget_hide (ppd_label);
      gtk_widget_hide (ppd_model_label);
      gtk_widget_hide (ppd_model);
    }
  gtk_entry_set_text (GTK_ENTRY (custom_command_entry),
		      stpui_plist_get_custom_command (pv));

  adjustment = GTK_CLIST (printer_driver)->vadjustment;
  gtk_adjustment_set_value
    (adjustment,
     adjustment->lower + idx * (adjustment->upper - adjustment->lower) /
     GTK_CLIST (printer_driver)->rows);

  i = stpui_plist_get_command_type(pv);
  if (i >= 0 && i < command_options_count)
    gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(command_options[i].button),
				 TRUE);
}

static void
ppd_file_callback(GtkWidget *widget, gpointer data)
{
  const gchar *name = gtk_entry_get_text(GTK_ENTRY(widget));
  if (name && pv && pv->v)
    {
      stp_parameter_t desc;
      stp_vars_t *v = stp_vars_create_copy(pv->v);
      stp_set_file_parameter(v, "PPDFile", name);
      stp_describe_parameter(v, "ModelName", &desc);
      if (desc.p_type == STP_PARAMETER_TYPE_STRING_LIST && desc.is_active)
	gtk_label_set_text(GTK_LABEL(ppd_model), desc.deflt.str);
      else
	gtk_label_set_text(GTK_LABEL(ppd_model), "");
      stp_parameter_description_destroy(&desc);
      stp_vars_destroy(v);
    }
  else
    gtk_label_set_text(GTK_LABEL(ppd_model), "");
}  

/*
 *  setup_open_callback() -
 */
static void
setup_open_callback (void)
{
  static gboolean first_time = TRUE;
  manufacturer = stp_printer_get_manufacturer(stp_get_printer(pv->v));
  build_printer_driver_clist();

  reset_preview ();
  setup_update ();

/*  gtk_widget_set_sensitive(GTK_DIALOG(print_dialog)->action_area, FALSE); */
  gtk_widget_show (setup_dialog);

  if (first_time)
    {
      /* Make sure the driver scroller gets positioned correctly. */
      setup_update ();
      first_time = FALSE;
    }
}

/*
 *  new_printer_open_callback() -
 */
static void
new_printer_open_callback (void)
{
  reset_preview ();
  gtk_entry_set_text (GTK_ENTRY (new_printer_entry), "");
  gtk_widget_show (new_printer_dialog);
}

static void
set_printer(void)
{
  manufacturer = stp_printer_get_manufacturer(tmp_printer);
  build_printer_driver_clist();
  build_queue_combo();
  stp_set_driver (pv->v, stp_printer_get_driver (tmp_printer));
  stpui_plist_set_custom_command
    (pv, gtk_entry_get_text (GTK_ENTRY (custom_command_entry)));
  stpui_plist_set_output_filename
    (pv, gtk_entry_get_text (GTK_ENTRY (file_entry)));
  stp_set_file_parameter (pv->v, "PPDFile",
			  gtk_entry_get_text (GTK_ENTRY (ppd_file)));
  gtk_label_set_text (GTK_LABEL (printer_model_label),
                      gettext (stp_printer_get_long_name (tmp_printer)));

  plist_callback (NULL, (gpointer) stpui_plist_current);
}

/*
 *  setup_ok_callback() -
 */
static void
setup_ok_callback (void)
{
  gtk_widget_hide(ppd_browser);
  gtk_widget_hide(file_browser);
  gtk_widget_hide(setup_dialog);
  set_printer();
  gtk_widget_set_sensitive(GTK_DIALOG(print_dialog)->action_area, TRUE);
}

/*
 *  setup_cancel_callback() -
 */
static void
setup_cancel_callback (void)
{
  gtk_widget_hide(ppd_browser);
  gtk_widget_hide(file_browser);
  gtk_widget_hide(setup_dialog);
  manufacturer = stp_printer_get_manufacturer(stp_get_printer(pv->v));
  build_printer_driver_clist();
  setup_update();
  gtk_widget_set_sensitive(GTK_DIALOG(print_dialog)->action_area, TRUE);
}

/*
 *  setup_ok_callback() -
 */
static void
new_printer_ok_callback (void)
{
  const gchar *data = gtk_entry_get_text (GTK_ENTRY (new_printer_entry));
  stpui_plist_t   key;

  if (strlen(data))
    {
      memset(&key, 0, sizeof(key));
      stpui_printer_initialize (&key);
      stpui_plist_copy(&key, pv);
      stpui_plist_set_name(&key, data);

      if (stpui_plist_add (&key, 1))
	{
	  stp_vars_destroy(key.v);
	  g_free(key.name);
	  stpui_plist_current = stpui_plist_count - 1;
	  set_current_printer();
	  build_printer_combo ();
	  set_printer();
	}
    }

  gtk_widget_hide (new_printer_dialog);
}

static void
pop_ppd_box(void)
{
  const stp_vars_t *v = stp_printer_get_defaults(tmp_printer);
  if (stp_parameter_find_in_settings(v, "PPDFile"))
    {
      gtk_widget_show (ppd_label);
      gtk_widget_show (ppd_box);
      gtk_widget_show (ppd_model_label);
      gtk_widget_show (ppd_model);
    }
  else
    {
      gtk_widget_hide (ppd_label);
      gtk_widget_hide (ppd_box);
      gtk_widget_hide (ppd_model_label);
      gtk_widget_hide (ppd_model);
    }
}

static void
build_printer_driver_clist(void)
{
  int i;
  int current_idx = 0;
  gtk_clist_clear(GTK_CLIST(printer_driver));
  for (i = 0; i < stp_printer_model_count (); i ++)
    {
      const stp_printer_t *the_printer = stp_get_printer_by_index (i);

      if (strcmp(manufacturer, stp_printer_get_manufacturer(the_printer)) == 0)
	{
	  gchar *tmp=g_strdup(gettext(stp_printer_get_long_name(the_printer)));
	  /*
	   * FIXME Somehow if the raw printer comes before any of the
	   * "real" printers in the list of printers created in module.c,
	   * this code barfs on any of those printers added later.  For
	   * example, try listing olympus_LTX_stpi_module_data after
	   * raw_LTX_stpi_module_data.
	   */

	  gtk_clist_insert (GTK_CLIST (printer_driver), current_idx, &tmp);
	  gtk_clist_set_row_data (GTK_CLIST (printer_driver), current_idx,
				  (gpointer) i);
	  g_free(tmp);
	  current_idx++;
	}
    }
}

static void
manufacturer_callback(GtkWidget      *widget, /* I - Driver list */
		      gint            row,
		      gint            column,
		      GdkEventButton *event,
		      gpointer        data)
{
  static int calling_manufacturer_callback = 0;
  gchar *text;
  if (calling_manufacturer_callback)
    return;
  calling_manufacturer_callback++;
  if (gtk_clist_get_text(GTK_CLIST(widget), row, column, &text))
    manufacturer = text;
  build_printer_driver_clist();
  setup_update();
  calling_manufacturer_callback--;
}

/*
 *  print_driver_callback() - Update the current printer driver.
 */
static void
print_driver_callback (GtkWidget      *widget, /* I - Driver list */
		       gint            row,
		       gint            column,
		       GdkEventButton *event,
		       gpointer        data)   /* I - Data */
{
  char *tmp;
  static int calling_print_driver_callback = 0;
  if (calling_print_driver_callback)
    return;
  calling_print_driver_callback++;
  invalidate_frame ();
  invalidate_preview_thumbnail ();
  reset_preview ();
  data = gtk_clist_get_row_data (GTK_CLIST (widget), row);
  tmp_printer = stp_get_printer_by_index ((gint) data);
  tmp = stpui_build_standard_print_command(pv, tmp_printer);
  gtk_entry_set_text(GTK_ENTRY(standard_cmd_entry), tmp);
  g_free(tmp);

  pop_ppd_box();
  calling_print_driver_callback--;
}

/*
 *  ppd_browse_callback() -
 */
static void
ppd_browse_callback (void)
{
  reset_preview ();
  gtk_file_selection_set_filename (GTK_FILE_SELECTION (ppd_browser),
				   gtk_entry_get_text (GTK_ENTRY (ppd_file)));
  gtk_widget_show (ppd_browser);
}

/*
 *  ppd_ok_callback() -
 */
static void
ppd_ok_callback (void)
{
  reset_preview ();
  gtk_widget_hide (ppd_browser);
  gtk_entry_set_text
    (GTK_ENTRY (ppd_file),
     gtk_file_selection_get_filename (GTK_FILE_SELECTION (ppd_browser)));
  ppd_file_callback(ppd_file, NULL);
  update_options();
}

/*
 *  ppd_browse_callback() -
 */
static void
file_browse_callback (void)
{
  reset_preview ();
  gtk_file_selection_set_filename (GTK_FILE_SELECTION (file_browser),
				   gtk_entry_get_text (GTK_ENTRY (file_entry)));
  gtk_widget_show (file_browser);
}

/*
 *  file_ok_callback() - print to file and go away
 */
static void
file_ok_callback (void)
{
  const char *filename =
    gtk_file_selection_get_filename(GTK_FILE_SELECTION(file_browser));
  gtk_widget_hide (file_browser);
  gtk_entry_set_text(GTK_ENTRY(file_entry), filename);
  stpui_plist_set_output_filename(pv, filename);
  if (exit_after_file_ok)
    {
      runme = TRUE;
      destroy_dialogs ();
    }
}

/*
 *  file_cancel_callback() -
 */
static void
file_cancel_callback (void)
{
  exit_after_file_ok = 0;
  gtk_widget_hide (file_browser);
  dialogs_set_sensitive (TRUE);
}

static void
fill_buffer_writefunc(void *priv, const char *buffer, size_t bytes)
{
  int mask = 0;
  int i;

  priv_t *p = (priv_t *) priv;
  unsigned char *where = p->base_addr + p->offset;
  const unsigned char *xbuffer = (const unsigned char *)buffer;

  if (strcmp(p->output_type, "Whitescale") == 0)
    {
      memcpy(where, xbuffer, bytes);
      p->offset += bytes;
    }
  else if (strcmp(p->output_type, "Grayscale") == 0)
    {
      for (i = 0; i < bytes; i++)
	where[i] = ~xbuffer[i];
      p->offset += bytes;
    }
  else if (strcmp(p->output_type, "RGB") == 0)
    {
      int pixels = bytes / 3;
      if (bytes + p->offset > p->limit)
	bytes = p->limit - p->offset;
      if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(red_button)))
	mask |= 1;
      if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(green_button)))
	mask |= 2;
      if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(blue_button)))
	mask |= 4;

      memset(where, 0, pixels * 3);
      for (i = 0; i < pixels; i++)
	{
	  if (mask & 1)
	    where[0] = xbuffer[0];
	  if (mask & 2)
	    where[1] = xbuffer[1];
	  if (mask & 4)
	    where[2] = xbuffer[2];
	  where += 3;
	  xbuffer += 3;
	}
      p->offset += pixels * 3;
    }
  else if (strcmp(p->output_type, "CMY") == 0)
    {
      int pixels = bytes / 3;
      if (bytes + p->offset > p->limit)
	bytes = p->limit - p->offset;
      if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(cyan_button)))
	mask |= 1;
      if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(magenta_button)))
	mask |= 2;
      if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(yellow_button)))
	mask |= 4;

      memset(where, 0xff, pixels * 3);
      for (i = 0; i < pixels; i++)
	{
	  if (mask & 1)
	    where[0] = ~xbuffer[0];
	  if (mask & 2)
	    where[1] = ~xbuffer[1];
	  if (mask & 4)
	    where[2] = ~xbuffer[2];
	  where += 3;
	  xbuffer += 3;
	}
      p->offset += pixels * 3;
    }
  else
    {
      int pixels = bytes / 4;
      if (bytes + p->offset > p->limit)
	bytes = p->limit - p->offset;
      if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(cyan_button)))
	mask |= 1;
      if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(magenta_button)))
	mask |= 2;
      if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(yellow_button)))
	mask |= 4;
      if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(black_button)))
	mask |= 8;

      memset(where, 0xff, pixels * 3);
      for (i = 0; i < pixels; i++)
	{
	  if (mask & 8)
	    {
	      where[0] -= xbuffer[3];
	      where[1] -= xbuffer[3];
	      where[2] -= xbuffer[3];
	    }
	  if (mask & 1)
	    {
	      if (where[0] < xbuffer[0])
		where[0] = 0;
	      else
		where[0] -= xbuffer[0];
	    }
	  if (mask & 2)
	    {
	      if (where[1] < xbuffer[1])
		where[1] = 0;
	      else
		where[1] -= xbuffer[1];
	    }
	  if (mask & 4)
	    {
	      if (where[2] < xbuffer[2])
		where[2] = 0;
	      else
		where[2] -= xbuffer[2];
	    }
	  where += 3;
	  xbuffer += 4;
	}
      p->offset += pixels * 3;
    }
}

/*
 * update_adjusted_thumbnail()
 */

static void
redraw_color_swatch (void)
{
  static GdkGC *gc = NULL;
  static GdkColormap *cmap;

  if (adjusted_thumbnail_data && swatch && swatch->widget.window)
    {
      if (gc == NULL)
	{
	  gc = gdk_gc_new (swatch->widget.window);
	  cmap = gtk_widget_get_colormap (GTK_WIDGET(swatch));
	}

      if (!print_mode_is_color(pv->v))
	gdk_draw_gray_image(swatch->widget.window, gc, 0, 0,
			    thumbnail_w, thumbnail_h, GDK_RGB_DITHER_NORMAL,
			    adjusted_thumbnail_data, thumbnail_w);
      else
	gdk_draw_rgb_image(swatch->widget.window, gc, 0, 0,
			   thumbnail_w, thumbnail_h, GDK_RGB_DITHER_NORMAL,
			   adjusted_thumbnail_data, 3 * thumbnail_w);
    }
}

static void
initialize_thumbnail(void)
{
  int i;
  if (stpui_get_thumbnail_func())
    {
      const guchar *internal_thumbnail_data;
      /*
       * Fetch a thumbnail of the image we're to print from the Gimp.
       */

      thumbnail_w = thumbnail_hintw;
      thumbnail_h = thumbnail_hinth;
      internal_thumbnail_data =
	(stpui_get_thumbnail_func()) (stpui_get_thumbnail_data(), &thumbnail_w,
				      &thumbnail_h, &thumbnail_bpp, 0);
      if (adjusted_thumbnail_data)
	g_free(adjusted_thumbnail_data);
      if (preview_thumbnail_data)
	g_free(preview_thumbnail_data);
      if (thumbnail_data)
	g_free(thumbnail_data);

      if (internal_thumbnail_data)
	{
	  /*
	   * thumbnail_w and thumbnail_h have now been adjusted to the actual
	   * thumbnail dimensions.  Now initialize a color-adjusted version of
	   * the thumbnail.
	   */

	  adjusted_thumbnail_data = g_malloc (3 * thumbnail_w * thumbnail_h);
	  preview_thumbnail_data = g_malloc (3 * thumbnail_w * thumbnail_h);
	  thumbnail_data = g_malloc (3 * thumbnail_w * thumbnail_h);

	  switch (thumbnail_bpp)
	    {
	    case 1:
	      for (i = 0; i < thumbnail_w * thumbnail_h; i++)
		{
		  gint val = internal_thumbnail_data[i];
		  thumbnail_data[(3 * i) + 0] = val;
		  thumbnail_data[(3 * i) + 1] = val;
		  thumbnail_data[(3 * i) + 2] = val;
		}
	      break;
	    case 3:
	      memcpy(thumbnail_data, internal_thumbnail_data,
		     3 * thumbnail_w * thumbnail_h);
	      break;
	    case 2:
	      for (i = 0; i < thumbnail_w * thumbnail_h; i++)
		{
		  gint val = internal_thumbnail_data[2 * i];
		  gint alpha = internal_thumbnail_data[(2 * i) + 1];
		  thumbnail_data[(3 * i) +0] = val * alpha / 255 + 255 - alpha;
		  thumbnail_data[(3 * i) +1] = val * alpha / 255 + 255 - alpha;
		  thumbnail_data[(3 * i) +2] = val * alpha / 255 + 255 - alpha;
		}
	      break;
	    case 4:
	      for (i = 0; i < thumbnail_w * thumbnail_h; i++)
		{
		  gint r = internal_thumbnail_data[(4 * i)];
		  gint g = internal_thumbnail_data[(4 * i) + 1];
		  gint b = internal_thumbnail_data[(4 * i) + 2];
		  gint alpha = internal_thumbnail_data[(4 * i) + 3];
		  thumbnail_data[(3 * i) + 0] = r * alpha / 255 + 255 - alpha;
		  thumbnail_data[(3 * i) + 1] = g * alpha / 255 + 255 - alpha;
		  thumbnail_data[(3 * i) + 2] = b * alpha / 255 + 255 - alpha;
		}
	      break;
	    default:
	      break;
	      /* Whatever */
	    }
	  thumbnail_bpp = 3;
	}
      else
	{
	  thumbnail_h = 0;
	  thumbnail_w = 0;
	}
    }
  else
    {
      thumbnail_h = 0;
      thumbnail_w = 0;
    }
}

static int
compute_thumbnail(const stp_vars_t *v)
{
  priv_t priv;
  int answer = 1;
  stp_image_t *im = stpui_image_thumbnail_new(thumbnail_data, thumbnail_w,
					      thumbnail_h, thumbnail_bpp);
  stp_vars_t *nv = stp_vars_create_copy(v);
  const char *output_type = stp_describe_output(nv);
  stp_set_top(nv, 0);
  stp_set_left(nv, 0);
  stp_set_width(nv, thumbnail_w);
  stp_set_height(nv, thumbnail_h);
  stp_set_outfunc(nv, fill_buffer_writefunc);
  stp_set_outdata(nv, &priv);
  stp_set_errfunc(nv, stpui_get_errfunc());
  stp_set_errdata(nv, stpui_get_errdata());
  if (strcmp(output_type, "Whitescale") == 0)
    {
      gtk_widget_hide(output_color_vbox);
      priv.bpp = 1;
      priv.output_type = "Whitescale";
      stp_set_string_parameter(nv, "InkType", "RGBGray");
    }
  else if (strcmp(output_type, "Grayscale") == 0)
    {
      gtk_widget_hide(output_color_vbox);
      priv.bpp = 1;
      priv.output_type = "Grayscale";
      stp_set_string_parameter(nv, "InkType", "CMYGray");
    }
  else if (strcmp(output_type, "CMY") == 0)
    {
      gtk_widget_hide(black_button);
      gtk_widget_hide(red_button);
      gtk_widget_hide(green_button);
      gtk_widget_hide(blue_button);
      gtk_widget_show(cyan_button);
      gtk_widget_show(magenta_button);
      gtk_widget_show(yellow_button);
      gtk_widget_show(output_color_vbox);
      priv.bpp = 3;
      priv.output_type = "CMY";
      stp_set_string_parameter(nv, "InkType", "CMY");
    }
  else if (strcmp(output_type, "RGB") == 0)
    {
      gtk_widget_hide(cyan_button);
      gtk_widget_hide(magenta_button);
      gtk_widget_hide(yellow_button);
      gtk_widget_hide(black_button);
      gtk_widget_show(red_button);
      gtk_widget_show(green_button);
      gtk_widget_show(blue_button);
      gtk_widget_show(output_color_vbox);
      priv.bpp = 3;
      priv.output_type = "RGB";
      stp_set_string_parameter(nv, "InkType", "RGB");
    }
  else
    {
      gtk_widget_hide(red_button);
      gtk_widget_hide(green_button);
      gtk_widget_hide(blue_button);
      gtk_widget_show(cyan_button);
      gtk_widget_show(magenta_button);
      gtk_widget_show(yellow_button);
      gtk_widget_show(black_button);
      gtk_widget_show(output_color_vbox);
      priv.bpp = 4;
      priv.output_type = "CMYK";
      stp_set_string_parameter(nv, "InkType", "CMYK");
    }
  stp_set_page_height(nv, thumbnail_h);
  stp_set_page_width(nv, thumbnail_w);
  stp_set_driver(nv, "raw-data-8");
  stp_set_float_parameter(nv, "Density", 1.0);
  stp_set_float_parameter(nv, "InkLimit", 0);
  stp_set_string_parameter(nv, "InputImageType", "RGB");

  priv.base_addr = adjusted_thumbnail_data;
  priv.offset = 0;
  priv.limit = thumbnail_bpp * thumbnail_h * thumbnail_w;

  if (stp_verify(nv) != 1 || stp_print(nv, im) != 1)
    {
      answer = 0;
      fprintf(stderr, "Could not print thumbnail!\n");
    }
  stp_vars_destroy(nv);
  thumbnail_needs_rebuild = FALSE;
  return answer;
}

static void
set_thumbnail_orientation(void)
{
  gint           x, y;
  gint preview_limit = (thumbnail_h * thumbnail_w) - 1;
  gint bpp;
  if (!print_mode_is_color(pv->v))
    bpp = 1;
  else
    bpp = 3;
  switch (physical_orientation)
    {
    case ORIENT_PORTRAIT:
      memcpy(preview_thumbnail_data, adjusted_thumbnail_data,
	     bpp * thumbnail_h * thumbnail_w);
      break;
    case ORIENT_SEASCAPE:
      for (x = 0; x < thumbnail_w; x++)
	for (y = 0; y < thumbnail_h; y++)
	  memcpy((preview_thumbnail_data +
		  bpp * (preview_limit - (x * thumbnail_h + y))),
		 (adjusted_thumbnail_data +
		  bpp * ((thumbnail_h - y - 1) * thumbnail_w + x)), bpp);
      break;

    case ORIENT_UPSIDEDOWN:
      for (x = 0; x < thumbnail_h * thumbnail_w; x++)
	memcpy((preview_thumbnail_data + bpp * (preview_limit - x)),
	       adjusted_thumbnail_data + bpp * x, bpp);
      break;
    case ORIENT_LANDSCAPE:
      for (x = 0; x < thumbnail_w; x++)
	for (y = 0; y < thumbnail_h; y++)
	  memcpy((preview_thumbnail_data + bpp * (x * thumbnail_h + y)),
		 (adjusted_thumbnail_data +
		  bpp * ((thumbnail_h - y - 1) * thumbnail_w + x)), bpp);
      break;
    }
}

static void
draw_arrow (GdkWindow *w,
            GdkGC     *gc,
            gint       paper_left,
            gint       paper_top)
{
  gint u  = preview_ppi/2;
  gint ox = paper_left + preview_ppi * paper_width / INCH / 2;
  gint oy = paper_top + preview_ppi * paper_height / INCH / 2;

  oy -= preview_ppi * paper_height / INCH / 4;
  if (oy < paper_top + u)
    oy = paper_top + u;
  gdk_draw_line (w, gc, ox, oy - u, ox - u, oy);
  gdk_draw_line (w, gc, ox, oy - u, ox + u, oy);
  gdk_draw_line (w, gc, ox, oy - u, ox, oy + u);
}

static void
create_valid_preview(guchar **preview_data)
{
  if (adjusted_thumbnail_data)
    {
      gint bpp = (print_mode_is_color(pv->v)) ? 3 : 1;
      gint v_denominator = preview_h > 1 ? preview_h - 1 : 1;
      gint v_numerator = (preview_thumbnail_h - 1) % v_denominator;
      gint v_whole = (preview_thumbnail_h - 1) / v_denominator;
      gint h_denominator = preview_w > 1 ? preview_w - 1 : 1;
      gint h_numerator = (preview_thumbnail_w - 1) % h_denominator;
      gint h_whole = (preview_thumbnail_w - 1) / h_denominator;
      gint adjusted_preview_width = bpp * preview_w;
      gint adjusted_thumbnail_width = bpp * preview_thumbnail_w;
      gint v_cur = 0;
      gint v_last = -1;
      gint v_error = v_denominator / 2;
      gint y;
      gint i;

      if (*preview_data)
	g_free (*preview_data);
      *preview_data = g_malloc (bpp * preview_h * preview_w);

      for (y = 0; y < preview_h; y++)
	{
	  guchar *outbuf = *preview_data + adjusted_preview_width * y;

	  if (v_cur == v_last)
	    memcpy (outbuf, outbuf-adjusted_preview_width,
		    adjusted_preview_width);
	  else
	    {
	      guchar *inbuf = preview_thumbnail_data - bpp
		+ adjusted_thumbnail_width * v_cur;

	      gint h_cur = 0;
	      gint h_last = -1;
	      gint h_error = h_denominator / 2;
	      gint x;

	      v_last = v_cur;
	      for (x = 0; x < preview_w; x++)
		{
		  if (h_cur == h_last)
		    {
		      for (i = 0; i < bpp; i++)
			outbuf[i] = outbuf[i - bpp];
		    }
		  else
		    {
		      inbuf += bpp * (h_cur - h_last);
		      h_last = h_cur;
		      for (i = 0; i < bpp; i++)
			outbuf[i] = inbuf[i];
		    }
		  outbuf += bpp;
		  h_cur += h_whole;
		  h_error += h_numerator;
		  if (h_error >= h_denominator)
		    {
		      h_error -= h_denominator;
		      h_cur++;
		    }
		}
	    }
	  v_cur += v_whole;
	  v_error += v_numerator;
	  if (v_error >= v_denominator)
	    {
	      v_error -= v_denominator;
	      v_cur++;
	    }
	}
      preview_valid = TRUE;
    }
}

/*
 *  preview_update_callback() -
 */
static void
do_preview_thumbnail (void)
{
  static GdkGC	*gc    = NULL;
  static GdkGC  *gcinv = NULL;
  static GdkGC  *gcset = NULL;
  static guchar *preview_data = NULL;
  gint    opx = preview_x;
  gint    opy = preview_y;
  gint    oph = preview_h;
  gint    opw = preview_w;
  gint paper_display_left, paper_display_top;
  gint printable_display_left, printable_display_top;
  gint paper_display_width, paper_display_height;
  gint printable_display_width, printable_display_height;
  int l_bottom = stp_get_top(pv->v) + stp_get_height(pv->v);
  int l_right = stp_get_left(pv->v) + stp_get_width(pv->v);

  preview_ppi = preview_size_horiz * FINCH / (gdouble) paper_width;

  if (preview_ppi > preview_size_vert * FINCH / (gdouble) paper_height)
    preview_ppi = preview_size_vert * FINCH / (gdouble) paper_height;
  if (preview_ppi > MAX_PREVIEW_PPI)
    preview_ppi = MAX_PREVIEW_PPI;

  if (preview == NULL || preview->widget.window == NULL)
    return;
  /*
   * Center the page on the preview
   */
  paper_display_width = MAX(3, ROUNDUP(preview_ppi * paper_width, INCH));
  paper_display_height = MAX(3, ROUNDUP(preview_ppi * paper_height, INCH));

  paper_display_left = (preview_size_horiz - paper_display_width) / 2;
  paper_display_top = (preview_size_vert - paper_display_height) / 2;

  printable_display_width =
    MAX(3, ROUNDUP(preview_ppi * printable_width, INCH));
  printable_display_height =
    MAX(3, ROUNDUP(preview_ppi * printable_height, INCH));

  printable_display_left = paper_display_left + preview_ppi * left / INCH;
  printable_display_top  = paper_display_top + preview_ppi * top / INCH ;

  preview_x =
    1 + paper_display_left + preview_ppi * stp_get_left (pv->v) / INCH;
  preview_y =
    1 + paper_display_top + preview_ppi * stp_get_top (pv->v) / INCH;

  if (!preview_valid)
    {
      gint preview_r = 1 + paper_display_left + preview_ppi * l_right / INCH;
      gint preview_b = 1 + paper_display_top + preview_ppi * l_bottom / INCH;
      preview_w = preview_r - preview_x;
      preview_h = preview_b - preview_y;
      if (preview_w >= printable_display_width)
	preview_w = printable_display_width - 1;
      if (preview_h >= printable_display_height)
	preview_h = printable_display_height - 1;
    }

  if (preview_w + preview_x > printable_display_left + printable_display_width)
    preview_x--;
  if (preview_h + preview_y > printable_display_top + printable_display_height)
    preview_y--;

  if (gc == NULL)
    {
      gc = gdk_gc_new (preview->widget.window);
      gcinv = gdk_gc_new (preview->widget.window);
      gdk_gc_set_function (gcinv, GDK_INVERT);
      gcset = gdk_gc_new (preview->widget.window);
      gdk_gc_set_function (gcset, GDK_SET);
    }

  if (!preview_valid)
    create_valid_preview(&preview_data);

  if (printable_display_left < paper_display_left)
    printable_display_left = paper_display_left;
  if (printable_display_top < paper_display_top)
    printable_display_top = paper_display_top;
  if (printable_display_left + printable_display_width >
      paper_display_left + paper_display_width)
    printable_display_width =
      paper_display_width - (paper_display_left - printable_display_left);
  if (printable_display_top + printable_display_height >
      paper_display_top + paper_display_height)
    printable_display_height =
      paper_display_height - (paper_display_top - printable_display_top);
  if (need_exposure)
    {
      if (!frame_valid)
	{
	  gdk_window_clear (preview->widget.window);
	  frame_valid = TRUE;
	}
      /* draw paper frame */
      gdk_draw_rectangle (preview->widget.window, gc, 0,
			  paper_display_left, paper_display_top,
			  paper_display_width, paper_display_height);

      /* draw printable frame */
      gdk_draw_rectangle (preview->widget.window, gc, 0,
			  printable_display_left, printable_display_top,
			  printable_display_width, printable_display_height);
      need_exposure = FALSE;
    }
  else if (!frame_valid)
    {
      gdk_window_clear (preview->widget.window);
      /* draw paper frame */
      gdk_draw_rectangle (preview->widget.window, gc, 0,
			  paper_display_left, paper_display_top,
			  paper_display_width, paper_display_height);

      /* draw printable frame */
      gdk_draw_rectangle (preview->widget.window, gc, 0,
			  printable_display_left, printable_display_top,
			  printable_display_width, printable_display_height);
      frame_valid = TRUE;
    }
  else
    {
      if (opx + opw <= preview_x || opy + oph <= preview_y ||
	  preview_x + preview_w <= opx || preview_y + preview_h <= opy)
        {
          gdk_window_clear_area (preview->widget.window, opx, opy, opw, oph);
        }
      else
	{
	  if (opx < preview_x)
	    gdk_window_clear_area (preview->widget.window,
                                   opx, opy, preview_x - opx, oph);
	  if (opy < preview_y)
	    gdk_window_clear_area (preview->widget.window,
                                   opx, opy, opw, preview_y - opy);
	  if (opx + opw > preview_x + preview_w)
	    gdk_window_clear_area (preview->widget.window,
                                   preview_x + preview_w, opy,
                                   (opx + opw) - (preview_x + preview_w), oph);
	  if (opy + oph > preview_y + preview_h)
	    gdk_window_clear_area (preview->widget.window,
                                   opx, preview_y + preview_h,
                                   opw, (opy + oph) - (preview_y + preview_h));
	}
    }

  draw_arrow (preview->widget.window, gcset, paper_display_left,
	      paper_display_top);

  if (!preview_valid)
    gdk_draw_rectangle (preview->widget.window, gc, 1,
			preview_x, preview_y, preview_w, preview_h);
  else if (!print_mode_is_color(pv->v))
    gdk_draw_gray_image (preview->widget.window, gc,
			 preview_x, preview_y, preview_w, preview_h,
			 GDK_RGB_DITHER_NORMAL, preview_data, preview_w);
  else
    gdk_draw_rgb_image (preview->widget.window, gc,
			preview_x, preview_y, preview_w, preview_h,
			GDK_RGB_DITHER_NORMAL, preview_data, 3 * preview_w);

  /* If we're printing full bleed, redisplay the paper frame */
  if (preview_x <= paper_display_left || opx <= paper_display_left ||
      preview_y <= paper_display_top || opy <= paper_display_top ||
      preview_x + preview_w >= paper_display_left + paper_display_width ||
      opx + opw >= paper_display_left + paper_display_width ||
      preview_y + preview_h >= paper_display_top + paper_display_height ||
      opy + oph >= paper_display_top + paper_display_height)
    gdk_draw_rectangle (preview->widget.window, gc, 0,
			paper_display_left, paper_display_top,
			paper_display_width, paper_display_height);


  /* draw orientation arrow pointing to top-of-paper */
  draw_arrow (preview->widget.window, gcinv, paper_display_left,
	      paper_display_top);
  gdk_flush();
}

static gboolean
idle_preview_thumbnail(gpointer data)
{
  if (thumbnail_data && adjusted_thumbnail_data && do_update_thumbnail)
    {
      thumbnail_update_pending = TRUE;
      set_orientation(pv->orientation);
      if (thumbnail_needs_rebuild && compute_thumbnail(pv->v))
	{
	  set_thumbnail_orientation();
	  redraw_color_swatch ();
	}
      do_preview_thumbnail();
    }
  thumbnail_update_pending = FALSE;
  return FALSE;
}

static void
update_adjusted_thumbnail (gboolean regenerate_image)
{
  if (regenerate_image)
    thumbnail_needs_rebuild = TRUE;
  preview_update ();
}

static void
preview_expose (void)
{
  need_exposure = TRUE;
  preview_update ();
}

static void
preview_update (void)
{
  gdouble max_ppi_scaling;   /* Maximum PPI for current page size */
  gdouble min_ppi_scaling;   /* Minimum PPI for current page size */

  suppress_preview_update++;
  compute_printable_region();

  if (pv->scaling < 0)
    {
      gdouble twidth;

      compute_scaling_limits(&min_ppi_scaling, &max_ppi_scaling);

      if (pv->scaling < 0 && pv->scaling > -min_ppi_scaling)
	pv->scaling = -min_ppi_scaling;

      twidth = (FINCH * (gdouble) image_width / -pv->scaling);
      print_width = twidth + .5;
      print_height = (twidth * (gdouble) image_height / image_width) + .5;
      GTK_ADJUSTMENT (scaling_adjustment)->lower = min_ppi_scaling;
      GTK_ADJUSTMENT (scaling_adjustment)->upper = max_ppi_scaling;
      GTK_ADJUSTMENT (scaling_adjustment)->value = -pv->scaling;

      if (!suppress_scaling_adjustment)
	{
	  suppress_preview_reset++;
	  gtk_adjustment_changed (GTK_ADJUSTMENT (scaling_adjustment));
	  suppress_scaling_callback = TRUE;
	  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (scaling_ppi), TRUE);
	  suppress_scaling_callback = FALSE;
	  gtk_adjustment_value_changed (GTK_ADJUSTMENT (scaling_adjustment));
	  suppress_preview_reset--;
	}
    }
  else if (auto_paper_size)
    {
      gdouble twidth = printable_width * pv->scaling / 100;

      print_width = twidth + .5;
      print_height =
	(twidth * (gdouble) image_height / (gdouble) image_width) + .5;
    }
  else
    {
      /* we do pv->scaling % of height or width, whatever is less */
      /* this is relative to printable size */
      if (image_width * printable_height > printable_width * image_height)
	/* if image_width/image_height > printable_width/printable_height */
	/* i.e. if image is wider relative to its height than the width
	   of the printable area relative to its height */
	{
	  gdouble twidth = printable_width * pv->scaling / 100;

	  print_width = twidth + .5;
	  print_height =
	    (twidth * (gdouble) image_height / (gdouble) image_width) + .5;
	}
      else
	{
	  gdouble theight = printable_height * pv->scaling /100;

	  print_height = theight + .5;
	  print_width =
	    (theight * (gdouble) image_width / (gdouble) image_height) + .5;
	}
    }

  if (auto_paper_size)
    set_media_size(stp_get_string_parameter(pv->v, "PageSize"));

  stp_set_width(pv->v, print_width);
  stp_set_height(pv->v, print_height);

  if (pv->invalid_mask & INVALID_LEFT)
    stp_set_left (pv->v, (paper_width - print_width) / 2);

  if (stp_get_left(pv->v) < left)
    stp_set_left(pv->v, left);

  if (stp_get_left (pv->v) > right - print_width)
    stp_set_left (pv->v, right - print_width);

  if (pv->invalid_mask & INVALID_TOP)
    stp_set_top (pv->v, ((paper_height - print_height) / 2));
  if (stp_get_top(pv->v) < top)
    stp_set_top(pv->v, top);

  if (stp_get_top (pv->v) > bottom - print_height)
    stp_set_top (pv->v, bottom - print_height);

  pv->invalid_mask = 0;

  set_all_entry_values();
  suppress_preview_update--;

  /* draw image */
  if (! suppress_preview_update && !thumbnail_update_pending)
    {
      thumbnail_update_pending = TRUE;
      g_idle_add(idle_preview_thumbnail, NULL);
    }
}

/*
 *  preview_button_callback() -
 */
static void
preview_button_callback (GtkWidget      *widget,
			 GdkEventButton *event,
			 gpointer        data)
{
  if (event->type == GDK_BUTTON_PRESS)
    {
      if (preview_active == 0)
	{
	  mouse_x = event->x;
	  mouse_y = event->y;
	  orig_left = stp_get_left (pv->v);
	  orig_top = stp_get_top (pv->v);
	  mouse_button = event->button;
	  buttons_mask = 1 << event->button;
	  buttons_pressed++;
	  preview_active = 1;
	  stpui_disable_help();
	  move_constraint =
	    (event->state & GDK_SHIFT_MASK) ? MOVE_CONSTRAIN : MOVE_ANY;
	  if (event->state & GDK_CONTROL_MASK)
	    move_constraint |= MOVE_GRID;
	}
      else if ((buttons_mask & (1 << event->button)) == 0)
	{
	  if (preview_active == 1)
	    {
	      stpui_enable_help();
	      preview_active = -1;
	      stp_set_left (pv->v, orig_left);
	      stp_set_top (pv->v, orig_top);
	      preview_update ();
	    }
	  buttons_mask |= 1 << event->button;
	  buttons_pressed++;
	}
    }
  else if (event->type == GDK_BUTTON_RELEASE)
    {
      buttons_pressed--;
      buttons_mask &= ~(1 << event->button);
      if (buttons_pressed == 0)
	{
	  stpui_enable_help ();
	  preview_active = 0;
	}
    }
}

/*
 *  preview_motion_callback() -
 */
static void
preview_motion_callback (GtkWidget      *widget,
			 GdkEventMotion *event,
			 gpointer        data)
{

  gint old_top  = stp_get_top (pv->v);
  gint old_left = stp_get_left (pv->v);
  gint new_top  = old_top;
  gint new_left = old_left;
  gint steps;
  if (preview_active != 1 || event->type != GDK_MOTION_NOTIFY)
    return;
  if (move_constraint == MOVE_CONSTRAIN)
    {
      int dx = abs(event->x - mouse_x);
      int dy = abs(event->y - mouse_y);
      if (dx > dy && dx > 3)
	move_constraint = MOVE_HORIZONTAL;
      else if (dy > dx && dy > 3)
	move_constraint = MOVE_VERTICAL;
      else
	return;
    }

  switch (mouse_button)
    {
    case 1:
      if (move_constraint & MOVE_VERTICAL)
	new_top = orig_top + INCH * (event->y - mouse_y) / preview_ppi;
      if (move_constraint & MOVE_HORIZONTAL)
	new_left = orig_left + INCH * (event->x - mouse_x) / preview_ppi;
      break;
    case 3:
      if (move_constraint & MOVE_VERTICAL)
	new_top = orig_top + event->y - mouse_y;
      if (move_constraint & MOVE_HORIZONTAL)
	new_left = orig_left + event->x - mouse_x;
      break;
    case 2:
      if (move_constraint & MOVE_HORIZONTAL)
	{
	  gint increment_width =
	    ((move_constraint & MOVE_GRID) && pv->scaling > 0) ?
	    printable_width * pv->scaling / 100 : print_width;
	  gint x_threshold = MAX (1, (preview_ppi * increment_width) / INCH);
	  if (event->x > mouse_x)
	    steps = MIN((event->x - mouse_x) / x_threshold,
			((right - orig_left) / increment_width) - 1);
	  else
	    steps = -(MIN((mouse_x - event->x) / x_threshold,
			  (orig_left - left) / increment_width));
	  new_left = orig_left + steps * increment_width;
	}
      if (move_constraint & MOVE_VERTICAL)
	{
	  gint increment_height =
	    ((move_constraint & MOVE_GRID) && pv->scaling > 0) ?
	    printable_height * pv->scaling / 100 : print_height;
	  gint y_threshold = MAX (1, (preview_ppi * increment_height) / INCH);
	  if (event->y > mouse_y)
	    steps = MIN((event->y - mouse_y) / y_threshold,
			((bottom - orig_top) / increment_height) - 1);
	  else
	    steps = -(MIN((mouse_y - event->y) / y_threshold,
			  (orig_top - top) / increment_height));
	  new_top = orig_top + steps * increment_height;
	}
      break;
    }

  if (new_top < top)
    new_top = top;
  if (new_top > bottom - print_height)
    new_top = bottom - print_height;
  if (new_left < left)
    new_left = left;
  if (new_left > right - print_width)
    new_left = right - print_width;

  if (new_top != old_top || new_left != old_left)
    {
      stp_set_top (pv->v, new_top);
      stp_set_left (pv->v, new_left);
      preview_update ();
    }
}

static void
color_update (GtkAdjustment *adjustment)
{
  int i;
  for (i = 0; i < current_option_count; i++)
    {
      option_t *opt = &(current_options[i]);
      if (opt->fast_desc->p_type == STP_PARAMETER_TYPE_DOUBLE &&
	  opt->fast_desc->p_level <= MAXIMUM_PARAMETER_LEVEL &&
	  opt->info.flt.adjustment &&
	  adjustment == GTK_ADJUSTMENT(opt->info.flt.adjustment))
	{
	  invalidate_preview_thumbnail ();
	  if (stp_get_float_parameter(pv->v, opt->fast_desc->name) !=
	      adjustment->value)
	    {
	      stp_set_float_parameter(pv->v, opt->fast_desc->name,
				      adjustment->value);
	      update_adjusted_thumbnail(TRUE);
	    }
	}
    }
}

static void
dimension_update (GtkAdjustment *adjustment)
{
  int i;
  gdouble unit_scaler = units[pv->unit].scale;
  for (i = 0; i < current_option_count; i++)
    {
      option_t *opt = &(current_options[i]);
      if (opt->fast_desc->p_type == STP_PARAMETER_TYPE_DIMENSION &&
	  opt->fast_desc->p_level <= MAXIMUM_PARAMETER_LEVEL &&
	  opt->info.flt.adjustment &&
	  adjustment == GTK_ADJUSTMENT(opt->info.flt.adjustment))
	{
	  int new_value = (adjustment->value + (.5 / unit_scaler)) * unit_scaler;
	  invalidate_preview_thumbnail ();
	  if (stp_get_dimension_parameter(pv->v, opt->fast_desc->name) !=
	      new_value)
	    {
	      stp_set_dimension_parameter(pv->v, opt->fast_desc->name,
					  new_value);
	      update_adjusted_thumbnail(FALSE);
	    }
	}
    }
}

static void
set_controls_active (GtkObject *checkbutton, gpointer xopt)
{
  option_t *opt = (option_t *) xopt;
  stp_parameter_t desc;
  gboolean setting =
    gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(checkbutton));
  if (setting && opt->fast_desc->p_level <= MAXIMUM_PARAMETER_LEVEL)
    {
      switch (opt->fast_desc->p_type)
	{
	case STP_PARAMETER_TYPE_DOUBLE:
	  set_adjustment_active(opt, TRUE, FALSE);
	  if (! stp_check_float_parameter(pv->v, opt->fast_desc->name,
					  STP_PARAMETER_INACTIVE))
	    {
	      stp_describe_parameter(pv->v, opt->fast_desc->name, &desc);
	      stp_set_float_parameter(pv->v, opt->fast_desc->name,
				      desc.deflt.dbl);
	      stp_parameter_description_destroy(&desc);
	    }
	  stp_set_float_parameter_active(pv->v, opt->fast_desc->name,
					 STP_PARAMETER_ACTIVE);
	  break;
	case STP_PARAMETER_TYPE_DIMENSION:
	  set_adjustment_active(opt, TRUE, FALSE);
	  if (! stp_check_dimension_parameter(pv->v, opt->fast_desc->name,
					  STP_PARAMETER_INACTIVE))
	    {
	      stp_describe_parameter(pv->v, opt->fast_desc->name, &desc);
	      stp_set_dimension_parameter(pv->v, opt->fast_desc->name,
					  desc.deflt.dimension);
	      stp_parameter_description_destroy(&desc);
	    }
	  stp_set_dimension_parameter_active(pv->v, opt->fast_desc->name,
					     STP_PARAMETER_ACTIVE);
	  break;
	case STP_PARAMETER_TYPE_CURVE:
	  set_curve_active(opt, TRUE, FALSE);
	  if (! stp_check_curve_parameter(pv->v, opt->fast_desc->name,
					  STP_PARAMETER_INACTIVE))
	    {
	      stp_describe_parameter(pv->v, opt->fast_desc->name, &desc);
	      stp_set_curve_parameter(pv->v, opt->fast_desc->name,
				      desc.deflt.curve);
	      stp_parameter_description_destroy(&desc);
	    }
	  stp_set_curve_parameter_active(pv->v, opt->fast_desc->name,
					 STP_PARAMETER_ACTIVE);
	  break;
	case STP_PARAMETER_TYPE_STRING_LIST:
	  set_combo_active(opt, TRUE, FALSE);
	  if (! stp_check_string_parameter(pv->v, opt->fast_desc->name,
					   STP_PARAMETER_INACTIVE))
	    {
	      stp_describe_parameter(pv->v, opt->fast_desc->name, &desc);
	      stp_set_string_parameter(pv->v, opt->fast_desc->name,
				       desc.deflt.str);
	      stp_parameter_description_destroy(&desc);
	    }
	  stp_set_string_parameter_active(pv->v, opt->fast_desc->name,
					  STP_PARAMETER_ACTIVE);
	  break;
	case STP_PARAMETER_TYPE_BOOLEAN:
	  set_bool_active(opt, TRUE, FALSE);
	  if (! stp_check_boolean_parameter(pv->v, opt->fast_desc->name,
					    STP_PARAMETER_INACTIVE))
	    {
	      stp_describe_parameter(pv->v, opt->fast_desc->name, &desc);
	      stp_set_boolean_parameter(pv->v, opt->fast_desc->name,
					desc.deflt.boolean);
	      stp_parameter_description_destroy(&desc);
	    }
	  stp_set_boolean_parameter_active(pv->v, opt->fast_desc->name,
					   STP_PARAMETER_ACTIVE);
	  break;
	default:
	  break;
	}
    }
  else
    {
      switch (opt->fast_desc->p_type)
	{
	case STP_PARAMETER_TYPE_DOUBLE:
	  set_adjustment_active(opt, FALSE, FALSE);
	  stp_set_float_parameter_active(pv->v, opt->fast_desc->name,
					 STP_PARAMETER_INACTIVE);
	  break;
	case STP_PARAMETER_TYPE_DIMENSION:
	  set_adjustment_active(opt, FALSE, FALSE);
	  stp_set_dimension_parameter_active(pv->v, opt->fast_desc->name,
					     STP_PARAMETER_INACTIVE);
	  break;
	case STP_PARAMETER_TYPE_CURVE:
	  set_curve_active(opt, FALSE, FALSE);
	  stp_set_curve_parameter_active(pv->v, opt->fast_desc->name,
					 STP_PARAMETER_INACTIVE);
	  break;
	case STP_PARAMETER_TYPE_STRING_LIST:
	  set_combo_active(opt, FALSE, FALSE);
	  stp_set_string_parameter_active(pv->v, opt->fast_desc->name,
					  STP_PARAMETER_INACTIVE);
	  break;
	case STP_PARAMETER_TYPE_BOOLEAN: /* ??? */
	  set_bool_active(opt, FALSE, FALSE);
	  stp_set_boolean_parameter_active(pv->v, opt->fast_desc->name,
					   STP_PARAMETER_INACTIVE);
	  break;
	default:
	  break;
	}
    }
  invalidate_preview_thumbnail();
  update_adjusted_thumbnail(TRUE);
}

static void
set_color_defaults (void)
{
  int i;
  for (i = 0; i < current_option_count; i++)
    {
      option_t *opt = &(current_options[i]);
      if (opt->fast_desc->p_level <= MAXIMUM_PARAMETER_LEVEL &&
	  opt->fast_desc->p_class == STP_PARAMETER_CLASS_OUTPUT &&
	  opt->is_active && !opt->fast_desc->read_only)
	{
	  stp_parameter_activity_t active;
	  gdouble unit_scaler;
	  switch (opt->fast_desc->p_type)
	    {
	    case STP_PARAMETER_TYPE_DOUBLE:
	      active =
		stp_get_float_parameter_active(pv->v, opt->fast_desc->name);
	      stp_set_float_parameter(pv->v, opt->fast_desc->name,
				      opt->info.flt.deflt);
	      stp_set_float_parameter_active(pv->v, opt->fast_desc->name,
					     active);
	      break;
	    case STP_PARAMETER_TYPE_DIMENSION:
	      unit_scaler = units[pv->unit].scale;
	      active =
		stp_get_dimension_parameter_active(pv->v,
						   opt->fast_desc->name);
	      stp_set_dimension_parameter(pv->v, opt->fast_desc->name,
					  opt->info.flt.deflt * unit_scaler);
	      stp_set_dimension_parameter_active(pv->v, opt->fast_desc->name,
						 active);
	      break;
	    case STP_PARAMETER_TYPE_BOOLEAN:
	      active =
		stp_get_boolean_parameter_active(pv->v, opt->fast_desc->name);
	      stp_set_boolean_parameter(pv->v, opt->fast_desc->name,
					opt->info.bool.deflt);
	      stp_set_boolean_parameter_active(pv->v, opt->fast_desc->name,
					       active);
	      break;
	    case STP_PARAMETER_TYPE_STRING_LIST:
	      active =
		stp_get_string_parameter_active(pv->v, opt->fast_desc->name);
	      stp_set_string_parameter(pv->v, opt->fast_desc->name,
				       opt->info.list.default_val);
	      stp_set_string_parameter_active(pv->v, opt->fast_desc->name,
					      active);
	      break;
	    default:
	      break;
	    }
	}
    }

  do_color_updates ();
}

gint
stpui_do_print_dialog(void)
{
  /*
   * Get printrc options...
   */
  stpui_printrc_load ();

  /*
   * Print dialog window...
   */
  create_main_window();

  gtk_main ();
  gdk_flush ();

  /*
   * Set printrc options...
   */
  if (saveme)
    stpui_printrc_save ();

  /*
   * Return ok/cancel...
   */
  return (runme);
}
