/*
 * Geeqie
 * (C) 2004 John Ellis
 * Copyright (C) 2008 - 2009 The Geeqie Team
 *
 * Author: John Ellis
 *
 * This software is released under the GNU General Public License (GNU GPL).
 * Please read the included file COPYING for more information.
 * This software comes with no warranty of any kind, use at your own risk!
 */

#include "main.h"
#include "print.h"

#include "filedata.h"
#include "image.h"
#include "image-load.h"
#include "pixbuf_util.h"
#include "thumb.h"
#include "utilops.h"
#include "ui_bookmark.h"
#include "ui_menu.h"
#include "ui_misc.h"
#include "ui_utildlg.h"
#include "ui_fileops.h"
#include "ui_spinner.h"
#include "ui_tabcomp.h"

#include <locale.h>
#include <signal.h>
#include <glib/gprintf.h>

#define PRINT_LPR_COMMAND "lpr"
#define PRINT_LPR_CUSTOM  "lpr -P %s"
#define PRINT_LPR_QUERY   "lpstat -p"

#define PRINT_DLG_WIDTH 600
#define PRINT_DLG_HEIGHT 400

#define PRINT_DLG_PREVIEW_WIDTH 270
#define PRINT_DLG_PREVIEW_HEIGHT -1

/* these are in point units */
#define PRINT_MIN_WIDTH 100
#define PRINT_MIN_HEIGHT 100
#define PRINT_MAX_WIDTH 4000
#define PRINT_MAX_HEIGHT 4000

#define PRINT_MARGIN_DEFAULT 36

#define PRINT_PROOF_MIN_SIZE 8
#define PRINT_PROOF_MAX_SIZE 720
#define PRINT_PROOF_DEFAULT_SIZE 144
#define PRINT_PROOF_MARGIN 5

/* default page size */
#define PAGE_LAYOUT_WIDTH 850
#define PAGE_LAYOUT_HEIGHT 1100

/* preview uses 1 pixel = PRINT_PREVIEW_SCALE points */
#define PRINT_PREVIEW_SCALE 4

/* default dpi to use for printing ps image data */
#define PRINT_PS_DPI_DEFAULT 300.0
#define PRINT_PS_DPI_MIN 150.0
/* method to use when scaling down image data */
#define PRINT_PS_MAX_INTERP GDK_INTERP_BILINEAR
/* color to use as mask when printing transparent images */
#define PRINT_PS_MASK_R 255
#define PRINT_PS_MASK_G 255
#define PRINT_PS_MASK_B 255

/* padding between objects */
#define PRINT_TEXT_PADDING 3.0

/* locale for postscript portability */
#define POSTSCRIPT_LOCALE "C"


/* group and keys for saving prefs */
#define PRINT_PREF_GROUP	"print_settings"

#define PRINT_PREF_SAVE		"save_settings"

#define PRINT_PREF_OUTPUT	"output"
#define PRINT_PREF_FORMAT	"format"
#define PRINT_PREF_DPI		"dpi"
#define PRINT_PREF_UNITS	"units"
#define PRINT_PREF_SIZE		"size"
#define PRINT_PREF_ORIENTATION	"orientation"

#define PRINT_PREF_CUSTOM_WIDTH		"custom_width"
#define PRINT_PREF_CUSTOM_HEIGHT	"custom_height"
#define PRINT_PREF_MARGIN_LEFT		"margin_left"
#define PRINT_PREF_MARGIN_RIGHT		"margin_right"
#define PRINT_PREF_MARGIN_TOP		"margin_top"
#define PRINT_PREF_MARGIN_BOTTOM	"margin_bottom"
#define PRINT_PREF_PROOF_WIDTH		"proof_width"
#define PRINT_PREF_PROOF_HEIGHT		"proof_height"

#define PRINT_PREF_PRINTERC	"custom_printer"

#define PRINT_PREF_TEXT			"text"
#define PRINT_PREF_TEXTSIZE		"textsize"
#define PRINT_PREF_TEXTCOLOR_R		"textcolor_r"
#define PRINT_PREF_TEXTCOLOR_G		"textcolor_g"
#define PRINT_PREF_TEXTCOLOR_B		"textcolor_b"

#define PRINT_PREF_SOURCE		"source"
#define PRINT_PREF_LAYOUT		"layout"

#define PRINT_PREF_IMAGE_SCALE		"image_scale"

typedef enum {
	PRINT_SOURCE_IMAGE = 0,
	PRINT_SOURCE_SELECTION,
	PRINT_SOURCE_ALL,
	PRINT_SOURCE_COUNT
} PrintSource;

const gchar *print_source_text[] = {
	N_("Image"),
	N_("Selection"),
	N_("All"),
	NULL
};

typedef enum {
	PRINT_LAYOUT_IMAGE = 0,
	PRINT_LAYOUT_PROOF,
	PRINT_LAYOUT_COUNT
} PrintLayout;

const gchar *print_layout_text[] = {
	N_("One image per page"),
	N_("Proof sheet"),
	NULL
};

typedef enum {
	PRINT_OUTPUT_PS_LPR = 0,
	PRINT_OUTPUT_PS_CUSTOM,
	PRINT_OUTPUT_PS_FILE,
	PRINT_OUTPUT_RGB_FILE,
	PRINT_OUTPUT_COUNT
} PrintOutput;

const gchar *print_output_text[] = {
	N_("Default printer"),
	N_("Custom printer"),
	N_("PostScript file"),
	N_("Image file"),
	NULL,
	NULL
};

typedef enum {
	PRINT_FILE_JPG_LOW = 0,
	PRINT_FILE_JPG_NORMAL,
	PRINT_FILE_JPG_HIGH,
	PRINT_FILE_PNG,
	PRINT_FILE_COUNT
} PrintFileFormat;

const gchar *print_file_format_text[] = {
	N_("jpeg, low quality"),
	N_("jpeg, normal quality"),
	N_("jpeg, high quality"),
	"png",
	NULL
};

typedef enum {
	RENDER_FORMAT_PREVIEW,
	RENDER_FORMAT_RGB,
	RENDER_FORMAT_PS
} RenderFormat;

typedef enum {
	TEXT_INFO_FILENAME = 1 << 0,
	TEXT_INFO_FILEDATE = 1 << 1,
	TEXT_INFO_FILESIZE = 1 << 2,
	TEXT_INFO_DIMENSIONS = 1 << 3,
	TEXT_INFO_FILEPATH = 1 << 4
} TextInfo;

typedef enum {
	PAPER_UNIT_POINTS = 0,
	PAPER_UNIT_MM,
	PAPER_UNIT_CM,
	PAPER_UNIT_INCH,
	PAPER_UNIT_PICAS,
	PAPER_UNIT_COUNT
} PaperUnits;

typedef enum {
	PAPER_ORIENTATION_PORTRAIT = 0,
	PAPER_ORIENTATION_LANDSCAPE,
	PAPER_ORIENTATION_COUNT
} PaperOrientation;


typedef struct _PrintWindow PrintWindow;
struct _PrintWindow
{
	GenericDialog *dialog;

	FileData *source_fd;
	GList *source_selection;
	GList *source_list;

	PrintSource source;
	PrintLayout layout;
	PrintOutput output;

	gchar *output_path;
	gchar *output_custom;

	PrintFileFormat output_format;

	gdouble max_dpi;

	GtkWidget *notebook;

	GtkWidget *path_entry;
	GtkWidget *custom_entry;
	GtkWidget *path_format_menu;
	GtkWidget *max_dpi_menu;

	ImageWindow *layout_image;
	gdouble layout_width;
	gdouble layout_height;

	guint layout_idle_id; /* event source id */

	gdouble image_scale;

	GtkWidget *image_scale_spin;

	gdouble proof_width;
	gdouble proof_height;
	gint proof_columns;
	gint proof_rows;
	GList *proof_point;
	gint proof_position;
	gint proof_page;

	GtkWidget *proof_group;
	GtkWidget *proof_width_spin;
	GtkWidget *proof_height_spin;

	GtkWidget *paper_menu;
	GtkWidget *paper_width_spin;
	GtkWidget *paper_height_spin;
	GtkWidget *paper_units_menu;
	GtkWidget *paper_orientation_menu;

	GtkWidget *margin_left_spin;
	GtkWidget *margin_right_spin;
	GtkWidget *margin_top_spin;
	GtkWidget *margin_bottom_spin;

	PaperUnits paper_units;
	gint paper_size;
	gdouble paper_width;
	gdouble paper_height;
	PaperOrientation paper_orientation;

	gdouble margin_left;
	gdouble margin_right;
	gdouble margin_top;
	gdouble margin_bottom;

	GtkWidget *button_back;
	GtkWidget *button_next;
	GtkWidget *page_label;
	GtkWidget *print_button;

	gdouble single_scale;
	gdouble single_x;
	gdouble single_y;

	GtkWidget *single_scale_spin;

	TextInfo	text_fields;
	gint		text_points;
	guint8		text_r;
	guint8		text_g;
	guint8		text_b;

	gint save_settings;

	/* job printing */

	GenericDialog	*job_dialog;
	GtkWidget	*job_progress;
	GtkWidget	*job_progress_label;

	RenderFormat	 job_format;
	PrintOutput	 job_output;

	FILE		*job_file;
	FILE 		*job_pipe;
	gchar		*job_path;

	GdkPixbuf	*job_pixbuf;

	gint		 job_page;
	ImageLoader	*job_loader;
};


static void print_job_throw_error(PrintWindow *pw, const gchar *message);
static gint print_job_start(PrintWindow *pw, RenderFormat format, PrintOutput output);
static void print_job_close(PrintWindow *pw, gint error);
static void print_window_close(PrintWindow *pw);


/* misc utils */

static gboolean clip_region(gdouble x1, gdouble y1, gdouble w1, gdouble h1,
			    gdouble x2, gdouble y2, gdouble w2, gdouble h2,
			    gdouble *rx, gdouble *ry, gdouble *rw, gdouble *rh)
{
	if (x2 + w2 <= x1 || x2 >= x1 + w1 ||
	    y2 + h2 <= y1 || y2 >= y1 + h1)
		{
		return FALSE;
		}

	*rx = MAX(x1, x2);
	*rw = MIN((x1 + w1), (x2 + w2)) - *rx;

	*ry = MAX(y1, y2);
	*rh = MIN((y1 + h1), (y2 + h2)) - *ry;

	return TRUE;
}

static const gchar *print_output_name(PrintOutput output)
{
	if (output >= PRINT_OUTPUT_COUNT) return "";

	return _(print_output_text[output]);
}


/*
 *-----------------------------------------------------------------------------
 * data
 *-----------------------------------------------------------------------------
 */


typedef struct _PaperSize PaperSize;
struct _PaperSize {
	gchar *description;
	gint width;
	gint height;
	PaperOrientation orientation;
};

const gchar *print_paper_units[] = {
	N_("points"),
	N_("millimeters"),
	N_("centimeters"),
	N_("inches"),
	N_("picas"),
	NULL
};

const gchar *print_paper_orientation[] = {
	N_("Portrait"),
	N_("Landscape"),
	NULL
};

PaperSize print_paper_sizes[] = {
	{ N_("Custom"),		360,	720,	PAPER_ORIENTATION_PORTRAIT },
	{ N_("Letter"),		612,	792,	PAPER_ORIENTATION_PORTRAIT },	/* in 8.5 x 11 */
	{ N_("Legal"),		612,	1008,	PAPER_ORIENTATION_PORTRAIT },	/* in 8.5 x 14 */
	{ N_("Executive"),	522,	756,	PAPER_ORIENTATION_PORTRAIT },	/* in 7.25x 10.5 */
	{ "A0",			2384,	3370,	PAPER_ORIENTATION_PORTRAIT },	/* mm 841 x 1189 */
	{ "A1",			1684,	2384,	PAPER_ORIENTATION_PORTRAIT },	/* mm 594 x 841 */
	{ "A2",			1191,	1684,	PAPER_ORIENTATION_PORTRAIT },	/* mm 420 x 594 */
	{ "A3",			842,	1191,	PAPER_ORIENTATION_PORTRAIT },	/* mm 297 x 420 */
	{ "A4",			595,	842,	PAPER_ORIENTATION_PORTRAIT },	/* mm 210 x 297 */
	{ "A5",			420,	595,	PAPER_ORIENTATION_PORTRAIT },	/* mm 148 x 210 */
	{ "A6",			298,	420,	PAPER_ORIENTATION_PORTRAIT },	/* mm 105 x 148 */
	{ "B3",			1001,	1417,	PAPER_ORIENTATION_PORTRAIT },	/* mm 353 x 500 */
	{ "B4",			709,	1001,	PAPER_ORIENTATION_PORTRAIT },	/* mm 250 x 353 */
	{ "B5",			499,	709,	PAPER_ORIENTATION_PORTRAIT },	/* mm 176 x 250 */
	{ "B6",			354,	499,	PAPER_ORIENTATION_PORTRAIT },	/* mm 125 x 176 */
	{ N_("Envelope #10"),	297,	684,	PAPER_ORIENTATION_LANDSCAPE },	/* in 4.125 x 9.5 */
	{ N_("Envelope #9"),	279,	639,	PAPER_ORIENTATION_LANDSCAPE },	/* in 3.875 x 8.875 */
	{ N_("Envelope C4"),	649,	918,	PAPER_ORIENTATION_LANDSCAPE },	/* mm 229 x 324 */
	{ N_("Envelope C5"),	459,	649,	PAPER_ORIENTATION_LANDSCAPE },	/* mm 162 x 229 */
	{ N_("Envelope C6"),	323,	459,	PAPER_ORIENTATION_LANDSCAPE },	/* mm 114 x 162 */
	{ N_("Photo 6x4"),	432,	288,	PAPER_ORIENTATION_PORTRAIT },	/* in 6   x 4 */
	{ N_("Photo 8x10"),	576,	720,	PAPER_ORIENTATION_PORTRAIT },	/* in 8   x 10 */
	{ N_("Postcard"),	284,	419,	PAPER_ORIENTATION_LANDSCAPE },	/* mm 100 x 148 */
	{ N_("Tabloid"),	792,	1224,	PAPER_ORIENTATION_PORTRAIT },	/* in 11  x 17 */
	{ NULL, 0, 0, 0 }
};


static PaperSize *print_paper_size_nth(gint n)
{
	PaperSize *ps = NULL;
	gint i = 0;

	while (i <= n && print_paper_sizes[i].description)
		{
		ps = &print_paper_sizes[i];
		i++;
		}

	return ps;
}

static gint print_paper_size_lookup(gint n, gdouble *width, gdouble *height)
{
	PaperSize *ps;
	gdouble w, h;

	ps = print_paper_size_nth(n);
	if (!ps) return FALSE;

	if (ps->orientation == PAPER_ORIENTATION_PORTRAIT)
		{
		w = ps->width;
		h = ps->height;
		}
	else
		{
		h = ps->width;
		w = ps->height;
		}

	if (width) *width = w;
	if (height) *height = h;

	return TRUE;
}

static gdouble print_paper_size_convert_units(gdouble value, PaperUnits src, PaperUnits dst)
{
	gdouble ret;

	if (src == dst) return value;

	switch (src)
		{
		case PAPER_UNIT_MM:
			ret = value / 25.4 * 72.0;
			break;
		case PAPER_UNIT_CM:
			ret = value / 2.54 * 72.0;
			break;
		case PAPER_UNIT_INCH:
			ret = value * 72.0;
			break;
		case PAPER_UNIT_PICAS:
			ret = value * 12.0;
			break;
		case PAPER_UNIT_POINTS:
		default:
			ret = value;
			break;
		}

	switch (dst)
		{
		case PAPER_UNIT_MM:
			ret = ret / 72.0 * 25.4;
			break;
		case PAPER_UNIT_CM:
			ret = ret / 72.0 * 2.54;
			break;
		case PAPER_UNIT_INCH:
			ret = ret / 72.0;
			break;
		case PAPER_UNIT_PICAS:
			ret = ret / 12.0;
			break;
		case PAPER_UNIT_POINTS:
		default:
			break;
		}

	return ret;
}

static PaperUnits paper_unit_default(void)
{
	const gchar *result;
#if 0
	/* this is not used because it is not even slightly portable */
	#include <langinfo.h>

	result = nl_langinfo(_NL_MEASUREMENT_MEASUREMENT);
	if (result[0] == '2') return PAPER_UNIT_INCH;
#endif

#ifdef LC_MEASUREMENT
	result = setlocale(LC_MEASUREMENT, NULL);
#else
	result = setlocale(LC_ALL, NULL);
#endif
	if (result &&
	    (strstr(result, "_US") || strstr(result, "_PR")) )
		{
		return PAPER_UNIT_INCH;
		}

	return PAPER_UNIT_CM;
}

/*
 *-----------------------------------------------------------------------------
 * the layout window
 *-----------------------------------------------------------------------------
 */

static gint print_layout_page_count(PrintWindow *pw);


static gint print_preview_unit(gdouble points)
{
	return (gint)(points / PRINT_PREVIEW_SCALE);
}

static void print_proof_size(PrintWindow *pw, gdouble *width, gdouble *height)
{
	if (width) *width = pw->proof_width + PRINT_PROOF_MARGIN * 2;
	if (height)
		{
		gdouble h;

		h = pw->proof_height + PRINT_PROOF_MARGIN * 2;
		if (pw->text_fields != 0) h += PRINT_TEXT_PADDING;
		if (pw->text_fields & TEXT_INFO_FILENAME) h+= (gdouble)pw->text_points * 1.25;
		if (pw->text_fields & TEXT_INFO_DIMENSIONS) h+= (gdouble)pw->text_points * 1.25;
		if (pw->text_fields & TEXT_INFO_FILEDATE) h+= (gdouble)pw->text_points * 1.25;
		if (pw->text_fields & TEXT_INFO_FILESIZE) h+= (gdouble)pw->text_points * 1.25;
		*height = h;
		}
}

static void print_window_layout_status(PrintWindow *pw)
{
	gint total;
	gchar *buf;

	total = print_layout_page_count(pw);
	pw->proof_page = CLAMP(pw->proof_page, 0, total - 1);

	buf = g_strdup_printf(_("page %d of %d"), pw->proof_page + 1, (total > 0) ? total : 1);
	gtk_label_set_text(GTK_LABEL(pw->page_label), buf);
	g_free(buf);

	gtk_widget_set_sensitive(pw->page_label, (total > 0));

	gtk_widget_set_sensitive(pw->button_back, (pw->proof_page > 0));
	gtk_widget_set_sensitive(pw->button_next, (pw->proof_page < total - 1));

	gtk_widget_set_sensitive(pw->print_button, total > 0);
}

static void print_window_layout_render_stop(PrintWindow *pw)
{
	if (pw->layout_idle_id)
		{
		g_source_remove(pw->layout_idle_id);
		pw->layout_idle_id = 0;
		}
}

static gboolean print_window_layout_render_idle(gpointer data)
{
	PrintWindow *pw = data;

	print_job_close(pw, FALSE);
	print_job_start(pw, RENDER_FORMAT_PREVIEW, 0);

	pw->layout_idle_id = 0;
	return FALSE;
}

static void print_window_layout_render(PrintWindow *pw)
{
	gdouble proof_w, proof_h;

	print_proof_size(pw, &proof_w, &proof_h);
	pw->proof_columns = (pw->layout_width - pw->margin_left - pw->margin_right) / proof_w;
	pw->proof_rows = (pw->layout_height - pw->margin_top - pw->margin_bottom) / proof_h;

	print_window_layout_status(pw);

	if (!pw->layout_idle_id)
		{
		pw->layout_idle_id = g_idle_add(print_window_layout_render_idle, pw);
		}
}

static void print_window_layout_size(PrintWindow *pw)
{
	GdkPixbuf *pixbuf;
	gdouble width;
	gdouble height;
	gint sw, sh;

	if (!pw->layout_image) return;

	if (pw->paper_orientation == PAPER_ORIENTATION_LANDSCAPE)
		{
		width = pw->paper_height;
		height = pw->paper_width;
		}
	else
		{
		width = pw->paper_width;
		height = pw->paper_height;
		}

	pw->layout_width = width;
	pw->layout_height = height;

	sw = print_preview_unit(width);
	sh = print_preview_unit(height);
	pixbuf = image_get_pixbuf(pw->layout_image);
	if (!pixbuf ||
	    gdk_pixbuf_get_width(pixbuf) != sw ||
	    gdk_pixbuf_get_height(pixbuf) != sh)
		{
		pixbuf = gdk_pixbuf_new(GDK_COLORSPACE_RGB, FALSE, 8, sw, sh);
		image_change_pixbuf(pw->layout_image, pixbuf, 0.0, FALSE);
		g_object_unref(pixbuf);
		}

	print_window_layout_render(pw);
	print_window_layout_status(pw);
}

static gint print_layout_page_count(PrintWindow *pw)
{
	gint images;
	gint images_per_page;
	gint pages;

	if (pw->layout_width - pw->margin_left - pw->margin_right <= 0.0 ||
	    pw->layout_height - pw->margin_top - pw->margin_bottom <= 0.0)
		{
		return 0;
		}

	switch (pw->source)
		{
		case PRINT_SOURCE_ALL:
			images = g_list_length(pw->source_list);
			break;
		case PRINT_SOURCE_SELECTION:
			images = g_list_length(pw->source_selection);
			break;
		case PRINT_SOURCE_IMAGE:
		default:
			images = (pw->source_fd) ? 1 : 0;
			break;
		}

	switch (pw->layout)
		{
		case PRINT_LAYOUT_PROOF:
			images_per_page = pw->proof_columns * pw->proof_rows;
			break;
		case PRINT_LAYOUT_IMAGE:
		default:
			images_per_page = 1;
			break;
		}

	if (images < 1 || images_per_page < 1) return 0;

	pages = images / images_per_page;
	if (pages * images_per_page < images) pages++;

	return pages;
}

static void print_layout_page_step(PrintWindow *pw, gint step)
{
	gint max;
	gint page;

	max = print_layout_page_count(pw);
	page = pw->proof_page + step;

	if (page >= max) page = max - 1;
	if (page < 0) page = 0;

	if (page == pw->proof_page) return;

	pw->proof_page = page;
	print_window_layout_size(pw);
}

static void print_layout_page_back_cb(GtkWidget *widget, gpointer data)
{
	PrintWindow *pw = data;

	print_layout_page_step(pw, -1);
}

static void print_layout_page_next_cb(GtkWidget *widget, gpointer data)
{
	PrintWindow *pw = data;

	print_layout_page_step(pw, 1);
}

static void print_layout_zoom_in_cb(GtkWidget *widget, gpointer data)
{
	PrintWindow *pw = data;
	image_zoom_adjust(pw->layout_image, 0.25);
}

static void print_layout_zoom_out_cb(GtkWidget *widget, gpointer data)
{
	PrintWindow *pw = data;
	image_zoom_adjust(pw->layout_image, -0.25);
}

static void print_layout_zoom_original_cb(GtkWidget *widget, gpointer data)
{
	PrintWindow *pw = data;
	gdouble zoom;

	zoom = image_zoom_get(pw->layout_image);
	image_zoom_set(pw->layout_image, (zoom == 1.0) ? 0.0 : 1.0);
}

static GtkWidget *print_window_layout_setup(PrintWindow *pw, GtkWidget *box)
{
	GtkWidget *vbox;
	GtkWidget *hbox;
	GtkWidget *group;
	GtkWidget *button;

	vbox = pref_box_new(box, TRUE, GTK_ORIENTATION_VERTICAL, PREF_PAD_GAP);
	group = pref_frame_new(vbox, TRUE, _("Preview"), GTK_ORIENTATION_VERTICAL, PREF_PAD_GAP);

	pw->layout_idle_id = 0;

	pw->layout_image = image_new(FALSE);
	gtk_widget_set_size_request(pw->layout_image->widget, PRINT_DLG_PREVIEW_WIDTH, PRINT_DLG_PREVIEW_HEIGHT);

	gtk_box_pack_start(GTK_BOX(group), pw->layout_image->widget, TRUE, TRUE, 0);
	gtk_widget_show(pw->layout_image->widget);

	hbox = pref_box_new(group, FALSE, GTK_ORIENTATION_HORIZONTAL, PREF_PAD_GAP);
	pw->button_back = pref_button_new(hbox, GTK_STOCK_GO_BACK, NULL, TRUE,
					  G_CALLBACK(print_layout_page_back_cb), pw);
	pw->button_next = pref_button_new(hbox, GTK_STOCK_GO_FORWARD, NULL, TRUE,
					  G_CALLBACK(print_layout_page_next_cb), pw);
	pw->page_label = pref_label_new(hbox, "");

	button = pref_button_new(NULL, GTK_STOCK_ZOOM_OUT, NULL, TRUE,
				 G_CALLBACK(print_layout_zoom_out_cb), pw);
	gtk_box_pack_end(GTK_BOX(hbox), button, FALSE, FALSE, 0);
	gtk_widget_show(button);
	button = pref_button_new(NULL, GTK_STOCK_ZOOM_IN, NULL, TRUE,
				 G_CALLBACK(print_layout_zoom_in_cb), pw);
	gtk_box_pack_end(GTK_BOX(hbox), button, FALSE, FALSE, 0);
	gtk_widget_show(button);
	button = pref_button_new(NULL, GTK_STOCK_ZOOM_100, NULL, TRUE,
				 G_CALLBACK(print_layout_zoom_original_cb), pw);
	gtk_box_pack_end(GTK_BOX(hbox), button, FALSE, FALSE, 0);
	gtk_widget_show(button);

	print_window_layout_size(pw);

	return vbox;
}

static void print_window_spin_set(GtkSpinButton *spin, gpointer block_data,
				  gdouble value, gdouble min, gdouble max,
				  gdouble step, gdouble page, gint digits)
{
	if (block_data) g_signal_handlers_block_matched(G_OBJECT(spin), G_SIGNAL_MATCH_DATA,
							0, 0, NULL, NULL, block_data);
	gtk_spin_button_set_digits(spin, digits);
	gtk_spin_button_set_increments(spin, step, page);
	gtk_spin_button_set_range(spin, min, max);
	gtk_spin_button_set_value(spin, value);

	if (block_data) g_signal_handlers_unblock_matched(G_OBJECT(spin), G_SIGNAL_MATCH_DATA,
							  0, 0, NULL, NULL, block_data);
}

static void print_window_layout_sync_layout(PrintWindow *pw)
{
	gtk_widget_set_sensitive(pw->image_scale_spin, (pw->layout == PRINT_LAYOUT_IMAGE));
	gtk_widget_set_sensitive(pw->proof_group, (pw->layout == PRINT_LAYOUT_PROOF));
}

static void print_window_layout_sync_paper(PrintWindow *pw)
{
	gdouble width, height;
	gint digits;
	gdouble step;
	gdouble page;

	gtk_widget_set_sensitive(pw->paper_width_spin, (pw->paper_size == 0));
	gtk_widget_set_sensitive(pw->paper_height_spin, (pw->paper_size == 0));

	width = print_paper_size_convert_units((gdouble)pw->paper_width, PAPER_UNIT_POINTS, pw->paper_units);
	height = print_paper_size_convert_units((gdouble)pw->paper_height, PAPER_UNIT_POINTS, pw->paper_units);

	switch (pw->paper_units)
		{
		case PAPER_UNIT_MM:
			digits = 1;
			step = 1.0;
			page = 10.0;
			break;
		case PAPER_UNIT_CM:
			digits = 2;
			step = 0.5;
			page = 1.0;
			break;
		case PAPER_UNIT_INCH:
			digits = 3;
			step = 0.25;
			page = 1.0;
			break;
		case PAPER_UNIT_PICAS:
			digits = 2;
			step = 1.0;
			page = 6.0;
			break;
		case PAPER_UNIT_POINTS:
		default:
			digits = 1;
			step = 1.0;
			page = 10.0;
			break;
		}

	print_window_spin_set(GTK_SPIN_BUTTON(pw->paper_width_spin), pw, width,
			      print_paper_size_convert_units(PRINT_MIN_WIDTH, PAPER_UNIT_POINTS, pw->paper_units),
			      print_paper_size_convert_units(PRINT_MAX_WIDTH, PAPER_UNIT_POINTS, pw->paper_units),
			      step, page, digits);

	print_window_spin_set(GTK_SPIN_BUTTON(pw->paper_height_spin), pw, height,
			      print_paper_size_convert_units(PRINT_MIN_HEIGHT, PAPER_UNIT_POINTS, pw->paper_units),
			      print_paper_size_convert_units(PRINT_MAX_HEIGHT, PAPER_UNIT_POINTS, pw->paper_units),
			      step, page, digits);

	print_window_spin_set(GTK_SPIN_BUTTON(pw->margin_left_spin), pw,
			      print_paper_size_convert_units(pw->margin_left, PAPER_UNIT_POINTS, pw->paper_units),
			      0.0,
			      print_paper_size_convert_units(PRINT_MAX_WIDTH, PAPER_UNIT_POINTS, pw->paper_units),
			      step, page, digits);

	print_window_spin_set(GTK_SPIN_BUTTON(pw->margin_right_spin), pw,
			      print_paper_size_convert_units(pw->margin_right, PAPER_UNIT_POINTS, pw->paper_units),
			      0.0,
			      print_paper_size_convert_units(PRINT_MAX_WIDTH, PAPER_UNIT_POINTS, pw->paper_units),
			      step, page, digits);

	print_window_spin_set(GTK_SPIN_BUTTON(pw->margin_top_spin), pw,
			      print_paper_size_convert_units(pw->margin_top, PAPER_UNIT_POINTS, pw->paper_units),
			      0.0,
			      print_paper_size_convert_units(PRINT_MAX_HEIGHT, PAPER_UNIT_POINTS, pw->paper_units),
			      step, page, digits);

	print_window_spin_set(GTK_SPIN_BUTTON(pw->margin_bottom_spin), pw,
			      print_paper_size_convert_units(pw->margin_bottom, PAPER_UNIT_POINTS, pw->paper_units),
			      0.0,
			      print_paper_size_convert_units(PRINT_MAX_HEIGHT, PAPER_UNIT_POINTS, pw->paper_units),
			      step, page, digits);

	print_window_spin_set(GTK_SPIN_BUTTON(pw->proof_width_spin), pw,
			      print_paper_size_convert_units(pw->proof_width, PAPER_UNIT_POINTS, pw->paper_units),
			      print_paper_size_convert_units(PRINT_PROOF_MIN_SIZE, PAPER_UNIT_POINTS, pw->paper_units),
			      print_paper_size_convert_units(PRINT_PROOF_MAX_SIZE, PAPER_UNIT_POINTS, pw->paper_units),
			      step, page, digits);

	print_window_spin_set(GTK_SPIN_BUTTON(pw->proof_height_spin), pw,
			      print_paper_size_convert_units(pw->proof_height, PAPER_UNIT_POINTS, pw->paper_units),
			      print_paper_size_convert_units(PRINT_PROOF_MIN_SIZE, PAPER_UNIT_POINTS, pw->paper_units),
			      print_paper_size_convert_units(PRINT_PROOF_MAX_SIZE, PAPER_UNIT_POINTS, pw->paper_units),
			      step, page, digits);
}

static void print_window_layout_set_size(PrintWindow *pw, gdouble width, gdouble height)
{
	pw->paper_width = width;
	pw->paper_height = height;

	print_window_layout_sync_paper(pw);

	print_window_layout_size(pw);
}

static void print_window_layout_set_orientation(PrintWindow *pw, PaperOrientation o)
{
	if (pw->paper_orientation == o) return;

	pw->paper_orientation = o;

	print_window_layout_size(pw);
}

/*
 *-----------------------------------------------------------------------------
 * list printers
 *-----------------------------------------------------------------------------
 */

static GList *print_window_list_printers(void)
{
	FILE *p;
	GList *list = NULL;
	gchar buffer[2048];

	p = popen(PRINT_LPR_QUERY, "r");
	if (!p) return NULL;

	while (fgets(buffer, sizeof(buffer), p) != NULL)
		{
		gchar *ptr;
		gchar *end;

		ptr = buffer;
		if (strncmp(ptr, "printer ", 8) != 0) continue;
		if (strstr(ptr, "enabled") == NULL) continue;
		ptr += 8;
		end = ptr;
		while (*end != '\0' && *end != '\n' && *end != ' ' && *end != '\t') end++;
		*end = '\0';
		list = g_list_append(list, g_strdup(ptr));
		DEBUG_1("adding printer: %s", ptr);
		}

	pclose(p);

	return list;
}

/*
 *-----------------------------------------------------------------------------
 * print ps
 *-----------------------------------------------------------------------------
 */

typedef struct _PipeError PipeError;
struct _PipeError {
	struct sigaction old_action;
	sig_atomic_t *error;
};

static sig_atomic_t pipe_handler_error = FALSE;
static PipeError *pipe_handler_data = NULL;

static void pipe_handler_sigpipe_cb(gint fd)
{
	pipe_handler_error = TRUE;
}

static PipeError *pipe_handler_new(void)
{
	struct sigaction new_action;
	PipeError *pe;

	if (pipe_handler_data)
		{
		log_printf("warning SIGPIPE handler already in use\n");
		return NULL;
		}

	pe = g_new0(PipeError, 1);

	pipe_handler_error = FALSE;
	pe->error = &pipe_handler_error;

	new_action.sa_handler = pipe_handler_sigpipe_cb;
	sigemptyset(&new_action.sa_mask);
	new_action.sa_flags = 0;

	/* setup our signal handler */
	sigaction(SIGPIPE, &new_action, &pe->old_action);

	pipe_handler_data = pe;
	return pe;
}

static void pipe_handler_free(PipeError *pe)
{
	if (!pe) return;
	if (pe != pipe_handler_data) log_printf("warning SIGPIPE handler not closing same data\n");

	/* restore the original signal handler */
	sigaction(SIGPIPE, &pe->old_action, NULL);

	pipe_handler_data = NULL;
	g_free(pe);
}

static gboolean pipe_handler_check(PipeError *pe)
{
	if (!pe) return FALSE;
	return !!(*pe->error);
}

static FILE *print_job_ps_fd(PrintWindow *pw)
{
	if (pw->job_file) return pw->job_file;
	if (pw->job_pipe) return pw->job_pipe;
	return NULL;
}

static gboolean print_job_ps_init(PrintWindow *pw)
{
	FILE *f;
	PipeError *pe;
	const gchar *cmd = NULL;
	const gchar *path = NULL;
	gchar *lc_pointer;
	gboolean ret;

	if (pw->job_file != NULL || pw->job_pipe != NULL) return FALSE;

	switch (pw->job_output)
		{
		case PRINT_OUTPUT_PS_LPR:
			cmd = PRINT_LPR_COMMAND;
			break;
		case PRINT_OUTPUT_PS_CUSTOM:
			cmd = pw->output_custom;
			break;
		case PRINT_OUTPUT_PS_FILE:
			path = pw->output_path;
			break;
		default:
			return FALSE;
			break;
		}

	if (cmd)
		{
		pw->job_pipe = popen(cmd, "w");

		if (!pw->job_pipe)
			{
			gchar *buf;

			buf = g_strdup_printf(_("Unable to open pipe for writing.\n\"%s\""), cmd);
			print_job_throw_error(pw, buf);
			g_free(buf);

			return FALSE;
			}
		}
	else if (path)
		{
		gchar *pathl;

		if (isfile(path))
			{
			gchar *buf;

			buf = g_strdup_printf(_("A file with name %s already exists."), path);
			print_job_throw_error(pw, buf);
			g_free(buf);

			return FALSE;
			}

		pathl = path_from_utf8(path);
		pw->job_file = fopen(pathl, "w");
		g_free(pathl);

		if (!pw->job_file)
			{
			gchar *buf;

			buf = g_strdup_printf(_("Failure writing to file %s"), path);
			print_job_throw_error(pw, buf);
			g_free(buf);

			return FALSE;
			}

		g_free(pw->job_path);
		pw->job_path = g_strdup(path);
		}

	f = print_job_ps_fd(pw);
	if (!f) return FALSE;

	lc_pointer = g_strdup(setlocale(LC_NUMERIC, NULL));
	setlocale(LC_NUMERIC, POSTSCRIPT_LOCALE);

	pe = pipe_handler_new();

	/* comments, etc. */
	g_fprintf(f, "%%!PS-Adobe-3.0\n");
	g_fprintf(f, "%%%%Creator: %s Version %s\n", GQ_APPNAME, VERSION);
	g_fprintf(f, "%%%%CreationDate: \n");
	g_fprintf(f, "%%%%LanguageLevel 2\n");
	g_fprintf(f, "%%%%DocumentMedia: \n");
	g_fprintf(f, "%%%%Orientation: %s\n",
		(pw->paper_orientation == PAPER_ORIENTATION_PORTRAIT) ? "Portrait" : "Landscape");
	g_fprintf(f, "%%%%BoundingBox: %f %f %f %f\n",
		0.0, 0.0, pw->paper_width, pw->paper_height);
	g_fprintf(f, "%%%%Pages: %d\n", print_layout_page_count(pw));
	g_fprintf(f, "%%%%PageOrder: Ascend\n");
	g_fprintf(f, "%%%%Title:\n");

	/* setup page size, coordinates (do we really need this?) */
#if 0
	g_fprintf(f, "<<\n");
	g_fprintf(f, "/PageSize [%f %f]\n", pw->layout_width, pw->layout_height);
	g_fprintf(f, "/ImagingBBox [%f %f %f %f]\n", /* l b r t */
		pw->margin_left, pw->margin_bottom,
		pw->layout_width - pw->margin_right, pw->layout_height - pw->margin_top);
	g_fprintf(f, "/Orientation %d\n",
		(pw->paper_orientation == PAPER_ORIENTATION_PORTRAIT) ? 0 : 1);
	g_fprintf(f, ">> setpagedevice\n");
#endif

	ret = !pipe_handler_check(pe);
	pipe_handler_free(pe);

	if (lc_pointer)
		{
		setlocale(LC_NUMERIC, lc_pointer);
		g_free(lc_pointer);
		}

	if (!ret) print_job_throw_error(pw, _("SIGPIPE error writing to printer."));

	return ret;
}

static gboolean print_job_ps_page_new(PrintWindow *pw, gint page)
{
	FILE *f;
	PipeError *pe;
	gchar *lc_pointer;
	gboolean ret;

	f= print_job_ps_fd(pw);
	if (!f) return FALSE;

	lc_pointer = g_strdup(setlocale(LC_NUMERIC, NULL));
	setlocale(LC_NUMERIC, POSTSCRIPT_LOCALE);

	pe = pipe_handler_new();

	g_fprintf(f, "%%%% page %d\n", page + 1);

	if (pw->paper_orientation == PAPER_ORIENTATION_LANDSCAPE)
		{
		g_fprintf(f, "/pagelevel save def\n");
		g_fprintf(f, "%d 0 translate 90 rotate\n", (gint)pw->layout_height);
		}

	ret = !pipe_handler_check(pe);
	pipe_handler_free(pe);

	if (lc_pointer)
		{
		setlocale(LC_NUMERIC, lc_pointer);
		g_free(lc_pointer);
		}

	if (!ret) print_job_throw_error(pw, _("SIGPIPE error writing to printer."));

	return ret;
}

static gboolean print_job_ps_page_done(PrintWindow *pw)
{
	FILE *f;
	PipeError *pe;
	gchar *lc_pointer;
	gboolean ret;

	f = print_job_ps_fd(pw);
	if (!f) return FALSE;

	lc_pointer = g_strdup(setlocale(LC_NUMERIC, NULL));
	setlocale(LC_NUMERIC, POSTSCRIPT_LOCALE);

	pe = pipe_handler_new();

	if (pw->paper_orientation == PAPER_ORIENTATION_LANDSCAPE)
		{
		g_fprintf(f, "pagelevel restore\n");
		}

	g_fprintf(f, "showpage\n");

	ret = !pipe_handler_check(pe);
	pipe_handler_free(pe);

	if (lc_pointer)
		{
		setlocale(LC_NUMERIC, lc_pointer);
		g_free(lc_pointer);
		}

	if (!ret) print_job_throw_error(pw, _("SIGPIPE error writing to printer."));

	return ret;
}

static void print_job_ps_page_image_pixel(FILE *f, guchar *pix)
{
	static gchar hex_digits[] = "0123456789abcdef";
	gchar text[8];
	gint i;

	for (i = 0; i < 3; i++)
		{
		text[i*2] = hex_digits[pix[i] >> 4];
		text[i*2+1] = hex_digits[pix[i] & 0xf];
		}
	text[6] = '\0';

	g_fprintf(f, "%s", text);
}
static gboolean print_job_ps_page_image(PrintWindow *pw, GdkPixbuf *pixbuf,
				        gdouble x, gdouble y, gdouble w, gdouble h,
				        gdouble offx, gdouble offy)
{
	FILE *f;
	PipeError *pe;
	gchar *lc_pointer;
	gint sw, sh;
	gint bps;
	gint rowstride;
	guchar *pix;
	gint i, j;
	gint c;
	guchar *p;
	guchar bps_buf[3];
	gboolean ret;

	if (!pixbuf) return TRUE;

	f = print_job_ps_fd(pw);
	if (!f) return FALSE;

	sw = gdk_pixbuf_get_width(pixbuf);
	sh = gdk_pixbuf_get_height(pixbuf);

	if (pw->max_dpi >= PRINT_PS_DPI_MIN &&
	    sw / pw->max_dpi > w / 72.0)
		{
		pixbuf = gdk_pixbuf_scale_simple(pixbuf,
						(gint)(w / 72.0 * pw->max_dpi),
						(gint)(h / 72.0 * pw->max_dpi),
						PRINT_PS_MAX_INTERP);
		sw = gdk_pixbuf_get_width(pixbuf);
		sh = gdk_pixbuf_get_height(pixbuf);
		}
	else
		{
		g_object_ref(G_OBJECT(pixbuf));
		}

	bps = (gdk_pixbuf_get_has_alpha(pixbuf)) ? 4 : 3;
	rowstride = gdk_pixbuf_get_rowstride(pixbuf);
	pix = gdk_pixbuf_get_pixels(pixbuf);

	lc_pointer = g_strdup(setlocale(LC_NUMERIC, NULL));
	setlocale(LC_NUMERIC, POSTSCRIPT_LOCALE);

	pe = pipe_handler_new();

	g_fprintf(f, "gsave\n");
	g_fprintf(f, "[%f 0 0 %f %f %f] concat\n", w, h, x, pw->layout_height - h - y);
	g_fprintf(f, "/buf %d string def\n", sw * 3);
	g_fprintf(f, "%d %d %d\n", sw, sh, 8);
	g_fprintf(f, "[%d 0 0 -%d 0 %d]\n", sw, sh, sh);
	g_fprintf(f, "{ currentfile buf readhexstring pop }\n");
	g_fprintf(f, "false %d colorimage\n", 3);

	c = 0;
	for (j = 0; j < sh; j++)
		{
		p = pix + j * rowstride;
		for (i = 0; i < sw; i++)
			{
			if (bps == 3)
				{
				print_job_ps_page_image_pixel(f, p);
				}
			else
				{
				bps_buf[0] = (p[0] * p[3] + PRINT_PS_MASK_R * (256 - p[3])) >> 8;
				bps_buf[1] = (p[1] * p[3] + PRINT_PS_MASK_G * (256 - p[3])) >> 8;
				bps_buf[2] = (p[2] * p[3] + PRINT_PS_MASK_B * (256 - p[3])) >> 8;
				print_job_ps_page_image_pixel(f, bps_buf);
				}
			p+=bps;
			c++;
			if (c > 11)
				{
				g_fprintf(f, "\n");
				c = 0;
				}
			}
		}
	if (c > 0) g_fprintf(f, "\n");
	g_fprintf(f, "grestore\n");

	ret = !pipe_handler_check(pe);
	pipe_handler_free(pe);

	if (lc_pointer)
		{
		setlocale(LC_NUMERIC, lc_pointer);
		g_free(lc_pointer);
		}

	g_object_unref(G_OBJECT(pixbuf));

	if (!ret) print_job_throw_error(pw, _("SIGPIPE error writing to printer."));

	return ret;
}

static const gchar *ps_text_to_hex_array(FILE *f, const gchar *text, gdouble x, gdouble y)
{
	static gchar hex_digits[] = "0123456789abcdef";
	const gchar *p;

	if (!text) return NULL;

	g_fprintf(f, "%f %f moveto\n", x, y);
	g_fprintf(f, "<");

	/* fixme: convert utf8 to ascii or proper locale string.. */

	p = text;
	while (*p != '\0' && *p != '\n')
		{
		gchar text[3];

		text[0] = hex_digits[*p >> 4];
		text[1] = hex_digits[*p & 0xf];
		text[2] = '\0';

		g_fprintf(f, "%s", text);

		p++;
		}

	g_fprintf(f, ">\n");
	g_fprintf(f, "dup stringwidth pop 2 div neg 0 rmoveto show\n");

	return p;
}

static void ps_text_parse(FILE *f, const gchar *text, gdouble x, gdouble y, gdouble point_size)
{
	const gchar *p;

	if (!text) return;

	g_fprintf(f, "newpath\n");

	p = text;
	while (p && *p != '\0')
		{
		p = ps_text_to_hex_array(f, p, x, y);
		if (p && *p == '\n') p++;
		y -= point_size;
		}

	g_fprintf(f, "closepath\n");
}

static gboolean print_job_ps_page_text(PrintWindow *pw, const gchar *text, gdouble point_size,
				       gdouble x, gdouble y, gdouble width,
				       guint8 r, guint8 g, guint8 b)
{
	FILE *f;
	PipeError *pe;
	gchar *lc_pointer;
	gboolean ret;

	if (!text) return TRUE;

	f = print_job_ps_fd(pw);
	if (!f) return FALSE;

	lc_pointer = g_strdup(setlocale(LC_NUMERIC, NULL));
	setlocale(LC_NUMERIC, POSTSCRIPT_LOCALE);

	pe = pipe_handler_new();

	g_fprintf(f, "/Sans findfont\n");
	g_fprintf(f, "%f scalefont\n", point_size);
	g_fprintf(f, "setfont\n");

	g_fprintf(f, "%f %f %f setrgbcolor\n", (gdouble)r / 255.0, (gdouble)g / 255.0, (gdouble)b / 255.0);
	ps_text_parse(f, text, x, pw->layout_height - y - point_size, point_size);

	ret = !pipe_handler_check(pe);
	pipe_handler_free(pe);

	if (lc_pointer)
		{
		setlocale(LC_NUMERIC, lc_pointer);
		g_free(lc_pointer);
		}

	if (!ret) print_job_throw_error(pw, _("SIGPIPE error writing to printer."));

	return ret;
}

static gboolean print_job_ps_end(PrintWindow *pw)
{
	FILE *f;
	PipeError *pe;
	gchar *lc_pointer;
	gboolean ret;

	f = print_job_ps_fd(pw);
	if (!f) return FALSE;

	lc_pointer = g_strdup(setlocale(LC_NUMERIC, NULL));
	setlocale(LC_NUMERIC, POSTSCRIPT_LOCALE);

	pe = pipe_handler_new();

	g_fprintf(f, "%%%%EOF\n");

	ret = !pipe_handler_check(pe);
	pipe_handler_free(pe);

	if (lc_pointer)
		{
		setlocale(LC_NUMERIC, lc_pointer);
		g_free(lc_pointer);
		}

	if (!ret) print_job_throw_error(pw, _("SIGPIPE error writing to printer."));

	return ret;
}

/*
 *-----------------------------------------------------------------------------
 * print rgb
 *-----------------------------------------------------------------------------
 */

static gboolean print_job_rgb_page_new(PrintWindow *pw, gint page)
{
	gint total;

	if (pw->job_pixbuf)
		{
		pixbuf_set_rect_fill(pw->job_pixbuf, 0, 0,
				     gdk_pixbuf_get_width(pw->job_pixbuf),
				     gdk_pixbuf_get_height(pw->job_pixbuf),
				     255, 255, 255, 255);
		}

	g_free(pw->job_path);
	pw->job_path = NULL;

	total = print_layout_page_count(pw);

	if (!pw->output_path ||
	    page < 0 || page >= total) return FALSE;

	if (total > 1)
		{
		const gchar *ext;
		gchar *base;

		ext = extension_from_path(pw->output_path);

		if (ext)
			{
			base = g_strndup(pw->output_path, ext - pw->output_path);
			}
		else
			{
			base = g_strdup(pw->output_path);
			ext = "";
			}
		pw->job_path = g_strdup_printf("%s_%03d%s", base, page + 1, ext);
		g_free(base);
		}
	else
		{
		pw->job_path = g_strdup(pw->output_path);
		}

	if (isfile(pw->job_path))
		{
		gchar *buf;

		buf = g_strdup_printf(_("A file with name %s already exists."), pw->job_path);
		print_job_throw_error(pw, buf);
		g_free(buf);

		g_free(pw->job_path);
		pw->job_path = NULL;
		}

	return (pw->job_path != NULL);
}

static gboolean print_job_rgb_page_done(PrintWindow *pw)
{
	gchar *pathl;
	gboolean ret = FALSE;

	if (!pw->job_pixbuf) return FALSE;

	pathl = path_from_utf8(pw->job_path);

	if (pw->output_format == PRINT_FILE_PNG)
		{
		ret = pixbuf_to_file_as_png(pw->job_pixbuf, pathl);
		}
	else
		{
		gint quality = 0;

		switch (pw->output_format)
			{
			case PRINT_FILE_JPG_LOW:
				quality = 65;
				break;
			case PRINT_FILE_JPG_NORMAL:
				quality = 80;
				break;
			case PRINT_FILE_JPG_HIGH:
				quality = 95;
				break;
			default:
				break;
			}

		if (quality > 0)
			{
			ret = pixbuf_to_file_as_jpg(pw->job_pixbuf, pathl, quality);
			}
		}

	g_free(pathl);

	if (!ret)
		{
		gchar *buf;

		buf = g_strdup_printf(_("Failure writing to file %s"), pw->job_path);
		print_job_throw_error(pw, buf);
		g_free(buf);
		}

	return ret;
}

static gboolean print_job_rgb_page_image(PrintWindow *pw, GdkPixbuf *pixbuf,
				         gdouble x, gdouble y, gdouble w, gdouble h,
				         gdouble offx, gdouble offy)
{
	gdouble sw, sh;
	gdouble dw, dh;
	gdouble rx, ry, rw, rh;

	if (!pw->job_pixbuf) return FALSE;
	if (!pixbuf) return TRUE;

	sw = (gdouble)gdk_pixbuf_get_width(pixbuf);
	sh = (gdouble)gdk_pixbuf_get_height(pixbuf);

	dw = (gdouble)gdk_pixbuf_get_width(pw->job_pixbuf);
	dh = (gdouble)gdk_pixbuf_get_height(pw->job_pixbuf);

	if (clip_region(x, y, w, h,
			0.0, 0.0, dw, dh,
			&rx, &ry, &rw, &rh))
		{
		gdk_pixbuf_composite(pixbuf, pw->job_pixbuf, rx, ry, rw, rh,
				     x + offx, y + offy,
				     w / sw, h / sh,
				     (w / sw < 0.01 || h / sh < 0.01) ? GDK_INTERP_NEAREST : GDK_INTERP_BILINEAR, 255);
		}

	return TRUE;
}

static gdouble convert_pango_dpi(gdouble points)
{
	static gdouble dpi = 0.0;

	if (dpi == 0.0)
		{
		GtkSettings *settings;
		GObjectClass *klass;

		settings = gtk_settings_get_default();
		klass = G_OBJECT_CLASS(GTK_SETTINGS_GET_CLASS(settings));
		if (g_object_class_find_property(klass, "gtk-xft-dpi"))
			{
			gint int_dpi;
			g_object_get(settings, "gtk-xft-dpi", &int_dpi, NULL);
			dpi = (gdouble)int_dpi / PANGO_SCALE;
			}

		if (dpi < 25.0)
			{
			static gboolean warned = FALSE;
			gdouble fallback_dpi = 96.0;

			if (!warned)
				{
				if (dpi == 0.0)
					{
					log_printf("pango dpi unknown, assuming %.0f\n", fallback_dpi);
					}
				else
					{
					log_printf("pango dpi reported as %.0f ignored, assuming %.0f\n", dpi, fallback_dpi);
					}
				warned = TRUE;
				}

			dpi = fallback_dpi;
			}
		}

	if (dpi == 0) return points;
	return points * 72.0 / dpi;
}

static gboolean print_job_rgb_page_text(PrintWindow *pw, const gchar *text, gdouble point_size,
				        gdouble x, gdouble y, gdouble width,
				        guint8 r, guint8 g, guint8 b)
{
	PangoLayout *layout;
	PangoFontDescription *desc;
	gint lw, lh;

	if (!pw->job_pixbuf) return FALSE;

	layout = gtk_widget_create_pango_layout(pw->dialog->dialog, NULL);

	desc = pango_font_description_new();
	pango_font_description_set_size(desc, convert_pango_dpi(point_size) * PANGO_SCALE);
	pango_layout_set_font_description(layout, desc);
	pango_font_description_free(desc);

	pango_layout_set_alignment(layout, PANGO_ALIGN_CENTER);
	pango_layout_set_text(layout, text, -1);

	pango_layout_get_pixel_size(layout, &lw, &lh);
	x = x - (gdouble)lw / 2.0;

	pixbuf_draw_layout(pw->job_pixbuf, layout, pw->dialog->dialog, x, y, r, g, b, 255);
	g_object_unref(G_OBJECT(layout));

	return TRUE;
}

static gboolean print_job_rgb_init(PrintWindow *pw)
{
	if (pw->job_pixbuf) g_object_unref(pw->job_pixbuf);
	pw->job_pixbuf = gdk_pixbuf_new(GDK_COLORSPACE_RGB, FALSE, 8,
					(gint)pw->layout_width, (gint)pw->layout_height);

	return print_job_rgb_page_new(pw, pw->job_page);
}

/*
 *-----------------------------------------------------------------------------
 * print preview
 *-----------------------------------------------------------------------------
 */

static gboolean print_job_preview_page_new(PrintWindow *pw, gint page)
{
	GdkPixbuf *pixbuf;
	gint w, h;
	gint l, r, t, b;

	pixbuf = pw->job_pixbuf;
	if (!pixbuf) return FALSE;

	w = print_preview_unit(pw->layout_width);
	h = print_preview_unit(pw->layout_height);
	l = print_preview_unit(pw->margin_left);
	r = print_preview_unit(pw->margin_right);
	t = print_preview_unit(pw->margin_top);
	b = print_preview_unit(pw->margin_bottom);

	/* fill background */
	pixbuf_set_rect_fill(pixbuf, 0, 0, w, h,
			     255, 255, 255, 255);

	/* draw cm or inch grid */
	if (TRUE)
		{
		gdouble i;
		gdouble grid;
		PaperUnits units;

		units = (pw->paper_units == PAPER_UNIT_MM ||
			 pw->paper_units == PAPER_UNIT_CM) ? PAPER_UNIT_CM : PAPER_UNIT_INCH;

		grid = print_paper_size_convert_units(1.0, units, PAPER_UNIT_POINTS);
		for (i = grid ; i < pw->layout_width; i += grid)
			{
			pixbuf_draw_rect_fill(pixbuf, print_preview_unit(i), 0, 1, h, 0, 0, 0, 16);
			}
		for (i = grid; i < pw->layout_height; i += grid)
			{
			pixbuf_draw_rect_fill(pixbuf, 0, print_preview_unit(i), w, 1, 0, 0, 0, 16);
			}
		}

	/* proof sheet grid */
	if (pw->layout == PRINT_LAYOUT_PROOF)
		{
		gdouble i, j;
		gdouble proof_w, proof_h;
		gint uw, uh;

		print_proof_size(pw, &proof_w, &proof_h);
		uw = print_preview_unit(proof_w + PRINT_PREVIEW_SCALE - 0.1);
		uh = print_preview_unit(proof_h + PRINT_PREVIEW_SCALE - 0.1);

		for (i = 0; i < pw->proof_columns; i++)
		    for (j = 0; j < pw->proof_rows; j++)
			{
			gint x, y;

			x = pw->margin_left + (pw->layout_width - pw->margin_left - pw->margin_right - (pw->proof_columns * proof_w)) / 2 + i * proof_w;
			y = pw->margin_top + j * proof_h;

			pixbuf_draw_rect(pixbuf, print_preview_unit(x), print_preview_unit(y), uw, uh,
					 255, 0, 0, 64, 1, 1, 1, 1);
			}
		}

	/* non-printable region (margins) */
	pixbuf_draw_rect(pixbuf, 0, 0, w, h,
			 0, 0, 0, 16,
			 l, r, t, b);

	/* margin lines */
	pixbuf_draw_rect(pixbuf, l, 0, w - l - r, h,
			 0, 0, 255, 128,
			 1, 1, 0, 0);
	pixbuf_draw_rect(pixbuf, 0, t, w, h - t - b,
			 0, 0, 255, 128,
			 0, 0, 1, 1);

	/* border */
	pixbuf_draw_rect(pixbuf, 0, 0, w, h,
			 0, 0, 0, 255,
			 1, 1, 1, 1);

	image_area_changed(pw->layout_image, 0, 0, w, h);

	return TRUE;
}

static gboolean print_job_preview_page_done(PrintWindow *pw)
{
	return TRUE;
}

static gboolean print_job_preview_page_image(PrintWindow *pw, GdkPixbuf *pixbuf,
					     gdouble x, gdouble y, gdouble w, gdouble h,
					     gdouble offx, gdouble offy)
{
	gdouble sw, sh;
	gdouble dw, dh;
	gdouble rx, ry, rw, rh;

	if (!pw->job_pixbuf) return FALSE;
	if (!pixbuf) return TRUE;

	sw = (gdouble)gdk_pixbuf_get_width(pixbuf);
	sh = (gdouble)gdk_pixbuf_get_height(pixbuf);

	dw = (gdouble)gdk_pixbuf_get_width(pw->job_pixbuf);
	dh = (gdouble)gdk_pixbuf_get_height(pw->job_pixbuf);

	x = print_preview_unit(x);
	y = print_preview_unit(y);
	w = print_preview_unit(w);
	h = print_preview_unit(h);
	offx = print_preview_unit(offx);
	offy = print_preview_unit(offy);

	if (clip_region(x, y, w, h,
			0.0, 0.0, dw, dh,
			&rx, &ry, &rw, &rh))
		{
		gdk_pixbuf_composite(pixbuf, pw->job_pixbuf, rx, ry, rw, rh,
				     x + offx, y + offy,
				     w / sw, h / sh,
				     (w / sw < 0.01 || h / sh < 0.01) ? GDK_INTERP_NEAREST : GDK_INTERP_BILINEAR, 255);

		image_area_changed(pw->layout_image, rx, ry, rw, rh);
		}

	return TRUE;
}

static gboolean print_job_preview_page_text(PrintWindow *pw, const gchar *text, gdouble point_size,
					    gdouble x, gdouble y, gdouble width,
					    guint8 r, guint8 g, guint8 b)
{
	PangoLayout *layout;
	PangoFontDescription *desc;
	gint lw, lh;
	GdkPixbuf *pixbuf;

	if (!pw->job_pixbuf) return FALSE;

	layout = gtk_widget_create_pango_layout(pw->dialog->dialog, NULL);

	desc = pango_font_description_new();
	pango_font_description_set_size(desc, convert_pango_dpi(point_size) * PANGO_SCALE);
	pango_layout_set_font_description(layout, desc);
	pango_font_description_free(desc);

	pango_layout_set_alignment(layout, PANGO_ALIGN_CENTER);
	pango_layout_set_text(layout, text, -1);

	pango_layout_get_pixel_size(layout, &lw, &lh);
	x = x - (gdouble)lw / 2.0;

	pixbuf = gdk_pixbuf_new(GDK_COLORSPACE_RGB, TRUE, 8, lw, lh);
	pixbuf_set_rect_fill(pixbuf, 0, 0, lw, lh, 0, 0, 0, 0);
	pixbuf_draw_layout(pixbuf, layout, pw->dialog->dialog, 0, 0, r, g, b, 255);
	g_object_unref(G_OBJECT(layout));

	print_job_preview_page_image(pw, pixbuf, x, y, (gdouble)lw, (gdouble)lh, 0, 0);
	g_object_unref(pixbuf);

	return TRUE;
}

static gboolean print_job_preview_init(PrintWindow *pw)
{
	if (pw->job_pixbuf) g_object_unref(pw->job_pixbuf);
	pw->job_pixbuf = image_get_pixbuf(pw->layout_image);
	g_object_ref(pw->job_pixbuf);

	return print_job_preview_page_new(pw, pw->job_page);
}


/*
 *-----------------------------------------------------------------------------
 * wrappers
 *-----------------------------------------------------------------------------
 */

static gboolean print_job_page_new(PrintWindow *pw)
{
	switch (pw->job_format)
		{
		case RENDER_FORMAT_RGB:
			return print_job_rgb_page_new(pw, pw->job_page);
		case RENDER_FORMAT_PS:
			return print_job_ps_page_new(pw, pw->job_page);
		case RENDER_FORMAT_PREVIEW:
			return print_job_preview_page_new(pw, pw->job_page);
		}

	return FALSE;
}

static gboolean print_job_page_done(PrintWindow *pw)
{
	switch (pw->job_format)
		{
		case RENDER_FORMAT_RGB:
			return print_job_rgb_page_done(pw);
		case RENDER_FORMAT_PS:
			return print_job_ps_page_done(pw);
		case RENDER_FORMAT_PREVIEW:
			return print_job_preview_page_done(pw);
		}

	return FALSE;
}

static gboolean print_job_page_image(PrintWindow *pw, GdkPixbuf *pixbuf,
				     gdouble x, gdouble y, gdouble w, gdouble h,
				     gdouble offx, gdouble offy)
{
	gboolean success = FALSE;

	if (w <= 0.0 || h <= 0.0) return TRUE;

	switch (pw->job_format)
		{
		case RENDER_FORMAT_RGB:
			success = print_job_rgb_page_image(pw, pixbuf, x, y, w, h, offx, offy);
			break;
		case RENDER_FORMAT_PS:
			success = print_job_ps_page_image(pw, pixbuf, x, y, w, h, offx, offy);
			break;
		case RENDER_FORMAT_PREVIEW:
			success = print_job_preview_page_image(pw, pixbuf, x, y, w, h, offx, offy);
			break;
		}

	return success;
}

static gboolean print_job_page_text(PrintWindow *pw, const gchar *text, gdouble point_size,
				    gdouble x, gdouble y, gdouble width,
				    guint8 r, guint8 g, guint8 b)
{
	gboolean success = TRUE;

	if (!text) return TRUE;

	switch (pw->job_format)
		{
		case RENDER_FORMAT_RGB:
			success = print_job_rgb_page_text(pw, text, point_size, x, y, width, r, g, b);
			break;
		case RENDER_FORMAT_PS:
			success = print_job_ps_page_text(pw, text, point_size, x, y, width, r, g, b);
			break;
		case RENDER_FORMAT_PREVIEW:
			success = print_job_preview_page_text(pw, text, point_size, x, y, width, r, g, b);
			break;
		}

	return success;
}

/*
 *-----------------------------------------------------------------------------
 * print ?
 *-----------------------------------------------------------------------------
 */

static gboolean print_job_render_image(PrintWindow *pw);
static gboolean print_job_render_proof(PrintWindow *pw);


static void print_job_status(PrintWindow *pw)
{
	gdouble value;
	gint page;
	gint total;
	gchar *buf;

	if (!pw->job_progress) return;

	page = pw->job_page;
	total = print_layout_page_count(pw);

	if (pw->layout == PRINT_LAYOUT_PROOF && pw->proof_point)
		{
		GList *start;

		start = g_list_first(pw->proof_point);
		value = (gdouble)g_list_position(start, pw->proof_point) / g_list_length(start);
		}
	else
		{
		value = (total > 0) ? (gdouble)page / total : 0.0;
		}

	buf = g_strdup_printf(_("Page %d"), page + 1);
	gtk_progress_bar_set_text(GTK_PROGRESS_BAR(pw->job_progress), buf);
	g_free(buf);

	if (pw->job_path && pw->job_progress_label)
		{
		gtk_label_set_text(GTK_LABEL(pw->job_progress_label), pw->job_path);
		}

	gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(pw->job_progress), value);
}

static void print_job_throw_error(PrintWindow *pw, const gchar *message)
{
	GenericDialog *gd;
	GtkWidget *parent = NULL;
	GtkWidget *group;
	GtkWidget *label;
	gchar *buf;

	if (GTK_WIDGET_VISIBLE(pw->dialog->dialog)) parent = pw->dialog->dialog;

	gd = generic_dialog_new(_("Printing error"), "print_warning",
				parent, TRUE, NULL, NULL);
	generic_dialog_add_button(gd, GTK_STOCK_OK, NULL, NULL, TRUE);

	buf = g_strdup_printf(_("An error occured printing to %s."), print_output_name(pw->output));
	generic_dialog_add_message(gd, GTK_STOCK_DIALOG_ERROR, _("Printing error"), buf);
	g_free(buf);

	group = pref_group_new(gd->vbox, FALSE, _("Details"), GTK_ORIENTATION_VERTICAL);
	label = pref_label_new(group, message);
	gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.5);

	gtk_widget_show(gd->dialog);
}

static void print_job_done(PrintWindow *pw)
{
	print_job_close(pw, FALSE);
}

static gboolean print_job_text_image(PrintWindow *pw, const gchar *path,
				     gdouble x, gdouble y, gdouble width,
				     gint sw, gint sh, gint proof)
{
	GString *string;
	gboolean space = FALSE;
	gboolean newline = FALSE;
	gboolean ret;

	if (pw->text_fields == 0) return TRUE;

	string = g_string_new("");
	path = image_loader_get_fd(pw->job_loader)->path;

	if (pw->text_fields & TEXT_INFO_FILENAME)
		{
		if (pw->text_fields & TEXT_INFO_FILEPATH)
			g_string_append(string, path);
		else
			g_string_append(string, filename_from_path(path));
		newline = TRUE;
		}
	else if (pw->text_fields & TEXT_INFO_FILEPATH)
		{
		gchar *dirname = g_path_get_dirname(path);

		g_string_append_printf(string, "%s%s", dirname, G_DIR_SEPARATOR_S);
		g_free(dirname);
		newline = TRUE;
		}
	if (pw->text_fields & TEXT_INFO_DIMENSIONS)
		{
		if (newline) g_string_append(string, "\n");
		g_string_append_printf(string, "%d x %d", (gint)sw, (gint)sh);
		newline = proof;
		space = !proof;
		}
	if (pw->text_fields & TEXT_INFO_FILEDATE)
		{
		if (newline)  g_string_append(string, "\n");
		if (space) g_string_append(string, " - ");
		g_string_append(string, text_from_time(filetime(image_loader_get_fd(pw->job_loader)->path)));
		newline = proof;
		space = !proof;
		}
	if (pw->text_fields & TEXT_INFO_FILESIZE)
		{
		gchar *size;

		if (newline)  g_string_append(string, "\n");
		if (space) g_string_append(string, " - ");
		size = text_from_size_abrev(filesize(image_loader_get_fd(pw->job_loader)->path));
		g_string_append(string, size);
		g_free(size);
		}

	ret = print_job_page_text(pw, string->str, pw->text_points, x, y, width,
				  pw->text_r, pw->text_g, pw->text_b);

	g_string_free(string, TRUE);

	return ret;
}

static void print_job_render_image_loader_done(ImageLoader *il, gpointer data)
{
	PrintWindow *pw = data;
	GdkPixbuf *pixbuf;
	gboolean success = TRUE;

	pixbuf = image_loader_get_pixbuf(il);
	if (pixbuf)
		{
		gdouble sw, sh;
		gdouble dw, dh;
		gdouble x, y, w, h;
		gdouble scale;
		gdouble offx, offy;

		sw = (gdouble)gdk_pixbuf_get_width(pixbuf);
		sh = (gdouble)gdk_pixbuf_get_height(pixbuf);

		dw = pw->layout_width - pw->margin_left - pw->margin_right;
		dh = pw->layout_height - pw->margin_top - pw->margin_bottom;

		if (dw / sw < dh / sh)
			{
			w = dw;
			h = dw / sw * sh;
			scale = w / sw;
			}
		else
			{
			h = dh;
			w = dh / sh *sw;
			scale = h / sh;
			}

		if (pw->image_scale >= 5.0)
			{
			w = w * pw->image_scale / 100.0;
			h = h * pw->image_scale / 100.0;
			}

		x = pw->margin_left + (dw / 2) - (w / 2);
		y = pw->margin_top + (dh / 2) - (h / 2);

		offx = offy = 0;

		if (x < 0)
			{
			w += x;
			offx = x;
			x = 0;
			}
		if (x + w >= pw->layout_width) w = pw->layout_width - x;

		if (y < 0)
			{
			h += y;
			offy = y;
			y = 0;
			}
		if (y + h >= pw->layout_height) h = pw->layout_height - y;

		success = (success &&
			   print_job_page_image(pw, pixbuf, x, y, w, h, offx, offy));

		x = x + w / 2;
		y = y + h + PRINT_TEXT_PADDING;

		success = (success &&
			   print_job_text_image(pw, image_loader_get_fd(pw->job_loader)->path, x, y, dw, sw, sh, FALSE));
		}

	image_loader_free(pw->job_loader);
	pw->job_loader = NULL;

	if (pw->job_format == RENDER_FORMAT_PREVIEW)
		{
		print_job_done(pw);
		return;
		}

	success = (success && print_job_page_done(pw));
	if (!success)
		{
		print_job_close(pw, TRUE);
		return;
		}

	pw->job_page++;
	print_job_status(pw);

	if (print_job_render_image(pw))
		{
		if (!print_job_page_new(pw)) print_job_close(pw, TRUE);
		}
	else
		{
		print_job_done(pw);
		}
}

static gboolean print_job_render_image(PrintWindow *pw)
{
	FileData *fd = NULL;

	switch (pw->source)
		{
		case PRINT_SOURCE_SELECTION:
			fd = g_list_nth_data(pw->source_selection, pw->job_page);
			break;
		case PRINT_SOURCE_ALL:
			fd = g_list_nth_data(pw->source_list, pw->job_page);
			break;
		case PRINT_SOURCE_IMAGE:
		default:
			if (pw->job_page == 0) fd = pw->source_fd;
			break;
		}

	image_loader_free(pw->job_loader);
	pw->job_loader = NULL;

	if (!fd) return FALSE;

	pw->job_loader = image_loader_new(fd);
	g_signal_connect(G_OBJECT(pw->job_loader), "done", (GCallback)print_job_render_image_loader_done, pw);
	if (!image_loader_start(pw->job_loader))
		{
		image_loader_free(pw->job_loader);
		pw->job_loader= NULL;
		}

	return TRUE;
}

static void print_job_render_proof_loader_done(ImageLoader *il, gpointer data)
{
	PrintWindow *pw = data;
	GdkPixbuf *pixbuf;
	gdouble x, y;
	gdouble w, h;
	gdouble proof_w, proof_h;
	gdouble icon_w, icon_h;
	gdouble scale;
	gboolean success = TRUE;

	if (pw->proof_columns < 1 || pw->proof_rows < 1)
		{
		image_loader_free(pw->job_loader);
		pw->job_loader = NULL;

		print_job_done(pw);

		return;
		}

	pixbuf = image_loader_get_pixbuf(il);

	w = gdk_pixbuf_get_width(pixbuf);
	h = gdk_pixbuf_get_height(pixbuf);

	if (pw->proof_width / w < pw->proof_height / h)
		{
		icon_w = pw->proof_width;
		icon_h = pw->proof_width / w * h;
		scale = icon_w / w;
		}
	else
		{
		icon_h = pw->proof_height;
		icon_w = pw->proof_height / h * w;
		scale = icon_h / h;
		}

	y = pw->proof_position / pw->proof_columns;
	x = pw->proof_position - (y * pw->proof_columns);

	print_proof_size(pw, &proof_w, &proof_h);

	x *= proof_w;
	y *= proof_h;
	x += pw->margin_left + (pw->layout_width - pw->margin_left - pw->margin_right - (pw->proof_columns * proof_w)) / 2 + (proof_w - icon_w) / 2;
	y += pw->margin_top + PRINT_PROOF_MARGIN + (pw->proof_height - icon_h) / 2;

	success = (success &&
		   print_job_page_image(pw, pixbuf, x, y, icon_w, icon_h, 0, 0));

	x = x + icon_w / 2;
	y = y + icon_h + (pw->proof_height - icon_h) / 2 + PRINT_TEXT_PADDING;

	success = (success &&
		   print_job_text_image(pw, image_loader_get_fd(pw->job_loader)->path, x, y, icon_w + PRINT_PROOF_MARGIN * 2, w, h, TRUE));

	if (!success)
		{
		print_job_close(pw, TRUE);
		return;
		}

	if (pw->proof_point) pw->proof_point = pw->proof_point->next;

	pw->proof_position++;
	if (pw->proof_position >= pw->proof_columns * pw->proof_rows)
		{
		if (pw->job_format == RENDER_FORMAT_PREVIEW)
			{
			print_job_done(pw);
			return;
			}

		if (!print_job_page_done(pw))
			{
			print_job_close(pw, TRUE);
			return;
			}

		pw->proof_position = 0;
		pw->job_page++;
		if (print_job_render_proof(pw))
			{
			if (!print_job_page_new(pw))
				{
				print_job_close(pw, TRUE);
				return;
				}
			print_job_status(pw);
			}
		else
			{
			print_job_done(pw);
			}
		}
	else
		{
		if (print_job_render_proof(pw))
			{
			print_job_status(pw);
			}
		else
			{
			if (print_job_page_done(pw))
				{
				print_job_done(pw);
				}
			else
				{
				print_job_close(pw, TRUE);
				}
			}
		}
}

static gboolean print_job_render_proof(PrintWindow *pw)
{
	FileData *fd = NULL;

	if (pw->proof_columns < 1 || pw->proof_rows < 1) return FALSE;

	if (!pw->proof_point && pw->proof_position == 0 && pw->source == PRINT_SOURCE_IMAGE)
		{
		fd = pw->source_fd;
		}
	else if (pw->proof_point &&
		 pw->proof_position < pw->proof_columns * pw->proof_rows)
		{
		fd = pw->proof_point->data;
		}

	if (!fd) return FALSE;

	image_loader_free(pw->job_loader);
	pw->job_loader = image_loader_new(fd);
	g_signal_connect(G_OBJECT(pw->job_loader), "done", (GCallback)print_job_render_proof_loader_done, pw);
	if (!image_loader_start(pw->job_loader))
		{
		image_loader_free(pw->job_loader);
		pw->job_loader = NULL;
		}

	return TRUE;
}

static void print_job_render(PrintWindow *pw)
{
	gdouble proof_w, proof_h;
	gboolean finished;

	pw->proof_position = 0;

	switch (pw->source)
		{
		case PRINT_SOURCE_SELECTION:
			pw->proof_point = pw->source_selection;
			break;
		case PRINT_SOURCE_ALL:
			pw->proof_point = pw->source_list;
			break;
		case PRINT_SOURCE_IMAGE:
		default:
			pw->proof_point = NULL;
			break;
		}

	print_proof_size(pw, &proof_w, &proof_h);
	pw->proof_columns = (pw->layout_width - pw->margin_left - pw->margin_right) / proof_w;
	pw->proof_rows = (pw->layout_height - pw->margin_top - pw->margin_bottom) / proof_h;

	if (pw->job_format == RENDER_FORMAT_PREVIEW)
		{
		gint total;

		total = print_layout_page_count(pw);
		if (pw->job_page < 0 || pw->job_page >= total)
			{
			print_job_done(pw);
			return;
			}

		if (pw->proof_point && pw->job_page > 0)
			{
			pw->proof_point = g_list_nth(pw->proof_point, pw->job_page * pw->proof_columns * pw->proof_rows);
			}
		}

	if (!print_job_page_new(pw))
		{
		print_job_close(pw, TRUE);
		return;
		}

	if (pw->layout == PRINT_LAYOUT_IMAGE)
		{
		finished = !print_job_render_image(pw);
		}
	else
		{
		finished = !print_job_render_proof(pw);
		}

	if (finished) print_job_done(pw);
}

static gboolean print_job_init(PrintWindow *pw)
{
	gboolean success = FALSE;

	pw->job_page = 0;

	switch (pw->job_format)
		{
		case RENDER_FORMAT_RGB:
			success = print_job_rgb_init(pw);
			break;
		case RENDER_FORMAT_PS:
			success = print_job_ps_init(pw);
			break;
		case RENDER_FORMAT_PREVIEW:
			pw->job_page = pw->proof_page;
			success = print_job_preview_init(pw);
			break;
		}

	return success;
}

static gboolean print_job_finish(PrintWindow *pw)
{
	gboolean success = FALSE;

	switch (pw->job_format)
		{
		case RENDER_FORMAT_RGB:
			success = TRUE;
			break;
		case RENDER_FORMAT_PS:
			print_job_ps_end(pw);
			break;
		case RENDER_FORMAT_PREVIEW:
			success = TRUE;
			break;
		}

	return success;
}

static void print_job_close_file(PrintWindow *pw)
{
	if (pw->job_file)
		{
		fclose(pw->job_file);
		pw->job_file = NULL;
		}

	if (pw->job_pipe)
		{
		PipeError *pe;

		pe = pipe_handler_new();
		pclose(pw->job_pipe);
		pipe_handler_free(pe);

		pw->job_pipe = NULL;
		}
}

static gboolean print_job_close_finish_cb(gpointer data)
{
	PrintWindow *pw = data;

	print_window_close(pw);
	return FALSE;
}

static void print_job_close(PrintWindow *pw, gint error)
{
	if (!error) print_job_finish(pw);

	print_job_close_file(pw);
	g_free(pw->job_path);
	pw->job_path = NULL;

	if (pw->job_dialog)
		{
		generic_dialog_close(pw->job_dialog);
		pw->job_dialog = NULL;
		pw->job_progress = NULL;
		}

	image_loader_free(pw->job_loader);
	pw->job_loader = NULL;

	if (pw->job_pixbuf)
		{
		g_object_unref(pw->job_pixbuf);
		pw->job_pixbuf = NULL;
		}

	if (pw->dialog && !GTK_WIDGET_VISIBLE(pw->dialog->dialog))
		{
		g_idle_add_full(G_PRIORITY_HIGH_IDLE, print_job_close_finish_cb, pw, NULL);
		}
}

static void print_job_cancel_cb(GenericDialog *gd, gpointer data)
{
	PrintWindow *pw = data;

	print_job_close(pw, FALSE);
}

static void print_pref_store(PrintWindow *pw)
{

	pref_list_int_set(PRINT_PREF_GROUP, PRINT_PREF_SAVE, pw->save_settings);

	if (!pw->save_settings) return;

	/* only store values that are actually used in this print job, hence the if()s */

	pref_list_int_set(PRINT_PREF_GROUP, PRINT_PREF_OUTPUT, pw->output);

	if (pw->output == PRINT_OUTPUT_RGB_FILE)
		{
		pref_list_int_set(PRINT_PREF_GROUP, PRINT_PREF_FORMAT, pw->output_format);
		}

	if (pw->job_format == RENDER_FORMAT_PS)
		{
		pref_list_double_set(PRINT_PREF_GROUP, PRINT_PREF_DPI, pw->max_dpi);
		}

	pref_list_int_set(PRINT_PREF_GROUP, PRINT_PREF_UNITS, pw->paper_units);
	pref_list_int_set(PRINT_PREF_GROUP, PRINT_PREF_SIZE, pw->paper_size);
	pref_list_int_set(PRINT_PREF_GROUP, PRINT_PREF_ORIENTATION, pw->paper_orientation);

	if (pw->paper_size == 0)
		{
		pref_list_double_set(PRINT_PREF_GROUP, PRINT_PREF_CUSTOM_WIDTH, pw->paper_width);
		pref_list_double_set(PRINT_PREF_GROUP, PRINT_PREF_CUSTOM_HEIGHT, pw->paper_height);
		}

	pref_list_double_set(PRINT_PREF_GROUP, PRINT_PREF_MARGIN_LEFT, pw->margin_left);
	pref_list_double_set(PRINT_PREF_GROUP, PRINT_PREF_MARGIN_RIGHT, pw->margin_right);
	pref_list_double_set(PRINT_PREF_GROUP, PRINT_PREF_MARGIN_TOP, pw->margin_top);
	pref_list_double_set(PRINT_PREF_GROUP, PRINT_PREF_MARGIN_BOTTOM, pw->margin_bottom);

	if (pw->layout == PRINT_LAYOUT_PROOF)
		{
		pref_list_double_set(PRINT_PREF_GROUP, PRINT_PREF_PROOF_WIDTH, pw->proof_width);
		pref_list_double_set(PRINT_PREF_GROUP, PRINT_PREF_PROOF_HEIGHT, pw->proof_height);
		}

	if (pw->output == PRINT_OUTPUT_PS_CUSTOM)
		{
		pref_list_string_set(PRINT_PREF_GROUP, PRINT_PREF_PRINTERC, pw->output_custom);
		}

	if (pw->output == PRINT_OUTPUT_RGB_FILE ||
	    pw->output == PRINT_OUTPUT_PS_FILE)
		{
		tab_completion_append_to_history(pw->path_entry, pw->output_path);
		}

	pref_list_int_set(PRINT_PREF_GROUP, PRINT_PREF_TEXT, pw->text_fields);
	pref_list_int_set(PRINT_PREF_GROUP, PRINT_PREF_TEXTSIZE, pw->text_points);
	pref_list_int_set(PRINT_PREF_GROUP, PRINT_PREF_TEXTCOLOR_R, pw->text_r);
	pref_list_int_set(PRINT_PREF_GROUP, PRINT_PREF_TEXTCOLOR_G, pw->text_g);
	pref_list_int_set(PRINT_PREF_GROUP, PRINT_PREF_TEXTCOLOR_B, pw->text_b);

	pref_list_int_set(PRINT_PREF_GROUP, PRINT_PREF_SOURCE, pw->source);
	pref_list_int_set(PRINT_PREF_GROUP, PRINT_PREF_LAYOUT, pw->layout);

	pref_list_double_set(PRINT_PREF_GROUP, PRINT_PREF_IMAGE_SCALE, pw->image_scale);
}

static gboolean print_job_start(PrintWindow *pw, RenderFormat format, PrintOutput output)
{
	GtkWidget *hbox;
	GtkWidget *spinner;
	gchar *msg;

	if (pw->job_dialog) return FALSE;

	pw->job_format = format;
	pw->job_output = output;

	if (!print_job_init(pw))
		{
		print_job_close(pw, TRUE);
		return FALSE;
		}

	if (format == RENDER_FORMAT_PREVIEW)
		{
		print_job_render(pw);
		return TRUE;
		}

	print_pref_store(pw);

	gtk_widget_hide(pw->dialog->dialog);

	pw->job_dialog = file_util_gen_dlg(_("Print"), "print_job_dialog",
					   (GtkWidget *)gtk_window_get_transient_for(GTK_WINDOW(pw->dialog->dialog)), FALSE,
					   print_job_cancel_cb, pw);

	msg = g_strdup_printf(_("Printing %d pages to %s."), print_layout_page_count(pw), print_output_name(pw->output));
	generic_dialog_add_message(pw->job_dialog, NULL, msg, NULL);
	g_free(msg);

	if (pw->job_output == PRINT_OUTPUT_PS_FILE ||
	    pw->job_output == PRINT_OUTPUT_RGB_FILE)
		{
		hbox = pref_box_new(pw->job_dialog->vbox, FALSE, GTK_ORIENTATION_HORIZONTAL, PREF_PAD_SPACE);
		pref_label_new(hbox, _("Filename:"));

		pw->job_progress_label = pref_label_new(hbox, "");
		}
	else
		{
		pw->job_progress_label = NULL;
		}

	pref_spacer(pw->job_dialog->vbox, PREF_PAD_SPACE);

	hbox = pref_box_new(pw->job_dialog->vbox, FALSE, GTK_ORIENTATION_HORIZONTAL, PREF_PAD_SPACE);

	pw->job_progress = gtk_progress_bar_new();
	gtk_box_pack_start(GTK_BOX(hbox), pw->job_progress, TRUE, TRUE, 0);
	gtk_widget_show(pw->job_progress);

	spinner = spinner_new(NULL, SPINNER_SPEED);
	gtk_box_pack_start(GTK_BOX(hbox), spinner, FALSE, FALSE, 0);
	gtk_widget_show(spinner);

	gtk_widget_show(pw->job_dialog->dialog);

	print_job_render(pw);
	print_job_status(pw);

	return TRUE;
}

static void print_window_print_start(PrintWindow *pw)
{
	RenderFormat format;

	switch (pw->output)
		{
		case PRINT_OUTPUT_RGB_FILE:
			format = RENDER_FORMAT_RGB;
			break;
		case PRINT_OUTPUT_PS_FILE:
		case PRINT_OUTPUT_PS_CUSTOM:
		case PRINT_OUTPUT_PS_LPR:
		default:
			format = RENDER_FORMAT_PS;
			break;
		}

	print_job_start(pw, format, pw->output);
}


/*
 *-----------------------------------------------------------------------------
 * combo box util
 *-----------------------------------------------------------------------------
 */

static GtkWidget *print_combo_menu(const gchar *text[], gint count, gint preferred,
				   GCallback func, gpointer data)
{
	GtkWidget *combo;
	gint i;

	combo = gtk_combo_box_new_text();

	for (i = 0 ; i < count; i++)
		{
		gtk_combo_box_append_text(GTK_COMBO_BOX(combo), _(text[i]));
		}

	if (preferred >= 0 && preferred < count)
		{
		gtk_combo_box_set_active(GTK_COMBO_BOX(combo), preferred);
		}

	if (func) g_signal_connect(G_OBJECT(combo), "changed", func, data);

	return combo;
}


/*
 *-----------------------------------------------------------------------------
 * paper selection
 *-----------------------------------------------------------------------------
 */

static GtkWidget *print_paper_menu(GtkWidget *table, gint column, gint row,
				   PaperOrientation preferred, GCallback func, gpointer data)
{
	GtkWidget *combo;
	gint i;

	pref_table_label(table, column, row, (_("Format:")), 1.0);

	combo = gtk_combo_box_new_text();

	i = 0;
	while (print_paper_sizes[i].description)
		{
		gtk_combo_box_append_text(GTK_COMBO_BOX(combo), _(print_paper_sizes[i].description));
		i++;
		}

	gtk_combo_box_set_active(GTK_COMBO_BOX(combo), preferred);
	if (func) g_signal_connect(G_OBJECT(combo), "changed", func, data);

	gtk_table_attach(GTK_TABLE(table), combo, column + 1, column + 2, row, row + 1,
			 GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 0);
	gtk_widget_show(combo);

	return combo;
}

static void print_paper_select_cb(GtkWidget *combo, gpointer data)
{
	PrintWindow *pw = data;
	PaperSize *ps;
	gint n;

	n = gtk_combo_box_get_active(GTK_COMBO_BOX(combo));
	ps = print_paper_size_nth(n);

	if (!ps) return;

	pw->paper_size = n;

	if (pw->paper_size == 0)
		{
		print_window_layout_sync_paper(pw);
		return;
		}

	if (ps->orientation == PAPER_ORIENTATION_PORTRAIT)
		{
		print_window_layout_set_size(pw, ps->width, ps->height);
		}
	else
		{
		print_window_layout_set_size(pw, ps->height, ps->width);
		}
}

static void print_paper_size_cb(GtkWidget *spin, gpointer data)
{
	PrintWindow *pw = data;
	gdouble value;

	value = print_paper_size_convert_units(gtk_spin_button_get_value(GTK_SPIN_BUTTON(spin)),
					       pw->paper_units, PAPER_UNIT_POINTS);

	if (spin == pw->paper_width_spin)
		{
		pw->paper_width = value;
		}
	else
		{
		pw->paper_height = value;
		}

	print_window_layout_set_size(pw, pw->paper_width, pw->paper_height);
}

static GtkWidget *print_paper_units_menu(GtkWidget *table, gint column, gint row,
					 PaperUnits units, GCallback func, gpointer data)
{
	GtkWidget *combo;

	pref_table_label(table, column, row, (_("Units:")), 1.0);

	combo = print_combo_menu(print_paper_units, PAPER_UNIT_COUNT, units, func, data);

	gtk_table_attach(GTK_TABLE(table), combo, column + 1, column + 2, row, row + 1,
			 GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 0);
	gtk_widget_show(combo);

	return combo;
}

static void print_paper_units_set(PrintWindow *pw, PaperUnits units)
{
	PaperUnits old_units;

	if (units >= PAPER_UNIT_COUNT) return;

	old_units = pw->paper_units;
	pw->paper_units = units;
	print_window_layout_sync_paper(pw);

	if ((units == PAPER_UNIT_MM || units == PAPER_UNIT_CM) !=
	    (old_units == PAPER_UNIT_MM || old_units == PAPER_UNIT_CM))
		{
		print_window_layout_render(pw);
		}
}

static void print_paper_units_cb(GtkWidget *combo, gpointer data)
{
	PrintWindow *pw = data;
	PaperUnits units;

	units = gtk_combo_box_get_active(GTK_COMBO_BOX(combo));

	print_paper_units_set(pw, units);
}

static GtkWidget *print_paper_orientation_menu(GtkWidget *table, gint column, gint row,
					       PaperOrientation preferred,
					       GCallback func, gpointer data)
{
	GtkWidget *combo;

	pref_table_label(table, column, row, (_("Orientation:")), 1.0);

	combo = print_combo_menu(print_paper_orientation, PAPER_ORIENTATION_COUNT, preferred, func, data);

	gtk_table_attach(GTK_TABLE(table), combo, column + 1, column + 2, row, row + 1,
			 GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 0);
	gtk_widget_show(combo);

	return combo;
}

static void print_paper_orientation_cb(GtkWidget *combo, gpointer data)
{
	PrintWindow *pw = data;
	PaperOrientation o;

	o = gtk_combo_box_get_active(GTK_COMBO_BOX(combo));

	print_window_layout_set_orientation(pw, o);
}

static void print_paper_margin_cb(GtkWidget *spin, gpointer data)
{
	PrintWindow *pw = data;
	gdouble value;

	value = print_paper_size_convert_units(gtk_spin_button_get_value(GTK_SPIN_BUTTON(spin)),
					       pw->paper_units, PAPER_UNIT_POINTS);

	if (spin == pw->margin_left_spin)
		{
		pw->margin_left = CLAMP(value, 0.0, pw->paper_width);
		}
	else if (spin == pw->margin_right_spin)
		{
		pw->margin_right = CLAMP(value, 0.0, pw->paper_width);
		}
	else if (spin == pw->margin_top_spin)
		{
		pw->margin_top = CLAMP(value, 0.0, pw->paper_height);
		}
	else if (spin == pw->margin_bottom_spin)
		{
		pw->margin_bottom = CLAMP(value, 0.0, pw->paper_height);
		}

	print_window_layout_set_size(pw, pw->paper_width, pw->paper_height);
}

static GtkWidget *print_misc_menu(GtkWidget *parent_box, gint preferred,
				  const gchar *title, const gchar *key,
				  gint count, const gchar **text,
				  GCallback func, gpointer data)
{
	GtkWidget *box;
	GtkWidget *button = NULL;
	gint i;

	box = pref_group_new(parent_box, FALSE, title, GTK_ORIENTATION_VERTICAL);

	for (i = 0; i < count; i++)
		{
		button = gtk_radio_button_new_with_label_from_widget(GTK_RADIO_BUTTON(button), _(text[i]));
		if (i == preferred)
			{
			gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), TRUE);
			}
		g_object_set_data(G_OBJECT(button), key, GINT_TO_POINTER(i));
		if (func) g_signal_connect(G_OBJECT(button), "clicked", func, data);
		gtk_box_pack_start(GTK_BOX(box), button, FALSE, FALSE, 0);
		gtk_widget_show(button);
		}

	return box;
}

static void print_source_select_cb(GtkWidget *widget, gpointer data)
{
	PrintWindow *pw = data;

	if (!gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget))) return;

	pw->source = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(widget), "print_source"));
	print_window_layout_size(pw);
}

static void print_layout_select_cb(GtkWidget *widget, gpointer data)
{
	PrintWindow *pw = data;

	if (!gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget))) return;

	pw->layout = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(widget), "print_layout"));

	print_window_layout_sync_layout(pw);
	print_window_layout_size(pw);
}

static void print_image_scale_cb(GtkWidget *spin, gpointer data)
{
	PrintWindow *pw = data;

	pw->image_scale = gtk_spin_button_get_value(GTK_SPIN_BUTTON(spin));

	print_window_layout_set_size(pw, pw->paper_width, pw->paper_height);
}

static void print_proof_size_cb(GtkWidget *spin, gpointer data)
{
	PrintWindow *pw = data;
	gdouble value;

	value = print_paper_size_convert_units(gtk_spin_button_get_value(GTK_SPIN_BUTTON(spin)),
					       pw->paper_units, PAPER_UNIT_POINTS);

	if (spin == pw->proof_width_spin)
		{
		pw->proof_width = value;
		}
	else
		{
		pw->proof_height = value;
		}

	print_window_layout_render(pw);
}

static GtkWidget *print_output_menu(GtkWidget *table, gint column, gint row,
				    PrintOutput preferred, GCallback func, gpointer data)
{
	GtkWidget *combo;

	pref_table_label(table, column, row, (_("Destination:")), 1.0);

	combo = print_combo_menu(print_output_text, PRINT_OUTPUT_COUNT, preferred, func, data);

	gtk_table_attach(GTK_TABLE(table), combo, column + 1, column + 2, row, row + 1,
			 GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 0);
	gtk_widget_show(combo);

	return combo;
}

static void print_custom_entry_set(PrintWindow *pw, GtkWidget *combo)
{
	GtkListStore *store;
	const gchar *text;
	GList *list;
	GList *work;

	store = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(combo)));
	gtk_list_store_clear(store);

	list = print_window_list_printers();
	work = list;
	while (work)
		{
		gchar *name;
		gchar *buf;

		name = work->data;
		work = work->next;

		buf = g_strdup_printf(PRINT_LPR_CUSTOM, name);
		gtk_combo_box_append_text(GTK_COMBO_BOX(combo), buf);
		g_free(buf);
		}
	string_list_free(list);

	if (pref_list_string_get(PRINT_PREF_GROUP, PRINT_PREF_PRINTERC, &text))
		{
		gtk_entry_set_text(GTK_ENTRY(pw->custom_entry), text);
		}
	else
		{
		text = gtk_entry_get_text(GTK_ENTRY(pw->custom_entry));
		if (!text || strlen(text) == 0)
			{
			gchar *buf;

			buf = g_strdup_printf(PRINT_LPR_CUSTOM, _("<printer name>"));
			gtk_entry_set_text(GTK_ENTRY(pw->custom_entry), buf);
			g_free(buf);
			}
		}
}

static void print_output_set(PrintWindow *pw, PrintOutput output)
{
	gboolean use_file = FALSE;
	gboolean use_custom = FALSE;
	gboolean use_format = FALSE;

	pw->output = output;

	switch (pw->output)
		{
		case PRINT_OUTPUT_RGB_FILE:
			use_file = TRUE;
			use_format = TRUE;
			break;
		case PRINT_OUTPUT_PS_FILE:
			use_file = TRUE;
			break;
		case PRINT_OUTPUT_PS_CUSTOM:
			use_custom = TRUE;
			break;
		case PRINT_OUTPUT_PS_LPR:
		default:
			break;
		}

	gtk_widget_set_sensitive(gtk_widget_get_parent(pw->path_entry), use_file);
	gtk_widget_set_sensitive(gtk_widget_get_parent(pw->custom_entry), use_custom);
	gtk_widget_set_sensitive(pw->path_format_menu, use_format);
	gtk_widget_set_sensitive(pw->max_dpi_menu, !use_format);
}

static void print_output_cb(GtkWidget *combo, gpointer data)
{
	PrintWindow *pw = data;
	PrintOutput output;

	output = gtk_combo_box_get_active(GTK_COMBO_BOX(combo));

	print_output_set(pw, output);
}

static GtkWidget *print_output_format_menu(GtkWidget * table, gint column, gint row,
					   PrintFileFormat preferred, GCallback func, gpointer data)
{
	GtkWidget *combo;

	combo = print_combo_menu(print_file_format_text, PRINT_FILE_COUNT, preferred, func, data);

	gtk_table_attach(GTK_TABLE(table), combo, column, column + 1, row, row + 1,
			 GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 0);
	gtk_widget_show(combo);

	return combo;
}

static void print_output_format_cb(GtkWidget *combo, gpointer data)
{
	PrintWindow *pw = data;

	pw->output_format = gtk_combo_box_get_active(GTK_COMBO_BOX(combo));
}

static GtkWidget *print_output_dpi_menu(GtkWidget * table, gint column, gint row,
					gdouble dpi, GCallback func, gpointer data)
{
	static gint dpilist[] = { 150, 300, 600, 1200, 0, -1};
	GtkWidget *combo;
	GtkListStore *store;
	GtkCellRenderer *renderer;
	gint current = 1;
	gint i;

	store = gtk_list_store_new(2, G_TYPE_STRING, G_TYPE_INT);

	i = 0;
	while (dpilist[i] != -1)
		{
		GtkTreeIter iter;
		gchar *text;

		if (dpilist[i] == 0)
			{
			text = g_strdup(_("Unlimited"));
			}
		else
			{
			text = g_strdup_printf("%d", dpilist[i]);
			}

		gtk_list_store_append(store, &iter);
		gtk_list_store_set(store, &iter, 0, text, 1, dpilist[i], -1);
		g_free(text);

		if (dpi == (gdouble)dpilist[i]) current = i;

		i++;
		}

	combo = gtk_combo_box_new_with_model(GTK_TREE_MODEL(store));
	g_object_unref(store);

	gtk_combo_box_set_active(GTK_COMBO_BOX(combo), current);
	if (func) g_signal_connect(G_OBJECT(combo), "changed", func, data);

	renderer = gtk_cell_renderer_text_new();
	gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(combo), renderer, TRUE);
	gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(combo), renderer, "text", 0, NULL);

	gtk_table_attach(GTK_TABLE(table), combo, column, column + 1, row, row + 1,
			 GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 0);
	gtk_widget_show(combo);

	return combo;
}

static void print_output_dpi_cb(GtkWidget *combo, gpointer data)
{
	PrintWindow *pw = data;
	GtkTreeModel *store;
	GtkTreeIter iter;
	gint n = -1;

	store = gtk_combo_box_get_model(GTK_COMBO_BOX(combo));
	if (!gtk_combo_box_get_active_iter(GTK_COMBO_BOX(combo), &iter)) return;
	gtk_tree_model_get(store, &iter, 1, &n, -1);

	pw->max_dpi = (gdouble)n;
}

static void print_text_field_set(PrintWindow *pw, TextInfo field, gboolean active)
{
	if (active)
		{
		pw->text_fields |= field;
		}
	else
		{
		pw->text_fields &= ~field;
		}

	print_window_layout_render(pw);
}

static void print_text_cb_name(GtkWidget *widget, gpointer data)
{
	PrintWindow *pw = data;
	gboolean active;

	active = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget));
	print_text_field_set(pw, TEXT_INFO_FILENAME, active);
}

static void print_text_cb_path(GtkWidget *widget, gpointer data)
{
	PrintWindow *pw = data;
	gboolean active;

	active = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget));
	print_text_field_set(pw, TEXT_INFO_FILEPATH, active);
}

static void print_text_cb_date(GtkWidget *widget, gpointer data)
{
	PrintWindow *pw = data;
	gboolean active;

	active = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget));
	print_text_field_set(pw, TEXT_INFO_FILEDATE, active);
}

static void print_text_cb_size(GtkWidget *widget, gpointer data)
{
	PrintWindow *pw = data;
	gboolean active;

	active = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget));
	print_text_field_set(pw, TEXT_INFO_FILESIZE, active);
}

static void print_text_cb_dims(GtkWidget *widget, gpointer data)
{
	PrintWindow *pw = data;
	gboolean active;

	active = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget));
	print_text_field_set(pw, TEXT_INFO_DIMENSIONS, active);
}

static void print_text_cb_points(GtkWidget *widget, gpointer data)
{
	PrintWindow *pw = data;

	pw->text_points = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(widget));
	print_window_layout_render(pw);
}

static void print_text_menu(GtkWidget *box, PrintWindow *pw)
{
	GtkWidget *group;

	group = pref_group_new(box, FALSE, _("Show"), GTK_ORIENTATION_VERTICAL);

	pref_checkbox_new(group, _("Name"), (pw->text_fields & TEXT_INFO_FILENAME),
			  G_CALLBACK(print_text_cb_name), pw);
	pref_checkbox_new(group, _("Path"), (pw->text_fields & TEXT_INFO_FILEPATH),
			  G_CALLBACK(print_text_cb_path), pw);
	pref_checkbox_new(group, _("Date"), (pw->text_fields & TEXT_INFO_FILEDATE),
			  G_CALLBACK(print_text_cb_date), pw);
	pref_checkbox_new(group, _("Size"), (pw->text_fields & TEXT_INFO_FILESIZE),
			  G_CALLBACK(print_text_cb_size), pw);
	pref_checkbox_new(group, _("Dimensions"), (pw->text_fields & TEXT_INFO_DIMENSIONS),
			  G_CALLBACK(print_text_cb_dims), pw);

	group = pref_group_new(box, FALSE, _("Font"), GTK_ORIENTATION_VERTICAL);

	pref_spin_new(group, _("Size:"), _("points"),
		      8.0, 100.0, 1.0, 0, pw->text_points,
		      G_CALLBACK(print_text_cb_points), pw);

#if 0
	button = color_selection_new();
	gtk_box_pack_start(GTK_BOX(group), button, FALSE, FALSE, 0);
	gtk_widget_show(button);
#endif
}

/*
 *-----------------------------------------------------------------------------
 * print window
 *-----------------------------------------------------------------------------
 */

static void print_window_close(PrintWindow *pw)
{
	print_window_layout_render_stop(pw);

	generic_dialog_close(pw->dialog);
	pw->dialog = NULL;

	print_job_close(pw, FALSE);

	file_data_unref(pw->source_fd);
	filelist_free(pw->source_selection);
	filelist_free(pw->source_list);

	g_free(pw->output_path);
	g_free(pw->output_custom);

	g_free(pw);
}

static void print_window_print_cb(GenericDialog *gd, gpointer data)
{
	PrintWindow *pw = data;

	switch (pw->output)
		{
		case PRINT_OUTPUT_RGB_FILE:
		case PRINT_OUTPUT_PS_FILE:
			g_free(pw->output_path);
			pw->output_path = g_strdup(gtk_entry_get_text(GTK_ENTRY(pw->path_entry)));
			break;
		case PRINT_OUTPUT_PS_CUSTOM:
			g_free(pw->output_custom);
			pw->output_custom = g_strdup(gtk_entry_get_text(GTK_ENTRY(pw->custom_entry)));
			break;
		case PRINT_OUTPUT_PS_LPR:
		default:
			break;
		}

	print_window_print_start(pw);
}

static void print_window_cancel_cb(GenericDialog *gd, gpointer data)
{
	PrintWindow *pw = data;

	print_window_close(pw);
}

static gint print_pref_int(const gchar *key, gint fallback)
{
	gint value;

	if (pref_list_int_get(PRINT_PREF_GROUP, key, &value)) return value;
	return fallback;
}

static gdouble print_pref_double(const gchar *key, gdouble fallback)
{
	gdouble value;

	if (pref_list_double_get(PRINT_PREF_GROUP, key, &value)) return value;
	return fallback;
}

void print_window_new(FileData *fd, GList *selection, GList *list, GtkWidget *parent)
{
	PrintWindow *pw;
	GdkGeometry geometry;
	GtkWidget *main_box;
	GtkWidget *vbox;
	GtkWidget *label;
	GtkWidget *combo;
	GtkWidget *box;
	GtkWidget *table;

	pw = g_new0(PrintWindow, 1);

	pw->source_fd = file_data_ref(fd);
	pw->source_selection = selection;
	pw->source_list = list;

	pw->source = print_pref_int(PRINT_PREF_SOURCE, PRINT_SOURCE_SELECTION);
	pw->layout = print_pref_int(PRINT_PREF_LAYOUT, PRINT_LAYOUT_IMAGE);
	
	pw->image_scale = print_pref_double(PRINT_PREF_IMAGE_SCALE, 100.0);
	
	pw->output = print_pref_int(PRINT_PREF_OUTPUT, PRINT_OUTPUT_PS_LPR);
	pw->output_format = print_pref_int(PRINT_PREF_FORMAT, PRINT_FILE_JPG_NORMAL);

	pw->max_dpi = print_pref_double(PRINT_PREF_DPI, PRINT_PS_DPI_DEFAULT);

	pw->paper_units = print_pref_int(PRINT_PREF_UNITS, paper_unit_default());
	pw->paper_size = print_pref_int(PRINT_PREF_SIZE, 1);
	if (pw->paper_size == 0 ||
	    !print_paper_size_lookup(pw->paper_size, &pw->paper_width, &pw->paper_height))
		{
		pw->paper_width = print_pref_double(PRINT_PREF_CUSTOM_WIDTH, 360.0);
		pw->paper_height = print_pref_double(PRINT_PREF_CUSTOM_HEIGHT, 720.0);
		}
	pw->paper_orientation = print_pref_int(PRINT_PREF_ORIENTATION, PAPER_ORIENTATION_PORTRAIT);

	pw->margin_left = print_pref_double(PRINT_PREF_MARGIN_LEFT, PRINT_MARGIN_DEFAULT);
	pw->margin_right = print_pref_double(PRINT_PREF_MARGIN_RIGHT, PRINT_MARGIN_DEFAULT);
	pw->margin_top = print_pref_double(PRINT_PREF_MARGIN_TOP, PRINT_MARGIN_DEFAULT);
	pw->margin_bottom = print_pref_double(PRINT_PREF_MARGIN_BOTTOM, PRINT_MARGIN_DEFAULT);

	pw->proof_width = print_pref_double(PRINT_PREF_PROOF_WIDTH, PRINT_PROOF_DEFAULT_SIZE);
	pw->proof_height = print_pref_double(PRINT_PREF_PROOF_HEIGHT, PRINT_PROOF_DEFAULT_SIZE);

	pw->text_fields = print_pref_int(PRINT_PREF_TEXT, TEXT_INFO_FILENAME);
	pw->text_points = print_pref_int(PRINT_PREF_TEXTSIZE, 10);
	pw->text_r = print_pref_int(PRINT_PREF_TEXTCOLOR_R, 0);
	pw->text_g = print_pref_int(PRINT_PREF_TEXTCOLOR_G, 0);
	pw->text_b = print_pref_int(PRINT_PREF_TEXTCOLOR_B, 0);

	pw->save_settings = print_pref_int(PRINT_PREF_SAVE, TRUE);

	pw->dialog = file_util_gen_dlg(_("Print"), "print_dialog",
				       parent, FALSE,
				       print_window_cancel_cb, pw);

	geometry.min_width = DEFAULT_MINIMAL_WINDOW_SIZE;
	geometry.min_height = DEFAULT_MINIMAL_WINDOW_SIZE;
	geometry.base_width = PRINT_DLG_WIDTH;
	geometry.base_height = PRINT_DLG_HEIGHT;
	gtk_window_set_geometry_hints(GTK_WINDOW(pw->dialog->dialog), NULL, &geometry,
				      GDK_HINT_MIN_SIZE | GDK_HINT_BASE_SIZE);

	pw->print_button = generic_dialog_add_button(pw->dialog, GTK_STOCK_PRINT, NULL, print_window_print_cb, TRUE);

	main_box = pref_box_new(pw->dialog->vbox, TRUE, GTK_ORIENTATION_HORIZONTAL, PREF_PAD_GAP);

	pw->notebook = gtk_notebook_new();
	gtk_notebook_set_tab_pos(GTK_NOTEBOOK(pw->notebook), GTK_POS_TOP);
	gtk_box_pack_start(GTK_BOX(main_box), pw->notebook, FALSE, FALSE, 0);

	/* layout tab */

	vbox = gtk_vbox_new(FALSE, 0);
	gtk_container_set_border_width(GTK_CONTAINER(vbox), PREF_PAD_BORDER);
	gtk_widget_show(vbox);
	label = gtk_label_new(_("Layout"));
	gtk_notebook_append_page(GTK_NOTEBOOK(pw->notebook), vbox, label);

	print_misc_menu(vbox, pw->source, _("Source"), "print_source",
			PRINT_SOURCE_COUNT, print_source_text,
			G_CALLBACK(print_source_select_cb), pw);

	box = print_misc_menu(vbox, pw->layout, _("Layout"), "print_layout",
			      PRINT_LAYOUT_COUNT, print_layout_text,
			      G_CALLBACK(print_layout_select_cb), pw);

	pref_spacer(box, PREF_PAD_GROUP);

	table = pref_table_new(box, 2, 2, FALSE, FALSE);

	pw->image_scale_spin = pref_table_spin(table, 0, 0, _("Image size:"), "%",
					       5.0, 100.0, 1.0, 0, pw->image_scale,
					       G_CALLBACK(print_image_scale_cb), pw);

	label = pref_table_label(table, 0, 1, _("Proof size:"), 1.0);
	pw->proof_group = pref_table_box(table, 1, 1, GTK_ORIENTATION_HORIZONTAL, NULL);
	pref_link_sensitivity(label, pw->proof_group);

	pw->proof_width_spin = pref_spin_new(pw->proof_group, NULL, NULL,
					     0.0, 50.0, 0.1, 3, 0.0,
					     G_CALLBACK(print_proof_size_cb), pw);
	pw->proof_height_spin = pref_spin_new(pw->proof_group, "x", NULL,
					      0.0, 50.0, 0.1, 3, 0.0,
					      G_CALLBACK(print_proof_size_cb), pw);

	/* text tab */

	vbox = gtk_vbox_new(FALSE, 0);
	gtk_container_set_border_width(GTK_CONTAINER(vbox), PREF_PAD_BORDER);
	gtk_widget_show(vbox);
	label = gtk_label_new(_("Text"));
	gtk_notebook_append_page(GTK_NOTEBOOK(pw->notebook), vbox, label);

	print_text_menu(vbox, pw);

	/* paper tab */

	vbox = gtk_vbox_new(FALSE, 0);
	gtk_container_set_border_width(GTK_CONTAINER(vbox), PREF_PAD_BORDER);
	gtk_widget_show(vbox);
	label = gtk_label_new(_("Paper"));
	gtk_notebook_append_page(GTK_NOTEBOOK(pw->notebook), vbox, label);

	table = pref_table_new(vbox, 2, 4, FALSE, FALSE);

	print_paper_menu(table, 0, 0, pw->paper_size, G_CALLBACK(print_paper_select_cb), pw);

	label = pref_table_label(table, 0, 1, (_("Size:")), 1.0);
	box = pref_table_box(table, 1, 1, GTK_ORIENTATION_HORIZONTAL, NULL);
	pw->paper_width_spin = pref_spin_new(box, NULL, NULL,
					     1.0, 10000.0, 1.0, 2, 66,
					     G_CALLBACK(print_paper_size_cb), pw);
	pw->paper_height_spin = pref_spin_new(box, "x", NULL,
					      1.0, 10000.0, 1.0, 2, 66,
					      G_CALLBACK(print_paper_size_cb), pw);
	pref_link_sensitivity(label, pw->paper_width_spin);

	pw->paper_units_menu = print_paper_units_menu(table, 0, 2, pw->paper_units,
					G_CALLBACK(print_paper_units_cb), pw);

	print_paper_orientation_menu(table, 0, 3, pw->paper_orientation,
				     G_CALLBACK(print_paper_orientation_cb), pw);

	box = pref_group_new(vbox, FALSE, _("Margins"), GTK_ORIENTATION_VERTICAL);
	table = pref_table_new(box, 4, 2, FALSE, FALSE);
	pw->margin_left_spin = pref_table_spin(table, 0, 0, _("Left:"), NULL,
					0.0, 50.0, 0.1, 3, 0.0,
					G_CALLBACK(print_paper_margin_cb), pw);
	pw->margin_right_spin = pref_table_spin(table, 2, 0, _("Right:"), NULL,
					0.0, 50.0, 0.1, 3, 0.0,
					G_CALLBACK(print_paper_margin_cb), pw);
	pw->margin_top_spin = pref_table_spin(table, 0, 1, _("Top:"), NULL,
					0.0, 50.0, 0.1, 3, 0.0,
					G_CALLBACK(print_paper_margin_cb), pw);
	pw->margin_bottom_spin = pref_table_spin(table, 2, 1, _("Bottom:"), NULL,
					0.0, 50.0, 0.1, 3, 0.0,
					G_CALLBACK(print_paper_margin_cb), pw);

	/* printer tab */

	vbox = gtk_vbox_new(FALSE, 0);
	gtk_container_set_border_width(GTK_CONTAINER(vbox), PREF_PAD_BORDER);
	gtk_widget_show(vbox);
	label = gtk_label_new(_("Printer"));
	gtk_notebook_append_page(GTK_NOTEBOOK(pw->notebook), vbox, label);

	table = pref_table_new(vbox, 2, 5, FALSE, FALSE);
	print_output_menu(table, 0, 0, pw->output, G_CALLBACK(print_output_cb), pw);

	label = pref_table_label(table, 0, 1, _("Custom printer:"), 1.0);
	combo = history_combo_new(&pw->custom_entry, NULL, "print_custom", -1);
	print_custom_entry_set(pw, combo);
	gtk_table_attach(GTK_TABLE(table), combo, 1, 2, 1, 2,
			 GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 0);
	gtk_widget_show(combo);

	pref_link_sensitivity(label, combo);

	label = pref_table_label(table, 0, 2, _("File:"), 1.0);
	combo = tab_completion_new_with_history(&pw->path_entry, NULL, "print_path", -1, NULL, pw);
	tab_completion_add_select_button(pw->path_entry, NULL, FALSE);
	gtk_table_attach(GTK_TABLE(table), combo, 1, 2, 2, 3,
			 GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 0);
	gtk_widget_show(combo);

	pref_link_sensitivity(label, combo);

	label = pref_table_label(table, 0, 3, _("File format:"), 1.0);
	pw->path_format_menu = print_output_format_menu(table, 1, 3, pw->output_format,
							G_CALLBACK(print_output_format_cb), pw);
	pref_link_sensitivity(label, pw->path_format_menu);

	label = pref_table_label(table, 0, 4, _("DPI:"), 1.0);
	pw->max_dpi_menu = print_output_dpi_menu(table, 1, 4, pw->max_dpi,
						 G_CALLBACK(print_output_dpi_cb), pw);
	pref_link_sensitivity(label, pw->max_dpi_menu);

	print_output_set(pw, pw->output);

	vbox = print_window_layout_setup(pw, main_box);
	pref_checkbox_new_int(vbox, _("Remember print settings"), pw->save_settings, &pw->save_settings);

	print_window_layout_sync_layout(pw);
	print_window_layout_sync_paper(pw);

	gtk_widget_show(pw->notebook);
	gtk_widget_show(pw->dialog->dialog);
}
/* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */
