sane-project-backends/frontend/xscanimage.c

1733 wiersze
42 KiB
C

/* xscanimage -- a graphical scanner-oriented SANE frontend
Authors:
Andreas Beck <becka@sunserver1.rz.uni-duesseldorf.de>
Tristan Tarrant <ttarrant@etnoteam.it>
David Mosberger-Tang <davidm@azstarnet.com>
Copyright (C) 1997, 1998 Andreas Beck, Tristan Tarrant, and David
Mosberger
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., 675 Mass Ave, Cambridge, MA 02139, USA. */
#ifdef _AIX
# include <lalloca.h> /* MUST come first for AIX! */
#endif
#include <sane/config.h>
#include <lalloca.h>
#include <errno.h>
#include <fcntl.h>
#include <getopt.h>
#include <gtkglue.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sane/sane.h>
#include <sane/sanei.h>
#include <sane/saneopts.h>
#include <progress.h>
#include <preferences.h>
#include <preview.h>
#ifndef PATH_MAX
# define PATH_MAX 1024
#endif
#define OUTFILENAME "out.pnm"
#ifdef HAVE_LIBGIMP_GIMP_H
#include <libgimp/gimp.h>
static void query (void);
static void run (char *name, int nparams, GParam * param,
int *nreturn_vals, GParam ** return_vals);
GPlugInInfo PLUG_IN_INFO =
{
NULL, /* init_proc */
NULL, /* quit_proc */
query, /* query_proc */
run, /* run_proc */
};
#endif /* HAVE_LIBGIMP_GIMP_H */
enum
{
STANDALONE, GIMP_EXTENSION
};
static struct
{
GtkWidget *shell;
GtkWidget *hruler;
GtkWidget *vruler;
GtkWidget *info_label;
Preview *preview;
gint32 mode;
/* various scanning related state: */
size_t num_bytes;
size_t bytes_read;
Progress_t *progress;
int input_tag;
SANE_Parameters param;
int x, y;
/* for standalone mode: */
GtkWidget *filename_entry;
FILE *out;
long header_size;
#ifdef HAVE_LIBGIMP_GIMP_H
/* for GIMP mode: */
gint32 image_ID;
GDrawable *drawable;
guchar *tile;
unsigned tile_offset;
GPixelRgn region;
int first_frame; /* used for RED/GREEN/BLUE frames */
#endif
}
scan_win;
static const char *prog_name;
static GtkWidget *choose_device_dialog;
static GSGDialog *dialog;
static const SANE_Device **devlist;
static gint seldev = -1; /* The selected device */
static gint ndevs; /* The number of available devices */
static struct option long_options[] =
{
{"help", no_argument, NULL, 'h'},
{"version", no_argument, NULL, 'V'},
{0, }
};
/* forward declarations: */
int main (int argc, char ** argv);
static void interface (int argc, char **argv);
static void scan_start (void);
static void scan_done (void);
#ifdef HAVE_LIBGIMP_GIMP_H
static int
encode_devname (const char *devname, int n, char *buf)
{
static const char hexdigit[] = "0123456789abcdef";
char *dst, *limit;
const char *src;
char ch;
limit = buf + n;
for (src = devname, dst = buf; *src; ++src)
{
if (dst >= limit)
return -1;
ch = *src;
/* don't use the ctype.h macros here since we don't want to
allow anything non-ASCII here... */
if ((ch >= '0' && ch <= '9')
|| (ch >= 'a' && ch <= 'z')
|| (ch >= 'A' && ch <= 'Z'))
*dst++ = ch;
else
{
/* encode */
if (dst + 4 >= limit)
return -1;
*dst++ = '-';
if (ch == '-')
*dst++ = '-';
else
{
*dst++ = hexdigit[(ch >> 4) & 0x0f];
*dst++ = hexdigit[(ch >> 0) & 0x0f];
*dst++ = '-';
}
}
}
if (dst >= limit)
return -1;
*dst = '\0';
return 0;
}
static int
decode_devname (const char *encoded_devname, int n, char *buf)
{
char *dst, *limit;
const char *src;
char ch, val;
limit = buf + n;
for (src = encoded_devname, dst = buf; *src; ++dst)
{
if (dst >= limit)
return -1;
ch = *src++;
/* don't use the ctype.h macros here since we don't want to
allow anything non-ASCII here... */
if (ch != '-')
*dst = ch;
else
{
/* decode */
ch = *src++;
if (ch == '-')
*dst = ch;
else
{
if (ch >= 'a' && ch <= 'f')
val = (ch - 'a') + 10;
else
val = (ch - '0');
val <<= 4;
ch = *src++;
if (ch >= 'a' && ch <= 'f')
val |= (ch - 'a') + 10;
else
val |= (ch - '0');
*dst = val;
++src; /* simply skip terminating '-' for now... */
}
}
}
if (dst >= limit)
return -1;
*dst = '\0';
return 0;
}
static void
query (void)
{
static GParamDef args[] =
{
{PARAM_INT32, "run_mode", "Interactive, non-interactive"},
};
static GParamDef *return_vals = NULL;
static int nargs = sizeof (args) / sizeof (args[0]);
static int nreturn_vals = 0;
char mpath[1024];
char name[1024];
size_t len;
int i, j;
gimp_install_procedure (
"xscanimage",
"Front-end to the SANE interface",
"This function provides access to scanners and other image acquisition "
"devices through the SANE (Scanner Access Now Easy) interface.",
"Andy Beck, Tristan Tarrant, and David Mosberger",
"Andy Beck, Tristan Tarrant, and David Mosberger",
"8th June 1997",
"<Toolbox>/Xtns/Acquire Image/Device dialog...",
"RGB, GRAY",
PROC_EXTENSION,
nargs, nreturn_vals,
args, return_vals);
sane_init (0, 0);
sane_get_devices (&devlist, SANE_FALSE);
for (i = 0; devlist[i]; ++i)
{
strcpy (name, "xscanimage-");
if (encode_devname (devlist[i]->name, sizeof (name) - 11, name + 11) < 0)
continue; /* name too long... */
strncpy (mpath, "<Toolbox>/Xtns/Acquire Image/", sizeof (mpath));
len = strlen (mpath);
for (j = 0; devlist[i]->name[j]; ++j)
{
if (devlist[i]->name[j] == '/')
mpath[len++] = '\'';
else
mpath[len++] = devlist[i]->name[j];
}
mpath[len++] = '\0';
gimp_install_procedure
(name, "Front-end to the SANE interface",
"This function provides access to scanners and other image "
"acquisition devices through the SANE (Scanner Access Now Easy) "
"interface.",
"Andy Beck, Tristan Tarrant, and David Mosberger",
"Andy Beck, Tristan Tarrant, and David Mosberger",
"8th June 1997", mpath, "RGB, GRAY", PROC_EXTENSION,
nargs, nreturn_vals, args, return_vals);
}
sane_exit ();
}
static void
run (char *name, int nparams, GParam * param,
int *nreturn_vals, GParam ** return_vals)
{
static GParam values[2];
GRunModeType run_mode;
char devname[1024];
char *args[2];
int nargs;
run_mode = param[0].data.d_int32;
scan_win.mode = GIMP_EXTENSION;
*nreturn_vals = 1;
*return_vals = values;
values[0].type = PARAM_STATUS;
values[0].data.d_status = STATUS_CALLING_ERROR;
nargs = 0;
args[nargs++] = "xscanimage";
seldev = -1;
if (strncmp (name, "xscanimage-", 11) == 0)
{
if (decode_devname (name + 11, sizeof (devname), devname) < 0)
return; /* name too long */
args[nargs++] = devname;
}
switch (run_mode)
{
case RUN_INTERACTIVE:
interface (nargs, args);
values[0].data.d_status = STATUS_SUCCESS;
break;
case RUN_NONINTERACTIVE:
/* Make sure all the arguments are there! */
break;
case RUN_WITH_LAST_VALS:
/* Possibly retrieve data */
break;
default:
break;
}
}
static void
null_print_func (const gchar *msg)
{
}
#endif /* HAVE_LIBGIMP_GIMP_H */
static void
update_preview (GSGDialog *dialog, void *arg)
{
if (scan_win.preview)
preview_update (scan_win.preview);
}
/* Update the info line with the latest size information. */
static void
update_param (GSGDialog *dialog, void *arg)
{
SANE_Parameters params;
gchar buf[200];
if (!scan_win.info_label)
return;
if (sane_get_parameters (gsg_dialog_get_device (dialog), &params)
== SANE_STATUS_GOOD)
{
u_long size = 10 * params.bytes_per_line * params.lines;
const char *unit = "B";
if (params.format >= SANE_FRAME_RED && params.format <= SANE_FRAME_BLUE)
size *= 3;
if (size >= 10 * 1024 * 1024)
{
size /= 1024 * 1024;
unit = "MB";
}
else if (size >= 10 * 1024)
{
size /= 1024;
unit = "KB";
}
sprintf (buf, "%dx%d: %lu.%01lu %s", params.pixels_per_line,
params.lines, size / 10, size % 10, unit);
}
else
sprintf (buf, "Invalid parameters.");
gtk_label_set (GTK_LABEL (scan_win.info_label), buf);
if (scan_win.preview)
preview_update (scan_win.preview);
}
static void
pref_xscan_save (void)
{
char filename[PATH_MAX];
int fd;
/* first save xscanimage-specific preferences: */
gsg_make_path (sizeof (filename), filename, "xscanimage", "xscanimage",
0, ".rc");
fd = open (filename, O_WRONLY | O_CREAT | O_TRUNC, 0666);
if (fd < 0)
{
char buf[256];
snprintf (buf, sizeof (buf), "Failed to create file: %s.",
strerror (errno));
gsg_error (buf);
return;
}
preferences_save (fd);
close (fd);
}
static void
pref_xscan_restore (void)
{
char filename[PATH_MAX];
int fd;
gsg_make_path (sizeof (filename), filename, "xscanimage", "xscanimage",
0, ".rc");
fd = open (filename, O_RDONLY);
if (fd >= 0)
{
preferences_restore (fd);
close (fd);
}
if (!preferences.filename)
preferences.filename = strdup (OUTFILENAME);
}
static void
quit_xscan (void)
{
if (scan_win.preview)
{
Preview *preview = scan_win.preview;
scan_win.preview = 0;
preview_destroy (preview);
}
while (gsg_message_dialog_active)
{
if (!gtk_events_pending ())
usleep (100000);
gtk_main_iteration ();
}
pref_xscan_save ();
if (dialog && gsg_dialog_get_device (dialog))
sane_close (gsg_dialog_get_device (dialog));
sane_exit ();
gtk_main_quit ();
#ifdef HAVE_LIBGIMP_GIMP_H
if (scan_win.mode == GIMP_EXTENSION)
gimp_quit ();
#endif
exit (0);
}
/* Invoked when window manager's "delete" (or "close") function is
invoked. */
static gint
scan_win_delete (GtkWidget *w, gpointer data)
{
quit_xscan();
return FALSE;
}
static void
preview_window_destroyed (GtkWidget *widget, gpointer call_data)
{
gtk_toggle_button_set_state (GTK_TOGGLE_BUTTON (call_data), FALSE);
}
/* Invoked when the preview button is pressed. */
static void
scan_preview (GtkWidget * widget, gpointer call_data)
{
if (GTK_TOGGLE_BUTTON (widget)->active)
{
if (!scan_win.preview)
{
scan_win.preview = preview_new (dialog);
if (scan_win.preview && scan_win.preview->top)
gtk_signal_connect (GTK_OBJECT (scan_win.preview->top),
"destroy",
GTK_SIGNAL_FUNC (preview_window_destroyed),
widget);
else
gtk_toggle_button_set_state (GTK_TOGGLE_BUTTON (widget), FALSE);
}
}
else if (scan_win.preview)
{
preview_destroy (scan_win.preview);
scan_win.preview = 0;
}
}
#ifdef HAVE_LIBGIMP_GIMP_H
static void
advance (void)
{
if (++scan_win.x >= scan_win.param.pixels_per_line)
{
int tile_height = gimp_tile_height ();
scan_win.x = 0;
++scan_win.y;
if (scan_win.y % tile_height == 0)
{
gimp_pixel_rgn_set_rect (&scan_win.region, scan_win.tile,
0, scan_win.y - tile_height,
scan_win.param.pixels_per_line, tile_height);
if (scan_win.param.format >= SANE_FRAME_RED
&& scan_win.param.format <= SANE_FRAME_BLUE)
{
int height;
scan_win.tile_offset %= 3;
if (!scan_win.first_frame)
{
/* get the data for the existing tile: */
height = tile_height;
if (scan_win.y + height >= scan_win.param.lines)
height = scan_win.param.lines - scan_win.y;
gimp_pixel_rgn_get_rect (&scan_win.region, scan_win.tile,
0, scan_win.y,
scan_win.param.pixels_per_line,
height);
}
}
else
scan_win.tile_offset = 0;
}
}
}
#endif /* HAVE_LIBGIMP_GIMP_H */
static void
input_available (gpointer data, gint source, GdkInputCondition cond)
{
SANE_Handle dev = gsg_dialog_get_device (dialog);
static char buf[32768];
SANE_Status status;
SANE_Int len;
int i;
while (1)
{
status = sane_read (dev, buf, sizeof (buf), &len);
if (status != SANE_STATUS_GOOD)
{
if (status == SANE_STATUS_EOF)
{
if (!scan_win.param.last_frame)
{
scan_start ();
break;
}
}
else
{
snprintf (buf, sizeof (buf), "Error during read: %s.",
sane_strstatus (status));
gsg_error (buf);
}
scan_done ();
return;
}
if (!len)
break; /* out of data for now */
scan_win.bytes_read += len;
progress_update (scan_win.progress,
scan_win.bytes_read / (gfloat) scan_win.num_bytes);
if (scan_win.input_tag < 0)
while (gtk_events_pending ())
gtk_main_iteration ();
switch (scan_win.param.format)
{
case SANE_FRAME_GRAY:
if (scan_win.mode == STANDALONE)
fwrite (buf, 1, len, scan_win.out);
#ifdef HAVE_LIBGIMP_GIMP_H
else
{
switch (scan_win.param.depth)
{
case 1:
for (i = 0; i < len; ++i)
{
u_char mask;
int j;
mask = buf[i];
for (j = 7; j >= 0; --j)
{
u_char gl = (mask & (1 << j)) ? 0x00 : 0xff;
scan_win.tile[scan_win.tile_offset++] = gl;
advance ();
if (scan_win.x == 0)
break;
}
}
break;
case 8:
for (i = 0; i < len; ++i)
{
scan_win.tile[scan_win.tile_offset++] = buf[i];
advance ();
}
break;
default:
goto bad_depth;
}
}
#endif /* HAVE_LIBGIMP_GIMP_H */
break;
case SANE_FRAME_RGB:
if (scan_win.mode == STANDALONE)
fwrite (buf, 1, len, scan_win.out);
#ifdef HAVE_LIBGIMP_GIMP_H
else
{
switch (scan_win.param.depth)
{
case 1:
if (scan_win.param.format == SANE_FRAME_RGB)
goto bad_depth;
for (i = 0; i < len; ++i)
{
u_char mask;
int j;
mask = buf[i];
for (j = 0; j < 8; ++j)
{
u_char gl = (mask & 1) ? 0xff : 0x00;
mask >>= 1;
scan_win.tile[scan_win.tile_offset++] = gl;
advance ();
if (scan_win.x == 0)
break;
}
}
break;
case 8:
for (i = 0; i < len; ++i)
{
scan_win.tile[scan_win.tile_offset++] = buf[i];
if (scan_win.tile_offset % 3 == 0)
advance ();
}
break;
default:
goto bad_depth;
}
}
#endif /* HAVE_LIBGIMP_GIMP_H */
break;
case SANE_FRAME_RED:
case SANE_FRAME_GREEN:
case SANE_FRAME_BLUE:
if (scan_win.mode == STANDALONE)
for (i = 0; i < len; ++i)
{
fwrite (buf + i, 1, 1, scan_win.out);
fseek (scan_win.out, 2, SEEK_CUR);
}
#ifdef HAVE_LIBGIMP_GIMP_H
else
{
switch (scan_win.param.depth)
{
case 1:
for (i = 0; i < len; ++i)
{
u_char mask;
int j;
mask = buf[i];
for (j = 0; j < 8; ++j)
{
u_char gl = (mask & 1) ? 0xff : 0x00;
mask >>= 1;
scan_win.tile[scan_win.tile_offset] = gl;
scan_win.tile_offset += 3;
advance ();
if (scan_win.x == 0)
break;
}
}
break;
case 8:
for (i = 0; i < len; ++i)
{
scan_win.tile[scan_win.tile_offset] = buf[i];
scan_win.tile_offset += 3;
advance ();
}
break;
}
}
#endif /* HAVE_LIBGIMP_GIMP_H */
break;
default:
fprintf (stderr, "%s.input_available: bad frame format %d\n",
prog_name, scan_win.param.format);
scan_done ();
return;
}
}
return;
#ifdef HAVE_LIBGIMP_GIMP_H
bad_depth:
snprintf (buf, sizeof (buf), "Cannot handle depth %d.",
scan_win.param.depth);
gsg_error (buf);
scan_done ();
return;
#endif
}
static void
scan_done (void)
{
gsg_set_sensitivity (dialog, TRUE);
if (scan_win.input_tag >= 0)
{
gdk_input_remove (scan_win.input_tag);
scan_win.input_tag = -1;
}
if (!scan_win.progress)
return;
progress_free (scan_win.progress);
scan_win.progress = 0;
scan_win.header_size = 0;
if (scan_win.mode == STANDALONE)
{
fclose (scan_win.out);
scan_win.out = 0;
}
#ifdef HAVE_LIBGIMP_GIMP_H
else
{
int remaining;
/* GIMP mode */
if (scan_win.y > scan_win.param.lines)
scan_win.y = scan_win.param.lines;
remaining = scan_win.y % gimp_tile_height ();
if (remaining)
gimp_pixel_rgn_set_rect (&scan_win.region, scan_win.tile,
0, scan_win.y - remaining,
scan_win.param.pixels_per_line, remaining);
gimp_drawable_flush (scan_win.drawable);
gimp_display_new (scan_win.image_ID);
gimp_drawable_detach (scan_win.drawable);
free (scan_win.tile);
scan_win.tile = 0;
}
#endif /* HAVE_LIBGIMP_GIMP_H */
sane_cancel (gsg_dialog_get_device (dialog));
}
static void
progress_cancel (void)
{
sane_cancel (gsg_dialog_get_device (dialog));
}
static void
scan_start (void)
{
SANE_Status status;
SANE_Handle dev = gsg_dialog_get_device (dialog);
const char *frame_type = 0;
char buf[256];
int fd;
gsg_set_sensitivity (dialog, FALSE);
#ifdef HAVE_LIBGIMP_GIMP_H
if (scan_win.mode == GIMP_EXTENSION && scan_win.tile)
{
int height, remaining;
/* write the last tile of the frame to the GIMP region: */
if (scan_win.y > scan_win.param.lines) /* sanity check */
scan_win.y = scan_win.param.lines;
remaining = scan_win.y % gimp_tile_height ();
if (remaining)
gimp_pixel_rgn_set_rect (&scan_win.region, scan_win.tile,
0, scan_win.y - remaining,
scan_win.param.pixels_per_line, remaining);
/* initialize the tile with the first tile of the GIMP region: */
height = gimp_tile_height ();
if (height >= scan_win.param.lines)
height = scan_win.param.lines;
gimp_pixel_rgn_get_rect (&scan_win.region, scan_win.tile,
0, 0, scan_win.param.pixels_per_line, height);
}
#endif /* HAVE_LIBGIMP_GIMP_H */
scan_win.x = scan_win.y = 0;
status = sane_start (dev);
if (status != SANE_STATUS_GOOD)
{
gsg_set_sensitivity (dialog, TRUE);
snprintf (buf, sizeof (buf), "Failed to start scanner: %s",
sane_strstatus (status));
gsg_error (buf);
return;
}
status = sane_get_parameters (dev, &scan_win.param);
if (status != SANE_STATUS_GOOD)
{
gsg_set_sensitivity (dialog, TRUE);
snprintf (buf, sizeof (buf), "Failed to get parameters: %s",
sane_strstatus (status));
gsg_error (buf);
return;
}
scan_win.num_bytes = scan_win.param.lines * scan_win.param.bytes_per_line;
scan_win.bytes_read = 0;
switch (scan_win.param.format)
{
case SANE_FRAME_RGB: frame_type = "RGB"; break;
case SANE_FRAME_RED: frame_type = "red"; break;
case SANE_FRAME_GREEN: frame_type = "green"; break;
case SANE_FRAME_BLUE: frame_type = "blue"; break;
case SANE_FRAME_GRAY: frame_type = "gray"; break;
}
if (scan_win.mode == STANDALONE)
{ /* We are running in standalone mode */
if (!scan_win.header_size)
{
switch (scan_win.param.format)
{
case SANE_FRAME_RGB:
case SANE_FRAME_RED:
case SANE_FRAME_GREEN:
case SANE_FRAME_BLUE:
fprintf (scan_win.out, "P6\n# SANE data follows\n%d %d\n255\n",
scan_win.param.pixels_per_line, scan_win.param.lines);
break;
case SANE_FRAME_GRAY:
if (scan_win.param.depth == 1)
fprintf (scan_win.out, "P4\n# SANE data follows\n%d %d\n",
scan_win.param.pixels_per_line, scan_win.param.lines);
else
fprintf (scan_win.out, "P5\n# SANE data follows\n%d %d\n255\n",
scan_win.param.pixels_per_line, scan_win.param.lines);
break;
}
scan_win.header_size = ftell (scan_win.out);
}
if (scan_win.param.format >= SANE_FRAME_RED
&& scan_win.param.format <= SANE_FRAME_BLUE)
fseek (scan_win.out,
scan_win.header_size + scan_win.param.format - SANE_FRAME_RED,
SEEK_SET);
snprintf (buf, sizeof (buf), "Receiving %s data for `%s'...",
frame_type, preferences.filename);
}
#ifdef HAVE_LIBGIMP_GIMP_H
else
{
size_t tile_size;
/* We are running under the GIMP */
scan_win.tile_offset = 0;
tile_size = scan_win.param.pixels_per_line * gimp_tile_height ();
if (scan_win.param.format != SANE_FRAME_GRAY)
tile_size *= 3; /* 24 bits/pixel */
if (scan_win.tile)
scan_win.first_frame = 0;
else
{
GImageType image_type = RGB;
GDrawableType drawable_type = RGB_IMAGE;
gint32 layer_ID;
if (scan_win.param.format == SANE_FRAME_GRAY)
{
image_type = GRAY;
drawable_type = GRAY_IMAGE;
}
scan_win.image_ID = gimp_image_new (scan_win.param.pixels_per_line,
scan_win.param.lines, image_type);
layer_ID = gimp_layer_new (scan_win.image_ID, "Background",
scan_win.param.pixels_per_line,
scan_win.param.lines,
drawable_type, 100, NORMAL_MODE);
gimp_image_add_layer (scan_win.image_ID, layer_ID, 0);
scan_win.drawable = gimp_drawable_get (layer_ID);
gimp_pixel_rgn_init (&scan_win.region, scan_win.drawable, 0, 0,
scan_win.drawable->width,
scan_win.drawable->height, TRUE, FALSE);
scan_win.tile = g_new (guchar, tile_size);
scan_win.first_frame = 1;
}
if (scan_win.param.format >= SANE_FRAME_RED
&& scan_win.param.format <= SANE_FRAME_BLUE)
scan_win.tile_offset = scan_win.param.format - SANE_FRAME_RED;
snprintf (buf, sizeof (buf), "Receiving %s data for GIMP...",
frame_type);
}
#endif /* HAVE_LIBGIMP_GIMP_H */
if (scan_win.progress)
progress_free (scan_win.progress);
scan_win.progress = progress_new ("Scanning", buf,
(GtkSignalFunc) progress_cancel, 0);
scan_win.input_tag = -1;
if (sane_set_io_mode (dev, SANE_TRUE) == SANE_STATUS_GOOD
&& sane_get_select_fd (dev, &fd) == SANE_STATUS_GOOD)
scan_win.input_tag = gdk_input_add (fd, GDK_INPUT_READ,
input_available, 0);
else
input_available (0, -1, GDK_INPUT_READ);
}
/* Invoked when the scan button is pressed */
static void
scan_dialog (GtkWidget * widget, gpointer call_data)
{
char buf[256];
if (scan_win.mode == STANDALONE)
{ /* We are running in standalone mode */
scan_win.out = fopen (preferences.filename, "w");
if (!scan_win.out)
{
snprintf (buf, sizeof (buf), "Failed to open `%s': %s",
preferences.filename, strerror (errno));
gsg_error (buf);
return;
}
}
gsg_sync (dialog);
scan_start ();
}
#if 0
static void
zoom_in_preview (GtkWidget * widget, gpointer data)
{
if (Selection.x1 >= Selection.x2
|| Selection.y1 >= Selection.y2
|| !Selection.active)
return;
Selection.active = FALSE;
draw_selection (TRUE);
gtk_ruler_set_range (GTK_RULER (scan_win.hruler), Selection.x1,
Selection.x2, 0, 20);
gtk_ruler_set_range (GTK_RULER (scan_win.vruler), Selection.y1,
Selection.y2, 0, 20);
}
static void
zoom_out_preview (GtkWidget * widget, gpointer data)
{
gtk_ruler_set_range (GTK_RULER (scan_win.hruler), 0,
Preview.PhysWidth, 0, 20);
gtk_ruler_set_range (GTK_RULER (scan_win.vruler), 0,
Preview.PhysHeight, 0, 20);
}
#endif /* 0 */
static void
files_exit_callback (GtkWidget *widget, gpointer data)
{
quit_xscan ();
}
static GtkWidget *
files_build_menu (void)
{
GtkWidget *menu, *item;
menu = gtk_menu_new ();
item = gtk_menu_item_new ();
gtk_container_add (GTK_CONTAINER (menu), item);
gtk_widget_show (item);
item = gtk_menu_item_new_with_label ("Exit");
gtk_container_add (GTK_CONTAINER (menu), item);
gtk_signal_connect (GTK_OBJECT (item), "activate",
(GtkSignalFunc) files_exit_callback, 0);
gtk_widget_show (item);
return menu;
}
static void
pref_set_unit_callback (GtkWidget *widget, gpointer data)
{
const char *unit = data;
double unit_conversion_factor = 1.0;
if (strcmp (unit, "cm") == 0)
unit_conversion_factor = 10.0;
else if (strcmp (unit, "in") == 0)
unit_conversion_factor = 25.4;
preferences.length_unit = unit_conversion_factor;
gsg_refresh_dialog (dialog);
if (scan_win.preview)
preview_update (scan_win.preview);
pref_xscan_save ();
}
static void
update_int_callback (GtkWidget *widget, gpointer data)
{
int *valuep = data;
*valuep = (GTK_TOGGLE_BUTTON (widget)->active != 0);
}
static void
update_double_callback (GtkWidget *widget, gpointer data)
{
double *valuep = data;
char *start, *end;
double v;
start = gtk_entry_get_text (GTK_ENTRY (widget));
if (!start)
return;
v = strtod (start, &end);
if (end > start && v > 0.0)
*valuep = v;
}
static void
preview_options_ok_callback (GtkWidget *widget, gpointer data)
{
GtkWidget *dialog = data;
char buf[1024];
gtk_widget_destroy (dialog);
pref_xscan_save ();
snprintf (buf, sizeof (buf),
"It is necessary to restart %s for the changes to take effect.",
prog_name);
gsg_warning (buf);
}
static void
preview_options_cancel_callback (GtkWidget *widget, gpointer data)
{
GtkWidget *dialog = data;
gtk_widget_destroy (dialog);
}
static void
preview_options_dialog (GtkWidget *widget, gpointer data)
{
GtkWidget *dialog, *vbox, *hbox, *button, *label, *text;
char buf[64];
dialog = gtk_dialog_new ();
sprintf (buf, "%s preview options", prog_name);
gtk_window_set_title (GTK_WINDOW (dialog), buf);
vbox = GTK_DIALOG (dialog)->vbox;
/* preserve preview image: */
hbox = gtk_hbox_new (/* homogeneous */ FALSE, 0);
gtk_box_pack_start (GTK_BOX (vbox), hbox, TRUE, TRUE, 2);
button = gtk_check_button_new_with_label ("Preserve preview image");
gtk_signal_connect (GTK_OBJECT (button), "toggled",
(GtkSignalFunc) update_int_callback,
&preferences.preserve_preview);
gtk_toggle_button_set_state (GTK_TOGGLE_BUTTON (button),
preferences.preserve_preview);
gtk_box_pack_start (GTK_BOX (hbox), button, TRUE, TRUE, 2);
gtk_widget_show (button);
gtk_widget_show (hbox);
/* private colormap: */
hbox = gtk_hbox_new (/* homogeneous */ FALSE, 0);
gtk_box_pack_start (GTK_BOX (vbox), hbox, TRUE, TRUE, 2);
button = gtk_check_button_new_with_label ("Use private colormap");
gtk_signal_connect (GTK_OBJECT (button), "toggled",
(GtkSignalFunc) update_int_callback,
&preferences.preview_own_cmap);
gtk_toggle_button_set_state (GTK_TOGGLE_BUTTON (button),
preferences.preview_own_cmap);
gtk_box_pack_start (GTK_BOX (hbox), button, TRUE, TRUE, 2);
gtk_widget_show (button);
gtk_widget_show (hbox);
/* gamma correction value: */
hbox = gtk_hbox_new (/* homogeneous */ FALSE, 0);
gtk_box_pack_start (GTK_BOX (vbox), hbox, TRUE, TRUE, 2);
gtk_widget_show (hbox);
label = gtk_label_new ("Gamma correction value");
gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 2);
gtk_widget_show (label);
sprintf (buf, "%g", preferences.preview_gamma);
text = gtk_entry_new ();
gtk_entry_set_text (GTK_ENTRY (text), buf);
gtk_box_pack_start (GTK_BOX (hbox), text, TRUE, TRUE, 2);
gtk_signal_connect (GTK_OBJECT (text), "changed",
(GtkSignalFunc) update_double_callback,
&preferences.preview_gamma);
gtk_widget_show (text);
/* fill in action area: */
hbox = GTK_DIALOG (dialog)->action_area;
button = gtk_button_new_with_label ("OK");
GTK_WIDGET_SET_FLAGS (button, GTK_CAN_DEFAULT);
gtk_signal_connect (GTK_OBJECT (button), "clicked",
(GtkSignalFunc) preview_options_ok_callback, dialog);
gtk_box_pack_start (GTK_BOX (hbox), button, TRUE, TRUE, 0);
gtk_widget_grab_default (button);
gtk_widget_show (button);
button = gtk_button_new_with_label ("Cancel");
gtk_signal_connect (GTK_OBJECT (button), "clicked",
(GtkSignalFunc) preview_options_cancel_callback, dialog);
gtk_box_pack_start (GTK_BOX (hbox), button, TRUE, TRUE, 0);
gtk_widget_show (button);
gtk_widget_show (dialog);
}
static void
pref_device_save (GtkWidget *widget, gpointer data)
{
char filename[PATH_MAX];
int fd;
gsg_make_path (sizeof (filename), filename,
"xscanimage", 0, dialog->dev_name, ".rc");
fd = open (filename, O_WRONLY | O_CREAT | O_TRUNC, 0666);
if (fd < 0)
{
char buf[256];
snprintf (buf, sizeof (buf), "Failed to create file: %s.",
strerror (errno));
gsg_error (buf);
return;
}
gsg_sync (dialog);
sanei_save_values (fd, dialog->dev);
close (fd);
}
static void
pref_device_restore (void)
{
char filename[PATH_MAX];
int fd;
gsg_make_path (sizeof (filename), filename,
"xscanimage", 0, dialog->dev_name, ".rc");
fd = open (filename, O_RDONLY);
if (fd < 0)
return;
sanei_load_values (fd, dialog->dev);
close (fd);
gsg_refresh_dialog (dialog);
}
static void
pref_toggle_advanced (GtkWidget *widget, gpointer data)
{
preferences.advanced = (GTK_CHECK_MENU_ITEM (widget)->active != 0);
gsg_set_advanced (dialog, preferences.advanced);
pref_xscan_save ();
}
static void
pref_toggle_tooltips (GtkWidget *widget, gpointer data)
{
preferences.tooltips_enabled = (GTK_CHECK_MENU_ITEM (widget)->active != 0);
gsg_set_tooltips (dialog, preferences.tooltips_enabled);
pref_xscan_save ();
}
static GtkWidget *
pref_build_menu (void)
{
GtkWidget *menu, *item, *submenu, *subitem;
menu = gtk_menu_new ();
/* advanced user option: */
item = gtk_check_menu_item_new_with_label ("Show advanced options");
gtk_check_menu_item_set_state (GTK_CHECK_MENU_ITEM (item),
preferences.advanced);
gtk_menu_append (GTK_MENU (menu), item);
gtk_widget_show (item);
gtk_signal_connect (GTK_OBJECT (item), "toggled",
(GtkSignalFunc) pref_toggle_advanced, 0);
/* tooltips submenu: */
item = gtk_check_menu_item_new_with_label ("Show tooltips");
gtk_check_menu_item_set_state (GTK_CHECK_MENU_ITEM (item),
preferences.tooltips_enabled);
gtk_menu_append (GTK_MENU (menu), item);
gtk_widget_show (item);
gtk_signal_connect (GTK_OBJECT (item), "toggled",
(GtkSignalFunc) pref_toggle_tooltips, 0);
/* length unit submenu: */
item = gtk_menu_item_new_with_label ("Length unit");
gtk_menu_append (GTK_MENU (menu), item);
gtk_widget_show (item);
submenu = gtk_menu_new ();
subitem = gtk_menu_item_new_with_label ("millimeters");
gtk_menu_append (GTK_MENU (submenu), subitem);
gtk_signal_connect (GTK_OBJECT (subitem), "activate",
(GtkSignalFunc) pref_set_unit_callback, "mm");
gtk_widget_show (subitem);
subitem = gtk_menu_item_new_with_label ("centimeters");
gtk_menu_append (GTK_MENU (submenu), subitem);
gtk_signal_connect (GTK_OBJECT (subitem), "activate",
(GtkSignalFunc) pref_set_unit_callback, "cm");
gtk_widget_show (subitem);
subitem = gtk_menu_item_new_with_label ("inches");
gtk_menu_append (GTK_MENU (submenu), subitem);
gtk_signal_connect (GTK_OBJECT (subitem), "activate",
(GtkSignalFunc) pref_set_unit_callback, "in");
gtk_widget_show (subitem);
gtk_menu_item_set_submenu (GTK_MENU_ITEM (item), submenu);
/* preview options: */
item = gtk_menu_item_new_with_label ("Preview options...");
gtk_menu_append (GTK_MENU (menu), item);
gtk_signal_connect (GTK_OBJECT (item), "activate",
(GtkSignalFunc) preview_options_dialog, 0);
gtk_widget_show (item);
/* insert separator: */
item = gtk_menu_item_new ();
gtk_menu_append (GTK_MENU (menu), item);
gtk_widget_show (item);
item = gtk_menu_item_new_with_label ("Save device settings");
gtk_menu_append (GTK_MENU (menu), item);
gtk_signal_connect (GTK_OBJECT (item), "activate",
(GtkSignalFunc) pref_device_save, 0);
gtk_widget_show (item);
item = gtk_menu_item_new_with_label ("Restore device settings");
gtk_menu_append (GTK_MENU (menu), item);
gtk_signal_connect (GTK_OBJECT (item), "activate",
(GtkSignalFunc) pref_device_restore, 0);
gtk_widget_show (item);
return menu;
}
static void
browse_filename_callback (GtkWidget *widget, gpointer data)
{
char filename[1024];
if (preferences.filename)
{
strncpy (filename, preferences.filename, sizeof (filename));
filename[sizeof (filename) - 1] = '\0';
}
else
strcpy (filename, OUTFILENAME);
gsg_get_filename ("Output Filename", filename, sizeof (filename), filename);
gtk_entry_set_text (GTK_ENTRY (scan_win.filename_entry), filename);
if (preferences.filename)
free ((void *) preferences.filename);
preferences.filename = strdup (filename);
}
static void
filename_changed_callback (GtkWidget *widget, gpointer data)
{
if (preferences.filename)
free ((void *) preferences.filename);
preferences.filename = strdup (gtk_entry_get_text (GTK_ENTRY (widget)));
}
/* Create the main dialog box. */
static void
device_dialog (void)
{
GtkWidget *vbox, *hbox, *button, *frame, *dialog_window, *label, *text;
GtkWidget *menubar, *menubar_item;
const gchar *devname;
/* first, restore xscan preferences */
pref_xscan_restore ();
devname = devlist[seldev]->name;
/* create the dialog box */
scan_win.shell = gtk_dialog_new ();
gtk_window_set_title (GTK_WINDOW (scan_win.shell), (char *) devname);
gtk_window_set_policy (GTK_WINDOW (scan_win.shell), FALSE, TRUE, TRUE);
gtk_signal_connect (GTK_OBJECT (scan_win.shell), "delete_event",
GTK_SIGNAL_FUNC (scan_win_delete), NULL);
/* create the main vbox */
vbox = GTK_DIALOG (scan_win.shell)->vbox;
/* create the menubar */
menubar = gtk_menu_bar_new ();
gtk_box_pack_start (GTK_BOX (vbox), menubar, FALSE, FALSE, 0);
/* "Files" submenu: */
menubar_item = gtk_menu_item_new_with_label ("File");
gtk_container_add (GTK_CONTAINER (menubar), menubar_item);
gtk_menu_item_set_submenu (GTK_MENU_ITEM (menubar_item),
files_build_menu ());
gtk_widget_show (menubar_item);
/* "Preferences" submenu: */
menubar_item = gtk_menu_item_new_with_label ("Preferences");
gtk_container_add (GTK_CONTAINER (menubar), menubar_item);
gtk_menu_item_set_submenu (GTK_MENU_ITEM (menubar_item),
pref_build_menu ());
gtk_widget_show (menubar_item);
gtk_widget_show (menubar);
/* if we're running in standalone mode, provide a output filename box: */
if (scan_win.mode == STANDALONE)
{
frame = gtk_frame_new ("Output");
gtk_container_border_width (GTK_CONTAINER (frame), 4);
gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_ETCHED_IN);
gtk_box_pack_start (GTK_BOX (vbox), frame, FALSE, FALSE, 0);
hbox = gtk_hbox_new (FALSE, 2);
gtk_container_border_width (GTK_CONTAINER (hbox), 2);
gtk_container_add (GTK_CONTAINER (frame), hbox);
label = gtk_label_new ("Filename");
gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 2);
text = gtk_entry_new ();
gtk_entry_set_text (GTK_ENTRY (text), (char *) preferences.filename);
gtk_box_pack_start (GTK_BOX (hbox), text, TRUE, TRUE, 2);
gtk_signal_connect (GTK_OBJECT (text), "changed",
(GtkSignalFunc) filename_changed_callback, 0);
scan_win.filename_entry = text;
button = gtk_button_new_with_label ("Browse");
gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, FALSE, 2);
gtk_signal_connect (GTK_OBJECT (button), "clicked",
(GtkSignalFunc) browse_filename_callback, 0);
gtk_widget_show (button);
gtk_widget_show (label);
gtk_widget_show (text);
gtk_widget_show (hbox);
gtk_widget_show (frame);
}
/* create a subwindow so the dialog keeps its position on rebuilds: */
dialog_window = gtk_hbox_new (/* homogeneous */ FALSE, 0);
gtk_box_pack_start (GTK_BOX (vbox), dialog_window, TRUE, TRUE, 0);
gtk_widget_show (dialog_window);
dialog = gsg_create_dialog (dialog_window, devname,
update_preview, 0, update_param, 0);
if (!dialog)
return;
/* The info row */
hbox = gtk_hbox_new (FALSE, 5);
gtk_container_border_width (GTK_CONTAINER (hbox), 3);
gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0);
gtk_widget_show (hbox);
frame = gtk_frame_new (NULL);
gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_IN);
gtk_box_pack_start (GTK_BOX (hbox), frame, FALSE, FALSE, 0);
gtk_widget_show (frame);
hbox = gtk_hbox_new (FALSE, 5);
gtk_container_border_width (GTK_CONTAINER (hbox), 2);
gtk_container_add (GTK_CONTAINER (frame), hbox);
gtk_widget_show (hbox);
scan_win.info_label = gtk_label_new ("0x0: 0KB");
gtk_box_pack_start (GTK_BOX (hbox), scan_win.info_label, FALSE, FALSE, 0);
gtk_widget_show (scan_win.info_label);
update_param (dialog, 0);
/* The bottom row of buttons */
hbox = GTK_DIALOG (scan_win.shell)->action_area;
/* The Scan button */
button = gtk_button_new_with_label ("Scan");
gtk_signal_connect (GTK_OBJECT (button), "clicked",
(GtkSignalFunc) scan_dialog,
NULL);
gtk_box_pack_start (GTK_BOX (hbox), button, TRUE, TRUE, 0);
gtk_widget_show (button);
/* The Preview button */
button = gtk_toggle_button_new_with_label ("Preview Window");
gtk_signal_connect (GTK_OBJECT (button), "clicked",
(GtkSignalFunc) scan_preview, NULL);
gtk_box_pack_start (GTK_BOX (hbox), button, TRUE, TRUE, 0);
gtk_widget_show (button);
#if 0
/* The Zoom in button */
button = gtk_button_new_with_label ("Zoom");
gtk_signal_connect (GTK_OBJECT (button), "clicked",
(GtkSignalFunc) zoom_in_preview,
NULL);
gtk_box_pack_start (GTK_BOX (hbox), button, TRUE, TRUE, 0);
gtk_widget_show (button);
/* The Zoom out button */
button = gtk_button_new_with_label ("Zoom out");
gtk_signal_connect (GTK_OBJECT (button), "clicked",
(GtkSignalFunc) zoom_out_preview,
NULL);
gtk_box_pack_start (GTK_BOX (hbox), button, TRUE, TRUE, 0);
gtk_widget_show (button);
#endif
pref_device_restore (); /* restore device-settings */
gtk_widget_show (scan_win.shell);
}
static void
ok_choose_dialog_callback (void)
{
gtk_widget_destroy (choose_device_dialog);
device_dialog ();
}
static void
select_device_callback (GtkWidget * widget, GdkEventButton *event,
gpointer data)
{
seldev = (long) data;
if (event->type == GDK_2BUTTON_PRESS && event->button == 1)
ok_choose_dialog_callback ();
}
static gint32
choose_device (void)
{
GtkWidget *main_vbox, *vbox, *hbox, *button;
GSList *owner;
const SANE_Device *adev;
gint i;
choose_device_dialog = gtk_dialog_new ();
gtk_window_set_title (GTK_WINDOW (choose_device_dialog), "Select device");
main_vbox = GTK_DIALOG (choose_device_dialog)->vbox;
/* The list of drivers */
vbox = gtk_vbox_new (FALSE, 5);
gtk_container_border_width (GTK_CONTAINER (vbox), 3);
gtk_box_pack_start (GTK_BOX (main_vbox), vbox, TRUE, TRUE, 0);
gtk_widget_show (vbox);
/* The radio buttons */
owner = NULL;
for (i = 0; i < ndevs; i++)
{
adev = devlist[i];
button = gtk_radio_button_new_with_label (owner, (char *) adev->name);
gtk_signal_connect (GTK_OBJECT (button), "button_press_event",
(GtkSignalFunc) select_device_callback,
(void *) (long) i);
gtk_box_pack_start (GTK_BOX (vbox), button, TRUE, TRUE, 0);
gtk_widget_show (button);
owner = gtk_radio_button_group (GTK_RADIO_BUTTON (button));;
}
/* The bottom row of buttons */
hbox = GTK_DIALOG (choose_device_dialog)->action_area;
/* The OK button */
button = gtk_button_new_with_label ("OK");
GTK_WIDGET_SET_FLAGS (button, GTK_CAN_DEFAULT);
gtk_signal_connect (GTK_OBJECT (button), "clicked",
(GtkSignalFunc) ok_choose_dialog_callback,
NULL);
gtk_box_pack_start (GTK_BOX (hbox), button, TRUE, TRUE, 0);
gtk_widget_grab_default (button);
gtk_widget_show (button);
/* The Cancel button */
button = gtk_button_new_with_label ("Cancel");
gtk_signal_connect (GTK_OBJECT (button), "clicked",
(GtkSignalFunc) files_exit_callback, NULL);
gtk_box_pack_start (GTK_BOX (hbox), button, TRUE, TRUE, 0);
gtk_widget_show (button);
gtk_widget_show (choose_device_dialog);
return 0;
}
static void
usage (void)
{
printf ("Usage: %s [OPTION]... [DEVICE]\n\
\n\
Start up graphical user interface to access SANE (Scanner Access Now\n\
Easy) devices.\n\
\n\
-h, --help display this help message and exit\n\
-V, --version print version information\n", prog_name);
}
static void
init (int argc, char **argv)
{
char filename[PATH_MAX];
struct stat st;
gtk_init (&argc, &argv);
#ifdef HAVE_LIBGIMP_GIMP_H
gtk_rc_parse (gimp_gtkrc ());
gdk_set_use_xshm (gimp_use_xshm ());
#endif
gsg_make_path (sizeof (filename), filename, 0, "sane-style", 0, ".rc");
if (stat (filename, &st) >= 0)
gtk_rc_parse (filename);
else
{
strncpy (filename, STRINGIFY(PATH_SANE_DATA_DIR) "/sane-style.rc",
sizeof (filename));
filename[sizeof (filename) - 1] = '\0';
if (stat (filename, &st) >= 0)
gtk_rc_parse (filename);
}
sane_init (0, 0);
if (argc > 1)
{
static SANE_Device dev;
static const SANE_Device *device_list[] = { &dev, 0 };
int ch;
while ((ch = getopt_long (argc, argv, "ghV", long_options, 0)) != EOF)
{
switch (ch)
{
case 'g':
printf ("%s: GIMP support missing.\n", argv[0]);
exit (0);
case 'V':
printf ("xscanimage (%s) %s\n", PACKAGE, VERSION);
exit (0);
case 'h':
default:
usage ();
exit (0);
}
}
if (optind < argc)
{
memset (&dev, 0, sizeof (dev));
dev.name = argv[argc - 1];
dev.vendor = "Unknown";
dev.type = "unknown";
dev.model = "unknown";
devlist = device_list;
seldev = 0;
}
}
if (seldev < 0)
sane_get_devices (&devlist, SANE_FALSE);
}
static void
interface (int argc, char **argv)
{
scan_win.info_label = NULL;
init (argc, argv);
for (ndevs = 0; devlist[ndevs]; ++ndevs);
if (seldev >= 0)
{
if (seldev >= ndevs)
{
fprintf (stderr, "%s: device %d is unavailable.\n",
prog_name, seldev);
quit_xscan ();
}
device_dialog ();
if (!dialog)
quit_xscan ();
}
else
{
if (ndevs > 0)
{
seldev = 0;
if (ndevs == 1)
{
device_dialog ();
if (!dialog)
quit_xscan ();
}
else
choose_device ();
}
else
{
fprintf (stderr, "%s: no devices available.\n", prog_name);
quit_xscan ();
}
}
gtk_main ();
sane_exit ();
}
int
main (int argc, char **argv)
{
scan_win.mode = STANDALONE;
prog_name = strrchr (argv[0], '/');
if (prog_name)
++prog_name;
else
prog_name = argv[0];
#ifdef HAVE_LIBGIMP_GIMP_H
{
GPrintFunc old_print_func;
int result;
/* Temporarily install a print function that discards all output.
This is to avoid annoying "you must run this program under
gimp" messages when xscanimage gets invoked in stand-alone
mode. */
old_print_func = g_set_print_handler (null_print_func);
/* gimp_main () returns 1 if xscanimage wasn't invoked by GIMP */
result = gimp_main (argc, argv);
g_set_message_handler (old_print_func);
if (result)
interface (argc, argv);
}
#else
interface (argc, argv);
#endif
return 0;
}