diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/Makefile.in | 120 | ||||
-rw-r--r-- | src/font_6x11.h | 3337 | ||||
-rw-r--r-- | src/gtkglue.c | 1495 | ||||
-rw-r--r-- | src/gtkglue.h | 121 | ||||
-rw-r--r-- | src/preferences.c | 185 | ||||
-rw-r--r-- | src/preferences.h | 25 | ||||
-rw-r--r-- | src/preview.c | 1536 | ||||
-rw-r--r-- | src/preview.h | 87 | ||||
-rw-r--r-- | src/progress.c | 93 | ||||
-rw-r--r-- | src/progress.h | 34 | ||||
-rw-r--r-- | src/sane-style.rc | 21 | ||||
-rw-r--r-- | src/scanadf.c | 1590 | ||||
-rw-r--r-- | src/xcam.c | 1871 | ||||
-rw-r--r-- | src/xscanimage-gimp-1_0-compat.h | 47 | ||||
-rw-r--r-- | src/xscanimage.c | 2224 |
15 files changed, 12786 insertions, 0 deletions
diff --git a/src/Makefile.in b/src/Makefile.in new file mode 100644 index 0000000..af064d0 --- /dev/null +++ b/src/Makefile.in @@ -0,0 +1,120 @@ +SHELL = /bin/sh + +VPATH = @srcdir@ +srcdir = @srcdir@ +top_srcdir = @top_srcdir@ +top_builddir = .. + +PACKAGE = @PACKAGE@ +VERSION = @VERSION@ +distdir = $(top_srcdir)/$(PACKAGE)-$(VERSION) + +sane_prefix = @SANE_PREFIX@ +prefix = @prefix@ +exec_prefix = @exec_prefix@ +bindir = @bindir@ +libexecdir = @libexecdir@ +datadir = @datadir@ +sysconfdir = @sysconfdir@ +sharedstatedir = @sharedstatedir@ +localstatedir = @localstatedir@ +libdir = @libdir@ +infodir = @infodir@ +mandir = @mandir@ +includedir = @includedir@ +oldincludedir = /usr/include +configdir = ${sysconfdir}/sane.d +sanedatadir = ${datadir}/sane + +MKDIR = $(top_srcdir)/mkinstalldirs +INSTALL = @INSTALL@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_DATA = @INSTALL_DATA@ + +CC = @CC@ +INCLUDES = -I. -I$(srcdir) -I$(top_builddir)/include -I$(top_srcdir)/include \ + @INCLUDES@ @GTK_CFLAGS@ @GIMP_CFLAGS@ +DEFS = @DEFS@ +CPPFLAGS = @CPPFLAGS@ +CFLAGS = @CFLAGS@ @SANE_CFLAGS@ +LDFLAGS = @LDFLAGS@ @SANE_LDFLAGS@ +LIBS = @LIBS@ @SANE_LIBS@ +GTK_LIBS = @GTK_LIBS@ +GTK_CFLAGS = @GTK_CFLAGS@ +GIMP_LIBS = @GIMP_LIBS@ + +BINPROGS = @BINPROGS@ + +COMPILE = $(CC) -c $(DEFS) $(INCLUDES) $(CPPFLAGS) $(CFLAGS) $(GTK_CFLAGS) +LINK = $(CC) $(LDFLAGS) -o $@ + +@SET_MAKE@ + +PROGRAMS = $(BINPROGS) +LIBLIB = ../lib/liblib.a +LIBSANEI = ../sanei/libsanei.a + +XSCANIMAGE_OBJS = xscanimage.o progress.o preview.o preferences.o gtkglue.o +XCAM_OBJS = xcam.o preferences.o gtkglue.o +SCANADF_OBJS = scanadf.o + +DISTFILES = Makefile.in gtkglue.c gtkglue.h preferences.c preferences.h \ + preview.c preview.h progress.c progress.h sane-style.rc xcam.c \ + xscanimage-gimp-1_0-compat.h xscanimage.c scanadf.c font_6x11.h + +.PHONY: all clean depend dist distclean install uninstall + +.c.o: + $(COMPILE) $< + +all: $(PROGRAMS) + +install: $(PROGRAMS) + @if test -z "$(BINPROGS)" ; then \ + echo "*** The list of frontends to install is empty." ; \ + echo "*** Check the output of configure and the file config.log," ; \ + echo "*** maybe the GTK libraries weren't found?" ; \ + exit 1 ; \ + fi + $(MKDIR) $(DESTDIR)$(bindir) $(DESTDIR)$(datadir) $(DESTDIR)$(sanedatadir) + @for program in $(BINPROGS); do \ + echo installing $${program} in $(bindir)/$${program}... ; \ + $(INSTALL_PROGRAM) $${program} \ + $(DESTDIR)$(bindir)/$${program}; \ + done + $(INSTALL_DATA) $(srcdir)/sane-style.rc \ + $(DESTDIR)$(sanedatadir)/sane-style.rc + +uninstall: + @for program in $(BINPROGS); do \ + echo removing $(bindir)/$${program}...; \ + rm -f $(bindir)/$${program}; \ + done + rm -f $(sanedatadir)/sane-style.rc + +xscanimage: $(XSCANIMAGE_OBJS) $(LIBSANEI) $(LIBLIB) + $(LINK) $(XSCANIMAGE_OBJS) $(LIBSANEI) \ + $(LIBLIB) $(GIMP_LIBS) $(GTK_LIBS) $(LIBS) + +xcam: $(XCAM_OBJS) $(LIBSANEI) $(LIBLIB) + $(LINK) $(XCAM_OBJS) $(LIBSANEI) \ + $(LIBLIB) $(GTK_LIBS) $(LIBS) + +scanadf: $(SCANADF_OBJS) $(LIBLIB) + $(LINK) $(SCANADF_OBJS) $(LIBLIB) $(LIBS) + +clean: + rm -f *.o *~ .*~ *.bak + rm -rf .libs + +distclean: clean + rm -f Makefile $(PROGRAMS) + +depend: + makedepend $(INCLUDES) *.c + +dist: $(DISTFILES) + for file in $(DISTFILES); do \ + ln $$file $(distdir)/src 2> /dev/null \ + || cp -p $$file $(distdir)/src ; \ + done diff --git a/src/font_6x11.h b/src/font_6x11.h new file mode 100644 index 0000000..844ff4f --- /dev/null +++ b/src/font_6x11.h @@ -0,0 +1,3337 @@ +/**********************************************/ +/* */ +/* Font file generated by rthelen */ +/* */ +/**********************************************/ + +static unsigned char fontdata[] = { + + /* 0 0x00 '^A' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 1 0x01 '^B' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 2 0x02 '^C' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 3 0x03 '^D' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 4 0x04 '^E' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 5 0x05 '^F' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 6 0x06 '^G' */ + 0x00, /* 00000000 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 7 0x07 '^H' */ + 0x00, /* 00000000 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 8 0x08 '^I' */ + 0x00, /* 00000000 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 9 0x09 '^J' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 10 0x0a '^K' */ + 0x00, /* 00000000 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 11 0x0b '^L' */ + 0x00, /* 00000000 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 12 0x0c '^M' */ + 0x00, /* 00000000 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 13 0x0d '^N' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 14 0x0e '^O' */ + 0x00, /* 00000000 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 15 0x0f '^P' */ + 0x00, /* 00000000 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 16 0x10 '^Q' */ + 0x00, /* 00000000 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 17 0x11 '^R' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x28, /* 00 0 000 */ + 0x54, /* 0 0 0 00 */ + 0x38, /* 00 000 */ + 0x54, /* 0 0 0 00 */ + 0x28, /* 00 0 000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 18 0x12 '^S' */ + 0x04, /* 00000 00 */ + 0x04, /* 00000 00 */ + 0x08, /* 0000 000 */ + 0x08, /* 0000 000 */ + 0x50, /* 0 0 0000 */ + 0x50, /* 0 0 0000 */ + 0x20, /* 00 00000 */ + 0x20, /* 00 00000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 19 0x13 '^T' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x10, /* 000 0000 */ + 0x38, /* 00 000 */ + 0x7c, /* 0 00 */ + 0x38, /* 00 000 */ + 0x10, /* 000 0000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 20 0x14 '^U' */ + 0x18, /* 000 000 */ + 0x10, /* 000 0000 */ + 0x28, /* 00 0 000 */ + 0x7c, /* 0 00 */ + 0x78, /* 0 000 */ + 0x78, /* 0 000 */ + 0x7c, /* 0 00 */ + 0x28, /* 00 0 000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 21 0x15 '^V' */ + 0x00, /* 00000000 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 22 0x16 '^W' */ + 0x00, /* 00000000 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 23 0x17 '^X' */ + 0x00, /* 00000000 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 24 0x18 '^Y' */ + 0x00, /* 00000000 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 25 0x19 '^Z' */ + 0x00, /* 00000000 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 26 0x1a '^[' */ + 0x00, /* 00000000 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 27 0x1b '^\' */ + 0x00, /* 00000000 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 28 0x1c '^]' */ + 0x00, /* 00000000 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 29 0x1d '^^' */ + 0x00, /* 00000000 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 30 0x1e '^_' */ + 0x00, /* 00000000 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 31 0x1f '^`' */ + 0x00, /* 00000000 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 32 0x20 ' ' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 33 0x21 '!' */ + 0x00, /* 00000000 */ + 0x10, /* 000 0000 */ + 0x10, /* 000 0000 */ + 0x10, /* 000 0000 */ + 0x10, /* 000 0000 */ + 0x10, /* 000 0000 */ + 0x00, /* 00000000 */ + 0x10, /* 000 0000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 34 0x22 '"' */ + 0x28, /* 00 0 000 */ + 0x28, /* 00 0 000 */ + 0x28, /* 00 0 000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 35 0x23 '#' */ + 0x00, /* 00000000 */ + 0x28, /* 00 0 000 */ + 0x7c, /* 0 00 */ + 0x28, /* 00 0 000 */ + 0x7c, /* 0 00 */ + 0x28, /* 00 0 000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 36 0x24 '$' */ + 0x10, /* 000 0000 */ + 0x38, /* 00 000 */ + 0x54, /* 0 0 0 00 */ + 0x50, /* 0 0 0000 */ + 0x38, /* 00 000 */ + 0x14, /* 000 0 00 */ + 0x54, /* 0 0 0 00 */ + 0x38, /* 00 000 */ + 0x10, /* 000 0000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 37 0x25 '%' */ + 0x00, /* 00000000 */ + 0x3c, /* 00 00 */ + 0x54, /* 0 0 0 00 */ + 0x58, /* 0 0 000 */ + 0x28, /* 00 0 000 */ + 0x34, /* 00 0 00 */ + 0x54, /* 0 0 0 00 */ + 0x48, /* 0 00 000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 38 0x26 '&' */ + 0x00, /* 00000000 */ + 0x30, /* 00 0000 */ + 0x48, /* 0 00 000 */ + 0x50, /* 0 0 0000 */ + 0x20, /* 00 00000 */ + 0x54, /* 0 0 0 00 */ + 0x48, /* 0 00 000 */ + 0x34, /* 00 0 00 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 39 0x27 ''' */ + 0x10, /* 000 0000 */ + 0x10, /* 000 0000 */ + 0x10, /* 000 0000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 40 0x28 '(' */ + 0x04, /* 00000 00 */ + 0x08, /* 0000 000 */ + 0x10, /* 000 0000 */ + 0x10, /* 000 0000 */ + 0x10, /* 000 0000 */ + 0x10, /* 000 0000 */ + 0x10, /* 000 0000 */ + 0x08, /* 0000 000 */ + 0x04, /* 00000 00 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 41 0x29 ')' */ + 0x20, /* 00 00000 */ + 0x10, /* 000 0000 */ + 0x08, /* 0000 000 */ + 0x08, /* 0000 000 */ + 0x08, /* 0000 000 */ + 0x08, /* 0000 000 */ + 0x08, /* 0000 000 */ + 0x10, /* 000 0000 */ + 0x20, /* 00 00000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 42 0x2a '*' */ + 0x00, /* 00000000 */ + 0x10, /* 000 0000 */ + 0x54, /* 0 0 0 00 */ + 0x38, /* 00 000 */ + 0x54, /* 0 0 0 00 */ + 0x10, /* 000 0000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 43 0x2b '+' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x10, /* 000 0000 */ + 0x10, /* 000 0000 */ + 0x7c, /* 0 00 */ + 0x10, /* 000 0000 */ + 0x10, /* 000 0000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 44 0x2c ',' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x30, /* 00 0000 */ + 0x30, /* 00 0000 */ + 0x10, /* 000 0000 */ + 0x20, /* 00 00000 */ + 0x00, /* 00000000 */ + + /* 45 0x2d '-' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x7c, /* 0 00 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 46 0x2e '.' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x18, /* 000 000 */ + 0x18, /* 000 000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 47 0x2f '/' */ + 0x04, /* 00000 00 */ + 0x04, /* 00000 00 */ + 0x08, /* 0000 000 */ + 0x08, /* 0000 000 */ + 0x10, /* 000 0000 */ + 0x10, /* 000 0000 */ + 0x20, /* 00 00000 */ + 0x20, /* 00 00000 */ + 0x40, /* 0 000000 */ + 0x40, /* 0 000000 */ + 0x00, /* 00000000 */ + + /* 48 0x30 '0' */ + 0x00, /* 00000000 */ + 0x38, /* 00 000 */ + 0x44, /* 0 000 00 */ + 0x4c, /* 0 00 00 */ + 0x54, /* 0 0 0 00 */ + 0x64, /* 0 00 00 */ + 0x44, /* 0 000 00 */ + 0x38, /* 00 000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 49 0x31 '1' */ + 0x00, /* 00000000 */ + 0x08, /* 0000 000 */ + 0x18, /* 000 000 */ + 0x08, /* 0000 000 */ + 0x08, /* 0000 000 */ + 0x08, /* 0000 000 */ + 0x08, /* 0000 000 */ + 0x1c, /* 000 00 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 50 0x32 '2' */ + 0x00, /* 00000000 */ + 0x38, /* 00 000 */ + 0x44, /* 0 000 00 */ + 0x04, /* 00000 00 */ + 0x08, /* 0000 000 */ + 0x10, /* 000 0000 */ + 0x20, /* 00 00000 */ + 0x7c, /* 0 00 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 51 0x33 '3' */ + 0x00, /* 00000000 */ + 0x38, /* 00 000 */ + 0x44, /* 0 000 00 */ + 0x04, /* 00000 00 */ + 0x18, /* 000 000 */ + 0x04, /* 00000 00 */ + 0x44, /* 0 000 00 */ + 0x38, /* 00 000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 52 0x34 '4' */ + 0x00, /* 00000000 */ + 0x08, /* 0000 000 */ + 0x18, /* 000 000 */ + 0x28, /* 00 0 000 */ + 0x48, /* 0 00 000 */ + 0x7c, /* 0 00 */ + 0x08, /* 0000 000 */ + 0x1c, /* 000 00 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 53 0x35 '5' */ + 0x00, /* 00000000 */ + 0x7c, /* 0 00 */ + 0x40, /* 0 000000 */ + 0x78, /* 0 000 */ + 0x04, /* 00000 00 */ + 0x04, /* 00000 00 */ + 0x44, /* 0 000 00 */ + 0x38, /* 00 000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 54 0x36 '6' */ + 0x00, /* 00000000 */ + 0x38, /* 00 000 */ + 0x40, /* 0 000000 */ + 0x78, /* 0 000 */ + 0x44, /* 0 000 00 */ + 0x44, /* 0 000 00 */ + 0x44, /* 0 000 00 */ + 0x38, /* 00 000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 55 0x37 '7' */ + 0x00, /* 00000000 */ + 0x7c, /* 0 00 */ + 0x04, /* 00000 00 */ + 0x04, /* 00000 00 */ + 0x08, /* 0000 000 */ + 0x10, /* 000 0000 */ + 0x10, /* 000 0000 */ + 0x10, /* 000 0000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 56 0x38 '8' */ + 0x00, /* 00000000 */ + 0x38, /* 00 000 */ + 0x44, /* 0 000 00 */ + 0x44, /* 0 000 00 */ + 0x38, /* 00 000 */ + 0x44, /* 0 000 00 */ + 0x44, /* 0 000 00 */ + 0x38, /* 00 000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 57 0x39 '9' */ + 0x00, /* 00000000 */ + 0x38, /* 00 000 */ + 0x44, /* 0 000 00 */ + 0x44, /* 0 000 00 */ + 0x44, /* 0 000 00 */ + 0x3c, /* 00 00 */ + 0x04, /* 00000 00 */ + 0x38, /* 00 000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 58 0x3a ':' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x18, /* 000 000 */ + 0x18, /* 000 000 */ + 0x00, /* 00000000 */ + 0x18, /* 000 000 */ + 0x18, /* 000 000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 59 0x3b ';' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x30, /* 00 0000 */ + 0x30, /* 00 0000 */ + 0x00, /* 00000000 */ + 0x30, /* 00 0000 */ + 0x30, /* 00 0000 */ + 0x10, /* 000 0000 */ + 0x20, /* 00 00000 */ + 0x00, /* 00000000 */ + + /* 60 0x3c '<' */ + 0x00, /* 00000000 */ + 0x04, /* 00000 00 */ + 0x08, /* 0000 000 */ + 0x10, /* 000 0000 */ + 0x20, /* 00 00000 */ + 0x10, /* 000 0000 */ + 0x08, /* 0000 000 */ + 0x04, /* 00000 00 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 61 0x3d '=' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x7c, /* 0 00 */ + 0x00, /* 00000000 */ + 0x7c, /* 0 00 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 62 0x3e '>' */ + 0x00, /* 00000000 */ + 0x20, /* 00 00000 */ + 0x10, /* 000 0000 */ + 0x08, /* 0000 000 */ + 0x04, /* 00000 00 */ + 0x08, /* 0000 000 */ + 0x10, /* 000 0000 */ + 0x20, /* 00 00000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 63 0x3f '?' */ + 0x00, /* 00000000 */ + 0x38, /* 00 000 */ + 0x44, /* 0 000 00 */ + 0x04, /* 00000 00 */ + 0x08, /* 0000 000 */ + 0x10, /* 000 0000 */ + 0x00, /* 00000000 */ + 0x10, /* 000 0000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 64 0x40 '@' */ + 0x00, /* 00000000 */ + 0x38, /* 00 000 */ + 0x44, /* 0 000 00 */ + 0x74, /* 0 0 00 */ + 0x54, /* 0 0 0 00 */ + 0x78, /* 0 000 */ + 0x40, /* 0 000000 */ + 0x38, /* 00 000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 65 0x41 'A' */ + 0x00, /* 00000000 */ + 0x38, /* 00 000 */ + 0x44, /* 0 000 00 */ + 0x44, /* 0 000 00 */ + 0x7c, /* 0 00 */ + 0x44, /* 0 000 00 */ + 0x44, /* 0 000 00 */ + 0x44, /* 0 000 00 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 66 0x42 'B' */ + 0x00, /* 00000000 */ + 0x78, /* 0 000 */ + 0x44, /* 0 000 00 */ + 0x44, /* 0 000 00 */ + 0x78, /* 0 000 */ + 0x44, /* 0 000 00 */ + 0x44, /* 0 000 00 */ + 0x78, /* 0 000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 67 0x43 'C' */ + 0x00, /* 00000000 */ + 0x38, /* 00 000 */ + 0x44, /* 0 000 00 */ + 0x40, /* 0 000000 */ + 0x40, /* 0 000000 */ + 0x40, /* 0 000000 */ + 0x44, /* 0 000 00 */ + 0x38, /* 00 000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 68 0x44 'D' */ + 0x00, /* 00000000 */ + 0x78, /* 0 000 */ + 0x44, /* 0 000 00 */ + 0x44, /* 0 000 00 */ + 0x44, /* 0 000 00 */ + 0x44, /* 0 000 00 */ + 0x44, /* 0 000 00 */ + 0x78, /* 0 000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 69 0x45 'E' */ + 0x00, /* 00000000 */ + 0x7c, /* 0 00 */ + 0x40, /* 0 000000 */ + 0x40, /* 0 000000 */ + 0x78, /* 0 000 */ + 0x40, /* 0 000000 */ + 0x40, /* 0 000000 */ + 0x7c, /* 0 00 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 70 0x46 'F' */ + 0x00, /* 00000000 */ + 0x7c, /* 0 00 */ + 0x40, /* 0 000000 */ + 0x40, /* 0 000000 */ + 0x78, /* 0 000 */ + 0x40, /* 0 000000 */ + 0x40, /* 0 000000 */ + 0x40, /* 0 000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 71 0x47 'G' */ + 0x00, /* 00000000 */ + 0x38, /* 00 000 */ + 0x44, /* 0 000 00 */ + 0x40, /* 0 000000 */ + 0x4c, /* 0 00 00 */ + 0x44, /* 0 000 00 */ + 0x44, /* 0 000 00 */ + 0x38, /* 00 000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 72 0x48 'H' */ + 0x00, /* 00000000 */ + 0x44, /* 0 000 00 */ + 0x44, /* 0 000 00 */ + 0x44, /* 0 000 00 */ + 0x7c, /* 0 00 */ + 0x44, /* 0 000 00 */ + 0x44, /* 0 000 00 */ + 0x44, /* 0 000 00 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 73 0x49 'I' */ + 0x00, /* 00000000 */ + 0x38, /* 00 000 */ + 0x10, /* 000 0000 */ + 0x10, /* 000 0000 */ + 0x10, /* 000 0000 */ + 0x10, /* 000 0000 */ + 0x10, /* 000 0000 */ + 0x38, /* 00 000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 74 0x4a 'J' */ + 0x00, /* 00000000 */ + 0x04, /* 00000 00 */ + 0x04, /* 00000 00 */ + 0x04, /* 00000 00 */ + 0x04, /* 00000 00 */ + 0x44, /* 0 000 00 */ + 0x44, /* 0 000 00 */ + 0x38, /* 00 000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 75 0x4b 'K' */ + 0x00, /* 00000000 */ + 0x44, /* 0 000 00 */ + 0x48, /* 0 00 000 */ + 0x50, /* 0 0 0000 */ + 0x60, /* 0 00000 */ + 0x50, /* 0 0 0000 */ + 0x48, /* 0 00 000 */ + 0x44, /* 0 000 00 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 76 0x4c 'L' */ + 0x00, /* 00000000 */ + 0x40, /* 0 000000 */ + 0x40, /* 0 000000 */ + 0x40, /* 0 000000 */ + 0x40, /* 0 000000 */ + 0x40, /* 0 000000 */ + 0x40, /* 0 000000 */ + 0x7c, /* 0 00 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 77 0x4d 'M' */ + 0x00, /* 00000000 */ + 0x44, /* 0 000 00 */ + 0x6c, /* 0 0 00 */ + 0x54, /* 0 0 0 00 */ + 0x54, /* 0 0 0 00 */ + 0x44, /* 0 000 00 */ + 0x44, /* 0 000 00 */ + 0x44, /* 0 000 00 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 78 0x4e 'N' */ + 0x00, /* 00000000 */ + 0x44, /* 0 000 00 */ + 0x64, /* 0 00 00 */ + 0x54, /* 0 0 0 00 */ + 0x4c, /* 0 00 00 */ + 0x44, /* 0 000 00 */ + 0x44, /* 0 000 00 */ + 0x44, /* 0 000 00 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 79 0x4f 'O' */ + 0x00, /* 00000000 */ + 0x38, /* 00 000 */ + 0x44, /* 0 000 00 */ + 0x44, /* 0 000 00 */ + 0x44, /* 0 000 00 */ + 0x44, /* 0 000 00 */ + 0x44, /* 0 000 00 */ + 0x38, /* 00 000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 80 0x50 'P' */ + 0x00, /* 00000000 */ + 0x78, /* 0 000 */ + 0x44, /* 0 000 00 */ + 0x44, /* 0 000 00 */ + 0x78, /* 0 000 */ + 0x40, /* 0 000000 */ + 0x40, /* 0 000000 */ + 0x40, /* 0 000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 81 0x51 'Q' */ + 0x00, /* 00000000 */ + 0x38, /* 00 000 */ + 0x44, /* 0 000 00 */ + 0x44, /* 0 000 00 */ + 0x44, /* 0 000 00 */ + 0x44, /* 0 000 00 */ + 0x54, /* 0 0 0 00 */ + 0x38, /* 00 000 */ + 0x04, /* 00000 00 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 82 0x52 'R' */ + 0x00, /* 00000000 */ + 0x78, /* 0 000 */ + 0x44, /* 0 000 00 */ + 0x44, /* 0 000 00 */ + 0x78, /* 0 000 */ + 0x44, /* 0 000 00 */ + 0x44, /* 0 000 00 */ + 0x44, /* 0 000 00 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 83 0x53 'S' */ + 0x00, /* 00000000 */ + 0x38, /* 00 000 */ + 0x44, /* 0 000 00 */ + 0x40, /* 0 000000 */ + 0x38, /* 00 000 */ + 0x04, /* 00000 00 */ + 0x44, /* 0 000 00 */ + 0x38, /* 00 000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 84 0x54 'T' */ + 0x00, /* 00000000 */ + 0x7c, /* 0 00 */ + 0x10, /* 000 0000 */ + 0x10, /* 000 0000 */ + 0x10, /* 000 0000 */ + 0x10, /* 000 0000 */ + 0x10, /* 000 0000 */ + 0x10, /* 000 0000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 85 0x55 'U' */ + 0x00, /* 00000000 */ + 0x44, /* 0 000 00 */ + 0x44, /* 0 000 00 */ + 0x44, /* 0 000 00 */ + 0x44, /* 0 000 00 */ + 0x44, /* 0 000 00 */ + 0x44, /* 0 000 00 */ + 0x38, /* 00 000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 86 0x56 'V' */ + 0x00, /* 00000000 */ + 0x44, /* 0 000 00 */ + 0x44, /* 0 000 00 */ + 0x44, /* 0 000 00 */ + 0x44, /* 0 000 00 */ + 0x44, /* 0 000 00 */ + 0x28, /* 00 0 000 */ + 0x10, /* 000 0000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 87 0x57 'W' */ + 0x00, /* 00000000 */ + 0x44, /* 0 000 00 */ + 0x44, /* 0 000 00 */ + 0x44, /* 0 000 00 */ + 0x54, /* 0 0 0 00 */ + 0x54, /* 0 0 0 00 */ + 0x6c, /* 0 0 00 */ + 0x44, /* 0 000 00 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 88 0x58 'X' */ + 0x00, /* 00000000 */ + 0x44, /* 0 000 00 */ + 0x44, /* 0 000 00 */ + 0x28, /* 00 0 000 */ + 0x10, /* 000 0000 */ + 0x28, /* 00 0 000 */ + 0x44, /* 0 000 00 */ + 0x44, /* 0 000 00 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 89 0x59 'Y' */ + 0x00, /* 00000000 */ + 0x44, /* 0 000 00 */ + 0x44, /* 0 000 00 */ + 0x44, /* 0 000 00 */ + 0x28, /* 00 0 000 */ + 0x10, /* 000 0000 */ + 0x10, /* 000 0000 */ + 0x10, /* 000 0000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 90 0x5a 'Z' */ + 0x00, /* 00000000 */ + 0x7c, /* 0 00 */ + 0x04, /* 00000 00 */ + 0x08, /* 0000 000 */ + 0x10, /* 000 0000 */ + 0x20, /* 00 00000 */ + 0x40, /* 0 000000 */ + 0x7c, /* 0 00 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 91 0x5b '[' */ + 0x0c, /* 0000 00 */ + 0x08, /* 0000 000 */ + 0x08, /* 0000 000 */ + 0x08, /* 0000 000 */ + 0x08, /* 0000 000 */ + 0x08, /* 0000 000 */ + 0x08, /* 0000 000 */ + 0x08, /* 0000 000 */ + 0x0c, /* 0000 00 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 92 0x5c '\' */ + 0x20, /* 00 00000 */ + 0x20, /* 00 00000 */ + 0x10, /* 000 0000 */ + 0x10, /* 000 0000 */ + 0x08, /* 0000 000 */ + 0x08, /* 0000 000 */ + 0x04, /* 00000 00 */ + 0x04, /* 00000 00 */ + 0x02, /* 000000 0 */ + 0x02, /* 000000 0 */ + 0x00, /* 00000000 */ + + /* 93 0x5d ']' */ + 0x30, /* 00 0000 */ + 0x10, /* 000 0000 */ + 0x10, /* 000 0000 */ + 0x10, /* 000 0000 */ + 0x10, /* 000 0000 */ + 0x10, /* 000 0000 */ + 0x10, /* 000 0000 */ + 0x10, /* 000 0000 */ + 0x30, /* 00 0000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 94 0x5e '^' */ + 0x00, /* 00000000 */ + 0x10, /* 000 0000 */ + 0x28, /* 00 0 000 */ + 0x44, /* 0 000 00 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 95 0x5f '_' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x7e, /* 0 0 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 96 0x60 '`' */ + 0x20, /* 00 00000 */ + 0x10, /* 000 0000 */ + 0x08, /* 0000 000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 97 0x61 'a' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x3c, /* 00 00 */ + 0x44, /* 0 000 00 */ + 0x44, /* 0 000 00 */ + 0x4c, /* 0 00 00 */ + 0x34, /* 00 0 00 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 98 0x62 'b' */ + 0x00, /* 00000000 */ + 0x40, /* 0 000000 */ + 0x40, /* 0 000000 */ + 0x78, /* 0 000 */ + 0x44, /* 0 000 00 */ + 0x44, /* 0 000 00 */ + 0x44, /* 0 000 00 */ + 0x78, /* 0 000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 99 0x63 'c' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x38, /* 00 000 */ + 0x44, /* 0 000 00 */ + 0x40, /* 0 000000 */ + 0x44, /* 0 000 00 */ + 0x38, /* 00 000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 100 0x64 'd' */ + 0x00, /* 00000000 */ + 0x04, /* 00000 00 */ + 0x04, /* 00000 00 */ + 0x3c, /* 00 00 */ + 0x44, /* 0 000 00 */ + 0x44, /* 0 000 00 */ + 0x44, /* 0 000 00 */ + 0x3c, /* 00 00 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 101 0x65 'e' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x38, /* 00 000 */ + 0x44, /* 0 000 00 */ + 0x7c, /* 0 00 */ + 0x40, /* 0 000000 */ + 0x3c, /* 00 00 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 102 0x66 'f' */ + 0x00, /* 00000000 */ + 0x0c, /* 0000 00 */ + 0x10, /* 000 0000 */ + 0x38, /* 00 000 */ + 0x10, /* 000 0000 */ + 0x10, /* 000 0000 */ + 0x10, /* 000 0000 */ + 0x10, /* 000 0000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 103 0x67 'g' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x3c, /* 00 00 */ + 0x44, /* 0 000 00 */ + 0x44, /* 0 000 00 */ + 0x44, /* 0 000 00 */ + 0x3c, /* 00 00 */ + 0x04, /* 00000 00 */ + 0x38, /* 00 000 */ + 0x00, /* 00000000 */ + + /* 104 0x68 'h' */ + 0x00, /* 00000000 */ + 0x40, /* 0 000000 */ + 0x40, /* 0 000000 */ + 0x78, /* 0 000 */ + 0x44, /* 0 000 00 */ + 0x44, /* 0 000 00 */ + 0x44, /* 0 000 00 */ + 0x44, /* 0 000 00 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 105 0x69 'i' */ + 0x00, /* 00000000 */ + 0x10, /* 000 0000 */ + 0x00, /* 00000000 */ + 0x30, /* 00 0000 */ + 0x10, /* 000 0000 */ + 0x10, /* 000 0000 */ + 0x10, /* 000 0000 */ + 0x38, /* 00 000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 106 0x6a 'j' */ + 0x00, /* 00000000 */ + 0x10, /* 000 0000 */ + 0x00, /* 00000000 */ + 0x30, /* 00 0000 */ + 0x10, /* 000 0000 */ + 0x10, /* 000 0000 */ + 0x10, /* 000 0000 */ + 0x10, /* 000 0000 */ + 0x10, /* 000 0000 */ + 0x60, /* 0 00000 */ + 0x00, /* 00000000 */ + + /* 107 0x6b 'k' */ + 0x00, /* 00000000 */ + 0x40, /* 0 000000 */ + 0x40, /* 0 000000 */ + 0x48, /* 0 00 000 */ + 0x50, /* 0 0 0000 */ + 0x70, /* 0 0000 */ + 0x48, /* 0 00 000 */ + 0x44, /* 0 000 00 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 108 0x6c 'l' */ + 0x00, /* 00000000 */ + 0x30, /* 00 0000 */ + 0x10, /* 000 0000 */ + 0x10, /* 000 0000 */ + 0x10, /* 000 0000 */ + 0x10, /* 000 0000 */ + 0x10, /* 000 0000 */ + 0x38, /* 00 000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 109 0x6d 'm' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x78, /* 0 000 */ + 0x54, /* 0 0 0 00 */ + 0x54, /* 0 0 0 00 */ + 0x54, /* 0 0 0 00 */ + 0x54, /* 0 0 0 00 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 110 0x6e 'n' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x58, /* 0 0 000 */ + 0x64, /* 0 00 00 */ + 0x44, /* 0 000 00 */ + 0x44, /* 0 000 00 */ + 0x44, /* 0 000 00 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 111 0x6f 'o' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x38, /* 00 000 */ + 0x44, /* 0 000 00 */ + 0x44, /* 0 000 00 */ + 0x44, /* 0 000 00 */ + 0x38, /* 00 000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 112 0x70 'p' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x78, /* 0 000 */ + 0x44, /* 0 000 00 */ + 0x44, /* 0 000 00 */ + 0x44, /* 0 000 00 */ + 0x78, /* 0 000 */ + 0x40, /* 0 000000 */ + 0x40, /* 0 000000 */ + 0x00, /* 00000000 */ + + /* 113 0x71 'q' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x3c, /* 00 00 */ + 0x44, /* 0 000 00 */ + 0x44, /* 0 000 00 */ + 0x44, /* 0 000 00 */ + 0x3c, /* 00 00 */ + 0x04, /* 00000 00 */ + 0x04, /* 00000 00 */ + 0x00, /* 00000000 */ + + /* 114 0x72 'r' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x58, /* 0 0 000 */ + 0x64, /* 0 00 00 */ + 0x40, /* 0 000000 */ + 0x40, /* 0 000000 */ + 0x40, /* 0 000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 115 0x73 's' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x3c, /* 00 00 */ + 0x40, /* 0 000000 */ + 0x38, /* 00 000 */ + 0x04, /* 00000 00 */ + 0x78, /* 0 000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 116 0x74 't' */ + 0x00, /* 00000000 */ + 0x10, /* 000 0000 */ + 0x10, /* 000 0000 */ + 0x38, /* 00 000 */ + 0x10, /* 000 0000 */ + 0x10, /* 000 0000 */ + 0x10, /* 000 0000 */ + 0x0c, /* 0000 00 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 117 0x75 'u' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x44, /* 0 000 00 */ + 0x44, /* 0 000 00 */ + 0x44, /* 0 000 00 */ + 0x4c, /* 0 00 00 */ + 0x34, /* 00 0 00 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 118 0x76 'v' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x44, /* 0 000 00 */ + 0x44, /* 0 000 00 */ + 0x44, /* 0 000 00 */ + 0x28, /* 00 0 000 */ + 0x10, /* 000 0000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 119 0x77 'w' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x54, /* 0 0 0 00 */ + 0x54, /* 0 0 0 00 */ + 0x54, /* 0 0 0 00 */ + 0x54, /* 0 0 0 00 */ + 0x28, /* 00 0 000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 120 0x78 'x' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x44, /* 0 000 00 */ + 0x28, /* 00 0 000 */ + 0x10, /* 000 0000 */ + 0x28, /* 00 0 000 */ + 0x44, /* 0 000 00 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 121 0x79 'y' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x44, /* 0 000 00 */ + 0x44, /* 0 000 00 */ + 0x44, /* 0 000 00 */ + 0x44, /* 0 000 00 */ + 0x3c, /* 00 00 */ + 0x04, /* 00000 00 */ + 0x38, /* 00 000 */ + 0x00, /* 00000000 */ + + /* 122 0x7a 'z' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x7c, /* 0 00 */ + 0x08, /* 0000 000 */ + 0x10, /* 000 0000 */ + 0x20, /* 00 00000 */ + 0x7c, /* 0 00 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 123 0x7b '{' */ + 0x08, /* 0000 000 */ + 0x08, /* 0000 000 */ + 0x08, /* 0000 000 */ + 0x08, /* 0000 000 */ + 0x10, /* 000 0000 */ + 0x08, /* 0000 000 */ + 0x08, /* 0000 000 */ + 0x08, /* 0000 000 */ + 0x08, /* 0000 000 */ + 0x04, /* 00000 00 */ + 0x00, /* 00000000 */ + + /* 124 0x7c '|' */ + 0x10, /* 000 0000 */ + 0x10, /* 000 0000 */ + 0x10, /* 000 0000 */ + 0x10, /* 000 0000 */ + 0x10, /* 000 0000 */ + 0x10, /* 000 0000 */ + 0x10, /* 000 0000 */ + 0x10, /* 000 0000 */ + 0x10, /* 000 0000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 125 0x7d '}' */ + 0x10, /* 000 0000 */ + 0x10, /* 000 0000 */ + 0x10, /* 000 0000 */ + 0x10, /* 000 0000 */ + 0x08, /* 0000 000 */ + 0x10, /* 000 0000 */ + 0x10, /* 000 0000 */ + 0x10, /* 000 0000 */ + 0x10, /* 000 0000 */ + 0x20, /* 00 00000 */ + 0x00, /* 00000000 */ + + /* 126 0x7e '~' */ + 0x00, /* 00000000 */ + 0x34, /* 00 0 00 */ + 0x58, /* 0 0 000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 127 0x7f '^?' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 128 0x80 '\200' */ + 0x00, /* 00000000 */ + 0x38, /* 00 000 */ + 0x44, /* 0 000 00 */ + 0x44, /* 0 000 00 */ + 0x7c, /* 0 00 */ + 0x44, /* 0 000 00 */ + 0x44, /* 0 000 00 */ + 0x44, /* 0 000 00 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 129 0x81 '\201' */ + 0x28, /* 00 0 000 */ + 0x38, /* 00 000 */ + 0x44, /* 0 000 00 */ + 0x44, /* 0 000 00 */ + 0x7c, /* 0 00 */ + 0x44, /* 0 000 00 */ + 0x44, /* 0 000 00 */ + 0x44, /* 0 000 00 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 130 0x82 '\202' */ + 0x00, /* 00000000 */ + 0x38, /* 00 000 */ + 0x44, /* 0 000 00 */ + 0x40, /* 0 000000 */ + 0x40, /* 0 000000 */ + 0x40, /* 0 000000 */ + 0x44, /* 0 000 00 */ + 0x38, /* 00 000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 131 0x83 '\203' */ + 0x10, /* 000 0000 */ + 0x7c, /* 0 00 */ + 0x40, /* 0 000000 */ + 0x40, /* 0 000000 */ + 0x78, /* 0 000 */ + 0x40, /* 0 000000 */ + 0x40, /* 0 000000 */ + 0x7c, /* 0 00 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 132 0x84 '\204' */ + 0x58, /* 0 0 000 */ + 0x44, /* 0 000 00 */ + 0x64, /* 0 00 00 */ + 0x54, /* 0 0 0 00 */ + 0x4c, /* 0 00 00 */ + 0x44, /* 0 000 00 */ + 0x44, /* 0 000 00 */ + 0x44, /* 0 000 00 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 133 0x85 '\205' */ + 0x00, /* 00000000 */ + 0x38, /* 00 000 */ + 0x44, /* 0 000 00 */ + 0x44, /* 0 000 00 */ + 0x44, /* 0 000 00 */ + 0x44, /* 0 000 00 */ + 0x44, /* 0 000 00 */ + 0x38, /* 00 000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 134 0x86 '\206' */ + 0x00, /* 00000000 */ + 0x44, /* 0 000 00 */ + 0x44, /* 0 000 00 */ + 0x44, /* 0 000 00 */ + 0x44, /* 0 000 00 */ + 0x44, /* 0 000 00 */ + 0x44, /* 0 000 00 */ + 0x38, /* 00 000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 135 0x87 '\207' */ + 0x08, /* 0000 000 */ + 0x10, /* 000 0000 */ + 0x00, /* 00000000 */ + 0x3c, /* 00 00 */ + 0x44, /* 0 000 00 */ + 0x44, /* 0 000 00 */ + 0x4c, /* 0 00 00 */ + 0x34, /* 00 0 00 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 136 0x88 '\210' */ + 0x10, /* 000 0000 */ + 0x08, /* 0000 000 */ + 0x00, /* 00000000 */ + 0x3c, /* 00 00 */ + 0x44, /* 0 000 00 */ + 0x44, /* 0 000 00 */ + 0x4c, /* 0 00 00 */ + 0x34, /* 00 0 00 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 137 0x89 '\211' */ + 0x10, /* 000 0000 */ + 0x28, /* 00 0 000 */ + 0x00, /* 00000000 */ + 0x3c, /* 00 00 */ + 0x44, /* 0 000 00 */ + 0x44, /* 0 000 00 */ + 0x4c, /* 0 00 00 */ + 0x34, /* 00 0 00 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 138 0x8a '\212' */ + 0x00, /* 00000000 */ + 0x28, /* 00 0 000 */ + 0x00, /* 00000000 */ + 0x3c, /* 00 00 */ + 0x44, /* 0 000 00 */ + 0x44, /* 0 000 00 */ + 0x4c, /* 0 00 00 */ + 0x34, /* 00 0 00 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 139 0x8b '\213' */ + 0x34, /* 00 0 00 */ + 0x58, /* 0 0 000 */ + 0x00, /* 00000000 */ + 0x3c, /* 00 00 */ + 0x44, /* 0 000 00 */ + 0x44, /* 0 000 00 */ + 0x4c, /* 0 00 00 */ + 0x34, /* 00 0 00 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 140 0x8c '\214' */ + 0x18, /* 000 000 */ + 0x24, /* 00 00 00 */ + 0x18, /* 000 000 */ + 0x3c, /* 00 00 */ + 0x44, /* 0 000 00 */ + 0x44, /* 0 000 00 */ + 0x4c, /* 0 00 00 */ + 0x34, /* 00 0 00 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 141 0x8d '\215' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x38, /* 00 000 */ + 0x44, /* 0 000 00 */ + 0x40, /* 0 000000 */ + 0x40, /* 0 000000 */ + 0x3c, /* 00 00 */ + 0x10, /* 000 0000 */ + 0x20, /* 00 00000 */ + 0x00, /* 00000000 */ + + /* 142 0x8e '\216' */ + 0x08, /* 0000 000 */ + 0x10, /* 000 0000 */ + 0x00, /* 00000000 */ + 0x38, /* 00 000 */ + 0x44, /* 0 000 00 */ + 0x7c, /* 0 00 */ + 0x40, /* 0 000000 */ + 0x3c, /* 00 00 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 143 0x8f '\217' */ + 0x20, /* 00 00000 */ + 0x10, /* 000 0000 */ + 0x00, /* 00000000 */ + 0x38, /* 00 000 */ + 0x44, /* 0 000 00 */ + 0x7c, /* 0 00 */ + 0x40, /* 0 000000 */ + 0x3c, /* 00 00 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 144 0x90 '\220' */ + 0x10, /* 000 0000 */ + 0x28, /* 00 0 000 */ + 0x00, /* 00000000 */ + 0x38, /* 00 000 */ + 0x44, /* 0 000 00 */ + 0x7c, /* 0 00 */ + 0x40, /* 0 000000 */ + 0x3c, /* 00 00 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 145 0x91 '\221' */ + 0x00, /* 00000000 */ + 0x28, /* 00 0 000 */ + 0x00, /* 00000000 */ + 0x38, /* 00 000 */ + 0x44, /* 0 000 00 */ + 0x7c, /* 0 00 */ + 0x40, /* 0 000000 */ + 0x3c, /* 00 00 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 146 0x92 '\222' */ + 0x08, /* 0000 000 */ + 0x10, /* 000 0000 */ + 0x00, /* 00000000 */ + 0x10, /* 000 0000 */ + 0x10, /* 000 0000 */ + 0x10, /* 000 0000 */ + 0x10, /* 000 0000 */ + 0x10, /* 000 0000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 147 0x93 '\223' */ + 0x20, /* 00 00000 */ + 0x10, /* 000 0000 */ + 0x00, /* 00000000 */ + 0x10, /* 000 0000 */ + 0x10, /* 000 0000 */ + 0x10, /* 000 0000 */ + 0x10, /* 000 0000 */ + 0x10, /* 000 0000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 148 0x94 '\224' */ + 0x10, /* 000 0000 */ + 0x28, /* 00 0 000 */ + 0x00, /* 00000000 */ + 0x10, /* 000 0000 */ + 0x10, /* 000 0000 */ + 0x10, /* 000 0000 */ + 0x10, /* 000 0000 */ + 0x10, /* 000 0000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 149 0x95 '\225' */ + 0x00, /* 00000000 */ + 0x28, /* 00 0 000 */ + 0x00, /* 00000000 */ + 0x10, /* 000 0000 */ + 0x10, /* 000 0000 */ + 0x10, /* 000 0000 */ + 0x10, /* 000 0000 */ + 0x10, /* 000 0000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 150 0x96 '\226' */ + 0x34, /* 00 0 00 */ + 0x58, /* 0 0 000 */ + 0x00, /* 00000000 */ + 0x58, /* 0 0 000 */ + 0x64, /* 0 00 00 */ + 0x44, /* 0 000 00 */ + 0x44, /* 0 000 00 */ + 0x44, /* 0 000 00 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 151 0x97 '\227' */ + 0x08, /* 0000 000 */ + 0x10, /* 000 0000 */ + 0x00, /* 00000000 */ + 0x38, /* 00 000 */ + 0x44, /* 0 000 00 */ + 0x44, /* 0 000 00 */ + 0x44, /* 0 000 00 */ + 0x38, /* 00 000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 152 0x98 '\230' */ + 0x20, /* 00 00000 */ + 0x10, /* 000 0000 */ + 0x00, /* 00000000 */ + 0x38, /* 00 000 */ + 0x44, /* 0 000 00 */ + 0x44, /* 0 000 00 */ + 0x44, /* 0 000 00 */ + 0x38, /* 00 000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 153 0x99 '\231' */ + 0x10, /* 000 0000 */ + 0x28, /* 00 0 000 */ + 0x00, /* 00000000 */ + 0x38, /* 00 000 */ + 0x44, /* 0 000 00 */ + 0x44, /* 0 000 00 */ + 0x44, /* 0 000 00 */ + 0x38, /* 00 000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 154 0x9a '\232' */ + 0x00, /* 00000000 */ + 0x28, /* 00 0 000 */ + 0x00, /* 00000000 */ + 0x38, /* 00 000 */ + 0x44, /* 0 000 00 */ + 0x44, /* 0 000 00 */ + 0x44, /* 0 000 00 */ + 0x38, /* 00 000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 155 0x9b '\233' */ + 0x34, /* 00 0 00 */ + 0x58, /* 0 0 000 */ + 0x00, /* 00000000 */ + 0x38, /* 00 000 */ + 0x44, /* 0 000 00 */ + 0x44, /* 0 000 00 */ + 0x44, /* 0 000 00 */ + 0x38, /* 00 000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 156 0x9c '\234' */ + 0x08, /* 0000 000 */ + 0x10, /* 000 0000 */ + 0x00, /* 00000000 */ + 0x44, /* 0 000 00 */ + 0x44, /* 0 000 00 */ + 0x44, /* 0 000 00 */ + 0x4c, /* 0 00 00 */ + 0x34, /* 00 0 00 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 157 0x9d '\235' */ + 0x20, /* 00 00000 */ + 0x10, /* 000 0000 */ + 0x00, /* 00000000 */ + 0x44, /* 0 000 00 */ + 0x44, /* 0 000 00 */ + 0x44, /* 0 000 00 */ + 0x4c, /* 0 00 00 */ + 0x34, /* 00 0 00 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 158 0x9e '\236' */ + 0x10, /* 000 0000 */ + 0x28, /* 00 0 000 */ + 0x00, /* 00000000 */ + 0x44, /* 0 000 00 */ + 0x44, /* 0 000 00 */ + 0x44, /* 0 000 00 */ + 0x4c, /* 0 00 00 */ + 0x34, /* 00 0 00 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 159 0x9f '\237' */ + 0x00, /* 00000000 */ + 0x28, /* 00 0 000 */ + 0x00, /* 00000000 */ + 0x44, /* 0 000 00 */ + 0x44, /* 0 000 00 */ + 0x44, /* 0 000 00 */ + 0x4c, /* 0 00 00 */ + 0x34, /* 00 0 00 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 160 0xa0 '\240' */ + 0x00, /* 00000000 */ + 0x10, /* 000 0000 */ + 0x38, /* 00 000 */ + 0x10, /* 000 0000 */ + 0x10, /* 000 0000 */ + 0x10, /* 000 0000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 161 0xa1 '\241' */ + 0x18, /* 000 000 */ + 0x24, /* 00 00 00 */ + 0x24, /* 00 00 00 */ + 0x18, /* 000 000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 162 0xa2 '\242' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x10, /* 000 0000 */ + 0x38, /* 00 000 */ + 0x54, /* 0 0 0 00 */ + 0x50, /* 0 0 0000 */ + 0x54, /* 0 0 0 00 */ + 0x38, /* 00 000 */ + 0x10, /* 000 0000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 163 0xa3 '\243' */ + 0x30, /* 00 0000 */ + 0x48, /* 0 00 000 */ + 0x40, /* 0 000000 */ + 0x70, /* 0 0000 */ + 0x40, /* 0 000000 */ + 0x40, /* 0 000000 */ + 0x44, /* 0 000 00 */ + 0x78, /* 0 000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 164 0xa4 '\244' */ + 0x44, /* 0 000 00 */ + 0x24, /* 00 00 00 */ + 0x50, /* 0 0 0000 */ + 0x48, /* 0 00 000 */ + 0x24, /* 00 00 00 */ + 0x14, /* 000 0 00 */ + 0x48, /* 0 00 000 */ + 0x44, /* 0 000 00 */ + 0x38, /* 00 000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 165 0xa5 '\245' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x38, /* 00 000 */ + 0x7c, /* 0 00 */ + 0x7c, /* 0 00 */ + 0x7c, /* 0 00 */ + 0x38, /* 00 000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 166 0xa6 '\246' */ + 0x3c, /* 00 00 */ + 0x54, /* 0 0 0 00 */ + 0x54, /* 0 0 0 00 */ + 0x54, /* 0 0 0 00 */ + 0x3c, /* 00 00 */ + 0x14, /* 000 0 00 */ + 0x14, /* 000 0 00 */ + 0x14, /* 000 0 00 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 167 0xa7 '\247' */ + 0x18, /* 000 000 */ + 0x24, /* 00 00 00 */ + 0x44, /* 0 000 00 */ + 0x48, /* 0 00 000 */ + 0x48, /* 0 00 000 */ + 0x44, /* 0 000 00 */ + 0x44, /* 0 000 00 */ + 0x58, /* 0 0 000 */ + 0x40, /* 0 000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 168 0xa8 '\250' */ + 0x00, /* 00000000 */ + 0x70, /* 0 0000 */ + 0x08, /* 0000 000 */ + 0x64, /* 0 00 00 */ + 0x54, /* 0 0 0 00 */ + 0x64, /* 0 00 00 */ + 0x58, /* 0 0 000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 169 0xa9 '\251' */ + 0x00, /* 00000000 */ + 0x70, /* 0 0000 */ + 0x08, /* 0000 000 */ + 0x34, /* 00 0 00 */ + 0x44, /* 0 000 00 */ + 0x34, /* 00 0 00 */ + 0x08, /* 0000 000 */ + 0x70, /* 0 0000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 170 0xaa '\252' */ + 0x00, /* 00000000 */ + 0x7a, /* 0 0 0 */ + 0x2e, /* 00 0 0 */ + 0x2e, /* 00 0 0 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 171 0xab '\253' */ + 0x00, /* 00000000 */ + 0x08, /* 0000 000 */ + 0x10, /* 000 0000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 172 0xac '\254' */ + 0x00, /* 00000000 */ + 0x28, /* 00 0 000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 173 0xad '\255' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x08, /* 0000 000 */ + 0x7c, /* 0 00 */ + 0x10, /* 000 0000 */ + 0x7c, /* 0 00 */ + 0x20, /* 00 00000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 174 0xae '\256' */ + 0x00, /* 00000000 */ + 0x3c, /* 00 00 */ + 0x50, /* 0 0 0000 */ + 0x50, /* 0 0 0000 */ + 0x78, /* 0 000 */ + 0x50, /* 0 0 0000 */ + 0x50, /* 0 0 0000 */ + 0x5c, /* 0 0 00 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 175 0xaf '\257' */ + 0x00, /* 00000000 */ + 0x38, /* 00 000 */ + 0x44, /* 0 000 00 */ + 0x4c, /* 0 00 00 */ + 0x54, /* 0 0 0 00 */ + 0x64, /* 0 00 00 */ + 0x44, /* 0 000 00 */ + 0x38, /* 00 000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 176 0xb0 '\260' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x6c, /* 0 0 00 */ + 0x54, /* 0 0 0 00 */ + 0x6c, /* 0 0 00 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 177 0xb1 '\261' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x10, /* 000 0000 */ + 0x10, /* 000 0000 */ + 0x7c, /* 0 00 */ + 0x10, /* 000 0000 */ + 0x10, /* 000 0000 */ + 0x7c, /* 0 00 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 178 0xb2 '\262' */ + 0x00, /* 00000000 */ + 0x08, /* 0000 000 */ + 0x10, /* 000 0000 */ + 0x20, /* 00 00000 */ + 0x10, /* 000 0000 */ + 0x08, /* 0000 000 */ + 0x00, /* 00000000 */ + 0x38, /* 00 000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 179 0xb3 '\263' */ + 0x00, /* 00000000 */ + 0x10, /* 000 0000 */ + 0x08, /* 0000 000 */ + 0x04, /* 00000 00 */ + 0x08, /* 0000 000 */ + 0x10, /* 000 0000 */ + 0x00, /* 00000000 */ + 0x1c, /* 000 00 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 180 0xb4 '\264' */ + 0x00, /* 00000000 */ + 0x44, /* 0 000 00 */ + 0x28, /* 00 0 000 */ + 0x7c, /* 0 00 */ + 0x10, /* 000 0000 */ + 0x7c, /* 0 00 */ + 0x10, /* 000 0000 */ + 0x10, /* 000 0000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 181 0xb5 '\265' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x48, /* 0 00 000 */ + 0x48, /* 0 00 000 */ + 0x48, /* 0 00 000 */ + 0x48, /* 0 00 000 */ + 0x74, /* 0 0 00 */ + 0x40, /* 0 000000 */ + 0x40, /* 0 000000 */ + 0x00, /* 00000000 */ + + /* 182 0xb6 '\266' */ + 0x00, /* 00000000 */ + 0x10, /* 000 0000 */ + 0x08, /* 0000 000 */ + 0x0c, /* 0000 00 */ + 0x14, /* 000 0 00 */ + 0x24, /* 00 00 00 */ + 0x24, /* 00 00 00 */ + 0x18, /* 000 000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 183 0xb7 '\267' */ + 0x00, /* 00000000 */ + 0x7c, /* 0 00 */ + 0x24, /* 00 00 00 */ + 0x10, /* 000 0000 */ + 0x08, /* 0000 000 */ + 0x10, /* 000 0000 */ + 0x24, /* 00 00 00 */ + 0x7c, /* 0 00 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 184 0xb8 '\270' */ + 0x00, /* 00000000 */ + 0x7c, /* 0 00 */ + 0x28, /* 00 0 000 */ + 0x28, /* 00 0 000 */ + 0x28, /* 00 0 000 */ + 0x28, /* 00 0 000 */ + 0x28, /* 00 0 000 */ + 0x28, /* 00 0 000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 185 0xb9 '\271' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x7c, /* 0 00 */ + 0x28, /* 00 0 000 */ + 0x28, /* 00 0 000 */ + 0x28, /* 00 0 000 */ + 0x28, /* 00 0 000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 186 0xba '\272' */ + 0x10, /* 000 0000 */ + 0x10, /* 000 0000 */ + 0x10, /* 000 0000 */ + 0x10, /* 000 0000 */ + 0x10, /* 000 0000 */ + 0x10, /* 000 0000 */ + 0x10, /* 000 0000 */ + 0x10, /* 000 0000 */ + 0x10, /* 000 0000 */ + 0x60, /* 0 00000 */ + 0x00, /* 00000000 */ + + /* 187 0xbb '\273' */ + 0x00, /* 00000000 */ + 0x1c, /* 000 00 */ + 0x24, /* 00 00 00 */ + 0x24, /* 00 00 00 */ + 0x1c, /* 000 00 */ + 0x00, /* 00000000 */ + 0x3c, /* 00 00 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 188 0xbc '\274' */ + 0x00, /* 00000000 */ + 0x18, /* 000 000 */ + 0x24, /* 00 00 00 */ + 0x24, /* 00 00 00 */ + 0x18, /* 000 000 */ + 0x00, /* 00000000 */ + 0x3c, /* 00 00 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 189 0xbd '\275' */ + 0x00, /* 00000000 */ + 0x38, /* 00 000 */ + 0x44, /* 0 000 00 */ + 0x44, /* 0 000 00 */ + 0x44, /* 0 000 00 */ + 0x44, /* 0 000 00 */ + 0x28, /* 00 0 000 */ + 0x6c, /* 0 0 00 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 190 0xbe '\276' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x38, /* 00 000 */ + 0x54, /* 0 0 0 00 */ + 0x5c, /* 0 0 00 */ + 0x50, /* 0 0 0000 */ + 0x3c, /* 00 00 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 191 0xbf '\277' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x38, /* 00 000 */ + 0x4c, /* 0 00 00 */ + 0x54, /* 0 0 0 00 */ + 0x64, /* 0 00 00 */ + 0x38, /* 00 000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 192 0xc0 '\300' */ + 0x00, /* 00000000 */ + 0x10, /* 000 0000 */ + 0x00, /* 00000000 */ + 0x10, /* 000 0000 */ + 0x20, /* 00 00000 */ + 0x40, /* 0 000000 */ + 0x44, /* 0 000 00 */ + 0x38, /* 00 000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 193 0xc1 '\301' */ + 0x00, /* 00000000 */ + 0x08, /* 0000 000 */ + 0x00, /* 00000000 */ + 0x08, /* 0000 000 */ + 0x08, /* 0000 000 */ + 0x08, /* 0000 000 */ + 0x08, /* 0000 000 */ + 0x08, /* 0000 000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 194 0xc2 '\302' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x7c, /* 0 00 */ + 0x04, /* 00000 00 */ + 0x04, /* 00000 00 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 195 0xc3 '\303' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x0c, /* 0000 00 */ + 0x08, /* 0000 000 */ + 0x10, /* 000 0000 */ + 0x50, /* 0 0 0000 */ + 0x20, /* 00 00000 */ + 0x20, /* 00 00000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 196 0xc4 '\304' */ + 0x10, /* 000 0000 */ + 0x10, /* 000 0000 */ + 0x10, /* 000 0000 */ + 0x38, /* 00 000 */ + 0x10, /* 000 0000 */ + 0x10, /* 000 0000 */ + 0x10, /* 000 0000 */ + 0x10, /* 000 0000 */ + 0x10, /* 000 0000 */ + 0x60, /* 0 00000 */ + 0x00, /* 00000000 */ + + /* 197 0xc5 '\305' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x04, /* 00000 00 */ + 0x38, /* 00 000 */ + 0x44, /* 0 000 00 */ + 0x38, /* 00 000 */ + 0x40, /* 0 000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 198 0xc6 '\306' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x10, /* 000 0000 */ + 0x10, /* 000 0000 */ + 0x28, /* 00 0 000 */ + 0x28, /* 00 0 000 */ + 0x44, /* 0 000 00 */ + 0x7c, /* 0 00 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 199 0xc7 '\307' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x24, /* 00 00 00 */ + 0x48, /* 0 00 000 */ + 0x48, /* 0 00 000 */ + 0x24, /* 00 00 00 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 200 0xc8 '\310' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x48, /* 0 00 000 */ + 0x24, /* 00 00 00 */ + 0x24, /* 00 00 00 */ + 0x48, /* 0 00 000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 201 0xc9 '\311' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x54, /* 0 0 0 00 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 202 0xca '\312' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 203 0xcb '\313' */ + 0x10, /* 000 0000 */ + 0x38, /* 00 000 */ + 0x44, /* 0 000 00 */ + 0x44, /* 0 000 00 */ + 0x7c, /* 0 00 */ + 0x44, /* 0 000 00 */ + 0x44, /* 0 000 00 */ + 0x44, /* 0 000 00 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 204 0xcc '\314' */ + 0x58, /* 0 0 000 */ + 0x38, /* 00 000 */ + 0x44, /* 0 000 00 */ + 0x44, /* 0 000 00 */ + 0x7c, /* 0 00 */ + 0x44, /* 0 000 00 */ + 0x44, /* 0 000 00 */ + 0x44, /* 0 000 00 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 205 0xcd '\315' */ + 0x58, /* 0 0 000 */ + 0x38, /* 00 000 */ + 0x44, /* 0 000 00 */ + 0x44, /* 0 000 00 */ + 0x44, /* 0 000 00 */ + 0x44, /* 0 000 00 */ + 0x44, /* 0 000 00 */ + 0x38, /* 00 000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 206 0xce '\316' */ + 0x00, /* 00000000 */ + 0x3c, /* 00 00 */ + 0x50, /* 0 0 0000 */ + 0x50, /* 0 0 0000 */ + 0x58, /* 0 0 000 */ + 0x50, /* 0 0 0000 */ + 0x50, /* 0 0 0000 */ + 0x3c, /* 00 00 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 207 0xcf '\317' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x28, /* 00 0 000 */ + 0x54, /* 0 0 0 00 */ + 0x5c, /* 0 0 00 */ + 0x50, /* 0 0 0000 */ + 0x2c, /* 00 0 00 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 208 0xd0 '\320' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x38, /* 00 000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 209 0xd1 '\321' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x7e, /* 0 0 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 210 0xd2 '\322' */ + 0x00, /* 00000000 */ + 0x14, /* 000 0 00 */ + 0x28, /* 00 0 000 */ + 0x28, /* 00 0 000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 211 0xd3 '\323' */ + 0x00, /* 00000000 */ + 0x14, /* 000 0 00 */ + 0x14, /* 000 0 00 */ + 0x28, /* 00 0 000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 212 0xd4 '\324' */ + 0x00, /* 00000000 */ + 0x08, /* 0000 000 */ + 0x10, /* 000 0000 */ + 0x18, /* 000 000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 213 0xd5 '\325' */ + 0x00, /* 00000000 */ + 0x18, /* 000 000 */ + 0x08, /* 0000 000 */ + 0x10, /* 000 0000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 214 0xd6 '\326' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x10, /* 000 0000 */ + 0x00, /* 00000000 */ + 0x7c, /* 0 00 */ + 0x00, /* 00000000 */ + 0x10, /* 000 0000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 215 0xd7 '\327' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x10, /* 000 0000 */ + 0x28, /* 00 0 000 */ + 0x44, /* 0 000 00 */ + 0x28, /* 00 0 000 */ + 0x10, /* 000 0000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 216 0xd8 '\330' */ + 0x00, /* 00000000 */ + 0x28, /* 00 0 000 */ + 0x00, /* 00000000 */ + 0x44, /* 0 000 00 */ + 0x44, /* 0 000 00 */ + 0x44, /* 0 000 00 */ + 0x44, /* 0 000 00 */ + 0x3c, /* 00 00 */ + 0x04, /* 00000 00 */ + 0x38, /* 00 000 */ + 0x00, /* 00000000 */ + + /* 217 0xd9 '\331' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x7e, /* 0 0 */ + 0x00, /* 00000000 */ + 0x7e, /* 0 0 */ + 0x00, /* 00000000 */ + 0x7e, /* 0 0 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 218 0xda '\332' */ + 0x00, /* 00000000 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 219 0xdb '\333' */ + 0x00, /* 00000000 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 220 0xdc '\334' */ + 0x00, /* 00000000 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 221 0xdd '\335' */ + 0x00, /* 00000000 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 222 0xde '\336' */ + 0x00, /* 00000000 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 223 0xdf '\337' */ + 0x00, /* 00000000 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 224 0xe0 '\340' */ + 0x00, /* 00000000 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 225 0xe1 '\341' */ + 0x00, /* 00000000 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 226 0xe2 '\342' */ + 0x00, /* 00000000 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 227 0xe3 '\343' */ + 0x00, /* 00000000 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 228 0xe4 '\344' */ + 0x00, /* 00000000 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 229 0xe5 '\345' */ + 0x00, /* 00000000 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 230 0xe6 '\346' */ + 0x00, /* 00000000 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 231 0xe7 '\347' */ + 0x00, /* 00000000 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 232 0xe8 '\350' */ + 0x00, /* 00000000 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 233 0xe9 '\351' */ + 0x00, /* 00000000 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 234 0xea '\352' */ + 0x00, /* 00000000 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 235 0xeb '\353' */ + 0x00, /* 00000000 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 236 0xec '\354' */ + 0x00, /* 00000000 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 237 0xed '\355' */ + 0x00, /* 00000000 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 238 0xee '\356' */ + 0x00, /* 00000000 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 239 0xef '\357' */ + 0x00, /* 00000000 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 240 0xf0 '\360' */ + 0x00, /* 00000000 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 241 0xf1 '\361' */ + 0x00, /* 00000000 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 242 0xf2 '\362' */ + 0x00, /* 00000000 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 243 0xf3 '\363' */ + 0x00, /* 00000000 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 244 0xf4 '\364' */ + 0x00, /* 00000000 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 245 0xf5 '\365' */ + 0x00, /* 00000000 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 246 0xf6 '\366' */ + 0x00, /* 00000000 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 247 0xf7 '\367' */ + 0x00, /* 00000000 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 248 0xf8 '\370' */ + 0x00, /* 00000000 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 249 0xf9 '\371' */ + 0x00, /* 00000000 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 250 0xfa '\372' */ + 0x00, /* 00000000 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 251 0xfb '\373' */ + 0x00, /* 00000000 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 252 0xfc '\374' */ + 0x00, /* 00000000 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 253 0xfd '\375' */ + 0x00, /* 00000000 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 254 0xfe '\376' */ + 0x00, /* 00000000 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 255 0xff '\377' */ + 0x00, /* 00000000 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x3c, /* 00 00 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + +}; diff --git a/src/gtkglue.c b/src/gtkglue.c new file mode 100644 index 0000000..ba5cbf5 --- /dev/null +++ b/src/gtkglue.c @@ -0,0 +1,1495 @@ +/* gtk/SANE-glue -- gtk interfacing routines for SANE + Uses the SANE library. + Copyright (C) 1997 David Mosberger and Tristan Tarrant + + 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 "../include/lalloca.h" /* MUST come first for AIX! */ +#endif + +#include "../include/sane/config.h" +#include "../include/lalloca.h" + +#include <assert.h> +#include <errno.h> +#include <memory.h> +#include <pwd.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include <sys/types.h> /* Apollo/DomainOS needs this _before_ sys/stat.h */ +#include <sys/stat.h> + +#include "gtkglue.h" + +#include "preferences.h" +#include <sane/sane.h> +#include <sane/saneopts.h> + +#define DBG_fatal 0 +#define DBG_error 1 +#define DBG_warning 2 +#define DBG_info 3 +#define DBG_debug 4 + +#define BACKEND_NAME gtkglue +#include "../include/sane/sanei_debug.h" + +int gsg_message_dialog_active = 0; + +/* forward declarations: */ +static void panel_rebuild (GSGDialog * dialog); + +static const char * +unit_string (SANE_Unit unit) +{ + double d; + + switch (unit) + { + case SANE_UNIT_NONE: + return "none"; + case SANE_UNIT_PIXEL: + return "pixel"; + case SANE_UNIT_BIT: + return "bit"; + case SANE_UNIT_DPI: + return "dpi"; + case SANE_UNIT_PERCENT: + return "%"; + case SANE_UNIT_MM: + d = preferences.length_unit; + if (d > 9.9 && d < 10.1) + return "cm"; + else if (d > 25.3 && d < 25.5) + return "in"; + return "mm"; + case SANE_UNIT_MICROSECOND: + return "us"; + } + return 0; +} + +static void +set_tooltip (GtkTooltips * tooltips, GtkWidget * widget, const char *desc) +{ + if (desc && desc[0]) +#ifdef HAVE_GTK_TOOLTIPS_SET_TIPS + /* pre 0.99.4: */ + gtk_tooltips_set_tips (tooltips, widget, (char *) desc); +#else + gtk_tooltips_set_tip (tooltips, widget, desc, 0); +#endif +} + +int +gsg_make_path (size_t buf_size, char *buf, + const char *prog_name, + const char *prefix, const char *dev_name, const char *postfix) +{ + struct passwd *pw; + size_t len, extra; + int i; + + /* first, make sure ~/.sane exists: */ + pw = getpwuid (getuid ()); + if (!pw) + { + snprintf (buf, buf_size, "Failed to determine home directory: %s.", + strerror (errno)); + gsg_error (buf); + return -1; + } + snprintf (buf, buf_size, "%s/.sane", pw->pw_dir); + mkdir (buf, 0777); /* ensure ~/.sane directory exists */ + + len = strlen (buf); + + if (prog_name) + { + extra = strlen (prog_name); + if (len + extra + 1 >= buf_size) + goto filename_too_long; + + buf[len++] = '/'; + memcpy (buf + len, prog_name, extra); + len += extra; + buf[len] = '\0'; + mkdir (buf, 0777); /* ensure ~/.sane/PROG_NAME directory exists */ + } + if (len >= buf_size) + goto filename_too_long; + + buf[len++] = '/'; + + if (prefix) + { + extra = strlen (prefix); + if (len + extra >= buf_size) + goto filename_too_long; + + memcpy (buf + len, prefix, extra); + len += extra; + } + + if (dev_name) + { + /* Turn devicename into valid filename by replacing slashes by + "+-". A lonely `+' gets translated into "++" so we can tell + it from a substituted slash. */ + + for (i = 0; dev_name[i]; ++i) + { + if (len + 2 >= buf_size) + goto filename_too_long; + + switch (dev_name[i]) + { + case '/': + buf[len++] = '+'; + buf[len++] = '-'; + break; + +#ifdef HAVE_OS2_H + case ':': /* OS2 can not handle colons in filenames */ + buf[len++] = '+'; + buf[len++] = '_'; + break; +#endif + + case '+': + buf[len++] = '+'; + default: + buf[len++] = dev_name[i]; + break; + } + } + } + + if (postfix) + { + extra = strlen (postfix); + if (len + extra >= buf_size) + goto filename_too_long; + memcpy (buf + len, postfix, extra); + len += extra; + } + if (len >= buf_size) + goto filename_too_long; + + buf[len++] = '\0'; + return 0; + +filename_too_long: + gsg_error ("Filename too long."); + errno = E2BIG; + return -1; +} + +static void +set_option (GSGDialog * dialog, int opt_num, void *val, SANE_Action action) +{ + SANE_Status status; + SANE_Int info; + char buf[256]; + + status = sane_control_option (dialog->dev, opt_num, action, val, &info); + if (status != SANE_STATUS_GOOD) + { + snprintf (buf, sizeof (buf), "Failed to set value of option %s: %s.", + sane_get_option_descriptor (dialog->dev, opt_num)->name, + sane_strstatus (status)); + gsg_error (buf); + return; + } + if (info & SANE_INFO_RELOAD_OPTIONS) + { + panel_rebuild (dialog); + if (dialog->option_reload_callback) + (*dialog->option_reload_callback) (dialog, dialog->option_reload_arg); + } + if ((info & SANE_INFO_RELOAD_PARAMS) && dialog->param_change_callback) + (*dialog->param_change_callback) (dialog, dialog->param_change_arg); +} + +void +gsg_close_dialog_callback (GtkWidget * widget, gpointer data) +{ + gtk_widget_destroy (data); + gsg_message_dialog_active = 0; +} + +void +gsg_message (gchar * title, gchar * message) +{ + GtkWidget *main_vbox, *label; + GtkWidget *button, *message_dialog; + + if (gsg_message_dialog_active) + { + fprintf (stderr, "%s: %s\n", title, message); + return; + } + gsg_message_dialog_active = 1; + message_dialog = gtk_window_new (GTK_WINDOW_TOPLEVEL); + gtk_window_position (GTK_WINDOW (message_dialog), GTK_WIN_POS_MOUSE); + gtk_window_set_title (GTK_WINDOW (message_dialog), title); + + /* create the main vbox */ + main_vbox = gtk_vbox_new (TRUE, 5); + gtk_container_border_width (GTK_CONTAINER (main_vbox), 5); + gtk_widget_show (main_vbox); + + gtk_container_add (GTK_CONTAINER (message_dialog), main_vbox); + + /* the message */ + label = gtk_label_new (message); + gtk_container_add (GTK_CONTAINER (main_vbox), label); + gtk_widget_show (label); + + /* the confirmation button */ + button = gtk_button_new_with_label ("OK"); + gtk_signal_connect (GTK_OBJECT (button), "clicked", + (GtkSignalFunc) gsg_close_dialog_callback, + message_dialog); + gtk_container_add (GTK_CONTAINER (main_vbox), button); + + gtk_widget_show (button); + gtk_widget_show (message_dialog); +} + +void +gsg_error (gchar * error) +{ + gsg_message ("Error", error); +} + +void +gsg_warning (gchar * warning) +{ + gsg_message ("Warning", warning); +} + +static void +get_filename_button_clicked (GtkWidget * w, gpointer data) +{ + int *clicked = data; + *clicked = 1; +} + +int +gsg_get_filename (const char *label, const char *default_name, + size_t max_len, char *filename) +{ + int cancel = 0, ok = 0; + GtkWidget *filesel; + + filesel = gtk_file_selection_new ((char *) label); + + gtk_window_set_modal (GTK_WINDOW (filesel), TRUE); + + gtk_signal_connect (GTK_OBJECT + (GTK_FILE_SELECTION (filesel)->cancel_button), + "clicked", (GtkSignalFunc) get_filename_button_clicked, + &cancel); + gtk_signal_connect (GTK_OBJECT (GTK_FILE_SELECTION (filesel)->ok_button), + "clicked", (GtkSignalFunc) get_filename_button_clicked, + &ok); + if (default_name) + gtk_file_selection_set_filename (GTK_FILE_SELECTION (filesel), + (char *) default_name); + + gtk_widget_show (filesel); + + while (!cancel && !ok) + { + if (!gtk_events_pending ()) + usleep (100000); + gtk_main_iteration (); + } + + if (ok) + { + size_t len, cwd_len; + char *cwd; + + strncpy (filename, + gtk_file_selection_get_filename (GTK_FILE_SELECTION (filesel)), + max_len - 1); + filename[max_len - 1] = '\0'; + + len = strlen (filename); + cwd = alloca (len + 2); + getcwd (cwd, len + 1); + cwd_len = strlen (cwd); + cwd[cwd_len++] = '/'; + cwd[cwd_len] = '\0'; + if (strncmp (filename, cwd, cwd_len) == 0) + memcpy (filename, filename + cwd_len, len - cwd_len + 1); + } + gtk_widget_destroy (filesel); + return cancel ? -1 : 0; +} + +static gint +autobutton_update (GtkWidget * widget, GSGDialogElement * elem) +{ + GSGDialog *dialog = elem->dialog; + int opt_num = elem - dialog->element; + const SANE_Option_Descriptor *opt; + SANE_Status status; + SANE_Word val; + char buf[256]; + + opt = sane_get_option_descriptor (dialog->dev, opt_num); + if (GTK_TOGGLE_BUTTON (widget)->active) + set_option (dialog, opt_num, 0, SANE_ACTION_SET_AUTO); + else + { + status = sane_control_option (dialog->dev, opt_num, + SANE_ACTION_GET_VALUE, &val, 0); + if (status != SANE_STATUS_GOOD) + { + snprintf (buf, sizeof (buf), + "Failed to obtain value of option %s: %s.", + opt->name, sane_strstatus (status)); + gsg_error (buf); + } + set_option (dialog, opt_num, &val, SANE_ACTION_SET_VALUE); + } + return FALSE; +} + +static void +autobutton_new (GtkWidget * parent, GSGDialogElement * elem, + GtkWidget * label, GtkTooltips * tooltips) +{ + GtkWidget *button, *alignment; + + button = gtk_check_button_new (); + gtk_container_border_width (GTK_CONTAINER (button), 0); + gtk_widget_set_usize (button, 20, 20); + gtk_signal_connect (GTK_OBJECT (button), "toggled", + (GtkSignalFunc) autobutton_update, elem); + set_tooltip (tooltips, button, "Turns on automatic mode."); + + alignment = gtk_alignment_new (0.0, 1.0, 0.5, 0.5); + gtk_container_add (GTK_CONTAINER (alignment), button); + + gtk_box_pack_end (GTK_BOX (parent), label, FALSE, FALSE, 0); + gtk_box_pack_end (GTK_BOX (parent), alignment, FALSE, FALSE, 2); + + gtk_widget_show (alignment); + gtk_widget_show (button); +} + +static gint +button_update (GtkWidget * widget, GSGDialogElement * elem) +{ + GSGDialog *dialog = elem->dialog; + int opt_num = elem - dialog->element; + const SANE_Option_Descriptor *opt; + SANE_Word val = SANE_FALSE; + + opt = sane_get_option_descriptor (dialog->dev, opt_num); + if (GTK_TOGGLE_BUTTON (widget)->active) + val = SANE_TRUE; + set_option (dialog, opt_num, &val, SANE_ACTION_SET_VALUE); + return FALSE; +} + +static void +button_new (GtkWidget * parent, const char *name, SANE_Word val, + GSGDialogElement * elem, GtkTooltips * tooltips, const char *desc, + gint is_settable) +{ + GtkWidget *button; + + button = gtk_check_button_new_with_label ((char *) name); + gtk_toggle_button_set_state (GTK_TOGGLE_BUTTON (button), val); + gtk_signal_connect (GTK_OBJECT (button), "toggled", + (GtkSignalFunc) button_update, elem); + gtk_box_pack_start (GTK_BOX (parent), button, FALSE, TRUE, 0); + if (!is_settable) + gtk_widget_set_sensitive (GTK_WIDGET (button), FALSE); + gtk_widget_show (button); + set_tooltip (tooltips, button, desc); + + elem->widget = button; +} + +static void +scale_update (GtkAdjustment * adj_data, GSGDialogElement * elem) +{ + const SANE_Option_Descriptor *opt; + GSGDialog *dialog = elem->dialog; + SANE_Word val, new_val; + int opt_num; + double d; + SANE_Status status; + + DBG_INIT (); + + DBG (DBG_debug, "scale_update\n"); + + + opt_num = elem - dialog->element; + opt = sane_get_option_descriptor (dialog->dev, opt_num); + switch (opt->type) + { + case SANE_TYPE_INT: + val = adj_data->value + 0.5; + break; + + case SANE_TYPE_FIXED: + d = adj_data->value; + if (opt->unit == SANE_UNIT_MM) + d *= preferences.length_unit; + val = SANE_FIX (d); + break; + + default: + fprintf (stderr, "scale_update: unknown type %d\n", opt->type); + return; + } + set_option (dialog, opt_num, &val, SANE_ACTION_SET_VALUE); + status = + sane_control_option (dialog->dev, opt_num, SANE_ACTION_GET_VALUE, + &new_val, 0); + if (status != SANE_STATUS_GOOD) + { + DBG (DBG_fatal, "scale_update: sane_control_option failed: %s\n", + sane_strstatus (status)); + return; + } + + if (new_val != val) + { + val = new_val; + goto value_changed; + } + return; /* value didn't change */ + +value_changed: + switch (opt->type) + { + case SANE_TYPE_INT: + adj_data->value = val; + break; + + case SANE_TYPE_FIXED: + d = SANE_UNFIX (val); + if (opt->unit == SANE_UNIT_MM) + d /= preferences.length_unit; + adj_data->value = d; + break; + + default: + break; + } + /* Let widget know that value changed _again_. This must converge + quickly---otherwise things would get very slow very quickly (as + in "infinite recursion"): */ + gtk_signal_emit_by_name (GTK_OBJECT (adj_data), "value_changed"); + return; +} + +static void +scale_new (GtkWidget * parent, const char *name, gfloat val, + gfloat min, gfloat max, gfloat quant, int automatic, + GSGDialogElement * elem, GtkTooltips * tooltips, const char *desc, + gint is_settable) +{ + GtkWidget *hbox, *label, *scale; + + hbox = gtk_hbox_new (FALSE, 2); + gtk_container_border_width (GTK_CONTAINER (hbox), 0); + gtk_box_pack_start (GTK_BOX (parent), hbox, FALSE, FALSE, 0); + + label = gtk_label_new ((char *) name); + gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 2); + + elem->data = gtk_adjustment_new (val, min, max, quant, 1.0, 0.0); + scale = gtk_hscale_new (GTK_ADJUSTMENT (elem->data)); + set_tooltip (tooltips, scale, desc); + gtk_widget_set_usize (scale, 200, 0); + + if (automatic) + autobutton_new (hbox, elem, scale, tooltips); + else + gtk_box_pack_end (GTK_BOX (hbox), scale, FALSE, FALSE, 0); + + gtk_range_set_update_policy (GTK_RANGE (scale), GTK_UPDATE_CONTINUOUS); + gtk_scale_set_value_pos (GTK_SCALE (scale), GTK_POS_TOP); + if (quant - (int) quant == 0.0) + gtk_scale_set_digits (GTK_SCALE (scale), 0); + else + /* one place behind decimal point */ + gtk_scale_set_digits (GTK_SCALE (scale), 1); + + gtk_signal_connect (elem->data, "value_changed", + (GtkSignalFunc) scale_update, elem); + + gtk_widget_show (label); + gtk_widget_show (scale); + if (!is_settable) + gtk_widget_set_sensitive (GTK_WIDGET (hbox), FALSE); + + gtk_widget_show (hbox); + + elem->widget = scale; +} + +static void +push_button_callback (GtkWidget * widget, gpointer data) +{ + GSGDialogElement *elem = data; + GSGDialog *dialog = elem->dialog; + int opt_num; + + opt_num = elem - dialog->element; + set_option (dialog, opt_num, 0, SANE_ACTION_SET_VALUE); +} + +static int +option_menu_lookup (GSGMenuItem menu_items[], const char *string) +{ + int i; + + for (i = 0; strcmp (menu_items[i].label, string) != 0; ++i); + return i; +} + +static void +option_menu_callback (GtkWidget * widget, gpointer data) +{ + GSGMenuItem *menu_item = data; + GSGDialogElement *elem = menu_item->elem; + const SANE_Option_Descriptor *opt; + GSGDialog *dialog = elem->dialog; + int opt_num; + double dval; + SANE_Word val; + void *valp = &val; + + opt_num = elem - dialog->element; + opt = sane_get_option_descriptor (dialog->dev, opt_num); + switch (opt->type) + { + case SANE_TYPE_INT: + sscanf (menu_item->label, "%d", &val); + break; + + case SANE_TYPE_FIXED: + sscanf (menu_item->label, "%lg", &dval); + val = SANE_FIX (dval); + break; + + case SANE_TYPE_STRING: + valp = menu_item->label; + break; + + default: + fprintf (stderr, "option_menu_callback: unexpected type %d\n", + opt->type); + break; + } + set_option (dialog, opt_num, valp, SANE_ACTION_SET_VALUE); +} + +static void +option_menu_new (GtkWidget * parent, const char *name, char *str_list[], + const char *val, GSGDialogElement * elem, + GtkTooltips * tooltips, const char *desc, gint is_settable) +{ + GtkWidget *hbox, *label, *option_menu, *menu, *item; + GSGMenuItem *menu_items; + int i, num_items; + + hbox = gtk_hbox_new (FALSE, 2); + gtk_container_border_width (GTK_CONTAINER (hbox), 0); + gtk_box_pack_start (GTK_BOX (parent), hbox, FALSE, FALSE, 0); + + label = gtk_label_new ((char *) name); + gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 2); + + for (num_items = 0; str_list[num_items]; ++num_items); + menu_items = malloc (num_items * sizeof (menu_items[0])); + + menu = gtk_menu_new (); + for (i = 0; i < num_items; ++i) + { + item = gtk_menu_item_new_with_label (str_list[i]); + gtk_container_add (GTK_CONTAINER (menu), item); + gtk_signal_connect (GTK_OBJECT (item), "activate", + (GtkSignalFunc) option_menu_callback, + menu_items + i); + + gtk_widget_show (item); + + menu_items[i].label = str_list[i]; + menu_items[i].elem = elem; + menu_items[i].index = i; + } + + option_menu = gtk_option_menu_new (); + gtk_box_pack_end (GTK_BOX (hbox), option_menu, FALSE, FALSE, 2); + gtk_option_menu_set_menu (GTK_OPTION_MENU (option_menu), menu); + gtk_option_menu_set_history (GTK_OPTION_MENU (option_menu), + option_menu_lookup (menu_items, val)); + set_tooltip (tooltips, option_menu, desc); + + gtk_widget_show (label); + gtk_widget_show (option_menu); + if (!is_settable) + gtk_widget_set_sensitive (GTK_WIDGET (hbox), FALSE); + gtk_widget_show (hbox); + + elem->widget = option_menu; + elem->menu_size = num_items; + elem->menu = menu_items; +} + +static void +text_entry_callback (GtkWidget * w, gpointer data) +{ + GSGDialogElement *elem = data; + const SANE_Option_Descriptor *opt; + GSGDialog *dialog = elem->dialog; + const gchar *text; + int opt_num; + char *buf; + + opt_num = elem - dialog->element; + opt = sane_get_option_descriptor (dialog->dev, opt_num); + + buf = alloca (opt->size); + buf[0] = '\0'; + + text = gtk_entry_get_text (GTK_ENTRY (elem->widget)); + if (text) + strncpy (buf, text, opt->size); + buf[opt->size - 1] = '\0'; + + set_option (dialog, opt_num, buf, SANE_ACTION_SET_VALUE); + + if (strcmp (buf, text) != 0) + /* the backend modified the option value; update widget: */ + gtk_entry_set_text (GTK_ENTRY (elem->widget), buf); +} + +static void +text_entry_new (GtkWidget * parent, const char *name, const char *val, + GSGDialogElement * elem, + GtkTooltips * tooltips, const char *desc, gint is_settable) +{ + GtkWidget *hbox, *text, *label; + + hbox = gtk_hbox_new (FALSE, 2); + gtk_container_border_width (GTK_CONTAINER (hbox), 0); + gtk_box_pack_start (GTK_BOX (parent), hbox, FALSE, FALSE, 0); + + label = gtk_label_new ((char *) name); + gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 2); + + text = gtk_entry_new (); + gtk_entry_set_text (GTK_ENTRY (text), (char *) val); + gtk_box_pack_start (GTK_BOX (hbox), text, FALSE, TRUE, 0); + gtk_signal_connect (GTK_OBJECT (text), "changed", + (GtkSignalFunc) text_entry_callback, elem); + set_tooltip (tooltips, text, desc); + + gtk_widget_show (hbox); + if (!is_settable) + gtk_widget_set_sensitive (GTK_WIDGET (hbox), FALSE); + gtk_widget_show (label); + gtk_widget_show (text); + + elem->widget = text; +} + +static GtkWidget * +group_new (GtkWidget * parent, const char *title) +{ + GtkWidget *frame, *vbox; + + frame = gtk_frame_new ((char *) title); + 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 (parent), frame, FALSE, FALSE, 0); + + vbox = gtk_vbox_new (FALSE, 4); + gtk_container_border_width (GTK_CONTAINER (vbox), 2); + gtk_container_add (GTK_CONTAINER (frame), vbox); + gtk_widget_show (vbox); + return vbox; +} + +static GtkWidget * +curve_new (GSGDialog * dialog, int optnum) +{ + const SANE_Option_Descriptor *opt; + gfloat fmin, fmax, val, *vector; + SANE_Word *optval, min, max; + GtkWidget *curve, *gamma; + SANE_Status status; + SANE_Handle dev; + int i, optlen; + + gamma = gtk_gamma_curve_new (); + curve = GTK_GAMMA_CURVE (gamma)->curve; + dev = dialog->dev; + + opt = sane_get_option_descriptor (dev, optnum); + optlen = opt->size / sizeof (SANE_Word); + vector = alloca (optlen * (sizeof (vector[0]) + sizeof (optval[0]))); + optval = (SANE_Word *) (vector + optlen); + + min = max = 0; + switch (opt->constraint_type) + { + case SANE_CONSTRAINT_RANGE: + min = opt->constraint.range->min; + max = opt->constraint.range->max; + break; + + case SANE_CONSTRAINT_WORD_LIST: + if (opt->constraint.word_list[0] > 1) + { + min = max = opt->constraint.word_list[1]; + for (i = 2; i < opt->constraint.word_list[0]; ++i) + { + if (opt->constraint.word_list[i] < min) + min = opt->constraint.word_list[i]; + if (opt->constraint.word_list[i] > max) + max = opt->constraint.word_list[i]; + } + } + break; + + default: + break; + } + if (min == max) + { + fprintf (stderr, + "curve_new: warning: option `%s' has no value constraint\n", + opt->name); + fmin = 0; + fmax = 255; + } + else if (opt->type == SANE_TYPE_FIXED) + { + fmin = SANE_UNFIX (min); + fmax = SANE_UNFIX (max); + } + else + { + fmin = min; + fmax = max; + } + gtk_curve_set_range (GTK_CURVE (curve), 0, optlen - 1, fmin, fmax); + + status = sane_control_option (dev, optnum, SANE_ACTION_GET_VALUE, + optval, 0); + if (status == SANE_STATUS_GOOD) + { + for (i = 0; i < optlen; ++i) + { + if (opt->type == SANE_TYPE_FIXED) + val = SANE_UNFIX (optval[i]); + else + val = optval[i]; + vector[i] = val; + } + gtk_curve_set_vector (GTK_CURVE (curve), optlen, vector); + } + else + gtk_widget_set_sensitive (gamma, FALSE); + + return gamma; +} + +static void +vector_new (GSGDialog * dialog, GtkWidget * vbox, int num_vopts, int *vopts) +{ + GtkWidget *notebook, *label, *curve; + const SANE_Option_Descriptor *opt; + int i; + + notebook = gtk_notebook_new (); + gtk_container_border_width (GTK_CONTAINER (notebook), 4); + gtk_box_pack_start (GTK_BOX (vbox), notebook, TRUE, TRUE, 0); + + for (i = 0; i < num_vopts; ++i) + { + opt = sane_get_option_descriptor (dialog->dev, vopts[i]); + + label = gtk_label_new ((char *) opt->title); + vbox = gtk_vbox_new ( /* homogeneous */ FALSE, 0); + gtk_notebook_append_page (GTK_NOTEBOOK (notebook), vbox, label); + gtk_widget_show (vbox); + gtk_widget_show (label); + + curve = curve_new (dialog, vopts[i]); + gtk_container_border_width (GTK_CONTAINER (curve), 4); + gtk_box_pack_start (GTK_BOX (vbox), curve, TRUE, TRUE, 0); + gtk_widget_show (curve); + + dialog->element[vopts[i]].widget = curve; + } + gtk_widget_show (notebook); +} + +static void +panel_destroy (GSGDialog * dialog) +{ + const SANE_Option_Descriptor *opt; + GSGDialogElement *elem; + int i, j; + +#ifdef HAVE_GTK_TOOLTIPS_SET_TIPS + /* pre 0.99.4: */ + gtk_tooltips_unref (dialog->tooltips); +#else +# if GTK_MAJOR_VERSION == 2 + gtk_object_sink (GTK_OBJECT (dialog->tooltips)); +# else + gtk_object_unref (GTK_OBJECT (dialog->tooltips)); +# endif /* GTK_MAJOR_VERSION == 2 */ +#endif + gtk_widget_destroy (dialog->main_hbox); + + /* free the menu labels of integer/fix-point word-lists: */ + for (i = 0; i < dialog->num_elements; ++i) + { + if (dialog->element[i].menu) + { + opt = sane_get_option_descriptor (dialog->dev, i); + elem = dialog->element + i; + if (opt->type != SANE_TYPE_STRING) + for (j = 0; j < elem->menu_size; ++j) + if (elem->menu[j].label) + { + free (elem->menu[j].label); + elem->menu[j].label = 0; + } + free (elem->menu); + elem->menu = 0; + } + } + memset (dialog->element, 0, + dialog->num_elements * sizeof (dialog->element[0])); +} + +static void +panel_build (GSGDialog * dialog) +{ + GtkWidget *main_hbox, *standard_vbox, *advanced_vbox, *option_vbox; + GtkWidget *parent, *vbox, *button, *label; + const SANE_Option_Descriptor *opt; + SANE_Handle dev = dialog->dev; + double dval, dmin, dmax, dquant; + char *buf, str[16], title[256]; + GSGDialogElement *elem; + SANE_Word quant, val; + SANE_Status status; + SANE_Int num_words; + char **str_list; + int i, j; + int num_vector_opts = 0, *vector_opts; + + main_hbox = gtk_hbox_new (FALSE, 2); + + if (dialog->twocolumn) + { + option_vbox = gtk_hbox_new (FALSE, 2); /* two column display */ + } + else + { + option_vbox = gtk_vbox_new (FALSE, 2); /* one column display */ + } + + gtk_box_pack_start (GTK_BOX (main_hbox), option_vbox, FALSE, FALSE, 0); + gtk_widget_show (option_vbox); + + /* standard options vbox */ + + standard_vbox = gtk_vbox_new ( /* homogeneous */ FALSE, 0); + gtk_widget_show (standard_vbox); + gtk_box_pack_start (GTK_BOX (option_vbox), standard_vbox, FALSE, FALSE, 0); + + /* advanced options page */ + + advanced_vbox = gtk_vbox_new ( /* homogeneous */ FALSE, 0); + gtk_box_pack_start (GTK_BOX (option_vbox), advanced_vbox, TRUE, TRUE, 0); + + /* use black as foreground: */ + dialog->tooltips = gtk_tooltips_new (); + dialog->tooltips_fg.red = 0; + dialog->tooltips_fg.green = 0; + dialog->tooltips_fg.blue = 0; + /* postit yellow (khaki) as background: */ + gdk_color_alloc (gtk_widget_get_colormap (main_hbox), &dialog->tooltips_fg); + dialog->tooltips_bg.red = 61669; + dialog->tooltips_bg.green = 59113; + dialog->tooltips_bg.blue = 35979; + gdk_color_alloc (gtk_widget_get_colormap (main_hbox), &dialog->tooltips_bg); + + /* GTK2 doesn't have the function (seems like it's not needed with GTK2 anyway) */ +#if GTK_MAJOR_VERSION < 2 + gtk_tooltips_set_colors (dialog->tooltips, + &dialog->tooltips_bg, &dialog->tooltips_fg); +#endif /* GTK_MAJOR_VERSION < 2 */ + + gsg_set_tooltips (dialog, preferences.tooltips_enabled); + + gtk_container_add (GTK_CONTAINER (dialog->window), main_hbox); + dialog->main_hbox = main_hbox; + dialog->advanced_vbox = advanced_vbox; + + /* reset well-known options: */ + dialog->well_known.preview = -1; + dialog->well_known.dpi = -1; + dialog->well_known.coord[GSG_TL_X] = -1; + dialog->well_known.coord[GSG_TL_Y] = -1; + dialog->well_known.coord[GSG_BR_X] = -1; + dialog->well_known.coord[GSG_BR_Y] = -1; + + vector_opts = alloca (dialog->num_elements * sizeof (int)); + + parent = standard_vbox; + for (i = 1; i < dialog->num_elements; ++i) + { + opt = sane_get_option_descriptor (dev, i); + if (!SANE_OPTION_IS_ACTIVE (opt->cap)) + continue; + /* pick up well-known options as we go: */ + if (opt->name) + { + if (strcmp (opt->name, SANE_NAME_PREVIEW) == 0 + && opt->type == SANE_TYPE_BOOL) + { + dialog->well_known.preview = i; + continue; + } + else if (strcmp (opt->name, SANE_NAME_SCAN_RESOLUTION) == 0 + && opt->unit == SANE_UNIT_DPI + && (opt->type == SANE_TYPE_INT + || opt->type == SANE_TYPE_FIXED)) + dialog->well_known.dpi = i; + else if (strcmp (opt->name, SANE_NAME_SCAN_TL_X) == 0) + dialog->well_known.coord[GSG_TL_X] = i; + else if (strcmp (opt->name, SANE_NAME_SCAN_TL_Y) == 0) + dialog->well_known.coord[GSG_TL_Y] = i; + else if (strcmp (opt->name, SANE_NAME_SCAN_BR_X) == 0) + dialog->well_known.coord[GSG_BR_X] = i; + else if (strcmp (opt->name, SANE_NAME_SCAN_BR_Y) == 0) + dialog->well_known.coord[GSG_BR_Y] = i; + } + + elem = dialog->element + i; + elem->dialog = dialog; + + if (opt->unit == SANE_UNIT_NONE) + strncpy (title, opt->title, sizeof (title)); + else + snprintf (title, sizeof (title), + "%s [%s]", opt->title, unit_string (opt->unit)); + + switch (opt->type) + { + case SANE_TYPE_GROUP: + /* group a set of options */ + vbox = standard_vbox; + if (opt->cap & SANE_CAP_ADVANCED) + vbox = advanced_vbox; + parent = group_new (vbox, title); + elem->widget = parent; + break; + + case SANE_TYPE_BOOL: + if ((opt->cap & SANE_CAP_ADVANCED) && !dialog->advanced) + break; + if (!(opt->cap & SANE_CAP_SOFT_DETECT)) + break; + assert (opt->size == sizeof (SANE_Word)); + status = sane_control_option (dialog->dev, i, SANE_ACTION_GET_VALUE, + &val, 0); + if (status != SANE_STATUS_GOOD) + goto get_value_failed; + + button_new (parent, title, val, elem, dialog->tooltips, opt->desc, + SANE_OPTION_IS_SETTABLE (opt->cap)); + gtk_widget_show (parent->parent); + break; + + case SANE_TYPE_INT: + if ((opt->cap & SANE_CAP_ADVANCED) && !dialog->advanced) + break; + if (!(opt->cap & SANE_CAP_SOFT_DETECT)) + break; + if (opt->size != sizeof (SANE_Word)) + { + vector_opts[num_vector_opts++] = i; + break; + } + status = sane_control_option (dialog->dev, i, SANE_ACTION_GET_VALUE, + &val, 0); + if (status != SANE_STATUS_GOOD) + goto get_value_failed; + + switch (opt->constraint_type) + { + case SANE_CONSTRAINT_RANGE: + /* use a scale */ + quant = opt->constraint.range->quant; + if (quant == 0) + quant = 1; + scale_new (parent, title, val, + opt->constraint.range->min, + opt->constraint.range->max, quant, + (opt->cap & SANE_CAP_AUTOMATIC), elem, + dialog->tooltips, opt->desc, + SANE_OPTION_IS_SETTABLE (opt->cap)); + gtk_widget_show (parent->parent); + break; + + case SANE_CONSTRAINT_WORD_LIST: + /* use a "list-selection" widget */ + num_words = opt->constraint.word_list[0]; + str_list = malloc ((num_words + 1) * sizeof (str_list[0])); + for (j = 0; j < num_words; ++j) + { + sprintf (str, "%d", opt->constraint.word_list[j + 1]); + str_list[j] = strdup (str); + } + str_list[j] = 0; + sprintf (str, "%d", val); + option_menu_new (parent, title, str_list, str, elem, + dialog->tooltips, opt->desc, + SANE_OPTION_IS_SETTABLE (opt->cap)); + free (str_list); + gtk_widget_show (parent->parent); + break; + + case SANE_CONSTRAINT_NONE: + /* having no constraint for an int is strange but allowed + by the SANE standard so we just ignore such options here */ + break; + + default: + fprintf (stderr, "panel_build: unknown constraint %d!\n", + opt->constraint_type); + break; + } + break; + + case SANE_TYPE_FIXED: + if ((opt->cap & SANE_CAP_ADVANCED) && !dialog->advanced) + break; + if (!(opt->cap & SANE_CAP_SOFT_DETECT)) + break; + if (opt->size != sizeof (SANE_Word)) + { + vector_opts[num_vector_opts++] = i; + break; + } + status = sane_control_option (dialog->dev, i, SANE_ACTION_GET_VALUE, + &val, 0); + if (status != SANE_STATUS_GOOD) + goto get_value_failed; + + switch (opt->constraint_type) + { + case SANE_CONSTRAINT_RANGE: + /* use a scale */ + quant = opt->constraint.range->quant; + if (quant == 0) + quant = 1; + dval = SANE_UNFIX (val); + dmin = SANE_UNFIX (opt->constraint.range->min); + dmax = SANE_UNFIX (opt->constraint.range->max); + dquant = SANE_UNFIX (quant); + if (opt->unit == SANE_UNIT_MM) + { + dval /= preferences.length_unit; + dmin /= preferences.length_unit; + dmax /= preferences.length_unit; + dquant /= preferences.length_unit; + } + scale_new (parent, title, dval, dmin, dmax, dquant, + (opt->cap & SANE_CAP_AUTOMATIC), elem, + dialog->tooltips, opt->desc, + SANE_OPTION_IS_SETTABLE (opt->cap)); + gtk_widget_show (parent->parent); + break; + + case SANE_CONSTRAINT_WORD_LIST: + /* use a "list-selection" widget */ + num_words = opt->constraint.word_list[0]; + str_list = malloc ((num_words + 1) * sizeof (str_list[0])); + for (j = 0; j < num_words; ++j) + { + sprintf (str, "%g", + SANE_UNFIX (opt->constraint.word_list[j + 1])); + str_list[j] = strdup (str); + } + str_list[j] = 0; + sprintf (str, "%g", SANE_UNFIX (val)); + option_menu_new (parent, title, str_list, str, elem, + dialog->tooltips, opt->desc, + SANE_OPTION_IS_SETTABLE (opt->cap)); + free (str_list); + gtk_widget_show (parent->parent); + break; + + case SANE_CONSTRAINT_NONE: + /* having no constraint for a fixed is strange but allowed + by the SANE standard so we just ignore such options here */ + break; + + default: + fprintf (stderr, "panel_build: unknown constraint %d!\n", + opt->constraint_type); + break; + } + break; + + case SANE_TYPE_STRING: + if ((opt->cap & SANE_CAP_ADVANCED) && !dialog->advanced) + break; + if (!(opt->cap & SANE_CAP_SOFT_DETECT)) + break; + buf = malloc (opt->size); + status = sane_control_option (dialog->dev, i, SANE_ACTION_GET_VALUE, + buf, 0); + if (status != SANE_STATUS_GOOD) + { + free (buf); + goto get_value_failed; + } + + switch (opt->constraint_type) + { + case SANE_CONSTRAINT_STRING_LIST: + /* use a "list-selection" widget */ + option_menu_new (parent, title, + (char **) opt->constraint.string_list, buf, + elem, dialog->tooltips, opt->desc, + SANE_OPTION_IS_SETTABLE (opt->cap)); + gtk_widget_show (parent->parent); + break; + + case SANE_CONSTRAINT_NONE: + text_entry_new (parent, title, buf, elem, + dialog->tooltips, opt->desc, + SANE_OPTION_IS_SETTABLE (opt->cap)); + gtk_widget_show (parent->parent); + break; + + default: + fprintf (stderr, "panel_build: unknown constraint %d!\n", + opt->constraint_type); + break; + } + free (buf); + break; + + case SANE_TYPE_BUTTON: + if ((opt->cap & SANE_CAP_ADVANCED) && !dialog->advanced) + break; + button = gtk_button_new (); + gtk_signal_connect (GTK_OBJECT (button), "clicked", + (GtkSignalFunc) push_button_callback, elem); + set_tooltip (dialog->tooltips, button, opt->desc); + + label = gtk_label_new (title); + gtk_container_add (GTK_CONTAINER (button), label); + + gtk_box_pack_start (GTK_BOX (parent), button, FALSE, TRUE, 0); + + gtk_widget_show (label); + gtk_widget_show (button); + + elem->widget = button; + gtk_widget_show (parent->parent); + break; + + default: + fprintf (stderr, "panel_build: Unknown type %d\n", opt->type); + break; + } + continue; + + get_value_failed: + { + char msg[256]; + + sprintf (msg, "Failed to obtain value of option %s: %s.", + opt->name, sane_strstatus (status)); + gsg_error (msg); + } + } + + /* now add in vector editor, if necessary: */ + + if (num_vector_opts) + vector_new (dialog, main_hbox, num_vector_opts, vector_opts); + + if (dialog->advanced) + gtk_widget_show (dialog->advanced_vbox); + else + gtk_widget_hide (dialog->advanced_vbox); + gtk_widget_show (main_hbox); +} + +/* When an setting an option changes the dialog, everything may + change: the option titles, the activity-status of the option, its + constraints or what not. Thus, rather than trying to be clever in + detecting what exactly changed, we use a brute-force method of + rebuilding the entire dialog. */ +static void +panel_rebuild (GSGDialog * dialog) +{ + panel_destroy (dialog); + panel_build (dialog); +} + +GSGDialog * +gsg_create_dialog (GtkWidget * window, const char *device_name, + GSGCallback option_reload_callback, + void *option_reload_arg, GSGCallback param_change_callback, + void *param_change_arg) +{ + SANE_Int num_elements; + GSGDialog *dialog; + SANE_Status status; + SANE_Handle dev; + char buf[256]; + + status = sane_open (device_name, &dev); + if (status != SANE_STATUS_GOOD) + { + sprintf (buf, "Failed to open device `%s': %s.", + device_name, sane_strstatus (status)); + gsg_error (buf); + return 0; + } + + if (sane_control_option (dev, 0, SANE_ACTION_GET_VALUE, &num_elements, 0) + != SANE_STATUS_GOOD) + { + gsg_error ("Error obtaining option count."); + sane_close (dev); + return 0; + } + + dialog = malloc (sizeof (*dialog)); + memset (dialog, 0, sizeof (*dialog)); + + dialog->window = window; + dialog->dev = dev; + dialog->dev_name = strdup (device_name); + dialog->num_elements = num_elements; + dialog->option_reload_callback = option_reload_callback; + dialog->option_reload_arg = option_reload_arg; + dialog->param_change_callback = param_change_callback; + dialog->param_change_arg = param_change_arg; + dialog->advanced = preferences.advanced; + dialog->twocolumn = preferences.twocolumn_enabled; + + dialog->element = malloc (num_elements * sizeof (dialog->element[0])); + memset (dialog->element, 0, num_elements * sizeof (dialog->element[0])); + + panel_build (dialog); + return dialog; +} + +void +gsg_refresh_dialog (GSGDialog * dialog) +{ + panel_rebuild (dialog); + if (dialog->param_change_callback) + (*dialog->param_change_callback) (dialog, dialog->param_change_arg); +} + +void +gsg_update_scan_window (GSGDialog * dialog) +{ + const SANE_Option_Descriptor *opt; + double old_val, new_val; + GSGDialogElement *elem; + SANE_Status status; + SANE_Word word; + int i, optnum; + char str[64]; + + for (i = 0; i < 4; ++i) + if (dialog->well_known.coord[i] > 0) + { + optnum = dialog->well_known.coord[i]; + elem = dialog->element + optnum; + opt = sane_get_option_descriptor (dialog->dev, optnum); + + status = sane_control_option (dialog->dev, optnum, + SANE_ACTION_GET_VALUE, &word, 0); + if (status != SANE_STATUS_GOOD) + continue; /* sliently ignore errors */ + + switch (opt->constraint_type) + { + case SANE_CONSTRAINT_RANGE: + if (opt->type == SANE_TYPE_INT) + { + old_val = GTK_ADJUSTMENT (elem->data)->value; + new_val = word; + GTK_ADJUSTMENT (elem->data)->value = new_val; + } + else + { + old_val = GTK_ADJUSTMENT (elem->data)->value; + new_val = SANE_UNFIX (word); + if (opt->unit == SANE_UNIT_MM) + new_val /= preferences.length_unit; + GTK_ADJUSTMENT (elem->data)->value = new_val; + } + if (old_val != new_val) + gtk_signal_emit_by_name (GTK_OBJECT (elem->data), + "value_changed"); + break; + + case SANE_CONSTRAINT_WORD_LIST: + if (opt->type == SANE_TYPE_INT) + sprintf (str, "%d", word); + else + sprintf (str, "%g", SANE_UNFIX (word)); + /* XXX maybe we should call this only when the value changes... */ + gtk_option_menu_set_history (GTK_OPTION_MENU (elem->widget), + option_menu_lookup (elem->menu, + str)); + break; + + default: + break; + } + } +} + +/* Ensure sure the device has up-to-date option values. Except for + vectors, all option values are kept current. Vectors are + downloaded into the device during this call. */ +void +gsg_sync (GSGDialog * dialog) +{ + const SANE_Option_Descriptor *opt; + gfloat val, *vector; + SANE_Word *optval; + int i, j, optlen; + GtkWidget *curve; + + for (i = 1; i < dialog->num_elements; ++i) + { + opt = sane_get_option_descriptor (dialog->dev, i); + if (!SANE_OPTION_IS_ACTIVE (opt->cap)) + continue; + + if (opt->type != SANE_TYPE_INT && opt->type != SANE_TYPE_FIXED) + continue; + + if (opt->size == sizeof (SANE_Word)) + continue; + + /* ok, we're dealing with an active vector */ + + optlen = opt->size / sizeof (SANE_Word); + optval = alloca (optlen * sizeof (optval[0])); + vector = alloca (optlen * sizeof (vector[0])); + + curve = GTK_GAMMA_CURVE (dialog->element[i].widget)->curve; + gtk_curve_get_vector (GTK_CURVE (curve), optlen, vector); + for (j = 0; j < optlen; ++j) + { + val = vector[j]; + if (opt->type == SANE_TYPE_FIXED) + optval[j] = SANE_FIX (val); + else + optval[j] = val + 0.5; + } + + set_option (dialog, i, optval, SANE_ACTION_SET_VALUE); + } +} + +void +gsg_set_advanced (GSGDialog * dialog, int advanced) +{ + dialog->advanced = advanced; + panel_rebuild (dialog); +} + +void +gsg_set_tooltips (GSGDialog * dialog, int enable) +{ + if (!dialog->tooltips) + return; + + if (enable) + gtk_tooltips_enable (dialog->tooltips); + else + gtk_tooltips_disable (dialog->tooltips); +} + +void +gsg_set_twocolumn (GSGDialog * dialog, int twocolumn) +{ + dialog->twocolumn = twocolumn; + panel_rebuild (dialog); +} + +void +gsg_set_sensitivity (GSGDialog * dialog, int sensitive) +{ + const SANE_Option_Descriptor *opt; + int i; + + for (i = 0; i < dialog->num_elements; ++i) + { + opt = sane_get_option_descriptor (dialog->dev, i); + + if (!SANE_OPTION_IS_ACTIVE (opt->cap) + || opt->type == SANE_TYPE_GROUP || !dialog->element[i].widget) + continue; + + if (!(opt->cap & SANE_CAP_ALWAYS_SETTABLE)) + gtk_widget_set_sensitive (dialog->element[i].widget, sensitive); + } +} + +void +gsg_destroy_dialog (GSGDialog * dialog) +{ + SANE_Handle dev = dialog->dev; + + panel_destroy (dialog); + free ((void *) dialog->dev_name); + free (dialog->element); + free (dialog); + + sane_close (dev); +} diff --git a/src/gtkglue.h b/src/gtkglue.h new file mode 100644 index 0000000..00b1666 --- /dev/null +++ b/src/gtkglue.h @@ -0,0 +1,121 @@ +#ifndef gtkglue_h +#define gtkglue_h + +#include <sys/types.h> + +#include <gtk/gtk.h> + +#include "../include/sane/config.h" +#include <sane/sane.h> + +struct GSGDialog; + +typedef void (*GSGCallback) (struct GSGDialog *dialog, void *arg); + +typedef enum + { + GSG_TL_X, /* top-left x */ + GSG_TL_Y, /* top-left y */ + GSG_BR_X, /* bottom-right x */ + GSG_BR_Y /* bottom-right y */ + } +GSGCornerCoordinates; + +typedef struct + { + /* The option number of the well-known options. Each of these may + be -1 in case the backend doesn't define the respective option. */ + int preview; + int dpi; + int coord[4]; + } +GSGWellKnownOptions; + +typedef struct + { + gchar *label; + struct GSGDialogElement *elem; + gint index; + } +GSGMenuItem; + +typedef struct GSGDialogElement + { + struct GSGDialog *dialog; /* wasteful, but is there a better solution? */ + GtkWidget *automatic; /* auto button for options that support this */ + GtkWidget *widget; + GtkObject *data; + int menu_size; /* # of items in menu (if any) */ + GSGMenuItem *menu; + } +GSGDialogElement; + +typedef struct GSGDialog + { + GtkWidget *window; + GtkWidget *main_hbox; + GtkWidget *advanced_vbox; + int twocolumn; + GtkTooltips *tooltips; + GdkColor tooltips_fg; + GdkColor tooltips_bg; + SANE_Handle *dev; + const char *dev_name; + GSGWellKnownOptions well_known; + int num_elements; + GSGDialogElement *element; + gint idle_id; + u_int rebuild : 1; + u_int advanced : 1; + /* This callback gets invoked whenever the backend notifies us + that the option descriptors have changed. */ + GSGCallback option_reload_callback; + void *option_reload_arg; + /* This callback gets invoked whenever the backend notifies us + that the parameters have changed. */ + GSGCallback param_change_callback; + void *param_change_arg; + } +GSGDialog; + + +extern int gsg_message_dialog_active; + +/* Construct the path and return it in filename_ret (this buffer must + be at least max_len bytes long). The path is constructed as + follows: + + ~/.sane/${PROG_NAME}/${PREFIX}${DEV_NAME}${POSTFIX} + + If PROG_NAME is NULL, an empty string is used and the leading slash + is removed. On success, 0 is returned, on error a negative number and + ERRNO is set to the appropriate value. */ +extern int gsg_make_path (size_t max_len, char *filename_ret, + const char *prog_name, + const char *prefix, const char *dev_name, + const char *postfix); + +extern void gsg_message (gchar *title, gchar * message); +extern void gsg_error (gchar * error_message); +extern void gsg_warning (gchar * warning_message); +extern int gsg_get_filename (const char *label, const char *default_name, + size_t max_len, char *filename); + +extern GSGDialog *gsg_create_dialog (GtkWidget *window, + const char *device_name, + GSGCallback option_reload_callback, + void *option_reload_arg, + GSGCallback param_callback, + void *param_arg); +extern void gsg_sync (GSGDialog *dialog); +extern void gsg_refresh_dialog (GSGDialog *dialog); +extern void gsg_update_scan_window (GSGDialog *dialog); +extern void gsg_set_advanced (GSGDialog *dialog, int advanced); +extern void gsg_set_tooltips (GSGDialog *dialog, int enable); +extern void gsg_set_twocolumn (GSGDialog *dialog, int enable); +extern void gsg_set_sensitivity (GSGDialog *dialog, int sensitive); +extern void gsg_destroy_dialog (GSGDialog * dialog); + +#define gsg_dialog_get_device(dialog) ((dialog)->dev) + +#endif /* gtkglue_h */ diff --git a/src/preferences.c b/src/preferences.c new file mode 100644 index 0000000..16c3839 --- /dev/null +++ b/src/preferences.c @@ -0,0 +1,185 @@ +/* sane - Scanner Access Now Easy. + Copyright (C) 1997 David Mosberger-Tang + This file is part of the SANE package. + + SANE is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + SANE is distributed in the hope that it will be useful, but WITHOUT + ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public + License for more details. + + You should have received a copy of the GNU General Public License + along with sane; see the file COPYING. If not, write to the Free + Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "../include/sane/config.h" + +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include <preferences.h> +#include <sane/sane.h> +#include "../include/sane/sanei.h" +#include "../include/sane/sanei_wire.h" +#include "../include/sane/sanei_codec_ascii.h" + +#define POFFSET(field) ((char *) &((Preferences *) 0)->field - (char *) 0) +#define PFIELD(p,offset,type) (*((type *)(((char *)(p)) + (offset)))) + +Preferences preferences = + { + 0, /* no preferred device (must be 0 or malloced!) */ + 0, /* no default filename */ + 0, /* advanced user */ + 1, /* tooltips enabled */ + 1, /* two column enabled */ + 10.0, /* length unit */ + 1, /* preserve_preview */ + 0, /* preview_own_cmap */ + 1.0 /* preview_gamma */ + }; + +static void w_string (Wire *w, Preferences *p, long offset); +static void w_double (Wire *w, Preferences *p, long offset); +static void w_int (Wire *w, Preferences *p, long offset); + +static struct + { + SANE_String name; + void (*codec) (Wire *w, Preferences *p, long offset); + long offset; + } +desc[] = + { + {"device", w_string, POFFSET(device)}, + {"filename", w_string, POFFSET(filename)}, + {"advanced", w_int, POFFSET(advanced)}, + {"tool-tips", w_int, POFFSET(tooltips_enabled)}, + {"twocolumn", w_int, POFFSET(twocolumn_enabled)}, + {"length-unit", w_double, POFFSET(length_unit)}, + {"preserve-preview", w_int, POFFSET(preserve_preview)}, + {"preview-own-cmap", w_int, POFFSET(preview_own_cmap)}, + {"preview-gamma", w_double, POFFSET(preview_gamma)}, + }; + +static void +w_string (Wire *w, Preferences *p, long offset) +{ + SANE_String string; + + if (w->direction == WIRE_ENCODE) + string = PFIELD (p, offset, char *); + + sanei_w_string (w, &string); + + if (w->direction == WIRE_DECODE) + { + if (w->status == 0) + { + const char **field; + + field = &PFIELD (p, offset, const char *); + if (*field) + free ((char *) *field); + *field = string ? strdup (string) : 0; + } + sanei_w_free (w, (WireCodecFunc) sanei_w_string, &string); + } +} + +static void +w_double (Wire *w, Preferences *p, long offset) +{ + SANE_Word word; + + if (w->direction == WIRE_ENCODE) + word = SANE_FIX (PFIELD (p, offset, double)); + + sanei_w_word (w, &word); + + if (w->direction == WIRE_DECODE) + { + if (w->status == 0) + PFIELD (p, offset, double) = SANE_UNFIX (word); + sanei_w_free (w, (WireCodecFunc) sanei_w_word, &word); + } +} + +static void +w_int (Wire *w, Preferences *p, long offset) +{ + SANE_Word word; + + if (w->direction == WIRE_ENCODE) + word = PFIELD (p, offset, int); + + sanei_w_word (w, &word); + + if (w->direction == WIRE_DECODE) + { + if (w->status == 0) + PFIELD (p, offset, int) = word; + sanei_w_free (w, (WireCodecFunc) sanei_w_word, &word); + } +} + +void +preferences_save (int fd) +{ + Wire w; + int i; + + w.io.fd = fd; + w.io.read = read; + w.io.write = write; + sanei_w_init (&w, sanei_codec_ascii_init); + sanei_w_set_dir (&w, WIRE_ENCODE); + + for (i = 0; i < NELEMS (desc); ++i) + { + sanei_w_string (&w, &desc[i].name); + (*desc[i].codec) (&w, &preferences, desc[i].offset); + } + + sanei_w_set_dir (&w, WIRE_DECODE); /* flush it out */ +} + +void +preferences_restore (int fd) +{ + SANE_String name; + Wire w; + int i; + + w.io.fd = fd; + w.io.read = read; + w.io.write = write; + sanei_w_init (&w, sanei_codec_ascii_init); + sanei_w_set_dir (&w, WIRE_DECODE); + + while (1) + { + sanei_w_space (&w, 3); + if (w.status) + return; + + sanei_w_string (&w, &name); + if (w.status || !name) + return; + + for (i = 0; i < NELEMS (desc); ++i) + { + if (strcmp (name, desc[i].name) == 0) + { + (*desc[i].codec) (&w, &preferences, desc[i].offset); + break; + } + } + } +} diff --git a/src/preferences.h b/src/preferences.h new file mode 100644 index 0000000..5c58df3 --- /dev/null +++ b/src/preferences.h @@ -0,0 +1,25 @@ +#ifndef preferences_h +#define preferences_h + +#include <sane/sane.h> + +typedef struct + { + const char *device; /* name of preferred device (or NULL) */ + const char *filename; /* default filename */ + int advanced; /* advanced user? */ + int tooltips_enabled; /* should tooltips be disabled? */ + int twocolumn_enabled; /* should two column be disabled? */ + double length_unit; /* 1.0==mm, 10.0==cm, 25.4==inches, etc. */ + int preserve_preview; /* save/restore preview image(s)? */ + int preview_own_cmap; /* install colormap for preview */ + double preview_gamma; /* gamma value for previews */ + } +Preferences; + +extern Preferences preferences; + +extern void preferences_save (int fd); +extern void preferences_restore (int fd); + +#endif /* preferences_h */ diff --git a/src/preview.c b/src/preview.c new file mode 100644 index 0000000..6fb21c0 --- /dev/null +++ b/src/preview.c @@ -0,0 +1,1536 @@ +/* sane - Scanner Access Now Easy. + Copyright (C) 1997 David Mosberger-Tang and Tristan Tarrant + This file is part of the SANE package. + + SANE is free software; you can redistribute it and/or modify it under + the terms of the GNU General Public License as published by the Free + Software Foundation; either version 2 of the License, or (at your + option) any later version. + + SANE is distributed in the hope that it will be useful, but WITHOUT + ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + for more details. + + You should have received a copy of the GNU General Public License + along with sane; see the file COPYING. If not, write to the Free + Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ +/* + The preview strategy is as follows: + + 1) A preview always acquires an image that covers the entire + scan surface. This is necessary so the user can see not + only what is, but also what isn't selected. + + 2) The preview must be zoomable so the user can precisely pick + the selection area even for small scans on a large scan + surface. The user also should have the option of resizing + the preview window. + + 3) We let the user/backend pick whether a preview is in color, + grayscale, lineart or what not. The only options that the + preview may (temporarily) modify are: + + - resolution (set so the preview fills the window) + - scan area options (top-left corner, bottom-right corner) + - preview option (to let the backend know we're doing a preview) + + 4) The size of the scan surface is determined based on the constraints + of the four corner coordinates. Missing constraints are replaced + by +/-INF as appropriate (-INF form top-left, +INF for bottom-right + coords). + + 5) The size of the preview window is determined based on the scan + surface size: + + If the surface area is specified in pixels and if that size + fits on the screen, we use that size. In all other cases, + we make the window of a size so that neither the width nor + the height is bigger than some fraction of the screen-size + while preserving the aspect ratio (a surface width or height + of INF implies an aspect ratio of 1). + + 6) Given the preview window size and the scan surface size, we + select the resolution so the acquired preview image just fits + in the preview window. The resulting resolution may be out + of range in which case we pick the minum/maximum if there is + a range or word-list constraint or a default value if there is + no such constraint. + + 7) Once a preview image has been acquired, we know the size of the + preview image (in pixels). An initial scale factor is chosen + so the image fits into the preview window. + + */ + +#include "../include/sane/config.h" + +#include <assert.h> +#include <math.h> +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> + +#include <sys/param.h> + +#include "gtkglue.h" +#include "preview.h" +#include "preferences.h" + +#define DBG_fatal 0 +#define DBG_error 1 +#define DBG_warning 2 +#define DBG_info 3 +#define DBG_debug 4 + +#define BACKEND_NAME preview +#include "../include/sane/sanei_debug.h" + +#ifndef PATH_MAX +# define PATH_MAX 1024 +#endif + +/* Anything bigger than 2G will do, since SANE coordinates are 32 bit + values. */ +#define INF 5.0e9 + +#define MM_PER_INCH 25.4 + +/* Cut fp conversion routines some slack: */ +#define GROSSLY_DIFFERENT(f1,f2) (fabs ((f1) - (f2)) > 1e-3) + +#ifdef __alpha__ + /* This seems to be necessary for at least some XFree86 3.1.2 + servers. It's known to be necessary for the XF86_TGA server for + Linux/Alpha. Fortunately, it's no great loss so we turn this on + by default for now. */ +# define XSERVER_WITH_BUGGY_VISUALS +#endif + +/* forward declarations */ +static void scan_start (Preview * p); +static void scan_done (Preview * p); + +static void +draw_rect (GdkWindow * win, GdkGC * gc, int coord[4]) +{ + gint x, y, w, h; + + x = coord[0]; + y = coord[1]; + w = coord[2] - x; + h = coord[3] - y; + if (w < 0) + { + x = coord[2]; + w = -w; + } + if (h < 0) + { + y = coord[3]; + h = -h; + } + gdk_draw_rectangle (win, gc, FALSE, x, y, w + 1, h + 1); +} + +static void +draw_selection (Preview * p) +{ + if (!p->gc) + /* window isn't mapped yet */ + return; + + if (p->previous_selection.active) + draw_rect (p->window->window, p->gc, p->previous_selection.coord); + + if (p->selection.active) + draw_rect (p->window->window, p->gc, p->selection.coord); + + p->previous_selection = p->selection; +} + +static void +update_selection (Preview * p) +{ + float min, max, normal, dev_selection[4]; + const SANE_Option_Descriptor *opt; + SANE_Status status; + SANE_Word val; + int i, optnum; + + p->previous_selection = p->selection; + + memcpy (dev_selection, p->surface, sizeof (dev_selection)); + for (i = 0; i < 4; ++i) + { + optnum = p->dialog->well_known.coord[i]; + if (optnum > 0) + { + opt = sane_get_option_descriptor (p->dialog->dev, optnum); + status = sane_control_option (p->dialog->dev, optnum, + SANE_ACTION_GET_VALUE, &val, 0); + if (status != SANE_STATUS_GOOD) + continue; + if (opt->type == SANE_TYPE_FIXED) + dev_selection[i] = SANE_UNFIX (val); + else + dev_selection[i] = val; + } + } + for (i = 0; i < 2; ++i) + { + min = p->surface[i]; + if (min <= -INF) + min = 0.0; + max = p->surface[i + 2]; + if (max >= INF) + max = p->preview_width; + + normal = ((i == 0) ? p->preview_width : p->preview_height) - 1; + normal /= (max - min); + p->selection.active = TRUE; + p->selection.coord[i] = ((dev_selection[i] - min) * normal) + 0.5; + p->selection.coord[i + 2] = + ((dev_selection[i + 2] - min) * normal) + 0.5; + if (p->selection.coord[i + 2] < p->selection.coord[i]) + p->selection.coord[i + 2] = p->selection.coord[i]; + } + draw_selection (p); +} + +static void +get_image_scale (Preview * p, float *xscalep, float *yscalep) +{ + float xscale, yscale; + + if (p->image_width == 0) + xscale = 1.0; + else + { + xscale = p->image_width / (float) p->preview_width; + if (p->image_height > 0 && p->preview_height * xscale < p->image_height) + xscale = p->image_height / (float) p->preview_height; + } + yscale = xscale; + + if (p->surface_unit == SANE_UNIT_PIXEL + && p->image_width <= p->preview_width + && p->image_height <= p->preview_height) + { + float swidth, sheight; + + assert (p->surface_type == SANE_TYPE_INT); + swidth = (p->surface[GSG_BR_X] - p->surface[GSG_TL_X] + 1); + sheight = (p->surface[GSG_BR_Y] - p->surface[GSG_TL_Y] + 1); + xscale = 1.0; + yscale = 1.0; + if (p->image_width > 0 && swidth < INF) + xscale = p->image_width / swidth; + if (p->image_height > 0 && sheight < INF) + yscale = p->image_height / sheight; + } + *xscalep = xscale; + *yscalep = yscale; +} + +static void +paint_image (Preview * p) +{ + float xscale, yscale, src_x, src_y; + int dst_x, dst_y, height, x, y, src_offset; + gint gwidth, gheight; + + gwidth = p->preview_width; + gheight = p->preview_height; + + get_image_scale (p, &xscale, &yscale); + + memset (p->preview_row, 0xff, 3 * gwidth); + + /* don't draw last line unless it's complete: */ + height = p->image_y; + if (p->image_x == 0 && height < p->image_height) + ++height; + + /* for now, use simple nearest-neighbor interpolation: */ + src_offset = 0; + src_x = src_y = 0.0; + for (dst_y = 0; dst_y < gheight; ++dst_y) + { + y = (int) (src_y + 0.5); + if (y >= height) + break; + src_offset = y * 3 * p->image_width; + + if (p->image_data) + for (dst_x = 0; dst_x < gwidth; ++dst_x) + { + x = (int) (src_x + 0.5); + if (x >= p->image_width) + break; + + p->preview_row[3 * dst_x + 0] = + p->image_data[src_offset + 3 * x + 0]; + p->preview_row[3 * dst_x + 1] = + p->image_data[src_offset + 3 * x + 1]; + p->preview_row[3 * dst_x + 2] = + p->image_data[src_offset + 3 * x + 2]; + src_x += xscale; + } + gtk_preview_draw_row (GTK_PREVIEW (p->window), p->preview_row, + 0, dst_y, gwidth); + src_x = 0.0; + src_y += yscale; + } +} + +static void +display_partial_image (Preview * p) +{ + paint_image (p); + + if (GTK_WIDGET_DRAWABLE (p->window)) + { + GtkPreview *preview = GTK_PREVIEW (p->window); + int src_x, src_y; + + src_x = (p->window->allocation.width - preview->buffer_width) / 2; + src_y = (p->window->allocation.height - preview->buffer_height) / 2; + gtk_preview_put (preview, p->window->window, p->window->style->black_gc, + src_x, src_y, + 0, 0, p->preview_width, p->preview_height); + } +} + +static void +display_maybe (Preview * p) +{ + time_t now; + + time (&now); + if (now > p->image_last_time_updated) + { + p->image_last_time_updated = now; + display_partial_image (p); + } +} + +static void +display_image (Preview * p) +{ + if (p->params.lines <= 0 && p->image_y < p->image_height) + { + p->image_height = p->image_y; + p->image_data = realloc (p->image_data, + 3 * p->image_width * p->image_height); + assert (p->image_data); + } + display_partial_image (p); + scan_done (p); +} + +static void +preview_area_resize (GtkWidget * widget) +{ + float min_x, max_x, min_y, max_y, xscale, yscale, f; + Preview *p; + + p = gtk_object_get_data (GTK_OBJECT (widget), "PreviewPointer"); + + p->preview_width = widget->allocation.width; + p->preview_height = widget->allocation.height; + + if (p->preview_row) + p->preview_row = realloc (p->preview_row, 3 * p->preview_width); + else + p->preview_row = malloc (3 * p->preview_width); + + /* set the ruler ranges: */ + + min_x = p->surface[GSG_TL_X]; + if (min_x <= -INF) + min_x = 0.0; + + max_x = p->surface[GSG_BR_X]; + if (max_x >= INF) + max_x = p->preview_width - 1; + + min_y = p->surface[GSG_TL_Y]; + if (min_y <= -INF) + min_y = 0.0; + + max_y = p->surface[GSG_BR_Y]; + if (max_y >= INF) + max_y = p->preview_height - 1; + + /* convert mm to inches if that's what the user wants: */ + + if (p->surface_unit == SANE_UNIT_MM) + { + double factor = 1.0 / preferences.length_unit; + min_x *= factor; + max_x *= factor; + min_y *= factor; + max_y *= factor; + } + + get_image_scale (p, &xscale, &yscale); + + if (p->surface_unit == SANE_UNIT_PIXEL) + f = 1.0 / xscale; + else if (p->image_width) + f = xscale * p->preview_width / p->image_width; + else + f = 1.0; + gtk_ruler_set_range (GTK_RULER (p->hruler), f * min_x, f * max_x, f * min_x, + /* max_size */ 20); + + if (p->surface_unit == SANE_UNIT_PIXEL) + f = 1.0 / yscale; + else if (p->image_height) + f = yscale * p->preview_height / p->image_height; + else + f = 1.0; + gtk_ruler_set_range (GTK_RULER (p->vruler), f * min_y, f * max_y, f * min_y, + /* max_size */ 20); + + paint_image (p); + update_selection (p); +} + +static void +get_bounds (const SANE_Option_Descriptor * opt, float *minp, float *maxp) +{ + float min, max; + int i; + + min = -INF; + max = INF; + switch (opt->constraint_type) + { + case SANE_CONSTRAINT_RANGE: + min = opt->constraint.range->min; + max = opt->constraint.range->max; + break; + + case SANE_CONSTRAINT_WORD_LIST: + min = INF; + max = -INF; + for (i = 1; i <= opt->constraint.word_list[0]; ++i) + { + if (opt->constraint.word_list[i] < min) + min = opt->constraint.word_list[i]; + if (opt->constraint.word_list[i] > max) + max = opt->constraint.word_list[i]; + } + break; + + default: + break; + } + if (opt->type == SANE_TYPE_FIXED) + { + if (min > -INF && min < INF) + min = SANE_UNFIX (min); + if (max > -INF && max < INF) + max = SANE_UNFIX (max); + } + *minp = min; + *maxp = max; +} + +static void +save_option (Preview * p, int option, SANE_Word * save_loc, int *valid) +{ + SANE_Status status; + + if (option <= 0) + { + *valid = 0; + return; + } + status = sane_control_option (p->dialog->dev, option, SANE_ACTION_GET_VALUE, + save_loc, 0); + *valid = (status == SANE_STATUS_GOOD); +} + +static void +restore_option (Preview * p, int option, SANE_Word saved_value, int valid) +{ + const SANE_Option_Descriptor *opt; + SANE_Status status; + SANE_Handle dev; + + if (!valid) + return; + + dev = p->dialog->dev; + status = sane_control_option (dev, option, SANE_ACTION_SET_VALUE, + &saved_value, 0); + if (status != SANE_STATUS_GOOD) + { + char buf[256]; + opt = sane_get_option_descriptor (dev, option); + snprintf (buf, sizeof (buf), "Failed restore value of option %s: %s.", + opt->name, sane_strstatus (status)); + gsg_error (buf); + } +} + +static SANE_Status +set_option_float (Preview * p, int option, float value) +{ + const SANE_Option_Descriptor *opt; + SANE_Handle dev; + SANE_Word word; + SANE_Status status; + + if (option <= 0 || value <= -INF || value >= INF) + return SANE_STATUS_INVAL; + + dev = p->dialog->dev; + opt = sane_get_option_descriptor (dev, option); + if (opt->type == SANE_TYPE_FIXED) + word = SANE_FIX (value) + 0.5; + else + word = value + 0.5; + status = sane_control_option (dev, option, SANE_ACTION_SET_VALUE, &word, 0); + if (status != SANE_STATUS_GOOD) + { + char buf[256]; + opt = sane_get_option_descriptor (dev, option); + snprintf (buf, sizeof (buf), "Failed to set option %s: %s.", + opt->name, sane_strstatus (status)); + gsg_error (buf); + return status; + } + return SANE_STATUS_GOOD; +} + +static void +set_option_bool (Preview * p, int option, SANE_Bool value) +{ + SANE_Handle dev; + SANE_Status status; + + if (option <= 0) + return; + + dev = p->dialog->dev; + status = + sane_control_option (dev, option, SANE_ACTION_SET_VALUE, &value, 0); + if (status != SANE_STATUS_GOOD) + { + DBG (DBG_fatal, "set_option_bool: sane_control_option failed: %s\n", + sane_strstatus (status)); + return; + } +} + +static int +increment_image_y (Preview * p) +{ + size_t extra_size, offset; + char buf[256]; + + p->image_x = 0; + ++p->image_y; + if (p->params.lines <= 0 && p->image_y >= p->image_height) + { + offset = 3 * p->image_width * p->image_height; + extra_size = 3 * 32 * p->image_width; + p->image_height += 32; + p->image_data = realloc (p->image_data, offset + extra_size); + if (!p->image_data) + { + snprintf (buf, sizeof (buf), + "Failed to reallocate image memory: %s.", + strerror (errno)); + gsg_error (buf); + scan_done (p); + return -1; + } + memset (p->image_data + offset, 0xff, extra_size); + } + return 0; +} + +static void +input_available (gpointer data, gint source, GdkInputCondition cond) +{ + SANE_Status status; + Preview *p = data; + u_char buf[8192]; + SANE_Handle dev; + SANE_Int len; + int i, j; + + DBG_INIT (); + + DBG (DBG_debug, "input_available: enter\n"); + + dev = p->dialog->dev; + while (1) + { + status = sane_read (dev, buf, sizeof (buf), &len); + if (status != SANE_STATUS_GOOD) + { + if (status == SANE_STATUS_EOF) + { + if (p->params.last_frame) + display_image (p); + else + { + if (p->input_tag < 0) + { + display_maybe (p); + while (gtk_events_pending ()) + gtk_main_iteration (); + } + else + { + gdk_input_remove (p->input_tag); + p->input_tag = -1; + } + scan_start (p); + break; + } + } + else + { + snprintf (buf, sizeof (buf), "Error during read: %s.", + sane_strstatus (status)); + gsg_error (buf); + } + scan_done (p); + return; + } + if (!len) /* out of data for now */ + { + if (p->input_tag < 0) + { + display_maybe (p); + while (gtk_events_pending ()) + gtk_main_iteration (); + continue; + } + else + break; + } + + switch (p->params.format) + { + case SANE_FRAME_RGB: + switch (p->params.depth) + { + case 1: + for (i = 0; i < len; ++i) + { + u_char mask = buf[i]; + + for (j = 7; j >= 0; --j) + { + u_char gl = (mask & (1 << j)) ? 0xff : 0x00; + + p->image_data[p->image_offset] = gl; + if (j != 0) + p->image_offset += 3; + else + { + if ((p->image_offset % 3) != 2) + p->image_offset -= 20; + else + p->image_offset++; + } + if (p->image_offset % 3 == 0) + { + if (++p->image_x >= p->image_width) + { + if (increment_image_y (p) < 0) + return; + } + } + } + } + break; + + case 8: + for (i = 0; i < len; ++i) + { + p->image_data[p->image_offset++] = buf[i]; + if (p->image_offset % 3 == 0) + { + if (++p->image_x >= p->image_width + && increment_image_y (p) < 0) + return; + } + } + break; + case 16: + for (i = 0; i < len; ++i) + { + guint16 value = buf[i]; + if ((i % 2) == 1) + p->image_data[p->image_offset++] = *(guint8 *) (&value); + if (p->image_offset % 6 == 0) + { + if (++p->image_x >= p->image_width + && increment_image_y (p) < 0) + return; + } + } + break; + default: + goto bad_depth; + } + break; + + case SANE_FRAME_GRAY: + switch (p->params.depth) + { + case 1: + for (i = 0; i < len; ++i) + { + u_char mask = buf[i]; + + for (j = 7; j >= 0; --j) + { + u_char gl = (mask & (1 << j)) ? 0x00 : 0xff; + p->image_data[p->image_offset++] = gl; + p->image_data[p->image_offset++] = gl; + p->image_data[p->image_offset++] = gl; + if (++p->image_x >= p->image_width) + { + if (increment_image_y (p) < 0) + return; + break; /* skip padding bits */ + } + } + } + break; + + case 8: + for (i = 0; i < len; ++i) + { + u_char gl = buf[i]; + p->image_data[p->image_offset++] = gl; + p->image_data[p->image_offset++] = gl; + p->image_data[p->image_offset++] = gl; + if (++p->image_x >= p->image_width + && increment_image_y (p) < 0) + return; + } + break; + case 16: + for (i = 0; i < len; ++i) + { + guint16 value = buf[i]; + if ((i % 2) == 1) + { + p->image_data[p->image_offset++] = *(guint8 *) (&value); + p->image_data[p->image_offset++] = *(guint8 *) (&value); + p->image_data[p->image_offset++] = *(guint8 *) (&value); + } + if (p->image_offset % 2 == 0) + { + if (++p->image_x >= p->image_width + && increment_image_y (p) < 0) + return; + } + } + break; + + default: + goto bad_depth; + } + break; + + case SANE_FRAME_RED: + case SANE_FRAME_GREEN: + case SANE_FRAME_BLUE: + switch (p->params.depth) + { + case 1: + for (i = 0; i < len; ++i) + { + u_char mask = buf[i]; + + for (j = 7; j >= 0; --j) + { + u_char gl = (mask & (1 << j)) ? 0xff : 0x00; + p->image_data[p->image_offset] = gl; + p->image_offset += 3; + if (++p->image_x >= p->image_width + && increment_image_y (p) < 0) + return; + } + } + break; + + case 8: + for (i = 0; i < len; ++i) + { + p->image_data[p->image_offset] = buf[i]; + p->image_offset += 3; + if (++p->image_x >= p->image_width + && increment_image_y (p) < 0) + return; + } + break; + + case 16: + for (i = 0; i < len; ++i) + { + guint16 value = buf[i]; + if ((i % 2) == 1) + { + p->image_data[p->image_offset] = *(guint8 *) (&value); + p->image_offset += 3; + } + if (p->image_offset % 2 == 0) + { + if (++p->image_x >= p->image_width + && increment_image_y (p) < 0) + return; + } + } + break; + + default: + goto bad_depth; + } + break; + + default: + fprintf (stderr, "preview.input_available: bad frame format %d\n", + p->params.format); + scan_done (p); + return; + } + if (p->input_tag < 0) + { + display_maybe (p); + while (gtk_events_pending ()) + gtk_main_iteration (); + } + } + display_maybe (p); + return; + +bad_depth: + snprintf (buf, sizeof (buf), "Preview cannot handle depth %d.", + p->params.depth); + gsg_error (buf); + scan_done (p); + return; +} + +static void +scan_done (Preview * p) +{ + int i; + + p->scanning = FALSE; + if (p->input_tag >= 0) + { + gdk_input_remove (p->input_tag); + p->input_tag = -1; + } + sane_cancel (p->dialog->dev); + + restore_option (p, p->dialog->well_known.dpi, + p->saved_dpi, p->saved_dpi_valid); + for (i = 0; i < 4; ++i) + restore_option (p, p->dialog->well_known.coord[i], + p->saved_coord[i], p->saved_coord_valid[i]); + set_option_bool (p, p->dialog->well_known.preview, SANE_FALSE); + + gtk_widget_set_sensitive (p->cancel, FALSE); + gtk_widget_set_sensitive (p->preview, TRUE); + gsg_set_sensitivity (p->dialog, TRUE); + gtk_widget_set_sensitive (p->dialog->window->parent->parent->parent, TRUE); +} + +static void +scan_start (Preview * p) +{ + SANE_Handle dev = p->dialog->dev; + SANE_Status status; + char buf[256]; + int fd, y; + + gtk_widget_set_sensitive (p->cancel, TRUE); + gtk_widget_set_sensitive (p->preview, FALSE); + gsg_set_sensitivity (p->dialog, FALSE); + gtk_widget_set_sensitive (p->dialog->window->parent->parent->parent, FALSE); + + /* clear old preview: */ + memset (p->preview_row, 0xff, 3 * p->preview_width); + for (y = 0; y < p->preview_height; ++y) + gtk_preview_draw_row (GTK_PREVIEW (p->window), p->preview_row, + 0, y, p->preview_width); + + if (p->input_tag >= 0) + { + gdk_input_remove (p->input_tag); + p->input_tag = -1; + } + + gsg_sync (p->dialog); + + status = sane_start (dev); + if (status != SANE_STATUS_GOOD) + { + snprintf (buf, sizeof (buf), + "Failed to start scanner: %s.", sane_strstatus (status)); + gsg_error (buf); + scan_done (p); + return; + } + + status = sane_get_parameters (dev, &p->params); + if (status != SANE_STATUS_GOOD) + { + snprintf (buf, sizeof (buf), + "Failed to obtain parameters: %s.", sane_strstatus (status)); + gsg_error (buf); + scan_done (p); + return; + } + + if ((p->params.format >= SANE_FRAME_RGB && + p->params.format <= SANE_FRAME_BLUE) && + p->params.depth == 1 && p->params.pixels_per_line % 8 != 0) + { + snprintf (buf, sizeof (buf), + "Can't handle unaligned 1 bit RGB or three-pass mode."); + gsg_error (buf); + scan_done (p); + return; + } + + p->image_offset = p->image_x = p->image_y = 0; + + if (p->params.format >= SANE_FRAME_RED + && p->params.format <= SANE_FRAME_BLUE) + p->image_offset = p->params.format - SANE_FRAME_RED; + + if (!p->image_data || p->params.pixels_per_line != p->image_width + || (p->params.lines >= 0 && p->params.lines != p->image_height)) + { + /* image size changed */ + if (p->image_data) + free (p->image_data); + + p->image_width = p->params.pixels_per_line; + p->image_height = p->params.lines; + if (p->image_height < 0) + p->image_height = 32; /* may have to adjust as we go... */ + + p->image_data = malloc (p->image_width * p->image_height * 3); + if (!p->image_data) + { + snprintf (buf, sizeof (buf), + "Failed to allocate image memory: %s.", strerror (errno)); + gsg_error (buf); + scan_done (p); + return; + } + memset (p->image_data, 0xff, 3 * p->image_width * p->image_height); + } + + if (p->selection.active) + { + p->previous_selection = p->selection; + p->selection.active = FALSE; + draw_selection (p); + } + p->scanning = TRUE; + + if (sane_set_io_mode (dev, SANE_TRUE) == SANE_STATUS_GOOD + && sane_get_select_fd (dev, &fd) == SANE_STATUS_GOOD) + p->input_tag = + gdk_input_add (fd, GDK_INPUT_READ | GDK_INPUT_EXCEPTION, + input_available, p); + else + input_available (p, -1, GDK_INPUT_READ); +} + +static void +establish_selection (Preview * p) +{ + float min, max, normal, dev_selection[4]; + int i; + + memcpy (dev_selection, p->surface, sizeof (dev_selection)); + if (p->selection.active) + for (i = 0; i < 2; ++i) + { + min = p->surface[i]; + if (min <= -INF) + min = 0.0; + max = p->surface[i + 2]; + if (max >= INF) + max = p->preview_width; + + normal = + 1.0 / (((i == 0) ? p->preview_width : p->preview_height) - 1); + normal *= (max - min); + dev_selection[i] = p->selection.coord[i] * normal + min; + dev_selection[i + 2] = p->selection.coord[i + 2] * normal + min; + } + for (i = 0; i < 4; ++i) + set_option_float (p, p->dialog->well_known.coord[i], dev_selection[i]); + gsg_update_scan_window (p->dialog); + if (p->dialog->param_change_callback) + (*p->dialog->param_change_callback) (p->dialog, + p->dialog->param_change_arg); +} + +static int +make_preview_image_path (Preview * p, size_t filename_size, char *filename) +{ + return gsg_make_path (filename_size, filename, 0, "preview-", + p->dialog->dev_name, ".ppm"); +} + +static void +restore_preview_image (Preview * p) +{ + u_int psurface_type, psurface_unit; + char filename[PATH_MAX]; + int width, height; + float psurface[4]; + size_t nread; + FILE *in; + + /* See whether there is a saved preview and load it if present: */ + + if (make_preview_image_path (p, sizeof (filename), filename) < 0) + return; + + in = fopen (filename, "r"); + if (!in) + return; + + /* Be careful about consuming too many bytes after the final newline + (e.g., consider an image whose first image byte is 13 (`\r'). */ + if (fscanf (in, "P6\n# surface: %g %g %g %g %u %u\n%d %d\n255%*[\n]", + psurface + 0, psurface + 1, psurface + 2, psurface + 3, + &psurface_type, &psurface_unit, &width, &height) != 8) + return; + + if (GROSSLY_DIFFERENT (psurface[0], p->surface[0]) + || GROSSLY_DIFFERENT (psurface[1], p->surface[1]) + || GROSSLY_DIFFERENT (psurface[2], p->surface[2]) + || GROSSLY_DIFFERENT (psurface[3], p->surface[3]) + || psurface_type != p->surface_type || psurface_unit != p->surface_unit) + /* ignore preview image that was acquired for/with a different surface */ + return; + + p->image_width = width; + p->image_height = height; + if ((width == 0) || (height == 0)) + return; + p->image_data = malloc (3 * width * height); + if (!p->image_data) + return; + + nread = fread (p->image_data, 3, width * height, in); + + p->image_y = nread / width; + p->image_x = nread % width; +} + +/* This is executed _after_ the gtkpreview's expose routine. */ +static gint +expose_handler (GtkWidget * window, GdkEvent * event, gpointer data) +{ + Preview *p = data; + + p->selection.active = FALSE; + update_selection (p); + return FALSE; +} + +static gint +event_handler (GtkWidget * window, GdkEvent * event, gpointer data) +{ + Preview *p = data; + int i, tmp; + + if (event->type == GDK_EXPOSE) + { + if (!p->gc) + { + p->gc = gdk_gc_new (p->window->window); + gdk_gc_set_function (p->gc, GDK_INVERT); + gdk_gc_set_line_attributes (p->gc, 1, GDK_LINE_ON_OFF_DASH, + GDK_CAP_BUTT, GDK_JOIN_MITER); + paint_image (p); + } + else + { + p->selection.active = FALSE; + draw_selection (p); + } + } + else if (!p->scanning) + switch (event->type) + { + case GDK_UNMAP: + case GDK_MAP: + break; + + case GDK_BUTTON_PRESS: + p->selection.coord[0] = event->button.x; + p->selection.coord[1] = event->button.y; + p->selection_drag = TRUE; + break; + + case GDK_BUTTON_RELEASE: + if (!p->selection_drag) + break; + p->selection_drag = FALSE; + + p->selection.coord[2] = event->button.x; + p->selection.coord[3] = event->button.y; + p->selection.active = + (p->selection.coord[0] != p->selection.coord[2] + || p->selection.coord[1] != p->selection.coord[3]); + + if (p->selection.active) + { + for (i = 0; i < 2; i += 1) + if (p->selection.coord[i] > p->selection.coord[i + 2]) + { + tmp = p->selection.coord[i]; + p->selection.coord[i] = p->selection.coord[i + 2]; + p->selection.coord[i + 2] = tmp; + } + if (p->selection.coord[0] < 0) + p->selection.coord[0] = 0; + if (p->selection.coord[1] < 0) + p->selection.coord[1] = 0; + if (p->selection.coord[2] >= p->preview_width) + p->selection.coord[2] = p->preview_width - 1; + if (p->selection.coord[3] >= p->preview_height) + p->selection.coord[3] = p->preview_height - 1; + } + draw_selection (p); + establish_selection (p); + break; + + case GDK_MOTION_NOTIFY: + if (p->selection_drag) + { + p->selection.active = TRUE; + p->selection.coord[2] = event->motion.x; + p->selection.coord[3] = event->motion.y; + draw_selection (p); + } + break; + + default: +#if 0 + fprintf (stderr, "event_handler: unhandled event type %d\n", + event->type); +#endif + break; + } + return FALSE; +} + +static void +start_button_clicked (GtkWidget * widget, gpointer data) +{ + preview_scan (data); +} + +static void +cancel_button_clicked (GtkWidget * widget, gpointer data) +{ + scan_done (data); +} + +static void +top_destroyed (GtkWidget * widget, gpointer call_data) +{ + Preview *p = call_data; + + p->top = NULL; +} + +Preview * +preview_new (GSGDialog * dialog) +{ + static int first_time = 1; + GtkWidget *table, *frame; + GtkSignalFunc signal_func; + GtkWidgetClass *class; + GtkBox *vbox, *hbox; + Preview *p; + + p = malloc (sizeof (*p)); + if (!p) + return 0; + memset (p, 0, sizeof (*p)); + + p->dialog = dialog; + p->input_tag = -1; + + if (first_time) + { + first_time = 0; + gtk_preview_set_gamma (preferences.preview_gamma); + gtk_preview_set_install_cmap (preferences.preview_own_cmap); + } + +#ifndef XSERVER_WITH_BUGGY_VISUALS + gtk_widget_push_visual (gtk_preview_get_visual ()); +#endif + gtk_widget_push_colormap (gtk_preview_get_cmap ()); + + p->top = gtk_dialog_new (); + gtk_signal_connect (GTK_OBJECT (p->top), "destroy", + GTK_SIGNAL_FUNC (top_destroyed), p); + gtk_window_set_title (GTK_WINDOW (p->top), "xscanimage preview"); + vbox = GTK_BOX (GTK_DIALOG (p->top)->vbox); + hbox = GTK_BOX (GTK_DIALOG (p->top)->action_area); + + /* construct the preview area (table with sliders & preview window) */ + table = gtk_table_new (2, 2, /* homogeneous */ FALSE); + gtk_table_set_col_spacing (GTK_TABLE (table), 0, 1); + gtk_table_set_row_spacing (GTK_TABLE (table), 0, 1); + gtk_container_border_width (GTK_CONTAINER (table), 2); + gtk_box_pack_start (vbox, table, /* expand */ TRUE, /* fill */ TRUE, + /* padding */ 0); + + /* the empty box in the top-left corner */ + frame = gtk_frame_new ( /* label */ 0); + gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_OUT); + gtk_table_attach (GTK_TABLE (table), frame, 0, 1, 0, 1, + GTK_FILL, GTK_FILL, 0, 0); + + /* the horizontal ruler */ + p->hruler = gtk_hruler_new (); + gtk_table_attach (GTK_TABLE (table), p->hruler, 1, 2, 0, 1, + GTK_FILL, 0, 0, 0); + + /* the vertical ruler */ + p->vruler = gtk_vruler_new (); + gtk_table_attach (GTK_TABLE (table), p->vruler, 0, 1, 1, 2, 0, + GTK_FILL, 0, 0); + + /* the preview area */ + + p->window = gtk_preview_new (GTK_PREVIEW_COLOR); + gtk_preview_set_expand (GTK_PREVIEW (p->window), TRUE); + gtk_widget_set_events (p->window, + GDK_EXPOSURE_MASK | + GDK_POINTER_MOTION_MASK | + GDK_POINTER_MOTION_HINT_MASK | + GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK); + gtk_signal_connect (GTK_OBJECT (p->window), "event", + (GtkSignalFunc) event_handler, p); + gtk_signal_connect_after (GTK_OBJECT (p->window), "expose_event", + (GtkSignalFunc) expose_handler, p); + gtk_signal_connect_after (GTK_OBJECT (p->window), "size_allocate", + (GtkSignalFunc) preview_area_resize, 0); + gtk_object_set_data (GTK_OBJECT (p->window), "PreviewPointer", p); + + /* Connect the motion-notify events of the preview area with the + rulers. Nifty stuff! */ + +#if GTK_MAJOR_VERSION == 2 + class = GTK_WIDGET_CLASS (GTK_OBJECT_GET_CLASS (GTK_OBJECT (p->hruler))); +#else + class = GTK_WIDGET_CLASS (GTK_OBJECT (p->hruler)->klass); +#endif /* GTK_MAJOR_VERSION == 2 */ + signal_func = (GtkSignalFunc) class->motion_notify_event; + gtk_signal_connect_object (GTK_OBJECT (p->window), "motion_notify_event", + signal_func, GTK_OBJECT (p->hruler)); + +#if GTK_MAJOR_VERSION == 2 + class = GTK_WIDGET_CLASS (GTK_OBJECT_GET_CLASS (GTK_OBJECT (p->vruler))); +#else + class = GTK_WIDGET_CLASS (GTK_OBJECT (p->vruler)->klass); +#endif /* GTK_MAJOR_VERSION == 2 */ + signal_func = (GtkSignalFunc) class->motion_notify_event; + gtk_signal_connect_object (GTK_OBJECT (p->window), "motion_notify_event", + signal_func, GTK_OBJECT (p->vruler)); + + p->viewport = gtk_frame_new ( /* label */ 0); + gtk_frame_set_shadow_type (GTK_FRAME (p->viewport), GTK_SHADOW_IN); + gtk_container_add (GTK_CONTAINER (p->viewport), p->window); + + gtk_table_attach (GTK_TABLE (table), p->viewport, 1, 2, 1, 2, + GTK_FILL | GTK_EXPAND | GTK_SHRINK, + GTK_FILL | GTK_EXPAND | GTK_SHRINK, 0, 0); + + preview_update (p); + + /* fill in action area: */ + + /* Preview button */ + p->preview = gtk_button_new_with_label ("Acquire Preview"); + gtk_signal_connect (GTK_OBJECT (p->preview), "clicked", + (GtkSignalFunc) start_button_clicked, p); + gtk_box_pack_start (GTK_BOX (hbox), p->preview, TRUE, TRUE, 0); + gtk_widget_show (p->preview); + + /* Cancel button */ + p->cancel = gtk_button_new_with_label ("Cancel Preview"); + gtk_signal_connect (GTK_OBJECT (p->cancel), "clicked", + (GtkSignalFunc) cancel_button_clicked, p); + gtk_box_pack_start (GTK_BOX (hbox), p->cancel, TRUE, TRUE, 0); + gtk_widget_set_sensitive (p->cancel, FALSE); + + gtk_widget_show (p->cancel); + gtk_widget_show (p->viewport); + gtk_widget_show (p->window); + gtk_widget_show (p->hruler); + gtk_widget_show (p->vruler); + gtk_widget_show (frame); + gtk_widget_show (table); + gtk_widget_show (p->top); + + gtk_widget_pop_colormap (); +#ifndef XSERVER_WITH_BUGGY_VISUALS + gtk_widget_pop_visual (); +#endif + return p; +} + +void +preview_update (Preview * p) +{ + float val, width, height, max_width, max_height; + const SANE_Option_Descriptor *opt; + int i, surface_changed; + SANE_Value_Type type; + SANE_Unit unit; + float min, max; + + surface_changed = 0; + unit = SANE_UNIT_PIXEL; + type = SANE_TYPE_INT; + for (i = 0; i < 4; ++i) + { + val = (i & 2) ? INF : -INF; + if (p->dialog->well_known.coord[i] > 0) + { + opt = sane_get_option_descriptor (p->dialog->dev, + p->dialog->well_known.coord[i]); + assert (opt->unit == SANE_UNIT_PIXEL || opt->unit == SANE_UNIT_MM); + unit = opt->unit; + type = opt->type; + + get_bounds (opt, &min, &max); + if (i & 2) + val = max; + else + val = min; + } + if (p->surface[i] != val) + { + surface_changed = 1; + p->surface[i] = val; + } + } + if (p->surface_unit != unit) + { + surface_changed = 1; + p->surface_unit = unit; + } + if (p->surface_type != type) + { + surface_changed = 1; + p->surface_type = type; + } + if (surface_changed && p->image_data) + { + free (p->image_data); + p->image_data = 0; + p->image_width = 0; + p->image_height = 0; + } + + /* guess the initial preview window size: */ + + width = p->surface[GSG_BR_X] - p->surface[GSG_TL_X]; + height = p->surface[GSG_BR_Y] - p->surface[GSG_TL_Y]; + if (p->surface_type == SANE_TYPE_INT) + { + width += 1.0; + height += 1.0; + } + else + { + width += SANE_UNFIX (1.0); + height += SANE_UNFIX (1.0); + } + + assert (width > 0.0 && height > 0.0); + + if (width >= INF || height >= INF) + p->aspect = 1.0; + else + p->aspect = width / height; + + if (surface_changed) + { + max_width = 0.5 * gdk_screen_width (); + max_height = 0.5 * gdk_screen_height (); + } + else + { + max_width = p->window->allocation.width; + max_height = p->window->allocation.height; + } + + if (p->surface_unit != SANE_UNIT_PIXEL) + { + width = max_width; + height = width / p->aspect; + + if (height > max_height) + { + height = max_height; + width = height * p->aspect; + } + } + else + { + if (width > max_width) + width = max_width; + + if (height > max_height) + height = max_height; + } + + /* re-adjust so we maintain aspect without exceeding max size: */ + if (width / height != p->aspect) + { + if (p->aspect > 1.0) + height = width / p->aspect; + else + width = height * p->aspect; + } + + p->preview_width = width + 0.5; + p->preview_height = height + 0.5; + if (surface_changed) + { + gtk_widget_set_usize (GTK_WIDGET (p->window), + p->preview_width, p->preview_height); + if (GTK_WIDGET_DRAWABLE (p->window)) + preview_area_resize (p->window); + + if (preferences.preserve_preview) + restore_preview_image (p); + } + update_selection (p); +} + +void +preview_scan (Preview * p) +{ + float min, max, swidth, sheight, width, height, dpi = 0; + const SANE_Option_Descriptor *opt; + gint gwidth, gheight; + int i; + SANE_Status status; + + + save_option (p, p->dialog->well_known.dpi, + &p->saved_dpi, &p->saved_dpi_valid); + for (i = 0; i < 4; ++i) + save_option (p, p->dialog->well_known.coord[i], + &p->saved_coord[i], p->saved_coord_valid + i); + + /* determine dpi, if necessary: */ + + if (p->dialog->well_known.dpi > 0) + { + opt = sane_get_option_descriptor (p->dialog->dev, + p->dialog->well_known.dpi); + + gwidth = p->preview_width; + gheight = p->preview_height; + + height = gheight; + width = height * p->aspect; + if (width > gwidth) + { + width = gwidth; + height = width / p->aspect; + } + + swidth = (p->surface[GSG_BR_X] - p->surface[GSG_TL_X]); + if (swidth < INF) + dpi = MM_PER_INCH * width / swidth; + else + { + sheight = (p->surface[GSG_BR_Y] - p->surface[GSG_TL_Y]); + if (sheight < INF) + dpi = MM_PER_INCH * height / sheight; + else + dpi = 18.0; + } + get_bounds (opt, &min, &max); + if (dpi < min) + dpi = min; + if (dpi > max) + dpi = max; + status = set_option_float (p, p->dialog->well_known.dpi, dpi); + if (status != SANE_STATUS_GOOD) + return; + } + + /* set the scan window (necessary since backends may default to + non-maximum size): */ + for (i = 0; i < 4; ++i) + set_option_float (p, p->dialog->well_known.coord[i], p->surface[i]); + set_option_bool (p, p->dialog->well_known.preview, SANE_TRUE); + + /* OK, all set to go */ + scan_start (p); +} + +void +preview_destroy (Preview * p) +{ + char filename[PATH_MAX]; + FILE *out; + + if (p->scanning) + scan_done (p); /* don't save partial window */ + else if (preferences.preserve_preview && p->image_data + && make_preview_image_path (p, sizeof (filename), filename) >= 0) + { + /* save preview image */ + out = fopen (filename, "w"); + if (out) + { + /* always save it as a PPM image: */ + fprintf (out, "P6\n# surface: %g %g %g %g %u %u\n%d %d\n255\n", + p->surface[0], p->surface[1], p->surface[2], p->surface[3], + p->surface_type, p->surface_unit, + p->image_width, p->image_height); + fwrite (p->image_data, 3, p->image_width * p->image_height, out); + fclose (out); + } + } + if (p->image_data) + free (p->image_data); + if (p->preview_row) + free (p->preview_row); + if (p->gc) + gdk_gc_destroy (p->gc); + if (p->top) + gtk_widget_destroy (p->top); + free (p); +} diff --git a/src/preview.h b/src/preview.h new file mode 100644 index 0000000..5b0b585 --- /dev/null +++ b/src/preview.h @@ -0,0 +1,87 @@ +/* sane - Scanner Access Now Easy. + Copyright (C) 1997 David Mosberger-Tang + This file is part of the SANE package. + + SANE is free software; you can redistribute it and/or modify it under + the terms of the GNU General Public License as published by the Free + Software Foundation; either version 2 of the License, or (at your + option) any later version. + + SANE is distributed in the hope that it will be useful, but WITHOUT + ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + for more details. + + You should have received a copy of the GNU General Public License + along with sane; see the file COPYING. If not, write to the Free + Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ +#ifndef preview_h +#define preview_h + +#include <sys/types.h> + +#include "../include/sane/config.h" +#include <sane/sane.h> + +typedef struct + { + GSGDialog *dialog; /* the dialog for this preview */ + + SANE_Value_Type surface_type; + SANE_Unit surface_unit; + float surface[4]; /* the corners of the scan surface (device coords) */ + float aspect; /* the aspect ratio of the scan surface */ + + int saved_dpi_valid; + SANE_Word saved_dpi; + int saved_coord_valid[4]; + SANE_Word saved_coord[4]; + + /* desired/user-selected preview-window size: */ + int preview_width; + int preview_height; + u_char *preview_row; + + int scanning; + time_t image_last_time_updated; + gint input_tag; + SANE_Parameters params; + int image_offset; + int image_x; + int image_y; + int image_width; + int image_height; + u_char *image_data; /* 3 * image_width * image_height bytes */ + + GdkGC *gc; + int selection_drag; + struct + { + int active; + int coord[4]; + } + selection, previous_selection; + + GtkWidget *top; /* top-level widget */ + GtkWidget *hruler; + GtkWidget *vruler; + GtkWidget *viewport; + GtkWidget *window; /* the preview window */ + GtkWidget *cancel; /* the cancel button */ + GtkWidget *preview; /* the preview button */ + } +Preview; + +/* Create a new preview based on the info in DIALOG. */ +extern Preview *preview_new (GSGDialog *dialog); + +/* Some of the parameters may have changed---update the preview. */ +extern void preview_update (Preview *p); + +/* Acquire a preview image and display it. */ +extern void preview_scan (Preview *p); + +/* Destroy a preview. */ +extern void preview_destroy (Preview *p); + +#endif /* preview_h */ diff --git a/src/progress.c b/src/progress.c new file mode 100644 index 0000000..4957295 --- /dev/null +++ b/src/progress.c @@ -0,0 +1,93 @@ +/* The GIMP -- an image manipulation program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * Hacked (C) 1996 Tristan Tarrant + * + * 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. + */ +#include "../include/sane/config.h" + +#include <stdlib.h> +#include <string.h> +#include <gtk/gtk.h> +#include "progress.h" + +static const int progress_x = 5; +static const int progress_y = 5; + +void +progress_cancel (GtkWidget * widget, gpointer data) +{ + Progress_t *p = (Progress_t *) data; + + (*p->callback) (); +} + + +Progress_t * +progress_new (char *title, char *text, + GtkSignalFunc callback, gpointer callback_data) +{ + GtkWidget *button, *label; + GtkBox *vbox, *hbox; + Progress_t *p; + + p = (Progress_t *) malloc (sizeof (Progress_t)); + p->callback = callback; + + p->shell = gtk_dialog_new (); + gtk_widget_set_uposition (p->shell, progress_x, progress_y); + gtk_window_set_title (GTK_WINDOW (p->shell), title); + vbox = GTK_BOX (GTK_DIALOG (p->shell)->vbox); + hbox = GTK_BOX (GTK_DIALOG (p->shell)->action_area); + + gtk_container_border_width (GTK_CONTAINER (vbox), 7); + + label = gtk_label_new (text); + gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5); + gtk_box_pack_start (vbox, label, FALSE, TRUE, 0); + + p->pbar = gtk_progress_bar_new (); + gtk_widget_set_usize (p->pbar, 200, 20); + gtk_box_pack_start (vbox, p->pbar, TRUE, TRUE, 0); + + button = gtk_toggle_button_new_with_label ("Cancel"); + gtk_signal_connect (GTK_OBJECT (button), "clicked", + (GtkSignalFunc) progress_cancel, p); + gtk_box_pack_start (hbox, button, TRUE, TRUE, 0); + + gtk_widget_show (label); + gtk_widget_show (p->pbar); + gtk_widget_show (button); + gtk_widget_show (GTK_WIDGET (p->shell)); + gtk_progress_bar_update (GTK_PROGRESS_BAR (p->pbar), 0); + return p; +} + +void +progress_free (Progress_t * p) +{ + if (p) + { + gtk_widget_destroy (p->shell); + free (p); + } +} + +void +progress_update (Progress_t * p, gfloat newval) +{ + if (p) + gtk_progress_bar_update (GTK_PROGRESS_BAR (p->pbar), newval); +} diff --git a/src/progress.h b/src/progress.h new file mode 100644 index 0000000..13a924e --- /dev/null +++ b/src/progress.h @@ -0,0 +1,34 @@ +/* The GIMP -- an image manipulation program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ +#ifndef progress_h +#define progress_h + +typedef struct Progress_t + { + GtkSignalFunc callback; + gpointer callback_data; + GtkWidget *shell; + GtkWidget *pbar; + } +Progress_t; + +extern Progress_t *progress_new (char *, char *, GtkSignalFunc, void *); +extern void progress_free (Progress_t *); +extern void progress_update (Progress_t *, gfloat); + +#endif /* progress_h */ diff --git a/src/sane-style.rc b/src/sane-style.rc new file mode 100644 index 0000000..f85259c --- /dev/null +++ b/src/sane-style.rc @@ -0,0 +1,21 @@ +# style <name> [= <name>] +# { +# <option> +# } +# +# widget <widget_set> style <style_name> +# widget_class <widget_class_set> style <style_name> +# accelerator <widget_name> <accelerator> + +style "progressbar" +{ + bg[PRELIGHT] = { 22500, 53280, 22500 } # green +} + +style "curve" +{ + fg[NORMAL] = { 58000, 0, 0 } # red +} + +widget "*GtkCurve" style "curve" +widget "*GtkProgressBar" style "progressbar" diff --git a/src/scanadf.c b/src/scanadf.c new file mode 100644 index 0000000..d4ff566 --- /dev/null +++ b/src/scanadf.c @@ -0,0 +1,1590 @@ +/* saneadf - a SANE front end for document scanning + based on + bnhscan by tummy.com and + scanimage by Andreas Beck and David Mosberger + + Copyright (C) 1999 Tom Martone + + 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 <assert.h> +#include <getopt.h> +#include <signal.h> +#include <unistd.h> +#include <errno.h> + +#include <limits.h> +#include <ctype.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/wait.h> + +#include "sane/sane.h" +#include "sane/sanei.h" +#include "sane/saneopts.h" + +#ifndef PATH_MAX +#define PATH_MAX 1024 +#endif + +#ifndef sane_isbasicframe +#define SANE_FRAME_TEXT 10 +#define SANE_FRAME_JPEG 11 +#define SANE_FRAME_G31D 12 +#define SANE_FRAME_G32D 13 +#define SANE_FRAME_G42D 14 +#define sane_strframe(f) ( (f) == SANE_FRAME_GRAY ? "gray" : \ + (f) == SANE_FRAME_RGB ? "RGB" : \ + (f) == SANE_FRAME_RED ? "red" : \ + (f) == SANE_FRAME_GREEN ? "green" : \ + (f) == SANE_FRAME_BLUE ? "blue" : \ + (f) == SANE_FRAME_TEXT ? "text" : \ + (f) == SANE_FRAME_JPEG ? "jpeg" : \ + (f) == SANE_FRAME_G31D ? "g31d" : \ + (f) == SANE_FRAME_G32D ? "g32d" : \ + (f) == SANE_FRAME_G42D ? "g42d" : \ + "unknown" ) + +#define sane_isbasicframe(f) ( (f) == SANE_FRAME_GRAY || \ + (f) == SANE_FRAME_RGB || \ + (f) == SANE_FRAME_RED || \ + (f) == SANE_FRAME_GREEN || \ + (f) == SANE_FRAME_BLUE ) + +#endif + +#ifndef HAVE_ATEXIT +# define atexit(func) on_exit(func, 0) /* works for SunOS, at least */ +#endif + +typedef struct + { + u_char *data; + int Bpp; /* bytes/pixel */ + int width; + int height; + int x; + int y; + } +Image; + +static struct option basic_options[] = +{ + {"device-name", required_argument, NULL, 'd'}, + {"list-devices", no_argument, NULL, 'L'}, + {"help", no_argument, NULL, 'h'}, + {"verbose", no_argument, NULL, 'v'}, + {"version", no_argument, NULL, 'V'}, + {"no-overwrite", no_argument, NULL, 'N'}, + + { "output-file", required_argument, 0, 'o' }, + { "start-count", required_argument, 0, 's' }, + { "end-count", required_argument, 0, 'e' }, + { "scan-script", required_argument, 0, 'S' }, + { "script-wait", no_argument, 0, 128 }, + { "raw", no_argument, 0, 'r' }, + {0, } +}; + +#define BASE_OPTSTRING "d:hLvVNTo:s:e:S:r" +#define STRIP_HEIGHT 256 /* # lines we increment image height */ + +static struct option * all_options; +static int option_number_len; +static int * option_number; +static SANE_Handle device; +static int verbose; +static int help; +static const char * prog_name; +static SANE_Option_Descriptor window_option[2]; +static int window[4]; +static int resolution_opt = -1, x_resolution_opt = -1, y_resolution_opt = -1; +static SANE_Word window_val[2]; +static int window_val_user[2]; /* is width/height user-specified? */ + +static const char usage[] = "Usage: %s [OPTION]...\n\ +\n\ +Start image acquisition on a scanner device and write image data to\n\ +output files.\n\ +\n\ + [ -d | --device-name <device> ] use a given scanner device.\n\ + [ -h | --help ] display this help message and exit.\n\ + [ -L | --list-devices ] show available scanner devices.\n\ + [ -v | --verbose ] give even more status messages.\n\ + [ -V | --version ] print version information.\n\ + [ -N | --no-overwrite ] don't overwrite existing files.\n\ +\n\ + [ -o | --output-file <name> ] name of file to write image data\n\ + (%%d replacement in output file name).\n\ + [ -S | --scan-script <name> ] name of script to run after every scan.\n\ + [ --script-wait ] wait for scripts to finish before exit\n\ + [ -s | --start-count <num> ] page count of first scanned image.\n\ + [ -e | --end-count <num> ] last page number to scan.\n\ + [ -r | --raw ] write raw image data to file.\n"; + +static RETSIGTYPE +sighandler (int signum) +{ + if (device) + { + fprintf (stderr, "%s: stopping scanner...\n", prog_name); + sane_cancel (device); + } +} + +static void +print_unit (SANE_Unit unit) +{ + switch (unit) + { + case SANE_UNIT_NONE: break; + case SANE_UNIT_PIXEL: fputs ("pel", stdout); break; + case SANE_UNIT_BIT: fputs ("bit", stdout); break; + case SANE_UNIT_MM: fputs ("mm", stdout); break; + case SANE_UNIT_DPI: fputs ("dpi", stdout); break; + case SANE_UNIT_PERCENT: fputc ('%', stdout); break; + case SANE_UNIT_MICROSECOND: fputs ("us", stdout); break; + } +} + +static void +print_option (SANE_Device *device, int opt_num, char short_name) +{ + const char *str, *last_break, *start; + const SANE_Option_Descriptor *opt; + SANE_Bool not_first = SANE_FALSE; + int i, column; + + opt = sane_get_option_descriptor (device, opt_num); + + if (short_name) + printf (" -%c", short_name); + else + printf (" --%s", opt->name); + + if (opt->type == SANE_TYPE_BOOL) + { + fputs ("[=(", stdout); + if (opt->cap & SANE_CAP_AUTOMATIC) + fputs ("auto|", stdout); + fputs ("yes|no)]", stdout); + } + else if (opt->type != SANE_TYPE_BUTTON) + { + fputc (' ', stdout); + if (opt->cap & SANE_CAP_AUTOMATIC) + { + fputs ("auto|", stdout); + not_first = SANE_TRUE; + } + switch (opt->constraint_type) + { + case SANE_CONSTRAINT_NONE: + switch (opt->type) + { + case SANE_TYPE_INT: fputs ("<int>", stdout); break; + case SANE_TYPE_FIXED: fputs ("<float>", stdout); break; + case SANE_TYPE_STRING: fputs ("<string>", stdout); break; + default: + break; + } + if (opt->type != SANE_TYPE_STRING && opt->size > sizeof (SANE_Word)) + fputs (",...", stdout); + break; + + case SANE_CONSTRAINT_RANGE: + if (opt->type == SANE_TYPE_INT) + { + printf ("%d..%d", + opt->constraint.range->min, opt->constraint.range->max); + print_unit (opt->unit); + if (opt->size > sizeof (SANE_Word)) + fputs (",...", stdout); + if (opt->constraint.range->quant) + printf (" (in steps of %d)", + opt->constraint.range->quant); + } + else + { + printf ("%g..%g", + SANE_UNFIX(opt->constraint.range->min), + SANE_UNFIX(opt->constraint.range->max)); + print_unit (opt->unit); + if (opt->size > sizeof (SANE_Word)) + fputs (",...", stdout); + if (opt->constraint.range->quant) + printf (" (in steps of %g)", + SANE_UNFIX(opt->constraint.range->quant)); + } + break; + + case SANE_CONSTRAINT_WORD_LIST: + for (i = 0; i < opt->constraint.word_list[0]; ++i) + { + if (not_first) + fputc ('|', stdout); + + not_first = SANE_TRUE; + + if (opt->type == SANE_TYPE_INT) + printf ("%d", opt->constraint.word_list[i + 1]); + else + printf ("%g", + SANE_UNFIX(opt->constraint.word_list[i + 1])); + } + print_unit (opt->unit); + if (opt->size > sizeof (SANE_Word)) + fputs (",...", stdout); + break; + + case SANE_CONSTRAINT_STRING_LIST: + for (i = 0; opt->constraint.string_list[i]; ++i) + { + if (i > 0) + fputc ('|', stdout); + + fputs (opt->constraint.string_list[i], stdout); + } + break; + } + } + if (opt->type == SANE_TYPE_STRING || + opt->type == SANE_TYPE_BOOL || + opt->type == SANE_TYPE_INT || + opt->type == SANE_TYPE_FIXED) + { + /* print current option value */ + fputs (" [", stdout); + if (SANE_OPTION_IS_ACTIVE(opt->cap)) + { + void * val = alloca (opt->size); + sane_control_option (device, opt_num, SANE_ACTION_GET_VALUE, val, 0); + switch (opt->type) + { + case SANE_TYPE_BOOL: + fputs (*(SANE_Bool *)val ? "yes" : "no", stdout); + break; + + case SANE_TYPE_INT: + printf ("%d", *(SANE_Int *)val); + break; + + case SANE_TYPE_FIXED: + printf ("%g", SANE_UNFIX(*(SANE_Fixed *)val)); + break; + + case SANE_TYPE_STRING: + fputs ((char *) val, stdout); + break; + + default: + break; + } + } + else + fputs ("inactive", stdout); + fputc (']', stdout); + } + fputs ("\n ", stdout); + + switch (short_name) + { + case 'x': + fputs ("Width of scan-area.", stdout); + break; + + case 'y': + fputs ("Height of scan-area.", stdout); + break; + + default: + column = 8; last_break = 0; start = opt->desc; + for (str = opt->desc; *str; ++str) + { + ++column; + if (*str == ' ') + last_break = str; + if (column >= 79 && last_break) + { + while (start < last_break) + fputc (*start++, stdout); + start = last_break + 1; /* skip blank */ + fputs ("\n ", stdout); + column = 8 + (str - start); + } + } + while (*start) + fputc (*start++, stdout); + } + fputc ('\n', stdout); +} + +/* A scalar has the following syntax: + + V [ U ] + + V is the value of the scalar. It is either an integer or a + floating point number, depending on the option type. + + U is an optional unit. If not specified, the default unit is used. + The following table lists which units are supported depending on + what the option's default unit is: + + Option's unit: Allowed units: + + SANE_UNIT_NONE: + SANE_UNIT_PIXEL: pel + SANE_UNIT_BIT: b (bit), B (byte) + SANE_UNIT_MM: mm (millimeter), cm (centimeter), in or " (inches), + SANE_UNIT_DPI: dpi + SANE_UNIT_PERCENT: % + SANE_UNIT_PERCENT: us + */ +static const char * +parse_scalar (const SANE_Option_Descriptor * opt, const char * str, + SANE_Word * value) +{ + char * end; + double v; + + if (opt->type == SANE_TYPE_FIXED) + v = strtod (str, &end) * (1 << SANE_FIXED_SCALE_SHIFT); + else + v = strtol (str, &end, 10); + + if (str == end) + { + fprintf (stderr, + "%s: option --%s: bad option value (rest of option: %s)\n", + prog_name, opt->name, str); + exit (1); + } + str = end; + + switch (opt->unit) + { + case SANE_UNIT_NONE: + case SANE_UNIT_PIXEL: + break; + + case SANE_UNIT_BIT: + if (*str == 'b' || *str == 'B') + { + if (*str++ == 'B') + v *= 8; + } + break; + + case SANE_UNIT_MM: + if (str[0] == '\0') + v *= 1.0; /* default to mm */ + else if (strcmp (str, "mm") == 0) + str += sizeof ("mm") - 1; + else if (strcmp (str, "cm") == 0) + { + str += sizeof ("cm") - 1; + v *= 10.0; + } + else if (strcmp (str, "in") == 0 || *str == '"') + { + if (*str++ != '"') + ++str; + v *= 25.4; /* 25.4 mm/inch */ + } + else + { + fprintf (stderr, + "%s: option --%s: illegal unit (rest of option: %s)\n", + prog_name, opt->name, str); + return 0; + } + break; + + case SANE_UNIT_DPI: + if (strcmp (str, "dpi") == 0) + str += sizeof ("dpi") - 1; + break; + + case SANE_UNIT_PERCENT: + if (*str == '%') + ++str; + break; + + case SANE_UNIT_MICROSECOND: + if (strcmp (str, "us") == 0) + str += sizeof ("us") - 1; + break; + } + *value = v + 0.5; + return str; +} + +/* A vector has the following syntax: + + [ '[' I ']' ] S { [','|'-'] [ '[' I ']' S } + + The number in brackets (I), if present, determines the index of the + vector element to be set next. If I is not present, the value of + last index used plus 1 is used. The first index value used is 0 + unless I is present. + + S is a scalar value as defined by parse_scalar(). + + If two consecutive value specs are separated by a comma (,) their + values are set independently. If they are separated by a dash (-), + they define the endpoints of a line and all vector values between + the two endpoints are set according to the value of the + interpolated line. For example, [0]15-[255]15 defines a vector of + 256 elements whose value is 15. Similarly, [0]0-[255]255 defines a + vector of 256 elements whose value starts at 0 and increases to + 255. */ +static void +parse_vector (const SANE_Option_Descriptor * opt, const char * str, + SANE_Word * vector, size_t vector_length) +{ + SANE_Word value, prev_value = 0; + int index = -1, prev_index = 0; + char * end, separator = '\0'; + + /* initialize vector to all zeroes: */ + memset (vector, 0, vector_length * sizeof (SANE_Word)); + + do { + if (*str == '[') + { + /* read index */ + index = strtol (++str, &end, 10); + if (str == end || *end != ']') + { + fprintf (stderr, "%s: option --%s: closing bracket missing " + "(rest of option: %s)\n", prog_name, opt->name, str); + exit (1); + } + str = end + 1; + } + else + ++index; + + if (index < 0 || index >= vector_length) + { + fprintf (stderr, "%s: option --%s: index %d out of range [0..%ld]\n", + prog_name, opt->name, index, (long) vector_length - 1); + exit (1); + } + + /* read value */ + str = parse_scalar (opt, str, &value); + if (!str) + exit (1); + + if (*str && *str != '-' && *str != ',') + { + fprintf (stderr, + "%s: option --%s: illegal separator (rest of option: %s)\n", + prog_name, opt->name, str); + exit (1); + } + + /* store value: */ + vector[index] = value; + if (separator == '-') + { + /* interpolate */ + double v, slope; + int i; + + v = (double) prev_value; + slope = ((double) value - v) / (index - prev_index); + + for (i = prev_index + 1; i < index; ++i) + { + v += slope; + vector[i] = (SANE_Word) v; + } + } + + prev_index = index; + prev_value = value; + separator = *str++; + } while (separator == ',' || separator == '-'); + + if (verbose > 2) + { + int i; + + fprintf (stderr, "%s: value for --%s is: ", prog_name, opt->name); + for (i = 0; i < vector_length; ++i) + if (opt->type == SANE_TYPE_FIXED) + fprintf (stderr, "%g ", SANE_UNFIX(vector[i])); + else + fprintf (stderr, "%d ", vector[i]); + fputc ('\n', stderr); + } +} + +void +fetch_options (SANE_Device * device) +{ + const SANE_Option_Descriptor * opt; + SANE_Int num_dev_options; + int i, option_count; + + /* and now build the full table of long options: */ + + sane_control_option (device, 0, SANE_ACTION_GET_VALUE, &num_dev_options, 0); + + option_count = 0; + for (i = 0; i < num_dev_options; ++i) + { + opt = sane_get_option_descriptor (device, i); + + if (!SANE_OPTION_IS_SETTABLE (opt->cap)) + continue; + + option_number[option_count] = i; + + all_options[option_count].name = (char *) opt->name; + all_options[option_count].flag = 0; + all_options[option_count].val = 0; + + if (opt->type == SANE_TYPE_BOOL) + all_options[option_count].has_arg = optional_argument; + else if (opt->type == SANE_TYPE_BUTTON) + all_options[option_count].has_arg = no_argument; + else + all_options[option_count].has_arg = required_argument; + + /* Keep track of resolution options */ + if (strcmp (opt->name, SANE_NAME_SCAN_RESOLUTION) == 0) + { + resolution_opt = i; + } + else if (strcmp (opt->name, SANE_NAME_SCAN_X_RESOLUTION) == 0) + { + x_resolution_opt = i; + } + if (strcmp (opt->name, SANE_NAME_SCAN_Y_RESOLUTION) == 0) + { + y_resolution_opt = i; + } + + /* Keep track of top-left corner options (if they exist at + all) and replace the bottom-right corner options by a + width/height option (if they exist at all). */ + if ((opt->type == SANE_TYPE_FIXED || opt->type == SANE_TYPE_INT) + && opt->size == sizeof (SANE_Int) + && (opt->unit == SANE_UNIT_MM || opt->unit == SANE_UNIT_PIXEL)) + { + if (strcmp (opt->name, SANE_NAME_SCAN_TL_X) == 0) + { + window[2] = i; + all_options[option_count].val = 'l'; + } + else if (strcmp (opt->name, SANE_NAME_SCAN_TL_Y) == 0) + { + window[3] = i; + all_options[option_count].val = 't'; + } + else if (strcmp (opt->name, SANE_NAME_SCAN_BR_X) == 0) + { + window[0] = i; + all_options[option_count].name = "width"; + all_options[option_count].val = 'x'; + window_option[0] = *opt; + window_option[0].title = "Scan width"; + window_option[0].desc = "Width of scanning area."; + if (!window_val_user[0]) + sane_control_option (device, i, SANE_ACTION_GET_VALUE, + &window_val[0], 0); + } + else if (strcmp (opt->name, SANE_NAME_SCAN_BR_Y) == 0) + { + window[1] = i; + all_options[option_count].name = "height"; + all_options[option_count].val = 'y'; + window_option[1] = *opt; + window_option[1].title = "Scan height"; + window_option[1].desc = "Height of scanning area."; + if (!window_val_user[1]) + sane_control_option (device, i, SANE_ACTION_GET_VALUE, + &window_val[1], 0); + } + } + ++option_count; + } + memcpy (all_options + option_count, basic_options, sizeof (basic_options)); + option_count += NELEMS(basic_options); + memset (all_options + option_count, 0, sizeof (all_options[0])); + + /* Initialize width & height options based on backend default + values for top-left x/y and bottom-right x/y: */ + for (i = 0; i < 2; ++i) + { + if (window[i] && window[i + 2] && !window_val_user[i]) + { + SANE_Word pos; + sane_control_option (device, window[i + 2], + SANE_ACTION_GET_VALUE, &pos, 0); + window_val[i] = window_val[i] - pos + 1; + } + } +} + +static void +set_option (SANE_Handle device, int optnum, void * valuep) +{ + const SANE_Option_Descriptor * opt; + SANE_Status status; + SANE_Word orig = 0; + SANE_Int info; + + opt = sane_get_option_descriptor (device, optnum); + + if (opt->size == sizeof (SANE_Word) && opt->type != SANE_TYPE_STRING) + orig = *(SANE_Word *) valuep; + + status = sane_control_option (device, optnum, SANE_ACTION_SET_VALUE, + valuep, &info); + if (status != SANE_STATUS_GOOD) + { + fprintf (stderr, "%s: setting of option --%s failed (%s)\n", + prog_name, opt->name, sane_strstatus (status)); + exit (1); + } + + if ((info & SANE_INFO_INEXACT) && opt->size == sizeof (SANE_Word)) + { + if (opt->type == SANE_TYPE_INT) + fprintf (stderr, "%s: rounded value of %s from %d to %d\n", + prog_name, opt->name, orig, *(SANE_Word *) valuep); + else if (opt->type == SANE_TYPE_FIXED) + fprintf (stderr, "%s: rounded value of %s from %g to %g\n", + prog_name, opt->name, + SANE_UNFIX(orig), SANE_UNFIX(*(SANE_Word *) valuep)); + } + + if (info & SANE_INFO_RELOAD_OPTIONS) + fetch_options (device); +} + +static void +process_backend_option (SANE_Handle device, int optnum, const char * optarg) +{ + static SANE_Word * vector = 0; + static size_t vector_size = 0; + const SANE_Option_Descriptor * opt; + size_t vector_length; + SANE_Status status; + SANE_Word value; + void * valuep; + + opt = sane_get_option_descriptor (device, optnum); + + if (!SANE_OPTION_IS_ACTIVE(opt->cap)) + { + fprintf (stderr, "%s: attempted to set inactive option %s\n", + prog_name, opt->name); + exit (1); + } + + if ((opt->cap & SANE_CAP_AUTOMATIC) && strncasecmp (optarg, "auto", 4) == 0) + { + status = sane_control_option (device, optnum, SANE_ACTION_SET_AUTO, + 0, 0); + if (status != SANE_STATUS_GOOD) + { + fprintf (stderr, "%s: failed to set option --%s to automatic (%s)\n", + prog_name, opt->name, sane_strstatus (status)); + exit (1); + } + return; + } + + valuep = &value; + switch (opt->type) + { + case SANE_TYPE_BOOL: + value = 1; /* no argument means option is set */ + if (optarg) + { + if (strncasecmp (optarg, "yes", strlen (optarg)) == 0) + value = 1; + else if (strncasecmp (optarg, "no", strlen (optarg)) == 0) + value = 0; + else + { + fprintf (stderr, "%s: option --%s: bad option value `%s'\n", + prog_name, opt->name, optarg); + exit (1); + } + } + break; + + case SANE_TYPE_INT: + case SANE_TYPE_FIXED: + /* ensure vector is long enough: */ + vector_length = opt->size / sizeof (SANE_Word); + if (vector_size < vector_length) + { + vector_size = vector_length; + vector = realloc (vector, vector_length * sizeof (SANE_Word)); + if (!vector) + { + fprintf (stderr, "%s: out of memory\n", prog_name); + exit (1); + } + } + parse_vector (opt, optarg, vector, vector_length); + valuep = vector; + break; + + case SANE_TYPE_STRING: + valuep = (void *) optarg; + break; + + case SANE_TYPE_BUTTON: + value = 0; /* value doesn't matter */ + break; + + default: + fprintf (stderr, "%s: duh, got unknown option type %d\n", + prog_name, opt->type); + return; + } + set_option (device, optnum, valuep); +} + +static void +write_pnm_header_to_file (FILE *fp, SANE_Frame format, int width, int height, int depth) +{ + switch (format) + { + case SANE_FRAME_RED: + case SANE_FRAME_GREEN: + case SANE_FRAME_BLUE: + case SANE_FRAME_RGB: + fprintf (fp, "P6\n# SANE data follows\n%d %d\n255\n", width, height); + break; + + case SANE_FRAME_GRAY: + if (depth == 1) + fprintf (fp, "P4\n# SANE data follows\n%d %d\n", width, height); + else + fprintf (fp, "P5\n# SANE data follows\n%d %d\n255\n", width, height); + break; + default: + /* default action for unknown frametypes; don't write a header */ + break; + } +#ifdef __EMX__ /* OS2 - write in binary mode. */ + _fsetmode(fp, "b"); +#endif +} + +static void * +advance (Image *image) +{ + if (++image->x >= image->width) + { + image->x = 0; + if (++image->y >= image->height || !image->data) + { + size_t old_size = 0, new_size; + + if (image->data) + old_size = image->height * image->width * image->Bpp; + + image->height += STRIP_HEIGHT; + new_size = image->height * image->width * image->Bpp; + + if (image->data) + image->data = realloc (image->data, new_size); + else + image->data = malloc (new_size); + if (image->data) + memset (image->data + old_size, 0, new_size - old_size); + } + } + if (!image->data) + fprintf (stderr, "%s: can't allocate image buffer (%dx%d)\n", + prog_name, image->width, image->height); + return image->data; +} + +static SANE_Int +get_resolution(SANE_Device *device) +{ + SANE_Int res = 200; + SANE_Word val; + const SANE_Option_Descriptor *opt; + + if (resolution_opt >= 0) + { + opt = sane_get_option_descriptor (device, resolution_opt); + + sane_control_option (device, resolution_opt, + SANE_ACTION_GET_VALUE, &val, 0); + switch (opt->type) + { + case SANE_TYPE_INT: + res = val; + break; + + case SANE_TYPE_FIXED: + res = (SANE_Int) SANE_UNFIX(val); + break; + + case SANE_TYPE_STRING: + case SANE_TYPE_BOOL: + default: + if (verbose) + fprintf(stderr, + "Peculiar option data type for resolution, " + "using default value.\n"); + break; + } + } + else + { + if (verbose) + fprintf(stderr, "No resolution option found, using default value.\n"); + } + + return res; +} + +static SANE_Status +scan_it_raw (const char *fname, SANE_Bool raw, const char *script) +{ + int i, len, first_frame = 1, offset = 0, must_buffer = 0; + SANE_Byte buffer[32*1024], min = 0xff, max = 0; + SANE_Parameters parm; + SANE_Status status; + Image image = {0, }; + FILE *fp = NULL; + + do + { + status = sane_start (device); + if (status != SANE_STATUS_GOOD) + { + if (status != SANE_STATUS_NO_DOCS) + { + fprintf (stderr, "%s: sane_start: %s\n", + prog_name, sane_strstatus (status)); + } + goto cleanup; + } + + status = sane_get_parameters (device, &parm); + if (status != SANE_STATUS_GOOD) + { + fprintf (stderr, "%s: sane_get_parameters: %s\n", + prog_name, sane_strstatus (status)); + goto cleanup; + } + + fp = fopen(fname, "wb"); + if (!fp) { + fprintf(stderr, "Error opening output `%s': %s (%d)\n", + fname, strerror(errno), errno); + status = SANE_STATUS_IO_ERROR; + goto cleanup; + } + + if (verbose) + { + if (first_frame) + { + if (sane_isbasicframe(parm.format)) + { + if (parm.lines >= 0) + fprintf (stderr, "%s: scanning image of size %dx%d pixels" + " at %d bits/pixel\n", + prog_name, parm.pixels_per_line, parm.lines, + 8 * parm.bytes_per_line / parm.pixels_per_line); + else + fprintf (stderr, "%s: scanning image %d pixels wide and " + "variable height at %d bits/pixel\n", + prog_name, parm.pixels_per_line, + 8 * parm.bytes_per_line / parm.pixels_per_line); + } + else + { + fprintf (stderr, "%s: receiving %s frame " + "bytes/line=%d, " + "pixels/line=%d, " + "lines=%d, " + "depth=%d\n", + prog_name, sane_strframe(parm.format), + parm.bytes_per_line, + parm.pixels_per_line, + parm.lines, + parm.depth); + } + } + fprintf (stderr, "%s: acquiring %s frame\n", prog_name, + sane_strframe(parm.format)); + } + + if (first_frame) + { + switch (parm.format) + { + case SANE_FRAME_RED: + case SANE_FRAME_GREEN: + case SANE_FRAME_BLUE: + assert (parm.depth == 8); + must_buffer = 1; + offset = parm.format - SANE_FRAME_RED; + break; + + case SANE_FRAME_RGB: + assert (parm.depth == 8); + case SANE_FRAME_GRAY: + assert (parm.depth == 1 || parm.depth == 8); + /* if we're writing raw, we skip the header and never + * have to buffer a single frame format. + */ + if (raw == SANE_FALSE) + { + if (parm.lines < 0) + { + must_buffer = 1; + offset = 0; + } + else + { + write_pnm_header_to_file (fp, parm.format, + parm.pixels_per_line, + parm.lines, parm.depth); + } + } + break; + + case SANE_FRAME_TEXT: + case SANE_FRAME_JPEG: + case SANE_FRAME_G31D: + case SANE_FRAME_G32D: + case SANE_FRAME_G42D: + if (!parm.last_frame) + { + status = SANE_STATUS_INVAL; + fprintf (stderr, "%s: bad %s frame: must be last_frame\n", + prog_name, sane_strframe (parm.format)); + goto cleanup; + } + /* write them out without a header; don't buffer */ + break; + + default: + /* Default action for unknown frametypes; write them out + * without a header; issue a warning in verbose mode. + * Since we're not writing a header, there's no need to + * buffer. + */ + if (verbose) + { + fprintf(stderr, "scanadf: unknown frame format %d\n", + (int) parm.format); + } + if (!parm.last_frame) + { + status = SANE_STATUS_INVAL; + fprintf (stderr, "%s: bad %s frame: must be last_frame\n", + prog_name, sane_strframe (parm.format)); + goto cleanup; + } + break; + } + + if (must_buffer) + { + /* We're either scanning a multi-frame image or the + scanner doesn't know what the eventual image height + will be (common for hand-held scanners). In either + case, we need to buffer all data before we can write + the image. */ + image.width = parm.pixels_per_line; + if (parm.lines >= 0) + /* See advance(); we allocate one extra line so we + don't end up realloc'ing in when the image has been + filled in. */ + image.height = parm.lines - STRIP_HEIGHT + 1; + else + image.height = 0; + image.Bpp = 3; + if (parm.format == SANE_FRAME_GRAY || + !sane_isbasicframe(parm.format)) + image.Bpp = 1; + image.x = image.width - 1; + image.y = -1; + if (!advance (&image)) + goto cleanup; + } + } + else + { + assert (parm.format >= SANE_FRAME_RED + && parm.format <= SANE_FRAME_BLUE); + offset = parm.format - SANE_FRAME_RED; + image.x = image.y = 0; + } + + while (1) + { + status = sane_read (device, buffer, sizeof (buffer), &len); + if (status != SANE_STATUS_GOOD) + { + if (verbose && parm.depth == 8) + fprintf (stderr, "%s: min/max graylevel value = %d/%d\n", + prog_name, min, max); + if (status != SANE_STATUS_EOF) + { + fprintf (stderr, "%s: sane_read: %s\n", + prog_name, sane_strstatus (status)); + goto cleanup; + } + break; + } + if (must_buffer) + { + switch (parm.format) + { + case SANE_FRAME_RED: + case SANE_FRAME_GREEN: + case SANE_FRAME_BLUE: + for (i = 0; i < len; ++i) + { + image.data[offset + 3*i] = buffer[i]; + if (!advance (&image)) + goto cleanup; + } + offset += 3*len; + break; + + case SANE_FRAME_RGB: + for (i = 0; i < len; ++i) + { + image.data[offset + i] = buffer[i]; + if ((offset + i) % 3 == 0 && !advance (&image)) + goto cleanup; + } + offset += len; + break; + + case SANE_FRAME_GRAY: + for (i = 0; i < len; ++i) + { + image.data[offset + i] = buffer[i]; + if (!advance (&image)) + goto cleanup; + } + offset += len; + break; + default: + /* optional frametypes are never buffered */ + fprintf(stderr, "%s: ERROR: trying to buffer %s" + " frametype\n", + prog_name, + sane_strframe(parm.format)); + break; + } + } + else + fwrite (buffer, 1, len, fp); + + if (verbose && parm.depth == 8) + { + for (i = 0; i < len; ++i) + if (buffer[i] >= max) + max = buffer[i]; + else if (buffer[i] < min) + min = buffer[i]; + } + } + first_frame = 0; + } + while (!parm.last_frame); + + if (must_buffer) + { + image.height = image.y; + if (raw == SANE_FALSE) + { + /* if we're writing raw, we skip the header */ + write_pnm_header_to_file (fp, parm.format, image.width, + image.height, parm.depth); + } + fwrite (image.data, image.Bpp, image.height * image.width, fp); + } + + if (fp) + { + fclose(fp); + fp = NULL; + } + + if (script) + { + static char cmd[PATH_MAX * 2]; + static char env[6][PATH_MAX * 2]; + int pid; + SANE_Int res; + SANE_Frame format; + extern char **environ; + + res = get_resolution(device); + + format = parm.format; + if (format == SANE_FRAME_RED || + format == SANE_FRAME_GREEN || + format == SANE_FRAME_BLUE) + { + /* the resultant format is RGB */ + format = SANE_FRAME_RGB; + } + sprintf(env[0], "SCAN_RES=%d", res); + if (putenv(env[0])) + fprintf(stderr, "putenv:failed\n"); + sprintf(env[1], "SCAN_WIDTH=%d", parm.pixels_per_line); + if (putenv(env[1])) + fprintf(stderr, "putenv:failed\n"); + sprintf(env[2], "SCAN_HEIGHT=%d", parm.lines); + if (putenv(env[2])) + fprintf(stderr, "putenv:failed\n"); + sprintf(env[3], "SCAN_FORMAT_ID=%d", (int) parm.format); + if (putenv(env[3])) + fprintf(stderr, "putenv:failed\n"); + sprintf(env[4], "SCAN_FORMAT=%s", + sane_strframe(parm.format)); + if (putenv(env[4])) + fprintf(stderr, "putenv:failed\n"); + sprintf(env[5], "SCAN_DEPTH=%d", parm.depth); + if (putenv(env[5])) + fprintf(stderr, "putenv:failed\n"); + signal(SIGCHLD, SIG_IGN); + switch ((pid = fork())) + { + case -1: + /* fork failed */ + fprintf(stderr, "Error forking: %s (%d)\n", strerror(errno), errno); + break; + + case 0: + /* in child process */ + sprintf(cmd, "%s '%s'", script, fname); + /* system(cmd); */ + execle(script, script, fname, NULL, environ); + exit(0); + + default: + if (verbose) + fprintf(stderr, "Started script `%s' as pid=%d\n", script, pid); + break; + } + } + +cleanup: + if (image.data) + free (image.data); + if (fp) fclose(fp); + + return status; +} + +static SANE_Int +scan_docs (int start, int end, int no_overwrite, SANE_Bool raw, const char *outfmt, const char *script) +{ + SANE_Status status = SANE_STATUS_GOOD; + SANE_Int scannedPages = 0; + SANE_Char fname[PATH_MAX]; + struct stat statbuf; + int res; + + while (end < 0 || start <= end) + { + /*!!! buffer overflow; need protection */ + sprintf(fname, outfmt, start); + + /* does the filename already exist? */ + if (no_overwrite) + { + res = stat (fname, &statbuf); + if (res == 0) + { + status = SANE_STATUS_INVAL; + fprintf (stderr, "Filename %s already exists; will not overwrite\n", fname); + } + } + + /* Scan the document */ + if (status == SANE_STATUS_GOOD) + status = scan_it_raw(fname, raw, script); + + /* Any scan errors? */ + if (status == SANE_STATUS_NO_DOCS) + { + /* out of paper in the hopper; this is our normal exit */ + status = SANE_STATUS_GOOD; + break; + } + else if (status == SANE_STATUS_EOF) + { + /* done with this doc */ + status = SANE_STATUS_GOOD; + fprintf(stderr, "Scanned document %s\n", fname); + scannedPages++; + start++; + } + else + { + /* unexpected error */ + fprintf(stderr, "%s\n", sane_strstatus(status)); + break; + } + } + + fprintf(stderr, "Scanned %d pages\n", scannedPages); + + return status; +} + +int +main (int argc, char **argv) +{ + int ch, i, index, all_options_len; + const SANE_Option_Descriptor * opt; + const SANE_Device ** device_list; + SANE_Int num_dev_options = 0; + const char * devname = 0; + SANE_Status status; + char *full_optstring; + SANE_Bool raw = SANE_FALSE; + const char *scanScript = NULL; /* script to run at end of scan */ + int scriptWait = 0; + const char *outputFile = "image-%04d"; /* file name(format) to write output to */ + int startNum = 1, endNum = -1; /* start/end numbers of pages to scan */ + int no_overwrite = 0; + + atexit (sane_exit); + + prog_name = strrchr (argv[0], '/'); + if (prog_name) + ++prog_name; + else + prog_name = argv[0]; + + sane_init (0, 0); + + /* make a first pass through the options with error printing and argument + permutation disabled: */ + opterr = 0; + while ((ch = getopt_long (argc, argv, "-" BASE_OPTSTRING, basic_options, + &index)) + != EOF) + { + switch (ch) + { + case ':': + case '?': + break; /* may be an option that we'll parse later on */ + + case 'd': devname = optarg; break; + case 'h': help = 1; break; + case 'N': no_overwrite = 1; break; + case 'v': ++verbose; break; + case 'L': + { + int i; + + status = sane_get_devices (&device_list, SANE_FALSE); + if (status != SANE_STATUS_GOOD) + { + fprintf (stderr, "%s: sane_get_devices() failed: %s\n", + prog_name, sane_strstatus (status)); + exit (1); + } + + for (i = 0; device_list[i]; ++i) + { + printf ("device `%s' is a %s %s %s\n", + device_list[i]->name, device_list[i]->vendor, + device_list[i]->model, device_list[i]->type); + } + exit (0); + } + + case 'o': outputFile = optarg; break; + case 'S': scanScript = optarg; break; + case 128: scriptWait = 1; break; + case 's': startNum = atoi(optarg); break; + case 'e': endNum = atoi(optarg); break; + case 'r': raw = SANE_TRUE; break; + + case 'V': + printf ("scanadf (%s) %s\n", PACKAGE, VERSION); + exit (0); + + default: + break; /* ignore device specific options for now */ + } + } + + if (scriptWait && !scanScript) + scriptWait = 0; + + if (help) + printf (usage, prog_name); + + if (!devname) + { + /* If no device name was specified explicitly, we open the first + device we find (if any): */ + + status = sane_get_devices (&device_list, SANE_FALSE); + if (status != SANE_STATUS_GOOD) + { + fprintf (stderr, "%s: sane_get_devices() failed: %s\n", + prog_name, sane_strstatus (status)); + exit (1); + } + if (!device_list[0]) + { + fprintf (stderr, "%s: no SANE devices found\n", prog_name); + exit (1); + } + devname = device_list[0]->name; + } + + status = sane_open (devname, &device); + if (status != SANE_STATUS_GOOD) + { + fprintf (stderr, "%s: open of device %s failed: %s\n", + prog_name, devname, sane_strstatus (status)); + if (help) + device = 0; + else + exit (1); + } + + if (device) + { + /* We got a device, find out how many options it has: */ + status = sane_control_option (device, 0, SANE_ACTION_GET_VALUE, + &num_dev_options, 0); + if (status != SANE_STATUS_GOOD) + { + fprintf (stderr, "%s: unable to determine option count\n", + prog_name); + exit (1); + } + + all_options_len = num_dev_options + NELEMS(basic_options) + 1; + all_options = malloc (all_options_len * sizeof (all_options[0])); + option_number_len = num_dev_options; + option_number = malloc (option_number_len * sizeof (option_number[0])); + if (!all_options || !option_number) + { + fprintf (stderr, "%s: out of memory in fetch_options()\n", + prog_name); + exit (1); + } + + fetch_options (device); + + { + char *larg, *targ, *xarg, *yarg; + larg = targ = xarg = yarg = ""; + + /* Maybe accept t, l, x, and y options. */ + if (window[0]) + xarg = "x:"; + + if (window[1]) + yarg = "y:"; + + if (window[2]) + larg = "l:"; + + if (window[3]) + targ = "t:"; + + /* Now allocate the full option list. */ + full_optstring = malloc (strlen (BASE_OPTSTRING) + + strlen (larg) + strlen (targ) + + strlen (xarg) + strlen (yarg) + 1); + + if (!full_optstring) + { + fprintf (stderr, "%s: out of memory\n", prog_name); + exit (1); + } + + strcpy (full_optstring, BASE_OPTSTRING); + strcat (full_optstring, larg); + strcat (full_optstring, targ); + strcat (full_optstring, xarg); + strcat (full_optstring, yarg); + } + + optind = 0; + opterr = 1; /* re-enable error printing and arg permutation */ + while ((ch = getopt_long (argc, argv, full_optstring, all_options, + &index)) + != EOF) + { + switch (ch) + { + case ':': + case '?': + exit (1); /* error message is printed by getopt_long() */ + + case 'd': case 'h': case 'v': case 'V': case 'T': + case 'o': case 'S': case 's': case 'e': case 'r': + + /* previously handled options */ + break; + + case 'x': + window_val_user[0] = 1; + parse_vector (&window_option[0], optarg, &window_val[0], 1); + break; + + case 'y': + window_val_user[1] = 1; + parse_vector (&window_option[1], optarg, &window_val[1], 1); + break; + + case 'l': /* tl-x */ + process_backend_option (device, window[2], optarg); + break; + + case 't': /* tl-y */ + process_backend_option (device, window[3], optarg); + break; + + case 0: + process_backend_option (device, option_number[index], optarg); + break; + } + } + + free (full_optstring); + + for (index = 0; index < 2; ++index) + if (window[index]) + { + SANE_Word val, pos; + + if (window[index + 2]) + sane_control_option (device, window[index + 2], + SANE_ACTION_GET_VALUE, &pos, 0); + val = pos + window_val[index] - 1; + set_option (device, window[index], &val); + } + if (help) + { + printf ("\nOptions specific to device `%s':\n", devname); + + for (i = 0; i < num_dev_options; ++i) + { + char short_name = '\0'; + int j; + + opt = 0; + for (j = 0; j < 4; ++j) + if (i == window[j]) + { + short_name = "xylt"[j]; + if (j < 2) + opt = window_option + j; + } + if (!opt) + opt = sane_get_option_descriptor (device, i); + + if (opt->type == SANE_TYPE_GROUP) + printf (" %s:\n", opt->title); + + if (!SANE_OPTION_IS_SETTABLE (opt->cap)) + continue; + + print_option (device, i, short_name); + } + if (num_dev_options) + fputc ('\n', stdout); + } + } + + if (help) + { + printf ("\ +Type ``%s --help -d DEVICE'' to get list of all options for DEVICE.\n\ +\n\ +List of available devices:", prog_name); + if (device) + sane_close(device); + + status = sane_get_devices (&device_list, SANE_FALSE); + if (status == SANE_STATUS_GOOD) + { + int column = 80; + + for (i = 0; device_list[i]; ++i) + { + if (column + strlen (device_list[i]->name) + 1 >= 80) + { + printf ("\n "); + column = 4; + } + if (column > 4) + { + fputc (' ', stdout); + column += 1; + } + fputs (device_list[i]->name, stdout); + column += strlen (device_list[i]->name); + } + } + fputc ('\n', stdout); + exit (0); + } + + signal (SIGHUP, sighandler); + signal (SIGINT, sighandler); + signal (SIGPIPE, sighandler); + signal (SIGTERM, sighandler); + + status = scan_docs (startNum, endNum, no_overwrite, raw, outputFile, scanScript); + + sane_cancel (device); + sane_close (device); + + if (scriptWait) + while (wait(NULL) != -1); + + return (status == SANE_STATUS_GOOD) ? 0 : 1; +} + diff --git a/src/xcam.c b/src/xcam.c new file mode 100644 index 0000000..2d494a5 --- /dev/null +++ b/src/xcam.c @@ -0,0 +1,1871 @@ +/* xcam -- X-based camera frontend + Uses the SANE library. + Copyright (C) 1997 David Mosberger and Tristan Tarrant + + Update 2005 Gerard Klaver + The add_text routine and font_6x11.h file are taken from the (GPLed) + webcam.c file, part of xawtv, (c) 1998-2002 Gerd Knorr. + add_text was modified for this program (xcam_add_text). + + 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. */ + +#include "../include/sane/config.h" + +#include "../include/lalloca.h" + +#include <assert.h> +#include <errno.h> +#include <fcntl.h> +#include <getopt.h> +#include <limits.h> +#include <math.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <time.h> + +/* for xcam_add-text routine */ +#include "font_6x11.h" +/*-----------------------*/ + + +#include <sys/types.h> +#include <sys/stat.h> + +#include "gtkglue.h" +#include "preferences.h" + +#include <sane/sane.h> +#include <sane/saneopts.h> +#include "../include/sane/sanei.h" + +#define BACKEND_NAME xcam +#include "../include/sane/sanei_debug.h" + +#ifndef PATH_MAX +# define PATH_MAX 1024 +#endif + +#define OUTFILENAME "out.pnm" + +#define MAX_LUM 64 /* how many graylevels for 8 bit displays */ + +#ifndef HAVE_ATEXIT +# define atexit(func) on_exit(func, 0) /* works for SunOS, at least */ +#endif + +typedef struct Canvas +{ + GtkWidget *preview; + GdkGC *gc; + GdkImage *gdk_image; + GdkColormap *graylevel_cmap; /* for 8 bit displays */ + guint32 graylevel[MAX_LUM]; /* graylevel pixels */ + GdkColormap *cube_cmap; + GdkColor cube_colors[5 * 6 * 5]; +} +Canvas; + +static const char *prog_name; +static const SANE_Device **device; +static GSGDialog *dialog; +static char device_settings_filename[1024] = "device.rc"; + +#define DBG_fatal 0 +#define DBG_error 1 +#define DBG_warning 2 +#define DBG_info 3 +#define DBG_debug 4 + +static struct +{ + GtkWidget *shell; + GtkWidget *dialog_box; + GtkWidget *play_stop_label; + GtkWidget *info_label; + GtkWidget *device_info_label; + GtkWidget *save_frame_label; + GtkWidget *rgb_bgr_label; + GtkWidget *txt_label; + struct + { + GtkWidget *item; /* the menu bar item */ + GtkWidget *menu; /* the associated menu */ + } + devices; + Canvas canvas; + gint gdk_input_tag; /* tag returned by gdk_input_add () */ + int playing; /* are we playing video? */ + int saving; /* are we saving to file */ + + SANE_Byte *buf; + size_t buf_backend_size; + size_t remaining; + SANE_Parameters params; + gpointer data; /* image data */ + int x, y; /* x and y position */ + int input_tag; +/* for standalone mode: */ + GtkWidget *filename_entry; + FILE *out; + long header_size; + gboolean have_odd_byte; + guint8 odd_byte; + + int num_bytes; + int bytes_read; + char picmsg_ps[50]; + int value_rgb; + int value_txt; + + double fps; + double fps_av; + double fps_old1; + double fps_old2; + double fps_old3; + long f_count; + time_t time1; + time_t time2; + int i_time; +} +win; + +/* forward declarations: */ +int main (int argc, char **argv); + +static void rescan_devices (GtkWidget * widget, gpointer client_data, + gpointer call_data); +static void next_frame (void); + +static void save_frame (void); + +static void update_param (GSGDialog * dialog, void *arg); + +static void load_defaults (int silent); + +static struct option long_options[] = { + {"help", no_argument, NULL, 'h'}, + {"version", no_argument, NULL, 'V'}, + {"buffersize", no_argument, NULL, 'B'}, + {0, 0, 0, 0} +}; + +/* Test if this machine is little endian (from coolscan.c) */ +/* static gboolean +calc_little_endian (void) +{ + SANE_Int testvalue = 255; + u_int8_t *firstbyte = (u_int8_t *) & testvalue; + + if (*firstbyte == 255) + return TRUE; + return FALSE; +} */ + +#define CANVAS_EVENT_MASK GDK_BUTTON1_MOTION_MASK | \ + GDK_EXPOSURE_MASK | \ + GDK_BUTTON_PRESS_MASK | \ + GDK_ENTER_NOTIFY_MASK + +static void +display_image (Canvas * canvas) +{ + if (canvas->gdk_image) + { + gdk_draw_image (canvas->preview->window, canvas->gc, canvas->gdk_image, + 0, 0, 0, 0, + canvas->gdk_image->width, canvas->gdk_image->height); + gdk_flush (); + } +} + +static gint +canvas_events (GtkWidget * widget, GdkEvent * event) +{ + Canvas *canvas = &win.canvas; + if (!canvas) + return FALSE; + + switch (event->type) + { + case GDK_EXPOSE: + if (!canvas->gc) + canvas->gc = gdk_gc_new (canvas->preview->window); + display_image (canvas); + break; + + case GDK_BUTTON_PRESS: + break; + + case GDK_BUTTON_RELEASE: + break; + + case GDK_MOTION_NOTIFY: + break; + + case GDK_ENTER_NOTIFY: +#if 0 + gdk_colors_store (win.canvas.cube_cmap, win.canvas.cube_colors, + NELEMS (win.canvas.cube_colors)); +#endif + break; + + default: + break; + } + return FALSE; +} + +static void +stop_camera (void) +{ + DBG (DBG_debug, "xcam: stop_camera: enter\n"); + + if (dialog) + sane_cancel (gsg_dialog_get_device (dialog)); + if (win.gdk_input_tag >= 0) + { + gdk_input_remove (win.gdk_input_tag); + } + else + win.playing = FALSE; + win.gdk_input_tag = -1; + if (!win.playing) + gtk_label_set (GTK_LABEL (win.play_stop_label), " Play "); + else + gtk_label_set (GTK_LABEL (win.play_stop_label), " Stop "); + + DBG (DBG_debug, "xcam: stop_camera: exit\n"); +} + +static void +switch_device (const SANE_Device * dev) +{ + char buf[512]; + + DBG (DBG_debug, "xcam: switch_device: enter\n"); + if (win.playing) + { + win.playing = FALSE; + stop_camera (); + } + + if (dialog) + gsg_destroy_dialog (dialog); + + dialog = gsg_create_dialog (GTK_WIDGET (win.dialog_box), dev->name, + 0, 0, 0, 0); + buf[0] = '\0'; + + if (dialog) + sprintf (buf, "%s %s %s", dev->vendor, dev->model, dev->type); + gtk_label_set (GTK_LABEL (win.device_info_label), buf); + DBG (DBG_debug, "xcam: switch_device: exit\n"); +} + +static void +switch_device_by_name (const char *device_name) +{ + SANE_Device dev_info; + int i; + + DBG (DBG_debug, "xcam: switch_device_by_name: enter\n"); + for (i = 0; device[i]; ++i) + if (strcmp (device[i]->name, device_name) == 0) + { + switch_device (device[i]); + return; + } + + /* the backends don't know about this device yet---make up an entry: */ + dev_info.name = device_name; + dev_info.vendor = "Unknown"; + dev_info.model = ""; + dev_info.type = ""; + switch_device (&dev_info); + DBG (DBG_debug, "xcam: switch_device_by_name: exit\n"); +} + +static void +save_settings (const char *filename) +{ + int fd; + + DBG (DBG_debug, "xcam: save_settings: enter\n"); + + 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; + } + write (fd, dialog->dev_name, strlen (dialog->dev_name)); + write (fd, "\n", 1); + sanei_save_values (fd, dialog->dev); + close (fd); + DBG (DBG_debug, "xcam: save_settings: exit\n"); +} + +#define MSG_MAXLEN 45 +#define CHAR_HEIGHT 11 +#define CHAR_WIDTH 6 +#define CHAR_START 4 + +static SANE_Status +xcam_add_text (SANE_Byte * image, int width, int height, char *txt) +{ + SANE_Status status; + time_t t; + struct tm *tm; + char line[MSG_MAXLEN + 1]; + SANE_Byte *ptr; + int i, x, y, f, len; + char fmtstring[25] = " %Y-%m-%d %H:%M:%S"; + char fmttxt[46]; + + DBG (DBG_debug, "xcam_add_text: enter\n"); + time (&t); + tm = localtime (&t); + if (strlen (txt) > (MSG_MAXLEN - 23)) + strncpy (fmttxt, txt, (MSG_MAXLEN - 23)); + else + strcpy (fmttxt, txt); + strcat (fmttxt, fmtstring); + + len = strftime (line, MSG_MAXLEN, fmttxt, tm); + + for (y = 0; y < CHAR_HEIGHT; y++) + { + ptr = image + 3 * width * (height - CHAR_HEIGHT - 2 + y) + 12; + + for (x = 0; x < len; x++) + { + f = fontdata[line[x] * CHAR_HEIGHT + y]; + for (i = CHAR_WIDTH - 1; i >= 0; i--) + { + if (f & (CHAR_START << i)) + { + ptr[0] = 255; + ptr[1] = 255; + ptr[2] = 255; + } + ptr += 3; + } /* for i */ + } /* for x */ + } /* for y */ + + DBG (DBG_debug, "xcam_add_text: exit vw=%d, vh=%d\n", width, height); + status = (SANE_STATUS_GOOD); + return status; + +} + + +/* Update the info line with the latest size information. */ +static void +update_param (GSGDialog * dialog, void *arg) +{ + gchar buf[200]; + + DBG (DBG_debug, "xcam: update_param: enter\n"); + + if (dialog == NULL) + return; + + if (!win.info_label) + return; + + if (sane_get_parameters (gsg_dialog_get_device (dialog), &win.params) + == SANE_STATUS_GOOD) + { + double size = + (double) win.params.bytes_per_line * (double) win.params.lines; + const char *unit = "B"; + + if (win.params.lines == -1) + { + snprintf (buf, sizeof (buf), "%dxunknown: unknown size", + win.params.pixels_per_line); + } + else + { + if (win.params.format >= SANE_FRAME_RED + && win.params.format <= SANE_FRAME_BLUE) + size *= 3; + + if (size >= 1024 * 1024) + { + size /= 1024 * 1024; + unit = "MByte"; + } + else if (size >= 1024) + { + size /= 1024; + unit = "kByte"; + } + snprintf (buf, sizeof (buf), + "%dx%d %1.1f %s \n%6ld f_count\n%2.2f fps %2.2f fps_av", + win.params.pixels_per_line, win.params.lines, size, unit, + win.f_count, win.fps, win.fps_av); + } + } + else + snprintf (buf, sizeof (buf), "Invalid parameters."); + gtk_label_set (GTK_LABEL (win.info_label), buf); + + DBG (DBG_debug, "xcam: update_param: exit\n"); +} + +static void +pref_xcam_save (void) +{ + char filename[PATH_MAX]; + int fd; + + DBG (DBG_debug, "xcam: pref_xcam_save: enter\n"); + /* first save xcam-specific preferences: */ + gsg_make_path (sizeof (filename), filename, "xcam", "xcam", 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); + DBG (DBG_debug, "xcam: pref_xcam_save: exit\n"); +} + +static void +pref_xcam_restore (void) +{ + char filename[PATH_MAX]; + int fd; + + DBG (DBG_debug, "xcam: pref_xcam_restore: enter\n"); + gsg_make_path (sizeof (filename), filename, "xcam", "xcam", 0, ".rc"); + fd = open (filename, O_RDONLY); + if (fd >= 0) + { + preferences_restore (fd); + close (fd); + } + if (!preferences.filename) + preferences.filename = strdup (OUTFILENAME); + DBG (DBG_debug, "xcam: pref_xcam_restore: exit\n"); +} + + +static void +load_settings (const char *filename, int silent) +{ + char buf[2 * PATH_MAX]; + char *end; + int fd; + + DBG (DBG_debug, "xcam: load_settings: enter\n"); + + fd = open (filename, O_RDONLY); + if (fd < 0) + { + if (!silent) + { + snprintf (buf, sizeof (buf), "Failed to open file %s: %s.", + filename, strerror (errno)); + gsg_error (buf); + } + return; /* fail silently */ + } + + /* first, read off the devicename that these settings are for: */ + read (fd, buf, sizeof (buf)); + buf[sizeof (buf) - 1] = '\0'; + end = strchr (buf, '\n'); + if (!end) + { + if (!silent) + { + snprintf (buf, sizeof (buf), "File %s is malformed.", filename); + gsg_error (buf); + } + return; + } + *end = '\0'; + if (strcmp (dialog->dev_name, buf) != 0) + switch_device_by_name (buf); + + /* position right behind device name: */ + lseek (fd, strlen (buf) + 1, SEEK_SET); + + sanei_load_values (fd, dialog->dev); + close (fd); + + gsg_refresh_dialog (dialog); + + DBG (DBG_debug, "xcam: load_settings: exit\n"); +} + +static int +make_default_filename (size_t buf_size, char *buf, const char *dev_name) +{ + return gsg_make_path (buf_size, buf, "xcam", 0, dev_name, ".rc"); +} + +static void +load_defaults (int silent) +{ + char filename[PATH_MAX]; + int fd; + + DBG (DBG_debug, "xcam, load_defaults: enter\n"); + if (make_default_filename (sizeof (filename), filename, dialog->dev_name) + < 0) + return; + if (fd < 0) + return; + load_settings (filename, silent); + sanei_load_values (fd, dialog->dev); + DBG (DBG_debug, "xcam, load_defaults: exit\n"); +} + +void +device_name_dialog_cancel (GtkWidget * widget, gpointer data) +{ + gtk_widget_destroy (data); +} + +void +device_name_dialog_ok (GtkWidget * widget, gpointer data) +{ + GtkWidget *text = data; + const char *name; + + name = gtk_entry_get_text (GTK_ENTRY (text)); + if (!name) + return; /* huh? how come? */ + switch_device_by_name (name); + + gtk_widget_destroy (gtk_widget_get_toplevel (text)); +} + +static void +prompt_for_device_name (GtkWidget * widget, gpointer data) +{ + GtkWidget *vbox, *hbox, *label, *text; + GtkWidget *button, *dialog; + + DBG (DBG_debug, "xcam: prompt_for_device_name: enter\n"); + + dialog = gtk_window_new (GTK_WINDOW_TOPLEVEL); + gtk_window_position (GTK_WINDOW (dialog), GTK_WIN_POS_MOUSE); + gtk_window_set_title (GTK_WINDOW (dialog), "Device name"); + + /* create the main vbox */ + vbox = gtk_vbox_new (TRUE, 5); + gtk_container_border_width (GTK_CONTAINER (vbox), 5); + gtk_container_add (GTK_CONTAINER (dialog), vbox); + + hbox = gtk_hbox_new (FALSE, 0); + gtk_container_add (GTK_CONTAINER (vbox), hbox); + + label = gtk_label_new ("Device name:"); + gtk_container_add (GTK_CONTAINER (hbox), label); + gtk_widget_show (label); + + text = gtk_entry_new (); + gtk_container_add (GTK_CONTAINER (hbox), text); + + gtk_widget_show (hbox); + + /* the confirmation button */ + hbox = gtk_hbox_new (FALSE, 0); + gtk_container_add (GTK_CONTAINER (vbox), hbox); + + button = gtk_button_new_with_label ("OK"); + GTK_WIDGET_SET_FLAGS (button, GTK_CAN_DEFAULT); + gtk_signal_connect (GTK_OBJECT (button), "clicked", + (GtkSignalFunc) device_name_dialog_ok, text); + gtk_box_pack_start (GTK_BOX (hbox), button, TRUE, TRUE, 5); + gtk_widget_grab_default (button); + gtk_widget_show (button); + + button = gtk_button_new_with_label ("Cancel"); + gtk_signal_connect (GTK_OBJECT (button), "clicked", + (GtkSignalFunc) device_name_dialog_cancel, dialog); + gtk_box_pack_start (GTK_BOX (hbox), button, TRUE, TRUE, 5); + gtk_widget_show (button); + + gtk_widget_show (hbox); + gtk_widget_show (text); + gtk_widget_show (vbox); + gtk_widget_show (dialog); + DBG (DBG_debug, "xcam: prompt_for_device_name: exit\n"); +} + +static void +exit_callback (GtkWidget * widget, gpointer data) +{ + if (dialog) + gsg_destroy_dialog (dialog); + dialog = 0; + exit (0); +} + +static void +save_defaults_callback (GtkWidget * widget, gpointer data) +{ + char buf[PATH_MAX]; + + if (make_default_filename (sizeof (buf), buf, dialog->dev_name) < 0) + return; + save_settings (buf); +} + +static void +load_defaults_callback (GtkWidget * widget, gpointer data) +{ + load_defaults (0); +} + +static void +save_as_callback (GtkWidget * widget, gpointer data) +{ + if (gsg_get_filename ("File to save settings to", device_settings_filename, + sizeof (device_settings_filename), + device_settings_filename) < 0) + return; + save_settings (device_settings_filename); +} + +static void +load_from_callback (GtkWidget * widget, gpointer data) +{ + if (gsg_get_filename + ("File to load settings from", device_settings_filename, + sizeof (device_settings_filename), device_settings_filename) < 0) + return; + load_settings (device_settings_filename, 0); +} + +static void +buttons_disable (void) +{ + + DBG (DBG_debug, "xcam: buttons_disable: enter\n"); + + gsg_set_sensitivity (dialog, FALSE); + gtk_widget_set_sensitive (win.play_stop_label, FALSE); + gtk_widget_set_sensitive (win.save_frame_label, FALSE); + gtk_widget_set_sensitive (win.rgb_bgr_label, FALSE); + gtk_widget_set_sensitive (win.txt_label, FALSE); + + DBG (DBG_debug, "xcam: buttons_disable: exit\n"); +} + +static void +buttons_enable (void) +{ + + DBG (DBG_debug, "xcam: buttons_enable: enter\n"); + + gsg_set_sensitivity (dialog, TRUE); + gtk_widget_set_sensitive (win.play_stop_label, TRUE); + gtk_widget_set_sensitive (win.save_frame_label, TRUE); + gtk_widget_set_sensitive (win.rgb_bgr_label, TRUE); + gtk_widget_set_sensitive (win.txt_label, TRUE); + + DBG (DBG_debug, "xcam: buttons_enable: exit\n"); +} + +static GtkWidget * +build_files_menu (void) +{ + GtkWidget *menu, *item; + + DBG (DBG_debug, "xcam: build_files_menu: enter\n"); + + 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) exit_callback, 0); + gtk_widget_show (item); + + DBG (DBG_debug, "xcam: build_files_menu: exit\n"); + return menu; +} + +static gint +delayed_switch (gpointer data) +{ + switch_device (data); + load_defaults (1); + return FALSE; +} + +static void +device_activate_callback (GtkWidget * widget, gpointer data) +{ + gtk_idle_add (delayed_switch, data); +} + +static GtkWidget * +build_device_menu (void) +{ + GtkWidget *menu, *item; + SANE_Status status; + int i; + + menu = gtk_menu_new (); + + status = sane_get_devices (&device, SANE_FALSE); + if (status != SANE_STATUS_GOOD) + { + fprintf (stderr, "%s: %s\n", prog_name, sane_strstatus (status)); + exit (1); + } + + for (i = 0; device[i]; ++i) + { + item = gtk_menu_item_new_with_label ((char *) device[i]->name); + gtk_container_add (GTK_CONTAINER (menu), item); + gtk_signal_connect (GTK_OBJECT (item), "activate", + (GtkSignalFunc) device_activate_callback, + (gpointer) device[i]); + gtk_widget_show (item); + } + + item = gtk_menu_item_new (); + gtk_container_add (GTK_CONTAINER (menu), item); + gtk_widget_show (item); + + item = gtk_menu_item_new_with_label ("Refresh device list..."); + gtk_signal_connect (GTK_OBJECT (item), "activate", + (GtkSignalFunc) rescan_devices, 0); + gtk_container_add (GTK_CONTAINER (menu), item); + gtk_widget_show (item); + + item = gtk_menu_item_new_with_label ("Specify device name..."); + gtk_signal_connect (GTK_OBJECT (item), "activate", + (GtkSignalFunc) prompt_for_device_name, 0); + gtk_container_add (GTK_CONTAINER (menu), item); + gtk_widget_show (item); + + return menu; +} + +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_xcam_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_xcam_save (); +} + +static void +pref_toggle_twocolumn (GtkWidget * widget, gpointer data) +{ + preferences.twocolumn_enabled = (GTK_CHECK_MENU_ITEM (widget)->active != 0); + gsg_set_twocolumn (dialog, preferences.twocolumn_enabled); + pref_xcam_save (); +} + +static GtkWidget * +build_preferences_menu (GSGDialog * dialog) +{ + GtkWidget *menu, *item; + + 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); + + /* twocolumn submenu: */ + + item = gtk_check_menu_item_new_with_label ("Show two column display"); + gtk_check_menu_item_set_state (GTK_CHECK_MENU_ITEM (item), + preferences.twocolumn_enabled); + gtk_menu_append (GTK_MENU (menu), item); + gtk_widget_show (item); + gtk_signal_connect (GTK_OBJECT (item), "toggled", + (GtkSignalFunc) pref_toggle_twocolumn, 0); + + item = gtk_menu_item_new (); + gtk_container_add (GTK_CONTAINER (menu), item); + gtk_widget_show (item); + + item = gtk_menu_item_new_with_label ("Save as default settings"); + gtk_container_add (GTK_CONTAINER (menu), item); + gtk_signal_connect (GTK_OBJECT (item), "activate", + (GtkSignalFunc) save_defaults_callback, 0); + gtk_widget_show (item); + + item = gtk_menu_item_new_with_label ("Load default settings"); + gtk_container_add (GTK_CONTAINER (menu), item); + gtk_signal_connect (GTK_OBJECT (item), "activate", + (GtkSignalFunc) load_defaults_callback, 0); + gtk_widget_show (item); + + item = gtk_menu_item_new (); + gtk_container_add (GTK_CONTAINER (menu), item); + gtk_widget_show (item); + + item = gtk_menu_item_new_with_label ("Save settings as..."); + gtk_container_add (GTK_CONTAINER (menu), item); + gtk_signal_connect (GTK_OBJECT (item), "activate", + (GtkSignalFunc) save_as_callback, 0); + gtk_widget_show (item); + + item = gtk_menu_item_new_with_label ("Load settings from..."); + gtk_container_add (GTK_CONTAINER (menu), item); + gtk_signal_connect (GTK_OBJECT (item), "activate", + (GtkSignalFunc) load_from_callback, 0); + gtk_widget_show (item); + + return menu; +} + +static void +rescan_devices (GtkWidget * widget, gpointer client_data, gpointer call_data) +{ + gtk_widget_destroy (GTK_WIDGET (win.devices.menu)); + win.devices.menu = build_device_menu (); + gtk_menu_item_set_submenu (GTK_MENU_ITEM (win.devices.item), + win.devices.menu); +} + +#define READ_SANE_PIXEL(buf, buf_end, format, depth, r, g, b) \ +{ \ + switch (format) \ + { \ + case SANE_FRAME_GRAY: \ + switch (depth) \ + { \ + case 1: \ + if (buf + 1 > buf_end) \ + goto end_of_buffer; \ + (r) = (g) = (b) = (*buf & src_mask) ? 0x0000 : 0xffff; \ + src_mask >>= 1; \ + if (src_mask == 0x00) \ + { \ + ++buf; \ + src_mask = 0x80; \ + } \ + break; \ + \ + case 8: \ + if (buf + 1 > buf_end) \ + goto end_of_buffer; \ + (r) = (g) = (b) = (*buf++ << 8); \ + break; \ + \ + case 16: \ + if (buf + 2 > buf_end) \ + goto end_of_buffer; \ + (r) = (g) = (b) = *((guint16 *) buf); \ + buf += 2; \ + break; \ + } \ + break; \ + \ + case SANE_FRAME_RGB: \ + switch (depth) \ + { \ + case 1: \ + case 8: \ + if (buf + 3 > buf_end) \ + goto end_of_buffer; \ + (r) = buf[0] << 8; (g) = buf[1] << 8; (b) = buf[2] << 8; \ + buf += 3; \ + break; \ + \ + case 16: \ + if (buf + 3 > buf_end) \ + goto end_of_buffer; \ + (r) = ((guint16 *)buf)[0]; \ + (g) = ((guint16 *)buf)[1]; \ + (b) = ((guint16 *)buf)[2]; \ + buf += 6; \ + break; \ + } \ + break; \ + \ + case SANE_FRAME_RED: \ + case SANE_FRAME_GREEN: \ + case SANE_FRAME_BLUE: \ + default: \ + fprintf (stderr, "%s: format %d not yet supported\n", \ + prog_name, (format)); \ + goto end_of_buffer; \ + } \ +} + +#define PUT_X11_PIXEL(buf, endian, depth, bpp, r, g, b, gl_map) \ +{ \ + switch (depth) \ + { \ + case 1: /* duh? A Sun3 or what?? */ \ + lum = 3*(r) + 5*(g) + 2*(b); \ + if (lum >= 5*0x8000) \ + *buf |= dst_mask; \ + dst_mask <<= 1; \ + if (dst_mask > 0xff) \ + { \ + buf += (bpp); \ + dst_mask = 0x01; \ + } \ + break; \ + \ + case 8: \ + lum = ((3*(r) + 5*(g) + 2*(b)) / (10 * 256/MAX_LUM)) >> 8; \ + if (lum >= MAX_LUM) \ + lum = MAX_LUM; \ + buf[0] = (gl_map)[lum]; \ + buf += (bpp); \ + break; \ + \ + case 15: \ + rgb = ( (((r) >> 11) << r_shift) /* 5 bits of red */ \ + | (((g) >> 11) << g_shift) /* 5 bits of green */ \ + | (((b) >> 11) << b_shift));/* 5 bits of blue */ \ + ((guint16 *)buf)[0] = rgb; \ + buf += (bpp); \ + break; \ + \ + case 16: \ + rgb = ( (((r) >> 11) << r_shift) /* 5 bits of red */ \ + | (((g) >> 10) << g_shift)/* 6 bits of green */ \ + | (((b) >> 11) << b_shift));/* 5 bits of blue */ \ + ((guint16 *)buf)[0] = rgb; \ + buf += (bpp); \ + break; \ + \ + case 24: \ + case 32: \ + if (bpp == 4) \ + { \ + rgb = ( (((r) >> 8) << r_shift) \ + | (((g) >> 8) << g_shift) \ + | (((b) >> 8) << b_shift)); \ + ((guint32 *)buf)[0] = rgb; \ + } \ + else \ + { \ + if ( ((endian) == GDK_LSB_FIRST && r_shift == 0) \ + || ((endian) == GDK_MSB_FIRST && b_shift == 0)) \ + { \ + buf[0] = (r) >> 8; buf[1] = (g) >> 8; buf[2] = (b) >> 8; \ + } \ + else \ + { \ + buf[0] = (b) >> 8; buf[1] = (g) >> 8; buf[2] = (r) >> 8; \ + } \ + } \ + buf += (bpp); \ + break; \ + } \ +} + +static void +input_available (gpointer ignore, gint source, GdkInputCondition cond) +{ + int x, pixels_per_line, bytes_per_line, dst_depth, src_depth; + guint32 r = 0, g = 0, b = 0, lum, rgb, src_mask, dst_mask; + gint r_shift, b_shift, g_shift; + size_t buf_size, remaining = win.remaining; + SANE_Byte *src, *src_end; + GdkByteOrder byte_order; + u_long bytes_per_pixel; + SANE_Frame format; + SANE_Status status; + SANE_Int len; + u_char *dst; + + double max_value = 60; /* min. 1 frame per min. */ + double frame_time = 50; /* dummy value */ + + DBG (DBG_debug, "xcam: input available: enter\n"); + + if (!win.playing) + /* looks like we got cancelled */ + goto stop_and_exit; + + buf_size = win.buf_backend_size; + format = win.params.format; + src_depth = win.params.depth; + dst_depth = win.canvas.gdk_image->depth; + pixels_per_line = win.params.pixels_per_line; + bytes_per_line = win.canvas.gdk_image->bpl; + bytes_per_pixel = win.canvas.gdk_image->bpp; + byte_order = win.canvas.gdk_image->byte_order; + + x = win.x; + dst = win.data; + src_mask = 0x80; /* SANE has left most bit is most significant bit */ + dst_mask = 0x01; + + r_shift = win.canvas.gdk_image->visual->red_shift; + g_shift = win.canvas.gdk_image->visual->green_shift; + b_shift = win.canvas.gdk_image->visual->blue_shift; + + while (1) + { + DBG (DBG_debug, "input available: enter sane_read\n"); + status = sane_read (gsg_dialog_get_device (dialog), + win.buf + remaining, buf_size - remaining, &len); + if (status != SANE_STATUS_GOOD) + { + if (status == SANE_STATUS_EOF) + { + display_image (&win.canvas); + stop_camera (); + if (win.playing) + { + next_frame (); /* arrange for next frame */ + return; + } + } + else + { + char buf[256]; + sprintf (buf, "Error during read: %s.", + sane_strstatus (status)); + gsg_error (buf); + } + win.playing = FALSE; + stop_camera (); + return; + } + win.f_count++; + update_param (dialog, 0); + win.i_time++; + if (win.i_time >= 30) + { + time (&win.time2); /* time marker */ + + frame_time = difftime (win.time2, win.time1); + + if (frame_time > max_value) + { + frame_time = max_value; + } + + win.fps_old3 = win.fps_old2; + win.fps_old2 = win.fps_old1; + win.fps_old1 = win.fps; + win.fps = 30 / frame_time; /* correction for loop 30 */ + /* avarage last 4 frames times */ + win.fps_av = + (win.fps_old3 + win.fps_old2 + win.fps_old1 + win.fps) / 4; + + DBG (DBG_debug, + "xcam: input_available fps count=%d, frame_time * 30 = %2.3f, fps=%2.3f, fps_av=%2.3f\n", + win.f_count, frame_time, win.fps, win.fps_av); + win.i_time = 0; + + time (&win.time1); /* time marker for new sequence */ + } + update_param (dialog, 0); + + if (!len) + break; + + if (win.value_txt == 1) + { + strcpy (win.picmsg_ps, "xcam "); + + status = + xcam_add_text (win.buf, win.params.pixels_per_line, + win.params.lines, win.picmsg_ps); + if (status != SANE_STATUS_GOOD) + { + DBG (DBG_info, "xcam: input available status NOK\n"); + return; + } + } + + src = win.buf; + src_end = src + len + remaining; + + while (1) + { + if (win.value_rgb == 0) + { + READ_SANE_PIXEL (src, src_end, format, src_depth, r, g, b); + } + else if (win.value_rgb == 1) + { + READ_SANE_PIXEL (src, src_end, format, src_depth, b, g, r); + } + PUT_X11_PIXEL (dst, byte_order, dst_depth, bytes_per_pixel, r, g, b, + win.canvas.graylevel); + if (++x >= pixels_per_line) + { + x = 0; + dst += bytes_per_line - pixels_per_line * bytes_per_pixel; + } + } + end_of_buffer: + remaining = src_end - src; + } + win.data = dst; + win.x = x; + win.remaining = remaining; + DBG (DBG_debug, "xcam: input available: exit\n"); + return; + +stop_and_exit: + win.playing = FALSE; + stop_camera (); + DBG (DBG_debug, "xcam: input available: stop and exit\n"); + return; +} + +static void +next_frame (void) +{ + char buf[256]; + SANE_Status status; + int fd; + DBG (DBG_debug, "xcam: next frame enter\n"); + buttons_disable (); + + DBG (DBG_debug, "xcam: next frame, start gsg_sync\n"); + gsg_sync (dialog); + + status = sane_start (gsg_dialog_get_device (dialog)); + if (status != SANE_STATUS_GOOD) + { + sprintf (buf, "Failed to start webcam: %s.", sane_strstatus (status)); + gsg_error (buf); + win.playing = FALSE; + stop_camera (); + return; + } + + status = sane_get_parameters (gsg_dialog_get_device (dialog), &win.params); + if (status != SANE_STATUS_GOOD) + { + sprintf (buf, "Failed to get parameters: %s.", sane_strstatus (status)); + gsg_error (buf); + win.playing = FALSE; + stop_camera (); + return; + } + + if (!win.canvas.gdk_image + || win.canvas.gdk_image->width != win.params.pixels_per_line + || win.canvas.gdk_image->height != win.params.lines) + { + GdkImageType image_type = GDK_IMAGE_FASTEST; + + if (win.canvas.gdk_image) + gdk_image_destroy (win.canvas.gdk_image); +#ifdef __alpha__ + /* Some X servers seem to have a problem with shared images that + have a width that is not a multiple of 8. Duh... ;-( */ + if (win.params.pixels_per_line % 8) + image_type = GDK_IMAGE_NORMAL; +#endif + win.canvas.gdk_image = + gdk_image_new (image_type, + gdk_window_get_visual (win.canvas.preview->window), + win.params.pixels_per_line, win.params.lines); + gtk_widget_set_usize (win.canvas.preview, + win.params.pixels_per_line, win.params.lines); + } + win.data = win.canvas.gdk_image->mem; + win.x = 0; + win.remaining = 0; + + + + buttons_enable (); + if (sane_set_io_mode (gsg_dialog_get_device (dialog), SANE_TRUE) + == SANE_STATUS_GOOD + && sane_get_select_fd (gsg_dialog_get_device (dialog), &fd) + == SANE_STATUS_GOOD) + win.gdk_input_tag = + gdk_input_add (fd, GDK_INPUT_READ, input_available, 0); + else + input_available (0, -1, 0); + + DBG (DBG_debug, "xcam: next frame: exit\n"); +} + +static void +play_stop_button (GtkWidget * widget, gpointer client_data, + gpointer call_data) +{ + DBG (DBG_debug, "xcam: play_stop_button: enter\n"); + if (!dialog) + return; + + if (win.playing) + { + win.playing = FALSE; + gtk_label_set (GTK_LABEL (win.play_stop_label), " Play "); + DBG (DBG_debug, "xcam: wait for play button to be pushed\n"); + } + else if (win.gdk_input_tag < 0) + { + win.playing = TRUE; + gtk_label_set (GTK_LABEL (win.play_stop_label), " Stop "); + DBG (DBG_debug, "xcam: wait for stop button to be pushed\n"); + next_frame (); + } + DBG (DBG_debug, "xcam: play_stop_button: exit\n"); +} + +/* Invoked when the save frame button is pressed */ +static void +save_frame_button (GtkWidget * widget, gpointer client_data, + gpointer call_data) +{ + char buf[256]; + char testfilename[256]; + + DBG (DBG_debug, "xcam: save_frame_button\n"); + if (!dialog) + return; + + if (win.saving) + win.saving = FALSE; + else if (win.gdk_input_tag < 0) + { + win.saving = TRUE; + gtk_label_set (GTK_LABEL (win.save_frame_label), "Saving started"); +/* ------------------------------------------ */ + + /* test for pnm formats */ + strncpy (testfilename, preferences.filename, sizeof (testfilename)); + testfilename[sizeof (testfilename)] = 0; + g_strreverse (testfilename); + if (!((!strncmp (testfilename, "mnp.", 4)) || + (!strncmp (testfilename, "mgp.", 4)) || + (!strncmp (testfilename, "mbp.", 4)) || + (!strncmp (testfilename, "mpp.", 4)) || + (!strncmp (testfilename, "MNP.", 4)) || + (!strncmp (testfilename, "MGP.", 4)) || + (!strncmp (testfilename, "MBP.", 4)) || + (!strncmp (testfilename, "MPP.", 4)))) + { + snprintf (buf, sizeof (buf), + "Failed to scan, wrong file extension, use pnm, pgm, pbm or ppm `%s'", + preferences.filename); + gsg_error (buf); + return; + } + win.out = fopen (preferences.filename, "w"); + if (!win.out) + { + snprintf (buf, sizeof (buf), "Failed to open `%s': %s", + preferences.filename, strerror (errno)); + gsg_error (buf); + return; + } + } + buttons_disable (); + save_frame (); + buttons_enable (); + gsg_sync (dialog); + gtk_label_set (GTK_LABEL (win.save_frame_label), "Save\nFrame"); + DBG (DBG_debug, "xcam: save_frame_button: exit\n"); +} + +/* Invoked when the TXT button is pressed */ +static void +txt_button (GtkWidget * widget, gpointer client_data, gpointer call_data) +{ + DBG (DBG_debug, "xcam: txt_button\n"); + if (!dialog) + return; + + if (win.saving) + { + win.saving = FALSE; + win.value_txt = 0; + gtk_label_set (GTK_LABEL (win.txt_label), " TXT \n OFF "); + } + else if (win.gdk_input_tag < 0) + { + win.saving = TRUE; + gtk_label_set (GTK_LABEL (win.txt_label), " TXT \n ON "); + win.value_txt = 1; + } + gsg_sync (dialog); + DBG (DBG_debug, "xcam: txt_button: exit\n"); +} + +/* Invoked when the RGB-BGR button is pressed */ +static void +rgb_bgr_button (GtkWidget * widget, gpointer client_data, gpointer call_data) +{ + DBG (DBG_debug, "xcam: rgb_bgr_button\n"); + if (!dialog) + return; + + if (win.saving) + { + win.saving = FALSE; + win.value_rgb = 0; + gtk_label_set (GTK_LABEL (win.rgb_bgr_label), "RGB"); + } + else if (win.gdk_input_tag < 0) + { + win.saving = TRUE; + gtk_label_set (GTK_LABEL (win.rgb_bgr_label), "BGR"); + win.value_rgb = 1; + } + gsg_sync (dialog); + DBG (DBG_debug, "xcam: rgb_bgr_button: exit\n"); +} + +static void +save_frame (void) +{ + SANE_Handle dev = gsg_dialog_get_device (dialog); + + const char *frame_type = 0; + char buf[256]; + int fd; + + DBG (DBG_debug, "xcam: save_frame: enter\n"); + + win.x = win.y = 0; + + win.num_bytes = win.params.lines * win.params.bytes_per_line; + win.bytes_read = 0; + win.have_odd_byte = FALSE; + + switch (win.params.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 (!win.header_size) + { + switch (win.params.format) + { + case SANE_FRAME_RED: + case SANE_FRAME_GREEN: + case SANE_FRAME_BLUE: + if (win.params.depth > 8) + { + gsg_set_sensitivity (dialog, TRUE); + snprintf (buf, sizeof (buf), + "Separate channel transfers are not supported " + "with %d bits/channel.", win.params.depth); + gsg_error (buf); + + buttons_enable (); + return; + } + /*FALLTHROUGH*/ case SANE_FRAME_RGB: + fprintf (win.out, "P6\n# SANE data follows\n%d %d\n%d\n", + win.params.pixels_per_line, win.params.lines, + (win.params.depth <= 8) ? 255 : 65535); + break; + + case SANE_FRAME_GRAY: + if (win.params.depth == 1) + fprintf (win.out, "P4\n# SANE data follows\n%d %d\n", + win.params.pixels_per_line, win.params.lines); + else + fprintf (win.out, "P5\n# SANE data follows\n%d %d\n%d\n", + win.params.pixels_per_line, win.params.lines, + (win.params.depth <= 8) ? 255 : 65535); + break; + } + win.header_size = ftell (win.out); + fwrite (win.buf, 1, win.num_bytes, win.out); + fclose (win.out); + win.out = 0; + } + if (win.params.format >= SANE_FRAME_RED + && win.params.format <= SANE_FRAME_BLUE) + fseek (win.out, + win.header_size + win.params.format - SANE_FRAME_RED, SEEK_SET); + snprintf (buf, sizeof (buf), "Receiving %s data for `%s'...", + frame_type, preferences.filename); + + win.input_tag = -1; + if (sane_set_io_mode (dev, SANE_TRUE) == SANE_STATUS_GOOD + && sane_get_select_fd (dev, &fd) == SANE_STATUS_GOOD) + win.input_tag = gdk_input_add (fd, GDK_INPUT_READ | GDK_INPUT_EXCEPTION, + input_available, 0); + else + { + while (gtk_events_pending ()) + gtk_main_iteration (); + input_available (0, -1, GDK_INPUT_READ); + } + + DBG (DBG_debug, "xcam: save_frame: exit\n"); +} + +static void +xcam_exit (void) +{ + static int active = 0; + + DBG (DBG_debug, "xcam: xcam_exit: enter\n"); + if (active) + return; + + active = 1; + pref_xcam_save (); + sane_exit (); + /* this has the habit of calling exit itself: */ + gtk_exit (0); + DBG (DBG_debug, "xcam: xcam_exit: exit\n"); +} + +/* Invoked when window manager's "delete" (or "close") function is + invoked. */ +static gint +xcam_win_delete (GtkWidget * w, gpointer data) +{ + xcam_exit (); + return FALSE; +} + +static void +browse_filename_callback (GtkWidget * widget, gpointer data) +{ + char filename[1024]; + + DBG (DBG_debug, "xcam: browse_filename_callback\n"); + 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 (win.filename_entry), filename); + + if (preferences.filename) + free ((void *) preferences.filename); + preferences.filename = strdup (filename); + DBG (DBG_debug, "xcam: browse_filename_callback: exit\n"); +} + +static void +filename_changed_callback (GtkWidget * widget, gpointer data) +{ + DBG (DBG_debug, "xcam: filename_changed_callback\n"); + if (preferences.filename) + free ((void *) preferences.filename); + preferences.filename = strdup (gtk_entry_get_text (GTK_ENTRY (widget))); + pref_xcam_save (); + DBG (DBG_debug, "xcam: filename_changed_callbackcallback: exit\n"); +} + +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\ +-B, --buffersize set buffersize 1024 * 1024\n\ +-V, --version print version information\n", prog_name); +} + +int +main (int argc, char **argv) +{ + GtkWidget *menu, *menu_bar, *menu_bar_item, *preview_vbox; + GtkWidget *hbox, *vbox, *button, *alignment, *frame, *label, *text, + *scrolled_window; + int i; + int ch; + SANE_Status status; + + DBG_INIT (); + + DBG (DBG_debug, "xcam: main\n"); + DBG (DBG_error, "xcam (version: %s, package: %s) starting\n", VERSION, + PACKAGE); + + win.buf_backend_size = (32 * 1024); + + pref_xcam_restore (); + + prog_name = strrchr (argv[0], '/'); + if (prog_name) + ++prog_name; + else + prog_name = argv[0]; + + /* turn on by default as we don't support graphical geometry selection */ + preferences.advanced = 1; + + status = sane_init (0, 0); + if (status != SANE_STATUS_GOOD) + { + DBG (DBG_fatal, "init: sane_main failed: %s\n", + sane_strstatus (status)); + exit (1); + } + + if (argc > 1) + { + + while ((ch = getopt_long (argc, argv, "hBV", long_options, 0)) != EOF) + { + switch (ch) + { + case 'V': + printf ("xcam (%s) %s\n", PACKAGE, VERSION); + exit (0); + + case 'B': + win.buf_backend_size = 1024 * 1024; + break; + case 'h': + default: + usage (); + exit (0); + } + } + } + + DBG (DBG_debug, "xcam.main: buf_backend_size 0x%x\n", win.buf_backend_size); + + win.buf = malloc (win.buf_backend_size); + + gdk_set_show_events (0); + gtk_init (&argc, &argv); + + atexit (xcam_exit); + + win.gdk_input_tag = -1; + win.shell = gtk_window_new (GTK_WINDOW_TOPLEVEL); + gtk_window_set_title (GTK_WINDOW (win.shell), (char *) prog_name); + gtk_signal_connect (GTK_OBJECT (win.shell), "delete_event", + GTK_SIGNAL_FUNC (xcam_win_delete), NULL); + + vbox = gtk_vbox_new (FALSE, 0); + gtk_container_border_width (GTK_CONTAINER (vbox), 0); + gtk_container_add (GTK_CONTAINER (win.shell), vbox); + gtk_widget_show (vbox); + + menu_bar = gtk_menu_bar_new (); + + win.devices.menu = build_device_menu (); + + /* "Files" entry: */ + menu_bar_item = gtk_menu_item_new_with_label ("File"); + gtk_container_add (GTK_CONTAINER (menu_bar), menu_bar_item); + menu = build_files_menu (); + gtk_menu_item_set_submenu (GTK_MENU_ITEM (menu_bar_item), menu); + gtk_widget_show (menu_bar_item); + + /* "Devices" entry: */ + win.devices.item = gtk_menu_item_new_with_label ("Devices"); + gtk_container_add (GTK_CONTAINER (menu_bar), win.devices.item); + gtk_menu_item_set_submenu (GTK_MENU_ITEM (win.devices.item), + win.devices.menu); + gtk_widget_show (win.devices.item); + + /* "Preferences" entry: */ + menu_bar_item = gtk_menu_item_new_with_label ("Preferences"); + gtk_container_add (GTK_CONTAINER (menu_bar), menu_bar_item); + gtk_menu_item_set_submenu (GTK_MENU_ITEM (menu_bar_item), + build_preferences_menu (dialog)); + gtk_widget_show (menu_bar_item); + + gtk_box_pack_start (GTK_BOX (vbox), menu_bar, FALSE, FALSE, 0); + gtk_widget_show (menu_bar); + + /* add device info at top: */ + frame = gtk_frame_new (0); + gtk_container_border_width (GTK_CONTAINER (frame), 8); + gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_ETCHED_IN); + gtk_box_pack_start (GTK_BOX (vbox), frame, FALSE, FALSE, 0); + gtk_widget_show (frame); + + win.device_info_label = gtk_label_new (""); + gtk_widget_show (win.device_info_label); + gtk_container_add (GTK_CONTAINER (frame), win.device_info_label); + + hbox = gtk_hbox_new (FALSE, 0); + gtk_container_add (GTK_CONTAINER (vbox), hbox); + gtk_widget_show (hbox); + + /* create the device dialog box: */ + win.dialog_box = gtk_vbox_new (FALSE, 0); + gtk_widget_show (win.dialog_box); + + DBG (DBG_debug, "xcam main, preview vbox on the left hand side \n"); + /* the preview vbox on the left hand side: */ + preview_vbox = gtk_vbox_new (FALSE, 0); + gtk_box_pack_start (GTK_BOX (hbox), preview_vbox, TRUE, TRUE, 0); + + frame = gtk_frame_new ("Image view"); + gtk_container_border_width (GTK_CONTAINER (frame), 8); + gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_ETCHED_IN); + gtk_container_add (GTK_CONTAINER (preview_vbox), frame); + + alignment = gtk_alignment_new (0, 0.5, 1.0, 1.0); + gtk_box_pack_start (GTK_BOX (preview_vbox), alignment, TRUE, TRUE, 0); + + win.canvas.preview = gtk_drawing_area_new (); + gtk_drawing_area_size (GTK_DRAWING_AREA (win.canvas.preview), 320, 200); + gtk_widget_set_events (win.canvas.preview, CANVAS_EVENT_MASK); + gtk_signal_connect (GTK_OBJECT (win.canvas.preview), "event", + (GtkSignalFunc) canvas_events, 0); + gtk_container_add (GTK_CONTAINER (frame), win.canvas.preview); + + gtk_widget_show (win.canvas.preview); + gtk_widget_show (alignment); + gtk_widget_show (frame); + gtk_widget_show (preview_vbox); + + win.canvas.graylevel_cmap = gdk_colormap_get_system (); + for (i = 0; i < NELEMS (win.canvas.graylevel); ++i) + { + GdkColor color; + color.red = color.green = color.blue = + i * 0xffff / (NELEMS (win.canvas.graylevel) - 1); + gdk_color_alloc (win.canvas.graylevel_cmap, &color); + win.canvas.graylevel[i] = color.pixel; + } + +#if 0 + { + win.canvas.cube_cmap + = gdk_colormap_new (win.canvas.preview->window->visual, 1); + for (i = 0; i < NELEMS (win.canvas.cube_colors); ++i) + { + win.canvas.cube_colors[i].pixel = i; + win.canvas.cube_colors[i].red = ((i / 30) % 5) * 0xffff / 4; + win.canvas.cube_colors[i].green = ((i / 5) % 6) * 0xffff / 5; + win.canvas.cube_colors[i].blue = ((i % 5)) * 0xffff / 4; + } + gdk_colors_store (win.canvas.cube_cmap, win.canvas.cube_colors, + NELEMS (win.canvas.cube_colors)); + gdk_window_set_colormap (win.shell->window, win.canvas.cube_cmap); + } +#endif + + DBG (DBG_debug, "xcam main, use a scrolled window \n"); + + /* use a scrolled window to show the device options, as in xscanimage */ + scrolled_window = gtk_scrolled_window_new (NULL, NULL); + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_window), + GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC); + gtk_scrolled_window_set_placement (GTK_SCROLLED_WINDOW (scrolled_window), + GTK_CORNER_TOP_RIGHT); + gtk_scrolled_window_add_with_viewport (GTK_SCROLLED_WINDOW + (scrolled_window), win.dialog_box); + gtk_container_add (GTK_CONTAINER (hbox), scrolled_window); + + gtk_widget_show (GTK_WIDGET (scrolled_window)); + + if (device[0]) + { + switch_device (device[0]); + load_defaults (1); + } + else + { + DBG (DBG_fatal, + " No vidcams were identified. If you were expecting something\n" + " different, check that the vidcam is plugged in, turned on and\n" + " detected by sane-find-scanner (if appropriate). Please read\n" + " the documentation which came with this software (README, FAQ,\n" + " manpages).\n"); + atexit (xcam_exit); + } + + if (dialog && gsg_dialog_get_device (dialog) + && (sane_get_parameters (gsg_dialog_get_device (dialog), &win.params) + == SANE_STATUS_GOOD)) + { + gtk_widget_set_usize (win.canvas.preview, + win.params.pixels_per_line, win.params.lines); + } + + /* The bottom row */ + + DBG (DBG_debug, "xcam main, button row: info\n"); + + /* 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); + + win.info_label = + gtk_label_new ("0 x 0 0 kByte \n0 f_count \n0 fps 0 fps_av"); + gtk_box_pack_start (GTK_BOX (hbox), win.info_label, FALSE, FALSE, 0); + gtk_widget_show (win.info_label); + + win.f_count = 0; + win.fps = 0; + win.fps_av = 0; + win.i_time = 0; + time (&win.time1); /* first time marker */ + update_param (dialog, 0); + + DBG (DBG_debug, "xcam main, bottom row: rgb-bgr button\n"); + + /* The TXT button */ + button = gtk_button_new_with_label (" TXT "); + win.txt_label = GTK_BIN (button)->child; + gtk_signal_connect (GTK_OBJECT (button), "clicked", + (GtkSignalFunc) txt_button, dialog); + gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, FALSE, 0); + gtk_widget_show (button); + DBG (DBG_debug, "xcam main, bottom row: txt button\n"); + + /* The RGB-BGR button */ + button = gtk_button_new_with_label ("RGB"); + win.rgb_bgr_label = GTK_BIN (button)->child; + gtk_signal_connect (GTK_OBJECT (button), "clicked", + (GtkSignalFunc) rgb_bgr_button, dialog); + gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, FALSE, 0); + gtk_widget_show (button); + + DBG (DBG_debug, "xcam main, bottom row: play button\n"); + + /* The Play button */ + button = gtk_button_new_with_label (" Play "); + win.play_stop_label = GTK_BIN (button)->child; + gtk_signal_connect (GTK_OBJECT (button), "clicked", + (GtkSignalFunc) play_stop_button, dialog); + gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, FALSE, 0); + gtk_widget_show (button); + + DBG (DBG_debug, "xcam main, bottom row: save frame button\n"); + + /* The Save Frame button */ + button = gtk_button_new_with_label ("Save\nFrame"); + win.save_frame_label = GTK_BIN (button)->child; + gtk_signal_connect (GTK_OBJECT (button), "clicked", + (GtkSignalFunc) save_frame_button, dialog); + gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, FALSE, 0); + gtk_widget_show (button); + + DBG (DBG_debug, "xcam main, bottom row: output filename part\n"); + + /* output filename part */ + 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 (hbox), 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); + + 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); + + gtk_widget_show (win.shell); + gtk_main (); + + pref_xcam_save (); + + DBG (DBG_debug, "xcam main exit\n"); + return 0; +} diff --git a/src/xscanimage-gimp-1_0-compat.h b/src/xscanimage-gimp-1_0-compat.h new file mode 100644 index 0000000..1647962 --- /dev/null +++ b/src/xscanimage-gimp-1_0-compat.h @@ -0,0 +1,47 @@ +/* xscanimage-gimp-1_0-compat.h -- stay compatible with gimp 1.0 + + 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. + + Copied from xsane (written by Oliver Rauch) +*/ + +#ifndef XSCANIMAGE_GIMP_1_0_COMPAT_H +#define XSCANIMAGE_GIMP_1_0_COMPAT_H + +#define GimpPlugInInfo GPlugInInfo +#define GimpParam GParam +#define GimpParamDef GParamDef +#define GimpDrawable GDrawable +#define GimpPixelRgn GPixelRgn +#define GimpRunModeType GRunModeType +#define GimpImageType GImageType + +#define GIMP_PDB_INT32 PARAM_INT32 +#define GIMP_PDB_STATUS PARAM_STATUS +#define GIMP_PDB_CALLING_ERROR STATUS_CALLING_ERROR +#define GIMP_PDB_SUCCESS STATUS_SUCCESS +#define GIMP_RUN_INTERACTIVE RUN_INTERACTIVE +#define GIMP_RUN_NONINTERACTIVE RUN_NONINTERACTIVE +#define GIMP_RUN_WITH_LAST_VALS RUN_WITH_LAST_VALS +#define GIMP_EXTENSION PROC_EXTENSION +#define GIMP_RGB RGB +#define GIMP_RGB_IMAGE RGB_IMAGE +#define GIMP_GRAY GRAY +#define GIMP_GRAY_IMAGE GRAY_IMAGE +#define GIMP_RGBA_IMAGE RGBA_IMAGE +#define GIMP_NORMAL_MODE NORMAL_MODE + +#endif + diff --git a/src/xscanimage.c b/src/xscanimage.c new file mode 100644 index 0000000..a36324f --- /dev/null +++ b/src/xscanimage.c @@ -0,0 +1,2224 @@ +/* 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 "../include/lalloca.h" /* MUST come first for AIX! */ +#endif + +#include "../include/sane/config.h" +#include "../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/saneopts.h> +#include "../include/sane/sanei.h" + +#define BACKEND_NAME xscanimage +#include "../include/sane/sanei_debug.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> + +# ifdef HAVE_LIBGIMP_GIMPFEATURES_H +# include <libgimp/gimpfeatures.h> +# elif defined(ENABLE_GIMP_1_2) +# define GIMP_CHECK_VERSION(major, minor, micro) 0 +# endif /* HAVE_LIBGIMP_GIMPFEATURES_H */ + +#ifndef ENABLE_GIMP_1_2 +# define GIMP_HAVE_RESOLUTION_INFO +#endif /* !ENABLE_GIMP_1_2 */ + +# ifdef GIMP_CHECK_VERSION +# if GIMP_CHECK_VERSION(1,1,25) +/* ok, we have the new gimp interface */ +# else +/* we have the old gimp interface and need the compatibility header file */ +# include "xscanimage-gimp-1_0-compat.h" +# endif +# else +# ifdef ENABLE_GIMP_1_2 +/* we have the old gimp interface and need the compatibility header file */ +# include "xscanimage-gimp-1_0-compat.h" +# endif /* ENABLE_GIMP_1_2 */ +# endif + +static void query (void); +#ifndef ENABLE_GIMP_1_2 +static void run (const gchar * name, gint nparams, const GimpParam * param, + gint * nreturn_vals, GimpParam ** return_vals); +#else +static void run (char *name, int nparams, GimpParam * param, + int *nreturn_vals, GimpParam ** return_vals); +#endif /* !ENABLE_GIMP_1_2 */ + +GimpPlugInInfo PLUG_IN_INFO = { + NULL, /* init_proc */ + NULL, /* quit_proc */ + query, /* query_proc */ + run, /* run_proc */ +}; + +#endif /* HAVE_LIBGIMP_GIMP_H */ + +#define DBG_fatal 0 +#define DBG_error 1 +#define DBG_warning 2 +#define DBG_info 3 +#define DBG_debug 4 + +enum +{ + STANDALONE, SANE_GIMP_EXTENSION +}; + +static struct +{ + GtkWidget *shell; + GtkWidget *menubar; + GtkWidget *hruler; + GtkWidget *vruler; + GtkWidget *info_label; + GtkWidget *preview_button; + GtkWidget *scan_button; + 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; + gboolean have_odd_byte; + guint8 odd_byte; +#ifdef HAVE_LIBGIMP_GIMP_H + /* for GIMP mode: */ + gint32 image_ID; + GimpDrawable *drawable; + guchar *tile; + unsigned tile_offset; + GimpPixelRgn 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 defdev = -1; /* The default device */ +static gint ndevs; /* The number of available devices */ +static gboolean little_endian; /* Is this computer little-endian ? */ +static struct option long_options[] = { + {"help", no_argument, NULL, 'h'}, + {"version", no_argument, NULL, 'V'}, + {0, 0, 0, 0} +}; + + +static int gtk_quit_flag; /* Call gtk_main_quit() only if at least one device + device is found. */ + +/* 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); + +/* Test if this machine is little endian (from coolscan.c) */ +static gboolean +calc_little_endian (void) +{ + SANE_Int testvalue = 255; + u_int8_t *firstbyte = (u_int8_t *) & testvalue; + + if (*firstbyte == 255) + return TRUE; + return FALSE; +} + +#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; + + DBG_INIT (); + + DBG (DBG_debug, "encode_devname\n"); + 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'; + DBG (DBG_debug, "encode_devname: %s --> %s\n", devname, buf); + + return 0; +} + +static int +decode_devname (const char *encoded_devname, int n, char *buf) +{ + char *dst, *limit; + const char *src; + char ch, val; + + DBG_INIT (); + + DBG (DBG_debug, "decode_devname\n"); + 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'; + DBG (DBG_debug, "deccode_devname: %s -> %s\n", encoded_devname, buf); + return 0; +} + +static void +query (void) +{ + static GimpParamDef args[] = { + {GIMP_PDB_INT32, "run_mode", "Interactive, non-interactive"}, + }; + static GimpParamDef *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; + SANE_Status status; + + DBG_INIT (); + + DBG (DBG_debug, "query\n"); + 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>/File/Acquire/xscanimage/Device dialog...", + "RGB, GRAY", + GIMP_EXTENSION, + nargs, nreturn_vals, args, return_vals); + + status = sane_init (0, 0); + if (status != SANE_STATUS_GOOD) + { + DBG (DBG_fatal, "query: sane_init failed: %s\n", + sane_strstatus (status)); + exit (1); + } + + status = sane_get_devices (&devlist, SANE_FALSE); + if (status != SANE_STATUS_GOOD) + { + DBG (DBG_fatal, "query: sane_get_devices failed: %s\n", + sane_strstatus (status)); + exit (1); + } + + 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>/File/Acquire/xscanimage/", 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", GIMP_EXTENSION, + nargs, nreturn_vals, args, return_vals); + } + sane_exit (); + DBG (DBG_debug, "query: finished\n"); +} + +#ifndef ENABLE_GIMP_1_2 +static void +run (const gchar * name, gint nparams, const GimpParam * param, + gint * nreturn_vals, GimpParam ** return_vals) +#else +static void +run (char *name, int nparams, GimpParam * param, + int *nreturn_vals, GimpParam ** return_vals) +#endif /* !ENABLE_GIMP_1_2 */ +{ + static GimpParam values[2]; +#ifndef ENABLE_GIMP_1_2 + GimpRunMode run_mode; +#else + GimpRunModeType run_mode; +#endif /* !ENABLE_GIMP_1_2 */ + char devname[1024]; + char *args[2]; + int nargs; + + DBG (DBG_debug, "run\n"); + run_mode = param[0].data.d_int32; + scan_win.mode = SANE_GIMP_EXTENSION; + + *nreturn_vals = 1; + *return_vals = values; + + values[0].type = GIMP_PDB_STATUS; + values[0].data.d_status = GIMP_PDB_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 GIMP_RUN_INTERACTIVE: +#ifndef ENABLE_GIMP_1_2 + gimp_extension_ack (); +#endif /* !ENABLE_GIMP_1_2 */ + interface (nargs, args); + values[0].data.d_status = GIMP_PDB_SUCCESS; + break; + + case GIMP_RUN_NONINTERACTIVE: + /* Make sure all the arguments are there! */ + break; + + case GIMP_RUN_WITH_LAST_VALS: + /* Possibly retrieve data */ + break; + + default: + break; + } + DBG (DBG_debug, "run: finished\n"); +} + +static void +null_print_func (const gchar * msg) +{ +} + +#endif /* HAVE_LIBGIMP_GIMP_H */ + +static SANE_Word +get_resolution (SANE_Handle dev) +{ + SANE_Status status; + SANE_Word resolution; + SANE_Int num_options, i; + const SANE_Option_Descriptor *option_desc; + + DBG (DBG_debug, "get_resolution\n"); + status = + sane_control_option (dev, 0, SANE_ACTION_GET_VALUE, &num_options, 0); + if (status != SANE_STATUS_GOOD) + return 0; + + for (i = 1; i < num_options; i++) + { + option_desc = sane_get_option_descriptor (dev, i); + if (option_desc) + if (option_desc->name) + { + if (strncmp (option_desc->name, SANE_NAME_SCAN_RESOLUTION, + sizeof (SANE_NAME_SCAN_RESOLUTION)) == 0) + { + status = sane_control_option (dev, i, SANE_ACTION_GET_VALUE, + &resolution, 0); + if (status == SANE_STATUS_GOOD) + { + if (option_desc->type == SANE_TYPE_INT) + return resolution; + else if (option_desc->type == SANE_TYPE_FIXED) + return (SANE_Word) SANE_UNFIX (resolution); + } + return 0; + } + } + } + DBG (DBG_debug, "get_resolution: finished\n"); + return 0; +} + +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]; + + DBG (DBG_debug, "update_param\n"); + if (!scan_win.info_label) + return; + + if (sane_get_parameters (gsg_dialog_get_device (dialog), ¶ms) + == SANE_STATUS_GOOD) + { + double size = (double) params.bytes_per_line * (double) params.lines; + const char *unit = "B"; + + if (params.lines == -1) + { + snprintf (buf, sizeof (buf), "%dxunknown: unknown size", + params.pixels_per_line); + } + else + { + if (params.format >= SANE_FRAME_RED + && params.format <= SANE_FRAME_BLUE) + size *= 3; + + if (size >= 1024 * 1024) + { + size /= 1024 * 1024; + unit = "MB"; + } + else if (size >= 1024) + { + size /= 1024; + unit = "KB"; + } + snprintf (buf, sizeof (buf), "%dx%d: %1.1f %s", + params.pixels_per_line, params.lines, size, unit); + } + } + else + snprintf (buf, sizeof (buf), "Invalid parameters."); + gtk_label_set (GTK_LABEL (scan_win.info_label), buf); + + if (scan_win.preview) + preview_update (scan_win.preview); + DBG (DBG_debug, "update_param: finished\n"); +} + +static void +pref_xscanimage_save (void) +{ + char filename[PATH_MAX]; + int fd; + + DBG (DBG_debug, "pref_xscanimage_save\n"); + /* 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); + DBG (DBG_debug, "pref_xscanimage_save: finished\n"); +} + +static void +pref_xscanimage_restore (void) +{ + char filename[PATH_MAX]; + int fd; + + DBG (DBG_debug, "pref_xscanimage_restore\n"); + 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); + DBG (DBG_debug, "pref_xscanimage_restore: finished\n"); +} + +static void +quit_xscanimage (void) +{ + DBG (DBG_debug, "quit_xscanimage\n"); + 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_xscanimage_save (); + if (dialog && gsg_dialog_get_device (dialog)) + sane_close (gsg_dialog_get_device (dialog)); + sane_exit (); + if (gtk_quit_flag == 1) + gtk_main_quit (); +#ifdef HAVE_LIBGIMP_GIMP_H + if (scan_win.mode == SANE_GIMP_EXTENSION) + gimp_quit (); +#endif + DBG (DBG_debug, "quit_xscanimage: exiting\n"); + exit (0); +} + +/* Invoked when window manager's "delete" (or "close") function is + invoked. */ +static gint +scan_win_delete (GtkWidget * w, gpointer data) +{ + quit_xscanimage (); + 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) +{ + DBG (DBG_debug, "scan_preview\n"); + + 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); + if (scan_win.progress) + gtk_widget_set_sensitive (scan_win.preview->preview, FALSE); + } + } + else if (scan_win.preview) + { + preview_destroy (scan_win.preview); + scan_win.preview = 0; + } + DBG (DBG_debug, "scan_preview: finished\n"); +} + +#ifdef HAVE_LIBGIMP_GIMP_H +static void +advance (void) +{ + DBG (DBG_debug, "advance\n"); + + 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; + } + } + DBG (DBG_debug, "advance: finished\n"); +} + +#endif /* HAVE_LIBGIMP_GIMP_H */ + +static void +write_swapped_words (FILE * f, char *buf, guint len) +{ + char tmp_buf[2]; + char tmp; + unsigned int i; + + DBG (DBG_debug, "write_swapped_words\n"); + if (!len) + return; + if (scan_win.have_odd_byte) + { + tmp_buf[0] = *buf++; + tmp_buf[1] = scan_win.odd_byte; + fwrite (tmp_buf, 1, 2, f); + --len; + scan_win.have_odd_byte = FALSE; + } + if (len) + { + for (i = 1; i < len; i += 2) + { + tmp = buf[i]; + buf[i] = buf[i - 1]; + buf[i - 1] = tmp; + } + fwrite (buf, 1, len & ~1, f); + if (len & 1) + { + scan_win.have_odd_byte = TRUE; + scan_win.odd_byte = buf[len - 1]; + } + } + DBG (DBG_debug, "write_swapped_words: finished\n"); +} + +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; + + DBG (DBG_debug, "input_available\n"); + while (1) + { + status = sane_read (dev, (unsigned char *) buf, sizeof (buf), &len); + if (status != SANE_STATUS_GOOD) + { + if (status == SANE_STATUS_EOF) + { + if (!scan_win.param.last_frame) + { + if (scan_win.input_tag < 0) + { + while (gtk_events_pending ()) + gtk_main_iteration (); + } + else + { + gdk_input_remove (scan_win.input_tag); + scan_win.input_tag = -1; + } + scan_start (); + break; + } + } + else + { + snprintf (buf, sizeof (buf), "Error during read: %s.", + sane_strstatus (status)); + gsg_error (buf); + } + scan_done (); + return; + } + if (!len) /* out of data for now */ + { + if (scan_win.input_tag < 0) + { + while (gtk_events_pending ()) + gtk_main_iteration (); + continue; + } + else + break; + } + + 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) + { + if (scan_win.param.depth > 8 && little_endian) + write_swapped_words (scan_win.out, buf, len); + else + 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) + { + if (scan_win.param.depth > 8 && little_endian) + write_swapped_words (scan_win.out, buf, len); + else + 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; + } + } + DBG (DBG_debug, "input_available: finished\n"); + 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) +{ + DBG (DBG_debug, "scan_done\n"); + gsg_set_sensitivity (dialog, TRUE); + if (scan_win.preview) + gtk_widget_set_sensitive (scan_win.preview->preview, TRUE); + gtk_widget_set_sensitive (scan_win.scan_button, TRUE); + gtk_widget_set_sensitive (scan_win.menubar, TRUE); + + if (scan_win.input_tag >= 0) + { + gdk_input_remove (scan_win.input_tag); + scan_win.input_tag = -1; + } + + sane_cancel (gsg_dialog_get_device (dialog)); + + 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); + g_free (scan_win.tile); + scan_win.tile = 0; + } +#endif /* HAVE_LIBGIMP_GIMP_H */ + DBG (DBG_debug, "scan_done: finished\n"); +} + +static void +progress_cancel (void) +{ + DBG (DBG_debug, "progress_cancel\n"); + sane_cancel (gsg_dialog_get_device (dialog)); + DBG (DBG_debug, "progress_cancel: done\n"); +} + +static void +scan_start (void) +{ + SANE_Status status; + SANE_Handle dev = gsg_dialog_get_device (dialog); + +#ifdef HAVE_LIBGIMP_GIMP_H + SANE_Word resolution; +#endif /* HAVE_LIBGIMP_GIMP_H */ + + const char *frame_type = 0; + char buf[256]; + int fd; + + DBG (DBG_debug, "scan_start\n"); + + gsg_set_sensitivity (dialog, FALSE); + if (scan_win.preview) + gtk_widget_set_sensitive (scan_win.preview->preview, FALSE); + gtk_widget_set_sensitive (scan_win.scan_button, FALSE); + gtk_widget_set_sensitive (scan_win.menubar, FALSE); + +#ifdef HAVE_LIBGIMP_GIMP_H + if (scan_win.mode == SANE_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 */ + +#ifdef GIMP_HAVE_RESOLUTION_INFO + resolution = get_resolution (dev); +#endif + + scan_win.x = scan_win.y = 0; + + while (gtk_events_pending ()) + gtk_main_iteration (); + 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); + scan_done (); + return; + } + + if (scan_win.param.lines == -1) + { + gsg_set_sensitivity (dialog, TRUE); + snprintf (buf, sizeof (buf), "Hand-Scanner mode not supported"); + gsg_error (buf); + scan_done (); + return; + } + + scan_win.num_bytes = scan_win.param.lines * scan_win.param.bytes_per_line; + scan_win.bytes_read = 0; + scan_win.have_odd_byte = FALSE; + + 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_RED: + case SANE_FRAME_GREEN: + case SANE_FRAME_BLUE: + if (scan_win.param.depth > 8) + { + gsg_set_sensitivity (dialog, TRUE); + snprintf (buf, sizeof (buf), + "Separate channel transfers are not supported " + "with %d bits/channel.", scan_win.param.depth); + gsg_error (buf); + scan_done (); + return; + } + /*FALLTHROUGH*/ case SANE_FRAME_RGB: + fprintf (scan_win.out, "P6\n# SANE data follows\n%d %d\n%d\n", + scan_win.param.pixels_per_line, scan_win.param.lines, + (scan_win.param.depth <= 8) ? 255 : 65535); + 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\n%d\n", + scan_win.param.pixels_per_line, scan_win.param.lines, + (scan_win.param.depth <= 8) ? 255 : 65535); + 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 */ + /* Check whether the selected bit depth is supported by the Gimp */ + if (scan_win.param.depth > 8) + { + gsg_set_sensitivity (dialog, TRUE); + snprintf (buf, sizeof (buf), "The Gimp doesn't support images " + "with %d bits/channel.", scan_win.param.depth); + gsg_error (buf); + return; + } + + 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 + { + GimpImageType image_type = GIMP_RGB; + GimpImageType drawable_type = GIMP_RGB_IMAGE; + gint32 layer_ID; + + if (scan_win.param.format == SANE_FRAME_GRAY) + { + image_type = GIMP_GRAY; + drawable_type = GIMP_GRAY_IMAGE; + } + + scan_win.image_ID = gimp_image_new (scan_win.param.pixels_per_line, + scan_win.param.lines, + image_type); + +/* the following is supported since gimp-1.1.0 */ +#ifdef GIMP_HAVE_RESOLUTION_INFO + if (resolution > 0) + gimp_image_set_resolution (scan_win.image_ID, resolution, + resolution); +#endif + + layer_ID = gimp_layer_new (scan_win.image_ID, "Background", + scan_win.param.pixels_per_line, + scan_win.param.lines, + drawable_type, 100, GIMP_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 | GDK_INPUT_EXCEPTION, + input_available, 0); + else + { + while (gtk_events_pending ()) + gtk_main_iteration (); + input_available (0, -1, GDK_INPUT_READ); + } + DBG (DBG_debug, "scan_start: finished\n"); +} + +/* Invoked when the scan button is pressed */ +static void +scan_dialog (GtkWidget * widget, gpointer call_data) +{ + char buf[256]; + char testfilename[256]; + + DBG (DBG_debug, "scan_dialog\n"); + if (scan_win.mode == STANDALONE) + { /* We are running in standalone mode */ + /* test for pnm formats */ + strncpy (testfilename, preferences.filename, sizeof (testfilename)); + testfilename[sizeof (testfilename)] = 0; + g_strreverse (testfilename); + if (!((!strncmp (testfilename, "mnp.", 4)) || + (!strncmp (testfilename, "mgp.", 4)) || + (!strncmp (testfilename, "mbp.", 4)) || + (!strncmp (testfilename, "mpp.", 4)) || + (!strncmp (testfilename, "MNP.", 4)) || + (!strncmp (testfilename, "MGP.", 4)) || + (!strncmp (testfilename, "MBP.", 4)) || + (!strncmp (testfilename, "MPP.", 4)))) + { + snprintf (buf, sizeof (buf), + "Failed to scan, wrong file extension, use pnm, pgm, pbm or ppm `%s'", + preferences.filename); + gsg_error (buf); + return; + } + 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 (); + DBG (DBG_debug, "scan_dialog: finished\n"); +} + +#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_xscanimage (); +} + +static GtkWidget * +files_build_menu (void) +{ + GtkWidget *menu, *item; + + DBG (DBG_debug, "files_build_menu\n"); + 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); + + DBG (DBG_debug, "files_build_menu: finished\n"); + return menu; +} + +static void +pref_set_unit_callback (GtkWidget * widget, gpointer data) +{ + const char *unit = data; + double unit_conversion_factor = 1.0; + + DBG (DBG_debug, "pref_set_unit_callback\n"); + + 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_xscanimage_save (); + DBG (DBG_debug, "pref_set_unit_callback: finished\n"); +} + +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; + const char *start; + char *end; + double v; + + start = gtk_entry_get_text (GTK_ENTRY (widget)); + if (!start) + return; + + v = strtod (start, &end); + if (end > start) + *valuep = v; +} + +static void +preview_options_ok_callback (GtkWidget * widget, gpointer data) +{ + GtkWidget *dialog = data; + char buf[1024]; + + DBG (DBG_debug, "preview_options_ok_callback\n"); + + /* gamma min, max test */ + if (preferences.preview_gamma < 0.0 || preferences.preview_gamma > 255.0) + { + snprintf (buf, sizeof (buf), + "Gamma value %g is < 0 or > 255, please change!", + preferences.preview_gamma); + gsg_warning (buf); + return; + } + + + gtk_widget_destroy (dialog); + pref_xscanimage_save (); + + + snprintf (buf, sizeof (buf), + "It is necessary to restart %s for the changes to take effect.", + prog_name); + gsg_warning (buf); + DBG (DBG_debug, "preview_options_ok_callback: finished\n"); +} + +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]; + + DBG (DBG_debug, "preview_options_dialog\n"); + 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); + DBG (DBG_debug, "preview_options_dialog: finished\n"); +} + +static void +pref_device_save (GtkWidget * widget, gpointer data) +{ + char filename[PATH_MAX]; + int fd; + + DBG (DBG_debug, "pref_device_save\n"); + 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); + DBG (DBG_debug, "pref_device_save: finished\n"); +} + +static void +pref_device_restore (void) +{ + char filename[PATH_MAX]; + int fd; + + DBG (DBG_debug, "pref_device_restore\n"); + 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); + DBG (DBG_debug, "pref_device_restore: finished\n"); +} + +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_xscanimage_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_xscanimage_save (); +} + +static void +pref_toggle_twocolumn (GtkWidget * widget, gpointer data) +{ + preferences.twocolumn_enabled = (GTK_CHECK_MENU_ITEM (widget)->active != 0); + gsg_set_twocolumn (dialog, preferences.twocolumn_enabled); + pref_xscanimage_save (); +} + +static GtkWidget * +pref_build_menu (void) +{ + GtkWidget *menu, *item, *submenu; + GtkWidget *unit_mm, *unit_cm, *unit_in; + GSList *units_group = NULL; + double unit; + + DBG (DBG_debug, "pref_build_menu\n"); + 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); + + /* two column submenu: */ + + item = gtk_check_menu_item_new_with_label ("Show two column display"); + gtk_check_menu_item_set_state (GTK_CHECK_MENU_ITEM (item), + preferences.twocolumn_enabled); + gtk_menu_append (GTK_MENU (menu), item); + gtk_widget_show (item); + gtk_signal_connect (GTK_OBJECT (item), "toggled", + (GtkSignalFunc) pref_toggle_twocolumn, 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 (); + + unit_mm = gtk_radio_menu_item_new_with_label (units_group, "millimeters"); + units_group = gtk_radio_menu_item_group (GTK_RADIO_MENU_ITEM (unit_mm)); + gtk_menu_append (GTK_MENU (submenu), unit_mm); + gtk_widget_show (unit_mm); + + unit_cm = gtk_radio_menu_item_new_with_label (units_group, "centimeters"); + units_group = gtk_radio_menu_item_group (GTK_RADIO_MENU_ITEM (unit_cm)); + gtk_menu_append (GTK_MENU (submenu), unit_cm); + gtk_widget_show (unit_cm); + + unit_in = gtk_radio_menu_item_new_with_label (units_group, "inches"); + gtk_menu_append (GTK_MENU (submenu), unit_in); + gtk_widget_show (unit_in); + + unit = preferences.length_unit; + if ((unit > 9.9) && (unit < 10.1)) + gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (unit_cm), TRUE); + else if ((unit > 25.3) && (unit < 25.5)) + gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (unit_in), TRUE); + else + { + gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (unit_mm), TRUE); + preferences.length_unit = 1.0; + } + gtk_signal_connect (GTK_OBJECT (unit_mm), "toggled", + (GtkSignalFunc) pref_set_unit_callback, "mm"); + gtk_signal_connect (GTK_OBJECT (unit_cm), "toggled", + (GtkSignalFunc) pref_set_unit_callback, "cm"); + gtk_signal_connect (GTK_OBJECT (unit_in), "toggled", + (GtkSignalFunc) pref_set_unit_callback, "in"); + + 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); + + DBG (DBG_debug, "pref_build_menu: finished\n"); + return menu; +} + +static void +browse_filename_callback (GtkWidget * widget, gpointer data) +{ + char filename[1024]; + + DBG (DBG_debug, "browse_filename_callback\n"); + 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); + DBG (DBG_debug, "browse_filename_callback: finished\n"); +} + +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, *scrolled_window, *dialog_window, + *label, *text; + GtkWidget *menubar_item; + const gchar *devname; + + DBG (DBG_debug, "device_dialog\n"); + /* first, restore xscanimage preferences */ + pref_xscanimage_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_window_set_default_size (GTK_WINDOW (scan_win.shell), 400, 400); + 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 */ + + scan_win.menubar = gtk_menu_bar_new (); + gtk_box_pack_start (GTK_BOX (vbox), scan_win.menubar, FALSE, FALSE, 0); + + /* "Files" submenu: */ + menubar_item = gtk_menu_item_new_with_label ("File"); + gtk_container_add (GTK_CONTAINER (scan_win.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 (scan_win.menubar), menubar_item); + gtk_menu_item_set_submenu (GTK_MENU_ITEM (menubar_item), + pref_build_menu ()); + gtk_widget_show (menubar_item); + + gtk_widget_show (scan_win.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: */ + scrolled_window = gtk_scrolled_window_new (NULL, NULL); + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_window), + GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC); + gtk_scrolled_window_set_placement (GTK_SCROLLED_WINDOW (scrolled_window), + GTK_CORNER_TOP_RIGHT); + dialog_window = gtk_hbox_new ( /* homogeneous */ FALSE, 0); + gtk_scrolled_window_add_with_viewport (GTK_SCROLLED_WINDOW + (scrolled_window), dialog_window); + + gtk_box_pack_start (GTK_BOX (vbox), scrolled_window, TRUE, TRUE, 0); + gtk_widget_show (scrolled_window); + 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 */ + scan_win.scan_button = gtk_button_new_with_label ("Scan"); + gtk_signal_connect (GTK_OBJECT (scan_win.scan_button), "clicked", + (GtkSignalFunc) scan_dialog, NULL); + gtk_box_pack_start (GTK_BOX (hbox), scan_win.scan_button, TRUE, TRUE, 0); + gtk_widget_show (scan_win.scan_button); + + /* The Preview button */ + scan_win.preview_button = + gtk_toggle_button_new_with_label ("Preview Window"); + gtk_signal_connect (GTK_OBJECT (scan_win.preview_button), "clicked", + (GtkSignalFunc) scan_preview, NULL); + gtk_box_pack_start (GTK_BOX (hbox), scan_win.preview_button, TRUE, TRUE, 0); + gtk_widget_show (scan_win.preview_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); + DBG (DBG_debug, "device_dialog: finished\n"); +} + +static void +ok_choose_dialog_callback (void) +{ + gtk_widget_destroy (choose_device_dialog); + device_dialog (); +} + +static int +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 (); + + return 0; +} + +static gint32 +choose_device (void) +{ + GtkWidget *main_vbox, *vbox, *hbox, *button; + GSList *owner; + const SANE_Device *adev; + gint i; + + DBG (DBG_debug, "choose_device\n"); + choose_device_dialog = gtk_dialog_new (); + gtk_window_set_title (GTK_WINDOW (choose_device_dialog), "Select device"); + gtk_signal_connect (GTK_OBJECT (choose_device_dialog), "delete_event", + GTK_SIGNAL_FUNC (files_exit_callback), NULL); + + 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)); + if (i == defdev) + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button), TRUE); + } + + /* 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); + DBG (DBG_debug, "choose_device: finished\n"); + 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; + SANE_Status status; + + DBG_INIT (); + + DBG (DBG_debug, "init\n"); + gtk_init (&argc, &argv); +#ifdef HAVE_LIBGIMP_GIMP_H + gtk_rc_parse (gimp_gtkrc ()); + +# ifndef ENABLE_GIMP_1_2 + /* GIMP 2.0 defines gimp_use_xshm() as a macro always returning TRUE + * (in the compat header) */ + gdk_set_use_xshm (TRUE); +# else + gdk_set_use_xshm (gimp_use_xshm ()); +# endif /* !ENABLE_GIMP_1_2 */ +#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); + } + + status = sane_init (0, 0); + if (status != SANE_STATUS_GOOD) + { + DBG (DBG_fatal, "init: sane_init failed: %s\n", + sane_strstatus (status)); + exit (1); + } + + 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) + { + char *defdevname; + + status = sane_get_devices (&devlist, SANE_FALSE); + if (status != SANE_STATUS_GOOD) + { + DBG (DBG_fatal, "init: sane_get_devices failed: %s\n", + sane_strstatus (status)); + exit (1); + } + + if ((defdevname = getenv ("SANE_DEFAULT_DEVICE")) != NULL) + { + int i; + + for (i = 0; devlist[i] != 0; i++) + { + if (strcmp (devlist[i]->name, defdevname) == 0) + { + defdev = i; + break; + } + } + if (defdev < 0) + DBG (DBG_error, "default device is `%s' wasn't found by " + "sane_get_devices() \n", defdevname); + else + DBG (DBG_info, "default device is `%s'\n", defdevname); + } + } + DBG (DBG_debug, "init: finished\n"); +} + +static void +interface (int argc, char **argv) +{ + scan_win.info_label = NULL; + + DBG (DBG_debug, "interface\n"); + 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_xscanimage (); + } + device_dialog (); + if (!dialog) + quit_xscanimage (); + } + else + { + if (ndevs > 0) + { + seldev = 0; + if (ndevs == 1) + { + device_dialog (); + if (!dialog) + quit_xscanimage (); + } + else + choose_device (); + } + else + { + DBG (DBG_fatal, + "No scanners were identified. If you were expecting something\n" + " different, check that the scanner is plugged in, turned on and\n" + " detected by sane-find-scanner (if appropriate). Please read\n" + " the documentation which came with this software (README, FAQ,\n" + " manpages).\n"); + quit_xscanimage (); + } + } + gtk_quit_flag = 1; + DBG (DBG_debug, "interface: now running gtk_main ()\n"); + gtk_main (); + sane_exit (); + DBG (DBG_debug, "interface: finished\n"); +} + +int +main (int argc, char **argv) +{ + DBG_INIT (); + DBG (DBG_debug, "main\n"); + DBG (DBG_error, "xscanimage (version: %s, package: %s) starting\n", VERSION, + PACKAGE); + little_endian = calc_little_endian (); + scan_win.mode = STANDALONE; + gtk_quit_flag = 0; + prog_name = strrchr (argv[0], '/'); + if (prog_name) + ++prog_name; + else + prog_name = argv[0]; + +#ifdef HAVE_LIBGIMP_GIMP_H + { + GPrintFunc old_print_func; + GPrintFunc old_printerr_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); + old_printerr_func = g_set_printerr_handler (null_print_func); + /* gimp_main () returns 1 if xscanimage wasn't invoked by GIMP */ +# ifdef HAVE_OS2_H + set_gimp_PLUG_IN_INFO (&PLUG_IN_INFO); +# endif +# ifndef ENABLE_GIMP_1_2 + result = gimp_main (&PLUG_IN_INFO, argc, argv); +# else + result = gimp_main (argc, argv); +# endif /* !ENABLE_GIMP_1_2 */ + g_set_print_handler (old_print_func); + g_set_printerr_handler (old_printerr_func); + if (result) + interface (argc, argv); + } +#else + interface (argc, argv); +#endif /* HAVE_LIBGIMP_GIMP_H */ + DBG (DBG_debug, "main: finished\n"); + return 0; +} |