diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/Makefile.am | 35 | ||||
| -rw-r--r-- | src/Makefile.in | 661 | ||||
| -rw-r--r-- | src/book-view.c | 743 | ||||
| -rw-r--r-- | src/book-view.h | 65 | ||||
| -rw-r--r-- | src/book.c | 466 | ||||
| -rw-r--r-- | src/book.h | 60 | ||||
| -rw-r--r-- | src/page-view.c | 1170 | ||||
| -rw-r--r-- | src/page-view.h | 79 | ||||
| -rw-r--r-- | src/page.c | 951 | ||||
| -rw-r--r-- | src/page.h | 121 | ||||
| -rw-r--r-- | src/scanner.c | 1630 | ||||
| -rw-r--r-- | src/scanner.h | 134 | ||||
| -rw-r--r-- | src/simple-scan.c | 633 | ||||
| -rw-r--r-- | src/ui.c | 1648 | ||||
| -rw-r--r-- | src/ui.h | 71 | 
15 files changed, 8467 insertions, 0 deletions
| diff --git a/src/Makefile.am b/src/Makefile.am new file mode 100644 index 0000000..e6654d0 --- /dev/null +++ b/src/Makefile.am @@ -0,0 +1,35 @@ +bin_PROGRAMS = simple-scan + +simple_scan_SOURCES = \ +	book.c \ +	book.h \ +	book-view.c \ +	book-view.h \ +	page.c \ +	page.h \ +	page-view.c \ +	page-view.h \ +	simple-scan.c \ +	scanner.c \ +	scanner.h \ +	ui.c \ +	ui.h + +simple_scan_CFLAGS = \ +	$(SIMPLE_SCAN_CFLAGS) \ +	$(WARN_CFLAGS) \ +	-DVERSION=\"$(VERSION)\" \ +	-DGETTEXT_PACKAGE=\"$(GETTEXT_PACKAGE)\" \ +	-DLOCALE_DIR=\"$(localedir)\" \ +	-DUI_DIR=\"$(datadir)/simple-scan/\" \ +	-DICON_DIR=\"$(datadir)/simple-scan/icons\" \ +	-DGCONF_DIR=\"/apps/simple-scan\" \ +	-DSIMPLE_SCAN_BINARY=\"simple-scan\" + +simple_scan_LDADD = \ +	$(SIMPLE_SCAN_LIBS) \ +	-lsane \ +	-lm + +DISTCLEANFILES = \ +	Makefile.in diff --git a/src/Makefile.in b/src/Makefile.in new file mode 100644 index 0000000..5665293 --- /dev/null +++ b/src/Makefile.in @@ -0,0 +1,661 @@ +# Makefile.in generated by automake 1.11.1 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, +# 2003, 2004, 2005, 2006, 2007, 2008, 2009  Free Software Foundation, +# Inc. +# This Makefile.in is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + +@SET_MAKE@ + +VPATH = @srcdir@ +pkgdatadir = $(datadir)/@PACKAGE@ +pkgincludedir = $(includedir)/@PACKAGE@ +pkglibdir = $(libdir)/@PACKAGE@ +pkglibexecdir = $(libexecdir)/@PACKAGE@ +am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +install_sh_DATA = $(install_sh) -c -m 644 +install_sh_PROGRAM = $(install_sh) -c +install_sh_SCRIPT = $(install_sh) -c +INSTALL_HEADER = $(INSTALL_DATA) +transform = $(program_transform_name) +NORMAL_INSTALL = : +PRE_INSTALL = : +POST_INSTALL = : +NORMAL_UNINSTALL = : +PRE_UNINSTALL = : +POST_UNINSTALL = : +bin_PROGRAMS = simple-scan$(EXEEXT) +subdir = src +DIST_COMMON = $(srcdir)/Makefile.am $(srcdir)/Makefile.in +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/configure.ac +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ +	$(ACLOCAL_M4) +mkinstalldirs = $(SHELL) $(top_srcdir)/mkinstalldirs +CONFIG_CLEAN_FILES = +CONFIG_CLEAN_VPATH_FILES = +am__installdirs = "$(DESTDIR)$(bindir)" +PROGRAMS = $(bin_PROGRAMS) +am_simple_scan_OBJECTS = simple_scan-book.$(OBJEXT) \ +	simple_scan-book-view.$(OBJEXT) simple_scan-page.$(OBJEXT) \ +	simple_scan-page-view.$(OBJEXT) \ +	simple_scan-simple-scan.$(OBJEXT) \ +	simple_scan-scanner.$(OBJEXT) simple_scan-ui.$(OBJEXT) +simple_scan_OBJECTS = $(am_simple_scan_OBJECTS) +am__DEPENDENCIES_1 = +simple_scan_DEPENDENCIES = $(am__DEPENDENCIES_1) +simple_scan_LINK = $(CCLD) $(simple_scan_CFLAGS) $(CFLAGS) \ +	$(AM_LDFLAGS) $(LDFLAGS) -o $@ +DEFAULT_INCLUDES = -I.@am__isrc@ +depcomp = $(SHELL) $(top_srcdir)/depcomp +am__depfiles_maybe = depfiles +am__mv = mv -f +AM_V_lt = $(am__v_lt_$(V)) +am__v_lt_ = $(am__v_lt_$(AM_DEFAULT_VERBOSITY)) +am__v_lt_0 = --silent +COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ +	$(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) +AM_V_CC = $(am__v_CC_$(V)) +am__v_CC_ = $(am__v_CC_$(AM_DEFAULT_VERBOSITY)) +am__v_CC_0 = @echo "  CC    " $@; +AM_V_at = $(am__v_at_$(V)) +am__v_at_ = $(am__v_at_$(AM_DEFAULT_VERBOSITY)) +am__v_at_0 = @ +CCLD = $(CC) +LINK = $(CCLD) $(AM_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@ +AM_V_CCLD = $(am__v_CCLD_$(V)) +am__v_CCLD_ = $(am__v_CCLD_$(AM_DEFAULT_VERBOSITY)) +am__v_CCLD_0 = @echo "  CCLD  " $@; +AM_V_GEN = $(am__v_GEN_$(V)) +am__v_GEN_ = $(am__v_GEN_$(AM_DEFAULT_VERBOSITY)) +am__v_GEN_0 = @echo "  GEN   " $@; +SOURCES = $(simple_scan_SOURCES) +DIST_SOURCES = $(simple_scan_SOURCES) +ETAGS = etags +CTAGS = ctags +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +ACLOCAL = @ACLOCAL@ +ALL_LINGUAS = @ALL_LINGUAS@ +AMTAR = @AMTAR@ +AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +CATALOGS = @CATALOGS@ +CATOBJEXT = @CATOBJEXT@ +CC = @CC@ +CCDEPMODE = @CCDEPMODE@ +CFLAGS = @CFLAGS@ +CPP = @CPP@ +CPPFLAGS = @CPPFLAGS@ +CYGPATH_W = @CYGPATH_W@ +DATADIRNAME = @DATADIRNAME@ +DEFS = @DEFS@ +DEPDIR = @DEPDIR@ +DISABLE_DEPRECATED = @DISABLE_DEPRECATED@ +DISTCHECK_CONFIGURE_FLAGS = @DISTCHECK_CONFIGURE_FLAGS@ +DOC_USER_FORMATS = @DOC_USER_FORMATS@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +EGREP = @EGREP@ +EXEEXT = @EXEEXT@ +GCONF_SCHEMA_CONFIG_SOURCE = @GCONF_SCHEMA_CONFIG_SOURCE@ +GCONF_SCHEMA_FILE_DIR = @GCONF_SCHEMA_FILE_DIR@ +GETTEXT_PACKAGE = @GETTEXT_PACKAGE@ +GMOFILES = @GMOFILES@ +GMSGFMT = @GMSGFMT@ +GREP = @GREP@ +HELP_DIR = @HELP_DIR@ +INSTALL = @INSTALL@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +INSTOBJEXT = @INSTOBJEXT@ +INTLLIBS = @INTLLIBS@ +INTLTOOL_EXTRACT = @INTLTOOL_EXTRACT@ +INTLTOOL_MERGE = @INTLTOOL_MERGE@ +INTLTOOL_PERL = @INTLTOOL_PERL@ +INTLTOOL_UPDATE = @INTLTOOL_UPDATE@ +LDFLAGS = @LDFLAGS@ +LIBOBJS = @LIBOBJS@ +LIBS = @LIBS@ +LN_S = @LN_S@ +LTLIBOBJS = @LTLIBOBJS@ +MAINT = @MAINT@ +MAKEINFO = @MAKEINFO@ +MKDIR_P = @MKDIR_P@ +MKINSTALLDIRS = @MKINSTALLDIRS@ +MSGFMT = @MSGFMT@ +MSGFMT_OPTS = @MSGFMT_OPTS@ +MSGMERGE = @MSGMERGE@ +OBJEXT = @OBJEXT@ +OMF_DIR = @OMF_DIR@ +PACKAGE = @PACKAGE@ +PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +PACKAGE_NAME = @PACKAGE_NAME@ +PACKAGE_STRING = @PACKAGE_STRING@ +PACKAGE_TARNAME = @PACKAGE_TARNAME@ +PACKAGE_URL = @PACKAGE_URL@ +PACKAGE_VERSION = @PACKAGE_VERSION@ +PATH_SEPARATOR = @PATH_SEPARATOR@ +PKG_CONFIG = @PKG_CONFIG@ +POFILES = @POFILES@ +POSUB = @POSUB@ +PO_IN_DATADIR_FALSE = @PO_IN_DATADIR_FALSE@ +PO_IN_DATADIR_TRUE = @PO_IN_DATADIR_TRUE@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +SIMPLE_SCAN_CFLAGS = @SIMPLE_SCAN_CFLAGS@ +SIMPLE_SCAN_LIBS = @SIMPLE_SCAN_LIBS@ +STRIP = @STRIP@ +USE_NLS = @USE_NLS@ +VERSION = @VERSION@ +WARN_CFLAGS = @WARN_CFLAGS@ +XGETTEXT = @XGETTEXT@ +abs_builddir = @abs_builddir@ +abs_srcdir = @abs_srcdir@ +abs_top_builddir = @abs_top_builddir@ +abs_top_srcdir = @abs_top_srcdir@ +ac_ct_CC = @ac_ct_CC@ +am__include = @am__include@ +am__leading_dot = @am__leading_dot@ +am__quote = @am__quote@ +am__tar = @am__tar@ +am__untar = @am__untar@ +bindir = @bindir@ +build_alias = @build_alias@ +builddir = @builddir@ +datadir = @datadir@ +datarootdir = @datarootdir@ +docdir = @docdir@ +dvidir = @dvidir@ +exec_prefix = @exec_prefix@ +host_alias = @host_alias@ +htmldir = @htmldir@ +includedir = @includedir@ +infodir = @infodir@ +install_sh = @install_sh@ +libdir = @libdir@ +libexecdir = @libexecdir@ +localedir = @localedir@ +localstatedir = @localstatedir@ +mandir = @mandir@ +mkdir_p = @mkdir_p@ +oldincludedir = @oldincludedir@ +pdfdir = @pdfdir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +psdir = @psdir@ +sbindir = @sbindir@ +sharedstatedir = @sharedstatedir@ +srcdir = @srcdir@ +sysconfdir = @sysconfdir@ +target_alias = @target_alias@ +top_build_prefix = @top_build_prefix@ +top_builddir = @top_builddir@ +top_srcdir = @top_srcdir@ +simple_scan_SOURCES = \ +	book.c \ +	book.h \ +	book-view.c \ +	book-view.h \ +	page.c \ +	page.h \ +	page-view.c \ +	page-view.h \ +	simple-scan.c \ +	scanner.c \ +	scanner.h \ +	ui.c \ +	ui.h + +simple_scan_CFLAGS = \ +	$(SIMPLE_SCAN_CFLAGS) \ +	$(WARN_CFLAGS) \ +	-DVERSION=\"$(VERSION)\" \ +	-DGETTEXT_PACKAGE=\"$(GETTEXT_PACKAGE)\" \ +	-DLOCALE_DIR=\"$(localedir)\" \ +	-DUI_DIR=\"$(datadir)/simple-scan/\" \ +	-DICON_DIR=\"$(datadir)/simple-scan/icons\" \ +	-DGCONF_DIR=\"/apps/simple-scan\" \ +	-DSIMPLE_SCAN_BINARY=\"simple-scan\" + +simple_scan_LDADD = \ +	$(SIMPLE_SCAN_LIBS) \ +	-lsane \ +	-lm + +DISTCLEANFILES = \ +	Makefile.in + +all: all-am + +.SUFFIXES: +.SUFFIXES: .c .o .obj +$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am  $(am__configure_deps) +	@for dep in $?; do \ +	  case '$(am__configure_deps)' in \ +	    *$$dep*) \ +	      ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ +	        && { if test -f $@; then exit 0; else break; fi; }; \ +	      exit 1;; \ +	  esac; \ +	done; \ +	echo ' cd $(top_srcdir) && $(AUTOMAKE) --gnu src/Makefile'; \ +	$(am__cd) $(top_srcdir) && \ +	  $(AUTOMAKE) --gnu src/Makefile +.PRECIOUS: Makefile +Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status +	@case '$?' in \ +	  *config.status*) \ +	    cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ +	  *) \ +	    echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe)'; \ +	    cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \ +	esac; + +$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) +	cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh + +$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps) +	cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps) +	cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(am__aclocal_m4_deps): +install-binPROGRAMS: $(bin_PROGRAMS) +	@$(NORMAL_INSTALL) +	test -z "$(bindir)" || $(MKDIR_P) "$(DESTDIR)$(bindir)" +	@list='$(bin_PROGRAMS)'; test -n "$(bindir)" || list=; \ +	for p in $$list; do echo "$$p $$p"; done | \ +	sed 's/$(EXEEXT)$$//' | \ +	while read p p1; do if test -f $$p; \ +	  then echo "$$p"; echo "$$p"; else :; fi; \ +	done | \ +	sed -e 'p;s,.*/,,;n;h' -e 's|.*|.|' \ +	    -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \ +	sed 'N;N;N;s,\n, ,g' | \ +	$(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \ +	  { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \ +	    if ($$2 == $$4) files[d] = files[d] " " $$1; \ +	    else { print "f", $$3 "/" $$4, $$1; } } \ +	  END { for (d in files) print "f", d, files[d] }' | \ +	while read type dir files; do \ +	    if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \ +	    test -z "$$files" || { \ +	      echo " $(INSTALL_PROGRAM_ENV) $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(bindir)$$dir'"; \ +	      $(INSTALL_PROGRAM_ENV) $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(bindir)$$dir" || exit $$?; \ +	    } \ +	; done + +uninstall-binPROGRAMS: +	@$(NORMAL_UNINSTALL) +	@list='$(bin_PROGRAMS)'; test -n "$(bindir)" || list=; \ +	files=`for p in $$list; do echo "$$p"; done | \ +	  sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \ +	      -e 's/$$/$(EXEEXT)/' `; \ +	test -n "$$list" || exit 0; \ +	echo " ( cd '$(DESTDIR)$(bindir)' && rm -f" $$files ")"; \ +	cd "$(DESTDIR)$(bindir)" && rm -f $$files + +clean-binPROGRAMS: +	-test -z "$(bin_PROGRAMS)" || rm -f $(bin_PROGRAMS) +simple-scan$(EXEEXT): $(simple_scan_OBJECTS) $(simple_scan_DEPENDENCIES)  +	@rm -f simple-scan$(EXEEXT) +	$(AM_V_CCLD)$(simple_scan_LINK) $(simple_scan_OBJECTS) $(simple_scan_LDADD) $(LIBS) + +mostlyclean-compile: +	-rm -f *.$(OBJEXT) + +distclean-compile: +	-rm -f *.tab.c + +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/simple_scan-book-view.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/simple_scan-book.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/simple_scan-page-view.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/simple_scan-page.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/simple_scan-scanner.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/simple_scan-simple-scan.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/simple_scan-ui.Po@am__quote@ + +.c.o: +@am__fastdepCC_TRUE@	$(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@am__fastdepCC_FALSE@	$(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@	source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@	$(COMPILE) -c $< + +.c.obj: +@am__fastdepCC_TRUE@	$(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'` +@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@am__fastdepCC_FALSE@	$(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@	source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@	$(COMPILE) -c `$(CYGPATH_W) '$<'` + +simple_scan-book.o: book.c +@am__fastdepCC_TRUE@	$(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(simple_scan_CFLAGS) $(CFLAGS) -MT simple_scan-book.o -MD -MP -MF $(DEPDIR)/simple_scan-book.Tpo -c -o simple_scan-book.o `test -f 'book.c' || echo '$(srcdir)/'`book.c +@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/simple_scan-book.Tpo $(DEPDIR)/simple_scan-book.Po +@am__fastdepCC_FALSE@	$(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@	source='book.c' object='simple_scan-book.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@	$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(simple_scan_CFLAGS) $(CFLAGS) -c -o simple_scan-book.o `test -f 'book.c' || echo '$(srcdir)/'`book.c + +simple_scan-book.obj: book.c +@am__fastdepCC_TRUE@	$(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(simple_scan_CFLAGS) $(CFLAGS) -MT simple_scan-book.obj -MD -MP -MF $(DEPDIR)/simple_scan-book.Tpo -c -o simple_scan-book.obj `if test -f 'book.c'; then $(CYGPATH_W) 'book.c'; else $(CYGPATH_W) '$(srcdir)/book.c'; fi` +@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/simple_scan-book.Tpo $(DEPDIR)/simple_scan-book.Po +@am__fastdepCC_FALSE@	$(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@	source='book.c' object='simple_scan-book.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@	$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(simple_scan_CFLAGS) $(CFLAGS) -c -o simple_scan-book.obj `if test -f 'book.c'; then $(CYGPATH_W) 'book.c'; else $(CYGPATH_W) '$(srcdir)/book.c'; fi` + +simple_scan-book-view.o: book-view.c +@am__fastdepCC_TRUE@	$(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(simple_scan_CFLAGS) $(CFLAGS) -MT simple_scan-book-view.o -MD -MP -MF $(DEPDIR)/simple_scan-book-view.Tpo -c -o simple_scan-book-view.o `test -f 'book-view.c' || echo '$(srcdir)/'`book-view.c +@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/simple_scan-book-view.Tpo $(DEPDIR)/simple_scan-book-view.Po +@am__fastdepCC_FALSE@	$(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@	source='book-view.c' object='simple_scan-book-view.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@	$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(simple_scan_CFLAGS) $(CFLAGS) -c -o simple_scan-book-view.o `test -f 'book-view.c' || echo '$(srcdir)/'`book-view.c + +simple_scan-book-view.obj: book-view.c +@am__fastdepCC_TRUE@	$(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(simple_scan_CFLAGS) $(CFLAGS) -MT simple_scan-book-view.obj -MD -MP -MF $(DEPDIR)/simple_scan-book-view.Tpo -c -o simple_scan-book-view.obj `if test -f 'book-view.c'; then $(CYGPATH_W) 'book-view.c'; else $(CYGPATH_W) '$(srcdir)/book-view.c'; fi` +@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/simple_scan-book-view.Tpo $(DEPDIR)/simple_scan-book-view.Po +@am__fastdepCC_FALSE@	$(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@	source='book-view.c' object='simple_scan-book-view.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@	$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(simple_scan_CFLAGS) $(CFLAGS) -c -o simple_scan-book-view.obj `if test -f 'book-view.c'; then $(CYGPATH_W) 'book-view.c'; else $(CYGPATH_W) '$(srcdir)/book-view.c'; fi` + +simple_scan-page.o: page.c +@am__fastdepCC_TRUE@	$(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(simple_scan_CFLAGS) $(CFLAGS) -MT simple_scan-page.o -MD -MP -MF $(DEPDIR)/simple_scan-page.Tpo -c -o simple_scan-page.o `test -f 'page.c' || echo '$(srcdir)/'`page.c +@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/simple_scan-page.Tpo $(DEPDIR)/simple_scan-page.Po +@am__fastdepCC_FALSE@	$(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@	source='page.c' object='simple_scan-page.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@	$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(simple_scan_CFLAGS) $(CFLAGS) -c -o simple_scan-page.o `test -f 'page.c' || echo '$(srcdir)/'`page.c + +simple_scan-page.obj: page.c +@am__fastdepCC_TRUE@	$(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(simple_scan_CFLAGS) $(CFLAGS) -MT simple_scan-page.obj -MD -MP -MF $(DEPDIR)/simple_scan-page.Tpo -c -o simple_scan-page.obj `if test -f 'page.c'; then $(CYGPATH_W) 'page.c'; else $(CYGPATH_W) '$(srcdir)/page.c'; fi` +@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/simple_scan-page.Tpo $(DEPDIR)/simple_scan-page.Po +@am__fastdepCC_FALSE@	$(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@	source='page.c' object='simple_scan-page.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@	$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(simple_scan_CFLAGS) $(CFLAGS) -c -o simple_scan-page.obj `if test -f 'page.c'; then $(CYGPATH_W) 'page.c'; else $(CYGPATH_W) '$(srcdir)/page.c'; fi` + +simple_scan-page-view.o: page-view.c +@am__fastdepCC_TRUE@	$(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(simple_scan_CFLAGS) $(CFLAGS) -MT simple_scan-page-view.o -MD -MP -MF $(DEPDIR)/simple_scan-page-view.Tpo -c -o simple_scan-page-view.o `test -f 'page-view.c' || echo '$(srcdir)/'`page-view.c +@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/simple_scan-page-view.Tpo $(DEPDIR)/simple_scan-page-view.Po +@am__fastdepCC_FALSE@	$(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@	source='page-view.c' object='simple_scan-page-view.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@	$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(simple_scan_CFLAGS) $(CFLAGS) -c -o simple_scan-page-view.o `test -f 'page-view.c' || echo '$(srcdir)/'`page-view.c + +simple_scan-page-view.obj: page-view.c +@am__fastdepCC_TRUE@	$(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(simple_scan_CFLAGS) $(CFLAGS) -MT simple_scan-page-view.obj -MD -MP -MF $(DEPDIR)/simple_scan-page-view.Tpo -c -o simple_scan-page-view.obj `if test -f 'page-view.c'; then $(CYGPATH_W) 'page-view.c'; else $(CYGPATH_W) '$(srcdir)/page-view.c'; fi` +@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/simple_scan-page-view.Tpo $(DEPDIR)/simple_scan-page-view.Po +@am__fastdepCC_FALSE@	$(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@	source='page-view.c' object='simple_scan-page-view.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@	$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(simple_scan_CFLAGS) $(CFLAGS) -c -o simple_scan-page-view.obj `if test -f 'page-view.c'; then $(CYGPATH_W) 'page-view.c'; else $(CYGPATH_W) '$(srcdir)/page-view.c'; fi` + +simple_scan-simple-scan.o: simple-scan.c +@am__fastdepCC_TRUE@	$(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(simple_scan_CFLAGS) $(CFLAGS) -MT simple_scan-simple-scan.o -MD -MP -MF $(DEPDIR)/simple_scan-simple-scan.Tpo -c -o simple_scan-simple-scan.o `test -f 'simple-scan.c' || echo '$(srcdir)/'`simple-scan.c +@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/simple_scan-simple-scan.Tpo $(DEPDIR)/simple_scan-simple-scan.Po +@am__fastdepCC_FALSE@	$(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@	source='simple-scan.c' object='simple_scan-simple-scan.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@	$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(simple_scan_CFLAGS) $(CFLAGS) -c -o simple_scan-simple-scan.o `test -f 'simple-scan.c' || echo '$(srcdir)/'`simple-scan.c + +simple_scan-simple-scan.obj: simple-scan.c +@am__fastdepCC_TRUE@	$(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(simple_scan_CFLAGS) $(CFLAGS) -MT simple_scan-simple-scan.obj -MD -MP -MF $(DEPDIR)/simple_scan-simple-scan.Tpo -c -o simple_scan-simple-scan.obj `if test -f 'simple-scan.c'; then $(CYGPATH_W) 'simple-scan.c'; else $(CYGPATH_W) '$(srcdir)/simple-scan.c'; fi` +@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/simple_scan-simple-scan.Tpo $(DEPDIR)/simple_scan-simple-scan.Po +@am__fastdepCC_FALSE@	$(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@	source='simple-scan.c' object='simple_scan-simple-scan.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@	$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(simple_scan_CFLAGS) $(CFLAGS) -c -o simple_scan-simple-scan.obj `if test -f 'simple-scan.c'; then $(CYGPATH_W) 'simple-scan.c'; else $(CYGPATH_W) '$(srcdir)/simple-scan.c'; fi` + +simple_scan-scanner.o: scanner.c +@am__fastdepCC_TRUE@	$(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(simple_scan_CFLAGS) $(CFLAGS) -MT simple_scan-scanner.o -MD -MP -MF $(DEPDIR)/simple_scan-scanner.Tpo -c -o simple_scan-scanner.o `test -f 'scanner.c' || echo '$(srcdir)/'`scanner.c +@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/simple_scan-scanner.Tpo $(DEPDIR)/simple_scan-scanner.Po +@am__fastdepCC_FALSE@	$(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@	source='scanner.c' object='simple_scan-scanner.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@	$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(simple_scan_CFLAGS) $(CFLAGS) -c -o simple_scan-scanner.o `test -f 'scanner.c' || echo '$(srcdir)/'`scanner.c + +simple_scan-scanner.obj: scanner.c +@am__fastdepCC_TRUE@	$(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(simple_scan_CFLAGS) $(CFLAGS) -MT simple_scan-scanner.obj -MD -MP -MF $(DEPDIR)/simple_scan-scanner.Tpo -c -o simple_scan-scanner.obj `if test -f 'scanner.c'; then $(CYGPATH_W) 'scanner.c'; else $(CYGPATH_W) '$(srcdir)/scanner.c'; fi` +@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/simple_scan-scanner.Tpo $(DEPDIR)/simple_scan-scanner.Po +@am__fastdepCC_FALSE@	$(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@	source='scanner.c' object='simple_scan-scanner.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@	$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(simple_scan_CFLAGS) $(CFLAGS) -c -o simple_scan-scanner.obj `if test -f 'scanner.c'; then $(CYGPATH_W) 'scanner.c'; else $(CYGPATH_W) '$(srcdir)/scanner.c'; fi` + +simple_scan-ui.o: ui.c +@am__fastdepCC_TRUE@	$(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(simple_scan_CFLAGS) $(CFLAGS) -MT simple_scan-ui.o -MD -MP -MF $(DEPDIR)/simple_scan-ui.Tpo -c -o simple_scan-ui.o `test -f 'ui.c' || echo '$(srcdir)/'`ui.c +@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/simple_scan-ui.Tpo $(DEPDIR)/simple_scan-ui.Po +@am__fastdepCC_FALSE@	$(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@	source='ui.c' object='simple_scan-ui.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@	$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(simple_scan_CFLAGS) $(CFLAGS) -c -o simple_scan-ui.o `test -f 'ui.c' || echo '$(srcdir)/'`ui.c + +simple_scan-ui.obj: ui.c +@am__fastdepCC_TRUE@	$(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(simple_scan_CFLAGS) $(CFLAGS) -MT simple_scan-ui.obj -MD -MP -MF $(DEPDIR)/simple_scan-ui.Tpo -c -o simple_scan-ui.obj `if test -f 'ui.c'; then $(CYGPATH_W) 'ui.c'; else $(CYGPATH_W) '$(srcdir)/ui.c'; fi` +@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/simple_scan-ui.Tpo $(DEPDIR)/simple_scan-ui.Po +@am__fastdepCC_FALSE@	$(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@	source='ui.c' object='simple_scan-ui.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@	$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(simple_scan_CFLAGS) $(CFLAGS) -c -o simple_scan-ui.obj `if test -f 'ui.c'; then $(CYGPATH_W) 'ui.c'; else $(CYGPATH_W) '$(srcdir)/ui.c'; fi` + +ID: $(HEADERS) $(SOURCES) $(LISP) $(TAGS_FILES) +	list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \ +	unique=`for i in $$list; do \ +	    if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ +	  done | \ +	  $(AWK) '{ files[$$0] = 1; nonempty = 1; } \ +	      END { if (nonempty) { for (i in files) print i; }; }'`; \ +	mkid -fID $$unique +tags: TAGS + +TAGS:  $(HEADERS) $(SOURCES)  $(TAGS_DEPENDENCIES) \ +		$(TAGS_FILES) $(LISP) +	set x; \ +	here=`pwd`; \ +	list='$(SOURCES) $(HEADERS)  $(LISP) $(TAGS_FILES)'; \ +	unique=`for i in $$list; do \ +	    if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ +	  done | \ +	  $(AWK) '{ files[$$0] = 1; nonempty = 1; } \ +	      END { if (nonempty) { for (i in files) print i; }; }'`; \ +	shift; \ +	if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ +	  test -n "$$unique" || unique=$$empty_fix; \ +	  if test $$# -gt 0; then \ +	    $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ +	      "$$@" $$unique; \ +	  else \ +	    $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ +	      $$unique; \ +	  fi; \ +	fi +ctags: CTAGS +CTAGS:  $(HEADERS) $(SOURCES)  $(TAGS_DEPENDENCIES) \ +		$(TAGS_FILES) $(LISP) +	list='$(SOURCES) $(HEADERS)  $(LISP) $(TAGS_FILES)'; \ +	unique=`for i in $$list; do \ +	    if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ +	  done | \ +	  $(AWK) '{ files[$$0] = 1; nonempty = 1; } \ +	      END { if (nonempty) { for (i in files) print i; }; }'`; \ +	test -z "$(CTAGS_ARGS)$$unique" \ +	  || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ +	     $$unique + +GTAGS: +	here=`$(am__cd) $(top_builddir) && pwd` \ +	  && $(am__cd) $(top_srcdir) \ +	  && gtags -i $(GTAGS_ARGS) "$$here" + +distclean-tags: +	-rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags + +distdir: $(DISTFILES) +	@srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ +	topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ +	list='$(DISTFILES)'; \ +	  dist_files=`for file in $$list; do echo $$file; done | \ +	  sed -e "s|^$$srcdirstrip/||;t" \ +	      -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ +	case $$dist_files in \ +	  */*) $(MKDIR_P) `echo "$$dist_files" | \ +			   sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ +			   sort -u` ;; \ +	esac; \ +	for file in $$dist_files; do \ +	  if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ +	  if test -d $$d/$$file; then \ +	    dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ +	    if test -d "$(distdir)/$$file"; then \ +	      find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ +	    fi; \ +	    if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ +	      cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ +	      find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ +	    fi; \ +	    cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ +	  else \ +	    test -f "$(distdir)/$$file" \ +	    || cp -p $$d/$$file "$(distdir)/$$file" \ +	    || exit 1; \ +	  fi; \ +	done +check-am: all-am +check: check-am +all-am: Makefile $(PROGRAMS) +installdirs: +	for dir in "$(DESTDIR)$(bindir)"; do \ +	  test -z "$$dir" || $(MKDIR_P) "$$dir"; \ +	done +install: install-am +install-exec: install-exec-am +install-data: install-data-am +uninstall: uninstall-am + +install-am: all-am +	@$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-am +install-strip: +	$(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ +	  install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ +	  `test -z '$(STRIP)' || \ +	    echo "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'"` install +mostlyclean-generic: + +clean-generic: + +distclean-generic: +	-test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) +	-test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) +	-test -z "$(DISTCLEANFILES)" || rm -f $(DISTCLEANFILES) + +maintainer-clean-generic: +	@echo "This command is intended for maintainers to use" +	@echo "it deletes files that may require special tools to rebuild." +clean: clean-am + +clean-am: clean-binPROGRAMS clean-generic mostlyclean-am + +distclean: distclean-am +	-rm -rf ./$(DEPDIR) +	-rm -f Makefile +distclean-am: clean-am distclean-compile distclean-generic \ +	distclean-tags + +dvi: dvi-am + +dvi-am: + +html: html-am + +html-am: + +info: info-am + +info-am: + +install-data-am: + +install-dvi: install-dvi-am + +install-dvi-am: + +install-exec-am: install-binPROGRAMS + +install-html: install-html-am + +install-html-am: + +install-info: install-info-am + +install-info-am: + +install-man: + +install-pdf: install-pdf-am + +install-pdf-am: + +install-ps: install-ps-am + +install-ps-am: + +installcheck-am: + +maintainer-clean: maintainer-clean-am +	-rm -rf ./$(DEPDIR) +	-rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-am + +mostlyclean-am: mostlyclean-compile mostlyclean-generic + +pdf: pdf-am + +pdf-am: + +ps: ps-am + +ps-am: + +uninstall-am: uninstall-binPROGRAMS + +.MAKE: install-am install-strip + +.PHONY: CTAGS GTAGS all all-am check check-am clean clean-binPROGRAMS \ +	clean-generic ctags distclean distclean-compile \ +	distclean-generic distclean-tags distdir dvi dvi-am html \ +	html-am info info-am install install-am install-binPROGRAMS \ +	install-data install-data-am install-dvi install-dvi-am \ +	install-exec install-exec-am install-html install-html-am \ +	install-info install-info-am install-man install-pdf \ +	install-pdf-am install-ps install-ps-am install-strip \ +	installcheck installcheck-am installdirs maintainer-clean \ +	maintainer-clean-generic mostlyclean mostlyclean-compile \ +	mostlyclean-generic pdf pdf-am ps ps-am tags uninstall \ +	uninstall-am uninstall-binPROGRAMS + + +# Tell versions [3.59,3.63) of GNU make to not export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/src/book-view.c b/src/book-view.c new file mode 100644 index 0000000..e45e22c --- /dev/null +++ b/src/book-view.c @@ -0,0 +1,743 @@ +/* + * Copyright (C) 2009 Canonical Ltd. + * Author: Robert Ancell <robert.ancell@canonical.com> + *  + * 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 3 of the License, or (at your option) any later + * version. See http://www.gnu.org/copyleft/gpl.html the full text of the + * license. + */ + +#include <gdk/gdkkeysyms.h> + +#include "book-view.h" +#include "page-view.h" + +// FIXME: When scrolling, copy existing render sideways? +// FIXME: Only render pages that change and only the part that changed + +enum { +    PAGE_SELECTED, +    SHOW_PAGE, +    LAST_SIGNAL +}; +static guint signals[LAST_SIGNAL] = { 0, }; + +struct BookViewPrivate +{ +    /* Book being rendered */ +    Book *book; +    GHashTable *page_data; +     +    /* True if the view needs to be laid out again */ +    gboolean need_layout, laying_out, show_selected_page; + +    /* Currently selected page */ +    PageView *selected_page; +   +    /* Widget being rendered to */ +    GtkWidget *widget; + +    /* Horizontal adjustment */ +    GtkAdjustment *adjustment; + +    GtkWidget *box, *scroll; +   +    GtkWidget *page_menu; + +    gint cursor; +}; + +G_DEFINE_TYPE (BookView, book_view, G_TYPE_OBJECT); + + +BookView * +book_view_new () +{ +    return g_object_new (BOOK_VIEW_TYPE, NULL); +} + + +static PageView * +get_nth_page (BookView *view, gint n) +{ +    Page *page = book_get_page (view->priv->book, n); +    return g_hash_table_lookup (view->priv->page_data, page); +} + + +static PageView * +get_next_page (BookView *view, PageView *page) +{ +    gint i; +     +    for (i = 0; ; i++) { +        Page *p; +        p = book_get_page (view->priv->book, i); +        if (!p) +            break; +        if (p == page_view_get_page (page)) { +            p = book_get_page (view->priv->book, i + 1); +            if (p) +                return g_hash_table_lookup (view->priv->page_data, p); +        } +    } +     +    return page; +} + + +static PageView * +get_prev_page (BookView *view, PageView *page) +{ +    gint i; +    PageView *prev_page = page; + +    for (i = 0; ; i++) { +        Page *p; +        p = book_get_page (view->priv->book, i); +        if (!p) +            break; +        if (p == page_view_get_page (page)) +            return prev_page; +        prev_page = g_hash_table_lookup (view->priv->page_data, p); +    } + +    return page; +} + + +static void +page_view_changed_cb (PageView *page, BookView *view) +{ +    book_view_redraw (view); +} + + +static void +page_view_size_changed_cb (PageView *page, BookView *view) +{ +    view->priv->need_layout = TRUE; +    book_view_redraw (view); +} + + +static void +add_cb (Book *book, Page *page, BookView *view) +{ +    PageView *page_view; +    page_view = page_view_new (page); +    g_signal_connect (page_view, "changed", G_CALLBACK (page_view_changed_cb), view); +    g_signal_connect (page_view, "size-changed", G_CALLBACK (page_view_size_changed_cb), view);   +    g_hash_table_insert (view->priv->page_data, page, page_view); +    view->priv->need_layout = TRUE; +    book_view_redraw (view); +} + + +static void +set_selected_page (BookView *view, PageView *page) +{ +    /* Deselect existing page if changed */ +    if (view->priv->selected_page && page != view->priv->selected_page) +        page_view_set_selected (view->priv->selected_page, FALSE);   + +    view->priv->selected_page = page; +    if (!view->priv->selected_page) +        return; + +    /* Select new page if widget has focus */ +    if (!gtk_widget_has_focus (view->priv->widget)) +        page_view_set_selected (view->priv->selected_page, FALSE); +    else +        page_view_set_selected (view->priv->selected_page, TRUE); +} + + +static void +set_x_offset (BookView *view, gint offset) +{ +    gtk_adjustment_set_value (view->priv->adjustment, offset); +} + + +static gint +get_x_offset (BookView *view) +{ +    return (gint) gtk_adjustment_get_value (view->priv->adjustment); +} + + +static void +show_page (BookView *view, PageView *page) +{ +    gint left_edge, right_edge; + +    if (!page || !gtk_widget_get_visible (view->priv->scroll)) +        return; + +    left_edge = page_view_get_x_offset (page); +    right_edge = page_view_get_x_offset (page) + page_view_get_width (page); + +    if (left_edge - get_x_offset (view) < 0) +        set_x_offset(view, left_edge); +    else if (right_edge - get_x_offset (view) > view->priv->widget->allocation.width) +       set_x_offset(view, right_edge - view->priv->widget->allocation.width); +} + + +static void +select_page (BookView *view, PageView *page) +{ +    Page *p = NULL; +   +    if (view->priv->selected_page == page) +        return; + +    set_selected_page (view, page); + +    if (view->priv->need_layout) +        view->priv->show_selected_page = TRUE; +    else +        show_page (view, page); + +    if (page) +        p = page_view_get_page (page); +    g_signal_emit (view, signals[PAGE_SELECTED], 0, p); +} + + +static void +remove_cb (Book *book, Page *page, BookView *view) +{ +    PageView *new_selection = view->priv->selected_page; + +    /* Select previous page or next if removing the selected page */ +    if (page == book_view_get_selected (view)) { +        new_selection = get_prev_page (view, view->priv->selected_page); +        if (new_selection == view->priv->selected_page) +            new_selection = get_next_page (view, view->priv->selected_page); +        view->priv->selected_page = NULL; +    } + +    g_hash_table_remove (view->priv->page_data, page); + +    select_page (view, new_selection); + +    view->priv->need_layout = TRUE; +    book_view_redraw (view); +} + + +static void +clear_cb (Book *book, BookView *view) +{ +    g_hash_table_remove_all (view->priv->page_data); +    view->priv->selected_page = NULL; +    g_signal_emit (view, signals[PAGE_SELECTED], 0, NULL); +    view->priv->need_layout = TRUE; +    book_view_redraw (view); +} + + +void +book_view_set_book (BookView *view, Book *book) +{ +    gint i, n_pages; + +    g_return_if_fail (view != NULL); +    g_return_if_fail (book != NULL); + +    view->priv->book = g_object_ref (book); + +    /* Load existing pages */ +    n_pages = book_get_n_pages (view->priv->book); +    for (i = 0; i < n_pages; i++) { +        Page *page = book_get_page (book, i); +        add_cb (book, page, view); +    } + +    book_view_select_page (view, book_get_page (book, 0)); + +    /* Watch for new pages */ +    g_signal_connect (book, "page-added", G_CALLBACK (add_cb), view); +    g_signal_connect (book, "page-removed", G_CALLBACK (remove_cb), view); +    g_signal_connect (book, "cleared", G_CALLBACK (clear_cb), view); +} + + +Book * +book_view_get_book (BookView *view) +{ +    g_return_val_if_fail (view != NULL, NULL); + +    return view->priv->book; +} + + +static gboolean +configure_cb (GtkWidget *widget, GdkEventConfigure *event, BookView *view) +{ +    view->priv->need_layout = TRUE; +    return FALSE; +} + + +static void +layout_into (BookView *view, gint width, gint height, gint *book_width, gint *book_height) +{ +    gint spacing = 12; +    gint max_width = 0, max_height = 0; +    gdouble aspect, max_aspect; +    gint x_offset = 0; +    gint i, n_pages; +    gint max_dpi = 0; + +    n_pages = book_get_n_pages (view->priv->book); + +    /* Get maximum page resolution */ +    for (i = 0; i < n_pages; i++) { +        Page *page = book_get_page (view->priv->book, i); +        if (page_get_dpi (page) > max_dpi) +            max_dpi = page_get_dpi (page); +    } + +    /* Get area required to fit all pages */ +    for (i = 0; i < n_pages; i++) { +        Page *page = book_get_page (view->priv->book, i); +        gint w, h; + +        w = page_get_width (page); +        h = page_get_height (page); + +        /* Scale to the same DPI */ +        w = (double)w * max_dpi / page_get_dpi (page) + 0.5; +        h = (double)h * max_dpi / page_get_dpi (page) + 0.5; + +        if (w > max_width) +            max_width = w; +        if (h > max_height) +            max_height = h; +    } + +    aspect = (double)width / height; +    max_aspect = (double)max_width / max_height; + +    /* Get total dimensions of all pages */ +    *book_width = 0; +    *book_height = 0; +    for (i = 0; i < n_pages; i++) { +        PageView *page = get_nth_page (view, i); +        Page *p = page_view_get_page (page); +        gint h; + +        /* NOTE: Using double to avoid overflow for large images */ +        if (max_aspect > aspect) { +            /* Set width scaled on DPI and maximum width */ +            gint w = (double)page_get_width (p) * max_dpi * width / (page_get_dpi (p) * max_width); +            page_view_set_width (page, w); +        } +        else { +            /* Set height scaled on DPI and maximum height */ +            gint h = (double)page_get_height (p) * max_dpi * height / (page_get_dpi (p) * max_height); +            page_view_set_height (page, h); +        } + +        h = page_view_get_height (page); +        if (h > *book_height) +            *book_height = h; +        *book_width += page_view_get_width (page); +        if (i != 0) +            *book_width += spacing; +    } + +    for (i = 0; i < n_pages; i++) { +        PageView *page = get_nth_page (view, i); + +        /* Layout pages left to right */ +        page_view_set_x_offset (page, x_offset); +        x_offset += page_view_get_width (page) + spacing; + +        /* Centre page vertically */ +        page_view_set_y_offset (page, (height - page_view_get_height (page)) / 2); +    } +} + + +static void +layout (BookView *view) +{ +    gint width, height, book_width, book_height; +    gboolean right_aligned = TRUE; + +    if (!view->priv->need_layout) +        return; +   +    view->priv->laying_out = TRUE; + +    /* If scroll is right aligned then keep that after layout */ +    if (gtk_adjustment_get_value (view->priv->adjustment) < gtk_adjustment_get_upper (view->priv->adjustment) - gtk_adjustment_get_page_size (view->priv->adjustment)) +        right_aligned = FALSE; +   +    /* Try and fit without scrollbar */ +    width = view->priv->widget->allocation.width; +    height = view->priv->box->allocation.height; +    layout_into (view, width, height, &book_width, &book_height); + +    /* Relayout with scrollbar */ +    if (book_width > view->priv->widget->allocation.width) { +        gint max_offset; +       +        /* Re-layout leaving space for scrollbar */ +        height = view->priv->widget->allocation.height; +        layout_into (view, width, height, &book_width, &book_height); + +        /* Set scrollbar limits */ +        gtk_adjustment_set_lower (view->priv->adjustment, 0); +        gtk_adjustment_set_upper (view->priv->adjustment, book_width); +        gtk_adjustment_set_page_size (view->priv->adjustment, view->priv->widget->allocation.width); + +        /* Keep right-aligned */ +        max_offset = book_width - view->priv->widget->allocation.width; +        if (right_aligned || get_x_offset (view) > max_offset) +            set_x_offset(view, max_offset); + +        gtk_widget_show (view->priv->scroll); +    } else { +        gint offset; +        gtk_widget_hide (view->priv->scroll); +        offset = (book_width - view->priv->widget->allocation.width) / 2; +        gtk_adjustment_set_lower (view->priv->adjustment, offset); +        gtk_adjustment_set_upper (view->priv->adjustment, offset); +        gtk_adjustment_set_page_size (view->priv->adjustment, 0); +        set_x_offset(view, offset); +    } +   +    if (view->priv->show_selected_page) +       show_page (view, view->priv->selected_page); + +    view->priv->need_layout = FALSE; +    view->priv->show_selected_page = FALSE; +    view->priv->laying_out = FALSE; +} + + +static gboolean +expose_cb (GtkWidget *widget, GdkEventExpose *event, BookView *view) +{ +    gint i, n_pages; +    cairo_t *context; + +    n_pages = book_get_n_pages (view->priv->book); +    if (n_pages == 0) +        return FALSE; + +    layout (view); + +    context = gdk_cairo_create (widget->window); + +    /* Render each page */ +    for (i = 0; i < n_pages; i++) { +        PageView *page = get_nth_page (view, i); +        gint left_edge, right_edge; +       +        left_edge = page_view_get_x_offset (page) - get_x_offset (view); +        right_edge = page_view_get_x_offset (page) + page_view_get_width (page) - get_x_offset (view); +       +        /* Page not visible, don't render */ +        if (right_edge < event->area.x || left_edge > event->area.x + event->area.width) +            continue; + +        cairo_save (context); +        cairo_translate (context, -get_x_offset (view), 0); +        page_view_render (page, context); +        cairo_restore (context); + +        if (page_view_get_selected (page)) +            gtk_paint_focus (gtk_widget_get_style (view->priv->widget), +                             gtk_widget_get_window (view->priv->widget), +                             GTK_STATE_SELECTED, +                             &event->area, +                             NULL, +                             NULL, +                             page_view_get_x_offset (page) - get_x_offset (view), +                             page_view_get_y_offset (page), +                             page_view_get_width (page), +                             page_view_get_height (page)); +    } + +    cairo_destroy (context); + +    return FALSE; +} + + +static PageView * +get_page_at (BookView *view, gint x, gint y, gint *x_, gint *y_) +{ +    gint i, n_pages; + +    n_pages = book_get_n_pages (view->priv->book); +    for (i = 0; i < n_pages; i++) { +        PageView *page; +        gint left, right, top, bottom; + +        page = get_nth_page (view, i); +        left = page_view_get_x_offset (page); +        right = left + page_view_get_width (page); +        top = page_view_get_y_offset (page); +        bottom = top + page_view_get_height (page); +        if (x >= left && x <= right && y >= top && y <= bottom)  +        { +            *x_ = x - left; +            *y_ = y - top; +            return page; +        } +    } + +    return NULL; +} + + +static gboolean +button_cb (GtkWidget *widget, GdkEventButton *event, BookView *view) +{ +    gint x, y; + +    layout (view); + +    gtk_widget_grab_focus (view->priv->widget); + +    if (event->type == GDK_BUTTON_PRESS) +        select_page (view, get_page_at (view, event->x + get_x_offset (view), event->y, &x, &y)); + +    if (!view->priv->selected_page) +        return FALSE; + +    /* Modify page */ +    if (event->button == 1) { +        if (event->type == GDK_BUTTON_PRESS) +            page_view_button_press (view->priv->selected_page, x, y); +        else if (event->type == GDK_BUTTON_RELEASE) +            page_view_button_release (view->priv->selected_page, x, y); +        else if (event->type == GDK_2BUTTON_PRESS) +            g_signal_emit (view, signals[SHOW_PAGE], 0, book_view_get_selected (view)); +    } + +    /* Show pop-up menu on right click */ +    if (event->button == 3) { +        gtk_menu_popup (GTK_MENU (view->priv->page_menu), NULL, NULL, NULL, NULL, +                        event->button, event->time); +    } + +    return FALSE; +} + + +static void +set_cursor (BookView *view, gint cursor) +{ +    GdkCursor *c; +   +    if (view->priv->cursor == cursor) +        return; +    view->priv->cursor = cursor; + +    c = gdk_cursor_new (cursor); +    gdk_window_set_cursor (gtk_widget_get_window (view->priv->widget), c); +    gdk_cursor_destroy (c); +} + + +static gboolean +motion_cb (GtkWidget *widget, GdkEventMotion *event, BookView *view) +{ +    gint x, y; +    gint cursor = GDK_ARROW; +  +    /* Dragging */ +    if (view->priv->selected_page && (event->state & GDK_BUTTON1_MASK) != 0) { +        x = event->x + get_x_offset (view) - page_view_get_x_offset (view->priv->selected_page); +        y = event->y - page_view_get_y_offset (view->priv->selected_page); +        page_view_motion (view->priv->selected_page, x, y); +        cursor = page_view_get_cursor (view->priv->selected_page); +    } +    else { +        PageView *over_page; +        over_page = get_page_at (view, event->x + get_x_offset (view), event->y, &x, &y); +        if (over_page) { +            page_view_motion (over_page, x, y); +            cursor = page_view_get_cursor (over_page); +        } +    } + +    set_cursor (view, cursor); + +    return FALSE; +} + + +static gboolean +key_cb (GtkWidget *widget, GdkEventKey *event, BookView *view) +{ +    switch (event->keyval) { +    case GDK_Home: +        book_view_select_page (view, book_get_page (view->priv->book, 0)); +        return TRUE; +    case GDK_Left: +        select_page (view, get_prev_page (view, view->priv->selected_page)); +        return TRUE; +    case GDK_Right: +        select_page (view, get_next_page (view, view->priv->selected_page)); +        return TRUE; +    case GDK_End: +        book_view_select_page (view, book_get_page (view->priv->book, book_get_n_pages (view->priv->book) - 1)); +        return TRUE; + +    default: +        return FALSE; +    } +} + + +static gboolean +focus_cb (GtkWidget *widget, GdkEventFocus *event, BookView *view) +{ +    set_selected_page (view, view->priv->selected_page); +    return FALSE; +} + + +static void +scroll_cb (GtkAdjustment *adjustment, BookView *view) +{ +   if (!view->priv->laying_out) +       book_view_redraw (view); +} + + +void +book_view_set_widgets (BookView *view, GtkWidget *box, GtkWidget *area, GtkWidget *scroll, GtkWidget *page_menu) +{ +    g_return_if_fail (view != NULL); +    g_return_if_fail (view->priv->widget == NULL); + +    view->priv->widget = area; +    view->priv->box = box; +    view->priv->scroll = scroll; +    view->priv->adjustment = gtk_range_get_adjustment (GTK_RANGE (scroll)); +    view->priv->page_menu = page_menu; + +    g_signal_connect (area, "configure-event", G_CALLBACK (configure_cb), view); +    g_signal_connect (area, "expose-event", G_CALLBACK (expose_cb), view); +    g_signal_connect (area, "motion-notify-event", G_CALLBACK (motion_cb), view); +    g_signal_connect (area, "key-press-event", G_CALLBACK (key_cb), view); +    g_signal_connect (area, "button-press-event", G_CALLBACK (button_cb), view); +    g_signal_connect (area, "button-release-event", G_CALLBACK (button_cb), view); +    g_signal_connect_after (area, "focus-in-event", G_CALLBACK (focus_cb), view); +    g_signal_connect_after (area, "focus-out-event", G_CALLBACK (focus_cb), view); +    g_signal_connect (view->priv->adjustment, "value-changed", G_CALLBACK (scroll_cb), view); +} + + +void +book_view_redraw (BookView *view) +{ +    g_return_if_fail (view != NULL); +    gtk_widget_queue_draw (view->priv->widget);   +} + + +void +book_view_select_page (BookView *view, Page *page) +{ +    g_return_if_fail (view != NULL); + +    if (book_view_get_selected (view) == page) +        return; + +    if (page) +        select_page (view, g_hash_table_lookup (view->priv->page_data, page)); +    else +        select_page (view, NULL); +} + + +void +book_view_select_next_page (BookView *view) +{ +    g_return_if_fail (view != NULL); +    select_page (view, get_next_page (view, view->priv->selected_page)); +} + + +void +book_view_select_prev_page (BookView *view) +{ +    g_return_if_fail (view != NULL); +    select_page (view, get_prev_page (view, view->priv->selected_page)); +} + + +Page * +book_view_get_selected (BookView *view) +{ +    g_return_val_if_fail (view != NULL, NULL); + +    if (view->priv->selected_page) +        return page_view_get_page (view->priv->selected_page); +    else +        return NULL; +} + + +static void +book_view_finalize (GObject *object) +{ +    BookView *view = BOOK_VIEW (object); +    g_object_unref (view->priv->book); +    view->priv->book = NULL; +    g_hash_table_unref (view->priv->page_data); +    view->priv->page_data = NULL; +    G_OBJECT_CLASS (book_view_parent_class)->finalize (object); +} + + +static void +book_view_class_init (BookViewClass *klass) +{ +    GObjectClass *object_class = G_OBJECT_CLASS (klass); + +    object_class->finalize = book_view_finalize; + +    signals[PAGE_SELECTED] = +        g_signal_new ("page-selected", +                      G_TYPE_FROM_CLASS (klass), +                      G_SIGNAL_RUN_LAST, +                      G_STRUCT_OFFSET (BookViewClass, page_selected), +                      NULL, NULL, +                      g_cclosure_marshal_VOID__POINTER, +                      G_TYPE_NONE, 1, G_TYPE_POINTER); +    signals[SHOW_PAGE] = +        g_signal_new ("show-page", +                      G_TYPE_FROM_CLASS (klass), +                      G_SIGNAL_RUN_LAST, +                      G_STRUCT_OFFSET (BookViewClass, show_page), +                      NULL, NULL, +                      g_cclosure_marshal_VOID__POINTER, +                      G_TYPE_NONE, 1, G_TYPE_POINTER); + +    g_type_class_add_private (klass, sizeof (BookViewPrivate)); +} + + +static void +book_view_init (BookView *view) +{ +    view->priv = G_TYPE_INSTANCE_GET_PRIVATE (view, BOOK_VIEW_TYPE, BookViewPrivate); +    view->priv->need_layout = TRUE; +    view->priv->page_data = g_hash_table_new_full (g_direct_hash, g_direct_equal, +                                                   NULL, (GDestroyNotify) g_object_unref); +    view->priv->cursor = GDK_ARROW; +} diff --git a/src/book-view.h b/src/book-view.h new file mode 100644 index 0000000..acc3899 --- /dev/null +++ b/src/book-view.h @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2009 Canonical Ltd. + * Author: Robert Ancell <robert.ancell@canonical.com> + *  + * 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 3 of the License, or (at your option) any later + * version. See http://www.gnu.org/copyleft/gpl.html the full text of the + * license. + */ + +#ifndef _BOOK_VIEW_H_ +#define _BOOK_VIEW_H_ + +#include <glib-object.h> +#include <gtk/gtk.h> +#include <cairo.h> +#include "book.h" + +G_BEGIN_DECLS + +#define BOOK_VIEW_TYPE  (book_view_get_type ()) +#define BOOK_VIEW(obj)  (G_TYPE_CHECK_INSTANCE_CAST ((obj), BOOK_VIEW_TYPE, BookView)) + + +typedef struct BookViewPrivate BookViewPrivate; + +typedef struct +{ +    GObject          parent_instance; +    BookViewPrivate *priv; +} BookView; + +typedef struct +{ +    GObjectClass parent_class; +     +    void (*page_selected) (BookView *view, Page *page); +    void (*show_page) (BookView *view, Page *page); +} BookViewClass; + + +GType book_view_get_type (void); + +BookView *book_view_new (void); + +// FIXME: Book view should extend GtkVBox +void book_view_set_widgets (BookView *view, GtkWidget *box, GtkWidget *area, GtkWidget *scroll, GtkWidget *page_menu); + +// FIXME: Should be part of book_view_new +void book_view_set_book (BookView *view, Book *book); + +void book_view_redraw (BookView *view); + +Book *book_view_get_book (BookView *view); + +void book_view_select_page (BookView *view, Page *page); + +void book_view_select_next_page (BookView *view); + +void book_view_select_prev_page (BookView *view); + +Page *book_view_get_selected (BookView *view); + +#endif /* _BOOK_VIEW_H_ */ diff --git a/src/book.c b/src/book.c new file mode 100644 index 0000000..6bf4d0a --- /dev/null +++ b/src/book.c @@ -0,0 +1,466 @@ +/* + * Copyright (C) 2009 Canonical Ltd. + * Author: Robert Ancell <robert.ancell@canonical.com> + *  + * 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 3 of the License, or (at your option) any later + * version. See http://www.gnu.org/copyleft/gpl.html the full text of the + * license. + */ + +#include <string.h> +#include <math.h> +#include <gdk/gdk.h> +#include <gdk-pixbuf/gdk-pixbuf.h> +#include <cairo/cairo-pdf.h> +#include <cairo/cairo-ps.h> +#include <unistd.h> // TEMP: Needed for close() in get_temporary_filename() + +#include "book.h" + + +enum { +    PAGE_ADDED, +    PAGE_REMOVED, +    CLEARED, +    LAST_SIGNAL +}; +static guint signals[LAST_SIGNAL] = { 0, }; + +struct BookPrivate +{ +    GList *pages; +}; + +G_DEFINE_TYPE (Book, book, G_TYPE_OBJECT); + + +Book * +book_new () +{ +    return g_object_new (BOOK_TYPE, NULL); +} + + +void +book_clear (Book *book) +{ +    GList *iter; +    for (iter = book->priv->pages; iter; iter = iter->next) { +        Page *page = iter->data; +        g_object_unref (page); +    } +    g_list_free (book->priv->pages); +    book->priv->pages = NULL; +    g_signal_emit (book, signals[CLEARED], 0); +} + + +Page * +book_append_page (Book *book, gint width, gint height, gint dpi, Orientation orientation) +{ +    Page *page; + +    page = page_new (); +    page_setup (page, width, height, dpi, orientation); + +    book->priv->pages = g_list_append (book->priv->pages, page); + +    g_signal_emit (book, signals[PAGE_ADDED], 0, page); + +    return page; +} + + +void +book_delete_page (Book *book, Page *page) +{ +    g_signal_emit (book, signals[PAGE_REMOVED], 0, page); + +    book->priv->pages = g_list_remove (book->priv->pages, page); +    g_object_unref (page); +} + + +gint +book_get_n_pages (Book *book) +{ +    return g_list_length (book->priv->pages);     +} + + +Page * +book_get_page (Book *book, gint page_number) +{ +    if (page_number < 0) +        page_number = g_list_length (book->priv->pages) + page_number; +    return g_list_nth_data (book->priv->pages, page_number); +} + + +static GFile * +make_indexed_file (const gchar *uri, gint i) +{ +    gchar *basename, *suffix, *indexed_uri; +    GFile *file; + +    if (i == 0) +        return g_file_new_for_uri (uri); + +    basename = g_path_get_basename (uri); +    suffix = g_strrstr (basename, "."); + +    if (suffix) +        indexed_uri = g_strdup_printf ("%.*s-%d%s", (int) (strlen (uri) - strlen (suffix)), uri, i, suffix); +    else +        indexed_uri = g_strdup_printf ("%s-%d", uri, i); +    g_free (basename); + +    file = g_file_new_for_uri (indexed_uri); +    g_free (indexed_uri); + +    return file; +} + + +static gboolean +book_save_multi_file (Book *book, const gchar *type, GFile *file, GError **error) +{ +    GList *iter; +    gboolean result = TRUE; +    gint i; +    gchar *uri; + +    uri = g_file_get_uri (file); +    for (iter = book->priv->pages, i = 0; iter && result; iter = iter->next, i++) { +        Page *page = iter->data; +        GFile *file; + +        file = make_indexed_file (uri, i); +        result = page_save (page, type, file, error); +        g_object_unref (file); +    } +    g_free (uri); +    +    return result; +} + + +static void +save_ps_pdf_surface (cairo_surface_t *surface, GdkPixbuf *image, gdouble dpi) +{ +    cairo_t *context; +     +    context = cairo_create (surface); + +    cairo_scale (context, 72.0 / dpi, 72.0 / dpi); +    gdk_cairo_set_source_pixbuf (context, image, 0, 0); +    cairo_pattern_set_filter (cairo_get_source (context), CAIRO_FILTER_BEST); +    cairo_paint (context); + +    cairo_destroy (context); +} + + +static cairo_status_t +write_cairo_data (GFileOutputStream *stream, unsigned char *data, unsigned int length) +{ +    gboolean result; +    GError *error = NULL; + +    result = g_output_stream_write_all (G_OUTPUT_STREAM (stream), data, length, NULL, NULL, &error); +     +    if (error) { +        g_warning ("Error writing data: %s", error->message); +        g_error_free (error); +    } + +    return result ? CAIRO_STATUS_SUCCESS : CAIRO_STATUS_WRITE_ERROR; +} + + +static gboolean +book_save_ps (Book *book, GFile *file, GError **error) +{ +    GFileOutputStream *stream; +    GList *iter; +    cairo_surface_t *surface; + +    stream = g_file_replace (file, NULL, FALSE, G_FILE_CREATE_NONE, NULL, error); +    if (!stream) +        return FALSE; + +    surface = cairo_ps_surface_create_for_stream ((cairo_write_func_t) write_cairo_data, +                                                  stream, 0, 0); + +    for (iter = book->priv->pages; iter; iter = iter->next) { +        Page *page = iter->data; +        double width, height; +        GdkPixbuf *image; + +        image = page_get_cropped_image (page); + +        width = gdk_pixbuf_get_width (image) * 72.0 / page_get_dpi (page); +        height = gdk_pixbuf_get_height (image) * 72.0 / page_get_dpi (page); +        cairo_ps_surface_set_size (surface, width, height); +        save_ps_pdf_surface (surface, image, page_get_dpi (page)); +        cairo_surface_show_page (surface); +         +        g_object_unref (image); +    } + +    cairo_surface_destroy (surface); + +    g_object_unref (stream); + +    return TRUE; +} + + +// TEMP: Copied from simple-scan.c +static GFile * +get_temporary_file (const gchar *prefix, const gchar *extension) +{ +    gint fd; +    GFile *file; +    gchar *filename, *path; +    GError *error = NULL; + +    /* NOTE: I'm not sure if this is a 100% safe strategy to use g_file_open_tmp(), close and +     * use the filename but it appears to work in practise */ + +    filename = g_strdup_printf ("%s-XXXXXX.%s", prefix, extension); +    fd = g_file_open_tmp (filename, &path, &error); +    g_free (filename); +    if (fd < 0) { +        g_warning ("Error saving email attachment: %s", error->message); +        g_clear_error (&error); +        return NULL; +    } +    close (fd); +   +    file = g_file_new_for_path (path); +    g_free (path); + +    return file; +} + + +static goffset +get_file_size (GFile *file) +{ +    GFileInfo *info; +    goffset size = 0; +   +    info = g_file_query_info (file, +                              G_FILE_ATTRIBUTE_STANDARD_SIZE, +                              G_FILE_QUERY_INFO_NONE, +                              NULL, +                              NULL); +    if (info) { +        size = g_file_info_get_size (info); +        g_object_unref (info); +    } + +    return size; +} + + +static gboolean +book_save_pdf_with_imagemagick (Book *book, GFile *file, GError **error) +{ +    GList *iter; +    GString *command_line; +    gboolean result = TRUE; +    gint exit_status = 0; +    GFile *output_file = NULL; +    GList *link, *temporary_files = NULL; + +    /* ImageMagick command to create a PDF */ +    command_line = g_string_new ("convert -adjoin"); + +    /* Save each page to a file */ +    for (iter = book->priv->pages; iter && result; iter = iter->next) { +        Page *page = iter->data; +        GFile *jpeg_file, *tiff_file; +        gchar *path; +        gint jpeg_size, tiff_size; + +        jpeg_file = get_temporary_file ("simple-scan", "jpg"); +        result = page_save (page, "jpeg", jpeg_file, error); +        jpeg_size = get_file_size (jpeg_file); +        temporary_files = g_list_append (temporary_files, jpeg_file); + +        tiff_file = get_temporary_file ("simple-scan", "tiff"); +        result = page_save (page, "tiff", tiff_file, error); +        tiff_size = get_file_size (tiff_file); +        temporary_files = g_list_append (temporary_files, tiff_file); + +        /* Use the smallest file */ +        if (jpeg_size < tiff_size) +            path = g_file_get_path (jpeg_file); +        else +            path = g_file_get_path (tiff_file); +        g_string_append_printf (command_line, " %s", path); +        g_free (path); +    } + +    /* Use ImageMagick command to create a PDF */   +    if (result) { +        gchar *path, *stdout_text = NULL, *stderr_text = NULL; + +        output_file = get_temporary_file ("simple-scan", "pdf"); +        path = g_file_get_path (output_file); +        g_string_append_printf (command_line, " %s", path); +        g_free (path); + +        result = g_spawn_command_line_sync (command_line->str, &stdout_text, &stderr_text, &exit_status, error); +        if (result && exit_status != 0) { +            g_warning ("ImageMagick returned error code %d, command line was: %s", exit_status, command_line->str); +            g_warning ("stdout: %s", stdout_text); +            g_warning ("stderr: %s", stderr_text); +            result = FALSE; +        } +        g_free (stdout_text); +        g_free (stderr_text); +    } + +    /* Move to target URI */ +    if (result) { +        GFile *dest; +        result = g_file_move (output_file, file, G_FILE_COPY_OVERWRITE, NULL, NULL, NULL, error); +        g_object_unref (dest); +    } +   +    /* Delete page files */ +    for (link = temporary_files; link; link = link->next) { +        GFile *f = link->data; + +        g_file_delete (f, NULL, NULL); +        g_object_unref (f); +    } +    g_list_free (temporary_files); + +    if (output_file) +        g_object_unref (output_file); +    g_string_free (command_line, TRUE); + +    return result; +} + + +static gboolean +book_save_pdf (Book *book, GFile *file, GError **error) +{ +    GFileOutputStream *stream; +    GList *iter; +    cairo_surface_t *surface; +    gchar *imagemagick_executable; +   +    /* Use ImageMagick if it is available as then we can compress the images */ +    imagemagick_executable = g_find_program_in_path ("convert"); +    if (imagemagick_executable) { +        g_free (imagemagick_executable); +        return book_save_pdf_with_imagemagick (book, file, error); +    } + +    stream = g_file_replace (file, NULL, FALSE, G_FILE_CREATE_NONE, NULL, error); +    if (!stream) +        return FALSE; + +    surface = cairo_pdf_surface_create_for_stream ((cairo_write_func_t) write_cairo_data, +                                                   stream, 0, 0); + +    for (iter = book->priv->pages; iter; iter = iter->next) { +        Page *page = iter->data; +        double width, height; +        GdkPixbuf *image; + +        image = page_get_cropped_image (page); + +        width = gdk_pixbuf_get_width (image) * 72.0 / page_get_dpi (page); +        height = gdk_pixbuf_get_height (image) * 72.0 / page_get_dpi (page); +        cairo_pdf_surface_set_size (surface, width, height); +        save_ps_pdf_surface (surface, image, page_get_dpi (page)); +        cairo_surface_show_page (surface); +         +        g_object_unref (image); +    } + +    cairo_surface_destroy (surface); + +    g_object_unref (stream); + +    return TRUE; +} + + +gboolean +book_save (Book *book, const gchar *type, GFile *file, GError **error) +{ +    if (strcmp (type, "jpeg") == 0) +        return book_save_multi_file (book, "jpeg", file, error); +    else if (strcmp (type, "png") == 0) +        return book_save_multi_file (book, "png", file, error); +    else if (strcmp (type, "tiff") == 0) +        return book_save_multi_file (book, "tiff", file, error);     +    else if (strcmp (type, "ps") == 0) +        return book_save_ps (book, file, error);     +    else if (strcmp (type, "pdf") == 0) +        return book_save_pdf (book, file, error); +    else +        return FALSE; +} + + +static void +book_finalize (GObject *object) +{ +    Book *book = BOOK (object); +    book_clear (book); +    G_OBJECT_CLASS (book_parent_class)->finalize (object); +} + + +static void +book_class_init (BookClass *klass) +{ +    GObjectClass *object_class = G_OBJECT_CLASS (klass); + +    object_class->finalize = book_finalize; + +    signals[PAGE_ADDED] = +        g_signal_new ("page-added", +                      G_TYPE_FROM_CLASS (klass), +                      G_SIGNAL_RUN_LAST, +                      G_STRUCT_OFFSET (BookClass, page_added), +                      NULL, NULL, +                      g_cclosure_marshal_VOID__POINTER, +                      G_TYPE_NONE, 1, G_TYPE_POINTER); +    signals[PAGE_REMOVED] = +        g_signal_new ("page-removed", +                      G_TYPE_FROM_CLASS (klass), +                      G_SIGNAL_RUN_LAST, +                      G_STRUCT_OFFSET (BookClass, page_removed), +                      NULL, NULL, +                      g_cclosure_marshal_VOID__POINTER, +                      G_TYPE_NONE, 1, G_TYPE_POINTER); +    signals[CLEARED] = +        g_signal_new ("cleared", +                      G_TYPE_FROM_CLASS (klass), +                      G_SIGNAL_RUN_LAST, +                      G_STRUCT_OFFSET (BookClass, cleared), +                      NULL, NULL, +                      g_cclosure_marshal_VOID__VOID, +                      G_TYPE_NONE, 0); + +    g_type_class_add_private (klass, sizeof (BookPrivate)); +} + + +static void +book_init (Book *book) +{ +    book->priv = G_TYPE_INSTANCE_GET_PRIVATE (book, BOOK_TYPE, BookPrivate); +} diff --git a/src/book.h b/src/book.h new file mode 100644 index 0000000..4e9e05e --- /dev/null +++ b/src/book.h @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2009 Canonical Ltd. + * Author: Robert Ancell <robert.ancell@canonical.com> + *  + * 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 3 of the License, or (at your option) any later + * version. See http://www.gnu.org/copyleft/gpl.html the full text of the + * license. + */ + +#ifndef _BOOK_H_ +#define _BOOK_H_ + +#include <glib-object.h> +#include <gio/gio.h> +#include <cairo.h> +#include "page.h" + +G_BEGIN_DECLS + +#define BOOK_TYPE  (book_get_type ()) +#define BOOK(obj)  (G_TYPE_CHECK_INSTANCE_CAST ((obj), BOOK_TYPE, Book)) + + +typedef struct BookPrivate BookPrivate; + +typedef struct +{ +    GObject      parent_instance; +    BookPrivate *priv; +} Book; + +typedef struct +{ +    GObjectClass parent_class; + +    void (*page_added) (Book *book, Page *page); +    void (*page_removed) (Book *book, Page *page); +    void (*cleared) (Book *book); +} BookClass; + + +GType book_get_type (void); + +Book *book_new (void); + +void book_clear (Book *book); + +Page *book_append_page (Book *book, gint width, gint height, gint dpi, Orientation orientation); + +void book_delete_page (Book *book, Page *page); + +gint book_get_n_pages (Book *book); + +Page *book_get_page (Book *book, gint page_number); + +gboolean book_save (Book *book, const gchar *type, GFile *file, GError **error); + +#endif /* _BOOK_H_ */ diff --git a/src/page-view.c b/src/page-view.c new file mode 100644 index 0000000..2c37fc2 --- /dev/null +++ b/src/page-view.c @@ -0,0 +1,1170 @@ +/* + * Copyright (C) 2009 Canonical Ltd. + * Author: Robert Ancell <robert.ancell@canonical.com> + *  + * 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 3 of the License, or (at your option) any later + * version. See http://www.gnu.org/copyleft/gpl.html the full text of the + * license. + */ + +#include <math.h> + +#include "page-view.h" + +enum { +    CHANGED, +    SIZE_CHANGED, +    LAST_SIGNAL +}; +static guint signals[LAST_SIGNAL] = { 0, }; + +enum { +    PROP_0, +    PROP_PAGE +}; + +typedef enum +{ +    CROP_NONE = 0, +    CROP_MIDDLE, +    CROP_TOP, +    CROP_BOTTOM, +    CROP_LEFT, +    CROP_RIGHT, +    CROP_TOP_LEFT, +    CROP_TOP_RIGHT, +    CROP_BOTTOM_LEFT, +    CROP_BOTTOM_RIGHT +} CropLocation; + +struct PageViewPrivate +{ +    /* Page being rendered */ +    Page *page; + +    /* Image to render at current resolution */ +    GdkPixbuf *image; +   +    /* Border around image */ +    gboolean selected; +    gint border_width; + +    /* True if image needs to be regenerated */ +    gboolean update_image; +   +    /* Next scan line to render */ +    gint scan_line; + +    /* Dimensions of image to generate */ +    gint width, height; + +    /* Location to place this page */ +    gint x, y; + +    CropLocation crop_location; +    gdouble selected_crop_px, selected_crop_py; +    gint selected_crop_x, selected_crop_y; +    gint selected_crop_w, selected_crop_h; + +    /* Cursor over this page */ +    gint cursor; + +    gint animate_n_segments, animate_segment; +    guint animate_timeout; +}; + +G_DEFINE_TYPE (PageView, page_view, G_TYPE_OBJECT); + + +PageView * +page_view_new (Page *page) +{ +    return g_object_new (PAGE_VIEW_TYPE, "page", page, NULL); +} + + +Page * +page_view_get_page (PageView *view) +{ +    g_return_val_if_fail (view != NULL, NULL); +    return view->priv->page; +} + + +void +page_view_set_selected (PageView *view, gboolean selected) +{ +    g_return_if_fail (view != NULL); +    if ((view->priv->selected && selected) || (!view->priv->selected && !selected)) +        return; +    view->priv->selected = selected; +    g_signal_emit (view, signals[CHANGED], 0);   +} + + +gboolean page_view_get_selected (PageView *view) +{ +    g_return_val_if_fail (view != NULL, FALSE); +    return view->priv->selected; +} + + +void +page_view_set_x_offset (PageView *view, gint offset) +{ +    g_return_if_fail (view != NULL); +    view->priv->x = offset; +} + + +void +page_view_set_y_offset (PageView *view, gint offset) +{ +    g_return_if_fail (view != NULL); +    view->priv->y = offset; +} + + +gint +page_view_get_x_offset (PageView *view) +{ +    g_return_val_if_fail (view != NULL, 0);   +    return view->priv->x;   +} + + +gint +page_view_get_y_offset (PageView *view) +{ +    g_return_val_if_fail (view != NULL, 0); +    return view->priv->y; +} + + +static guchar * +get_pixel (guchar *input, gint rowstride, gint n_channels, gint x, gint y) +{ +    return input + rowstride * y + x * n_channels; +} + + +static void +set_pixel (guchar *input, gint rowstride, gint n_channels, +           double l, double r, double t, double b, guchar *pixel) +{ +    gint x, y; +    gint L, R, T, B; +    double scale, red, green, blue; +   +    /* Decimation: +     *  +     * Target pixel is defined by (t,l)-(b,r) +     * It touches 16 pixels in original image +     * It completely covers 4 pixels in original image (T,L)-(B,R) +     * Add covered pixels and add weighted partially covered pixels. +     * Divide by total area. +     *  +     *      l  L           R   r  +     *   +-----+-----+-----+-----+ +     *   |     |     |     |     | +     * t |  +--+-----+-----+---+ | +     * T +--+--+-----+-----+---+-+ +     *   |  |  |     |     |   | | +     *   |  |  |     |     |   | | +     *   +--+--+-----+-----+---+-+ +     *   |  |  |     |     |   | | +     *   |  |  |     |     |   | | +     * B +--+--+-----+-----+---+-+ +     *   |  |  |     |     |   | | +     * b |  +--+-----+-----+---+ | +     *   +-----+-----+-----+-----+ +     *  +     *  +     * Interpolation: +     *  +     *             l    r +     *   +-----+-----+-----+-----+ +     *   |     |     |     |     | +     *   |     |     |     |     | +     *   +-----+-----+-----+-----+ +     * t |     |   +-+--+  |     | +     *   |     |   | |  |  |     | +     *   +-----+---+-+--+--+-----+ +     * b |     |   +-+--+  |     | +     *   |     |     |     |     | +     *   +-----+-----+-----+-----+ +     *   |     |     |     |     | +     *   |     |     |     |     | +     *   +-----+-----+-----+-----+ +     *  +     * Same again, just no completely covered pixels. +     */ +   +    L = l; +    if (L != l) +        L++; +    R = r; +    T = t; +    if (T != t) +        T++; +    B = b; +   +    red = green = blue = 0.0; + +    /* Target can fit inside one source pixel +     * +-----+ +     * |     | +     * | +--+|      +-----+-----+      +-----+      +-----+      +-----+ +     * +-+--++  or  |   +-++    |  or  | +-+ |  or  | +--+|  or  |  +--+ +     * | +--+|      |   +-++    |      | +-+ |      | |  ||      |  |  | +     * |     |      +-----+-----+      +-----+      +-+--++      +--+--+ +     * +-----+ +     */ +    if ((r - l <= 1.0 && (gint)r == (gint)l) || (b - t <= 1.0 && (gint)b == (gint)t)) { +        /* Inside */ +        if ((gint)l == (gint)r || (gint)t == (gint)b) { +            guchar *p = get_pixel (input, rowstride, n_channels, (gint)l, (gint)t); +            pixel[0] = p[0]; +            pixel[1] = p[1]; +            pixel[2] = p[2]; +            return; +        } + +        /* Stradling horizontal edge */ +        if (L > R) { +            guchar *p = get_pixel (input, rowstride, n_channels, R, T-1); +            red   += p[0] * (r-l)*(T-t); +            green += p[1] * (r-l)*(T-t); +            blue  += p[2] * (r-l)*(T-t); +            for (y = T; y < B; y++) { +                guchar *p = get_pixel (input, rowstride, n_channels, R, y); +                red   += p[0] * (r-l); +                green += p[1] * (r-l); +                blue  += p[2] * (r-l); +            } +            p = get_pixel (input, rowstride, n_channels, R, B); +            red   += p[0] * (r-l)*(b-B); +            green += p[1] * (r-l)*(b-B); +            blue  += p[2] * (r-l)*(b-B); +        } +        /* Stradling vertical edge */ +        else { +            guchar *p = get_pixel (input, rowstride, n_channels, L - 1, B); +            red   += p[0] * (b-t)*(L-l); +            green += p[1] * (b-t)*(L-l); +            blue  += p[2] * (b-t)*(L-l); +            for (x = L; x < R; x++) { +                guchar *p = get_pixel (input, rowstride, n_channels, x, B); +                red   += p[0] * (b-t); +                green += p[1] * (b-t); +                blue  += p[2] * (b-t); +            } +            p = get_pixel (input, rowstride, n_channels, R, B); +            red   += p[0] * (b-t)*(r-R); +            green += p[1] * (b-t)*(r-R); +            blue  += p[2] * (b-t)*(r-R); +        } + +        scale = 1.0 / ((r - l) * (b - t)); +        pixel[0] = (guchar)(red * scale + 0.5); +        pixel[1] = (guchar)(green * scale + 0.5); +        pixel[2] = (guchar)(blue * scale + 0.5); +        return; +    } + +    /* Add the middle pixels */ +    for (x = L; x < R; x++) { +        for (y = T; y < B; y++) { +            guchar *p = get_pixel (input, rowstride, n_channels, x, y); +            red   += p[0]; +            green += p[1]; +            blue  += p[2]; +        } +    } + +    /* Add the weighted top and bottom pixels */ +    for (x = L; x < R; x++) { +        if (t != T) { +            guchar *p = get_pixel (input, rowstride, n_channels, x, T - 1); +            red   += p[0] * (T - t); +            green += p[1] * (T - t); +            blue  += p[2] * (T - t); +        } + +        if (b != B) {      +            guchar *p = get_pixel (input, rowstride, n_channels, x, B); +            red   += p[0] * (b - B); +            green += p[1] * (b - B); +            blue  += p[2] * (b - B); +        } +    } + +    /* Add the left and right pixels */ +    for (y = T; y < B; y++) { +        if (l != L) { +            guchar *p = get_pixel (input, rowstride, n_channels, L - 1, y); +            red   += p[0] * (L - l); +            green += p[1] * (L - l); +            blue  += p[2] * (L - l); +        } + +        if (r != R) {      +            guchar *p = get_pixel (input, rowstride, n_channels, R, y); +            red   += p[0] * (r - R); +            green += p[1] * (r - R); +            blue  += p[2] * (r - R); +        } +    } +   +    /* Add the corner pixels */ +    if (l != L && t != T) { +        guchar *p = get_pixel (input, rowstride, n_channels, L - 1, T - 1); +        red   += p[0] * (L - l)*(T - t); +        green += p[1] * (L - l)*(T - t); +        blue  += p[2] * (L - l)*(T - t); +    } +    if (r != R && t != T) { +        guchar *p = get_pixel (input, rowstride, n_channels, R, T - 1); +        red   += p[0] * (r - R)*(T - t); +        green += p[1] * (r - R)*(T - t); +        blue  += p[2] * (r - R)*(T - t); +    } +    if (r != R && b != B) { +        guchar *p = get_pixel (input, rowstride, n_channels, R, B); +        red   += p[0] * (r - R)*(b - B); +        green += p[1] * (r - R)*(b - B); +        blue  += p[2] * (r - R)*(b - B); +    } +    if (l != L && b != B) { +        guchar *p = get_pixel (input, rowstride, n_channels, L - 1, B); +        red   += p[0] * (L - l)*(b - B); +        green += p[1] * (L - l)*(b - B); +        blue  += p[2] * (L - l)*(b - B); +    } + +    /* Scale pixel values and clamp in range [0, 255] */ +    scale = 1.0 / ((r - l) * (b - t)); +    pixel[0] = (guchar)(red * scale + 0.5); +    pixel[1] = (guchar)(green * scale + 0.5); +    pixel[2] = (guchar)(blue * scale + 0.5); +} + + +static void +update_preview (GdkPixbuf *image, +                GdkPixbuf **output_image, gint output_width, gint output_height, +                Orientation orientation, gint old_scan_line, gint scan_line) +{ +    guchar *input, *output; +    gint input_width, input_height; +    gint input_rowstride, input_n_channels; +    gint output_rowstride, output_n_channels; +    gint x, y; +    gint L, R, T, B; + +    input = gdk_pixbuf_get_pixels (image); +    input_width = gdk_pixbuf_get_width (image); +    input_height = gdk_pixbuf_get_height (image); +    input_rowstride = gdk_pixbuf_get_rowstride (image); +    input_n_channels = gdk_pixbuf_get_n_channels (image); + +    /* Create new image if one does not exist or has changed size */ +    if (!*output_image || +        gdk_pixbuf_get_width (*output_image) != output_width || +        gdk_pixbuf_get_height (*output_image) != output_height) { +        if (*output_image) +            g_object_unref (*output_image);  +        *output_image = gdk_pixbuf_new (GDK_COLORSPACE_RGB, +                                        FALSE, +                                        8, +                                        output_width, +                                        output_height); +       +        /* Update entire image */ +        L = 0; +        R = output_width - 1; +        T = 0; +        B = output_height - 1; +    } +    /* Otherwise only update changed area */ +    else { +        switch (orientation) { +        case TOP_TO_BOTTOM: +            L = 0; +            R = output_width - 1; +            T = (gint)((double)old_scan_line * output_height / input_height); +            B = (gint)((double)scan_line * output_height / input_height + 0.5); +            break; +        case LEFT_TO_RIGHT: +            L = (gint)((double)old_scan_line * output_width / input_width); +            R = (gint)((double)scan_line * output_width / input_width + 0.5); +            T = 0; +            B = output_height - 1; +            break; +        case BOTTOM_TO_TOP: +            L = 0; +            R = output_width - 1; +            T = (gint)((double)(input_height - scan_line) * output_height / input_height); +            B = (gint)((double)(input_height - old_scan_line) * output_height / input_height + 0.5); +            break; +        case RIGHT_TO_LEFT: +            L = (gint)((double)(input_width - scan_line) * output_width / input_width); +            R = (gint)((double)(input_width - old_scan_line) * output_width / input_width + 0.5); +            T = 0; +            B = output_height - 1; +            break; +        default: +            L = R = B = T = 0; +            break; +        } +    } + +    /* FIXME: There's an off by one error in there somewhere... */ +    if (R >= output_width) +        R = output_width - 1; +    if (B >= output_height) +        B = output_height - 1; + +    g_return_if_fail (L >= 0); +    g_return_if_fail (R < output_width); +    g_return_if_fail (T >= 0); +    g_return_if_fail (B < output_height); +    g_return_if_fail (*output_image != NULL); + +    output = gdk_pixbuf_get_pixels (*output_image); +    output_rowstride = gdk_pixbuf_get_rowstride (*output_image); +    output_n_channels = gdk_pixbuf_get_n_channels (*output_image); + +    /* Update changed area */ +    for (x = L; x <= R; x++) { +        double l, r; +       +        l = (double)x * input_width / output_width; +        r = (double)(x + 1) * input_width / output_width; + +        for (y = T; y <= B; y++) { +            double t, b; + +            t = (double)y * input_height / output_height; +            b = (double)(y + 1) * input_height / output_height; + +            set_pixel (input, input_rowstride, input_n_channels, +                       l, r, t, b, +                       get_pixel (output, output_rowstride, output_n_channels, x, y)); +        } +    } +} + + +static gint +get_preview_width (PageView *view) +{ +    return view->priv->width - view->priv->border_width * 2; +} + + +static gint +get_preview_height (PageView *view) +{ +    return view->priv->height - view->priv->border_width * 2; +} + + +static void +update_page_view (PageView *view) +{ +    GdkPixbuf *image; +    gint old_scan_line, scan_line; + +    if (!view->priv->update_image) +        return; + +    image = page_get_image (view->priv->page); +    old_scan_line = view->priv->scan_line; +    scan_line = page_get_scan_line (view->priv->page); + +    update_preview (image, +                    &view->priv->image, +                    get_preview_width (view), +                    get_preview_height (view), +                    page_get_orientation (view->priv->page), old_scan_line, scan_line); +    g_object_unref (image); + +    view->priv->update_image = FALSE; +    view->priv->scan_line = scan_line; +} + + +static gint +page_to_screen_x (PageView *view, gint x) +{ +    return (double) x * get_preview_width (view) / page_get_width (view->priv->page) + 0.5; +} + + +static gint +page_to_screen_y (PageView *view, gint y) +{ +    return (double) y * get_preview_height (view) / page_get_height (view->priv->page) + 0.5;     +} + + +static gint +screen_to_page_x (PageView *view, gint x) +{ +    return (double) x * page_get_width (view->priv->page) / get_preview_width (view) + 0.5; +} + + +static gint +screen_to_page_y (PageView *view, gint y) +{ +    return (double) y * page_get_height (view->priv->page) / get_preview_height (view) + 0.5; +} + + +static CropLocation +get_crop_location (PageView *view, gint x, gint y) +{ +    gint cx, cy, cw, ch; +    gint dx, dy, dw, dh; +    gint ix, iy; +    gint crop_border = 20; +    gchar *name; + +    if (!page_has_crop (view->priv->page)) +        return 0; + +    page_get_crop (view->priv->page, &cx, &cy, &cw, &ch); +    dx = page_to_screen_x (view, cx); +    dy = page_to_screen_y (view, cy); +    dw = page_to_screen_x (view, cw); +    dh = page_to_screen_y (view, ch); +    ix = x - dx; +    iy = y - dy; + +    if (ix < 0 || ix > dw || iy < 0 || iy > dh) +        return CROP_NONE; + +    /* Can't resize named crops */ +    name = page_get_named_crop (view->priv->page); +    if (name != NULL) { +        g_free (name); +        return CROP_MIDDLE; +    } + +    /* Adjust borders so can select */ +    if (dw < crop_border * 3) +        crop_border = dw / 3; +    if (dh < crop_border * 3) +        crop_border = dh / 3; + +    /* Top left */ +    if (ix < crop_border && iy < crop_border) +        return CROP_TOP_LEFT; +    /* Top right */ +    if (ix > dw - crop_border && iy < crop_border) +        return CROP_TOP_RIGHT; +    /* Bottom left */ +    if (ix < crop_border && iy > dh - crop_border) +        return CROP_BOTTOM_LEFT; +    /* Bottom right */ +    if (ix > dw - crop_border && iy > dh - crop_border) +        return CROP_BOTTOM_RIGHT; + +    /* Left */ +    if (ix < crop_border) +        return CROP_LEFT; +    /* Right */ +    if (ix > dw - crop_border) +        return CROP_RIGHT; +    /* Top */ +    if (iy < crop_border) +        return CROP_TOP; +    /* Bottom */ +    if (iy > dh - crop_border) +        return CROP_BOTTOM; + +    /* In the middle */ +    return CROP_MIDDLE; +} + + +void +page_view_button_press (PageView *view, gint x, gint y) +{ +    CropLocation location; + +    g_return_if_fail (view != NULL); + +    /* See if selecting crop */ +    location = get_crop_location (view, x, y);; +    if (location != CROP_NONE) { +        view->priv->crop_location = location; +        view->priv->selected_crop_px = x; +        view->priv->selected_crop_py = y; +        page_get_crop (view->priv->page, +                       &view->priv->selected_crop_x, +                       &view->priv->selected_crop_y, +                       &view->priv->selected_crop_w, +                       &view->priv->selected_crop_h); +    } +} + + +void +page_view_motion (PageView *view, gint x, gint y) +{ +    gint pw, ph; +    gint cx, cy, cw, ch, dx, dy; +    gint new_x, new_y, new_w, new_h; +    CropLocation location; +    gint cursor; +    gint min_size; +   +    min_size = screen_to_page_x (view, 15); + +    g_return_if_fail (view != NULL); +   +    location = get_crop_location (view, x, y); +    switch (location) { +    case CROP_MIDDLE: +        cursor = GDK_HAND1; +        break; +    case CROP_TOP: +        cursor = GDK_TOP_SIDE; +        break; +    case CROP_BOTTOM: +        cursor = GDK_BOTTOM_SIDE; +        break; +    case CROP_LEFT: +        cursor = GDK_LEFT_SIDE; +        break; +    case CROP_RIGHT: +        cursor = GDK_RIGHT_SIDE; +        break; +    case CROP_TOP_LEFT: +        cursor = GDK_TOP_LEFT_CORNER; +        break; +    case CROP_TOP_RIGHT: +        cursor = GDK_TOP_RIGHT_CORNER; +        break; +    case CROP_BOTTOM_LEFT: +        cursor = GDK_BOTTOM_LEFT_CORNER; +        break; +    case CROP_BOTTOM_RIGHT: +        cursor = GDK_BOTTOM_RIGHT_CORNER; +        break; +    default: +        cursor = GDK_ARROW; +        break; +    } + +    if (view->priv->crop_location == CROP_NONE) { +        view->priv->cursor = cursor; +        return; +    } + +    /* Move the crop */   +    pw = page_get_width (view->priv->page); +    ph = page_get_height (view->priv->page); +    page_get_crop (view->priv->page, &cx, &cy, &cw, &ch); + +    dx = screen_to_page_x (view, x - view->priv->selected_crop_px); +    dy = screen_to_page_y (view, y - view->priv->selected_crop_py); + +    new_x = view->priv->selected_crop_x; +    new_y = view->priv->selected_crop_y; +    new_w = view->priv->selected_crop_w; +    new_h = view->priv->selected_crop_h; + +    /* Limit motion to remain within page and minimum crop size */ +    if (view->priv->crop_location == CROP_TOP_LEFT || +        view->priv->crop_location == CROP_LEFT || +        view->priv->crop_location == CROP_BOTTOM_LEFT) { +        if (dx > new_w - min_size) +            dx = new_w - min_size; +        if (new_x + dx < 0) +            dx = -new_x; +    } +    if (view->priv->crop_location == CROP_TOP_LEFT || +        view->priv->crop_location == CROP_TOP || +        view->priv->crop_location == CROP_TOP_RIGHT) { +        if (dy > new_h - min_size) +            dy = new_h - min_size; +        if (new_y + dy < 0) +            dy = -new_y; +    } +      +    if (view->priv->crop_location == CROP_TOP_RIGHT || +        view->priv->crop_location == CROP_RIGHT || +        view->priv->crop_location == CROP_BOTTOM_RIGHT) { +        if (dx < min_size - new_w) +            dx = min_size - new_w; +        if (new_x + new_w + dx > pw) +            dx = pw - new_x - new_w; +    } +    if (view->priv->crop_location == CROP_BOTTOM_LEFT || +        view->priv->crop_location == CROP_BOTTOM || +        view->priv->crop_location == CROP_BOTTOM_RIGHT) { +        if (dy < min_size - new_h) +            dy = min_size - new_h; +        if (new_y + new_h + dy > ph) +            dy = ph - new_y - new_h; +    } +    if (view->priv->crop_location == CROP_MIDDLE) { +        if (new_x + dx + new_w > pw) +            dx = pw - new_x - new_w; +        if (new_x + dx < 0) +            dx = -new_x; +        if (new_y + dy + new_h > ph) +            dy = ph - new_y - new_h; +        if (new_y + dy  < 0) +            dy = -new_y; +    } + +    /* Move crop */ +    if (view->priv->crop_location == CROP_MIDDLE) { +        new_x += dx; +        new_y += dy;           +    } +    if (view->priv->crop_location == CROP_TOP_LEFT || +        view->priv->crop_location == CROP_LEFT || +        view->priv->crop_location == CROP_BOTTOM_LEFT)  +    { +        new_x += dx; +        new_w -= dx; +    } +    if (view->priv->crop_location == CROP_TOP_LEFT || +        view->priv->crop_location == CROP_TOP || +        view->priv->crop_location == CROP_TOP_RIGHT) { +        new_y += dy; +        new_h -= dy; +    } +  +    if (view->priv->crop_location == CROP_TOP_RIGHT || +        view->priv->crop_location == CROP_RIGHT || +        view->priv->crop_location == CROP_BOTTOM_RIGHT) { +        new_w += dx; +    } +    if (view->priv->crop_location == CROP_BOTTOM_LEFT || +        view->priv->crop_location == CROP_BOTTOM || +        view->priv->crop_location == CROP_BOTTOM_RIGHT) { +        new_h += dy; +    } + +    page_move_crop (view->priv->page, new_x, new_y); + +    /* If reshaped crop, must be a custom crop */ +    if (new_w != cw || new_h != ch) +        page_set_custom_crop (view->priv->page, new_w, new_h); +} + + +void +page_view_button_release (PageView *view, gint x, gint y) +{ +    g_return_if_fail (view != NULL); + +    /* Complete crop */ +    view->priv->crop_location = CROP_NONE; +    g_signal_emit (view, signals[CHANGED], 0); +} + + +gint +page_view_get_cursor (PageView *view) +{ +    g_return_val_if_fail (view != NULL, 0); +    return view->priv->cursor; +} + + +static gboolean +animation_cb (PageView *view) +{ +    view->priv->animate_segment = (view->priv->animate_segment + 1) % view->priv->animate_n_segments; +    g_signal_emit (view, signals[CHANGED], 0); +    return TRUE; +} + + +static void +update_animation (PageView *view) +{ +    gboolean animate, is_animating; + +    animate = page_is_scanning (view->priv->page) && !page_has_data (view->priv->page); +    is_animating = view->priv->animate_timeout != 0; +    if (animate == is_animating) +        return; +   +    if (animate) { +        view->priv->animate_segment = 0; +        if (view->priv->animate_timeout == 0) +            view->priv->animate_timeout = g_timeout_add (150, (GSourceFunc) animation_cb, view); +    } +    else +    { +        if (view->priv->animate_timeout != 0) +            g_source_remove (view->priv->animate_timeout); +        view->priv->animate_timeout = 0; +    } +} + + +void +page_view_render (PageView *view, cairo_t *context) +{ +    gint width, height; + +    g_return_if_fail (view != NULL); +    g_return_if_fail (context != NULL); + +    update_animation (view); +    update_page_view (view); + +    width = get_preview_width (view); +    height = get_preview_height (view); + +    cairo_set_line_width (context, 1); +    cairo_translate (context, view->priv->x, view->priv->y); + +    /* Draw page border */ +    cairo_set_source_rgb (context, 0, 0, 0); +    cairo_set_line_width (context, view->priv->border_width); +    cairo_rectangle (context, +                     (double)view->priv->border_width / 2, +                     (double)view->priv->border_width / 2, +                     view->priv->width - view->priv->border_width, +                     view->priv->height - view->priv->border_width); +    cairo_stroke (context); + +    /* Draw image */ +    cairo_translate (context, view->priv->border_width, view->priv->border_width); +    if (view->priv->image) { +        gdk_cairo_set_source_pixbuf (context, view->priv->image, 0, 0); +        cairo_paint (context); +    } +    else { +        cairo_scale (context, +                     (double) get_preview_width (view) / page_get_width (view->priv->page), +                     (double) get_preview_height (view) / page_get_height (view->priv->page)); +        gdk_cairo_set_source_pixbuf (context, page_get_image (view->priv->page), 0, 0); + +        cairo_paint (context); +    } + +    /* Draw throbber */ +    if (page_is_scanning (view->priv->page) && !page_has_data (view->priv->page)) { +        gdouble inner_radius, outer_radius, x, y, arc, offset = 0.0; +        gint i; + +        if (width > height) +            outer_radius = 0.15 * width; +        else +            outer_radius = 0.15 * height; +        arc = M_PI / view->priv->animate_n_segments; + +        /* Space circles */ +        x = outer_radius * sin (arc); +        y = outer_radius * (cos (arc) - 1.0); +        inner_radius = 0.6 * sqrt (x*x + y*y); + +        for (i = 0; i < view->priv->animate_n_segments; i++, offset += arc * 2) { +            x = width / 2 + outer_radius * sin (offset); +            y = height / 2 - outer_radius * cos (offset); +            cairo_arc (context, x, y, inner_radius, 0, 2 * M_PI); + +            if (i == view->priv->animate_segment) { +                cairo_set_source_rgb (context, 0.75, 0.75, 0.75); +                cairo_fill_preserve (context); +            } + +            cairo_set_source_rgb (context, 0.5, 0.5, 0.5); +            cairo_stroke (context); +        } +    } + +    /* Draw scan line */ +    if (page_is_scanning (view->priv->page) && page_get_scan_line (view->priv->page) > 0) { +        gint scan_line; +        double s; +        double x1, y1, x2, y2; + +        scan_line = page_get_scan_line (view->priv->page); +         +        switch (page_get_orientation (view->priv->page)) { +        case TOP_TO_BOTTOM: +            s = page_to_screen_y (view, scan_line); +            x1 = 0; y1 = s + 0.5; +            x2 = width; y2 = s + 0.5; +            break; +        case BOTTOM_TO_TOP: +            s = page_to_screen_y (view, scan_line); +            x1 = 0; y1 = height - s + 0.5; +            x2 = width; y2 = height - s + 0.5; +            break; +        case LEFT_TO_RIGHT: +            s = page_to_screen_x (view, scan_line); +            x1 = s + 0.5; y1 = 0; +            x2 = s + 0.5; y2 = height; +            break; +        case RIGHT_TO_LEFT: +            s = page_to_screen_x (view, scan_line); +            x1 = width - s + 0.5; y1 = 0; +            x2 = width - s + 0.5; y2 = height; +            break; +        default: +            x1 = y1 = x2 = y2 = 0; +            break; +        } + +        cairo_move_to (context, x1, y1); +        cairo_line_to (context, x2, y2); +        cairo_set_source_rgb (context, 1.0, 0.0, 0.0); +        cairo_stroke (context); +    } +     +    /* Draw crop */ +    if (page_has_crop (view->priv->page)) { +        gint x, y, crop_width, crop_height; +        gdouble dx, dy, dw, dh; + +        page_get_crop (view->priv->page, &x, &y, &crop_width, &crop_height); + +        dx = page_to_screen_x (view, x); +        dy = page_to_screen_y (view, y); +        dw = page_to_screen_x (view, crop_width); +        dh = page_to_screen_y (view, crop_height); +         +        /* Shade out cropped area */ +        cairo_rectangle (context, +                         0, 0, +                         width, height); +        cairo_new_sub_path (context); +        cairo_rectangle (context, dx, dy, dw, dh); +        cairo_set_fill_rule (context, CAIRO_FILL_RULE_EVEN_ODD); +        cairo_set_source_rgba (context, 0.25, 0.25, 0.25, 0.2); +        cairo_fill (context); +         +        /* Show new edge */ +        cairo_rectangle (context, dx - 1.5, dy - 1.5, dw + 3, dh + 3); +        cairo_set_source_rgb (context, 1.0, 1.0, 1.0); +        cairo_stroke (context); +        cairo_rectangle (context, dx - 0.5, dy - 0.5, dw + 1, dh + 1); +        cairo_set_source_rgb (context, 0.0, 0.0, 0.0); +        cairo_stroke (context); +    } +} + + +void +page_view_set_width (PageView *view, gint width) +{ +    gint height; + +    g_return_if_fail (view != NULL); + +    // FIXME: Automatically update when get updated image +    height = (double)width * page_get_height (view->priv->page) / page_get_width (view->priv->page); +    if (view->priv->width == width && view->priv->height == height) +        return; + +    view->priv->width = width; +    view->priv->height = height; +   +    /* Regenerate image */ +    view->priv->update_image = TRUE; + +    g_signal_emit (view, signals[SIZE_CHANGED], 0); +    g_signal_emit (view, signals[CHANGED], 0); +} + + +void +page_view_set_height (PageView *view, gint height) +{ +    gint width; + +    g_return_if_fail (view != NULL); + +    // FIXME: Automatically update when get updated image +    width = (double)height * page_get_width (view->priv->page) / page_get_height (view->priv->page); +    if (view->priv->width == width && view->priv->height == height) +        return; + +    view->priv->width = width; +    view->priv->height = height; +   +    /* Regenerate image */ +    view->priv->update_image = TRUE; + +    g_signal_emit (view, signals[SIZE_CHANGED], 0);   +    g_signal_emit (view, signals[CHANGED], 0); +} + + +gint +page_view_get_width (PageView *view) +{ +    g_return_val_if_fail (view != NULL, 0); +    return view->priv->width; +} + + +gint +page_view_get_height (PageView *view) +{ +    g_return_val_if_fail (view != NULL, 0); +    return view->priv->height; +} + + +static void +page_image_changed_cb (Page *p, PageView *view) +{ +    /* Regenerate image */ +    view->priv->update_image = TRUE; +    g_signal_emit (view, signals[CHANGED], 0); +} + + +static void +page_size_changed_cb (Page *p, PageView *view) +{ +    /* Regenerate image */ +    view->priv->update_image = TRUE; +    g_signal_emit (view, signals[SIZE_CHANGED], 0); +    g_signal_emit (view, signals[CHANGED], 0); +} + + +static void +page_overlay_changed_cb (Page *p, PageView *view) +{ +    g_signal_emit (view, signals[CHANGED], 0); +} + + +static void +page_view_set_page (PageView *view, Page *page) +{ +    g_return_if_fail (view != NULL); +    g_return_if_fail (view->priv->page == NULL); + +    view->priv->page = g_object_ref (page); +    g_signal_connect (view->priv->page, "image-changed", G_CALLBACK (page_image_changed_cb), view); +    g_signal_connect (view->priv->page, "size-changed", G_CALLBACK (page_size_changed_cb), view); +    g_signal_connect (view->priv->page, "crop-changed", G_CALLBACK (page_overlay_changed_cb), view); +    g_signal_connect (view->priv->page, "scan-line-changed", G_CALLBACK (page_overlay_changed_cb), view); +} + + +static void +page_view_set_property (GObject      *object, +                        guint         prop_id, +                        const GValue *value, +                        GParamSpec   *pspec) +{ +    PageView *self; + +    self = PAGE_VIEW (object); + +    switch (prop_id) { +    case PROP_PAGE: +        page_view_set_page (self, g_value_get_object (value)); +        break; +    default: +        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); +        break; +    } +} + + +static void +page_view_get_property (GObject    *object, +                        guint       prop_id, +                        GValue     *value, +                        GParamSpec *pspec) +{ +    PageView *self; + +    self = PAGE_VIEW (object); + +    switch (prop_id) { +    case PROP_PAGE: +        g_value_set_object (value, self->priv->page); +        break; +    default: +        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); +        break; +    } +} + + +static void +page_view_finalize (GObject *object) +{ +    PageView *view = PAGE_VIEW (object); +    g_object_unref (view->priv->page); +    view->priv->page = NULL; +    if (view->priv->image) +        g_object_unref (view->priv->image); +    view->priv->image = NULL; +    if (view->priv->animate_timeout != 0) +        g_source_remove (view->priv->animate_timeout); +    view->priv->animate_timeout = 0; +    G_OBJECT_CLASS (page_view_parent_class)->finalize (object); +} + + +static void +page_view_class_init (PageViewClass *klass) +{ +    GObjectClass *object_class = G_OBJECT_CLASS (klass); + +    object_class->get_property = page_view_get_property; +    object_class->set_property = page_view_set_property; +    object_class->finalize = page_view_finalize; + +    signals[CHANGED] = +        g_signal_new ("changed", +                      G_TYPE_FROM_CLASS (klass), +                      G_SIGNAL_RUN_LAST, +                      G_STRUCT_OFFSET (PageViewClass, changed), +                      NULL, NULL, +                      g_cclosure_marshal_VOID__VOID, +                      G_TYPE_NONE, 0); +    signals[SIZE_CHANGED] = +        g_signal_new ("size-changed", +                      G_TYPE_FROM_CLASS (klass), +                      G_SIGNAL_RUN_LAST, +                      G_STRUCT_OFFSET (PageViewClass, size_changed), +                      NULL, NULL, +                      g_cclosure_marshal_VOID__VOID, +                      G_TYPE_NONE, 0); + +    g_type_class_add_private (klass, sizeof (PageViewPrivate)); + +    g_object_class_install_property (object_class, +                                     PROP_PAGE, +                                     g_param_spec_object ("page", +                                                          "page", +                                                          "Page being rendered", +                                                          PAGE_TYPE, +                                                          G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); +} + + +static void +page_view_init (PageView *view) +{ +    view->priv = G_TYPE_INSTANCE_GET_PRIVATE (view, PAGE_VIEW_TYPE, PageViewPrivate); +    view->priv->update_image = TRUE; +    view->priv->cursor = GDK_ARROW; +    view->priv->border_width = 1; +    view->priv->animate_n_segments = 7; +} diff --git a/src/page-view.h b/src/page-view.h new file mode 100644 index 0000000..dd64197 --- /dev/null +++ b/src/page-view.h @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2009 Canonical Ltd. + * Author: Robert Ancell <robert.ancell@canonical.com> + *  + * 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 3 of the License, or (at your option) any later + * version. See http://www.gnu.org/copyleft/gpl.html the full text of the + * license. + */ + +#ifndef _PAGE_VIEW_H_ +#define _PAGE_VIEW_H_ + +#include <glib-object.h> +#include <gtk/gtk.h> +#include <cairo.h> +#include "page.h" + +G_BEGIN_DECLS + +#define PAGE_VIEW_TYPE  (page_view_get_type ()) +#define PAGE_VIEW(obj)  (G_TYPE_CHECK_INSTANCE_CAST ((obj), PAGE_VIEW_TYPE, PageView)) + + +typedef struct PageViewPrivate PageViewPrivate; + +typedef struct +{ +    GObject          parent_instance; +    PageViewPrivate *priv; +} PageView; + +typedef struct +{ +    GObjectClass parent_class; + +    void (*changed) (PageView *view); +    void (*size_changed) (PageView *view); +} PageViewClass; + + +GType page_view_get_type (void); + +PageView *page_view_new (Page *page); + +Page *page_view_get_page (PageView *view); + +void page_view_set_selected (PageView *view, gboolean selected); + +gboolean page_view_get_selected (PageView *view); + +void page_view_set_x_offset (PageView *view, gint offset); + +void page_view_set_y_offset (PageView *view, gint offset); + +gint page_view_get_x_offset (PageView *view); + +gint page_view_get_y_offset (PageView *view); + +void page_view_set_width (PageView *view, gint width); + +void page_view_set_height (PageView *view, gint height); + +gint page_view_get_width (PageView *view); + +gint page_view_get_height (PageView *view); + +void page_view_button_press (PageView *view, gint x, gint y); + +void page_view_motion (PageView *view, gint x, gint y); + +void page_view_button_release (PageView *view, gint x, gint y); + +gint page_view_get_cursor (PageView *view); + +void page_view_render (PageView *view, cairo_t *context); + +#endif /* _PAGE_VIEW_H_ */ diff --git a/src/page.c b/src/page.c new file mode 100644 index 0000000..5888a46 --- /dev/null +++ b/src/page.c @@ -0,0 +1,951 @@ +/* + * Copyright (C) 2009 Canonical Ltd. + * Author: Robert Ancell <robert.ancell@canonical.com> + *  + * 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 3 of the License, or (at your option) any later + * version. See http://www.gnu.org/copyleft/gpl.html the full text of the + * license. + */ + +#include <string.h> +#include "page.h" + + +enum { +    IMAGE_CHANGED, +    SIZE_CHANGED, +    SCAN_LINE_CHANGED, +    ORIENTATION_CHANGED, +    CROP_CHANGED, +    LAST_SIGNAL +}; +static guint signals[LAST_SIGNAL] = { 0, }; + +struct PagePrivate +{ +    /* Resolution of page */ +    gint dpi; + +    /* Number of rows in this page or -1 if currently unknown */ +    gint rows; + +    /* Color profile */ +    gchar *color_profile; + +    /* Scanned image data */ +    GdkPixbuf *image; + +    /* Page is getting data */ +    gboolean scanning; + +    /* TRUE if have some page data */ +    gboolean has_data; + +    /* Expected next scan row */ +    gint scan_line; + +    /* Rotation of scanned data */ +    Orientation orientation; + +    /* Crop */ +    gboolean has_crop; +    gchar *crop_name; +    gint crop_x, crop_y, crop_width, crop_height; +}; + +G_DEFINE_TYPE (Page, page, G_TYPE_OBJECT); + + +Page * +page_new () +{ +    return g_object_new (PAGE_TYPE, NULL); +} + + +void +page_setup (Page *page, gint width, gint height, gint dpi, Orientation orientation) +{  +    page->priv->orientation = orientation; +    page->priv->dpi = dpi; +    if (orientation == LEFT_TO_RIGHT || orientation == RIGHT_TO_LEFT) +        page->priv->rows = width; +    else +        page->priv->rows = height; +    page->priv->image = gdk_pixbuf_new (GDK_COLORSPACE_RGB, FALSE, +                                        8, +                                        width, +                                        height); +    g_return_if_fail (page->priv->image != NULL); +    gdk_pixbuf_fill (page->priv->image, 0xFFFFFFFF); +} + + +void +page_set_scan_area (Page *page, gint width, gint rows, gint dpi) +{ +    gint height; + +    g_return_if_fail (page != NULL); + +    /* Variable height, try 50% of the width for now */ +    if (rows < 0) +        height = width / 2; +    else +        height = rows; + +    /* Rotate page */ +    if (page->priv->orientation == LEFT_TO_RIGHT || page->priv->orientation == RIGHT_TO_LEFT) { +        gint t; +        t = width; +        width = height; +        height = t; +    } + +    page->priv->rows = rows; +    page->priv->dpi = dpi; + +    /* Create a white page */ +    /* NOTE: Pixbuf only supports 8 bit RGB images */ +    if (page->priv->image) +        g_object_unref (page->priv->image); +    page->priv->image = gdk_pixbuf_new (GDK_COLORSPACE_RGB, FALSE, +                                        8, +                                        width, +                                        height); +    g_return_if_fail (page->priv->image != NULL); + +    gdk_pixbuf_fill (page->priv->image, 0xFFFFFFFF); +    g_signal_emit (page, signals[SIZE_CHANGED], 0); +    g_signal_emit (page, signals[IMAGE_CHANGED], 0); +} + + +void +page_start (Page *page) +{ +    g_return_if_fail (page != NULL); + +    page->priv->scanning = TRUE; +    g_signal_emit (page, signals[SCAN_LINE_CHANGED], 0); +} + + +gboolean page_is_scanning (Page *page) +{ +    g_return_val_if_fail (page != NULL, FALSE); +   +    return page->priv->scanning; +} + + +static gint +get_sample (guchar *data, gint depth, gint index) +{ +    gint i, offset, value, n_bits; + +    /* Optimise if using 8 bit samples */ +    if (depth == 8) +        return data[index]; + +    /* Bit offset for this sample */ +    offset = depth * index; + +    /* Get the remaining bits in the octet this sample starts in */ +    i = offset / 8; +    n_bits = 8 - offset % 8; +    value = data[i] & (0xFF >> (8 - n_bits)); +     +    /* Add additional octets until get enough bits */ +    while (n_bits < depth) { +        value = value << 8 | data[i++]; +        n_bits += 8; +    } + +    /* Trim remaining bits off */ +    if (n_bits > depth) +        value >>= n_bits - depth; + +    return value; +} + + +gboolean page_has_data (Page *page) +{ +    g_return_val_if_fail (page != NULL, FALSE); +    return page->priv->has_data; +} + + +gint page_get_scan_line (Page *page) +{ +    g_return_val_if_fail (page != NULL, -1); +    return page->priv->scan_line; +} + + +static void +set_pixel (ScanLine *line, gint n, gint x, guchar *pixel) +{ +    gint sample; +    guchar *data; +   +    data = line->data + line->data_length * n; + +    switch (line->format) { +    case LINE_RGB: +        pixel[0] = get_sample (data, line->depth, x*3) * 0xFF / ((1 << line->depth) - 1); +        pixel[1] = get_sample (data, line->depth, x*3+1) * 0xFF / ((1 << line->depth) - 1); +        pixel[2] = get_sample (data, line->depth, x*3+2) * 0xFF / ((1 << line->depth) - 1); +        break; +    case LINE_GRAY: +        /* Bitmap, 0 = white, 1 = black */ +        sample = get_sample (data, line->depth, x) * 0xFF / ((1 << line->depth) - 1); +        if (line->depth == 1) +            sample = sample ? 0x00 : 0xFF; + +        pixel[0] = pixel[1] = pixel[2] = sample; +        break; +    case LINE_RED: +        pixel[0] = get_sample (data, line->depth, x) * 0xFF / ((1 << line->depth) - 1); +        break; +    case LINE_GREEN: +        pixel[1] = get_sample (data, line->depth, x) * 0xFF / ((1 << line->depth) - 1); +        break; +    case LINE_BLUE: +        pixel[2] = get_sample (data, line->depth, x) * 0xFF / ((1 << line->depth) - 1); +        break; +    } +} + + +static void +parse_line (Page *page, ScanLine *line, gint n, gboolean *size_changed) +{ +    guchar *pixels; +    gint line_number; +    gint i, x = 0, y = 0, x_step = 0, y_step = 0; +    gint rowstride, n_channels; + +    line_number = line->number + n; + +    /* Extend image if necessary */ +    while (line_number >= page_get_scan_height (page)) { +        GdkPixbuf *image; +        gint height, width, new_width, new_height; + +        /* Extend image */ +        new_width = width = gdk_pixbuf_get_width (page->priv->image); +        new_height = height = gdk_pixbuf_get_height (page->priv->image); +        if (page->priv->orientation == TOP_TO_BOTTOM || page->priv->orientation == BOTTOM_TO_TOP) { +            new_height = height + width / 2; +            g_debug("Extending image height from %d pixels to %d pixels", height, new_height); +        } +        else { +            new_width = width + height / 2; +            g_debug("Extending image width from %d pixels to %d pixels", width, new_width); +        } +        image = gdk_pixbuf_new (GDK_COLORSPACE_RGB, FALSE, +                                8, new_width, new_height); + +        /* Copy old data */ +        gdk_pixbuf_fill (page->priv->image, 0xFFFFFFFF); +        if (page->priv->orientation == TOP_TO_BOTTOM || page->priv->orientation == LEFT_TO_RIGHT) +            gdk_pixbuf_copy_area (page->priv->image, 0, 0, width, height, +                                  image, 0, 0); +        else +            gdk_pixbuf_copy_area (page->priv->image, 0, 0, width, height, +                                  image, new_width - width, new_height - height); + +        g_object_unref (page->priv->image); +        page->priv->image = image; + +        *size_changed = TRUE; +    } +   +    switch (page->priv->orientation) { +    case TOP_TO_BOTTOM: +        x = 0; +        y = line_number; +        x_step = 1; +        y_step = 0; +        break; +    case BOTTOM_TO_TOP: +        x = page_get_width (page) - 1; +        y = page_get_height (page) - line_number - 1; +        x_step = -1; +        y_step = 0; +        break; +    case LEFT_TO_RIGHT: +        x = line_number; +        y = page_get_height (page) - 1; +        x_step = 0; +        y_step = -1; +        break; +    case RIGHT_TO_LEFT: +        x = page_get_width (page) - line_number - 1; +        y = 0; +        x_step = 0; +        y_step = 1; +        break; +    } +    pixels = gdk_pixbuf_get_pixels (page->priv->image); +    rowstride = gdk_pixbuf_get_rowstride (page->priv->image); +    n_channels = gdk_pixbuf_get_n_channels (page->priv->image); +    for (i = 0; i < line->width; i++) { +        guchar *pixel; + +        pixel = pixels + y * rowstride + x * n_channels; +        set_pixel (line, n, i, pixel); +        x += x_step; +        y += y_step; +    } + +    page->priv->scan_line = line_number; +} + + +void +page_parse_scan_line (Page *page, ScanLine *line) +{ +    gint i; +    gboolean size_changed = FALSE; + +    g_return_if_fail (page != NULL); + +    for (i = 0; i < line->n_lines; i++) +        parse_line (page, line, i, &size_changed); + +    page->priv->has_data = TRUE; + +    if (size_changed) +        g_signal_emit (page, signals[SIZE_CHANGED], 0); +    g_signal_emit (page, signals[SCAN_LINE_CHANGED], 0); +    g_signal_emit (page, signals[IMAGE_CHANGED], 0); +} + + +void +page_finish (Page *page) +{ +    gboolean size_changed = FALSE; + +    g_return_if_fail (page != NULL); + +    /* Trim page */ +    if (page->priv->rows < 0 && +        page->priv->scan_line != gdk_pixbuf_get_height (page->priv->image)) { +        GdkPixbuf *image; +        gint width, height, new_width, new_height; + +        new_width = width = gdk_pixbuf_get_width (page->priv->image); +        new_height = height = gdk_pixbuf_get_height (page->priv->image); +        if (page->priv->orientation == TOP_TO_BOTTOM || page->priv->orientation == BOTTOM_TO_TOP) { +            new_height = page->priv->scan_line; +            g_debug("Trimming image height from %d pixels to %d pixels", height, new_height); +        } +        else { +            new_width = page->priv->scan_line; +            g_debug("Trimming image width from %d pixels to %d pixels", width, new_width);           +        } +        image = gdk_pixbuf_new (GDK_COLORSPACE_RGB, FALSE, +                                8, +                                new_width, new_height); + +        /* Copy old data */ +        if (page->priv->orientation == TOP_TO_BOTTOM || page->priv->orientation == LEFT_TO_RIGHT) +            gdk_pixbuf_copy_area (page->priv->image, 0, 0, width, height, +                                  image, 0, 0); +        else +            gdk_pixbuf_copy_area (page->priv->image, width - new_width, height - new_height, width, height, +                                  image, 0, 0); + +        g_object_unref (page->priv->image); +        page->priv->image = image; +        size_changed = TRUE; +    } +    page->priv->scanning = FALSE; + +    if (size_changed) +        g_signal_emit (page, signals[SIZE_CHANGED], 0); +    g_signal_emit (page, signals[SCAN_LINE_CHANGED], 0); +} + + +Orientation +page_get_orientation (Page *page) +{ +    g_return_val_if_fail (page != NULL, TOP_TO_BOTTOM); + +    return page->priv->orientation; +} + + +void +page_set_orientation (Page *page, Orientation orientation) +{ +    gint left_steps, t; +    GdkPixbuf *image; +    gboolean size_changed = FALSE; +    gint width, height; + +    g_return_if_fail (page != NULL); + +    if (page->priv->orientation == orientation) +        return; + +    /* Work out how many times it has been rotated to the left */ +    left_steps = orientation - page->priv->orientation; +    if (left_steps < 0) +        left_steps += 4; +   +    width = page_get_width (page); +    height = page_get_height (page); +   +    /* Rotate image */ +    if (left_steps == 1) +        image = gdk_pixbuf_rotate_simple (page->priv->image, GDK_PIXBUF_ROTATE_COUNTERCLOCKWISE); +    else if (left_steps == 2) +        image = gdk_pixbuf_rotate_simple (page->priv->image, GDK_PIXBUF_ROTATE_UPSIDEDOWN); +    else +        image = gdk_pixbuf_rotate_simple (page->priv->image, GDK_PIXBUF_ROTATE_CLOCKWISE); +    g_object_unref (page->priv->image); +    page->priv->image = image; +    if (left_steps != 2) +        size_changed = TRUE; + +    /* Rotate crop */ +    if (page->priv->has_crop) { +        switch (left_steps) { +        /* 90 degrees counter-clockwise */ +        case 1: +            t = page->priv->crop_x; +            page->priv->crop_x = page->priv->crop_y; +            page->priv->crop_y = width - (t + page->priv->crop_width); +            t = page->priv->crop_width; +            page->priv->crop_width = page->priv->crop_height; +            page->priv->crop_height = t; +            break; +        /* 180 degrees */ +        case 2: +            page->priv->crop_x = width - (page->priv->crop_x + page->priv->crop_width); +            page->priv->crop_y = width - (page->priv->crop_y + page->priv->crop_height); +            break; +        /* 90 degrees clockwise */ +        case 3: +            t = page->priv->crop_y; +            page->priv->crop_y = page->priv->crop_x; +            page->priv->crop_x = height - (t + page->priv->crop_height); +            t = page->priv->crop_width; +            page->priv->crop_width = page->priv->crop_height; +            page->priv->crop_height = t; +            break; +        } +    } + +    page->priv->orientation = orientation; +    if (size_changed) +        g_signal_emit (page, signals[SIZE_CHANGED], 0); +    g_signal_emit (page, signals[IMAGE_CHANGED], 0); +    g_signal_emit (page, signals[ORIENTATION_CHANGED], 0); +    g_signal_emit (page, signals[CROP_CHANGED], 0); +} + + +void +page_rotate_left (Page *page) +{ +    Orientation orientation; + +    g_return_if_fail (page != NULL); + +    orientation = page_get_orientation (page); +    if (orientation == RIGHT_TO_LEFT) +        orientation = TOP_TO_BOTTOM; +    else +        orientation++; +    page_set_orientation (page, orientation); +} + + +void +page_rotate_right (Page *page) +{ +    Orientation orientation; + +    orientation = page_get_orientation (page); +    if (orientation == TOP_TO_BOTTOM) +        orientation = RIGHT_TO_LEFT; +    else +        orientation--; +    page_set_orientation (page, orientation); +} + + +gint +page_get_dpi (Page *page) +{ +    g_return_val_if_fail (page != NULL, 0); + +    return page->priv->dpi; +} + + +gboolean +page_is_landscape (Page *page) +{ +   return page_get_width (page) > page_get_height (page); +} + + +gint +page_get_width (Page *page) +{ +    g_return_val_if_fail (page != NULL, 0); +    return gdk_pixbuf_get_width (page->priv->image); +} + + +gint +page_get_height (Page *page) +{ +    g_return_val_if_fail (page != NULL, 0); +    return gdk_pixbuf_get_height (page->priv->image); +} + + +gint +page_get_scan_width (Page *page) +{ +    g_return_val_if_fail (page != NULL, 0); + +    if (page->priv->orientation == TOP_TO_BOTTOM || page->priv->orientation == BOTTOM_TO_TOP) +        return gdk_pixbuf_get_width (page->priv->image); +    else +        return gdk_pixbuf_get_height (page->priv->image); +} + + +gint +page_get_scan_height (Page *page) +{ +    g_return_val_if_fail (page != NULL, 0); + +    if (page->priv->orientation == TOP_TO_BOTTOM || page->priv->orientation == BOTTOM_TO_TOP) +        return gdk_pixbuf_get_height (page->priv->image); +    else +        return gdk_pixbuf_get_width (page->priv->image);   +} + + +void page_set_color_profile (Page *page, const gchar *color_profile) +{ +     g_free (page->priv->color_profile); +     page->priv->color_profile = g_strdup (color_profile); +} + + +const gchar *page_get_color_profile (Page *page) +{ +     return page->priv->color_profile; +} + + +void +page_set_no_crop (Page *page) +{ +    g_return_if_fail (page != NULL); + +    if (!page->priv->has_crop) +        return; +    page->priv->has_crop = FALSE; +    g_signal_emit (page, signals[CROP_CHANGED], 0); +} + + +void +page_set_custom_crop (Page *page, gint width, gint height) +{ +    //gint pw, ph; + +    g_return_if_fail (page != NULL); +    g_return_if_fail (width >= 1); +    g_return_if_fail (height >= 1); +     +    if (!page->priv->crop_name && +        page->priv->has_crop && +        page->priv->crop_width == width && +        page->priv->crop_height == height) +        return; +    g_free (page->priv->crop_name); +    page->priv->crop_name = NULL; +    page->priv->has_crop = TRUE; + +    page->priv->crop_width = width; +    page->priv->crop_height = height; + +    /*pw = page_get_width (page); +    ph = page_get_height (page); +    if (page->priv->crop_width < pw) +        page->priv->crop_x = (pw - page->priv->crop_width) / 2; +    else +        page->priv->crop_x = 0; +    if (page->priv->crop_height < ph) +        page->priv->crop_y = (ph - page->priv->crop_height) / 2; +    else +        page->priv->crop_y = 0;*/ +     +    g_signal_emit (page, signals[CROP_CHANGED], 0); +} + + +void +page_set_named_crop (Page *page, const gchar *name) +{ +    struct { +        const gchar *name; +        /* Width and height in inches */ +        gdouble width, height; +    } named_crops[] = +    { +        {"A4", 8.3, 11.7}, +        {"A5", 5.8, 8.3}, +        {"A6", 4.1, 5.8}, +        {"letter", 8.5, 11}, +        {"legal", 8.5, 14}, +        {"4x6", 4, 6}, +        {NULL, 0, 0} +    }; +    gint i; +    gint pw, ph; +    double width, height; + +    g_return_if_fail (page != NULL); +     +    for (i = 0; named_crops[i].name && strcmp (name, named_crops[i].name) != 0; i++); +    width = named_crops[i].width; +    height = named_crops[i].height; + +    if (!named_crops[i].name) { +        g_warning ("Unknown paper size '%s'", name); +        return; +    } + +    g_free (page->priv->crop_name); +    page->priv->crop_name = g_strdup (name); +    page->priv->has_crop = TRUE; +     +    pw = page_get_width (page); +    ph = page_get_height (page); +    +    /* Rotate to match original aspect */ +    if (pw > ph) { +        double t; +        t = width; +        width = height; +        height = t; +    } + +    /* Custom crop, make slightly smaller than original */ +    page->priv->crop_width = (int) (width * page->priv->dpi + 0.5); +    page->priv->crop_height = (int) (height * page->priv->dpi + 0.5); +         +    if (page->priv->crop_width < pw) +        page->priv->crop_x = (pw - page->priv->crop_width) / 2; +    else +        page->priv->crop_x = 0; +    if (page->priv->crop_height < ph) +        page->priv->crop_y = (ph - page->priv->crop_height) / 2; +    else +        page->priv->crop_y = 0; +    g_signal_emit (page, signals[CROP_CHANGED], 0); +} + + +void +page_move_crop (Page *page, gint x, gint y) +{ +    g_return_if_fail (x >= 0); +    g_return_if_fail (y >= 0); +    g_return_if_fail (x < page_get_width (page)); +    g_return_if_fail (y < page_get_height (page)); + +    page->priv->crop_x = x; +    page->priv->crop_y = y; +    g_signal_emit (page, signals[CROP_CHANGED], 0);     +} + + +void +page_rotate_crop (Page *page) +{ +    gint t; +     +    g_return_if_fail (page != NULL); +   +    if (!page->priv->has_crop) +        return; + +    t = page->priv->crop_width; +    page->priv->crop_width = page->priv->crop_height; +    page->priv->crop_height = t; +   +    /* Clip custom crops */ +    if (!page->priv->crop_name) { +        gint w, h; + +        w = page_get_width (page); +        h = page_get_height (page); +         +        if (page->priv->crop_x + page->priv->crop_width > w) +            page->priv->crop_x = w - page->priv->crop_width; +        if (page->priv->crop_x < 0) { +            page->priv->crop_x = 0; +            page->priv->crop_width = w; +        } +        if (page->priv->crop_y + page->priv->crop_height > h) +            page->priv->crop_y = h - page->priv->crop_height; +        if (page->priv->crop_y < 0) { +            page->priv->crop_y = 0; +            page->priv->crop_height = h; +        } +    } + +    g_signal_emit (page, signals[CROP_CHANGED], 0); +} + + +gboolean +page_has_crop (Page *page) +{ +    g_return_val_if_fail (page != NULL, FALSE); +    return page->priv->has_crop; +} + + +void +page_get_crop (Page *page, gint *x, gint *y, gint *width, gint *height) +{ +    g_return_if_fail (page != NULL); + +    if (x) +        *x = page->priv->crop_x; +    if (y) +        *y = page->priv->crop_y; +    if (width) +        *width = page->priv->crop_width; +    if (height) +        *height = page->priv->crop_height; +} + + +gchar * +page_get_named_crop (Page *page) +{ +    g_return_val_if_fail (page != NULL, NULL); + +    if (page->priv->crop_name) +        return g_strdup (page->priv->crop_name); +    else +        return NULL; +} + + +GdkPixbuf * +page_get_image (Page *page) +{ +    g_return_val_if_fail (page != NULL, NULL); +    return g_object_ref (page->priv->image); +} + + +GdkPixbuf * +page_get_cropped_image (Page *page) +{ +    GdkPixbuf *image, *cropped_image; +    gint x, y, w, h, pw, ph; + +    g_return_val_if_fail (page != NULL, NULL); + +    image = page_get_image (page); + +    if (!page->priv->has_crop) +        return image; + +    x = page->priv->crop_x; +    y = page->priv->crop_y; +    w = page->priv->crop_width; +    h = page->priv->crop_height; +    pw = gdk_pixbuf_get_width (image); +    ph = gdk_pixbuf_get_height (image); + +    /* Trim crop */ +    if (x + w >= pw) +        w = pw - x; +    if (y + h >= ph) +        h = ph - y; + +    cropped_image = gdk_pixbuf_new_subpixbuf (image, x, y, w, h); +    g_object_unref (image); + +    return cropped_image; +} + + +static gboolean +write_pixbuf_data (const gchar *buf, gsize count, GError **error, GFileOutputStream *stream) +{ +    return g_output_stream_write_all (G_OUTPUT_STREAM (stream), buf, count, NULL, NULL, error); +} + + +static gchar * +get_icc_data_encoded (const gchar *icc_profile_filename) +{ +    gchar *contents = NULL; +    gchar *contents_encode = NULL; +    gsize length; +    gboolean ret; +    GError *error = NULL; + +    /* Get binary data */ +    ret = g_file_get_contents (icc_profile_filename, &contents, &length, &error); +    if (!ret) { +        g_warning ("failed to get icc profile data: %s", error->message); +        g_error_free (error); +    } +    else { +        /* Encode into base64 */ +        contents_encode = g_base64_encode ((const guchar *) contents, length); +    } +   +    g_free (contents); +    return contents_encode; +} + + +gboolean +page_save (Page *page, const gchar *type, GFile *file, GError **error) +{ +    GFileOutputStream *stream; +    GdkPixbuf *image; +    gboolean result = FALSE; +    gchar *icc_profile_data = NULL; + +    stream = g_file_replace (file, NULL, FALSE, G_FILE_CREATE_NONE, NULL, error); +    if (!stream) +        return FALSE; + +    image = page_get_cropped_image (page); + +    if (page->priv->color_profile != NULL) +        icc_profile_data = get_icc_data_encoded (page->priv->color_profile); + +    if (strcmp (type, "jpeg") == 0) { +        /* ICC profile is awaiting review in gtk2+ bugzilla */ +        gchar *keys[] = { "quality", /* "icc-profile", */ NULL }; +        gchar *values[] = { "90", /* icc_profile_data, */ NULL }; +        result = gdk_pixbuf_save_to_callbackv (image, +                                               (GdkPixbufSaveFunc) write_pixbuf_data, stream, +                                               "jpeg", keys, values, error); +    } +    else if (strcmp (type, "png") == 0) { +        gchar *keys[] = { "icc-profile", NULL }; +        gchar *values[] = { icc_profile_data, NULL }; +        if (icc_profile_data == NULL) +            keys[0] = NULL; +        result = gdk_pixbuf_save_to_callbackv (image, +                                               (GdkPixbufSaveFunc) write_pixbuf_data, stream, +                                               "png", keys, values, error); +    } +    else if (strcmp (type, "tiff") == 0) { +        gchar *keys[] = { "compression", "icc-profile", NULL }; +        gchar *values[] = { "8" /* Deflate compression */, icc_profile_data, NULL }; +        if (icc_profile_data == NULL) +            keys[1] = NULL; +        result = gdk_pixbuf_save_to_callbackv (image, +                                               (GdkPixbufSaveFunc) write_pixbuf_data, stream, +                                               "tiff", keys, values, error); +    } +    else +        result = FALSE; // FIXME: Set GError + +    g_free (icc_profile_data); +    g_object_unref (image); +    g_object_unref (stream); + +    return result; +} + + +static void +page_finalize (GObject *object) +{ +    Page *page = PAGE (object); +    if (page->priv->image) +        g_object_unref (page->priv->image); +    page->priv->image = NULL; +    G_OBJECT_CLASS (page_parent_class)->finalize (object); +} + + +static void +page_class_init (PageClass *klass) +{ +    GObjectClass *object_class = G_OBJECT_CLASS (klass); + +    object_class->finalize = page_finalize; + +    signals[IMAGE_CHANGED] = +        g_signal_new ("image-changed", +                      G_TYPE_FROM_CLASS (klass), +                      G_SIGNAL_RUN_LAST, +                      G_STRUCT_OFFSET (PageClass, image_changed), +                      NULL, NULL, +                      g_cclosure_marshal_VOID__VOID, +                      G_TYPE_NONE, 0); +    signals[SIZE_CHANGED] = +        g_signal_new ("size-changed", +                      G_TYPE_FROM_CLASS (klass), +                      G_SIGNAL_RUN_LAST, +                      G_STRUCT_OFFSET (PageClass, size_changed), +                      NULL, NULL, +                      g_cclosure_marshal_VOID__VOID, +                      G_TYPE_NONE, 0); +    signals[SCAN_LINE_CHANGED] = +        g_signal_new ("scan-line-changed", +                      G_TYPE_FROM_CLASS (klass), +                      G_SIGNAL_RUN_LAST, +                      G_STRUCT_OFFSET (PageClass, scan_line_changed), +                      NULL, NULL, +                      g_cclosure_marshal_VOID__VOID, +                      G_TYPE_NONE, 0); +    signals[ORIENTATION_CHANGED] = +        g_signal_new ("orientation-changed", +                      G_TYPE_FROM_CLASS (klass), +                      G_SIGNAL_RUN_LAST, +                      G_STRUCT_OFFSET (PageClass, orientation_changed), +                      NULL, NULL, +                      g_cclosure_marshal_VOID__VOID, +                      G_TYPE_NONE, 0); +    signals[CROP_CHANGED] = +        g_signal_new ("crop-changed", +                      G_TYPE_FROM_CLASS (klass), +                      G_SIGNAL_RUN_LAST, +                      G_STRUCT_OFFSET (PageClass, crop_changed), +                      NULL, NULL, +                      g_cclosure_marshal_VOID__VOID, +                      G_TYPE_NONE, 0); + +    g_type_class_add_private (klass, sizeof (PagePrivate)); +} + + +static void +page_init (Page *page) +{ +    page->priv = G_TYPE_INSTANCE_GET_PRIVATE (page, PAGE_TYPE, PagePrivate); +    page->priv->orientation = TOP_TO_BOTTOM; +} diff --git a/src/page.h b/src/page.h new file mode 100644 index 0000000..b36fe32 --- /dev/null +++ b/src/page.h @@ -0,0 +1,121 @@ +/* + * Copyright (C) 2009 Canonical Ltd. + * Author: Robert Ancell <robert.ancell@canonical.com> + *  + * 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 3 of the License, or (at your option) any later + * version. See http://www.gnu.org/copyleft/gpl.html the full text of the + * license. + */ + +#ifndef _PAGE_H_ +#define _PAGE_H_ + +#include <glib-object.h> +#include <gio/gio.h> +#include <gdk-pixbuf/gdk-pixbuf.h> +#include "scanner.h" + +G_BEGIN_DECLS + +#define PAGE_TYPE  (page_get_type ()) +#define PAGE(obj)  (G_TYPE_CHECK_INSTANCE_CAST ((obj), PAGE_TYPE, Page)) +     +typedef enum +{ +    TOP_TO_BOTTOM, +    LEFT_TO_RIGHT, +    BOTTOM_TO_TOP, +    RIGHT_TO_LEFT +} Orientation; + + +typedef struct PagePrivate PagePrivate; + +typedef struct +{ +    GObject      parent_instance; +    PagePrivate *priv; +} Page; + +typedef struct +{ +    GObjectClass parent_class; + +    void (*image_changed) (Page *page); +    void (*size_changed) (Page *page); +    void (*scan_line_changed) (Page *page); +    void (*orientation_changed) (Page *page); +    void (*crop_changed) (Page *page); +} PageClass; + + +GType page_get_type (void); + +Page *page_new (void); + +// FIXME: Should be part of page_new +void page_setup (Page *page, gint width, gint height, gint dpi, Orientation orientation); + +void page_set_scan_area (Page *page, gint width, gint rows, gint dpi); + +gint page_get_dpi (Page *page); + +gboolean page_is_landscape (Page *page); + +gint page_get_width (Page *page); + +gint page_get_height (Page *page); + +gint page_get_scan_width (Page *page); + +gint page_get_scan_height (Page *page); + +void page_set_color_profile (Page *page, const gchar *color_profile); + +const gchar *page_get_color_profile (Page *page); + +void page_start (Page *page); + +gboolean page_is_scanning (Page *page); + +gboolean page_has_data (Page *page); + +gint page_get_scan_line (Page *page); + +void page_parse_scan_line (Page *page, ScanLine *line); + +void page_finish (Page *page); + +Orientation page_get_orientation (Page *page); + +void page_set_orientation (Page *page, Orientation orientation); + +void page_rotate_left (Page *page); + +void page_rotate_right (Page *page); + +void page_set_no_crop (Page *page); + +void page_set_custom_crop (Page *page, gint width, gint height); + +void page_set_named_crop (Page *page, const gchar *name); + +void page_move_crop (Page *page, gint x, gint y); + +void page_rotate_crop (Page *page); + +gboolean page_has_crop (Page *page); + +void page_get_crop (Page *page, gint *x, gint *y, gint *width, gint *height); + +gchar *page_get_named_crop (Page *page); + +GdkPixbuf *page_get_image (Page *page); + +GdkPixbuf *page_get_cropped_image (Page *page); + +gboolean page_save (Page *page, const gchar *type, GFile *file, GError **error); + +#endif /* _PAGE_H_ */ diff --git a/src/scanner.c b/src/scanner.c new file mode 100644 index 0000000..daf1b15 --- /dev/null +++ b/src/scanner.c @@ -0,0 +1,1630 @@ +/* + * Copyright (C) 2009 Canonical Ltd. + * Author: Robert Ancell <robert.ancell@canonical.com> + *  + * 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 3 of the License, or (at your option) any later + * version. See http://www.gnu.org/copyleft/gpl.html the full text of the + * license. + */ + +#include <stdlib.h> +#include <string.h> +#include <math.h> +#include <sane/sane.h> +#include <sane/saneopts.h> +#include <glib/gi18n.h> + +#include "scanner.h" + +/* TODO: Could indicate the start of the next page immediately after the last page is received (i.e. before the sane_cancel()) */ + +enum { +    UPDATE_DEVICES, +    AUTHORIZE, +    EXPECT_PAGE, +    GOT_PAGE_INFO, +    GOT_LINE, +    SCAN_FAILED, +    PAGE_DONE, +    DOCUMENT_DONE, +    SCANNING_CHANGED, +    LAST_SIGNAL +}; +static guint signals[LAST_SIGNAL] = { 0, }; + +typedef struct +{ +    Scanner *instance; +    guint sig; +    gpointer data; +} SignalInfo; + +typedef struct +{ +    gchar *device; +    gdouble dpi; +    ScanMode scan_mode; +    gint depth; +    gboolean type; +    gint page_width, page_height; +} ScanJob; + +typedef struct +{ +    enum +    { +       REQUEST_CANCEL, +       REQUEST_REDETECT, +       REQUEST_START_SCAN, +       REQUEST_QUIT +    } type; +    ScanJob *job; +} ScanRequest; + +typedef struct +{ +    gchar *username, *password; +} Credentials; + +typedef enum +{ +    STATE_IDLE = 0, +    STATE_REDETECT, +    STATE_OPEN, +    STATE_GET_OPTION, +    STATE_START, +    STATE_GET_PARAMETERS, +    STATE_READ +} ScanState; + +struct ScannerPrivate +{ +    GAsyncQueue *scan_queue, *authorize_queue; +    GThread *thread; + +    gchar *default_device; + +    ScanState state; +    gboolean redetect; +   +    GList *job_queue; + +    /* Handle to SANE device */ +    SANE_Handle handle; +    gchar *current_device; +   +    SANE_Parameters parameters; + +    /* Last option read */ +    SANE_Int option_index; + +    /* Buffer for received line */ +    SANE_Byte *buffer; +    SANE_Int buffer_size, n_used; + +    SANE_Int bytes_remaining, line_count, pass_number, page_number, notified_page; + +    gboolean scanning; +}; + +G_DEFINE_TYPE (Scanner, scanner, G_TYPE_OBJECT); + + +/* Table of scanner objects for each thread (required for authorization callback) */ +static GHashTable *scanners; + + +static gboolean +send_signal (SignalInfo *info) +{ +    g_signal_emit (info->instance, signals[info->sig], 0, info->data); + +    switch (info->sig) { +    case UPDATE_DEVICES: +        { +            GList *iter, *devices = info->data; +            for (iter = devices; iter; iter = iter->next) { +                ScanDevice *device = iter->data; +                g_free (device->name); +                g_free (device->label); +                g_free (device); +            } +            g_list_free (devices); +        } +        break; +    case AUTHORIZE: +        { +            gchar *resource = info->data; +            g_free (resource); +        } +        break; +    case GOT_PAGE_INFO: +        { +            ScanPageInfo *page_info = info->data; +            g_free (page_info); +        } +        break; +    case GOT_LINE: +        { +            ScanLine *line = info->data; +            g_free(line->data); +            g_free(line); +        } +        break; +    case SCAN_FAILED: +        { +            GError *error = info->data; +            g_error_free (error); +        } +        break; +    default: +    case EXPECT_PAGE: +    case PAGE_DONE: +    case DOCUMENT_DONE: +    case LAST_SIGNAL: +        g_assert (info->data == NULL); +        break; +    } +    g_free (info); + +    return FALSE; +} + + +/* Emit signals in main loop */ +static void +emit_signal (Scanner *scanner, guint sig, gpointer data) +{ +    SignalInfo *info; +     +    info = g_malloc(sizeof(SignalInfo)); +    info->instance = scanner; +    info->sig = sig; +    info->data = data; +    g_idle_add ((GSourceFunc) send_signal, info); +} + + +static void +set_scanning (Scanner *scanner, gboolean is_scanning) +{ +    if ((scanner->priv->scanning && !is_scanning) || (!scanner->priv->scanning && is_scanning)) { +        scanner->priv->scanning = is_scanning; +        emit_signal (scanner, SCANNING_CHANGED, NULL); +    } +} + + +static gint +get_device_weight (const gchar *device) +{ +    /* NOTE: This is using trends in the naming of SANE devices, SANE should be able to provide this information better */ +   +    /* Use webcams as a last resort */ +    if (g_str_has_prefix (device, "vfl:")) +       return 2; + +    /* Use locally connected devices first */ +    if (strstr (device, "usb")) +       return 0; + +    return 1; +} + + +static gint +compare_devices (ScanDevice *device1, ScanDevice *device2) +{ +    gint weight1, weight2; +   +    /* TODO: Should do some fuzzy matching on the last selected device and set that to the default */ + +    weight1 = get_device_weight (device1->name); +    weight2 = get_device_weight (device2->name); +    if (weight1 != weight2) +        return weight1 - weight2; +    +    return strcmp (device1->label, device2->label); +} + + +static const char * +get_status_string (SANE_Status status) +{ +    struct { +        SANE_Status status; +        const char *name; +    } status_names[] = { +        { SANE_STATUS_GOOD,          "SANE_STATUS_GOOD"}, +        { SANE_STATUS_UNSUPPORTED,   "SANE_STATUS_UNSUPPORTED"}, +        { SANE_STATUS_CANCELLED,     "SANE_STATUS_CANCELLED"}, +        { SANE_STATUS_DEVICE_BUSY,   "SANE_STATUS_DEVICE_BUSY"}, +        { SANE_STATUS_INVAL,         "SANE_STATUS_INVAL"}, +        { SANE_STATUS_EOF,           "SANE_STATUS_EOF"}, +        { SANE_STATUS_JAMMED,        "SANE_STATUS_JAMMED"}, +        { SANE_STATUS_NO_DOCS,       "SANE_STATUS_NO_DOCS"}, +        { SANE_STATUS_COVER_OPEN,    "SANE_STATUS_COVER_OPEN"}, +        { SANE_STATUS_IO_ERROR,      "SANE_STATUS_IO_ERROR"}, +        { SANE_STATUS_NO_MEM,        "SANE_STATUS_NO_MEM"}, +        { SANE_STATUS_ACCESS_DENIED, "SANE_STATUS_ACCESS_DENIED"}, +        { -1,                        NULL} +    }; +    static char *unknown_string = NULL; +    int i; + +    for (i = 0; status_names[i].name != NULL && status_names[i].status != status; i++); + +    if (status_names[i].name == NULL) { +        g_free (unknown_string); +        unknown_string = g_strdup_printf ("SANE_STATUS(%d)", status); +        return unknown_string; /* Note result is undefined on second call to this function */ +    } +   +    return status_names[i].name; +} + + +static const char * +get_action_string (SANE_Action action) +{ +    struct { +        SANE_Action action; +        const char *name; +    } action_names[] = { +        { SANE_ACTION_GET_VALUE, "SANE_ACTION_GET_VALUE" }, +        { SANE_ACTION_SET_VALUE, "SANE_ACTION_SET_VALUE" }, +        { SANE_ACTION_SET_AUTO,  "SANE_ACTION_SET_AUTO" }, +        { -1,                    NULL} +    }; +    static char *unknown_string = NULL; +    int i; + +    for (i = 0; action_names[i].name != NULL && action_names[i].action != action; i++); + +    if (action_names[i].name == NULL) { +        g_free (unknown_string); +        unknown_string = g_strdup_printf ("SANE_ACTION(%d)", action); +        return unknown_string; /* Note result is undefined on second call to this function */ +    } +   +    return action_names[i].name; +} + + +static const char * +get_frame_string (SANE_Frame frame) +{ +    struct { +        SANE_Frame frame; +        const char *name; +    } frame_names[] = { +        { SANE_FRAME_GRAY,  "SANE_FRAME_GRAY" }, +        { SANE_FRAME_RGB,   "SANE_FRAME_RGB" },       +        { SANE_FRAME_RED,   "SANE_FRAME_RED" }, +        { SANE_FRAME_GREEN, "SANE_FRAME_GREEN" }, +        { SANE_FRAME_BLUE,  "SANE_FRAME_BLUE" }, +        { -1,               NULL} +    }; +    static char *unknown_string = NULL; +    int i; + +    for (i = 0; frame_names[i].name != NULL && frame_names[i].frame != frame; i++); + +    if (frame_names[i].name == NULL) { +        g_free (unknown_string); +        unknown_string = g_strdup_printf ("SANE_FRAME(%d)", frame); +        return unknown_string; /* Note result is undefined on second call to this function */ +    } +   +    return frame_names[i].name; +} + + +static void +do_redetect (Scanner *scanner) +{ +    const SANE_Device **device_list, **device_iter; +    SANE_Status status; +    GList *devices = NULL; + +    status = sane_get_devices (&device_list, SANE_FALSE); +    g_debug ("sane_get_devices () -> %s", get_status_string (status)); +    if (status != SANE_STATUS_GOOD) { +        g_warning ("Unable to get SANE devices: %s", sane_strstatus(status)); +        scanner->priv->state = STATE_IDLE;         +        return; +    } + +    for (device_iter = device_list; *device_iter; device_iter++) { +        const SANE_Device *device = *device_iter; +        ScanDevice *scan_device; +        gchar *c, *label; + +        g_debug ("Device: name=\"%s\" vendor=\"%s\" model=\"%s\" type=\"%s\"", +                 device->name, device->vendor, device->model, device->type); +         +        scan_device = g_malloc0 (sizeof (ScanDevice)); + +        scan_device->name = g_strdup (device->name); + +        /* Abbreviate HP as it is a long string and does not match what is on the physical scanner */ +        if (strcmp (device->vendor, "Hewlett-Packard") == 0) +            label = g_strdup_printf ("HP %s", device->model); +        else +            label = g_strdup_printf ("%s %s", device->vendor, device->model); +         +        /* Replace underscored in name */ +        for (c = label; *c; c++) +            if (*c == '_') +                *c = ' '; + +        scan_device->label = label; + +        devices = g_list_append (devices, scan_device); +    } + +    /* Sort devices by priority */ +    devices = g_list_sort (devices, (GCompareFunc) compare_devices); + +    scanner->priv->redetect = FALSE; +    scanner->priv->state = STATE_IDLE; + +    g_free (scanner->priv->default_device); +    if (devices) { +        ScanDevice *device = g_list_nth_data (devices, 0); +        scanner->priv->default_device = g_strdup (device->name); +    } +    else +        scanner->priv->default_device = NULL; + +    emit_signal (scanner, UPDATE_DEVICES, devices); +} + + +static gboolean +control_option (SANE_Handle handle, const SANE_Option_Descriptor *option, SANE_Int index, SANE_Action action, void *value) +{ +    SANE_Status status; +    gchar *old_value; + +    switch (option->type) { +    case SANE_TYPE_BOOL: +        old_value = g_strdup_printf (*((SANE_Bool *) value) ? "SANE_TRUE" : "SANE_FALSE"); +        break; +    case SANE_TYPE_INT: +        old_value = g_strdup_printf ("%d", *((SANE_Int *) value)); +        break; +    case SANE_TYPE_FIXED: +        old_value = g_strdup_printf ("%f", SANE_UNFIX (*((SANE_Fixed *) value))); +        break; +    case SANE_TYPE_STRING: +        old_value = g_strdup_printf ("\"%s\"", (char *) value); +        break; +    default: +        old_value = g_strdup ("?"); +        break; +    } + +    status = sane_control_option (handle, index, action, value, NULL); +    switch (option->type) { +    case SANE_TYPE_BOOL: +        g_debug ("sane_control_option (%d, %s, %s) -> (%s, %s)", +                 index, get_action_string (action), +                 *((SANE_Bool *) value) ? "SANE_TRUE" : "SANE_FALSE", +                 get_status_string (status), +                 old_value); +        break; +    case SANE_TYPE_INT: +        g_debug ("sane_control_option (%d, %s, %d) -> (%s, %s)", +                 index, get_action_string (action), +                 *((SANE_Int *) value), +                 get_status_string (status), +                 old_value); +        break; +    case SANE_TYPE_FIXED: +        g_debug ("sane_control_option (%d, %s, %f) -> (%s, %s)", +                 index, get_action_string (action), +                 SANE_UNFIX (*((SANE_Fixed *) value)), +                 get_status_string (status), +                 old_value); +        break; +    case SANE_TYPE_STRING: +        g_debug ("sane_control_option (%d, %s, \"%s\") -> (%s, %s)", +                 index, get_action_string (action), +                 (char *) value, +                 get_status_string (status), +                 old_value); +        break; +    default: +        break; +    } +    g_free (old_value); + +    if (status != SANE_STATUS_GOOD) +        g_warning ("Error setting option %s: %s", option->name, sane_strstatus(status)); + +    return status == SANE_STATUS_GOOD; +} + + +static gboolean +set_default_option (SANE_Handle handle, const SANE_Option_Descriptor *option, SANE_Int option_index) +{ +    SANE_Status status; + +    status = sane_control_option (handle, option_index, SANE_ACTION_SET_AUTO, NULL, NULL); +    g_debug ("sane_control_option (%d, SANE_ACTION_SET_AUTO) -> %s", +             option_index, get_status_string (status)); +    if (status != SANE_STATUS_GOOD) +        g_warning ("Error setting default option %s: %s", option->name, sane_strstatus(status)); + +    return status == SANE_STATUS_GOOD; +} + + +static void +set_bool_option (SANE_Handle handle, const SANE_Option_Descriptor *option, SANE_Int option_index, SANE_Bool value, SANE_Bool *result) +{ +    SANE_Bool v = value; +    g_return_if_fail (option->type == SANE_TYPE_BOOL); +    control_option (handle, option, option_index, SANE_ACTION_SET_VALUE, &v); +    if (result) +        *result = v; +} + + +static void +set_int_option (SANE_Handle handle, const SANE_Option_Descriptor *option, SANE_Int option_index, SANE_Int value, SANE_Int *result) +{ +    SANE_Int v = value; + +    g_return_if_fail (option->type == SANE_TYPE_INT); + +    if (option->constraint_type == SANE_CONSTRAINT_RANGE) { +        if (option->constraint.range->quant) +            v *= option->constraint.range->quant; +        if (v < option->constraint.range->min) +            v = option->constraint.range->min; +        if (v > option->constraint.range->max) +            v = option->constraint.range->max; +    } +    else if (option->constraint_type == SANE_CONSTRAINT_WORD_LIST) { +        int i; +        SANE_Int distance = INT_MAX, nearest = 0; + +        /* Find nearest value to requested */ +        for (i = 0; i < option->constraint.word_list[0]; i++) { +            SANE_Int x = option->constraint.word_list[i+1]; +            if (abs (x - v) < distance) { +                distance = abs (x - v); +                nearest = x; +            } +        } +        v = nearest; +    } + +    control_option (handle, option, option_index, SANE_ACTION_SET_VALUE, &v); +    if (result) +        *result = v; +} + + +static void +set_fixed_option (SANE_Handle handle, const SANE_Option_Descriptor *option, SANE_Int option_index, gdouble value, gdouble *result) +{ +    gdouble v = value; +    SANE_Fixed v_fixed; + +    g_return_if_fail (option->type == SANE_TYPE_FIXED); + +    if (option->constraint_type == SANE_CONSTRAINT_RANGE) { +        double min = SANE_UNFIX (option->constraint.range->min); +        double max = SANE_UNFIX (option->constraint.range->max); + +        if (v < min) +            v = min; +        if (v > max) +            v = max; +    } +    else if (option->constraint_type == SANE_CONSTRAINT_WORD_LIST) { +        int i; +        double distance = DBL_MAX, nearest = 0.0; + +        /* Find nearest value to requested */ +        for (i = 0; i < option->constraint.word_list[0]; i++) { +            double x = SANE_UNFIX (option->constraint.word_list[i+1]); +            if (fabs (x - v) < distance) { +                distance = fabs (x - v); +                nearest = x; +            } +        } +        v = nearest; +    } + +    v_fixed = SANE_FIX (v); +    control_option (handle, option, option_index, SANE_ACTION_SET_VALUE, &v_fixed); +    if (result) +        *result = SANE_UNFIX (v_fixed); +} + + +static gboolean +set_string_option (SANE_Handle handle, const SANE_Option_Descriptor *option, SANE_Int option_index, const char *value, char **result) +{ +    char *string; +    gsize value_size, size; +    gboolean error; + +    g_return_val_if_fail (option->type == SANE_TYPE_STRING, FALSE); +     +    value_size = strlen (value) + 1; +    size = option->size > value_size ? option->size : value_size; +    string = g_malloc(sizeof(char) * size); +    strcpy (string, value); +    error = control_option (handle, option, option_index, SANE_ACTION_SET_VALUE, string); +    if (result) +        *result = string; +    else +        g_free (string); +    +    return error; +} + + +static gboolean +set_constrained_string_option (SANE_Handle handle, const SANE_Option_Descriptor *option, SANE_Int option_index, const char *values[], char **result) +{ +    gint i, j; + +    g_return_val_if_fail (option->type == SANE_TYPE_STRING, FALSE);   +    g_return_val_if_fail (option->constraint_type == SANE_CONSTRAINT_STRING_LIST, FALSE); +   +    for (i = 0; values[i] != NULL; i++) { +        for (j = 0; option->constraint.string_list[j]; j++) { +            if (strcmp (values[i], option->constraint.string_list[j]) == 0) +               break; +        } + +        if (option->constraint.string_list[j] != NULL) +            return set_string_option (handle, option, option_index, values[i], result); +    } +   +    return FALSE; +} + + +static void +log_option (SANE_Int index, const SANE_Option_Descriptor *option) +{ +    GString *string; +    SANE_Word i; +    SANE_Int cap; +     +    string = g_string_new (""); + +    g_string_append_printf (string, "Option %d:", index); +     +    if (option->name)     +        g_string_append_printf (string, " name='%s'", option->name); +     +    if (option->title) +        g_string_append_printf (string, " title='%s'", option->title); + +    switch (option->type) { +    case SANE_TYPE_BOOL: +        g_string_append (string, " type=bool"); +        break; +    case SANE_TYPE_INT: +        g_string_append (string, " type=int"); +        break; +    case SANE_TYPE_FIXED: +        g_string_append (string, " type=fixed");         +        break; +    case SANE_TYPE_STRING: +        g_string_append (string, " type=string");         +        break; +    case SANE_TYPE_BUTTON: +        g_string_append (string, " type=button");         +        break; +    case SANE_TYPE_GROUP: +        g_string_append (string, " type=group"); +        break; +    default: +        g_string_append_printf (string, " type=%d", option->type); +        break; +    } +     +    g_string_append_printf (string, " size=%d", option->size); + +    switch (option->unit) { +    case SANE_UNIT_NONE: +        break; +    case SANE_UNIT_PIXEL: +        g_string_append (string, " unit=pixels"); +        break; +    case SANE_UNIT_BIT: +        g_string_append (string, " unit=bits"); +        break; +    case SANE_UNIT_MM: +        g_string_append (string, " unit=mm"); +        break; +    case SANE_UNIT_DPI: +        g_string_append (string, " unit=dpi"); +        break; +    case SANE_UNIT_PERCENT: +        g_string_append (string, " unit=percent"); +        break; +    case SANE_UNIT_MICROSECOND: +        g_string_append (string, " unit=microseconds"); +        break; +    default: +        g_string_append_printf (string, " unit=%d", option->unit); +        break; +    } + +    switch (option->constraint_type) { +    case SANE_CONSTRAINT_RANGE: +        if (option->type == SANE_TYPE_FIXED) +            g_string_append_printf (string, " min=%f, max=%f, quant=%d", +                                    SANE_UNFIX (option->constraint.range->min), SANE_UNFIX (option->constraint.range->max), +                                    option->constraint.range->quant); +        else +            g_string_append_printf (string, " min=%d, max=%d, quant=%d", +                                    option->constraint.range->min, option->constraint.range->max, +                                    option->constraint.range->quant); +        break; +    case SANE_CONSTRAINT_WORD_LIST: +        g_string_append (string, " values=["); +        for (i = 0; i < option->constraint.word_list[0]; i++) { +            if (i != 0) +                g_string_append (string, ", "); +            if (option->type == SANE_TYPE_INT) +                g_string_append_printf (string, "%d", option->constraint.word_list[i+1]); +            else +                g_string_append_printf (string, "%f", SANE_UNFIX (option->constraint.word_list[i+1])); +        } +        g_string_append (string, "]"); +        break; +    case SANE_CONSTRAINT_STRING_LIST: +        g_string_append (string, " values=["); +        for (i = 0; option->constraint.string_list[i]; i++) { +            if (i != 0) +                g_string_append (string, ", "); +            g_string_append_printf (string, "\"%s\"", option->constraint.string_list[i]); +        } +        g_string_append (string, "]"); +        break; +    default: +        break; +    } +     +    cap = option->cap; +    if (cap) { +        struct { +            SANE_Int cap; +            const char *name; +        } caps[] = { +            { SANE_CAP_SOFT_SELECT,     "soft-select"}, +            { SANE_CAP_HARD_SELECT,     "hard-select"}, +            { SANE_CAP_SOFT_DETECT,     "soft-detect"}, +            { SANE_CAP_EMULATED,        "emulated"}, +            { SANE_CAP_AUTOMATIC,       "automatic"}, +            { SANE_CAP_INACTIVE,        "inactive"}, +            { SANE_CAP_ADVANCED,        "advanced"}, +            { 0,                        NULL} +        }; +        int i, n = 0; +         +        g_string_append (string, " cap="); +        for (i = 0; caps[i].cap > 0; i++) { +            if (cap & caps[i].cap) { +                cap &= ~caps[i].cap; +                if (n != 0) +                    g_string_append (string, ","); +                g_string_append (string, caps[i].name); +                n++; +            } +        } +        /* Unknown capabilities */ +        if (cap) { +            if (n != 0) +                g_string_append (string, ","); +            g_string_append_printf (string, "%x", cap); +        } +    } + +    g_debug ("%s", string->str); +    g_string_free (string, TRUE); + +    if (option->desc) +        g_debug ("  Description: %s", option->desc); +} + + +static void +authorization_cb (SANE_String_Const resource, SANE_Char username[SANE_MAX_USERNAME_LEN], SANE_Char password[SANE_MAX_PASSWORD_LEN]) +{ +    Scanner *scanner; +    Credentials *credentials; +    +    scanner = g_hash_table_lookup (scanners, g_thread_self ()); + +    emit_signal (scanner, AUTHORIZE, g_strdup (resource)); + +    credentials = g_async_queue_pop (scanner->priv->authorize_queue); +    strncpy (username, credentials->username, SANE_MAX_USERNAME_LEN); +    strncpy (password, credentials->password, SANE_MAX_PASSWORD_LEN); +    g_free (credentials); +} + + +void +scanner_authorize (Scanner *scanner, const gchar *username, const gchar *password) +{ +    Credentials *credentials; + +    credentials = g_malloc (sizeof (Credentials)); +    credentials->username = g_strdup (username); +    credentials->password = g_strdup (password); +    g_async_queue_push (scanner->priv->authorize_queue, credentials); +} + + +static void +close_device (Scanner *scanner) +{ +    GList *iter; + +    if (scanner->priv->handle) { +        sane_cancel (scanner->priv->handle); +        g_debug ("sane_cancel ()"); + +        sane_close (scanner->priv->handle); +        g_debug ("sane_close ()"); +        scanner->priv->handle = NULL; +    } +   +    g_free (scanner->priv->buffer); +    scanner->priv->buffer = NULL; + +    for (iter = scanner->priv->job_queue; iter; iter = iter->next) { +        ScanJob *job = (ScanJob *) iter->data; +        g_free (job->device); +        g_free (job); +    } +    g_list_free (scanner->priv->job_queue); +    scanner->priv->job_queue = NULL; +   +    set_scanning (scanner, FALSE); +} + +static void +fail_scan (Scanner *scanner, gint error_code, const gchar *error_string) +{ +    close_device (scanner); +    scanner->priv->state = STATE_IDLE; +    emit_signal (scanner, SCAN_FAILED, g_error_new (SCANNER_TYPE, error_code, "%s", error_string)); +} + + +static gboolean +handle_requests (Scanner *scanner) +{ +    gint request_count = 0; +   +    /* Redetect when idle */ +    if (scanner->priv->state == STATE_IDLE && scanner->priv->redetect) +        scanner->priv->state = STATE_REDETECT; +   +    /* Process all requests */ +    while (TRUE) { +        ScanRequest *request = NULL; + +        if ((scanner->priv->state == STATE_IDLE && request_count == 0) || +            g_async_queue_length (scanner->priv->scan_queue) > 0) +            request = g_async_queue_pop (scanner->priv->scan_queue); +        else +            return TRUE; + +         g_debug ("Processing request"); +         request_count++; + +         switch (request->type) { +         case REQUEST_REDETECT: +             break; + +         case REQUEST_START_SCAN: +             scanner->priv->job_queue = g_list_append (scanner->priv->job_queue, request->job); +             break; + +         case REQUEST_CANCEL: +             fail_scan (scanner, SANE_STATUS_CANCELLED, "Scan cancelled - do not report this error"); +             break; + +         case REQUEST_QUIT: +             close_device (scanner); +             g_free (request); +             return FALSE; +         } + +         g_free (request); +    } +   +    return TRUE; +} + + +static void +do_open (Scanner *scanner) +{ +    SANE_Status status; +    ScanJob *job; + +    job = (ScanJob *) scanner->priv->job_queue->data; + +    scanner->priv->line_count = 0; +    scanner->priv->pass_number = 0; +    scanner->priv->page_number = 0; +    scanner->priv->notified_page = -1; +    scanner->priv->option_index = 0; + +    if (!job->device && scanner->priv->default_device) +        job->device = g_strdup (scanner->priv->default_device); + +    if (!job->device) { +        g_warning ("No scan device available"); +        fail_scan (scanner, status, +                   /* Error displayed when no scanners to scan with */ +                   _("No scanners available.  Please connect a scanner.")); +        return; +    } +  +    /* See if we can use the already open device */ +    if (scanner->priv->handle) { +        if (strcmp (scanner->priv->current_device, job->device) == 0) { +            scanner->priv->state = STATE_GET_OPTION; +            return; +        } + +        sane_close (scanner->priv->handle); +        g_debug ("sane_close ()"); +        scanner->priv->handle = NULL; +    } +   +    g_free (scanner->priv->current_device); +    scanner->priv->current_device = NULL; + +    status = sane_open (job->device, &scanner->priv->handle); +    g_debug ("sane_open (\"%s\") -> %s", job->device, get_status_string (status)); + +    if (status != SANE_STATUS_GOOD) { +        g_warning ("Unable to get open device: %s", sane_strstatus (status)); +        scanner->priv->handle = NULL; +        fail_scan (scanner, status, +                   /* Error displayed when cannot connect to scanner */ +                   _("Unable to connect to scanner")); +        return; +    } + +    scanner->priv->current_device = g_strdup (job->device); +    scanner->priv->state = STATE_GET_OPTION; +} + + +static void +do_get_option (Scanner *scanner) +{ +    const SANE_Option_Descriptor *option; +    SANE_Int option_index; +    ScanJob *job; +   +    job = (ScanJob *) scanner->priv->job_queue->data;   + +    option = sane_get_option_descriptor (scanner->priv->handle, scanner->priv->option_index); +    g_debug ("sane_get_option_descriptor (%d)", scanner->priv->option_index); +    option_index = scanner->priv->option_index; +    scanner->priv->option_index++; + +    if (!option) { +        scanner->priv->state = STATE_START; +        return; +    } + +    log_option (option_index, option); +   +    /* Ignore groups */ +    if (option->type == SANE_TYPE_GROUP) +        return; + +    /* Option disabled */ +    if (option->cap & SANE_CAP_INACTIVE) +        return; +   +    /* Some options are unnammed (e.g. Option 0) */ +    if (option->name == NULL) +        return; + +    if (strcmp (option->name, SANE_NAME_SCAN_RESOLUTION) == 0) { +        if (option->type == SANE_TYPE_FIXED) { +            set_fixed_option (scanner->priv->handle, option, option_index, job->dpi, &job->dpi); +        } +        else { +            SANE_Int dpi; +            set_int_option (scanner->priv->handle, option, option_index, job->dpi, &dpi); +            job->dpi = dpi; +        } +    } +    else if (strcmp (option->name, SANE_NAME_SCAN_SOURCE) == 0) { +        const char *flatbed_sources[] = +        { +            "Flatbed", +            SANE_I18N ("Flatbed"), +            "FlatBed", +            "Normal", +            SANE_I18N ("Normal"), +            NULL +        }; + +        const char *adf_sources[] = +        { +            "Automatic Document Feeder", +            SANE_I18N ("Automatic Document Feeder"), +            "ADF", +            "Automatic Document Feeder(left aligned)", /* Seen in the proprietary brother3 driver */ +            "Automatic Document Feeder(centrally aligned)", /* Seen in the proprietary brother3 driver */ +            NULL +        }; + +        const char *adf_front_sources[] = +        { +            "ADF Front", +            SANE_I18N ("ADF Front"), +            NULL +        }; + +        const char *adf_back_sources[] = +        { +            "ADF Back", +            SANE_I18N ("ADF Back"), +            NULL +        }; + +        const char *adf_duplex_sources[] = +        { +            "ADF Duplex", +            SANE_I18N ("ADF Duplex"), +            NULL +        }; + +        switch (job->type) { +        case SCAN_SINGLE: +            if (!set_default_option (scanner->priv->handle, option, option_index)) +                if (!set_constrained_string_option (scanner->priv->handle, option, option_index, flatbed_sources, NULL)) +                    g_warning ("Unable to set single page source, please file a bug"); +            break; +        case SCAN_ADF_FRONT: +            if (!set_constrained_string_option (scanner->priv->handle, option, option_index, adf_front_sources, NULL)) +                if (!!set_constrained_string_option (scanner->priv->handle, option, option_index, adf_sources, NULL))                 +                    g_warning ("Unable to set front ADF source, please file a bug"); +            break; +        case SCAN_ADF_BACK: +            if (!set_constrained_string_option (scanner->priv->handle, option, option_index, adf_back_sources, NULL)) +                if (!set_constrained_string_option (scanner->priv->handle, option, option_index, adf_sources, NULL)) +                    g_warning ("Unable to set back ADF source, please file a bug"); +            break; +        case SCAN_ADF_BOTH: +            if (!set_constrained_string_option (scanner->priv->handle, option, option_index, adf_duplex_sources, NULL)) +                if (!set_constrained_string_option (scanner->priv->handle, option, option_index, adf_sources, NULL)) +                    g_warning ("Unable to set duplex ADF source, please file a bug"); +            break; +        } +    } +    else if (strcmp (option->name, SANE_NAME_BIT_DEPTH) == 0) { +        if (job->depth > 0) +            set_int_option (scanner->priv->handle, option, option_index, job->depth, NULL); +    } +    else if (strcmp (option->name, SANE_NAME_SCAN_MODE) == 0) { +        /* The names of scan modes often used in drivers, as taken from the sane-backends source */ +        const char *color_scan_modes[] = +        { +            SANE_VALUE_SCAN_MODE_COLOR, +            "Color", +            "24bit Color", /* Seen in the proprietary brother3 driver */ +            NULL +        }; +        const char *gray_scan_modes[] = +        { +            SANE_VALUE_SCAN_MODE_GRAY, +            "Gray", +            "Grayscale", +            SANE_I18N ("Grayscale"), +            NULL +        }; +        const char *lineart_scan_modes[] = +        { +            SANE_VALUE_SCAN_MODE_LINEART, +            "Lineart", +            "LineArt", +            SANE_I18N ("LineArt"), +            "Black & White", +            SANE_I18N ("Black & White"), +            "Binary", +            SANE_I18N ("Binary"), +            "Thresholded", +            SANE_VALUE_SCAN_MODE_GRAY, +            "Gray", +             "Grayscale", +            SANE_I18N ("Grayscale"), +            NULL +        }; +             +        switch (job->scan_mode) { +        case SCAN_MODE_COLOR: +            if (!set_constrained_string_option (scanner->priv->handle, option, option_index, color_scan_modes, NULL)) +                g_warning ("Unable to set Color mode, please file a bug"); +            break; +        case SCAN_MODE_GRAY: +            if (!set_constrained_string_option (scanner->priv->handle, option, option_index, gray_scan_modes, NULL)) +                g_warning ("Unable to set Gray mode, please file a bug"); +            break; +        case SCAN_MODE_LINEART: +            if (!set_constrained_string_option (scanner->priv->handle, option, option_index, lineart_scan_modes, NULL)) +                g_warning ("Unable to set Lineart mode, please file a bug"); +            break; +        default: +            break; +        } +    } +    /* Disable compression, we will compress after scanning */ +    else if (strcmp (option->name, "compression") == 0) { +        const char *disable_compression_names[] = +        { +            SANE_I18N ("None"), +            SANE_I18N ("none"), +            "None", +            "none", +            NULL +        }; +             +        if (!set_constrained_string_option (scanner->priv->handle, option, option_index, disable_compression_names, NULL)) +            g_warning ("Unable to disable compression, please file a bug"); +    } +    /* Always use maximum scan area - some scanners default to using partial areas.  This should be patched in sane-backends */ +    else if (strcmp (option->name, SANE_NAME_SCAN_BR_X) == 0 || +             strcmp (option->name, SANE_NAME_SCAN_BR_Y) == 0) { +        switch (option->constraint_type) +        { +        case SANE_CONSTRAINT_RANGE: +            if (option->type == SANE_TYPE_FIXED) +                set_fixed_option (scanner->priv->handle, option, option_index, SANE_UNFIX (option->constraint.range->max), NULL); +            else +                set_int_option (scanner->priv->handle, option, option_index, option->constraint.range->max, NULL); +            break; +        default: +            break; +        } +    } +    else if (strcmp (option->name, SANE_NAME_PAGE_WIDTH) == 0) { +        if (job->page_width > 0.0) { +            if (option->type == SANE_TYPE_FIXED) +                set_fixed_option (scanner->priv->handle, option, option_index, job->page_width / 10.0, NULL); +            else +                set_int_option (scanner->priv->handle, option, option_index, job->page_width / 10, NULL); +        } +    } +    else if (strcmp (option->name, SANE_NAME_PAGE_HEIGHT) == 0) { +        if (job->page_height > 0.0) { +            if (option->type == SANE_TYPE_FIXED) +                set_fixed_option (scanner->priv->handle, option, option_index, job->page_height / 10.0, NULL); +            else  +                set_int_option (scanner->priv->handle, option, option_index, job->page_height / 10, NULL); +        } +    } + +    /* Test scanner options (hoping will not effect other scanners...) */ +    if (strcmp (scanner->priv->current_device, "test") == 0) { +        if (strcmp (option->name, "hand-scanner") == 0) { +            set_bool_option (scanner->priv->handle, option, option_index, FALSE, NULL); +        } +        else if (strcmp (option->name, "three-pass") == 0) { +            set_bool_option (scanner->priv->handle, option, option_index, FALSE, NULL); +        }                     +        else if (strcmp (option->name, "test-picture") == 0) { +            set_string_option (scanner->priv->handle, option, option_index, "Color pattern", NULL); +        } +        else if (strcmp (option->name, "read-delay") == 0) { +            set_bool_option (scanner->priv->handle, option, option_index, TRUE, NULL); +        } +        else if (strcmp (option->name, "read-delay-duration") == 0) { +            set_int_option (scanner->priv->handle, option, option_index, 200000, NULL); +        } +    } +} + + +static void +do_complete_document (Scanner *scanner) +{ +    ScanJob *job; + +    job = (ScanJob *) scanner->priv->job_queue->data; +    g_free (job->device); +    g_free (job); +    scanner->priv->job_queue = g_list_remove_link (scanner->priv->job_queue, scanner->priv->job_queue); + +    scanner->priv->state = STATE_IDLE; +   +    /* Continue onto the next job */ +    if (scanner->priv->job_queue) { +        scanner->priv->state = STATE_OPEN; +        return; +    } + +    /* Trigger timeout to close */ +    // TODO + +    emit_signal (scanner, DOCUMENT_DONE, NULL); +    set_scanning (scanner, FALSE); +} + + +static void +do_start (Scanner *scanner) +{ +    SANE_Status status; + +    emit_signal (scanner, EXPECT_PAGE, NULL); + +    status = sane_start (scanner->priv->handle); +    g_debug ("sane_start (page=%d, pass=%d) -> %s", scanner->priv->page_number, scanner->priv->pass_number, get_status_string (status)); +    if (status == SANE_STATUS_GOOD) { +        scanner->priv->state = STATE_GET_PARAMETERS; +    } +    else if (status == SANE_STATUS_NO_DOCS) { +        do_complete_document (scanner); +    } +    else { +        g_warning ("Unable to start device: %s", sane_strstatus (status)); +        fail_scan (scanner, status, +                   /* Error display when unable to start scan */ +                   _("Unable to start scan")); +    } +} + + +static void +do_get_parameters (Scanner *scanner) +{ +    SANE_Status status; +    ScanPageInfo *info; +    ScanJob *job; + +    status = sane_get_parameters (scanner->priv->handle, &scanner->priv->parameters); +    g_debug ("sane_get_parameters () -> %s", get_status_string (status)); +    if (status != SANE_STATUS_GOOD) { +        g_warning ("Unable to get device parameters: %s", sane_strstatus (status)); +        fail_scan (scanner, status, +                   /* Error displayed when communication with scanner broken */ +                   _("Error communicating with scanner")); +        return; +    } +   +    job = (ScanJob *) scanner->priv->job_queue->data; + +    g_debug ("Parameters: format=%s last_frame=%s bytes_per_line=%d pixels_per_line=%d lines=%d depth=%d", +             get_frame_string (scanner->priv->parameters.format), +             scanner->priv->parameters.last_frame ? "SANE_TRUE" : "SANE_FALSE", +             scanner->priv->parameters.bytes_per_line, +             scanner->priv->parameters.pixels_per_line, +             scanner->priv->parameters.lines, +             scanner->priv->parameters.depth); + +    info = g_malloc(sizeof(ScanPageInfo)); +    info->width = scanner->priv->parameters.pixels_per_line; +    info->height = scanner->priv->parameters.lines; +    info->depth = scanner->priv->parameters.depth; +    info->dpi = job->dpi; // FIXME: This is the requested DPI, not the actual DPI +    info->device = g_strdup (scanner->priv->current_device); + +    if (scanner->priv->page_number != scanner->priv->notified_page) { +        emit_signal (scanner, GOT_PAGE_INFO, info); +        scanner->priv->notified_page = scanner->priv->page_number; +    } + +    /* Prepare for read */ +    scanner->priv->buffer_size = scanner->priv->parameters.bytes_per_line + 1; /* Use +1 so buffer is not resized if driver returns one line per read */ +    scanner->priv->buffer = g_malloc (sizeof(SANE_Byte) * scanner->priv->buffer_size); +    scanner->priv->n_used = 0; +    scanner->priv->line_count = 0; +    scanner->priv->pass_number = 0; +    scanner->priv->state = STATE_READ; +} + + +static void +do_complete_page (Scanner *scanner) +{ +    ScanJob *job; + +    emit_signal (scanner, PAGE_DONE, NULL); +   +    job = (ScanJob *) scanner->priv->job_queue->data; + +    /* If multi-pass then scan another page */ +    if (!scanner->priv->parameters.last_frame) { +        scanner->priv->pass_number++; +        scanner->priv->state = STATE_START; +        return; +    } + +    /* Go back for another page */ +    if (job->type != SCAN_SINGLE) { +        scanner->priv->page_number++; +        scanner->priv->pass_number = 0; +        emit_signal (scanner, PAGE_DONE, NULL); +        scanner->priv->state = STATE_START; +        return; +    } + +    sane_cancel (scanner->priv->handle); +    g_debug ("sane_cancel ()"); + +    do_complete_document (scanner); +} + + +static void +do_read (Scanner *scanner) +{ +    SANE_Status status; +    SANE_Int n_to_read, n_read; +    gboolean full_read = FALSE; + +    /* Read as many bytes as we expect */ +    n_to_read = scanner->priv->buffer_size - scanner->priv->n_used; + +    status = sane_read (scanner->priv->handle, +                        scanner->priv->buffer + scanner->priv->n_used, +                        n_to_read, &n_read); +    g_debug ("sane_read (%d) -> (%s, %d)", n_to_read, get_status_string (status), n_read); + +    /* End of variable length frame */ +    if (status == SANE_STATUS_EOF && +        scanner->priv->parameters.lines == -1 && +        scanner->priv->bytes_remaining == scanner->priv->parameters.bytes_per_line) { +        do_complete_page (scanner); +        return; +    } +             +    /* Communication error */ +    if (status != SANE_STATUS_GOOD) { +        g_warning ("Unable to read frame from device: %s", sane_strstatus (status)); +        fail_scan (scanner, status, +                   /* Error displayed when communication with scanner broken */ +                   _("Error communicating with scanner")); +        return; +    } + +    if (scanner->priv->n_used == 0 && n_read == scanner->priv->buffer_size) +        full_read = TRUE; +    scanner->priv->n_used += n_read; +   +    /* Feed out lines */ +    if (scanner->priv->n_used >= scanner->priv->parameters.bytes_per_line) { +        ScanLine *line; + +        line = g_malloc(sizeof(ScanLine)); +        switch (scanner->priv->parameters.format) { +        case SANE_FRAME_GRAY: +            line->format = LINE_GRAY; +            break; +        case SANE_FRAME_RGB: +            line->format = LINE_RGB; +            break; +        case SANE_FRAME_RED: +            line->format = LINE_RED; +            break; +        case SANE_FRAME_GREEN: +            line->format = LINE_GREEN; +            break; +        case SANE_FRAME_BLUE: +            line->format = LINE_BLUE; +            break; +        } +        line->width = scanner->priv->parameters.pixels_per_line; +        line->depth = scanner->priv->parameters.depth; +        line->data = scanner->priv->buffer; +        line->data_length = scanner->priv->parameters.bytes_per_line; +        line->number = scanner->priv->line_count; +        line->n_lines = scanner->priv->n_used / line->data_length; + +        scanner->priv->buffer = NULL; +        scanner->priv->line_count += line->n_lines; + +        /* On last line */ +        if (scanner->priv->parameters.lines > 0 && scanner->priv->line_count >= scanner->priv->parameters.lines) { +            emit_signal (scanner, GOT_LINE, line); +            do_complete_page (scanner); +        } +        else { +            int i, n_remaining; + +            /* Increase buffer size if did full read */ +            if (full_read) +                scanner->priv->buffer_size += scanner->priv->parameters.bytes_per_line; + +            scanner->priv->buffer = g_malloc(sizeof(SANE_Byte) * scanner->priv->buffer_size); +            n_remaining = scanner->priv->n_used - (line->n_lines * line->data_length); +            scanner->priv->n_used = 0; +            for (i = 0; i < n_remaining; i++) { +                scanner->priv->buffer[i] = line->data[i + (line->n_lines * line->data_length)]; +                scanner->priv->n_used++;                 +            } +            emit_signal (scanner, GOT_LINE, line); +        } +    } +} + + +static gpointer +scan_thread (Scanner *scanner) +{ +    SANE_Status status; +    SANE_Int version_code; +   +    g_hash_table_insert (scanners, g_thread_self (), scanner); +   +    scanner->priv->state = STATE_IDLE; + +    status = sane_init (&version_code, authorization_cb); +    g_debug ("sane_init () -> %s", get_status_string (status)); +    if (status != SANE_STATUS_GOOD) { +        g_warning ("Unable to initialize SANE backend: %s", sane_strstatus(status)); +        return FALSE; +    } +    g_debug ("SANE version %d.%d.%d", +             SANE_VERSION_MAJOR(version_code), +             SANE_VERSION_MINOR(version_code), +             SANE_VERSION_BUILD(version_code)); + +    /* Scan for devices on first start */ +    scanner_redetect (scanner); + +    while (handle_requests (scanner)) { +        switch (scanner->priv->state) { +        case STATE_IDLE: +             if (scanner->priv->job_queue) { +                 set_scanning (scanner, TRUE); +                 scanner->priv->state = STATE_OPEN; +             } +             break; +        case STATE_REDETECT: +            do_redetect (scanner); +            break; +        case STATE_OPEN: +            do_open (scanner); +            break; +        case STATE_GET_OPTION: +            do_get_option (scanner); +            break;             +        case STATE_START: +            do_start (scanner); +            break; +        case STATE_GET_PARAMETERS: +            do_get_parameters (scanner); +            break; +        case STATE_READ: +            do_read (scanner); +            break; +        } +    } +     +    return NULL; +} + + +Scanner * +scanner_new () +{ +    return g_object_new (SCANNER_TYPE, NULL); +} + + +void +scanner_start (Scanner *scanner) +{ +    GError *error = NULL; +    scanner->priv->thread = g_thread_create ((GThreadFunc) scan_thread, scanner, TRUE, &error); +    if (error) { +        g_critical ("Unable to create thread: %s", error->message); +        g_error_free (error); +    }     +} + + +void +scanner_redetect (Scanner *scanner) +{ +    ScanRequest *request; + +    if (scanner->priv->redetect) +        return; +    scanner->priv->redetect = TRUE; + +    g_debug ("Requesting redetection of scan devices"); + +    request = g_malloc0 (sizeof (ScanRequest)); +    request->type = REQUEST_REDETECT; +    g_async_queue_push (scanner->priv->scan_queue, request); +} + + +gboolean +scanner_is_scanning (Scanner *scanner) +{ +    return scanner->priv->scanning; +} + + +void +scanner_scan (Scanner *scanner, const char *device, ScanOptions *options) +{ +    ScanRequest *request; +    const gchar *type_string; +   +    switch (options->type) { +    case SCAN_SINGLE: +        type_string = "SCAN_SINGLE"; +        break; +    case SCAN_ADF_FRONT: +        type_string = "SCAN_ADF_FRONT"; +        break; +    case SCAN_ADF_BACK: +        type_string = "SCAN_ADF_BACK"; +        break; +    case SCAN_ADF_BOTH: +        type_string = "SCAN_ADF_BOTH"; +        break; +    default: +        g_assert (FALSE); +        return; +    } + +    g_debug ("scanner_scan (\"%s\", %d, %s)", device ? device : "(null)", options->dpi, type_string); +    request = g_malloc0 (sizeof (ScanRequest)); +    request->type = REQUEST_START_SCAN; +    request->job = g_malloc0 (sizeof (ScanJob)); +    request->job->device = g_strdup (device); +    request->job->dpi = options->dpi; +    request->job->scan_mode = options->scan_mode; +    request->job->depth = options->depth; +    request->job->type = options->type; +    request->job->page_width = options->paper_width; +    request->job->page_height = options->paper_height; +    g_async_queue_push (scanner->priv->scan_queue, request); +} + + +void +scanner_cancel (Scanner *scanner) +{ +    ScanRequest *request; +   +    request = g_malloc0 (sizeof (ScanRequest)); +    request->type = REQUEST_CANCEL; +    g_async_queue_push (scanner->priv->scan_queue, request); +} + + +void scanner_free (Scanner *scanner) +{ +    ScanRequest *request; + +    g_debug ("Stopping scan thread"); + +    request = g_malloc0 (sizeof (ScanRequest)); +    request->type = REQUEST_QUIT; +    g_async_queue_push (scanner->priv->scan_queue, request); + +    if (scanner->priv->thread) +        g_thread_join (scanner->priv->thread); + +    g_async_queue_unref (scanner->priv->scan_queue); +    g_object_unref (scanner); + +    sane_exit (); +    g_debug ("sane_exit ()"); +} + + +static void +scanner_class_init (ScannerClass *klass) +{ +    signals[AUTHORIZE] = +        g_signal_new ("authorize", +                      G_TYPE_FROM_CLASS (klass), +                      G_SIGNAL_RUN_LAST, +                      G_STRUCT_OFFSET (ScannerClass, authorize), +                      NULL, NULL, +                      g_cclosure_marshal_VOID__STRING, +                      G_TYPE_NONE, 1, G_TYPE_STRING); +    signals[UPDATE_DEVICES] = +        g_signal_new ("update-devices", +                      G_TYPE_FROM_CLASS (klass), +                      G_SIGNAL_RUN_LAST, +                      G_STRUCT_OFFSET (ScannerClass, update_devices), +                      NULL, NULL, +                      g_cclosure_marshal_VOID__POINTER, +                      G_TYPE_NONE, 1, G_TYPE_POINTER); +    signals[EXPECT_PAGE] = +        g_signal_new ("expect-page", +                      G_TYPE_FROM_CLASS (klass), +                      G_SIGNAL_RUN_LAST, +                      G_STRUCT_OFFSET (ScannerClass, expect_page), +                      NULL, NULL, +                      g_cclosure_marshal_VOID__VOID, +                      G_TYPE_NONE, 0); +    signals[GOT_PAGE_INFO] = +        g_signal_new ("got-page-info", +                      G_TYPE_FROM_CLASS (klass), +                      G_SIGNAL_RUN_LAST, +                      G_STRUCT_OFFSET (ScannerClass, got_page_info), +                      NULL, NULL, +                      g_cclosure_marshal_VOID__POINTER, +                      G_TYPE_NONE, 1, G_TYPE_POINTER); +    signals[GOT_LINE] = +        g_signal_new ("got-line", +                      G_TYPE_FROM_CLASS (klass), +                      G_SIGNAL_RUN_LAST, +                      G_STRUCT_OFFSET (ScannerClass, got_line), +                      NULL, NULL, +                      g_cclosure_marshal_VOID__POINTER, +                      G_TYPE_NONE, 1, G_TYPE_POINTER); +    signals[SCAN_FAILED] = +        g_signal_new ("scan-failed", +                      G_TYPE_FROM_CLASS (klass), +                      G_SIGNAL_RUN_LAST, +                      G_STRUCT_OFFSET (ScannerClass, scan_failed), +                      NULL, NULL, +                      g_cclosure_marshal_VOID__POINTER, +                      G_TYPE_NONE, 1, G_TYPE_POINTER); +    signals[PAGE_DONE] = +        g_signal_new ("page-done", +                      G_TYPE_FROM_CLASS (klass), +                      G_SIGNAL_RUN_LAST, +                      G_STRUCT_OFFSET (ScannerClass, page_done), +                      NULL, NULL, +                      g_cclosure_marshal_VOID__VOID, +                      G_TYPE_NONE, 0); +    signals[DOCUMENT_DONE] = +        g_signal_new ("document-done", +                      G_TYPE_FROM_CLASS (klass), +                      G_SIGNAL_RUN_LAST, +                      G_STRUCT_OFFSET (ScannerClass, document_done), +                      NULL, NULL, +                      g_cclosure_marshal_VOID__VOID, +                      G_TYPE_NONE, 0); +    signals[SCANNING_CHANGED] = +        g_signal_new ("scanning-changed", +                      G_TYPE_FROM_CLASS (klass), +                      G_SIGNAL_RUN_LAST, +                      G_STRUCT_OFFSET (ScannerClass, scanning_changed), +                      NULL, NULL, +                      g_cclosure_marshal_VOID__VOID, +                      G_TYPE_NONE, 0); + +    g_type_class_add_private (klass, sizeof (ScannerPrivate)); + +    scanners = g_hash_table_new (g_direct_hash, g_direct_equal); +} + + +static void +scanner_init (Scanner *scanner) +{ +    scanner->priv = G_TYPE_INSTANCE_GET_PRIVATE (scanner, SCANNER_TYPE, ScannerPrivate); +    scanner->priv->scan_queue = g_async_queue_new (); +    scanner->priv->authorize_queue = g_async_queue_new (); +} diff --git a/src/scanner.h b/src/scanner.h new file mode 100644 index 0000000..5acdc84 --- /dev/null +++ b/src/scanner.h @@ -0,0 +1,134 @@ +/* + * Copyright (C) 2009 Canonical Ltd. + * Author: Robert Ancell <robert.ancell@canonical.com> + *  + * 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 3 of the License, or (at your option) any later + * version. See http://www.gnu.org/copyleft/gpl.html the full text of the + * license. + */ + +#ifndef _SCANNER_H_ +#define _SCANNER_H_ + +#include <glib-object.h> + +G_BEGIN_DECLS + +#define SCANNER_TYPE  (scanner_get_type ()) +#define SCANNER(obj)  (G_TYPE_CHECK_INSTANCE_CAST ((obj), SCANNER_TYPE, Scanner)) + + +typedef struct +{ +    gchar *name, *label; +} ScanDevice; + +typedef struct +{ +    /* Width, height in pixels */ +    gint width, height; + +    /* Bit depth */ +    gint depth; + +    /* Resolution */ +    gdouble dpi; + +    /* The device this page came from */ +    gchar *device; +} ScanPageInfo; + +typedef struct +{ +    /* Line number */ +    gint number; +   +    /* Number of lines in this packet */ +    gint n_lines; + +    /* Width in pixels and format */ +    gint width, depth; +    enum +    { +        LINE_GRAY, +        LINE_RGB, +        LINE_RED, +        LINE_GREEN, +        LINE_BLUE +    } format; +     +    /* Raw line data */ +    guchar *data; +    gsize data_length; +} ScanLine; + +typedef enum +{ +    SCAN_MODE_DEFAULT,     +    SCAN_MODE_COLOR, +    SCAN_MODE_GRAY, +    SCAN_MODE_LINEART +} ScanMode; + +typedef enum +{ +    SCAN_SINGLE, +    SCAN_ADF_FRONT, +    SCAN_ADF_BACK, +    SCAN_ADF_BOTH +} ScanType; + +typedef struct +{ +    gint dpi; +    ScanMode scan_mode; +    gint depth; +    ScanType type; +    gint paper_width, paper_height; +} ScanOptions; + +typedef struct ScannerPrivate ScannerPrivate; + +typedef struct +{ +    GObject         parent_instance; +    ScannerPrivate *priv; +} Scanner; + +typedef struct +{ +    GObjectClass parent_class; + +    void (*update_devices) (Scanner *scanner, GList *devices); +    void (*authorize) (Scanner *scanner, const gchar *resource); +    void (*expect_page) (Scanner *scanner); +    void (*got_page_info) (Scanner *scanner, ScanPageInfo *info); +    void (*got_line) (Scanner *scanner, ScanLine *line); +    void (*scan_failed) (Scanner *scanner, GError *error); +    void (*page_done) (Scanner *scanner); +    void (*document_done) (Scanner *scanner); +    void (*scanning_changed) (Scanner *scanner); +} ScannerClass; + + +GType scanner_get_type (void); + +Scanner *scanner_new (void); + +void scanner_start (Scanner *scanner); + +void scanner_authorize (Scanner *scanner, const gchar *username, const gchar *password); + +void scanner_redetect (Scanner *scanner); + +gboolean scanner_is_scanning (Scanner *scanner); + +void scanner_scan (Scanner *scanner, const char *device, ScanOptions *options); + +void scanner_cancel (Scanner *scanner); + +void scanner_free (Scanner *scanner); + +#endif /* _SCANNER_H_ */ diff --git a/src/simple-scan.c b/src/simple-scan.c new file mode 100644 index 0000000..ab59299 --- /dev/null +++ b/src/simple-scan.c @@ -0,0 +1,633 @@ +/* + * Copyright (C) 2009 Canonical Ltd. + * Author: Robert Ancell <robert.ancell@canonical.com> + *  + * 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 3 of the License, or (at your option) any later + * version. See http://www.gnu.org/copyleft/gpl.html the full text of the + * license. + */ + +#include <stdlib.h> +#include <stdio.h> +#include <glib/gi18n.h> +#include <gtk/gtk.h> +#include <unistd.h> +#include <gudev/gudev.h> +#include <dbus/dbus-glib.h> + +#include <sane/sane.h> // For SANE_STATUS_CANCELLED + +#include "ui.h" +#include "scanner.h" +#include "book.h" + + +static const char *default_device = NULL; + +static GUdevClient *udev_client; + +static SimpleScan *ui; + +static Scanner *scanner; + +static Book *book; + +static GTimer *log_timer; + +static FILE *log_file; + +static gboolean debug = FALSE; + + +static void +update_scan_devices_cb (Scanner *scanner, GList *devices) +{ +    ui_set_scan_devices (ui, devices); +} + + +static void +authorize_cb (Scanner *scanner, const gchar *resource) +{ +    gchar *username = NULL, *password = NULL; +    ui_authorize (ui, resource, &username, &password); +    scanner_authorize (scanner, username, password); +    g_free (username); +    g_free (password); +} + + +static Page * +append_page () +{ +    Page *page; +    Orientation orientation = TOP_TO_BOTTOM; +    gboolean do_crop = FALSE; +    gchar *named_crop = NULL; +    gint width = 100, height = 100, dpi = 100, cx, cy, cw, ch; + +    /* Use current page if not used */ +    page = book_get_page (book, -1); +    if (page && !page_has_data (page)) { +        ui_set_selected_page (ui, page); +        page_start (page); +        return page; +    } +   +    /* Copy info from previous page */ +    if (page) { +        orientation = page_get_orientation (page); +        width = page_get_width (page); +        height = page_get_height (page); +        dpi = page_get_dpi (page); + +        do_crop = page_has_crop (page); +        if (do_crop) { +            named_crop = page_get_named_crop (page); +            page_get_crop (page, &cx, &cy, &cw, &ch); +        } +    } + +    page = book_append_page (book, width, height, dpi, orientation); +    if (do_crop) { +        if (named_crop)  { +            page_set_named_crop (page, named_crop); +            g_free (named_crop); +        } +        else +            page_set_custom_crop (page, cw, ch); +        page_move_crop (page, cx, cy); +    } +    ui_set_selected_page (ui, page); +    page_start (page); +   +    return page; +} + + +static void +scanner_new_page_cb (Scanner *scanner) +{ +    append_page (); +} + + +static gchar * +get_profile_for_device (const gchar *current_device) +{ +    gboolean ret; +    DBusGConnection *connection; +    DBusGProxy *proxy; +    GError *error = NULL; +    GType custom_g_type_string_string; +    GPtrArray *profile_data_array = NULL; +    gchar *device_id = NULL; +    gchar *icc_profile = NULL; + +    /* Connect to the color manager on the session bus */ +    connection = dbus_g_bus_get (DBUS_BUS_SESSION, NULL); +    proxy = dbus_g_proxy_new_for_name (connection, +                                       "org.gnome.ColorManager", +                                       "/org/gnome/ColorManager", +                                       "org.gnome.ColorManager"); + +    /* Get color profile */ +    device_id = g_strdup_printf ("sane:%s", current_device); +    custom_g_type_string_string = dbus_g_type_get_collection ("GPtrArray", +                                                              dbus_g_type_get_struct("GValueArray", +                                                                                     G_TYPE_STRING, +                                                                                     G_TYPE_STRING, +                                                                                     G_TYPE_INVALID)); +    ret = dbus_g_proxy_call (proxy, "GetProfilesForDevice", &error, +                             G_TYPE_STRING, device_id, +                             G_TYPE_STRING, "", +                             G_TYPE_INVALID, +                             custom_g_type_string_string, &profile_data_array, +                             G_TYPE_INVALID); +    g_object_unref (proxy); +    g_free (device_id); +    if (!ret) { +        g_debug ("The request failed: %s", error->message); +        g_error_free (error); +        return NULL; +    } + +    if (profile_data_array->len > 0) { +        GValueArray *gva; +        GValue *gv = NULL; + +        /* Just use the preferred profile filename */ +        gva = (GValueArray *) g_ptr_array_index (profile_data_array, 0); +        gv = g_value_array_get_nth (gva, 1); +        icc_profile = g_value_dup_string (gv); +        g_value_unset (gv); +    } +    else +        g_debug ("There are no ICC profiles for the device sane:%s", current_device); +    g_ptr_array_free (profile_data_array, TRUE); + +    return icc_profile; +} + + +static void +scanner_page_info_cb (Scanner *scanner, ScanPageInfo *info) +{ +    Page *page; + +    g_debug ("Page is %d pixels wide, %d pixels high, %d bits per pixel", +             info->width, info->height, info->depth); + +    /* Add a new page */ +    page = append_page (); +    page_set_scan_area (page, info->width, info->height, info->dpi); + +    /* Get ICC color profile */ +    /* FIXME: The ICC profile could change */ +    /* FIXME: Don't do a D-bus call for each page, cache color profiles */ +    page_set_color_profile (page, get_profile_for_device (info->device)); +} + + +static void +scanner_line_cb (Scanner *scanner, ScanLine *line) +{ +    Page *page; + +    page = book_get_page (book, book_get_n_pages (book) - 1); +    page_parse_scan_line (page, line); +} + + +static void +scanner_page_done_cb (Scanner *scanner) +{ +    Page *page; +    page = book_get_page (book, book_get_n_pages (book) - 1); +    page_finish (page); +} + + +static void +remove_empty_page () +{ +    Page *page; + +    page = book_get_page (book, book_get_n_pages (book) - 1); + +    /* Remove a failed page */ +    if (page_has_data (page)) +        page_finish (page); +    else +        book_delete_page (book, page);  +} + + +static void +scanner_document_done_cb (Scanner *scanner) +{ +    remove_empty_page (); +} + + +static void +scanner_failed_cb (Scanner *scanner, GError *error) +{ +    remove_empty_page (); +    if (!g_error_matches (error, SCANNER_TYPE, SANE_STATUS_CANCELLED)) { +        ui_show_error (ui, +                       /* Title of error dialog when scan failed */ +                       _("Failed to scan"), +                       error->message, +                       TRUE); +    } +} + + +static void +scanner_scanning_changed_cb (Scanner *scanner) +{ +    ui_set_scanning (ui, scanner_is_scanning (scanner)); +} + + +static void +scan_cb (SimpleScan *ui, const gchar *device, ScanOptions *options) +{ +    /* Default filename to use when saving document (and extension will be added, e.g. .jpg) */ +    const gchar *filename_prefix = _("Scanned Document"); +    const gchar *extension; +    gchar *filename; + +    if (options->scan_mode == SCAN_MODE_COLOR) +        extension = "jpg"; +    else +        extension = "pdf"; + +    g_debug ("Requesting scan at %d dpi from device '%s'", options->dpi, device); + +    if (!scanner_is_scanning (scanner)) +        append_page (); + +    filename = g_strdup_printf ("%s.%s", filename_prefix, extension); +    ui_set_default_file_name (ui, filename); +    g_free (filename); +    scanner_scan (scanner, device, options); +} + + +static void +cancel_cb (SimpleScan *ui) +{ +    scanner_cancel (scanner); +} + + +static gboolean +save_book_by_extension (GFile *file, GError **error) +{ +    gboolean result; +    gchar *uri, *uri_lower; + +    uri = g_file_get_uri (file); +    uri_lower = g_utf8_strdown (uri, -1); +    if (g_str_has_suffix (uri_lower, ".pdf")) +        result = book_save (book, "pdf", file, error); +    else if (g_str_has_suffix (uri_lower, ".ps")) +        result = book_save (book, "ps", file, error); +    else if (g_str_has_suffix (uri_lower, ".png")) +        result = book_save (book, "png", file, error); +    else if (g_str_has_suffix (uri_lower, ".tif") || g_str_has_suffix (uri_lower, ".tiff")) +        result = book_save (book, "tiff", file, error); +    else +        result = book_save (book, "jpeg", file, error); + +    g_free (uri); +    g_free (uri_lower); + +    return result; +} + + +static void +save_cb (SimpleScan *ui, const gchar *uri) +{ +    GError *error = NULL; +    GFile *file; + +    g_debug ("Saving to '%s'", uri); + +    file = g_file_new_for_uri (uri); +    if (!save_book_by_extension (file, &error)) { +        g_warning ("Error saving file: %s", error->message); +        ui_show_error (ui, +                       /* Title of error dialog when save failed */ +                       _("Failed to save file"), +                       error->message, +               FALSE); +        g_error_free (error); +    } +    g_object_unref (file); +} + + +static gchar * +get_temporary_filename (const gchar *prefix, const gchar *extension) +{ +    gint fd; +    gchar *filename, *path; +    GError *error = NULL; + +    /* NOTE: I'm not sure if this is a 100% safe strategy to use g_file_open_tmp(), close and +     * use the filename but it appears to work in practise */ + +    filename = g_strdup_printf ("%s-XXXXXX.%s", prefix, extension); +    fd = g_file_open_tmp (filename, &path, &error); +    g_free (filename); +    if (fd < 0) { +        g_warning ("Error saving email attachment: %s", error->message); +        g_clear_error (&error); +        return NULL; +    } +    close (fd); + +    return path; +} + + +static void +email_cb (SimpleScan *ui, const gchar *profile) +{ +    gboolean saved = FALSE; +    GError *error = NULL; +    GString *command_line; +   +    command_line = g_string_new ("xdg-email"); + +    /* Save text files as PDFs */ +    if (strcmp (profile, "text") == 0) { +        gchar *path; + +        /* Open a temporary file */ +        path = get_temporary_filename ("scanned-document", "pdf"); +        if (path) { +            GFile *file; + +            file = g_file_new_for_path (path); +            saved = book_save (book, "pdf", file, &error); +            g_string_append_printf (command_line, " --attach %s", path); +            g_free (path); +            g_object_unref (file); +        } +    } +    else { +        gint i; + +        for (i = 0; i < book_get_n_pages (book); i++) { +            gchar *path; +            GFile *file; + +            path = get_temporary_filename ("scanned-document", "jpg"); +            if (!path) { +                saved = FALSE; +                break; +            } + +            file = g_file_new_for_path (path); +            saved = page_save (book_get_page (book, i), "jpeg", file, &error); +            g_string_append_printf (command_line, " --attach %s", path); +            g_free (path); +            g_object_unref (file); +           +            if (!saved) +                break; +        } +    } + +    if (saved) { +        g_debug ("Launchind email client: %s", command_line->str); +        g_spawn_command_line_async (command_line->str, &error); + +        if (error) { +            g_warning ("Unable to start email: %s", error->message); +            g_clear_error (&error); +        } +    } +    else { +        g_warning ("Unable to save email file: %s", error->message); +        g_clear_error (&error); +    } + +    g_string_free (command_line, TRUE); +} + + +static void +quit_cb (SimpleScan *ui) +{ +    g_object_unref (book); +    g_object_unref (ui); +    g_object_unref (udev_client); +    scanner_free (scanner); +    gtk_main_quit (); +} + + +static void +version() +{ +    /* NOTE: Is not translated so can be easily parsed */ +    fprintf(stderr, "%1$s %2$s\n", SIMPLE_SCAN_BINARY, VERSION); +} + + +static void +usage(int show_gtk) +{ +    fprintf(stderr, +            /* Description on how to use simple-scan displayed on command-line */ +            _("Usage:\n" +              "  %s [DEVICE...] - Scanning utility"), SIMPLE_SCAN_BINARY); + +    fprintf(stderr, +            "\n\n"); + +    fprintf(stderr, +            /* Description on how to use simple-scan displayed on command-line */     +            _("Help Options:\n" +              "  -d, --debug                     Print debugging messages\n" +              "  -v, --version                   Show release version\n" +              "  -h, --help                      Show help options\n" +              "  --help-all                      Show all help options\n" +              "  --help-gtk                      Show GTK+ options")); +    fprintf(stderr, +            "\n\n"); + +    if (show_gtk) { +        fprintf(stderr, +                /* Description on simple-scan command-line GTK+ options displayed on command-line */ +                _("GTK+ Options:\n" +                  "  --class=CLASS                   Program class as used by the window manager\n" +                  "  --name=NAME                     Program name as used by the window manager\n" +                  "  --screen=SCREEN                 X screen to use\n" +                  "  --sync                          Make X calls synchronous\n" +                  "  --gtk-module=MODULES            Load additional GTK+ modules\n" +                  "  --g-fatal-warnings              Make all warnings fatal")); +        fprintf(stderr, +                "\n\n"); +    } +} + + +static void +log_cb (const gchar *log_domain, GLogLevelFlags log_level, +        const gchar *message, gpointer data) +{ +    /* Log everything to a file */ +    if (log_file) { +        const gchar *prefix; + +        switch (log_level & G_LOG_LEVEL_MASK) { +        case G_LOG_LEVEL_ERROR: +            prefix = "ERROR:"; +            break; +        case G_LOG_LEVEL_CRITICAL: +            prefix = "CRITICAL:"; +            break; +        case G_LOG_LEVEL_WARNING: +            prefix = "WARNING:"; +            break; +        case G_LOG_LEVEL_MESSAGE: +            prefix = "MESSAGE:"; +            break; +        case G_LOG_LEVEL_INFO: +            prefix = "INFO:"; +            break; +        case G_LOG_LEVEL_DEBUG: +            prefix = "DEBUG:"; +            break; +        default: +            prefix = "LOG:"; +            break; +        } + +        fprintf (log_file, "[%+.2fs] %s %s\n", g_timer_elapsed (log_timer, NULL), prefix, message); +    } + +    /* Only show debug if requested */ +    if (log_level & G_LOG_LEVEL_DEBUG) { +        if (debug) +            g_log_default_handler (log_domain, log_level, message, data); +    } +    else +        g_log_default_handler (log_domain, log_level, message, data);     +} + + +static void +get_options (int argc, char **argv) +{ +    int i; + +    for (i = 1; i < argc; i++) { +        char *arg = argv[i]; + +        if (strcmp (arg, "-d") == 0 || +            strcmp (arg, "--debug") == 0) { +            debug = TRUE; +        } +        else if (strcmp (arg, "-v") == 0 || +            strcmp (arg, "--version") == 0) { +            version (); +            exit (0); +        } +        else if (strcmp (arg, "-h") == 0 || +                 strcmp (arg, "--help") == 0) { +            usage (FALSE); +            exit (0); +        } +        else if (strcmp (arg, "--help-all") == 0 || +                 strcmp (arg, "--help-gtk") == 0) { +            usage (TRUE); +            exit (0); +        } +        else { +            if (default_device) { +                fprintf (stderr, "Unknown argument: '%s'\n", arg); +                exit (1); +            } +            default_device = arg; +        } +    }    +} + + +static void +on_uevent (GUdevClient *client, const gchar *action, GUdevDevice *device) +{ +    scanner_redetect (scanner); +} + + +int +main (int argc, char **argv) +{ +    const char *udev_subsystems[] = { "usb", NULL }; +    gchar *path; + +    g_thread_init (NULL); + +    /* Log to a file */ +    log_timer = g_timer_new (); +    path = g_build_filename (g_get_user_cache_dir (), "simple-scan", NULL); +    g_mkdir_with_parents (path, 0700); +    g_free (path); +    path = g_build_filename (g_get_user_cache_dir (), "simple-scan", "simple-scan.log", NULL); +    log_file = fopen (path, "w"); +    g_free (path); +    g_log_set_default_handler (log_cb, NULL); + +    bindtextdomain (GETTEXT_PACKAGE, LOCALE_DIR); +    bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8"); +    textdomain (GETTEXT_PACKAGE); +    +    gtk_init (&argc, &argv); + +    get_options (argc, argv); +   +    g_debug ("Starting Simple Scan %s, PID=%i", VERSION, getpid ()); +   +    ui = ui_new (); +    book = ui_get_book (ui); +    g_signal_connect (ui, "start-scan", G_CALLBACK (scan_cb), NULL); +    g_signal_connect (ui, "stop-scan", G_CALLBACK (cancel_cb), NULL); +    g_signal_connect (ui, "save", G_CALLBACK (save_cb), NULL); +    g_signal_connect (ui, "email", G_CALLBACK (email_cb), NULL); +    g_signal_connect (ui, "quit", G_CALLBACK (quit_cb), NULL); + +    scanner = scanner_new (); +    g_signal_connect (G_OBJECT (scanner), "update-devices", G_CALLBACK (update_scan_devices_cb), NULL); +    g_signal_connect (G_OBJECT (scanner), "authorize", G_CALLBACK (authorize_cb), NULL); +    g_signal_connect (G_OBJECT (scanner), "expect-page", G_CALLBACK (scanner_new_page_cb), NULL); +    g_signal_connect (G_OBJECT (scanner), "got-page-info", G_CALLBACK (scanner_page_info_cb), NULL); +    g_signal_connect (G_OBJECT (scanner), "got-line", G_CALLBACK (scanner_line_cb), NULL); +    g_signal_connect (G_OBJECT (scanner), "page-done", G_CALLBACK (scanner_page_done_cb), NULL); +    g_signal_connect (G_OBJECT (scanner), "document-done", G_CALLBACK (scanner_document_done_cb), NULL); +    g_signal_connect (G_OBJECT (scanner), "scan-failed", G_CALLBACK (scanner_failed_cb), NULL); +    g_signal_connect (G_OBJECT (scanner), "scanning-changed", G_CALLBACK (scanner_scanning_changed_cb), NULL); + +    udev_client = g_udev_client_new (udev_subsystems); +    g_signal_connect (udev_client, "uevent", G_CALLBACK (on_uevent), NULL); + +    if (default_device) +        ui_set_selected_device (ui, default_device); + +    ui_start (ui); +    scanner_start (scanner); + +    gtk_main (); + +    return 0; +} diff --git a/src/ui.c b/src/ui.c new file mode 100644 index 0000000..92e83c7 --- /dev/null +++ b/src/ui.c @@ -0,0 +1,1648 @@ +/* + * Copyright (C) 2009 Canonical Ltd. + * Author: Robert Ancell <robert.ancell@canonical.com> + *  + * 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 3 of the License, or (at your option) any later + * version. See http://www.gnu.org/copyleft/gpl.html the full text of the + * license. + */ + +#include <stdlib.h> +#include <string.h> +#include <glib/gi18n.h> +#include <gtk/gtk.h> +#include <gconf/gconf-client.h> +#include <math.h> +#include <unistd.h> // TEMP: Needed for close() in get_temporary_filename() + +#include "ui.h" +#include "book-view.h" + + +#define DEFAULT_TEXT_DPI 150 +#define DEFAULT_PHOTO_DPI 300 + + +enum { +    START_SCAN, +    STOP_SCAN, +    SAVE, +    EMAIL, +    QUIT, +    LAST_SIGNAL +}; +static guint signals[LAST_SIGNAL] = { 0, }; + + +struct SimpleScanPrivate +{ +    GConfClient *client; + +    GtkBuilder *builder; + +    GtkWidget *window; +    GtkWidget *preview_box, *preview_area, *preview_scroll; +    GtkWidget *page_delete_menuitem, *crop_rotate_menuitem; +    GtkWidget *stop_menuitem, *stop_toolbutton; + +    GtkWidget *text_toolbar_menuitem, *text_menu_menuitem; +    GtkWidget *photo_toolbar_menuitem, *photo_menu_menuitem; + +    GtkWidget *authorize_dialog; +    GtkWidget *authorize_label; +    GtkWidget *username_entry, *password_entry; + +    GtkWidget *preferences_dialog; +    GtkWidget *device_combo, *text_dpi_combo, *photo_dpi_combo, *page_side_combo, *paper_size_combo; +    GtkTreeModel *device_model, *text_dpi_model, *photo_dpi_model, *page_side_model, *paper_size_model; +    gboolean setting_devices, user_selected_device; + +    Book *book; +    BookView *book_view; +    gboolean updating_page_menu; +    gint default_page_width, default_page_height, default_page_dpi; +    Orientation default_page_orientation; +   +    gboolean have_device_list; + +    gchar *document_hint; + +    gchar *default_file_name; +    gboolean scanning; + +    gint window_width, window_height; +    gboolean window_is_maximized; +}; + +G_DEFINE_TYPE (SimpleScan, ui, G_TYPE_OBJECT); + +static struct +{ +   const gchar *key; +   Orientation orientation; +} orientation_keys[] =  +{ +  { "top-to-bottom", TOP_TO_BOTTOM }, +  { "bottom-to-top", BOTTOM_TO_TOP }, +  { "left-to-right", LEFT_TO_RIGHT }, +  { "right-to-left", RIGHT_TO_LEFT }, +  { NULL, 0 } +}; + + +static gboolean +find_scan_device (SimpleScan *ui, const char *device, GtkTreeIter *iter) +{ +    gboolean have_iter = FALSE; + +    if (gtk_tree_model_get_iter_first (ui->priv->device_model, iter)) { +        do { +            gchar *d; +            gtk_tree_model_get (ui->priv->device_model, iter, 0, &d, -1); +            if (strcmp (d, device) == 0) +                have_iter = TRUE; +            g_free (d); +        } while (!have_iter && gtk_tree_model_iter_next (ui->priv->device_model, iter)); +    } +     +    return have_iter; +} + + +void +ui_set_default_file_name (SimpleScan *ui, const gchar *default_file_name) +{ +    g_free (ui->priv->default_file_name); +    ui->priv->default_file_name = g_strdup (default_file_name); +} + + +void +ui_authorize (SimpleScan *ui, const gchar *resource, gchar **username, gchar **password) +{ +    GString *description; + +    description = g_string_new (""); +    g_string_printf (description, +                     /* Label in authorization dialog.  '%s' is replaced with the name of the resource requesting authorization */ +                     _("Username and password required to access '%s'"), +                     resource); + +    gtk_entry_set_text (GTK_ENTRY (ui->priv->username_entry), *username ? *username : ""); +    gtk_entry_set_text (GTK_ENTRY (ui->priv->password_entry), ""); +    gtk_label_set_text (GTK_LABEL (ui->priv->authorize_label), description->str); +    g_string_free (description, TRUE); + +    gtk_widget_show (ui->priv->authorize_dialog); +    gtk_dialog_run (GTK_DIALOG (ui->priv->authorize_dialog)); +    gtk_widget_hide (ui->priv->authorize_dialog); + +    *username = g_strdup (gtk_entry_get_text (GTK_ENTRY (ui->priv->username_entry))); +    *password = g_strdup (gtk_entry_get_text (GTK_ENTRY (ui->priv->password_entry))); +} + + +void device_combo_changed_cb (GtkWidget *widget, SimpleScan *ui); +G_MODULE_EXPORT +void +device_combo_changed_cb (GtkWidget *widget, SimpleScan *ui) +{ +    if (ui->priv->setting_devices) +        return; +    ui->priv->user_selected_device = TRUE; +} + + +void +ui_set_scan_devices (SimpleScan *ui, GList *devices) +{ +    GList *d; +    gboolean have_selection = FALSE; +    gint index; +    GtkTreeIter iter; +   +    ui->priv->setting_devices = TRUE; +  +    /* If the user hasn't chosen a scanner choose the best available one */ +    if (ui->priv->user_selected_device) +        have_selection = gtk_combo_box_get_active (GTK_COMBO_BOX (ui->priv->device_combo)) >= 0; + +    /* Add new devices */ +    index = 0; +    for (d = devices; d; d = d->next) { +        ScanDevice *device = (ScanDevice *) d->data; +        gint n_delete = -1; + +        /* Find if already exists */ +        if (gtk_tree_model_iter_nth_child (ui->priv->device_model, &iter, NULL, index)) { +            gint i = 0; +            do { +                gchar *name; +                gboolean matched; + +                gtk_tree_model_get (ui->priv->device_model, &iter, 0, &name, -1); +                matched = strcmp (name, device->name) == 0; +                g_free (name); + +                if (matched) { +                    n_delete = i; +                    break; +                } +                i++; +            } while (gtk_tree_model_iter_next (ui->priv->device_model, &iter)); +        } +       +        /* If exists, remove elements up to this one */ +        if (n_delete >= 0) { +            gint i; + +            for (i = 0; i < n_delete; i++) { +                gtk_tree_model_iter_nth_child (ui->priv->device_model, &iter, NULL, index); +                gtk_list_store_remove (GTK_LIST_STORE (ui->priv->device_model), &iter); +            } +        } +        else { +            gtk_list_store_insert (GTK_LIST_STORE (ui->priv->device_model), &iter, index); +            gtk_list_store_set (GTK_LIST_STORE (ui->priv->device_model), &iter, 0, device->name, 1, device->label, -1); +        } +        index++; +    } + +    /* Remove any remaining devices */ +    while (gtk_tree_model_iter_nth_child (ui->priv->device_model, &iter, NULL, index)) +        gtk_list_store_remove (GTK_LIST_STORE (ui->priv->device_model), &iter); + +    /* Select the first available device */ +    if (!have_selection && devices != NULL) +        gtk_combo_box_set_active (GTK_COMBO_BOX (ui->priv->device_combo), 0); + +    ui->priv->setting_devices = FALSE; + +    if (!ui->priv->have_device_list) { +        ui->priv->have_device_list = TRUE; + +        if (!devices) { +            ui_show_error (ui, +                           /* Warning displayed when no scanners are detected */ +                           _("No scanners detected"), +                           /* Hint to user on why there are no scanners detected */ +                           _("Please check your scanner is connected and powered on"), +                           FALSE); +        } +    } +} + + +static gchar * +get_selected_device (SimpleScan *ui) +{ +    GtkTreeIter iter; + +    if (gtk_combo_box_get_active_iter (GTK_COMBO_BOX (ui->priv->device_combo), &iter)) { +        gchar *device; +        gtk_tree_model_get (ui->priv->device_model, &iter, 0, &device, -1); +        return device; +    } + +    return NULL; +} + + +void +ui_set_selected_device (SimpleScan *ui, const gchar *device) +{ +    GtkTreeIter iter; + +    /* If doesn't exist add with label set to device name */ +    if (!find_scan_device (ui, device, &iter)) { +        gtk_list_store_append (GTK_LIST_STORE (ui->priv->device_model), &iter); +        gtk_list_store_set (GTK_LIST_STORE (ui->priv->device_model), &iter, 0, device, 1, device, -1); +    } + +    gtk_combo_box_set_active_iter (GTK_COMBO_BOX (ui->priv->device_combo), &iter); +} + + +static void +add_default_page (SimpleScan *ui) +{ +    Page *page; + +    page = book_append_page (ui->priv->book, +                             ui->priv->default_page_width, +                             ui->priv->default_page_height, +                             ui->priv->default_page_dpi, +                             ui->priv->default_page_orientation); +    book_view_select_page (ui->priv->book_view, page); +} + + +void new_button_clicked_cb (GtkWidget *widget, SimpleScan *ui); +G_MODULE_EXPORT +void +new_button_clicked_cb (GtkWidget *widget, SimpleScan *ui) +{ +    book_clear (ui->priv->book); +    add_default_page (ui); +} + + +static void +set_document_hint (SimpleScan *ui, const gchar *document_hint) +{ +    g_free (ui->priv->document_hint); +    ui->priv->document_hint = g_strdup (document_hint); + +    if (strcmp (document_hint, "text") == 0) { +        gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (ui->priv->text_toolbar_menuitem), TRUE); +        gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (ui->priv->text_menu_menuitem), TRUE); +    } +    else if (strcmp (document_hint, "photo") == 0) { +        gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (ui->priv->photo_toolbar_menuitem), TRUE); +        gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (ui->priv->photo_menu_menuitem), TRUE); +    } +} + + +void text_menuitem_toggled_cb (GtkWidget *widget, SimpleScan *ui); +G_MODULE_EXPORT +void +text_menuitem_toggled_cb (GtkWidget *widget, SimpleScan *ui) +{ +    if (gtk_check_menu_item_get_active (GTK_CHECK_MENU_ITEM (widget))) +        set_document_hint (ui, "text"); +} + + +void photo_menuitem_toggled_cb (GtkWidget *widget, SimpleScan *ui); +G_MODULE_EXPORT +void +photo_menuitem_toggled_cb (GtkWidget *widget, SimpleScan *ui) +{ +    if (gtk_check_menu_item_get_active (GTK_CHECK_MENU_ITEM (widget))) +        set_document_hint (ui, "photo"); +} + + +static void +set_page_side (SimpleScan *ui, const gchar *document_hint) +{ +    GtkTreeIter iter; + +    if (gtk_tree_model_get_iter_first (ui->priv->page_side_model, &iter)) { +        do { +            gchar *d; +            gboolean have_match; + +            gtk_tree_model_get (ui->priv->page_side_model, &iter, 0, &d, -1); +            have_match = strcmp (d, document_hint) == 0; +            g_free (d); + +            if (have_match) { +                gtk_combo_box_set_active_iter (GTK_COMBO_BOX (ui->priv->page_side_combo), &iter);                 +                return; +            } +        } while (gtk_tree_model_iter_next (ui->priv->page_side_model, &iter)); +     } +} + + +static void +set_paper_size (SimpleScan *ui, gint width, gint height) +{ +    GtkTreeIter iter; +    gboolean have_iter; +   +    for (have_iter = gtk_tree_model_get_iter_first (ui->priv->paper_size_model, &iter); +         have_iter; +         have_iter = gtk_tree_model_iter_next (ui->priv->paper_size_model, &iter)) { +        gint w, h; + +        gtk_tree_model_get (ui->priv->paper_size_model, &iter, 0, &w, 1, &h, -1); +        if (w == width && h == height) +            break; +    } +   +    if (!have_iter) +        have_iter = gtk_tree_model_get_iter_first (ui->priv->paper_size_model, &iter); +    if (have_iter) +        gtk_combo_box_set_active_iter (GTK_COMBO_BOX (ui->priv->paper_size_combo), &iter); +} + + +static gint +get_text_dpi (SimpleScan *ui) +{ +    GtkTreeIter iter; +    gint dpi = DEFAULT_TEXT_DPI; + +    if (gtk_combo_box_get_active_iter (GTK_COMBO_BOX (ui->priv->text_dpi_combo), &iter)) +        gtk_tree_model_get (ui->priv->text_dpi_model, &iter, 0, &dpi, -1); +     +    return dpi; +} + + +static gint +get_photo_dpi (SimpleScan *ui) +{ +    GtkTreeIter iter; +    gint dpi = DEFAULT_PHOTO_DPI; + +    if (gtk_combo_box_get_active_iter (GTK_COMBO_BOX (ui->priv->photo_dpi_combo), &iter)) +        gtk_tree_model_get (ui->priv->photo_dpi_model, &iter, 0, &dpi, -1); +     +    return dpi; +} + + +static gchar * +get_page_side (SimpleScan *ui) +{ +    GtkTreeIter iter; +    gchar *mode = NULL; + +    if (gtk_combo_box_get_active_iter (GTK_COMBO_BOX (ui->priv->page_side_combo), &iter)) +        gtk_tree_model_get (ui->priv->page_side_model, &iter, 0, &mode, -1); +     +    return mode; +} + + +static gboolean +get_paper_size (SimpleScan *ui, gint *width, gint *height) +{ +    GtkTreeIter iter; + +    if (gtk_combo_box_get_active_iter (GTK_COMBO_BOX (ui->priv->paper_size_combo), &iter)) { +        gtk_tree_model_get (ui->priv->paper_size_model, &iter, 0, width, 1, height, -1); +        return TRUE; +    } +   +    return FALSE; +} + + +static ScanOptions * +get_scan_options (SimpleScan *ui) +{ +    struct { +        const gchar *name; +        ScanMode mode; +    } profiles[] = +    { +        { "text",  SCAN_MODE_LINEART }, +        { "photo", SCAN_MODE_COLOR   }, +        { NULL,    SCAN_MODE_COLOR   } +    }; +    gint i; +    ScanOptions *options; + +    /* Find this profile */ +    // FIXME: Move this into scan-profile.c +    for (i = 0; profiles[i].name && strcmp (profiles[i].name, ui->priv->document_hint) != 0; i++); +   +    options = g_malloc0 (sizeof (ScanOptions)); +    options->scan_mode = profiles[i].mode; +    options->depth = 8; +    if (options->scan_mode == SCAN_MODE_COLOR) +        options->dpi = get_photo_dpi (ui); +    else +        options->dpi = get_text_dpi (ui); +    get_paper_size (ui, &options->paper_width, &options->paper_height); +   +    return options; +} + + +void scan_button_clicked_cb (GtkWidget *widget, SimpleScan *ui); +G_MODULE_EXPORT +void +scan_button_clicked_cb (GtkWidget *widget, SimpleScan *ui) +{ +    gchar *device; +    ScanOptions *options; + +    device = get_selected_device (ui); + +    options = get_scan_options (ui); +    options->type = SCAN_SINGLE; +    g_signal_emit (G_OBJECT (ui), signals[START_SCAN], 0, device, options); +    g_free (device); +    g_free (options); +} + + +void stop_scan_button_clicked_cb (GtkWidget *widget, SimpleScan *ui); +G_MODULE_EXPORT +void +stop_scan_button_clicked_cb (GtkWidget *widget, SimpleScan *ui) +{ +    g_signal_emit (G_OBJECT (ui), signals[STOP_SCAN], 0); +} + + +void continuous_scan_button_clicked_cb (GtkWidget *widget, SimpleScan *ui); +G_MODULE_EXPORT +void +continuous_scan_button_clicked_cb (GtkWidget *widget, SimpleScan *ui) +{ +    if (ui->priv->scanning) { +        g_signal_emit (G_OBJECT (ui), signals[STOP_SCAN], 0); +    } else { +        gchar *device, *side; +        ScanOptions *options; + +        device = get_selected_device (ui); +        options = get_scan_options (ui); +        side = get_page_side (ui); +        if (strcmp (side, "front") == 0) +            options->type = SCAN_ADF_FRONT; +        else if (strcmp (side, "back") == 0) +            options->type = SCAN_ADF_BACK; +        else +            options->type = SCAN_ADF_BOTH; + +        g_signal_emit (G_OBJECT (ui), signals[START_SCAN], 0, device, options); +        g_free (device); +        g_free (side); +        g_free (options); +    } +} + + +void preferences_button_clicked_cb (GtkWidget *widget, SimpleScan *ui); +G_MODULE_EXPORT +void +preferences_button_clicked_cb (GtkWidget *widget, SimpleScan *ui) +{ +    gtk_window_present (GTK_WINDOW (ui->priv->preferences_dialog)); +} + + +gboolean preferences_dialog_delete_event_cb (GtkWidget *widget, SimpleScan *ui); +G_MODULE_EXPORT +gboolean +preferences_dialog_delete_event_cb (GtkWidget *widget, SimpleScan *ui) +{ +    return TRUE; +} + + +void preferences_dialog_response_cb (GtkWidget *widget, gint response_id, SimpleScan *ui); +G_MODULE_EXPORT +void +preferences_dialog_response_cb (GtkWidget *widget, gint response_id, SimpleScan *ui) +{ +    gtk_widget_hide (ui->priv->preferences_dialog); +} + + +static void +page_selected_cb (BookView *view, Page *page, SimpleScan *ui) +{ +    char *name = NULL; + +    if (page == NULL) +        return; + +    ui->priv->updating_page_menu = TRUE; +     +    if (page_has_crop (page)) { +        char *crop_name; +       +        // FIXME: Make more generic, move into page-size.c and reuse +        crop_name = page_get_named_crop (page); +        if (crop_name) { +            if (strcmp (crop_name, "A4") == 0) +                name = "a4_menuitem"; +            else if (strcmp (crop_name, "A5") == 0) +                name = "a5_menuitem"; +            else if (strcmp (crop_name, "A6") == 0) +                name = "a6_menuitem"; +            else if (strcmp (crop_name, "letter") == 0) +                name = "letter_menuitem"; +            else if (strcmp (crop_name, "legal") == 0) +                name = "legal_menuitem"; +            else if (strcmp (crop_name, "4x6") == 0) +                name = "4x6_menuitem"; +            g_free (crop_name); +        } +        else +            name = "custom_crop_menuitem"; +    } +    else +        name = "no_crop_menuitem"; + +    gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (gtk_builder_get_object (ui->priv->builder, name)), TRUE); +    gtk_toggle_tool_button_set_active (GTK_TOGGLE_TOOL_BUTTON (gtk_builder_get_object (ui->priv->builder, "crop_toolbutton")), page_has_crop (page)); + +    ui->priv->updating_page_menu = FALSE; +} + + +// FIXME: Duplicated from simple-scan.c +static gchar * +get_temporary_filename (const gchar *prefix, const gchar *extension) +{ +    gint fd; +    gchar *filename, *path; +    GError *error = NULL; + +    /* NOTE: I'm not sure if this is a 100% safe strategy to use g_file_open_tmp(), close and +     * use the filename but it appears to work in practise */ + +    filename = g_strdup_printf ("%s-XXXXXX.%s", prefix, extension); +    fd = g_file_open_tmp (filename, &path, &error); +    g_free (filename); +    if (fd < 0) { +        g_warning ("Error saving page for viewing: %s", error->message); +        g_clear_error (&error); +        return NULL; +    } +    close (fd); + +    return path; +} + + +static void +show_page_cb (BookView *view, Page *page, SimpleScan *ui) +{ +    gchar *path; +    GFile *file; +    GdkScreen *screen; +    GError *error = NULL; +   +    path = get_temporary_filename ("scanned-page", "tiff"); +    if (!path) +        return; +    file = g_file_new_for_path (path); +    g_free (path); + +    screen = gtk_widget_get_screen (GTK_WIDGET (ui->priv->window)); + +    if (page_save (page, "tiff", file, &error)) { +        gchar *uri = g_file_get_uri (file); +        gtk_show_uri (screen, uri, gtk_get_current_event_time (), &error); +        g_free (uri); +    } + +    g_object_unref (file); + +    if (error) { +        ui_show_error (ui, +                       /* Error message display when unable to preview image */ +                       _("Unable to open image preview application"), +                       error->message, +                       FALSE); +        g_clear_error (&error); +    } +} + + +void rotate_left_button_clicked_cb (GtkWidget *widget, SimpleScan *ui); +G_MODULE_EXPORT +void +rotate_left_button_clicked_cb (GtkWidget *widget, SimpleScan *ui) +{ +    Page *page; + +    if (ui->priv->updating_page_menu) +        return; +    page = book_view_get_selected (ui->priv->book_view); +    page_rotate_left (page); +} + + +void rotate_right_button_clicked_cb (GtkWidget *widget, SimpleScan *ui); +G_MODULE_EXPORT +void +rotate_right_button_clicked_cb (GtkWidget *widget, SimpleScan *ui) +{ +    Page *page; + +    if (ui->priv->updating_page_menu) +        return; +    page = book_view_get_selected (ui->priv->book_view); +    page_rotate_right (page); +} + + +static void +set_crop (SimpleScan *ui, const gchar *crop_name) +{ +    Page *page; +     +    gtk_widget_set_sensitive (ui->priv->crop_rotate_menuitem, crop_name != NULL); + +    if (ui->priv->updating_page_menu) +        return; +     +    page = book_view_get_selected (ui->priv->book_view); +    if (!page) +        return; +     +    if (!crop_name) { +        page_set_no_crop (page); +        return; +    } + +    if (strcmp (crop_name, "custom") == 0) { +        gint width, height, crop_width, crop_height; + +        width = page_get_width (page); +        height = page_get_height (page); + +        crop_width = (int) (width * 0.8 + 0.5); +        crop_height = (int) (height * 0.8 + 0.5); +        page_set_custom_crop (page, crop_width, crop_height); +        page_move_crop (page, (width - crop_width) / 2, (height - crop_height) / 2); + +        return; +    } + +    page_set_named_crop (page, crop_name); +} + + +void no_crop_menuitem_toggled_cb (GtkWidget *widget, SimpleScan *ui); +G_MODULE_EXPORT +void +no_crop_menuitem_toggled_cb (GtkWidget *widget, SimpleScan *ui) +{ +    if (gtk_check_menu_item_get_active (GTK_CHECK_MENU_ITEM (widget))) +        set_crop (ui, NULL); +} + + +void custom_crop_menuitem_toggled_cb (GtkWidget *widget, SimpleScan *ui); +G_MODULE_EXPORT +void +custom_crop_menuitem_toggled_cb (GtkWidget *widget, SimpleScan *ui) +{ +    if (gtk_check_menu_item_get_active (GTK_CHECK_MENU_ITEM (widget))) +        set_crop (ui, "custom"); +} + +void crop_toolbutton_toggled_cb (GtkWidget *widget, SimpleScan *ui); +G_MODULE_EXPORT +void +crop_toolbutton_toggled_cb (GtkWidget *widget, SimpleScan *ui) +{ +    if (ui->priv->updating_page_menu) +        return; +   +    if (gtk_toggle_tool_button_get_active (GTK_TOGGLE_TOOL_BUTTON (widget))) +        gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (gtk_builder_get_object (ui->priv->builder, "custom_crop_menuitem")), TRUE); +    else +        gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (gtk_builder_get_object (ui->priv->builder, "no_crop_menuitem")), TRUE); +} + + +void four_by_six_menuitem_toggled_cb (GtkWidget *widget, SimpleScan *ui); +G_MODULE_EXPORT +void +four_by_six_menuitem_toggled_cb (GtkWidget *widget, SimpleScan *ui) +{ +    if (gtk_check_menu_item_get_active (GTK_CHECK_MENU_ITEM (widget))) +        set_crop (ui, "4x6"); +} + +                          +void legal_menuitem_toggled_cb (GtkWidget *widget, SimpleScan *ui); +G_MODULE_EXPORT +void +legal_menuitem_toggled_cb (GtkWidget *widget, SimpleScan *ui) +{ +    if (gtk_check_menu_item_get_active (GTK_CHECK_MENU_ITEM (widget))) +        set_crop (ui, "legal"); +} + +                          +void letter_menuitem_toggled_cb (GtkWidget *widget, SimpleScan *ui); +G_MODULE_EXPORT +void +letter_menuitem_toggled_cb (GtkWidget *widget, SimpleScan *ui) +{ +    if (gtk_check_menu_item_get_active (GTK_CHECK_MENU_ITEM (widget))) +        set_crop (ui, "letter"); +} + +                          +void a6_menuitem_toggled_cb (GtkWidget *widget, SimpleScan *ui); +G_MODULE_EXPORT +void +a6_menuitem_toggled_cb (GtkWidget *widget, SimpleScan *ui) +{ +    if (gtk_check_menu_item_get_active (GTK_CHECK_MENU_ITEM (widget))) +        set_crop (ui, "A6"); +} + + +void a5_menuitem_toggled_cb (GtkWidget *widget, SimpleScan *ui); +G_MODULE_EXPORT +void +a5_menuitem_toggled_cb (GtkWidget *widget, SimpleScan *ui) +{ +    if (gtk_check_menu_item_get_active (GTK_CHECK_MENU_ITEM (widget))) +        set_crop (ui, "A5"); +} + +                          +void a4_menuitem_toggled_cb (GtkWidget *widget, SimpleScan *ui); +G_MODULE_EXPORT +void +a4_menuitem_toggled_cb (GtkWidget *widget, SimpleScan *ui) +{ +    if (gtk_check_menu_item_get_active (GTK_CHECK_MENU_ITEM (widget))) +        set_crop (ui, "A4"); +} + + +void crop_rotate_menuitem_activate_cb (GtkWidget *widget, SimpleScan *ui); +G_MODULE_EXPORT +void +crop_rotate_menuitem_activate_cb (GtkWidget *widget, SimpleScan *ui) +{ +    Page *page; + +    page = book_view_get_selected (ui->priv->book_view); +    if (!page) +        return; +     +    page_rotate_crop (page); +} + + +void page_delete_menuitem_activate_cb (GtkWidget *widget, SimpleScan *ui); +G_MODULE_EXPORT +void +page_delete_menuitem_activate_cb (GtkWidget *widget, SimpleScan *ui) +{ +    book_delete_page (book_view_get_book (ui->priv->book_view), +                      book_view_get_selected (ui->priv->book_view)); +} + + +static void +on_file_type_changed (GtkTreeSelection *selection, GtkWidget *dialog) +{ +    GtkTreeModel *model; +    GtkTreeIter iter; +    gchar *path, *filename, *extension, *new_filename; + +    if (!gtk_tree_selection_get_selected (selection, &model, &iter)) +        return; + +    gtk_tree_model_get (model, &iter, 1, &extension, -1); +    path = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (dialog)); +    filename = g_path_get_basename (path); + +    /* Replace extension */ +    if (g_strrstr (filename, ".")) +        new_filename = g_strdup_printf ("%.*s%s", (int)(g_strrstr (filename, ".") - filename), filename, extension); +    else +        new_filename = g_strdup_printf ("%s%s", filename, extension); +    gtk_file_chooser_set_current_name (GTK_FILE_CHOOSER (dialog), new_filename); + +    g_free (path); +    g_free (filename); +    g_free (new_filename); +    g_free (extension); +} + + +void save_file_button_clicked_cb (GtkWidget *widget, SimpleScan *ui); +G_MODULE_EXPORT +void +save_file_button_clicked_cb (GtkWidget *widget, SimpleScan *ui) +{ +    GtkWidget *dialog; +    gint response; +    GtkFileFilter *filter; +    GtkWidget *expander, *file_type_view; +    GtkListStore *file_type_store; +    GtkTreeIter iter; +    GtkTreeViewColumn *column; +    const gchar *extension; +    gchar *directory; +    gint i; + +    struct +    { +        gchar *label, *extension; +    } file_types[] = +    { +        /* Save dialog: Label for saving in PDF format */ +        { _("PDF (multi-page document)"), ".pdf" }, +        /* Save dialog: Label for saving in JPEG format */ +        { _("JPEG (compressed)"), ".jpg" }, +        /* Save dialog: Label for saving in PNG format */ +        { _("PNG (lossless)"), ".png" }, +        { NULL, NULL } +    }; + +    /* Get directory to save to */ +    directory = gconf_client_get_string (ui->priv->client, GCONF_DIR "/save_directory", NULL); +    if (!directory || directory[0] == '\0') { +        g_free (directory); +        directory = g_strdup (g_get_user_special_dir (G_USER_DIRECTORY_DOCUMENTS)); +    } + +    dialog = gtk_file_chooser_dialog_new (/* Save dialog: Dialog title */ +                                          _("Save As..."), +                                          GTK_WINDOW (ui->priv->window), +                                          GTK_FILE_CHOOSER_ACTION_SAVE, +                                          GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, +                                          GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT, +                                          NULL); +    gtk_file_chooser_set_do_overwrite_confirmation (GTK_FILE_CHOOSER (dialog), TRUE); +    gtk_file_chooser_set_local_only (GTK_FILE_CHOOSER (dialog), FALSE); +    gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (dialog), directory); +    gtk_file_chooser_set_current_name (GTK_FILE_CHOOSER (dialog), ui->priv->default_file_name); +    g_free (directory); + +    /* Filter to only show images by default */ +    filter = gtk_file_filter_new (); +    gtk_file_filter_set_name (filter, +                              /* Save dialog: Filter name to show only image files */ +                              _("Image Files")); +    gtk_file_filter_add_pixbuf_formats (filter); +    gtk_file_filter_add_mime_type (filter, "application/pdf"); +    gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (dialog), filter); +    filter = gtk_file_filter_new (); +    gtk_file_filter_set_name (filter, +                              /* Save dialog: Filter name to show all files */ +                              _("All Files")); +    gtk_file_filter_add_pattern (filter, "*"); +    gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (dialog), filter); + +    expander = gtk_expander_new_with_mnemonic (/* */ +                                 _("Select File _Type")); +    gtk_expander_set_spacing (GTK_EXPANDER (expander), 5); +    gtk_file_chooser_set_extra_widget (GTK_FILE_CHOOSER (dialog), expander); +   +    extension = strstr (ui->priv->default_file_name, "."); +    if (!extension) +        extension = ""; + +    file_type_store = gtk_list_store_new (2, G_TYPE_STRING, G_TYPE_STRING); +    for (i = 0; file_types[i].label; i++) { +        gtk_list_store_append (file_type_store, &iter); +        gtk_list_store_set (file_type_store, &iter, 0, file_types[i].label, 1, file_types[i].extension, -1); +    } + +    file_type_view = gtk_tree_view_new_with_model (GTK_TREE_MODEL (file_type_store)); +    gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (file_type_view), FALSE); +    gtk_tree_view_set_rules_hint (GTK_TREE_VIEW (file_type_view), TRUE); +    column = gtk_tree_view_column_new_with_attributes ("", +                                                       gtk_cell_renderer_text_new (), +                                                       "text", 0, NULL); +    gtk_tree_view_append_column (GTK_TREE_VIEW (file_type_view), column); +    gtk_container_add (GTK_CONTAINER (expander), file_type_view); + +    if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (file_type_store), &iter)) { +        do { +            gchar *e; +            gtk_tree_model_get (GTK_TREE_MODEL (file_type_store), &iter, 1, &e, -1); +            if (strcmp (extension, e) == 0) +                gtk_tree_selection_select_iter (gtk_tree_view_get_selection (GTK_TREE_VIEW (file_type_view)), &iter); +            g_free (e); +        } while (gtk_tree_model_iter_next (GTK_TREE_MODEL (file_type_store), &iter)); +    } +    g_signal_connect (gtk_tree_view_get_selection (GTK_TREE_VIEW (file_type_view)), +                      "changed", +                      G_CALLBACK (on_file_type_changed), +                      dialog); + +    gtk_widget_show_all (expander); + +    response = gtk_dialog_run (GTK_DIALOG (dialog)); +    if (response == GTK_RESPONSE_ACCEPT) { +        gchar *uri; + +        uri = gtk_file_chooser_get_uri (GTK_FILE_CHOOSER (dialog)); +        g_signal_emit (G_OBJECT (ui), signals[SAVE], 0, uri); + +        g_free (uri); +    } + +    gconf_client_set_string (ui->priv->client, GCONF_DIR "/save_directory", +                             gtk_file_chooser_get_current_folder (GTK_FILE_CHOOSER (dialog)), +                             NULL); +   +    gtk_widget_destroy (dialog); +} + + +static void +draw_page (GtkPrintOperation *operation, +           GtkPrintContext   *print_context, +           gint               page_number, +           SimpleScan        *ui) +{ +    cairo_t *context; +    Page *page; +    GdkPixbuf *image; +    gboolean is_landscape = FALSE; + +    context = gtk_print_context_get_cairo_context (print_context); +    +    page = book_get_page (ui->priv->book, page_number); + +    /* Rotate to same aspect */ +    if (gtk_print_context_get_width (print_context) > gtk_print_context_get_height (print_context)) +        is_landscape = TRUE; +    if (page_is_landscape (page) != is_landscape) { +        cairo_translate (context, gtk_print_context_get_width (print_context), 0); +        cairo_rotate (context, M_PI_2); +    } +    +    cairo_scale (context, +                 gtk_print_context_get_dpi_x (print_context) / page_get_dpi (page), +                 gtk_print_context_get_dpi_y (print_context) / page_get_dpi (page)); + +    image = page_get_cropped_image (page); +    gdk_cairo_set_source_pixbuf (context, image, 0, 0); +    cairo_paint (context); + +    g_object_unref (image); +} + + +void email_button_clicked_cb (GtkWidget *widget, SimpleScan *ui); +G_MODULE_EXPORT +void +email_button_clicked_cb (GtkWidget *widget, SimpleScan *ui) +{ +    g_signal_emit (G_OBJECT (ui), signals[EMAIL], 0, ui->priv->document_hint); +} + + +void print_button_clicked_cb (GtkWidget *widget, SimpleScan *ui); +G_MODULE_EXPORT +void +print_button_clicked_cb (GtkWidget *widget, SimpleScan *ui) +{ +    GtkPrintOperation *print; +    GtkPrintOperationResult result; +    GError *error = NULL; +    +    print = gtk_print_operation_new (); +    gtk_print_operation_set_n_pages (print, book_get_n_pages (ui->priv->book)); +    g_signal_connect (print, "draw-page", G_CALLBACK (draw_page), ui); + +    result = gtk_print_operation_run (print, GTK_PRINT_OPERATION_ACTION_PRINT_DIALOG, +                                      GTK_WINDOW (ui->priv->window), &error); + +    g_object_unref (print); +} + + +void help_contents_menuitem_activate_cb (GtkWidget *widget, SimpleScan *ui); +G_MODULE_EXPORT +void +help_contents_menuitem_activate_cb (GtkWidget *widget, SimpleScan *ui) +{ +    GdkScreen *screen; +    GError *error = NULL; + +    screen = gtk_widget_get_screen (GTK_WIDGET (ui->priv->window)); +    gtk_show_uri (screen, "ghelp:simple-scan", gtk_get_current_event_time (), &error); + +    if (error) +    { +        ui_show_error (ui, +                       /* Error message displayed when unable to launch help browser */ +                       _("Unable to open help file"), +                       error->message, +                      FALSE); +        g_clear_error (&error); +    } +} + + +void about_menuitem_activate_cb (GtkWidget *widget, SimpleScan *ui); +G_MODULE_EXPORT +void +about_menuitem_activate_cb (GtkWidget *widget, SimpleScan *ui) +{ +    const gchar *authors[] = { "Robert Ancell <robert.ancell@canonical.com>", NULL }; + +    /* The license this software is under (GPL3+) */ +    const char *license = _("This program is free software: you can redistribute it and/or modify\n" +                            "it under the terms of the GNU General Public License as published by\n" +                            "the Free Software Foundation, either version 3 of the License, or\n" +                            "(at your option) any later version.\n" +                            "\n" +                            "This program is distributed in the hope that it will be useful,\n" +                            "but WITHOUT ANY WARRANTY; without even the implied warranty of\n" +                            "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n" +                            "GNU General Public License for more details.\n" +                            "\n" +                            "You should have received a copy of the GNU General Public License\n" +                            "along with this program.  If not, see <http://www.gnu.org/licenses/>."); + +    /* Title of about dialog */ +    const char *title = _("About Simple Scan"); + +    /* Description of program */ +    const char *description = _("Simple document scanning tool"); + +    gtk_show_about_dialog (GTK_WINDOW (ui->priv->window), +                           "title", title, +                           "program-name", "Simple Scan", +                           "version", VERSION, +                           "comments", description, +                           "logo-icon-name", "scanner", +                           "authors", authors, +                           "translator-credits", _("translator-credits"), +                           "website", "https://launchpad.net/simple-scan", +                           "copyright", "Copyright © 2009 Canonical Ltd.", +                           "license", license, +                           "wrap-license", TRUE, +                           NULL); +} + + +static void +quit (SimpleScan *ui) +{ +    char *device; +    gint paper_width = 0, paper_height = 0; +    gint i; + +    // FIXME: Warn if document with unsaved changes + +    device = get_selected_device (ui); +    if (device) { +        gconf_client_set_string(ui->priv->client, GCONF_DIR "/selected_device", device, NULL); +        g_free (device); +    } + +    gconf_client_set_string (ui->priv->client, GCONF_DIR "/document_type", ui->priv->document_hint, NULL); +    gconf_client_set_int (ui->priv->client, GCONF_DIR "/text_dpi", get_text_dpi (ui), NULL); +    gconf_client_set_int (ui->priv->client, GCONF_DIR "/photo_dpi", get_photo_dpi (ui), NULL); +    gconf_client_set_string (ui->priv->client, GCONF_DIR "/page_side", get_page_side (ui), NULL); +    get_paper_size (ui, &paper_width, &paper_height); +    gconf_client_set_int (ui->priv->client, GCONF_DIR "/paper_width", paper_width, NULL); +    gconf_client_set_int (ui->priv->client, GCONF_DIR "/paper_height", paper_height, NULL); + +    gconf_client_set_int(ui->priv->client, GCONF_DIR "/window_width", ui->priv->window_width, NULL); +    gconf_client_set_int(ui->priv->client, GCONF_DIR "/window_height", ui->priv->window_height, NULL); +    gconf_client_set_bool(ui->priv->client, GCONF_DIR "/window_is_maximized", ui->priv->window_is_maximized, NULL); + +    for (i = 0; orientation_keys[i].key != NULL && orientation_keys[i].orientation != ui->priv->default_page_orientation; i++); +    if (orientation_keys[i].key != NULL) +        gconf_client_set_string(ui->priv->client, GCONF_DIR "/scan_direction", orientation_keys[i].key, NULL); +    gconf_client_set_int (ui->priv->client, GCONF_DIR "/page_width", ui->priv->default_page_width, NULL); +    gconf_client_set_int (ui->priv->client, GCONF_DIR "/page_height", ui->priv->default_page_height, NULL); +    gconf_client_set_int (ui->priv->client, GCONF_DIR "/page_dpi", ui->priv->default_page_dpi, NULL); +    +    g_signal_emit (G_OBJECT (ui), signals[QUIT], 0); +} + + +void quit_menuitem_activate_cb (GtkWidget *widget, SimpleScan *ui); +G_MODULE_EXPORT +void +quit_menuitem_activate_cb (GtkWidget *widget, SimpleScan *ui) +{ +    quit (ui); +} + + +gboolean simple_scan_window_configure_event_cb (GtkWidget *widget, GdkEventConfigure *event, SimpleScan *ui); +G_MODULE_EXPORT +gboolean +simple_scan_window_configure_event_cb (GtkWidget *widget, GdkEventConfigure *event, SimpleScan *ui) +{ +    if (!ui->priv->window_is_maximized) { +        ui->priv->window_width = event->width; +        ui->priv->window_height = event->height; +    } + +    return FALSE; +} + + +gboolean simple_scan_window_window_state_event_cb (GtkWidget *widget, GdkEventWindowState *event, SimpleScan *ui); +G_MODULE_EXPORT +gboolean +simple_scan_window_window_state_event_cb (GtkWidget *widget, GdkEventWindowState *event, SimpleScan *ui) +{ +    if (event->changed_mask & GDK_WINDOW_STATE_MAXIMIZED) +        ui->priv->window_is_maximized = (event->new_window_state & GDK_WINDOW_STATE_MAXIMIZED) != 0; +    return FALSE; +} + + +gboolean window_delete_event_cb (GtkWidget *widget, GdkEvent *event, SimpleScan *ui); +G_MODULE_EXPORT +gboolean +window_delete_event_cb (GtkWidget *widget, GdkEvent *event, SimpleScan *ui) +{ +    quit (ui); +    return TRUE; +} + + +static void +page_size_changed_cb (Page *page, SimpleScan *ui) +{ +    ui->priv->default_page_width = page_get_width (page); +    ui->priv->default_page_height = page_get_height (page); +    ui->priv->default_page_dpi = page_get_dpi (page); +} + + +static void +page_orientation_changed_cb (Page *page, SimpleScan *ui) +{ +    ui->priv->default_page_orientation = page_get_orientation (page); +} + + +static void +page_added_cb (Book *book, Page *page, SimpleScan *ui) +{ +    ui->priv->default_page_width = page_get_width (page); +    ui->priv->default_page_height = page_get_height (page); +    ui->priv->default_page_dpi = page_get_dpi (page); +    ui->priv->default_page_orientation = page_get_orientation (page); +    g_signal_connect (page, "size-changed", G_CALLBACK (page_size_changed_cb), ui); +    g_signal_connect (page, "orientation-changed", G_CALLBACK (page_orientation_changed_cb), ui); +} + + +static void +page_removed_cb (Book *book, Page *page, SimpleScan *ui) +{ +    /* If this is the last page add a new blank one */ +    if (book_get_n_pages (ui->priv->book) == 1) +        add_default_page (ui); +} + + +static void +set_dpi_combo (GtkWidget *combo, gint default_dpi, gint current_dpi) +{ +    struct +    { +       gint dpi; +       const gchar *label; +    } scan_resolutions[] = +    { +      /* Preferences dialog: Label for minimum resolution in resolution list */ +      { 75,  _("%d dpi (draft)") }, +      /* Preferences dialog: Label for resolution value in resolution list (dpi = dots per inch) */ +      { 150, _("%d dpi") }, +      { 300, _("%d dpi") }, +      { 600, _("%d dpi") }, +      /* Preferences dialog: Label for maximum resolution in resolution list */       +      { 1200, _("%d dpi (high resolution)") }, +      { 2400, _("%d dpi") }, +      { -1, NULL } +    }; +    GtkCellRenderer *renderer; +    GtkTreeModel *model; +    gint i; + +    renderer = gtk_cell_renderer_text_new(); +    gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (combo), renderer, TRUE); +    gtk_cell_layout_add_attribute (GTK_CELL_LAYOUT (combo), renderer, "text", 1); + +    model = gtk_combo_box_get_model (GTK_COMBO_BOX (combo)); +    for (i = 0; scan_resolutions[i].dpi > 0; i++) +    { +        GtkTreeIter iter; +        gchar *label; +        gint dpi; + +        dpi = scan_resolutions[i].dpi; + +        if (dpi == default_dpi) +            label = g_strdup_printf (/* Preferences dialog: Label for default resolution in resolution list */ +                                     _("%d dpi (default)"), dpi); +        else +            label = g_strdup_printf (scan_resolutions[i].label, dpi); + +        gtk_list_store_append (GTK_LIST_STORE (model), &iter); +        gtk_list_store_set (GTK_LIST_STORE (model), &iter, 0, dpi, 1, label, -1); + +        if (dpi == current_dpi) +            gtk_combo_box_set_active_iter (GTK_COMBO_BOX (combo), &iter); + +        g_free (label); +    } +} + + +static void +ui_load (SimpleScan *ui) +{ +    GtkBuilder *builder; +    GError *error = NULL; +    GtkCellRenderer *renderer; +    gchar *device, *document_type, *scan_direction, *page_side; +    gint dpi, paper_width, paper_height; + +    gtk_icon_theme_append_search_path (gtk_icon_theme_get_default (), ICON_DIR); + +    gtk_window_set_default_icon_name ("scanner"); + +    builder = ui->priv->builder = gtk_builder_new (); +    gtk_builder_add_from_file (builder, UI_DIR "simple-scan.ui", &error); +    if (error) { +        g_critical ("Unable to load UI: %s\n", error->message); +        ui_show_error (ui, +                       /* Title of dialog when cannot load required files */ +                       _("Files missing"), +                       /* Description in dialog when cannot load required files */ +                       _("Please check your installation"), +                       FALSE); +        exit (1); +    } +    gtk_builder_connect_signals (builder, ui); + +    ui->priv->window = GTK_WIDGET (gtk_builder_get_object (builder, "simple_scan_window")); +    ui->priv->preview_box = GTK_WIDGET (gtk_builder_get_object (builder, "preview_vbox")); +    ui->priv->preview_area = GTK_WIDGET (gtk_builder_get_object (builder, "preview_area")); +    ui->priv->preview_scroll = GTK_WIDGET (gtk_builder_get_object (builder, "preview_scrollbar")); +    ui->priv->page_delete_menuitem = GTK_WIDGET (gtk_builder_get_object (builder, "page_delete_menuitem")); +    ui->priv->crop_rotate_menuitem = GTK_WIDGET (gtk_builder_get_object (builder, "crop_rotate_menuitem")); +    ui->priv->stop_menuitem = GTK_WIDGET (gtk_builder_get_object (builder, "stop_scan_menuitem")); +    ui->priv->stop_toolbutton = GTK_WIDGET (gtk_builder_get_object (builder, "stop_toolbutton")); + +    ui->priv->text_toolbar_menuitem = GTK_WIDGET (gtk_builder_get_object (builder, "text_toolbutton_menuitem")); +    ui->priv->text_menu_menuitem = GTK_WIDGET (gtk_builder_get_object (builder, "text_menuitem")); +    ui->priv->photo_toolbar_menuitem = GTK_WIDGET (gtk_builder_get_object (builder, "photo_toolbutton_menuitem")); +    ui->priv->photo_menu_menuitem = GTK_WIDGET (gtk_builder_get_object (builder, "photo_menuitem")); + +    ui->priv->authorize_dialog = GTK_WIDGET (gtk_builder_get_object (builder, "authorize_dialog")); +    ui->priv->authorize_label = GTK_WIDGET (gtk_builder_get_object (builder, "authorize_label")); +    ui->priv->username_entry = GTK_WIDGET (gtk_builder_get_object (builder, "username_entry")); +    ui->priv->password_entry = GTK_WIDGET (gtk_builder_get_object (builder, "password_entry")); +    +    ui->priv->preferences_dialog = GTK_WIDGET (gtk_builder_get_object (builder, "preferences_dialog")); +    ui->priv->device_combo = GTK_WIDGET (gtk_builder_get_object (builder, "device_combo")); +    ui->priv->device_model = gtk_combo_box_get_model (GTK_COMBO_BOX (ui->priv->device_combo)); +    ui->priv->text_dpi_combo = GTK_WIDGET (gtk_builder_get_object (builder, "text_dpi_combo")); +    ui->priv->text_dpi_model = gtk_combo_box_get_model (GTK_COMBO_BOX (ui->priv->text_dpi_combo)); +    ui->priv->photo_dpi_combo = GTK_WIDGET (gtk_builder_get_object (builder, "photo_dpi_combo")); +    ui->priv->photo_dpi_model = gtk_combo_box_get_model (GTK_COMBO_BOX (ui->priv->photo_dpi_combo)); +    ui->priv->page_side_combo = GTK_WIDGET (gtk_builder_get_object (builder, "page_side_combo")); +    ui->priv->page_side_model = gtk_combo_box_get_model (GTK_COMBO_BOX (ui->priv->page_side_combo)); +    ui->priv->paper_size_combo = GTK_WIDGET (gtk_builder_get_object (builder, "paper_size_combo")); +    ui->priv->paper_size_model = gtk_combo_box_get_model (GTK_COMBO_BOX (ui->priv->paper_size_combo)); + +    GtkTreeIter iter; +    gtk_list_store_append (GTK_LIST_STORE (ui->priv->paper_size_model), &iter); +    gtk_list_store_set (GTK_LIST_STORE (ui->priv->paper_size_model), &iter, 0, 0, 1, 0, 2, "Automatic", -1); +    gtk_list_store_append (GTK_LIST_STORE (ui->priv->paper_size_model), &iter); +    gtk_list_store_set (GTK_LIST_STORE (ui->priv->paper_size_model), &iter, 0, 2050, 1, 1480, 2, "A6", -1); +    gtk_list_store_append (GTK_LIST_STORE (ui->priv->paper_size_model), &iter); +    gtk_list_store_set (GTK_LIST_STORE (ui->priv->paper_size_model), &iter, 0, 1480, 1, 2100, 2, "A5", -1); +    gtk_list_store_append (GTK_LIST_STORE (ui->priv->paper_size_model), &iter); +    gtk_list_store_set (GTK_LIST_STORE (ui->priv->paper_size_model), &iter, 0, 2100, 1, 1970, 2, "A4", -1); +    gtk_list_store_append (GTK_LIST_STORE (ui->priv->paper_size_model), &iter); +    gtk_list_store_set (GTK_LIST_STORE (ui->priv->paper_size_model), &iter, 0, 2159, 1, 2794, 2, "Letter", -1); +    gtk_list_store_append (GTK_LIST_STORE (ui->priv->paper_size_model), &iter); +    gtk_list_store_set (GTK_LIST_STORE (ui->priv->paper_size_model), &iter, 0, 2159, 1, 3556, 2, "Legal", -1); +    gtk_list_store_append (GTK_LIST_STORE (ui->priv->paper_size_model), &iter); +    gtk_list_store_set (GTK_LIST_STORE (ui->priv->paper_size_model), &iter, 0, 1016, 1, 1524, 2, "4×6", -1); + +    dpi = gconf_client_get_int (ui->priv->client, GCONF_DIR "/text_dpi", NULL); +    if (dpi <= 0) +        dpi = DEFAULT_TEXT_DPI; +    set_dpi_combo (ui->priv->text_dpi_combo, DEFAULT_TEXT_DPI, dpi); +    dpi = gconf_client_get_int (ui->priv->client, GCONF_DIR "/photo_dpi", NULL); +    if (dpi <= 0) +        dpi = DEFAULT_PHOTO_DPI; +    set_dpi_combo (ui->priv->photo_dpi_combo, DEFAULT_PHOTO_DPI, dpi); + +    renderer = gtk_cell_renderer_text_new(); +    gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (ui->priv->device_combo), renderer, TRUE); +    gtk_cell_layout_add_attribute (GTK_CELL_LAYOUT (ui->priv->device_combo), renderer, "text", 1); + +    renderer = gtk_cell_renderer_text_new(); +    gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (ui->priv->page_side_combo), renderer, TRUE); +    gtk_cell_layout_add_attribute (GTK_CELL_LAYOUT (ui->priv->page_side_combo), renderer, "text", 1); +    page_side = gconf_client_get_string (ui->priv->client, GCONF_DIR "/page_side", NULL); +    if (page_side) { +        set_page_side (ui, page_side); +        g_free (page_side); +    } + +    renderer = gtk_cell_renderer_text_new(); +    gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (ui->priv->paper_size_combo), renderer, TRUE); +    gtk_cell_layout_add_attribute (GTK_CELL_LAYOUT (ui->priv->paper_size_combo), renderer, "text", 2); +    paper_width = gconf_client_get_int (ui->priv->client, GCONF_DIR "/paper_width", NULL); +    paper_height = gconf_client_get_int (ui->priv->client, GCONF_DIR "/paper_height", NULL); +    set_paper_size (ui, paper_width, paper_height); + +    device = gconf_client_get_string (ui->priv->client, GCONF_DIR "/selected_device", NULL); +    if (device) { +        GtkTreeIter iter; +        if (find_scan_device (ui, device, &iter)) +            gtk_combo_box_set_active_iter (GTK_COMBO_BOX (ui->priv->device_combo), &iter); +        g_free (device); +    } + +    document_type = gconf_client_get_string (ui->priv->client, GCONF_DIR "/document_type", NULL); +    if (document_type) { +        set_document_hint (ui, document_type); +        g_free (document_type); +    } + +    ui->priv->book_view = book_view_new (); +    g_signal_connect (ui->priv->book_view, "page-selected", G_CALLBACK (page_selected_cb), ui); +    g_signal_connect (ui->priv->book_view, "show-page", G_CALLBACK (show_page_cb), ui); +    book_view_set_widgets (ui->priv->book_view, +                           ui->priv->preview_box, +                           ui->priv->preview_area, +                           ui->priv->preview_scroll, +                           GTK_WIDGET (gtk_builder_get_object (builder, "page_menu"))); + +    /* Find default page details */ +    scan_direction = gconf_client_get_string(ui->priv->client, GCONF_DIR "/scan_direction", NULL); +    ui->priv->default_page_orientation = TOP_TO_BOTTOM; +    if (scan_direction) { +        gint i; +        for (i = 0; orientation_keys[i].key != NULL && strcmp (orientation_keys[i].key, scan_direction) != 0; i++); +        if (orientation_keys[i].key != NULL) +            ui->priv->default_page_orientation = orientation_keys[i].orientation; +        g_free (scan_direction); +    } +    ui->priv->default_page_width = gconf_client_get_int (ui->priv->client, GCONF_DIR "/page_width", NULL); +    if (ui->priv->default_page_width <= 0) +        ui->priv->default_page_width = 595; +    ui->priv->default_page_height = gconf_client_get_int (ui->priv->client, GCONF_DIR "/page_height", NULL); +    if (ui->priv->default_page_height <= 0) +        ui->priv->default_page_height = 842; +    ui->priv->default_page_dpi = gconf_client_get_int (ui->priv->client, GCONF_DIR "/page_dpi", NULL); +    if (ui->priv->default_page_dpi <= 0) +        ui->priv->default_page_dpi = 72; + +    /* Restore window size */ +    ui->priv->window_width = gconf_client_get_int (ui->priv->client, GCONF_DIR "/window_width", NULL); +    if (ui->priv->window_width <= 0) +        ui->priv->window_width = 600; +    ui->priv->window_height = gconf_client_get_int (ui->priv->client, GCONF_DIR "/window_height", NULL); +    if (ui->priv->window_height <= 0) +        ui->priv->window_height = 400; +    g_debug ("Restoring window to %dx%d pixels", ui->priv->window_width, ui->priv->window_height); +    gtk_window_set_default_size (GTK_WINDOW (ui->priv->window), ui->priv->window_width, ui->priv->window_height); +    ui->priv->window_is_maximized = gconf_client_get_bool (ui->priv->client, GCONF_DIR "/window_is_maximized", NULL); +    if (ui->priv->window_is_maximized) { +        g_debug ("Restoring window to maximized"); +        gtk_window_maximize (GTK_WINDOW (ui->priv->window)); +    } + +    if (book_get_n_pages (ui->priv->book) == 0) +        add_default_page (ui); +    book_view_set_book (ui->priv->book_view, ui->priv->book); +} + + +SimpleScan * +ui_new () +{ +    return g_object_new (SIMPLE_SCAN_TYPE, NULL); +} + + +Book * +ui_get_book (SimpleScan *ui) +{ +    return g_object_ref (ui->priv->book); +} + + +void +ui_set_selected_page (SimpleScan *ui, Page *page) +{ +    book_view_select_page (ui->priv->book_view, page); +} + + +Page * +ui_get_selected_page (SimpleScan *ui) +{ +    return book_view_get_selected (ui->priv->book_view); +} + + +void +ui_set_scanning (SimpleScan *ui, gboolean scanning) +{ +    ui->priv->scanning = scanning; +    gtk_widget_set_sensitive (ui->priv->page_delete_menuitem, !scanning); +    gtk_widget_set_sensitive (ui->priv->stop_menuitem, scanning); +    gtk_widget_set_sensitive (ui->priv->stop_toolbutton, scanning); +} + + +void +ui_show_error (SimpleScan *ui, const gchar *error_title, const gchar *error_text, gboolean change_scanner_hint) +{ +    GtkWidget *dialog; + +    dialog = gtk_message_dialog_new (GTK_WINDOW (ui->priv->window), +                                     GTK_DIALOG_MODAL, +                                     GTK_MESSAGE_WARNING, +                                     GTK_BUTTONS_NONE, +                                     "%s", error_title); +    if (change_scanner_hint) +        gtk_dialog_add_button (GTK_DIALOG (dialog), +                               /* Button in error dialog to open prefereces dialog and change scanner */ +                               _("Change _Scanner"), +                               1); +    gtk_dialog_add_button (GTK_DIALOG (dialog), GTK_STOCK_CLOSE, 0); +    gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog), +                                              "%s", error_text); + +    if (gtk_dialog_run (GTK_DIALOG (dialog)) == 1) { +        gtk_widget_grab_focus (ui->priv->device_combo); +        gtk_window_present (GTK_WINDOW (ui->priv->preferences_dialog));         +    } + +    gtk_widget_destroy (dialog); +} + + +void +ui_start (SimpleScan *ui) +{ +    gtk_widget_show (ui->priv->window); +} + + +/* Generated with glib-genmarshal */ +static void +g_cclosure_user_marshal_VOID__STRING_POINTER (GClosure     *closure, +                                              GValue       *return_value G_GNUC_UNUSED, +                                              guint         n_param_values, +                                              const GValue *param_values, +                                              gpointer      invocation_hint G_GNUC_UNUSED, +                                              gpointer      marshal_data) +{ +  typedef void (*GMarshalFunc_VOID__STRING_POINTER) (gpointer       data1, +                                                     gconstpointer  arg_1, +                                                     gconstpointer  arg_2, +                                                     gpointer       data2); +  register GMarshalFunc_VOID__STRING_POINTER callback; +  register GCClosure *cc = (GCClosure*) closure; +  register gpointer data1, data2; + +  g_return_if_fail (n_param_values == 3); + +  if (G_CCLOSURE_SWAP_DATA (closure)) +    { +      data1 = closure->data; +      data2 = g_value_peek_pointer (param_values + 0); +    } +  else +    { +      data1 = g_value_peek_pointer (param_values + 0); +      data2 = closure->data; +    } +  callback = (GMarshalFunc_VOID__STRING_POINTER) (marshal_data ? marshal_data : cc->callback); + +  callback (data1, +            g_value_get_string (param_values + 1), +            g_value_get_pointer (param_values + 2), +            data2); +} + + +static void +ui_finalize (GObject *object) +{ +    SimpleScan *ui = SIMPLE_SCAN (object); + +    g_object_unref (ui->priv->client); +    ui->priv->client = NULL; +    g_object_unref (ui->priv->builder); +    ui->priv->builder = NULL; +    g_object_unref (ui->priv->book); +    ui->priv->book = NULL; +    g_object_unref (ui->priv->book_view); +    ui->priv->book_view = NULL;     +   +    G_OBJECT_CLASS (ui_parent_class)->finalize (object); +} + + +static void +ui_class_init (SimpleScanClass *klass) +{ +    GObjectClass *object_class = G_OBJECT_CLASS (klass); + +    object_class->finalize = ui_finalize; + +    signals[START_SCAN] = +        g_signal_new ("start-scan", +                      G_TYPE_FROM_CLASS (klass), +                      G_SIGNAL_RUN_LAST, +                      G_STRUCT_OFFSET (SimpleScanClass, start_scan), +                      NULL, NULL, +                      g_cclosure_user_marshal_VOID__STRING_POINTER, +                      G_TYPE_NONE, 2, G_TYPE_STRING, G_TYPE_POINTER); +    signals[STOP_SCAN] = +        g_signal_new ("stop-scan", +                      G_TYPE_FROM_CLASS (klass), +                      G_SIGNAL_RUN_LAST, +                      G_STRUCT_OFFSET (SimpleScanClass, stop_scan), +                      NULL, NULL, +                      g_cclosure_marshal_VOID__VOID, +                      G_TYPE_NONE, 0); +    signals[SAVE] = +        g_signal_new ("save", +                      G_TYPE_FROM_CLASS (klass), +                      G_SIGNAL_RUN_LAST, +                      G_STRUCT_OFFSET (SimpleScanClass, save), +                      NULL, NULL, +                      g_cclosure_marshal_VOID__POINTER, +                      G_TYPE_NONE, 1, G_TYPE_POINTER); +    signals[EMAIL] = +        g_signal_new ("email", +                      G_TYPE_FROM_CLASS (klass), +                      G_SIGNAL_RUN_LAST, +                      G_STRUCT_OFFSET (SimpleScanClass, email), +                      NULL, NULL, +                      g_cclosure_marshal_VOID__STRING, +                      G_TYPE_NONE, 1, G_TYPE_STRING); +    signals[QUIT] = +        g_signal_new ("quit", +                      G_TYPE_FROM_CLASS (klass), +                      G_SIGNAL_RUN_LAST, +                      G_STRUCT_OFFSET (SimpleScanClass, quit), +                      NULL, NULL, +                      g_cclosure_marshal_VOID__VOID, +                      G_TYPE_NONE, 0); + +    g_type_class_add_private (klass, sizeof (SimpleScanPrivate)); +} + + +static void +ui_init (SimpleScan *ui) +{ +    ui->priv = G_TYPE_INSTANCE_GET_PRIVATE (ui, SIMPLE_SCAN_TYPE, SimpleScanPrivate); + +    ui->priv->book = book_new (); +    g_signal_connect (ui->priv->book, "page-removed", G_CALLBACK (page_removed_cb), ui); +    g_signal_connect (ui->priv->book, "page-added", G_CALLBACK (page_added_cb), ui); +    +    ui->priv->client = gconf_client_get_default(); +    gconf_client_add_dir(ui->priv->client, GCONF_DIR, GCONF_CLIENT_PRELOAD_NONE, NULL); + +    ui->priv->document_hint = g_strdup ("photo"); +    ui->priv->default_file_name = g_strdup (_("Scanned Document.pdf")); +    ui->priv->scanning = FALSE; +    ui_load (ui);    +} diff --git a/src/ui.h b/src/ui.h new file mode 100644 index 0000000..41d551f --- /dev/null +++ b/src/ui.h @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2009 Canonical Ltd. + * Author: Robert Ancell <robert.ancell@canonical.com> + *  + * 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 3 of the License, or (at your option) any later + * version. See http://www.gnu.org/copyleft/gpl.html the full text of the + * license. + */ + +#ifndef _UI_H_ +#define _UI_H_ + +#include <glib-object.h> +#include "book.h" +#include "scanner.h" + +G_BEGIN_DECLS + +#define SIMPLE_SCAN_TYPE  (ui_get_type ()) +#define SIMPLE_SCAN(obj)  (G_TYPE_CHECK_INSTANCE_CAST ((obj), SIMPLE_SCAN_TYPE, SimpleScan)) + + +typedef struct SimpleScanPrivate SimpleScanPrivate; + +typedef struct +{ +    GObject             parent_instance; +    SimpleScanPrivate  *priv; +} SimpleScan; + +typedef struct +{ +    GObjectClass parent_class; + +    void (*start_scan) (SimpleScan *ui, ScanOptions *options); +    void (*stop_scan) (SimpleScan *ui); +    void (*save) (SimpleScan *ui, const gchar *format); +    void (*email) (SimpleScan *ui, const gchar *profile); +    void (*quit) (SimpleScan *ui); +} SimpleScanClass; + + +GType ui_get_type (void); + +SimpleScan *ui_new (void); + +Book *ui_get_book (SimpleScan *ui); + +void ui_set_selected_page (SimpleScan *ui, Page *page); + +Page *ui_get_selected_page (SimpleScan *ui); + +void ui_set_default_file_name (SimpleScan *ui, const gchar *default_file_name); + +void ui_authorize (SimpleScan *ui, const gchar *resource, gchar **username, gchar **password); + +void ui_set_scan_devices (SimpleScan *ui, GList *devices); + +gchar *ui_get_selected_device (SimpleScan *ui); + +void ui_set_selected_device (SimpleScan *ui, const gchar *device); + +void ui_set_scanning (SimpleScan *ui, gboolean scanning); + +void ui_show_error (SimpleScan *ui, const gchar *error_title, const gchar *error_text, gboolean change_scanner_hint); + +void ui_start (SimpleScan *ui); + +#endif /* _UI_H_ */ | 
