#!/usr/bin/perl

eval 'exec /usr/bin/perl  -S $0 ${1+"$@"}'
    if 0; # not running under some shell

# Copyright (c) 2006--2007 by Jeff Ratcliffe (ra28145 at users dot sourceforge.net)
# This script is released under the GPL license.  Please
# see the included LICENCE file for details.

# To do
# 0. switch unpaper to using ocr stack
#    fix encoding problem with OCR output
#    move locales to locale directory for build
#    look at adding make clean to release procedure
#    put notes in help about hg
#    set up mailing list on sourceforge
#    delete cvs repository on sourceforge
#    check out freeze when gocr does not return properly
#    for scan profiles, switch to Config::General (libconfig-general-perl)
#    investigate JPEG or PNG compression
#    Add status line at bottom with messages like "filename saved"
#    Rewrite in python with python-sane & pytiff
#    Add KDE, Xfce menus
#    Add hidden flag whether a page has been saved
#    On quit or delete if page not saved put up dialog
#    Put windows up in centre of parent
#    Fix blocking at:
#      Importing (e.g. 12 page multi.tif)
#      Rotating
#    Use  $tree_view->window->set_cursor( Gtk2::Gdk::Cursor->new('watch') );
#      and    $tree_view->window->set_cursor (undef); when working.
#    Option to throw up PDF viewer with newly created PDF file
#    Translate documentation
#    Sort out sourceforge backups
#    Right click save to PDF or TIFF should default to page range "selected"
#    Work out a good place to log error messages
#    Add "translate this application" to help menu like gedit, opening launchpad in the default browswer.
#    check out scanbuttond
#    sort out "dpkg-source: warning: source directory `./gscan2pdf' is not <sourcepackage>-<upstreamversion> `gscan2pdf-0.9.0'" error
#    Switch viewing widget as soon as packages are available for:
#     http://giv.sourceforge.net/gtk-image-viewer/
#    As soon as gtk+ 2.12 is available and released in Feisty+1, remove the
#     EventBox wrappers from the ComboBoxes (check that the tooltips still work!)
#    steal rotate icons from eye of gnome 2.18
#
# From schmolch
# 1.) If the scanning process gets interrupted the automatic numbering screws up.
#      It would be very helpful to be able to tell the number at which it is supposed
#      to continue counting and into which direction (reverse counting stops working
#      after the interruption).
# 2.) Some basic image manipulation capabilities would be very useful like adjusting contrast and brightness. 
#
# 1. Progress meter for scans and imports.
# 2. Crop and autocrop
#
# Release procedure:
# 1. New screendump required? Print screen creates screenshot.png in Desktop.
#    Make sure that file list in .spec and hg status reflects MANIFEST
#    Make sure that changelog reflects History and hg log from bin/gscan2pdf
# 2. make rpmdist debdist
#    test dist sudo dpkg -i gscan2pdf-0....
# 3. hg status
#    hg tag vx-x-x
#    hg push ssh://ra28145@shell.sf.net//home/groups/g/gs/gscan2pdf/hg/gscan2pdf
# 4. make remote-dist remote-html 
# 5. Upload to sourceforge and release files
# 6. Freshmeat
# 7. Launchpad, upload .pot if necessary
# 8. http://www.gtkfiles.org/app.php/gscan2pdf
# 9. Ubuntu forum

use warnings;
use strict;
use Gtk2 -init;
use Gtk2::Ex::Simple::List;
use Gtk2::Gdk::Keysyms;
use Cwd;                             # To obtain current working directory
use File::Basename;                  # Split filename into dir, file, ext
use File::Temp qw(tempfile tempdir); # To create temporary files
use Glib qw(TRUE FALSE);             # To get TRUE and FALSE
use POSIX qw(locale_h);              # To sort out LC_NUMERIC
use Locale::gettext 1.05;            # For translations

my $program = "gscan2pdf";
my $version = "0.9.9";

# Standard paper sizes
my @paperg = qw(A4 Letter);
my @xg = ( 210, 215.9 );
my @yg = ( 297, 279.4 );
my (@paper, @x, @y);
my $tolerance = 1;

# Window parameters
my $border_width = 6;

# Image border to ensure that a scaled to fit image gets no scrollbars
my $border = 1;

# Set up domain for gettext (internationalisation)
# Expects /usr/share/locale/xx_XX/LC_MESSAGES/$program.mo
my $d = Locale::gettext->domain($program);

my @test;
my @device;
my @model;
my $debug = FALSE;
while (defined($ARGV[0])) {
# Set up test mode and make sure file has absolute path and is readable
 if ($ARGV[0] eq "--test") {
  shift @ARGV;
  my $test = shift @ARGV;
  if ($test =~ /(.*)=(.*)/) {
   push @device, $2;
   $test = $1;
   $test = getcwd."/$test" if ($test !~ /^\//);
   if (! -r $test) {
    warn sprintf($d->get("Error: cannot read file: %s\n"), $test);
    exit 1;
   }
   push @test, $test;

# Convert all underscores to spaces
   $test =~ s/_/ /g;
   push @model, basename($test);
  }
  else {
   warn sprintf($d->get("Usage:\n%s --test <file>=<model>\n"), $0); # better xgettext hack
   exit 1;
  }
 }
 elsif ($ARGV[0] eq "--help") {
  system("perldoc $0");
  exit;
 }
 elsif ($ARGV[0] eq "--locale") {
  shift @ARGV;
  if ($ARGV[0] !~ /^\//) {
   $d->dir(getcwd."/".shift @ARGV);
  }
  else {
   $d->dir(shift @ARGV);
  }
 }
 elsif ($ARGV[0] eq "--debug") {
  $debug = TRUE;
  shift @ARGV;
 }
 else {
  warn sprintf($d->get("Unknown option %s.\n"), shift @ARGV); # xgettext hack
  exit 1;
 }
}

if (check_utils()) {
 printf($d->get("%s requires the libtiff library.\nPlease install it.\n"),
                                                                      $program);
 exit 1;
}

# Set LC_NUMERIC to C to prevent decimal commas (or anything else) confusing
# scanimage
setlocale(LC_NUMERIC, "C");

# Read config file
my $config = "$ENV{'HOME'}/.$program";

# Set some defaults
my %SETTING = (
 'window_width'    => 800,
 'window_height'   => 600,
 'window_maximize' => TRUE,
 'Page range'      => 'all',
 'l'               => 0,
 't'               => 0,
);

if (-r $config) {
 open CONFIG, $config
  or die sprintf($d->get("Can't open config file: %s\n"), $config);

 while (<CONFIG>) {
  chomp;			# no newline
  s/#.*//;			# no comments
  s/^\s+//;			# no leading white
  s/\s+$//;			# no trailing white
  next unless length;		# anything left?
  my ($key, $value) = split(/\s*=\s*/, $_, 2);
  $SETTING{$key} = $value;
 }
 close CONFIG;
}

# Just in case dependencies have changed, put put startup warning again
$SETTING{'startup warning'} = TRUE
 if (! defined($SETTING{version}) or $SETTING{version} ne $version);
$SETTING{version} = $version;

# Create icons for rotate buttons
my $IconFactory = undef;
my $path;
if (-d '/usr/share/gscan2pdf') {
 $path = '/usr/share/gscan2pdf';
}
else {
 $path = '.'; # make this a big cleverer, going one dir down from bin.
}
init_icons( [ 'rotate90',  "$path/rotate90.png" ], 
            [ 'rotate180', "$path/rotate180.png" ], 
            [ 'rotate270', "$path/rotate270.png" ],
            [ 'scanner',   "$path/scanner.png" ],
            [ 'pdf',       "$path/pdf.png" ], );

# Define application-wide variables here so that they can be referenced
# in the menu callbacks
my ($windowp, $windowt, $windowv, $windowe, $windows, $windowh, $windowo,
    $windowu, $slist, $frames, $vboxd, $combobd, $combobp, $hboxc, @undo_buffer,
    @redo_buffer, @undo_selection, @redo_selection, %dependencies,
    @ocr_stack, $ocr_timer);

my @action_items = (
 # Fields for each action item:
 # [name, stock_id, value, label, accelerator, tooltip, callback]
 
 # File menu
 [ 'File', undef, $d->get('_File') ],
 [ 'New', 'gtk-new', $d->get('_New'), '<control>n', $d->get('Clears all pages'), \&new ],
 [ 'Import', 'gtk-open', $d->get('_Import'), '<control>i', $d->get('Import image file(s)'), \&import ],
 [ 'Scan', 'scanner', $d->get('S_can'), undef, $d->get('Scan document'), \&scan_dialog ],
 [ 'Save PDF', 'pdf', $d->get('_Save PDF'), '<shift><control>s', $d->get('Save as PDF'), \&save_PDF_dialog ],
 [ 'Save TIFF', 'gtk-save', $d->get('Save _TIFF'), '<control>s', $d->get('Save as TIFF'), \&save_TIFF_dialog ],
 [ 'Save DjVu', undef, $d->get('Save _DjVu'), undef, $d->get('Save as DjVu'), \&save_djvu_dialog ],
 [ 'Email as PDF', 'gtk-edit', $d->get('_Email as PDF'), '<control>e', $d->get('Attach as PDF to a new email'), \&email ],
 [ 'Quit', 'gtk-quit', $d->get('_Quit'), '<control>q', $d->get('Quit'), sub { quit(); Gtk2 -> main_quit; } ],
 
 # Edit menu
 [ 'Edit', undef, $d->get('_Edit') ],
 [ 'Undo', 'gtk-undo', $d->get('_Undo'), '<control>z', $d->get('Undo'), \&undo ],
 [ 'Redo', 'gtk-redo', $d->get('_Redo'), '<shift><control>z', $d->get('Redo'), \&unundo ],
 [ 'Delete', 'gtk-delete', $d->get('_Delete'), undef, $d->get('Delete selected pages'), \&delete_pages ],
 [ 'Renumber', 'gtk-sort-ascending', $d->get('_Renumber'), '<control>r', $d->get('Renumber pages from 1 to n'), sub { renumber($slist, 0, 1, 1); } ],
 [ 'Select All', 'gtk-select-all', $d->get('Select _All'), '<control>a', $d->get('Select all pages'), \&select_all ],
 [ 'Frontend', undef, $d->get('_Frontend') ],
 
 # View menu
 [ 'View', undef, $d->get('_View') ],
 [ 'Zoom 100', 'gtk-zoom-100', $d->get('Zoom _100%'), undef, $d->get('Zoom to 100%'), sub { zoom_button('100%'); } ],
 [ 'Zoom to fit', 'gtk-zoom-fit', $d->get('Zoom to _fit'), undef, $d->get('Zoom to fit'), sub { zoom_button('fit'); } ],
 [ 'Zoom in', 'gtk-zoom-in', $d->get('Zoom _in'), 'plus', $d->get('Zoom in'), sub { zoom_button('in'); } ],
 [ 'Zoom out', 'gtk-zoom-out', $d->get('Zoom _out'), 'minus', $d->get('Zoom out'), sub { zoom_button('out'); } ],
 [ 'Rotate 90', 'rotate90', $d->get('Rotate 90 clockwise'), undef, $d->get('Rotate 90 clockwise'), sub { rotate(90); } ],
 [ 'Rotate 180', 'rotate180', $d->get('Rotate 180'), undef, $d->get('Rotate 180'), sub { rotate(180); } ],
 [ 'Rotate 270', 'rotate270', $d->get('Rotate 90 anticlockwise'), undef, $d->get('Rotate 90 anticlockwise'), sub { rotate(270); } ],
 
 # Tools menu
 [ 'Tools', undef, $d->get('_Tools') ],
 [ 'unpaper', undef, $d->get('_unpaper'), undef, $d->get('Clean up current page with unpaper'), \&unpaper ],
 [ 'OCR', undef, $d->get('_OCR'), undef, $d->get('Optical Character Recognition of current page'), \&OCR ],

 # Help menu
 [ 'Help menu', undef, $d->get('_Help') ],
 [ 'Help', 'gtk-help', $d->get('_Help'), '<control>h', $d->get('Help'), \&view_pod ],
 [ 'About', 'gtk-about', $d->get('_About'), undef, $d->get('_About'), \&about ],
);

my @frontends = (
  #Fields for each radio-action item:
  #[name, stock_id, value, label, accelerator, tooltip, value]

 [ 'scanimage', undef, 'scan_image', undef, 'scanimage', 0 ],
 [ 'scanadf',  undef, 'scan_adf', undef, 'scanadf', 1 ],
);

# Declare the XML structure
my $uimanager;
my $ui = "<ui>
 <menubar name='MenuBar'>
  <menu action='File'> 
   <menuitem action='New'/>
   <menuitem action='Import'/>
   <menuitem action='Scan'/>
   <menuitem action='Save PDF'/>
   <menuitem action='Save TIFF'/>
   <menuitem action='Save DjVu'/>
   <menuitem action='Email as PDF'/>
   <separator/>
   <menuitem action='Quit'/>
  </menu>
  <menu action='Edit'> 
   <menuitem action='Undo'/>
   <menuitem action='Redo'/>
   <separator/>
   <menuitem action='Delete'/>
   <menuitem action='Renumber'/>
   <menuitem action='Select All'/>
   <separator/>
   <menu action='Frontend'>
    <menuitem action='scanimage'/>
    <menuitem action='scanadf'/>
   </menu>
   <separator/>
   <menuitem action='Options'/>
  </menu>
  <menu action='View'> 
   <menuitem action='Zoom 100'/>
   <menuitem action='Zoom to fit'/>
   <menuitem action='Zoom in'/>
   <menuitem action='Zoom out'/>
   <separator/>
   <menuitem action='Rotate 90'/>
   <menuitem action='Rotate 180'/>
   <menuitem action='Rotate 270'/>
  </menu>
  <menu action='Tools'> 
   <menuitem action='unpaper'/>
   <menuitem action='OCR'/>
  </menu>
  <menu action='Help menu'> 
   <menuitem action='Help'/>
   <menuitem action='About'/>
  </menu>
 </menubar>
 <toolbar name='ToolBar'>
  <toolitem action='New'/>
  <toolitem action='Import'/>
  <toolitem action='Scan'/>
  <toolitem action='Save PDF'/>
  <toolitem action='Save TIFF'/>
  <toolitem action='Email as PDF'/>
  <separator/>
  <toolitem action='Undo'/>
  <toolitem action='Redo'/>
  <separator/>
  <toolitem action='Delete'/>
  <toolitem action='Renumber'/>
  <toolitem action='Select All'/>
  <separator/>
  <toolitem action='Zoom 100'/>
  <toolitem action='Zoom to fit'/>
  <toolitem action='Zoom in'/>
  <toolitem action='Zoom out'/>
  <separator/>
  <toolitem action='Rotate 90'/>
  <toolitem action='Rotate 180'/>
  <toolitem action='Rotate 270'/>
  <separator/>
  <toolitem action='Help'/>
  <toolitem action='Quit'/>
 </toolbar>
 <popup name='Detail_Popup'>
  <menuitem action='Zoom 100'/>
  <menuitem action='Zoom to fit'/>
  <menuitem action='Zoom in'/>
  <menuitem action='Zoom out'/>
  <separator/>
  <menuitem action='Rotate 90'/>
  <menuitem action='Rotate 180'/>
  <menuitem action='Rotate 270'/>
  <separator/>
  <menuitem action='Delete'/>
 </popup>
 <popup name='Thumb_Popup'>
  <menuitem action='Save PDF'/>
  <menuitem action='Save TIFF'/>
  <menuitem action='Save DjVu'/>
  <menuitem action='Email as PDF'/>
  <separator/>
  <menuitem action='Renumber'/>
  <menuitem action='Select All'/>
  <separator/>
  <menuitem action='Rotate 90'/>
  <menuitem action='Rotate 180'/>
  <menuitem action='Rotate 270'/>
  <separator/>
  <menuitem action='Delete'/>
 </popup>
</ui>";

# Create the window
my $window = Gtk2::Window -> new;
$window -> set_title ( "$program v$version" );
$window -> signal_connect ( 'delete-event' => sub { quit(); Gtk2 -> main_quit; } );

# Note when the window is maximised or not.
$window -> signal_connect(window_state_event => sub {
 my ($w, $event) = @_;
 if ($event -> new_window_state & [ 'maximized' ]) {
  $SETTING{'window_maximize'} = TRUE;
 }
 else {
  $SETTING{'window_maximize'} = FALSE;
 }
});

# If defined in the config file, set the window state, size and position
$window -> set_default_size ($SETTING{'window_width'}, $SETTING{'window_height'});
if (defined($SETTING{'window_x'}) and defined($SETTING{'window_y'})) {
 $window -> move ($SETTING{'window_x'}, $SETTING{'window_y'});
}
$window -> maximize if ($SETTING{'window_maximize'});

$window -> set_default_icon_from_file ("$path/gscan2pdf.png");

my $main_vbox = new Gtk2::VBox ( FALSE, 1 );
$window -> add ( $main_vbox );

# Create the menu bar
my ($menubar, $toolbar) = create_menu_bar( $window );
$main_vbox -> pack_start( $menubar, FALSE, TRUE, 0 );
$main_vbox -> pack_start( $toolbar, FALSE, FALSE,0 );

my $tooltips = Gtk2::Tooltips->new;
$tooltips->enable;

# HPaned for thumbnails and detail view
my $hpaned = Gtk2::HPaned -> new;
$main_vbox -> pack_start($hpaned, TRUE, TRUE, 0);

# Thumbnail dimensions
my $widtht = 100;
my $heightt = 100;
if (defined($SETTING{'thumb panel'})) {
 $hpaned -> set_position ($SETTING{'thumb panel'});
}
else {
 $hpaned -> set_position ($widtht);
}

# Scrolled window for thumbnails
my $scwin_thumbs = Gtk2::ScrolledWindow -> new;
$hpaned -> pack1($scwin_thumbs, TRUE, TRUE);
$scwin_thumbs -> set_policy('automatic', 'automatic');
$scwin_thumbs -> set_shadow_type('etched-in');

# define hidden string column for filename and annotation buffer
Gtk2::Ex::Simple::List -> add_column_type( 'hstring',
                                           type => 'Glib::String',
                                           attr => 'hidden' );

# Set up a SimpleList
$slist = Gtk2::Ex::Simple::List -> new('#' => 'int',
                                       $d->get('Thumbnails') => 'pixbuf',
                                       'Filename' => 'hstring',
                                       'Buffer' => 'hstring');

# Callback for dropped signal.
$slist -> signal_connect('drag_drop' => sub {
# Block row-changed signal so that the list can be renumbered before the sort
# takes over.
 $slist -> get_model -> signal_handler_block($slist -> {signalid});
 return FALSE;
});

# Now that drag_drop has returned,
# check that the numbering is ascending and renumber if needed.
$slist->signal_connect (drag_end => sub {
 renumber($slist, 0);
 $slist -> get_model -> signal_handler_unblock($slist -> {signalid});
 return FALSE;
});

# If dragged below the bottom of the window, scroll it.
$slist->signal_connect('drag-motion' => sub {
 my ($tree, $dnd, $x, $y, $t) = @_;
 my ($path, $how) = $tree->get_dest_row_at_pos($x, $y) or return;
 my $scroll = $tree->parent;
 my $rectangle = $scroll->allocation;
 $tree->set_drag_dest_row($path, $how);

 my $adj = $scroll->get_vadjustment;
 my ($value, $step) = ($adj->value, $adj->step_increment);

 if ($y > $value + $adj->page_size - $step/2) {
   my $v = $value + $step;
   my $m = $adj->upper - $adj->page_size;
   $adj->set_value($v > $m ? $m : $v);
 }
 elsif ($y < $value + $step/2) {
   my $v = $value - $step;
   my $m = $adj->lower;
   $adj->set_value($v < $m ? $m : $v);
 }

 return FALSE;
});

# Set up callback for right mouse clicks.
$slist->signal_connect(button_press_event => \&handle_clicks);
$slist->signal_connect(button_release_event => \&handle_clicks);

$slist -> get_selection -> set_mode ('multiple');
$slist -> set_headers_visible(FALSE);
$slist -> set_reorderable( TRUE );

# Set the page number to be editable
$slist -> set_column_editable (0, TRUE);

# Set-up the callback when the page number has been edited.
$slist -> {signalid} = $slist -> get_model ->
 signal_connect('row-changed' => sub {
  $slist -> get_model -> signal_handler_block($slist -> {signalid});

# Sort pages
  manual_sort_by_column ($slist, 0);

# And make sure there are no duplicates
  renumber ($slist, 0);
  $slist -> get_model -> signal_handler_unblock($slist -> {signalid});
 });

$scwin_thumbs -> add($slist);

# VPaned for detail view and OCR buffer
my $vpaned = Gtk2::VPaned -> new;
$hpaned -> pack2 ($vpaned, TRUE, TRUE);

# Scrolled window for detail view
my $scwin_detail = Gtk2::ScrolledWindow -> new;
$vpaned -> pack1 ($scwin_detail, TRUE, TRUE);
$scwin_detail -> set_policy ('automatic', 'automatic');

my $scale;
my $image = Gtk2::Image -> new;

# Need to pack the image in an eventbox to get it to respond to mouse clicks
my $eventbox = Gtk2::EventBox -> new;
$eventbox -> add ( $image );
$eventbox->signal_connect(button_press_event => \&handle_clicks);
$eventbox->signal_connect(button_release_event => \&handle_clicks);

$scwin_detail -> add_with_viewport($eventbox);

# OCR buffer
my $scwin_buffer = Gtk2::ScrolledWindow->new;
$scwin_buffer -> set_policy ('never', 'automatic');
$scwin_buffer -> set_shadow_type('etched-in');
my $textbuffer = Gtk2::TextBuffer->new;
my $textview = Gtk2::TextView->new_with_buffer($textbuffer);
$textview->set_wrap_mode ('word');
$textview -> set_sensitive(FALSE);
$scwin_buffer->add($textview);
$vpaned -> pack2 ($scwin_buffer, TRUE, TRUE);
if (defined($SETTING{'ocr panel'})) {
 $vpaned -> set_position ($SETTING{'ocr panel'});
}
else {
# $vpaned -> allocation -> height gives 1
#  my $height = $vpaned -> allocation -> height;
 my ($width, $height) = $window->get_size;
 $vpaned -> set_position ($height*3/4);
}

# Keep the simple list buffer up to date
$textbuffer -> {signalid} = $textbuffer -> signal_connect( changed => sub {
 $slist -> get_model -> signal_handler_block($slist -> {signalid});
 my @page = $slist -> get_selected_indices;
 $slist -> {data}[$page[0]][3] =
  $textbuffer->get_text ($textbuffer->get_start_iter, $textbuffer->get_end_iter, FALSE)
   if ($#page > -1);
 $slist -> get_model -> signal_handler_unblock($slist -> {signalid});
} );

# Set up call back for list selection to update detail view
$slist -> get_selection -> signal_connect(changed => sub {
 my @page = $slist -> get_selected_indices;

# Display the new image
 if (@page) {

  my $path = Gtk2::TreePath->new_from_indices($page[0]);
  $slist->scroll_to_cell($path);

# Get dimensions for detail window
  my $widthd = $scwin_detail -> allocation -> width;
  my $heightd = $scwin_detail -> allocation -> height;

  $image -> set_from_pixbuf(
           get_pixbuf(scalar($slist->{data}[$page[0]][2]), $heightd, $widthd));
  $image -> show;

# Update the buffer, if created
  $textview -> set_sensitive(TRUE) if (! $textview -> is_sensitive);
  if (defined $slist -> {data}[$page[0]][3]) {
   $textbuffer->set_text ($slist -> {data}[$page[0]][3]);
  }
  else {
   $textbuffer -> delete($textbuffer->get_start_iter, $textbuffer->get_end_iter);
  }
 }
 else {
  $image -> clear;
  $textview -> set_sensitive(FALSE);
  $textbuffer -> set_text('');
 }
});

# _after ensures that Editables get first bite
$window -> signal_connect_after (key_press_event => sub {
 my ($widget, $event) = @_;

# Let the keypress propagate
 return FALSE unless ($event->keyval == $Gtk2::Gdk::Keysyms{Delete});

 delete_pages();
 return TRUE;
});

# If defined in the config file, set the current directory
$SETTING{'cwd'} = getcwd if (! defined($SETTING{'cwd'}));

# Create a temporary directory for scans
my $dir = tempdir;

# Set up the strings for the possible device-dependent options
my %ddo;
my %pddo = (
             'mode' => { string => $d->get('Mode'),
                         values => {
                                   'Lineart'       => $d->get('Lineart'),
                                   'Grayscale',    => $d->get('Grayscale'),
                                   'Gray'          => $d->get('Gray'),
                                   'Color'         => $d->get('Color'),
                                   'Black & White' => $d->get('Black & White'),
                                   'True Gray'     => $d->get('True Gray'),
                                   'Binary'        => $d->get('Binary'),
                                   'auto'          => $d->get('Auto'),
                                   'Halftone'      => $d->get('Halftone'),
                                   '24bit Color'   => $d->get('24bit Color'),
                                   }
                       },
	    'compression'     => { string => $d->get('Compression'),
				   values => {
					     'None' => $d->get('None'),
					     'JPEG' => $d->get('JPEG'),
					     },
				 },
            'resolution'      => { string => $d->get('Resolution') },
            'brightness'      => { string => $d->get('Brightness') },
            'batch-scan'      => { string => $d->get('Batch scan'),
                                   values => {
                                             'yes' => $d->get('Yes'),
                                             'no'  => $d->get('No'),
                                             },
                                 },
            'wait-for-button' => { string => $d->get('Wait for button'),
                                   values => {
                                             'yes' => $d->get('Yes'),
                                             'no'  => $d->get('No'),
                                             },
                                 },
            'button-wait'     => { string => $d->get('Wait for button'),
                                   values => {
                                             'yes' => $d->get('Yes'),
                                             'no'  => $d->get('No'),
                                             },
                                 },
            'source'          => { string => $d->get('Source'),
                                   values => {
                                     'Normal'  => $d->get('Normal'),
                                     'ADF'     => $d->get('ADF'),
                                     'Automatic Document Feeder' => $d->get('Automatic Document Feeder'),
                                     'XPA'     => $d->get('XPA'),
                                     'auto'    => $d->get('Auto'),
                                     'Auto'    => $d->get('Auto'),
                                     'Flatbed' => $d->get('Flatbed'),
                                     'Transparency Adapter' => $d->get('Transparency Adapter'),
                                     'Transparency Unit' => $d->get('Transparency Unit'),
                                   },
                                 },
          );

update_uimanager();

$window -> show_all;
Gtk2 -> main;



### Subroutines

# Create the menu bar, initialize its menus, and return the menu bar.

sub create_menu_bar {
 my ($window) = @_;

# Create a Gtk2::UIManager instance     
 $uimanager = Gtk2::UIManager->new;

# extract the accelgroup and add it to the window
 my $accelgroup = $uimanager->get_accel_group;
 $window->add_accel_group($accelgroup);

# Create the basic Gtk2::ActionGroup instance
# and fill it with Gtk2::Action instances
 my $actions_basic = Gtk2::ActionGroup->new ("actions_basic");
 $actions_basic->add_actions (\@action_items, undef);
	
# Add the actiongroup to the uimanager  
 $uimanager->insert_action_group($actions_basic, 0);

# Create the frontend Gtk2::ActionGroup instance
# and fill it with Gtk2::RadioAction instances
 my $actions_frontends = Gtk2::ActionGroup->new ("frontends");
 $actions_frontends->add_radio_actions(\@frontends, 0, \&change_frontend);

# Add the actiongroup to the uimanager          
 $uimanager->insert_action_group($actions_frontends, 0);

# Create the options Gtk2::ActionGroup instance
# and fill it with Gtk2::ToggleAction instances
 my $actions_options = Gtk2::ActionGroup->new ("options");
 $actions_options->add_toggle_actions ( [
  [ 'Options', 'gtk-preferences', $d->get('Enable Save _Options'), undef, $d->get('View options before saving'), undef, TRUE ],
 ] );

# Add the actiongroup to the uimanager          
 $uimanager->insert_action_group($actions_options, 0);

# add the basic XML description of the GUI
 $uimanager->add_ui_from_string ($ui);

# extract the menubar
 my $menubar = $uimanager->get_widget('/MenuBar');

# Check for presence of various packages
 $dependencies{pdfapi2} = ! eval { require PDF::API2 };
 $dependencies{perlmagick} = ! eval { require Image::Magick };
 $dependencies{imagemagick} = system("which convert >/dev/null 2>/dev/null");
 $dependencies{scanadf} = system("which scanadf >/dev/null 2>/dev/null");
 $dependencies{xdg} = system("which xdg-email >/dev/null 2>/dev/null");
 $dependencies{gocr} = system("which gocr >/dev/null 2>/dev/null");
 $dependencies{djvu} = system("which cjb2 >/dev/null 2>/dev/null");
 $dependencies{unpaper} = system("which unpaper >/dev/null 2>/dev/null");
 my $msg = '';

 $msg .= $d->get("PDF creation requires PDF::API2\n")
  if ($dependencies{pdfapi2});

# Ghost scanadf item if scanadf or imagemagick not available
 my $item = $uimanager->get_widget('/MenuBar/Edit/Frontend/scanadf');
 if ($dependencies{scanadf} != 0) {
  $item -> set_sensitive(FALSE);
  $SETTING{'frontend'} = 'scanimage';
  $msg .= $d->get("The scanadf frontend is not available\n")
 }

# Set scanadf active if in config
 elsif (defined($SETTING{'frontend'}) and $SETTING{'frontend'} eq 'scanadf') {
  $item -> set_active(TRUE);
 }
# if scanadf isn't set, make sure that scanimage is
 else {
  $SETTING{'frontend'} = 'scanimage';
 }

# Disable options if necessary
 $uimanager->get_widget('/MenuBar/Edit/Options') -> set_active(FALSE)
  if (defined($SETTING{'enable options'}) and ! $SETTING{'enable options'});

# Ghost djvu item if cjb2 not available
 $msg .= $d->get("Save as DjVu requires djvulibre-bin\n")
  if ($dependencies{djvu} != 0);

# Ghost email item if xdg-email not available
 $msg .= $d->get("Email as PDF requires xdg-email\n")
  if ($dependencies{xdg} != 0);

# Undo/redo start off ghosted anyway-
 $uimanager->get_widget('/MenuBar/Edit/Undo') -> set_sensitive(FALSE);
 $uimanager->get_widget('/MenuBar/Edit/Redo') -> set_sensitive(FALSE);
 $uimanager->get_widget('/ToolBar/Undo') -> set_sensitive(FALSE);
 $uimanager->get_widget('/ToolBar/Redo') -> set_sensitive(FALSE);

# save * start off ghosted anyway-
 $uimanager->get_widget('/MenuBar/File/Save PDF') -> set_sensitive(FALSE);
 $uimanager->get_widget('/MenuBar/File/Save TIFF') -> set_sensitive(FALSE);
 $uimanager->get_widget('/MenuBar/File/Save DjVu') -> set_sensitive(FALSE);
 $uimanager->get_widget('/MenuBar/File/Email as PDF') -> set_sensitive(FALSE);
 $uimanager->get_widget('/ToolBar/Save PDF') -> set_sensitive(FALSE);
 $uimanager->get_widget('/ToolBar/Save TIFF') -> set_sensitive(FALSE);
 $uimanager->get_widget('/ToolBar/Email as PDF') -> set_sensitive(FALSE);
 $uimanager->get_widget('/Thumb_Popup/Save PDF') -> set_sensitive(FALSE);
 $uimanager->get_widget('/Thumb_Popup/Save TIFF') -> set_sensitive(FALSE);
 $uimanager->get_widget('/Thumb_Popup/Save DjVu') -> set_sensitive(FALSE);
 $uimanager->get_widget('/Thumb_Popup/Email as PDF') -> set_sensitive(FALSE);

# a convenient place to put these
 $dependencies{pages} = -1;

# Ghost rotations and unpaper if perlmagick not available
 if ($dependencies{perlmagick}) {
  $uimanager->get_widget('/MenuBar/View/Rotate 90') -> set_sensitive(FALSE);
  $uimanager->get_widget('/MenuBar/View/Rotate 180') -> set_sensitive(FALSE);
  $uimanager->get_widget('/MenuBar/View/Rotate 270') -> set_sensitive(FALSE);
  $uimanager->get_widget('/ToolBar/Rotate 90') -> set_sensitive(FALSE);
  $uimanager->get_widget('/ToolBar/Rotate 180') -> set_sensitive(FALSE);
  $uimanager->get_widget('/ToolBar/Rotate 270') -> set_sensitive(FALSE);
  $uimanager->get_widget('/Detail_Popup/Rotate 90') -> set_sensitive(FALSE);
  $uimanager->get_widget('/Detail_Popup/Rotate 180') -> set_sensitive(FALSE);
  $uimanager->get_widget('/Detail_Popup/Rotate 270') -> set_sensitive(FALSE);
  $uimanager->get_widget('/Thumb_Popup/Rotate 90') -> set_sensitive(FALSE);
  $uimanager->get_widget('/Thumb_Popup/Rotate 180') -> set_sensitive(FALSE);
  $uimanager->get_widget('/Thumb_Popup/Rotate 270') -> set_sensitive(FALSE);
  $uimanager->get_widget('/MenuBar/Tools/unpaper') -> set_sensitive(FALSE);
  $msg .= $d->get("The rotating options, unpaper support and the scanadf frontend require perlmagick\n");
 }


# Ghost gocr and unpaper if imagemagick not available
 if ($dependencies{imagemagick}) {
  $uimanager->get_widget('/MenuBar/Tools/OCR') -> set_sensitive(FALSE);
  $uimanager->get_widget('/MenuBar/Tools/unpaper') -> set_sensitive(FALSE);
  $msg .= $d->get("OCR, unpaper support and the scanadf frontend require perlmagick\n");
 }

# Ghost unpaper item if unpaper not available
 if ($dependencies{unpaper} != 0) {
  $uimanager->get_widget('/MenuBar/Tools/unpaper') -> set_sensitive(FALSE);
  $msg .= $d->get("unpaper missing\n");
 }

# Ghost ocr item if gocr not available
 if ($dependencies{gocr} != 0) {
  $uimanager->get_widget('/MenuBar/Tools/OCR') -> set_sensitive(FALSE);
  $msg .= $d->get("OCR requires gocr\n");
 }

# Put up warning if needed
 if ((! defined($SETTING{'startup warning'})
      or $SETTING{'startup warning'} eq TRUE) and ($msg ne '')) {
  my $dialog = Gtk2::Dialog -> new ($d->get('Warning: missing packages'),
                                    $window, 'destroy-with-parent',
                                    'gtk-ok' => 'none');
  my $label = Gtk2::Label->new ($msg);
  $dialog->vbox->add ($label);
  my $cb = Gtk2::CheckButton->new_with_label (
                                     $d->get("Don't show this message again"));
  $cb->set_active (TRUE);
  $dialog->vbox->add ($cb);
  $dialog->show_all;
  $dialog -> run;
  $SETTING{'startup warning'} = FALSE if ($cb->get_active);
  $dialog -> destroy;
 }

# extract the toolbar
 my $toolbar = $uimanager->get_widget('/ToolBar');

# turn off labels
 my $settings = $toolbar->get_settings();
 $settings->set('gtk-toolbar-style', 'icons'); # only icons

 return ( $menubar, $toolbar );
}


# ghost or unghost as necessary as # pages > 0 or not.

sub update_uimanager {
 my @widgets = ( '/MenuBar/View/Zoom 100',
                 '/MenuBar/View/Zoom to fit',
                 '/MenuBar/View/Zoom in',
                 '/MenuBar/View/Zoom out',
                 '/MenuBar/View/Rotate 90',
                 '/MenuBar/View/Rotate 180',
                 '/MenuBar/View/Rotate 270',
                 '/MenuBar/Tools/unpaper',
                 '/MenuBar/Tools/OCR',

                 '/ToolBar/Zoom 100',
                 '/ToolBar/Zoom to fit',
                 '/ToolBar/Zoom in',
                 '/ToolBar/Zoom out',
                 '/ToolBar/Rotate 90',
                 '/ToolBar/Rotate 180',
                 '/ToolBar/Rotate 270',

                 '/Detail_Popup/Zoom 100',
                 '/Detail_Popup/Zoom to fit',
                 '/Detail_Popup/Zoom in',
                 '/Detail_Popup/Zoom out',
                 '/Detail_Popup/Rotate 90',
                 '/Detail_Popup/Rotate 180',
                 '/Detail_Popup/Rotate 270',

                 '/Thumb_Popup/Rotate 90',
                 '/Thumb_Popup/Rotate 180',
                 '/Thumb_Popup/Rotate 270', );

 if ($slist -> get_selected_indices) {
  foreach (@widgets) {
   $uimanager->get_widget($_) -> set_sensitive(TRUE);
  }
 }
 else {
  foreach (@widgets) {
   $uimanager->get_widget($_) -> set_sensitive(FALSE);
  }
 }
 if ($#{$slist -> {data}} > -1) {
  if ($dependencies{pages} == -1) {
   if ($dependencies{djvu} == 0) {
    $uimanager->get_widget('/MenuBar/File/Save DjVu') -> set_sensitive(TRUE);
    $uimanager->get_widget('/Thumb_Popup/Save DjVu') -> set_sensitive(TRUE);
   }
   if ($dependencies{xdg} == 0) {
    $uimanager->get_widget('/MenuBar/File/Email as PDF') -> set_sensitive(TRUE);
    $uimanager->get_widget('/ToolBar/Email as PDF') -> set_sensitive(TRUE);
    $uimanager->get_widget('/Thumb_Popup/Email as PDF') -> set_sensitive(TRUE);
   }
   $uimanager->get_widget('/MenuBar/File/Save PDF') -> set_sensitive(TRUE);
   $uimanager->get_widget('/MenuBar/File/Save TIFF') -> set_sensitive(TRUE);
   $uimanager->get_widget('/ToolBar/Save PDF') -> set_sensitive(TRUE);
   $uimanager->get_widget('/ToolBar/Save TIFF') -> set_sensitive(TRUE);
   $uimanager->get_widget('/Thumb_Popup/Save PDF') -> set_sensitive(TRUE);
   $uimanager->get_widget('/Thumb_Popup/Save TIFF') -> set_sensitive(TRUE);

   $dependencies{pages} = $#{$slist -> {data}};
  }
 }
 else {
  if ($dependencies{pages} > -1) {
   if ($dependencies{djvu} == 0) {
    $uimanager->get_widget('/MenuBar/File/Save DjVu') -> set_sensitive(FALSE);
    $uimanager->get_widget('/Thumb_Popup/Save DjVu') -> set_sensitive(FALSE);
    $windowv->hide if defined $windowv;
   }
   if ($dependencies{xdg} == 0) {
    $uimanager->get_widget('/MenuBar/File/Email as PDF') -> set_sensitive(FALSE);
    $uimanager->get_widget('/ToolBar/Email as PDF') -> set_sensitive(FALSE);
    $uimanager->get_widget('/Thumb_Popup/Email as PDF') -> set_sensitive(FALSE);
    $windowe->hide if defined $windowe;
   }
   $uimanager->get_widget('/MenuBar/File/Save PDF') -> set_sensitive(FALSE);
   $uimanager->get_widget('/MenuBar/File/Save TIFF') -> set_sensitive(FALSE);
   $uimanager->get_widget('/ToolBar/Save PDF') -> set_sensitive(FALSE);
   $uimanager->get_widget('/ToolBar/Save TIFF') -> set_sensitive(FALSE);
   $uimanager->get_widget('/Thumb_Popup/Save PDF') -> set_sensitive(FALSE);
   $uimanager->get_widget('/Thumb_Popup/Save TIFF') -> set_sensitive(FALSE);
   $windowp->hide if defined $windowp;
   $windowt->hide if defined $windowt;

   $dependencies{pages} = $#{$slist -> {data}};
  }
 }
}


# Callback from RadioItem Edit/Frontend

sub change_frontend {
 my ($action, $current) = @_;
 $SETTING{'frontend'} = $current->get_name;
 update_options($vboxd, $device[$combobd -> get_active]) if (defined $windows);
}


# Zoom the detail window

sub zoom_button {
 my ($button) = @_;

# Get dimensions for detail window
 my $widthd = $scwin_detail -> allocation -> width;
 my $heightd = $scwin_detail -> allocation -> height;
 my $pixbuf;

 my @page = $slist -> get_selected_indices;
 my $filename = $slist->{data}[$page[0]][2];

 if ($button eq '100%') {
  $pixbuf = Gtk2::Gdk::Pixbuf -> new_from_file ($filename);
  my $widthi = $pixbuf->get_width;
  my $heighti =  $pixbuf->get_height;
  if ($widthd > $border) {
   $scale = $widthi/($widthd-$border) > $heighti/($heightd-$border)
                     ? $widthi/($widthd-$border) : $heighti/($heightd-$border);
  }
  else {
   return ($heightd-$border)/$heighti;
  }
 }
 elsif ($button eq 'in') {
  $scale *= 1.2;
  $pixbuf = Gtk2::Gdk::Pixbuf ->
     new_from_file_at_scale ($filename, $widthd*$scale, $heightd*$scale, TRUE);
 }
 elsif ($button eq 'out') {
  $scale /= 1.2;
  $pixbuf = Gtk2::Gdk::Pixbuf ->
     new_from_file_at_scale ($filename, $widthd*$scale, $heightd*$scale, TRUE);
 }
 else {
  $scale = 1;
  $pixbuf = Gtk2::Gdk::Pixbuf ->
                   new_from_file_at_scale ($filename, $widthd, $heightd, TRUE);
 }

 $image -> set_from_pixbuf($pixbuf);
 $image -> show;
}


# Returns the pixbuf scaled to fit in the given box

sub get_pixbuf {
 my ($filename, $height, $width) = @_;
 
 my $pixbuf;
# Need the eval otherwise nothing else in the sub gets carried out after an error
# For some reason reading the tiff sometimes throws:
# Failed to load RGB data from TIFF file:
#        libpixbuf-tiff: Read error on strip 134; got 5190 bytes, expected 7130
# Normally succeeds on second attempt
 eval { $pixbuf = Gtk2::Gdk::Pixbuf ->
                  new_from_file_at_scale ($filename, $width, $height, TRUE); };
# if (Glib::Error::matches ($@, 'Mup::Thing::Error', 'flop')) {
#  recover_from_a_flop ();
# }
 if ($@) {
  warn $d->get('Warning: ')."$@\n";
  eval { $pixbuf = Gtk2::Gdk::Pixbuf ->
                  new_from_file_at_scale ($filename, $width, $height, TRUE); };
  if ($@) {
   return FALSE;
  }
  else {
   warn sprintf($d->get("Information: got %s on second attempt\n"), $filename);
  }
 }

 $scale = 1;
 return $pixbuf;
}


# Deletes all scans after warning.

sub new {

# Update undo/redo buffers
 take_snapshot();

# Depopulate the thumbnail list
 @{$slist -> {data}} = ();

 update_uimanager();
}


sub convert_to_tiff {
 my ($filename) = @_;
 my $image = Image::Magick->new;
 my $x = $image->Read($filename);
 warn "$x" if "$x";

# Calculate the resolution
 my $height = $image->Get('height');
 my $width = $image->Get('width');
 my $ratio = $height/$width;
 $ratio = 1/$ratio if ($ratio < 1);
 my $density = 72;
 for (my $i = 0; $i <= $#paperg; ++$i) {
  if (abs($ratio - $yg[$i]/$xg[$i]) < 0.01) {
   $density = (($height > $width) ? $height : $width)/$yg[$i]*25.4;
  }
 }

# Write the tif
 $image->Write(units => 'PixelsPerInch',
               density => $density.'x'.$density,
               filename => "$filename.tif");
 return "$filename.tif";
}


# Throw up file selector and import selected file

sub import {
 require Image::Magick;

# cd back to cwd to get filename
 chdir $SETTING{'cwd'};

 my $file_chooser = Gtk2::FileChooserDialog -> new(
                                      $d->get('Import image from file'),
                                      $window, 'open',
                                      'gtk-cancel' => 'cancel',
                                      'gtk-ok' => 'ok');
 $file_chooser -> set_select_multiple(TRUE);
 $file_chooser -> set_default_response('ok');

 if ('ok' eq $file_chooser->run) {

# cd back to tempdir to import
  chdir $dir;

# Update undo/redo buffers
  take_snapshot();

  my @filename = $file_chooser -> get_filenames;

  foreach my $filename (@filename) {

# Update cwd
   $SETTING{'cwd'} = dirname($filename);

# Get file type
   my $image = Image::Magick->new;
   my $x = $image->Read($filename);
   warn "$x" if "$x";

   my $format = $image->Get('format');
   undef $image;

   if (! defined $format) {
    my $dialog = Gtk2::MessageDialog -> new ($window,
                                            'destroy-with-parent',
                                            'error',
                                            'close',
                                            $d->get('Not a recognised file'));
    $dialog -> run;
    $dialog -> destroy;
   }
   elsif ($format eq 'Portable Document Format') {

# Extract images from PDF
    system ("pdfimages \"$filename\" x");

# Convert each image from ppm to tif and import
    my @images = <x-???.???>;
    foreach (@images) {
     my $tiff = convert_to_tiff($_);
     import_scan($tiff);
     unlink $_;
    }
   }
   elsif ($format eq 'Tagged Image File Format') {

# Split the tiff into its pages and import them individually
    system("tiffsplit \"$filename\"");
    my @pages = <x???.tif>;
    foreach (@pages) {
     import_scan($_);
    }
    
   }
   else {
    my $tiff = convert_to_tiff($filename);
    import_scan($tiff);
   }

  }

  update_uimanager();
 }

# cd back to tempdir
 chdir $dir;

 $file_chooser -> destroy;
}


# Create $window_new transient to $window with $title and $vbox

sub create_window {
 my ($window, $title, $destroy) = @_;
 my $window_new = Gtk2::Window -> new;
 $window_new -> set_border_width($border_width);
 $window_new -> set_title ($title);
 if ($destroy) {
  $window_new -> signal_connect (destroy => sub { $window_new -> destroy; } );
 }
 else {
  $window_new -> signal_connect (delete_event => sub {
   $window_new -> hide;
   return TRUE; # ensures that the window is not destroyed
  });
 }
 $window_new -> set_transient_for($window); # Assigns parent

# VBox for window
 my $vbox = Gtk2::VBox -> new;
 $window_new -> add ($vbox);

 return ($window_new, $vbox);
}


# Add a frame and radio buttons to $vbox,
# returning $buttona, $buttonc, $buttons

sub add_page_range {
 my ($vbox) = @_;

# Frame for page range
 my $frame = Gtk2::Frame -> new($d->get('Page range'));
 $vbox -> pack_start ($frame, TRUE, TRUE, 0);
 my $vboxp = Gtk2::VBox -> new;
 $vboxp -> set_border_width($border_width);
 $frame -> add ($vboxp);

#the first radio button has to set the group,
#which is undef for the first button
# All button
 my $buttona = Gtk2::RadioButton -> new(undef, $d->get('All'));
 $buttona -> signal_connect ('toggled' => sub {
  $SETTING{'Page range'} = 'all' if ($buttona -> get_active);
 });
 $vboxp -> pack_start($buttona, TRUE, TRUE, 0);
 my @group = $buttona -> get_group;

# Current button
 my $buttonc = Gtk2::RadioButton -> new(@group, $d->get('Current'));
 $buttonc -> signal_connect ('toggled' => sub {
  $SETTING{'Page range'} = 'current' if ($buttonc -> get_active);
 });
 $vboxp -> pack_start($buttonc, TRUE, TRUE, 0);

# Selected button
 my $buttons = Gtk2::RadioButton -> new(@group, $d->get('Selected'));
 $buttons -> signal_connect ('toggled' => sub {
  $SETTING{'Page range'} = 'selected' if ($buttons -> get_active);
 });
 $vboxp -> pack_start($buttons, TRUE, TRUE, 0);

# Set default
 if (defined($SETTING{'Page range'})) {
  if ($SETTING{'Page range'} eq 'current') {
   $buttonc -> set_active(TRUE);
  }
  elsif ($SETTING{'Page range'} eq 'selected') {
   $buttons -> set_active(TRUE);
  }
  else {
   $buttona -> set_active(TRUE);
  }
 }
 else {
  $buttona -> set_active(TRUE);
 }
 
 return ($buttona, $buttonc, $buttons);
}


# return string of filenames depending on which radiobutton is active

sub get_pagelist {
 my $n;
 my $pagelist;
 if ($SETTING{'Page range'} eq 'all') {
  $n = $#{$slist -> {data}};
  $pagelist = $slist -> {data}[0][2];
  my $i = 1;
  while ($i <= $#{$slist -> {data}}) {
   $pagelist = $pagelist." ".$slist -> {data}[$i][2];
   ++$i;
  }
 }
 elsif ($SETTING{'Page range'} eq 'current') {
  $n = 1;
  my @page = $slist -> get_selected_indices;
  $pagelist = $slist -> {data}[$page[0]][2];
 }
 elsif ($SETTING{'Page range'} eq 'selected') {
  my @page = $slist -> get_selected_indices;
  $n = $#page;
  $pagelist = $slist -> {data}[$page[0]][2];
  my $i = 1;
  while ($i <= $#page) {
   $pagelist = $pagelist." ".$slist -> {data}[$page[$i]][2];
   ++$i;
  }
 }
 
 return ($pagelist, $n);
}


# return array index of filenames depending on which radiobutton is active

sub get_page_index {
 my @page;
 if ($SETTING{'Page range'} eq 'all') {
  my $i = 0;
  while ($i <= $#{$slist -> {data}}) {
   push @page, $i;
   ++$i;
  }
 }
 elsif ($SETTING{'Page range'} eq 'current') {
  push @page, ($slist -> get_selected_indices)[0];
 }
 elsif ($SETTING{'Page range'} eq 'selected') {
  @page = $slist -> get_selected_indices;
 }
 
 return @page;
}


# Add PDF options to $vbox

sub add_PDF_options {
 my ($vbox) = @_;

# Frame for metadata
 my $framem = Gtk2::Frame -> new($d->get('Metadata'));
 $vbox -> pack_start ($framem, TRUE, TRUE, 0);
 my $vboxm = Gtk2::VBox -> new;
 $vboxm -> set_border_width($border_width);
 $framem -> add ($vboxm);

# Date/time
 my $hboxe = Gtk2::HBox -> new;
 $vboxm -> pack_start($hboxe, TRUE, TRUE, 0);
 my $labele = Gtk2::Label -> new ($d->get('Date'));
 $hboxe -> pack_start ($labele, FALSE, FALSE, 0);
 if (! (defined($SETTING{'year'}) and defined($SETTING{'month'})
                               and defined($SETTING{'day'}))) {
  ($SETTING{'day'}, $SETTING{'month'}, $SETTING{'year'})
                                                       = (localtime())[3, 4, 5];
  $SETTING{'year'} += 1900;
  $SETTING{'month'} += 1;
 }
 my $button = Gtk2::Button ->
                      new("$SETTING{'year'}/$SETTING{'month'}/$SETTING{'day'}");
 $button -> signal_connect( clicked => sub {
  my ($window, $vbox) = create_window($windowp, $d->get('Select Date'), TRUE);
  $window->set_resizable(FALSE);

  my $calendar = Gtk2::Calendar -> new;
  $calendar -> select_day($SETTING{'day'});
  $calendar -> select_month($SETTING{'month'}-1, $SETTING{'year'});
  $vbox -> pack_start ($calendar, TRUE, TRUE, 0);

  $calendar -> signal_connect(day_selected_double_click => sub {
   ($SETTING{'year'}, $SETTING{'month'}, $SETTING{'day'}) = $calendar ->
                                                                       get_date;
   $SETTING{'month'} += 1;
   $button -> set_label ("$SETTING{'year'}/$SETTING{'month'}/$SETTING{'day'}");
   $window -> destroy;
  });

  $window -> show_all;
 } );
 $tooltips -> set_tip ($button, $d->get('Year/Month/Day'));
 $hboxe -> pack_end( $button, TRUE, TRUE, 0 );

# Document author
 my $hboxa = Gtk2::HBox -> new;
 $vboxm -> pack_start($hboxa, TRUE, TRUE, 0);
 my $labela = Gtk2::Label -> new ($d->get('Document author'));
 $hboxa -> pack_start ($labela, FALSE, FALSE, 0);
 my $entrya = Gtk2::Entry -> new;
 $hboxa -> pack_end( $entrya, TRUE, TRUE, 0 );
 $entrya -> set_text($SETTING{'author'}) if (defined($SETTING{'author'}));

# Title
 my $hboxt = Gtk2::HBox -> new;
 $vboxm -> pack_start($hboxt, TRUE, TRUE, 0);
 my $labelt = Gtk2::Label -> new ($d->get('Title'));
 $hboxt -> pack_start ($labelt, FALSE, FALSE, 0);
 my $entryt = Gtk2::Entry -> new;
 $hboxt -> pack_end( $entryt, TRUE, TRUE, 0 );
 $entryt -> set_text($SETTING{'title'}) if (defined($SETTING{'title'}));

# Subject
 my $hboxs = Gtk2::HBox -> new;
 $vboxm -> pack_start($hboxs, TRUE, TRUE, 0);
 my $labels = Gtk2::Label -> new ($d->get('Subject'));
 $hboxs -> pack_start ($labels, FALSE, FALSE, 0);
 my $entrys = Gtk2::Entry -> new;
 $hboxs -> pack_end( $entrys, TRUE, TRUE, 0 );
 $entrys -> set_text($SETTING{'subject'}) if (defined($SETTING{'subject'}));

# Keywords
 my $hboxk = Gtk2::HBox -> new;
 $vboxm -> pack_start($hboxk, TRUE, TRUE, 0);
 my $labelk = Gtk2::Label -> new ($d->get('Keywords'));
 $hboxk -> pack_start ($labelk, FALSE, FALSE, 0);
 my $entryk = Gtk2::Entry -> new;
 $hboxk -> pack_end( $entryk, TRUE, TRUE, 0 );
 $entryk -> set_text($SETTING{'keywords'}) if (defined($SETTING{'keywords'}));

 return ($entrya, $entryt, $entrys, $entryk);
}


sub update_PDF_settings {
 my ($entrya, $entryt, $entrys, $entryk) = @_;

# Get metadata
 $SETTING{'author'} = $entrya -> get_text;
 $SETTING{'title'} = $entryt -> get_text;
 $SETTING{'subject'} = $entrys -> get_text;
 $SETTING{'keywords'} = $entryk -> get_text;
}


sub get_PDF_options {
 my %h;
 $h{'Author'} = $SETTING{'author'} if defined $SETTING{'author'};
 if (defined $SETTING{'year'} and defined $SETTING{'month'}
                                               and defined $SETTING{'day'}) {
  $h{'CreationDate'} = sprintf ("D:%4i%02i%02i000000+00'00'",
                        $SETTING{'year'}, $SETTING{'month'}, $SETTING{'day'});
  $h{'ModDate'} = sprintf ("D:%4i%02i%02i000000+00'00'",
                        $SETTING{'year'}, $SETTING{'month'}, $SETTING{'day'});
 }
 $h{'Creator'} = "$program v$version";
 $h{'Producer'} = "PDF::API2";
 $h{'Title'} = $SETTING{'title'} if defined $SETTING{'title'};
 $h{'Subject'} = $SETTING{'subject'} if defined $SETTING{'subject'};
 $h{'Keywords'} = $SETTING{'keywords'} if defined $SETTING{'keywords'};
 return %h;
}


# Create the PDF

sub create_PDF {
 my ($filename) = @_;

 require PDF::API2;

# fill $pagelist with filenames depending on which radiobutton is active
 my @pagelist = get_page_index();

# Create PDF with PDF::API2
 my $pdf = PDF::API2->new(-file => $filename);
 $pdf->info(get_PDF_options());
 foreach (@pagelist) {
  my $page = $pdf->page;

# Get the tiff size and resolution
  my $cmd = "tiffinfo \"".$slist -> {data}[$_][2]."\"";
  my $output = `$cmd`;
  my ($w, $h, $resolution);
  if ($output =~ /Image Width: (\d*)/) {
   $w = $1;
  }
  else {
   warn "Image width not found\n";
  }
  if ($output =~ /Image Length: (\d*)/) {
   $h = $1;
  }
  else {
   warn "Image height not found\n";
  }
  if ($output =~ /Resolution: (\d*)/) {
   $resolution = $1;
  }
  else {
   warn "Image resolution not found\n";
  }

  $page->mediabox($w*72/$resolution, $h*72/$resolution);

# Add OCR as annotation
  if (defined($slist -> {data}[$_][3]) and $slist -> {data}[$_][3] ne '') {
   my $ant=$page->annotation;
   $ant->text($slist -> {data}[$_][3],
    -rect=>[0,0,$w*72/$resolution,$h*72/$resolution]);

# Add OCR as text behind the scan
   my $font = $pdf->corefont('Times-Roman');
   my $text = $page->text;
   my $size = 1;
   $text->font($font, $size);
   $text->strokecolor('white');
   $text->fillcolor('white');
   my $y = $h*72/$resolution;
   foreach my $line (split("\n", $slist -> {data}[$_][3])) {
    my $x = 0;

# Add a word at a time in order to linewrap
    foreach my $word (split(' ', $line)) {
     if (length($word)*$size+$x > $w) {
      $x = 0;
      $y -= $size;
     }
     $text -> translate($x, $y);
     $word = ' '.$word if ($x > 0);
     $x += $text->text($word);
    }
    $y -= $size;
   }
  }

# Add scan
  my $gfx = $page->gfx;
  my $imgobj = $pdf->image_tiff($slist -> {data}[$_][2]);
  $gfx->image($imgobj,0,0,72/$resolution);
 }
 $pdf->save;
 $pdf->end();
}


# Throw up file selector and save selected pages as PDF under given name.

sub save_PDF {

# cd back to cwd to save
 chdir $SETTING{'cwd'};

# Set up file selector
 my $file_chooser = Gtk2::FileChooserDialog -> new($d->get('PDF filename'),
                                                    $windowp, 'save',
                                                    'gtk-cancel' => 'cancel',
                                                    'gtk-save' => 'ok');
 $file_chooser -> set_default_response('ok');

 if ('ok' eq $file_chooser->run) {
  my $filename = $file_chooser -> get_filename;
  $filename = $filename.".pdf" if ($filename !~ /\.pdf$/i);
  if (-e $filename) {
   my $dialog = Gtk2::MessageDialog -> new ($file_chooser,
    'destroy-with-parent',
    'question', # message type
    'ok-cancel', # which set of buttons?
    sprintf($d->get("File %s exists.\nReally overwrite?"), $filename));
   my $response = $dialog -> run;
   $dialog -> destroy;
   if ($response ne 'ok') {
    $file_chooser -> destroy;
    return TRUE;
   }
  }

# Update cwd
  $SETTING{'cwd'} = dirname($filename);

# Create the PDF
  create_PDF($filename);
  
  $windowp -> hide if defined $windowp;
 }

 $file_chooser -> destroy;

# cd back to tempdir
 chdir $dir;
}


sub save_PDF_dialog {

# $SETTING{'Page range'} = 'selected' if $SETTING{'RMB'};
#warn "rmb pdf $SETTING{'RMB'} $SETTING{'Page range'}\n";

 if ($uimanager->get_widget('/MenuBar/Edit/Options') -> get_active) {

  if (defined $windowp) {
   $windowp -> present;
   return;
  }

# PDF pop-up window
  ($windowp, my $vbox) = create_window($window, $d->get('Save as PDF'), FALSE);

# PDF options
  my ($entrya, $entryt, $entrys, $entryk) = add_PDF_options ($vbox);

# Frame for page range
  my ($buttona, $buttonc, $buttons) = add_page_range($vbox);

# HBox for buttons
  my $hboxb = Gtk2::HBox -> new;
  $vbox -> pack_start ($hboxb, FALSE, TRUE, 0);

# Save button
  my $sbutton = Gtk2::Button -> new_from_stock('gtk-save');
  $hboxb -> pack_start( $sbutton, TRUE, TRUE, 0 );
  $sbutton -> signal_connect (clicked => sub {
   update_PDF_settings($entrya, $entryt, $entrys, $entryk);
   save_PDF();
  } );

# Cancel button
  my $cbutton = Gtk2::Button -> new_from_stock('gtk-cancel');
  $hboxb -> pack_end( $cbutton, FALSE, FALSE, 0 );
  $cbutton -> signal_connect( clicked => sub { $windowp -> hide; } );

  $windowp -> show_all;
 }
 else {
  save_PDF();
 }
}


# Display page selector and on save a fileselector.

sub save_TIFF_dialog {

# $SETTING{'Page range'} = 'selected' if $SETTING{'RMB'};

 if ($uimanager->get_widget('/MenuBar/Edit/Options') -> get_active) {

  if (defined $windowt) {
   $windowt -> present;
   return;
  }

  ($windowt, my $vbox) = create_window($window, $d->get('Save as TIFF'), FALSE);

# Frame for page range
  my ($buttona, $buttonc, $buttons) = add_page_range($vbox);

  my @compression = (
   [ 'lzw', $d->get('LZW'), $d->get('Compress output with Lempel-Ziv & Welch encoding.') ],
   [ 'zip', $d->get('Zip'), $d->get('Compress output with deflate encoding.') ],
   [ 'jpeg', $d->get('JPEG'), $d->get('Compress output with JPEG encoding.') ],
   [ 'packbits', $d->get('Packbits'), $d->get('Compress output with Packbits encoding.') ],
   [ 'g3', $d->get('G3'), $d->get('Compress output with CCITT Group 3 encoding.') ],
   [ 'g4', $d->get('G4'), $d->get('Compress output with CCITT Group 4 encoding.') ],
   [ 'none', $d->get('None'), $d->get('Use no compression algorithm on output.') ],
  );

# Compression ComboBox
  my $hboxc = Gtk2::HBox -> new;
  $vbox -> pack_start($hboxc, TRUE, TRUE, 0);
  my $label = Gtk2::Label -> new ($d->get('Compression'));
  $hboxc -> pack_start ($label, FALSE, FALSE, 0);

# Set up quality spinbutton here so that it can be shown or hidden by callback
  my $hboxq = Gtk2::HBox -> new;
  $vbox -> pack_start($hboxq, TRUE, TRUE, 0);
  $label = Gtk2::Label -> new ($d->get('JPEG Quality'));
  $hboxq -> pack_start ($label, FALSE, FALSE, 0);
  my $spinbuttonq = Gtk2::SpinButton -> new_with_range(1, 100, 1);
  if (defined($SETTING{'quality'})) {
   $spinbuttonq->set_value($SETTING{'quality'});
  }
  else {
   $spinbuttonq->set_value(75);
  }
  $hboxq -> pack_end ($spinbuttonq, FALSE, FALSE, 0);

# Fill compression ComboBox
  my $i = 0;
  my $o;
  my $combobc = Gtk2::ComboBox->new_text;
  foreach my $option ( @compression ) {
   $combobc->append_text ($option->[1]);

   $o = $i
    if (defined($SETTING{'tiff compression'})
        and $option->[0] eq $SETTING{'tiff compression'});
   ++$i;
  }
  $o = 1 if (! defined $o);
  $combobc -> set_active ($o);
  $combobc -> signal_connect (changed => sub {
   if ($compression[$combobc->get_active][0] eq 'jpeg') {
    $hboxq -> show_all;
   }
   else {
    $hboxq -> hide_all;
   }
  });
  $hboxc -> pack_end ($combobc, FALSE, FALSE, 0);

# HBox for buttons
  my $hboxb = Gtk2::HBox -> new;
  $vbox -> pack_start ($hboxb, FALSE, TRUE, 0);

# Save button
  my $sbutton = Gtk2::Button -> new_from_stock('gtk-save');
  $hboxb -> pack_start( $sbutton, TRUE, TRUE, 0 );
  $sbutton -> signal_connect (clicked => sub {

# dig out the compression
   $SETTING{'tiff compression'} = $compression[$combobc->get_active][0];
   $SETTING{'quality'} = $spinbuttonq->get_value
    if ($SETTING{'tiff compression'} eq 'jpeg');

   save_TIFF();
  } );

# Cancel button
  my $cbutton = Gtk2::Button -> new_from_stock('gtk-cancel');
  $hboxb -> pack_end( $cbutton, FALSE, FALSE, 0 );
  $cbutton -> signal_connect( clicked => sub { $windowt -> hide; } );

  $windowt -> show_all;
  $hboxq -> hide_all if ($compression[$combobc->get_active][0] ne 'jpeg');
 }
 else {
  save_TIFF();
 }
}


sub save_TIFF {

# cd back to cwd to save
 chdir $SETTING{'cwd'};

# Set up file selector
 my $file_chooser = Gtk2::FileChooserDialog -> new($d->get('TIFF filename'),
                                                     $windowt, 'save',
                                                     'gtk-cancel' => 'cancel',
                                                     'gtk-save' => 'ok');
 $file_chooser -> set_default_response('ok');

 if ('ok' eq $file_chooser->run) {
  my $filename = $file_chooser -> get_filename;
  $filename = $filename.".tif" if ($filename !~ /\.tif$/i);
  if (-e $filename) {
   my $dialog = Gtk2::MessageDialog -> new ($file_chooser,
    'destroy-with-parent',
    'question', # message type
    'ok-cancel', # which set of buttons?
    sprintf($d->get("File %s exists.\nReally overwrite?"), $filename));
   my $response = $dialog -> run;
   $dialog -> destroy;
   return TRUE if ($response ne 'ok');
  }

# Update cwd
  $SETTING{'cwd'} = dirname($filename);

# cd back to tempdir
  chdir $dir;

# fill $pagelist with filenames depending on which radiobutton is active
  my ($pagelist, $n) = get_pagelist();

  my $compression = $SETTING{'tiff compression'};
  $compression .= ':'.$SETTING{'quality'} if ($compression eq 'jpeg');

# Create the tiff
  system ("tiffcp -c $compression $pagelist \"$filename\"");

  $windowt -> hide if defined $windowt;
 }

 $file_chooser -> destroy;
}


# Display page selector and on save a fileselector.

sub save_djvu_dialog {

# $SETTING{'Page range'} = 'selected' if $SETTING{'RMB'};

 if ($uimanager->get_widget('/MenuBar/Edit/Options') -> get_active) {

  if (defined $windowv) {
   $windowv -> present;
   return;
  }

  ($windowv, my $vbox) = create_window($window, $d->get('Save as DjVu'), FALSE);

# Compression ComboBox
  my $hboxc = Gtk2::HBox -> new;
  $vbox -> pack_start($hboxc, TRUE, TRUE, 0);
  my $label = Gtk2::Label -> new ($d->get('Compression'));
  $hboxc -> pack_start ($label, FALSE, FALSE, 0);
  my $combobc = Gtk2::ComboBox->new_text;

# Fill compression ComboBox
  $combobc->append_text ($d->get('Bitonal'));
  $hboxc -> pack_end ($combobc, FALSE, FALSE, 0);

# Frame for page range
  my ($buttona, $buttonc, $buttons) = add_page_range($vbox);

# HBox for buttons
  my $hboxb = Gtk2::HBox -> new;
  $vbox -> pack_start ($hboxb, FALSE, TRUE, 0);

# Save button
  my $sbutton = Gtk2::Button -> new_from_stock('gtk-save');
  $hboxb -> pack_start( $sbutton, TRUE, TRUE, 0 );
  $sbutton -> signal_connect (clicked => sub { save_djvu(); } );

# Cancel button
  my $cbutton = Gtk2::Button -> new_from_stock('gtk-cancel');
  $hboxb -> pack_end( $cbutton, FALSE, FALSE, 0 );
  $cbutton -> signal_connect( clicked => sub { $windowv -> hide; } );

  $windowv -> show_all;
 }
 else {
  save_djvu();
 }
}


sub save_djvu {

# cd back to cwd to save
 chdir $SETTING{'cwd'};

# Set up file selector
 my $file_chooser = Gtk2::FileChooserDialog -> new($d->get('DjVu filename'),
                                                     $windowv, 'save',
                                                     'gtk-cancel' => 'cancel',
                                                     'gtk-save' => 'ok');
 $file_chooser -> set_default_response('ok');

 if ('ok' eq $file_chooser->run) {
  my $filename = $file_chooser -> get_filename;
  $filename = $filename.".djvu" if ($filename !~ /\.djvu$/i);
  if (-e $filename) {
   my $dialog = Gtk2::MessageDialog -> new ($file_chooser,
    'destroy-with-parent',
    'question', # message type
    'ok-cancel', # which set of buttons?
    sprintf($d->get("File %s exists.\nReally overwrite?"), $filename));
   my $response = $dialog -> run;
   $dialog -> destroy;
   return TRUE if ($response ne 'ok');
  }

# Update cwd
  $SETTING{'cwd'} = dirname($filename);

# cd back to tempdir
  chdir $dir;

# fill $pagelist with filenames depending on which radiobutton is active
  my ($pagelist, $n) = get_pagelist();
   
  if ($n > 1) {
   my @pagelist = split / /, $pagelist;
   foreach (@pagelist) {

    my (undef, $djvu) = tempfile(DIR => $dir, SUFFIX => '.djvu');

# Create the djvu
    system ("cjb2 $_ $djvu");

    $_ = $djvu;
   }
   $pagelist = "@pagelist";
   system ("djvm -c $filename $pagelist");
  }
  else {
   system ("cjb2 $pagelist $filename");
  }

  $windowv -> hide;
 }

 $file_chooser -> destroy;
}


# Display page selector and email.

sub email {

 if (defined $windowe) {
  $windowe -> present;
  return;
 }

# $SETTING{'Page range'} = 'selected' if $SETTING{'RMB'};
 ($windowe, my $vbox) = create_window($window, $d->get('Email as PDF'), FALSE);

# PDF options
 my ($entrya, $entryt, $entrys, $entryk) = add_PDF_options ($vbox);

# Frame for page range
 my ($buttona, $buttonc, $buttons) = add_page_range($vbox);

# HBox for buttons
 my $hboxb = Gtk2::HBox -> new;
 $vbox -> pack_start ($hboxb, FALSE, TRUE, 0);

# OK button
 my $sbutton = Gtk2::Button -> new_from_stock('gtk-ok');
 $hboxb -> pack_start( $sbutton, TRUE, TRUE, 0 );
 $sbutton -> signal_connect (clicked => sub {

# fill $pagelist with filenames depending on which radiobutton is active
  my ($pagelist, $n) = get_pagelist();

# Set options
  update_PDF_settings($entrya, $entryt, $entrys, $entryk);

  my (undef, $pdf) = tempfile(DIR => $dir, SUFFIX => '.pdf');

# Create the PDF
  create_PDF($pdf);

  system ("xdg-email --attach $pdf 'x\@y'");

  $windowe -> hide;

 } );

# Cancel button
 my $cbutton = Gtk2::Button -> new_from_stock('gtk-cancel');
 $hboxb -> pack_end( $cbutton, FALSE, FALSE, 0 );
 $cbutton -> signal_connect( clicked => sub { $windowe -> hide; } );

 $windowe -> show_all;
}


# Scan

sub scan_dialog {

 if (defined $windows) {
  $windows -> present;
  return;
 }

 my $output;
 if (! @test) {
  $output = `scanimage --formatted-device-list="'%i','%d','%v %m'
" 2>/dev/null`;
  if ($? ne 0) {
   my $dialog = Gtk2::MessageDialog -> new ($window,
                                           'destroy-with-parent',
                                           'error',
                                           'close',
                                           'scanimage '.$d->get('not found'));
   $dialog -> run;
   $dialog -> destroy;
   return FALSE;
  }
  else {
   if ($output eq "") {
    my $dialog = Gtk2::MessageDialog -> new ($window,
                                            'destroy-with-parent',
                                            'error',
                                            'close',
                                            $d->get('No scanners found'));
    $dialog -> run;
    $dialog -> destroy;
    return FALSE;
   }
  }
 }

# scan pop-up window
 ($windows, my $vbox) = create_window($window, $d->get('Scan Document'), FALSE);

# HBox for devices
 my $hboxd = Gtk2::HBox -> new;
 $vbox -> pack_start ($hboxd, FALSE, FALSE, 0);

# device list
 my $labeld = Gtk2::Label -> new ($d->get('Device'));
 $hboxd -> pack_start ($labeld, FALSE, FALSE, 0);

# Need to define this here to reference it later.
 $vboxd = Gtk2::VBox -> new;
 my $ev = Gtk2::EventBox->new;
 $combobd = Gtk2::ComboBox->new_text;
 $ev->add($combobd);

# parse out the device and model names
 if (! @test) {
  my $device = substr($output, 0, index($output, "\n")+1);
  $output = substr($output, index($output, "\n")+1, length($output));
  while ($device =~ /'(\d*)','(.*)','(.*)'/) {
   $device[$1] = $2;

# Convert all underscores to spaces
   ($model[$1] = $3) =~ s/_/ /g;
   $device = substr($output, 0, index($output, "\n")+1);
   $output = substr($output, index($output, "\n")+1, length($output));
  }
 }

# Note any duplicate device names and delete if necessary
 my %seen;
 my $i = 0;
 while ($i < @device) {
  $seen{ $device[$i] }++;
  if ($seen{$device[$i]} > 1) {
   splice @device, $i, 1;
   splice @model, $i, 1;
  }
  else {
   $i++;
  }
 }

# Note any duplicate model names and add the device if necessary
 undef %seen;
 foreach ( @model ) {
  $seen{ $_ }++;
 }
 for (my $i = 0; $i < @model; $i++) {
  $model[$i] .= " on $device[$i]" if ($seen{$model[$i]} > 1);
 }

# read the model names into the combobox
 foreach (@model) {
  $combobd->append_text ($_);
 }
 $combobd -> signal_connect (changed => sub {
  update_options($vboxd, $device[$combobd -> get_active]);
  $vboxd -> show_all;
  hide_custom();
 });
 $tooltips -> set_tip ($ev,
                         $d->get('Sets the device to be used for the scan'));
 $hboxd -> pack_end ($ev, FALSE, FALSE, 0);

# If device not set by config and there is a default device, then set it
 if (! defined($SETTING{'device'})
      and defined($output) and $output =~ /default device is `(.*)'/) {
  $SETTING{'device'} = $1;
 }

# If device in settings then set it
 my $o = 0;
 if (defined($SETTING{'device'})) {
  for (my $i = 0; $i <= $#device; $i++) {
   $o = $i if ($SETTING{'device'} eq $device[$i]);
  }
 }
# This then fires the callback, updating the options, so no need to do it further down.
 $combobd -> set_active($o);
   
# Frame for # pages
 my $framen = Gtk2::Frame -> new($d->get('# Pages'));
 $vbox -> pack_start ($framen, FALSE, FALSE, 0);
 my $vboxn = Gtk2::VBox -> new;
 $vboxn -> set_border_width($border_width);
 $framen -> add ($vboxn);

#the first radio button has to set the group,
#which is undef for the first button
# All button
 my $buttona = Gtk2::RadioButton -> new(undef, $d->get('All'));
 $tooltips -> set_tip ($buttona, $d->get('Scan all pages'));
 $vboxn -> pack_start($buttona, TRUE, TRUE, 0);
 my @groupn = $buttona -> get_group;

# Entry button
 my $hboxn = Gtk2::HBox -> new;
 $vboxn -> pack_start($hboxn, TRUE, TRUE, 0);
 my $buttone = Gtk2::RadioButton -> new(@groupn, "#:");
 $tooltips -> set_tip ($buttone, $d->get('Set number of pages to scan'));
 $hboxn -> pack_start($buttone, FALSE, FALSE, 0);

# Number of pages
 my $spin_button = Gtk2::SpinButton -> new_with_range(1, 99, 1);
 $tooltips -> set_tip ($spin_button, $d->get('Set number of pages to scan'));
 $spin_button -> signal_connect ('value-changed' => sub {
  $buttone -> set_active(TRUE); # Set the radiobutton active
 });
 $hboxn -> pack_end ($spin_button, FALSE, FALSE, 0);

# Set default
 if (defined($SETTING{'pages to scan'})) {
  if ($SETTING{'pages to scan'} eq 'all') {
   $buttona -> set_active(TRUE);
  }
  else {
   $buttone -> set_active(TRUE);
   $spin_button -> set_value($SETTING{'pages to scan'});
  }
 }
 else {
  $buttone -> set_active(TRUE);
 }

# Frame for source document
 $frames = Gtk2::Frame -> new($d->get('Source document'));
 $vbox -> pack_start ($frames, FALSE, FALSE, 0);
 my $vboxs = Gtk2::VBox -> new;
 $vboxs -> set_border_width($border_width);
 $frames -> add ($vboxs);

# Single sided button
 my $buttons = Gtk2::RadioButton -> new(undef, $d->get('Single sided'));
 $tooltips -> set_tip ($buttons, $d->get('Source document is single-sided'));
 $vboxs -> pack_start($buttons, TRUE, TRUE, 0);
 my @groups = $buttons -> get_group;

# Double sided button
 my $buttond = Gtk2::RadioButton -> new(@groups, $d->get('Double sided'));
 $tooltips -> set_tip ($buttond, $d->get('Source document is double-sided'));
 $vboxs -> pack_start($buttond, FALSE, FALSE, 0);

# Facing/reverse page button
 my $hboxs = Gtk2::HBox -> new;
 $vboxs -> pack_start($hboxs, TRUE, TRUE, 0);
 my $labels = Gtk2::Label -> new ($d->get('Side to scan'));
 $hboxs -> pack_start($labels, FALSE, FALSE, 0);

 $ev = Gtk2::EventBox->new;
 my $combobs = Gtk2::ComboBox -> new_text;
 $ev->add($combobs);
 my @side = ($d->get('Facing'), $d->get('Reverse'));
 foreach (@side) {
  $combobs -> append_text ($_);
 }
 $combobs -> signal_connect (changed => sub {
  $buttond -> set_active(TRUE); # Set the radiobutton active
 });
 $tooltips -> set_tip ($ev,
              $d->get('Sets which side of a double-sided document is scanned'));
 $combobs -> set_active(0);
# Have to do this here because setting the facing combobox switches it
 $buttons -> set_active(TRUE);
 $hboxs -> pack_end ($ev, FALSE, FALSE, 0);

# Checkbox for OCR
 my $obutton = Gtk2::CheckButton -> new($d->get('OCR scanned pages'));
 $obutton -> set_active(TRUE);
 if ($dependencies{gocr} != 0) {
  $obutton -> set_sensitive(FALSE);
  $obutton -> set_active(FALSE);
 }
 $vbox -> pack_start($obutton, TRUE, TRUE, 0);

# Frame for device-dependent options
 my $framed = Gtk2::Frame -> new($d->get('Device-dependent options'));
 $vbox -> pack_start ($framed, FALSE, FALSE, 0);
 $vboxd -> set_border_width($border_width);
 $framed -> add ($vboxd);

# HBox for buttons
 my $hboxb = Gtk2::HBox -> new;
 $vbox -> pack_end ($hboxb, FALSE, FALSE, 0);

# Scan button
 my $sbutton = Gtk2::Button -> new($d->get('Scan'));
 $hboxb -> pack_start( $sbutton, TRUE, TRUE, 0 );
 $sbutton -> signal_connect (clicked => sub {

# Update undo/redo buffers
  take_snapshot();

# Get selected device
  $SETTING{'device'} = $device[$combobd -> get_active];

# Get device-specific options
  my %options;
  my @child = $vboxd -> get_children;
  foreach my $hbox (@child) {
   my $key;
   if ($hbox -> isa('Gtk2::HBox')) {
    my @child = $hbox -> get_children;
    foreach my $widget (@child) {
     if ($widget -> isa('Gtk2::Label')) {
      $key = get_key(\%ddo, $widget -> get_label);
     }
     elsif ($widget -> isa('Gtk2::EventBox')) {
      $widget = $widget -> get_child;

# ignore artificial paper size option
      $SETTING{$key} = get_value(\%ddo, $key, $widget -> get_active_text);
      $options{$key} = $SETTING{$key} if ($key ne 'Paper size');
     }
     elsif ($widget -> isa('Gtk2::SpinButton')) {
      $options{$key} = $widget -> get_value;
      $SETTING{$key} = $options{$key};
     }
    }
   }
  }

# Workaround for HP bug mode=lineart and compression!=none
  $options{compression} = 'None' if (defined($ddo{compression}));

# Get selected number of pages
  my $npages;
  if ($buttone -> get_active) {
   $SETTING{'pages to scan'} = $spin_button -> get_value;
   $npages = $SETTING{'pages to scan'};
  }
  else {
   $SETTING{'pages to scan'} = 'all';
   $npages = 0;
  }

# Start from next available page
  my $start;
  if ($#{$slist -> {data}} > -1) {
   $start = $slist -> {data}[$#{$slist -> {data}}][0] + 1;
  }
  else {
   $start = 1;
  }

# Set step according to single/double sided, facing/reverse page
  my $step = 1;
  if ($buttond -> get_active) {
   if (($combobs -> get_active) == 0) { # facing page
    $step = 2;
   }
   else { # reverse page
    if ($start == 1) {
     my $dialog = Gtk2::MessageDialog -> new ($windows,
      'destroy-with-parent',
      'error', # message type
      'cancel', # which set of buttons?
      $d->get('Must scan facing pages first'));
     $dialog -> run;
     $dialog -> destroy;
     return TRUE;
    }

    $step = -2;

# Check that there is room in the list for the reverse pages
    my $i = 1;
    my $j = $#{$slist -> {data}};
    while ($slist->{data}[$j][0] != $start+$i*$step
           and $start+$i*$step > 0
           and $j > -1) {
     if ($slist->{data}[$j][0] > $start+$i*$step) {
      --$j;
     }
     else {
      ++$i;
     }
    }
    if ($buttone -> get_active) {
     if (($spin_button -> get_value) > $i) {
      my $dialog = Gtk2::MessageDialog -> new ($windows,
       'destroy-with-parent',
       'error', # message type
       'cancel', # which set of buttons?
       $d->get("Cannot scan more reverse pages\nthan facing pages"));
      $dialog -> run;
      $dialog -> destroy;
      return TRUE;
     }
    }

# If user hasn't specified number of pages, then set number that is possible
    else {
     $npages = $i;
    }
   }
  }
  if ($SETTING{'frontend'} eq 'scanimage') {
   scanimage($SETTING{'device'}, $npages, $start-$step, $step,
                                                $obutton->get_active, %options);
  }
  else {
   scanadf($SETTING{'device'}, $npages, $start-$step, $step,
                                                $obutton->get_active, %options);
  }
 } );

# Cancel button
 my $cbutton = Gtk2::Button -> new_from_stock('gtk-close');
 $hboxb -> pack_end( $cbutton, FALSE, FALSE, 0 );
 $cbutton -> signal_connect( clicked => sub { $windows -> hide; } );

# Show window
 $windows -> show_all;
 hide_custom();
}


# Carry out the scan with scanimage and the options passed.

sub scanimage {
 my ($device, $npages, $offset, $step, $ocr, %options) = @_;

 require IPC::Open3;
 require IO::Handle;

# inverted commas needed for strange characters in device name
 $device = "--device-name='$device'";
 if ($npages != 0) {
  $npages = "--batch-count=$npages";
 }
 else {
  $npages = "";
 }

# Device-specific options
 my @options = hash2options(%options);

# Add basic options
 push @options, '--format=tiff';
 push @options, '--batch';

# Make sure we are in temp directory
 chdir $dir;

# Create command
 my $cmd = "scanimage $device $npages @options";
 warn "$cmd\n" if $debug;

 if (! @test) {

# flag to ignore error messages after cancelling scan
  my $cancel = FALSE;

# Interface to scanimage
  my ($write, $read);
  my $error = IO::Handle -> new; # this needed because of a bug in open3.
  my $pid = IPC::Open3::open3($write, $read, $error, $cmd)+1;
  
  my $dialog = Gtk2::Dialog -> new ($d->get('Scanning')."...", $windows,
                                    'destroy-with-parent',
                                    'gtk-cancel' => 'cancel');
  my $label = Gtk2::Label -> new ($d->get('Scanning')."...");
  $dialog -> vbox -> add ($label);

# Ensure that the dialog box is destroyed when the user responds.
  $dialog -> signal_connect (response => sub {
   $_[0] -> destroy;
   local $SIG{INT} = 'IGNORE';
   kill INT => $pid;
   $cancel = TRUE;
  });
  $dialog -> show_all;
 
  my $line;
  Glib::IO -> add_watch(fileno($error), ['in', 'hup'], sub {
   my ($fileno, $condition) = @_;
   my $buffer;
   if ($condition & 'in') { # bit field operation. >= would also work

# Only reading one buffer, rather than until sysread gives EOF because things seem to be strange for stderr
    sysread $error, $buffer, 1024;
    $line .= $buffer;

    while ($line =~ /\n/) {
     if ($line =~ /^Scanning (-?\d*) pages/) {
      $label -> set_text($d->get('Scanning')." $1 ".$d->get('pages')."...");
     }
     elsif ($line =~ /^Scanning page (\d*)/) {
      $label -> set_text(sprintf($d->get('Scanning page %i...'), $1*$step+$offset));
     }
     elsif ($line =~ /^Scanned page (\d*)\. \(scanner status = 5\)/) {

# If the scan can't be loaded then blow the scanning dialog away and
# show an error
      if (! import_scan ("out$1.tif", $1*$step+$offset, $ocr)) {
       $dialog -> destroy;
       $dialog = Gtk2::MessageDialog -> new ($windows, 'destroy-with-parent',
                         'error', 'close', $d->get('Unable to load image'));
       $dialog -> run;
       $dialog -> destroy;
       return FALSE;
      }
     }
     elsif ($line =~ /^\[snapscan\] Scanner warming up - waiting \d* seconds/) {
      $label -> set_text($d->get('Scanner warming up'));
     }
     elsif ($line =~ /^Scanned page \d*\. \(scanner status = 7\)/) {
      ;
     }
     elsif ($line =~ /^scanimage: sane_start: Document feeder out of documents/) {
      ;
     }
     elsif ($cancel
             and ($line =~ /^scanimage: sane_start: Error during device I\/O/
                  or $line =~ /^scanimage: received signal 2/
                  or $line =~ /^scanimage: trying to stop scanner/)) {
      ;
     }
     elsif ($line =~ /^scanimage: rounded/) {
      warn substr($line, 0, index($line, "\n")+1);
     }
     else {
      $dialog -> destroy;
      my $text = $d->get('Unknown message: ')
                                         . substr($line, 0, index($line, "\n"));
warn "$text\n";
      $dialog = Gtk2::MessageDialog -> new ($windows,
                                               'destroy-with-parent',
                                               'error',
                                               'close',
                                               $text);
      $dialog -> run;
      $dialog -> destroy;
     }
     $line = substr($line, index($line, "\n")+1, length($line));
    }
   }
# Because reading from stderr with sysread seems to cause problems, only
# allow the hup if sure an empty buffer has been read.
   if (($condition & 'hup') and (! defined($buffer) or $buffer eq "")) { # bit field operation. >= would also work
    close $read;
    $dialog -> destroy;
    return FALSE;  # uninstall
   }
   return TRUE;  # continue without uninstalling
  });
 }
 else {
  warn "$cmd\n";
 }
}


# Carry out the scan with scanadf and the options passed.

sub scanadf {
 my ($device, $npages, $offset, $step, $ocr, %options) = @_;

 require IPC::Open3;
 require IO::Handle;

# inverted commas needed for strange characters in device name
 $device = "--device-name='$device'";
 my $end;
 if ($npages != 0) {
  $end = "--end-count=".($npages+1);
 }
 else {
  $end = "";
 }
 my $start  = "--start-count=1";

# Device-specific options
 my @options = hash2options(%options);

# Add basic options
 push @options, '-o out%d.pnm -S scanscript.sh --script-wait';

# Make sure we are in temp directory
 chdir $dir;

# Create scanscript.sh
 my $script = <<END;
#!/bin/bash
convert \$1 \${1\%.*}.tif
status=\$?
echo "gscan2pdf created" \${1\%.*}.tif >> "/dev/stderr"
exit \$status
END

 open SCRIPT, "> scanscript.sh" or die "Can't create scanscript.sh: $!";
 print SCRIPT $script;
 close SCRIPT;
 chmod 0755, "scanscript.sh"; # u+rwx,go+rw

# Create command
 my $cmd = "scanadf $device $start $end @options > /dev/stderr";
 warn "$cmd\n" if $debug;

 if (! @test) {

# Interface to frontend
  my ($write, $read);
  my $error = IO::Handle -> new; # this needed because of a bug in open3.
  my $pid = IPC::Open3::open3($write, $read, $error, $cmd);
  
  my $dialog = Gtk2::Dialog -> new ($d->get('Scanning')."...", $windows,
                                    'destroy-with-parent',
                                    'gtk-cancel' => 'cancel');
  my $label = Gtk2::Label -> new ($d->get('Scanning')."...");
  $dialog -> vbox -> add ($label);

# Ensure that the dialog box is destroyed when the user responds.
  $dialog -> signal_connect (response => sub {
   $_[0] -> destroy;
   local $SIG{HUP} = 'IGNORE';
   kill HUP => $pid;
  });
  $dialog -> show_all;
 
  my $line;
  Glib::IO -> add_watch(fileno($error), ['in', 'hup'], sub {
   my ($fileno, $condition) = @_;
   my $buffer;
   if ($condition & 'in') { # bit field operation. >= would also work

# Only reading one buffer, rather than until sysread gives EOF because things seem to be strange for stderr
    sysread $error, $buffer, 1024;
    $line .= $buffer;

    while ($line =~ /\n/) {
     if ($line =~ /^Scanned document out(\d*)\.pnm/) {
      $label -> set_text(sprintf($d->get('Scanned page %i...'), $1*$step+$offset));
     }
     elsif ($line =~ /^\[snapscan\] Scanner warming up - waiting \d* seconds/) {
      $label -> set_text($d->get('Scanner warming up'));
     }
     elsif ($line =~ /^gscan2pdf created out(\d*)\.tif/) {

# If the scan can't be loaded then blow the scanning dialog away and
# show an error
      if (! import_scan ("out$1.tif", $1*$step+$offset, $ocr)) {
       $dialog -> destroy;
       $dialog = Gtk2::MessageDialog -> new ($windows, 'destroy-with-parent',
                         'error', 'close', $d->get('Unable to load image'));
       $dialog -> run;
       $dialog -> destroy;
       return FALSE;
      }
     }
     elsif ($line =~ /^Scanned \d* pages/) {
      ;
     }
     elsif ($line =~ /^scanadf: rounded/) {
      warn substr($line, 0, index($line, "\n")+1);
     }
     else {
      $dialog -> destroy;
      my $text = $d->get('Unknown message: ')
                                         . substr($line, 0, index($line, "\n"));
warn "$text\n";
      $dialog = Gtk2::MessageDialog -> new ($windows,
                                               'destroy-with-parent',
                                               'error',
                                               'close',
                                               $text);
      $dialog -> run;
      $dialog -> destroy;
     }
     $line = substr($line, index($line, "\n")+1, length($line));
    }
   }
# Because reading from stderr with sysread seems to cause problems, only
# allow the hup if sure an empty buffer has been read.
   if (($condition & 'hup') and (! defined($buffer) or $buffer eq "")) { # bit field operation. >= would also work
    close $read;
    $dialog -> destroy;
    return FALSE;  # uninstall
   }
   return TRUE;  # continue without uninstalling
  });
 }
 else {
  warn "$cmd\n";
 }
}


# Take a hash of options and push them onto an array

sub hash2options {
 my %options = @_;

 my (@options, $key, $value);
 while (($key, $value) = each(%options)) {
  if ($key eq 'x' or $key eq 'y' or $key eq 'l' or $key eq 't') {
   push @options, "-$key $value";
  }
  else {
   push @options, "--$key='$value'";
  }
 }
 return @options;
}


# Take new scan and display it

sub import_scan {
 my ($ofilename, $page, $ocr) = @_;
 my (undef, $filename) = tempfile(DIR => $dir, SUFFIX => '.tif');
 system("mv $ofilename $filename");

# Add to the page list
 $page = $#{$slist -> {data}}+2 if (! defined($page));

# Block the row-changed signal whilst adding the scan (row) and sorting it.
 $slist -> get_model -> signal_handler_block($slist -> {signalid});
 push @{$slist -> {data}},
                     [$page, get_pixbuf($filename, $heightt, $widtht), $filename];
 manual_sort_by_column ($slist, 0);
 $slist -> get_model -> signal_handler_unblock($slist -> {signalid});

# Select new page, deselecting others. This fires the select callback,
# displaying the page
 $slist -> get_selection -> unselect_all;
 my @page;

# Due to the sort, must search for new page
 $page[0] = 0;
# $page[0] < $#{$slist -> {data}} needed to prevent infinite loop in case of
# error importing.
 ++$page[0] while ($page[0] < $#{$slist -> {data}}
                    and $slist -> {data}[$page[0]][0] != $page);

 $slist -> select(@page);
 
 ocr_page(@page) if $ocr;

 update_uimanager();

 return TRUE;
}


# Helpers:
sub compare_numeric_col { $_[0] <=> $_[1] }
sub compare_text_col { $_[0] cmp $_[1] }


# Manual one-time sorting of the simplelist's data

sub manual_sort_by_column {
 my ($slist, $sortcol) = @_;

# The sort function depends on the column type
 my %sortfuncs = ( 'Glib::Scalar' => \&compare_text_col,
                   'Glib::String' => \&compare_text_col,
                   'Glib::Int'    => \&compare_numeric_col,
                   'Glib::Double' => \&compare_numeric_col, );

# Remember, this relies on the fact that simplelist keeps model
# and view column indices aligned.
 my $sortfunc = $sortfuncs{$slist->get_model->get_column_type($sortcol)};

# Deep copy the tied data so we can sort it. Otherwise, very bad things happen.
 my @data = map { [ @$_ ] } @{ $slist->{data} };
 @data = sort { $sortfunc->($a->[$sortcol], $b->[$sortcol]) } @data;

 @{$slist->{data}} = @data;
}


# Delete the selected scans

sub delete_pages {

# Update undo/redo buffers
 take_snapshot();

 my @pages = $slist -> get_selected_indices;
 my @page = @pages;
 while ($#pages > -1) {
  splice @{ $slist->{data} }, $pages[0], 1;
  @pages = $slist -> get_selected_indices;
 }

# Select nearest page to last current page
 if ($#{$slist->{data}} > -1 and @page) {

# Select just the first one
  @page = ($page[0]);
  $page[0] = $#{$slist->{data}} if ($page[0] > $#{$slist->{data}});
  $slist->select(@page);
 }

 update_uimanager();
}


# Select all scans

sub select_all {
 $slist -> get_selection -> select_all;
}


# Display about dialog

sub about {
 my $about = Gtk2::AboutDialog->new;
# Gtk2::AboutDialog->set_url_hook ($func, $data=undef);
# Gtk2::AboutDialog->set_email_hook ($func, $data=undef);
 $about->set_name ($program);
 $about->set_version ($version);
 $about->set_authors ('Jeff Ratcliffe');
 $about->set_comments ($d->get('To aid the scan-to-PDF process'));
 $about->set_copyright ($d->get('Copyright 2006--2007 Jeffrey Ratcliffe'));
 $about->set_license ($d->get('Licensed under the GPLv2'));
 $about->set_website ('http://gscan2pdf.sf.net');
 my $translators = <<EOS;
Jen Fraggle
Alberto Boiti
Petr Jelnek
Alexandre Prokoudine
mecedesjorge
cwchien
booxter
Daniel Nylander
Tikkel
Christoph Langner
Nicolas Velin
Mathieu Goeminne
Simon Leblanc
florian
Eric Spierings
Th3n3k
joeb
Maddy67
Pierre Slamich
EOS
 $about->set_translator_credits ($translators);
 $about->set_artists ('lodp');
 $about->run;
 $about->destroy;
}


# Check that tiffcp exists

sub check_utils {
 system("which tiffcp >/dev/null 2>/dev/null");
 return $? if $? != 0;
 return FALSE;
}


# Update device-dependent scan options

sub update_options {
 my ($vboxd, $device) = @_;

# Empty $vboxd first
 my @child = $vboxd -> get_children;
 foreach (@child) {
  $_ -> destroy;
 }

# Stupidly, the different frontends also show different paper sizes, so
# make a device-dependent paper size, starting from the global one
 @paper = @paperg;
 @x = @xg;
 @y = @yg;

 my $output = '';
 if (! @test) {

# Get output from scanimage or scanadf.
# Inverted commas needed for strange characters in device name
  my $cmd = "$SETTING{'frontend'} --help --device-name='$device'";
  warn "$cmd\n" if $debug;

# Set up ProgressBar
  my $pbar = Gtk2::ProgressBar->new;
  $pbar -> set_pulse_step(.1);
  $pbar->set_text ($d->get('Updating options'));
  $vboxd->pack_start ($pbar, FALSE, FALSE, 0);
  $vboxd -> show_all;
  my $running = TRUE;

# Timer will run until callback returns false 
  my $timer = Glib::Timeout->add (100, sub { if ($running) {
                                              $pbar->pulse;
                                              return TRUE;
                                             }
                                             else {
                                              return FALSE;
                                             } });

# Interface to frontend
  my $pid = open my $read, '-|', $cmd or die "can't open pipe: $!";
 
# Read without blocking
  Glib::IO->add_watch ( fileno($read), ['in', 'hup'], sub {
   my ($fileno, $condition) = @_;
   if ($condition & 'in') { # bit field operation. >= would also work
    my $line;
    $output .= $line while (sysread $read, $line, 1024);
   }
   if ($condition & 'hup') { # bit field operation. >= would also work
    close $read;
    $running = FALSE;
    $pbar -> destroy;
    $vboxd -> hide_all;
    warn $output if $debug;
    parse_options($vboxd, $device, $output);
    return FALSE;  # uninstall
   }
   return TRUE;  # continue without uninstalling
  });
 }
 else {
  my $i = 0;
  $i++ while ($device[$i] ne $device);

# Slurp it from file
  $output = do { local( @ARGV, $/ ) = $test[$i] ; <> } ;
  parse_options($vboxd, $device, $output);
 }
}


sub parse_options {
 my ($vboxd, $device, $output) = @_;

# Skip to the device-specific options
 $output = substr($output, index($output, "Options specific to device"), length($output));
 $output = substr($output, index($output, "\n")+1, length($output));

# Dig out the paper sizes
 my ($x, $y, $l, $t);
 $x = $2 if ($output =~ /-x (auto\|)?\d*\.?\d*\.\.(\d*\.?\d*)/);
 $y = $2 if ($output =~ /-y (auto\|)?\d*\.?\d*\.\.(\d*\.?\d*)/);
 $l = $2 if ($output =~ /-l (auto\|)?\d*\.?\d*\.\.(\d*\.?\d*)/);
 $t = $2 if ($output =~ /-t (auto\|)?\d*\.?\d*\.\.(\d*\.?\d*)/);

 if (defined($x) and defined($y)) {

# HBox for paper size
  my $hboxp = Gtk2::HBox -> new;
  $vboxd -> pack_start ($hboxp, FALSE, FALSE, 0);

# Paper list
  my $labelp = Gtk2::Label -> new ($d->get('Paper size'));
  $hboxp -> pack_start ($labelp, FALSE, FALSE, 0);

  my $ev = Gtk2::EventBox->new;
  $combobp = Gtk2::ComboBox -> new_text;
  $ev->add($combobp);

# Define custom paper here to reference it in callback
  $hboxc = Gtk2::HBox -> new;
  my $spin_buttonx = Gtk2::SpinButton -> new_with_range(0, $x, 1);
  my $spin_buttony = Gtk2::SpinButton -> new_with_range(0, $y, 1);
  $tooltips -> set_tip ($spin_buttonx, $d->get('Width of scan-area'));
  $tooltips -> set_tip ($spin_buttony, $d->get('Height of scan-area'));

# Add paper size to combobox if scanner large enough
# can't use "for" because of the splices
  my $i = 0;
  while ($i <= $#paper) {
   if ($x+$tolerance >= $x[$i] and $y+$tolerance >= $y[$i]) {
    $combobp -> append_text ($paper[$i]);
    ++$i;
   }

# If the paper size isn't possible, remove it from the arrays
   else {
    splice @paper, $i, 1;
    splice @x, $i, 1;
    splice @y, $i, 1;
   }
  }

# Add custom option
  $combobp -> append_text ($d->get('Custom'));
  $combobp -> signal_connect (changed => sub {
   if ($combobp -> get_active_text eq $d->get('Custom')) {
    $hboxc -> show_all;
   }
   else {
    my $i = $combobp -> get_active;
    $spin_buttonx -> set_value($x[$i]);
    $spin_buttony -> set_value($y[$i]);
    $hboxc -> hide;
    $windows -> resize(200, 200); # Doesn't matter that 200x200 is too small
   }
  });
  $tooltips -> set_tip ($ev,
                $d->get('Selects the paper size, e.g. A4, Letter, or Custom'));
  $hboxp -> pack_end ($ev, FALSE, FALSE, 0);

# Set default paper size from config
  my $o = 0;
  if (defined($SETTING{'Paper size'})) {
   $i = 0;
   while ($i <= $#paper) {
    $o = $i if ($paper[$i] eq $SETTING{'Paper size'});
    ++$i;
   }
  }
  $combobp -> set_active($o);

  $vboxd -> pack_start ($hboxc, FALSE, FALSE, 0);

# custom paper y entry
  my $labely = Gtk2::Label -> new ("y");
  $hboxc -> pack_end ($spin_buttony, FALSE, FALSE, 0);
  $hboxc -> pack_end ($labely, FALSE, FALSE, 0);

# custom paper x entry
  my $labelx = Gtk2::Label -> new ("x");
  $hboxc -> pack_end ($spin_buttonx, FALSE, FALSE, 0);
  $hboxc -> pack_end ($labelx, FALSE, FALSE, 0);

# Origin
  my $hboxo = Gtk2::HBox -> new;
  my $hboxol = Gtk2::HBox -> new;
  my $labelo = Gtk2::Label -> new ($d->get('Top-left position of scan area'));
  $hboxol -> pack_start ($labelo, FALSE, FALSE, 0);
  $vboxd -> pack_start ($hboxol, TRUE, TRUE, 0);
  my $labell = Gtk2::Label -> new ('l');
  my $spin_buttonl = Gtk2::SpinButton -> new_with_range(0, $l, 1);
  $spin_buttonl -> set_value($SETTING{'l'});
  my $labelt = Gtk2::Label -> new ('t');
  my $spin_buttont = Gtk2::SpinButton -> new_with_range(0, $t, 1);
  $spin_buttont -> set_value($SETTING{'t'});
  $tooltips -> set_tip ($spin_buttonl, $d->get('Top-left x position of scan area'));
  $tooltips -> set_tip ($spin_buttont, $d->get('Top-left y position of scan area'));
  $hboxo -> pack_end ($spin_buttont, FALSE, FALSE, 0);
  $hboxo -> pack_end ($labelt, FALSE, FALSE, 0);
  $hboxo -> pack_end ($spin_buttonl, FALSE, FALSE, 0);
  $hboxo -> pack_end ($labell, FALSE, FALSE, 0);
  $vboxd -> pack_start ($hboxo, FALSE, FALSE, 0);

  $spin_buttonx -> signal_connect ('value-changed' => sub {
   my ($minx, $maxx) = $spin_buttonx -> get_range;
   $spin_buttonl -> set_range(0, $maxx-$spin_buttonx->get_value);
  });
  $spin_buttony -> signal_connect ('value-changed' => sub {
   my ($miny, $maxy) = $spin_buttony -> get_range;
   $spin_buttont -> set_range(0, $maxy-$spin_buttony->get_value);
  });

# Set scan area from config if available, thus triggering updates for l & t
  if (defined($SETTING{'x'}) and defined($SETTING{'y'})) {
   $spin_buttonx -> set_value($SETTING{'x'});
   $spin_buttony -> set_value($SETTING{'y'});
  }

# Otherwise set from paper size if available
  elsif ($#paper > -1) {
   $i = $combobp -> get_active;
   $spin_buttonx -> set_value($x[$i]);
   $spin_buttony -> set_value($y[$i]);
  }

# Or max available
  else {
   $spin_buttonx -> set_value($x);
   $spin_buttony -> set_value($y);
  }
 }

# Set device-dependent options
# Dummy entries so that something is returned:
 %ddo = (
          'Paper size' => { string => $d->get('Paper size'),
                            values => {
                                      'A4' => $d->get('A4'),
                                      'Letter'  => $d->get('Letter'),
                                      } },
          'x'          => { string => 'x' },
          'y'          => { string => 'y' },
          'Origin'     => { string => $d->get('Top-left position of scan area'), },
          'l'          => { string => 'l' },
          't'          => { string => 't' },
        );

# Add remaining options
 while ($output =~ /--([a-z_-]*)(.*) \[(.*)\].*\n([\S\s]*)/) {
  my $option = $1;
  my $values = $2;
  my $default = $3;

# Remove everything on the option line and above.
  $output = $4;

# Parse tooltips from option description based on an 8-character indent.
  my $tip = "";
  while ($output =~ /^\s{8,}(.*)\n([\S\s]*)/) {
   if ($tip eq "") {
    $tip = $1;
   }
   else {
    $tip = "$tip $1";
   }

# Remove everything on the description line and above.
   $output = $2;
  }
# batch-scan is HP
# wait-for-button is Epson
# button-wait is HP
# source is various
  if (defined($pddo{$option}) and $default !~ /inactive/

# This needed for psc1315, that only has one option for source.
      and ($values =~ /(\d*\.?\d*)\.\.(\d*\.?\d*)/
       or $values =~ /\|/)) {

# Dig out of possible options
   $ddo{$option}{string} = $pddo{$option}{string};

# For HPs
   next if ($option eq 'compression');

# Set default from config
   $default = $SETTING{$option} if (defined($SETTING{$option}));

# HBox for option
   my $hbox = Gtk2::HBox -> new;
   $vboxd -> pack_start ($hbox, TRUE, TRUE, 0);

# Label
   my $label = Gtk2::Label -> new ($ddo{$option}{string});
   $hbox -> pack_start ($label, FALSE, FALSE, 0);

# ComboBox
   if ($values =~ /\|/) {
    my $ev = Gtk2::EventBox->new;
    my $combob = Gtk2::ComboBox -> new_text;
    $ev->add($combob);

# Counters for defaults
    my $i = -1;
    my $o = 0;

# Parse items from output
    while ($values =~ /\|/) {
     my $value = substr($values, 0, index($values, "|"));
     $values = substr($values, index($values, "|")+1, length($values));
     $value = substr($value, 1, length($value)) while ($value =~ /^ /);
     $value = substr($value, 3, length($value)) while ($value =~ /^\[=\(/);
     add_to_options($option, $value);

# Create item
     $combob -> append_text ($ddo{$option}{values}{$value});
     $i++;
     $o = $i if ($value eq $default);
    }

# Do it all again for the last one
    $values = substr($values, 0, length($values)-2) while ($values =~ /\)\]/);
    add_to_options($option, $values);
    $combob -> append_text ($ddo{$option}{values}{$values});
    $i++;
    $o = $i if ($values eq $default);

# Create ComboBox
    $combob -> set_active ($o);
    $hbox -> pack_end ($ev, FALSE, FALSE, 0);
    $tooltips -> set_tip ($ev, $tip);
   }

# SpinButton
   elsif ($values =~ /(-?\d*\.?\d*)\.\.(\d*\.?\d*)/) {
    my $spin_button = Gtk2::SpinButton -> new_with_range($1, $2, 1);
    $spin_button -> set_value($default);
    $hbox -> pack_end ($spin_button, FALSE, FALSE, 0);
    $tooltips -> set_tip ($spin_button, $tip);
   }
  }
 }

# Show window, hiding the custom hbox and sizing if necessary
 $vboxd -> show_all;
 hide_custom();
}


# Hides the custom paper size on scan dialog if not necessary

sub hide_custom {
 if (! defined($combobp) or ($combobp->get_active_text ne $d->get('Custom'))) {
# Not defined if scanner has no paper size (like my video grabber)
  $hboxc -> hide if (defined $hboxc);
  $windows -> resize(200, 200); # Doesn't matter that 200x200 is too small
 }
}


# Renumber pages

sub renumber {
 my ($slist, $column, $start, $step) = @_;

# Update undo/redo buffers
 take_snapshot();

 $step = 1 if (! defined($step));

 if (defined($start)) {
  for (0 .. $#{$slist -> {data}}) {
   $slist -> {data}[$_][$column] = $_*$step + $start;
  }
 }

# If $start and $step are undefined, just make sure that the numbering is
# ascending.
 else {
  for (0 .. $#{$slist -> {data}}-1) {
   if ($slist -> {data}[$_+1][$column] <= $slist -> {data}[$_][$column]) {

# If at the beginning of the list, start from 1.
    if ($_ == 0) {
     $slist -> {data}[0][$column] = 1;
     $slist -> {data}[1][$column] = 2;
    }
    else {
     $slist -> {data}[$_][$column] = $slist -> {data}[$_-1][$column] + 1;
     $slist -> {data}[$_+1][$column] = $slist -> {data}[$_][$column] + 1
      if ($slist -> {data}[$_+1][$column] <= $slist -> {data}[$_][$column]);
    }
   }
  }
 }
}


# Rotate image

sub rotate {
 my ($degrees) = @_;

 require Image::Magick;

# Update undo/redo buffers
 take_snapshot();

 my @page = $slist -> get_selected_indices;
 my $i = 0;
 while ($i <= $#page) {

# Rotate the tiff with imagemagick
  my $image = Image::Magick->new;
  my $x = $image->Read($slist -> {data}[$page[$i]][2]);
  warn "$x" if "$x";
  $x = $image->Rotate($degrees);
  warn "$x" if "$x";
  $x = $image->Write(filename => $slist -> {data}[$page[$i]][2]);
  warn "$x" if "$x";

# Use once I no longer have to develop with Gtk2 < 1.090!
#  $slist -> {data}[$page[$i]][1] =
#                  $slist -> {data}[$page[$i]][1] -> rotate_simple ($degrees);
  $slist -> {data}[$page[$i]][1] =
                 get_pixbuf($slist -> {data}[$page[$i]][2], $heightt, $widtht);
  ++$i;
 }

# Reselect the pages to display the rotated image(s)
 $slist->select(@page);
}


# Handle right-clicks

sub handle_clicks {
 my ($widget, $event) = @_;

# $SETTING{'RMB'} = ($event->button == 3);
#warn "rmb $SETTING{'RMB'}\n";

# let the event chain proceed if not right mouse button
 return FALSE if $event->button != 3;

 my $popup_menu;
 if ($widget->isa('Gtk2::EventBox'))  { # main image
  $popup_menu = $uimanager->get_widget('/Detail_Popup');
 }
 else { # Thumbnail simplelist
  $popup_menu = $uimanager->get_widget('/Thumb_Popup');
 }

 $popup_menu->show_all;
 $popup_menu->popup(undef, undef,
                    undef, undef,
                    $event->button,
                    $event->time);

 # block event propagation
 return TRUE;
}


# Run unpaper to clean up scan.

sub unpaper {

 if (defined $windowu) {
  $windowu -> present;
  return;
 }

 ($windowu, my $vbox) = create_window($window, $d->get('unpaper'), FALSE);

 my %option = (
  single => {
   string => $d->get('Single'),
   tooltip => $d->get('One page per sheet, oriented upwards without rotation.'),
  },
  double => {
   string => $d->get('Double'),
   tooltip => $d->get('Two pages per sheet, landscape orientation (one page on the left half, one page on the right half).'),
  },
  'no-deskew' => {
   string => $d->get('No deskew'),
   tooltip => $d->get('Disable deskewing.'),
  },
  'no-mask-scan' => {
   string => $d->get('No mask scan'),
   tooltip => $d->get('Disable mask detection.'),
  },
  'no-blackfilter' => {
   string => $d->get('No black filter'),
   tooltip => $d->get('Disable black area scan.'),
  },
  'no-noisefilter' => {
   string => $d->get('No noise filter'),
   tooltip => $d->get('Disable noise filter.'),
  },
  'no-blurfilter' => {
   string => $d->get('No blur filter'),
   tooltip => $d->get('Disable blur filter.'),
  },
  'no-border-scan' => {
   string => $d->get('No border scan'),
   tooltip => $d->get('Disable border scanning.'),
  },
  'no-border-align' => {
   string => $d->get('No border align'),
   tooltip => $d->get('Disable aligning of the area detected by border scanning.'),
  },
  'deskew-scan-direction' => {
   string => $d->get('Deskew to edge'),
   tooltip => $d->get("Edges from which to scan for rotation. Each edge of a mask can be used to detect the mask's rotation. If multiple edges are specified, the average value will be used, unless the statistical deviation exceeds --deskew-scan-deviation."),
   left => {
    string => $d->get('Left'),
    tooltip => $d->get("Use 'left' for scanning from the left edge."),
   },
   top => {
    string => $d->get('Top'),
    tooltip => $d->get("Use 'top' for scanning from the top edge."),
   },
   right => {
    string => $d->get('Right'),
    tooltip => $d->get("Use 'right' for scanning from the right edge."),
   },
   bottom => {
    string => $d->get('Bottom'),
    tooltip => $d->get("Use 'bottom' for scanning from the bottom."),
   },
  },
  'border-align' => {
   string => $d->get('Align to edge'),
   tooltip => $d->get('Edge to which to align the page.'),
   left => {
    string => $d->get('Left'),
    tooltip => $d->get("Use 'left' to align to the left edge."),
   },
   top => {
    string => $d->get('Top'),
    tooltip => $d->get("Use 'top' to align to the top edge."),
   },
   right => {
    string => $d->get('Right'),
    tooltip => $d->get("Use 'right' to align to the right edge."),
   },
   bottom => {
    string => $d->get('Bottom'),
    tooltip => $d->get("Use 'bottom' to align to the bottom."),
   },
  },
  'border-margin' => {
   string => $d->get('Margin'),
   tooltip => $d->get('Margin for aligned edges.'),
  },
  'white-threshold' => {
   string => $d->get('White threshold'),
   tooltip => $d->get('Brightness ratio above which a pixel is considered white.'),
  },
  'black-threshold' => {
   string => $d->get('Black threshold'),
   tooltip => $d->get('Brightness ratio below which a pixel is considered black (non-gray). This is used by the gray-filter. This value is also used when converting a grayscale image to black-and-white mode.'),
  },
 );

# Layout ComboBox
 my $hboxl = Gtk2::HBox -> new;
 $vbox -> pack_start($hboxl, TRUE, TRUE, 0);
 my $label = Gtk2::Label -> new ($d->get('Layout'));
 $hboxl -> pack_start ($label, FALSE, FALSE, 0);
 my $ev = Gtk2::EventBox->new;
 my $combobl = Gtk2::ComboBox->new_text;
 $ev->add($combobl);

# Fill layout combobox updating the tooltips when changed
 my @layout = qw/single double/;
 foreach (@layout) {
  $combobl -> append_text ($option{$_}{string});
 }
 $combobl -> signal_connect (changed => sub {
  $tooltips -> set_tip ($ev, $option{$layout[$combobl->get_active]}{tooltip});
 });

# Set default tooltip
 $tooltips -> set_tip ($ev, $option{single}{tooltip});
 $hboxl -> pack_end ($ev, FALSE, FALSE, 0);
 $combobl -> set_active (0);

# Notebook to collate options
 my $notebook = Gtk2::Notebook->new;
 $vbox->pack_start($notebook, TRUE, TRUE, 0);

# Notebook page 1
 my $vbox1 = Gtk2::VBox->new;
 $notebook->append_page($vbox1, $d->get('Deskew'));

 my $dsbutton = Gtk2::CheckButton -> new($option{'no-deskew'}{string});
 $tooltips -> set_tip ($dsbutton, $option{'no-deskew'}{tooltip});
 $vbox1 -> pack_start ($dsbutton, TRUE, TRUE, 0);

# Frame for Deskew Scan Direction
 my $dframe = Gtk2::Frame -> new($option{'deskew-scan-direction'}{string});
 $vbox1 -> pack_start ($dframe, TRUE, TRUE, 0);
 my $vboxd = Gtk2::VBox -> new;
 $vboxd -> set_border_width($border_width);
 $dframe -> add ($vboxd);
 $dsbutton -> signal_connect (toggled => sub {
  if ($dsbutton -> get_active) {
   $dframe->set_sensitive(FALSE);
  }
  else {
   $dframe->set_sensitive(TRUE);
  }
 });

 foreach (qw/left top right bottom/) {
  my $button = Gtk2::CheckButton -> new($option{'deskew-scan-direction'}{$_}{string});
  $tooltips -> set_tip ($button, $option{'deskew-scan-direction'}{tooltip}
                            ." ".$option{'deskew-scan-direction'}{$_}{tooltip});
  $button->set_active(TRUE) if (/left/ or /right/);

# Ensure that at least one checkbutton stays active
  $button -> signal_connect (toggled => sub {
   my $n = 0;
   my @child = $vboxd -> get_children;
   foreach (@child) {
    $n++ if ($_ -> get_active);
   }
   $button->set_active(TRUE) if ($n == 0);
  });
  $vboxd -> pack_start($button, TRUE, TRUE, 0);
 }

# Notebook page 2
 my $vbox2 = Gtk2::VBox->new;
 $notebook->append_page($vbox2, $d->get('Border'));

 my $bsbutton = Gtk2::CheckButton -> new($option{'no-border-scan'}{string});
 $tooltips -> set_tip ($bsbutton, $option{'no-border-scan'}{tooltip});
 $vbox2 -> pack_start ($bsbutton, TRUE, TRUE, 0);

 my $babutton = Gtk2::CheckButton -> new($option{'no-border-align'}{string});
 $tooltips -> set_tip ($babutton, $option{'no-border-align'}{tooltip});
 $vbox2 -> pack_start ($babutton, TRUE, TRUE, 0);

# Frame for Align Border
 my $bframe = Gtk2::Frame -> new($option{'border-align'}{string});
 $vbox2 -> pack_start ($bframe, TRUE, TRUE, 0);
 my $vboxb = Gtk2::VBox -> new;
 $vboxb -> set_border_width($border_width);
 $bframe -> add ($vboxb);
 $bsbutton -> signal_connect (toggled => sub {
  if ($bsbutton -> get_active) {
   $bframe->set_sensitive(FALSE);
   $babutton->set_sensitive(FALSE);
  }
  else {
   $babutton->set_sensitive(TRUE);
   $bframe->set_sensitive(TRUE) if (! ($babutton -> get_active));
  }
 });
 $babutton -> signal_connect (toggled => sub {
  if ($babutton -> get_active) {
   $bframe->set_sensitive(FALSE);
  }
  else {
   $bframe->set_sensitive(TRUE);
  }
 });

# Define margins here to reference them below
 my $hboxd = Gtk2::HBox -> new;
 $hboxd->set_sensitive(FALSE);
 my $spin_buttonv = Gtk2::SpinButton -> new_with_range(0, 10, 1);
 my $spin_buttonh = Gtk2::SpinButton -> new_with_range(0, 10, 1);
 $tooltips -> set_tip ($spin_buttonv, $d->get('Vertical margin'));
 $tooltips -> set_tip ($spin_buttonh, $d->get('Horizontal margin'));

 foreach (qw/left top right bottom/) {
  my $button = Gtk2::CheckButton -> new($option{'border-align'}{$_}{string});
  $tooltips -> set_tip ($button, $option{'border-align'}{tooltip}
                            ." ".$option{'border-align'}{$_}{tooltip});
  $vboxb -> pack_start($button, TRUE, TRUE, 0);

# Ghost margin if nothing selected
  $button -> signal_connect (toggled => sub {
   my $n = 0;
   my @child = $vboxb -> get_children;
   foreach (@child) {
    $n++ if ($_ -> isa('Gtk2::CheckButton') and $_ -> get_active);
   }
   if ($n == 0) {
    $hboxd->set_sensitive(FALSE);
   }
   else {
    $hboxd->set_sensitive(TRUE);
   }
  });
 }

 my $labelb = Gtk2::Label -> new ($option{'border-margin'}{string});
 $hboxd -> pack_start ($labelb, FALSE, FALSE, 0);

# border margin v entry
 my $labelv = Gtk2::Label -> new ('v');
 $hboxd -> pack_start ($labelv, FALSE, FALSE, 0);
 $hboxd -> pack_start ($spin_buttonv, FALSE, FALSE, 0);

# border margin v entry
 my $labelh = Gtk2::Label -> new ('h');
 $hboxd -> pack_start ($labelh, FALSE, FALSE, 0);
 $hboxd -> pack_start ($spin_buttonh, FALSE, FALSE, 0);
 $vboxb -> pack_start($hboxd, TRUE, TRUE, 0);

# Notebook page 3
 my $vbox3 = Gtk2::VBox->new;
 $notebook->append_page($vbox3, $d->get('Filters'));

# White threshold
 my $hboxwt = Gtk2::HBox -> new;
 my $labelwt = Gtk2::Label -> new ($option{'white-threshold'}{string});
 $hboxwt -> pack_start ($labelwt, FALSE, FALSE, 0);
 my $spinbuttonwt = Gtk2::SpinButton -> new_with_range(0, 1, .01);
 $spinbuttonwt->set_value(0.9);
 $tooltips -> set_tip ($spinbuttonwt, $option{'white-threshold'}{tooltip});
 $hboxwt -> pack_end ($spinbuttonwt, FALSE, FALSE, 0);
 $vbox3 -> pack_start ($hboxwt, TRUE, TRUE, 0);

# Black threshold
 my $hboxbt = Gtk2::HBox -> new;
 my $labelbt = Gtk2::Label -> new ($option{'black-threshold'}{string});
 $hboxbt -> pack_start ($labelbt, FALSE, FALSE, 0);
 my $spinbuttonbt = Gtk2::SpinButton -> new_with_range(0, 1, .01);
 $spinbuttonbt->set_value(0.33);
 $tooltips -> set_tip ($spinbuttonbt, $option{'black-threshold'}{tooltip});
 $hboxbt -> pack_end ($spinbuttonbt, FALSE, FALSE, 0);
 $vbox3 -> pack_start ($hboxbt, TRUE, TRUE, 0);

 my $msbutton = Gtk2::CheckButton -> new($option{'no-mask-scan'}{string});
 $tooltips -> set_tip ($msbutton, $option{'no-mask-scan'}{tooltip});
 $vbox3 -> pack_start ($msbutton, TRUE, TRUE, 0);

 my $bfbutton = Gtk2::CheckButton -> new($option{'no-blackfilter'}{string});
 $tooltips -> set_tip ($bfbutton, $option{'no-blackfilter'}{tooltip});
 $vbox3 -> pack_start ($bfbutton, TRUE, TRUE, 0);

 my $nfbutton = Gtk2::CheckButton -> new($option{'no-noisefilter'}{string});
 $tooltips -> set_tip ($nfbutton, $option{'no-noisefilter'}{tooltip});
 $vbox3 -> pack_start ($nfbutton, TRUE, TRUE, 0);

 my $blbutton = Gtk2::CheckButton -> new($option{'no-blurfilter'}{string});
 $tooltips -> set_tip ($blbutton, $option{'no-blurfilter'}{tooltip});
 $vbox3 -> pack_start ($blbutton, TRUE, TRUE, 0);

# Frame for page range
 my ($buttona, $buttonc, $buttons) = add_page_range($vbox);

# HBox for buttons
 my $hboxb = Gtk2::HBox -> new;
 $vbox -> pack_start ($hboxb, FALSE, TRUE, 0);

# OK button
 my $sbutton = Gtk2::Button -> new_from_stock('gtk-ok');
 $hboxb -> pack_start( $sbutton, TRUE, TRUE, 0 );
 $sbutton -> signal_connect (clicked => sub {

# Update undo/redo buffers
  take_snapshot();

  $SETTING{layout} = get_key(\%option, $combobl -> get_active_text);

  my $deskew = '';
  if ($dsbutton -> get_active) {
   $deskew = '--no-deskew';
  }
  else {
   my @child = $vboxd -> get_children;
   foreach (@child) {
    if ($_ -> get_active) {
     $deskew .= "," if ($deskew ne '');
     $deskew .= get_key($option{'deskew-scan-direction'}, $_ -> get_label);
    }
   }
   $deskew = "--deskew-scan-direction $deskew";
  }

  my $border_align = '';
  if ($bsbutton -> get_active) {
   $border_align = '--no-border-scan';
  }
  elsif ($babutton -> get_active) {
   $border_align = '--no-border-align';
  }
  else {
   my @child = $vboxb -> get_children;
   foreach (@child) {
    if ($_ -> isa('Gtk2::CheckButton') and $_ -> get_active) {
     $border_align .= "," if ($border_align ne '');
     $border_align .= get_key($option{'border-align'}, $_ -> get_label);
    }
   }
   $border_align = "--border-align $border_align"
    ." --border-margin ".$spin_buttonv->get_value.",".$spin_buttonh->get_value
    if ($border_align ne '');
  }

  my $white = "--white-threshold ".$spinbuttonwt->get_value;
  my $black = "--black-threshold ".$spinbuttonbt->get_value;
  
  my $maskscan = '';
  $maskscan = '--no-mask-scan' if ($msbutton -> get_active);

  my $blackfilter = '';
  $blackfilter = '--no-blackfilter' if ($bfbutton -> get_active);

  my $noisefilter = '';
  $noisefilter = '--no-noisefilter' if ($nfbutton -> get_active);

  my $blurfilter = '';
  $blurfilter = '--no-blurfilter' if ($blbutton -> get_active);

# Note page selection for later
  my @selection = $slist -> get_selected_indices;

  my @pagelist = get_page_index();
  
  my $dialog = Gtk2::Dialog -> new ($d->get('Running unpaper')."...", $windowo,
                                     'destroy-with-parent',
                                     'gtk-cancel' => 'cancel');
  my $label = Gtk2::Label -> new ($d->get('Running unpaper')."...");
  $dialog -> vbox -> add ($label);

# Set up ProgressBar
  my $pbar = Gtk2::ProgressBar->new;
  $dialog -> vbox -> add  ($pbar);

# Flag set if unpaper is running
  my $running = FALSE;
  my $cancelled = FALSE;
  my $page = -1;

# Ensure that the dialog box is destroyed when the user responds.
  my ($pid, $tif);
  $dialog -> signal_connect (response => sub {
   $_[0] -> destroy;
   local $SIG{HUP} = 'IGNORE';
   kill HUP => $pid;
   $cancelled = TRUE;

# Retain the existing page
   $tif = $slist -> {data}[$page][2];
  });
  $dialog -> show_all;

# Timer will run until callback returns false 
  my $timer = Glib::Timeout->add (100, sub {

# To prevent any further pages being processed
   if ($cancelled) {
    return FALSE;  # uninstall
   }
   elsif ($page < $#pagelist) {
    if (! $running) {
     $running = TRUE;
     $page++;
     my $i = $page+1;
     my $n = $#pagelist+1;
     $pbar->set_text(sprintf($d->get("Page %i of %i"), $i, $n));
     $pbar->set_fraction ($page/($#pagelist+1));

# Temporary filenames for pnm and new tif
     my (undef, $pnm) = tempfile(DIR => $dir, SUFFIX => '.pnm');
     (undef, $tif) = tempfile(DIR => $dir, SUFFIX => '.tif');
   
     my $cmd = "convert -compress zip ".$slist -> {data}[$page][2]." $pnm;"
              ."unpaper --layout $SETTING{layout} $deskew $border_align "
              ."$maskscan $white $black $blackfilter $noisefilter $blurfilter "
              ."--overwrite $pnm $pnm;"
              ."convert $pnm $tif; rm $pnm";
     warn "$cmd\n" if ($debug);

# Interface to unpaper
     $pid = open my $read, '-|', $cmd or die "can't open pipe: $!";
 
# Update TextBuffer without blocking
     Glib::IO->add_watch ( fileno($read), ['in', 'hup'], sub {
      my ($fileno, $condition) = @_;
      if ($condition & 'in') { # bit field operation. >= would also work
       my $line;
       while (sysread $read, $line, 1024) {}
      }
      if ($condition & 'hup') { # bit field operation. >= would also work
       close $read;

       $slist -> {data}[$page][2] = $tif;
       $slist -> {data}[$page][1] =
                      get_pixbuf($slist -> {data}[$page][2], $heightt, $widtht);

# Reselect selected pages, firing the display callback
       $slist -> get_selection -> unselect_all;
       $slist -> select(@selection);

       $running = FALSE;

       return FALSE;  # uninstall
      }
      return TRUE;  # continue without uninstalling
     });
    }
    return TRUE;  # continue without uninstalling
   }
   elsif (! $running) {
    $dialog -> destroy;
    return FALSE;  # uninstall
   }
   else {
    return TRUE;  # continue without uninstalling
   }
  });

 } );

# Cancel button
 my $cbutton = Gtk2::Button -> new_from_stock('gtk-cancel');
 $hboxb -> pack_end( $cbutton, FALSE, FALSE, 0 );
 $cbutton -> signal_connect( clicked => sub { $windowu -> hide; } );

 $windowu -> show_all;
}


# Add $page to the OCR stack, setting it off if not running.

sub ocr_page {
 push @ocr_stack, @_;

 if (! defined($ocr_timer)) {
  my $dialog = Gtk2::Dialog -> new ($d->get('Running OCR')."...", $windowo,
                                    'destroy-with-parent',
                                    'gtk-cancel' => 'cancel');
  my $label = Gtk2::Label -> new ($d->get('Running OCR')."...");
  $dialog -> vbox -> add($label);

# Set up ProgressBar
  my $pbar = Gtk2::ProgressBar->new;
  $dialog -> vbox -> add($pbar);

# Flag set if ocr is running
  my $running = FALSE;
  my $cancelled = FALSE;
  my $page = 0;
  my $npages = 0;

# Ensure that the dialog box is destroyed when the user responds.
  my $pid;
  $dialog -> signal_connect (response => sub {
   $_[0] -> destroy;
   local $SIG{HUP} = 'IGNORE';
   kill HUP => $pid;
   $cancelled = TRUE;
  });
  $dialog -> show_all;

# Timer will run until callback returns false 
  $ocr_timer = Glib::Timeout->add (100, sub {

# To prevent any further pages being processed
   if ($cancelled) {
    undef $ocr_timer;
    return FALSE;  # uninstall
   }
   elsif (@ocr_stack) {
    if (! $running) {
     $running = TRUE;
     $page++;
     $npages++;
     my $pagenum = shift @ocr_stack;
     $pbar->set_text(sprintf($d->get("Page %i of %i"), $page, $#ocr_stack+$npages+1));
     $pbar->set_fraction (($page-1)/($#ocr_stack+$npages+1));

# Temporary filenames for pnm and new tif
     my (undef, $pnm) = tempfile(DIR => $dir, SUFFIX => '.pnm');
     my $tif = $slist -> {data}[$pagenum][2];
   
     my $cmd = "convert $tif $pnm; gocr $pnm; rm $pnm";
     warn "$cmd\n" if ($debug);

# Interface to gocr
     $pid = open my $read, '-|', $cmd or die "can't open pipe: $!";
     my $page_buffer = '';
 
# Update TextBuffer without blocking
     Glib::IO->add_watch ( fileno($read), ['in', 'hup'], sub {
      my ($fileno, $condition) = @_;
      if ($condition & 'in') { # bit field operation. >= would also work
       my $line;
       $page_buffer .= $line while (sysread $read, $line, 1024);
      }
      if ($condition & 'hup') { # bit field operation. >= would also work
       close $read;
       $dialog -> destroy;
       $running = FALSE;

# Doing this once at the end to avoid firing the row-changed signal too often
       $slist -> get_model -> signal_handler_block($slist -> {signalid});
       $slist -> {data}[$pagenum][3] .= $page_buffer;
       $slist -> get_model -> signal_handler_unblock($slist -> {signalid});

# Update the buffer if current
       my @page = $slist -> get_selected_indices;
       if ($page[0] == $pagenum) {
        $textbuffer -> signal_handler_block($textbuffer -> {signalid});
        $textbuffer -> set_text ($slist -> {data}[$pagenum][3]);
        $textbuffer -> signal_handler_unblock($textbuffer -> {signalid});
       }
       return FALSE;  # uninstall
      }
      return TRUE;  # continue without uninstalling
     });
    }
    return TRUE;  # continue without uninstalling
   }
   elsif (! $running) {
    $dialog -> destroy;
    undef $ocr_timer;
    return FALSE;  # uninstall
   }
   else {
    return TRUE;  # continue without uninstalling
   }
  });
 }
}


# Run OCR on current page and display result

sub OCR {

 if (defined $windowo) {
  $windowo -> present;
  return;
 }

 ($windowo, my $vbox) = create_window($window, $d->get('OCR'), FALSE);

# Frame for page range
  my ($buttona, $buttonc, $buttons) = add_page_range($vbox);

# HBox for buttons
 my $hbox = Gtk2::HBox -> new;
 $vbox -> pack_start ($hbox, FALSE, TRUE, 0);

# Start button
 my $obutton = Gtk2::Button -> new($d->get('Start OCR'));
 $hbox -> pack_start( $obutton, TRUE, TRUE, 0 );
 $obutton -> signal_connect( clicked => sub {

# fill $pagelist with filenames depending on which radiobutton is active
  my @pagelist = get_page_index();
  if (! @pagelist) {
   my $dialog = Gtk2::MessageDialog -> new ($windowo,
                                           'destroy-with-parent',
                                           'error',
                                           'close',
                                           $d->get('No page selected'));
   $dialog -> run;
   $dialog -> destroy;
   return;
  }
  ocr_page(@pagelist);
 } );

# Close button
 my $cbutton = Gtk2::Button -> new_from_stock('gtk-close');
 $hbox -> pack_end( $cbutton, FALSE, FALSE, 0 );
 $cbutton -> signal_connect( clicked => sub { $windowo -> hide; } );

 $windowo -> show_all;
}


# Remove temporary files, note window state, save settings and quit.

sub quit {

# Remove temporary files (for some reason File::Temp wasn't doing its job here)
 unlink <$dir/*>;
 rmdir $dir;

# Write window state to settings
 ($SETTING{'window_width'}, $SETTING{'window_height'}) = $window -> get_size;
 ($SETTING{'window_x'}, $SETTING{'window_y'}) = $window -> get_position;
 $SETTING{'thumb panel'} = $hpaned -> get_position;
 $SETTING{'ocr panel'} = $vpaned -> get_position;

# Save options setting
 $SETTING{'enable options'} =
  $uimanager->get_widget('/MenuBar/Edit/Options') -> get_active;

# delete $SETTING{'RMB'};

# Write config file
 open (CONFIG, "> $config")
  or die sprintf($d->get("Can't open config file: %s"), $config); # xgettext hack
 while (my ($key, $value) = each %SETTING) {
  print CONFIG "$key = $value\n";
 }
 close CONFIG;
}


# View POD

sub view_pod {

 if (defined $windowh) {
  $windowh -> present;
  return;
 }

 eval {require Gtk2::Ex::PodViewer};
 if ($@) {
  my $dialog = Gtk2::MessageDialog -> new ($window,
                                          'destroy-with-parent',
                                          'error',
                                          'close',
    sprintf($d->get("The help viewer requires module Gtk2::Ex::PodViewer\n"
                        ."Alternatively, try: %s %s\n\n"), $program, "--help"));
  $dialog -> run;
  $dialog -> destroy;
  return;
 }

# Window
 $windowh = Gtk2::Window -> new;
 $windowh -> set_transient_for($window); # Assigns parent
 $windowh -> signal_connect ( delete_event => sub {
  $windowh -> hide;
  return TRUE; # ensures that the window is not destroyed
 } );
 $windowh -> set_default_size (800, 600);

# Vertical divider between index and viewer
 my $pane = Gtk2::HPaned->new;
 $pane->set_position(200);
 $windowh -> add($pane);

# Index list
 my $index = Gtk2::Ex::Simple::List->new('icon' => 'pixbuf',
                                         'title' => 'text',
                                         'link' => 'hstring');
 $index->set_headers_visible(FALSE);
 $index->get_column(1)->set_sizing('autosize');

# Index
 my $index_scrwin = Gtk2::ScrolledWindow->new;
 $index_scrwin->set_shadow_type('in');
 $index_scrwin->set_policy('automatic', 'automatic');
 $index_scrwin->add_with_viewport($index);
 $index_scrwin->get_child->set_shadow_type('none');

# Viewer
 my $viewer = Gtk2::Ex::PodViewer->new;
 $viewer->set_border_width($border_width);
 $viewer->set_cursor_visible(FALSE);
 $index->get_selection->signal_connect('changed', sub {
  my $idx = ($index->get_selected_indices)[0];
  my $mark = $index->{data}[$idx][2];
  $viewer->jump_to($mark);
  return TRUE;
 });

 my $viewer_scrwin = Gtk2::ScrolledWindow->new;
 $viewer_scrwin->set_shadow_type('in');
 $viewer_scrwin->set_policy('automatic', 'automatic');
 $viewer_scrwin->add($viewer);

 $pane->add1($index_scrwin);
 $pane->add2($viewer_scrwin);

 $viewer -> load($0);

# Index contents
 my $idx_pbf = Gtk2::Image->new->render_icon('gtk-jump-to', 'menu');
 map { push(@{$index->{data}}, [ $idx_pbf, strippod ($_), $_ ]) }
                                                             $viewer->get_marks;

 $windowh -> show_all;
}


# Remove formatting characters

sub strippod {
 my $text = shift;
 $text =~ s/B<([^<]*)>/$1/g;
 $text =~ s/E<gt>/>/g;
 $text
}


# Add option, value pair to options

sub add_to_options {
 my ($option, $value) = @_;

# Dig out of possible options, if defined
 if (defined($pddo{$option}) and defined($pddo{$option}{values})
                             and defined($pddo{$option}{values}{$value})) {
  $ddo{$option}{values}{$value} = $pddo{$option}{values}{$value};
 }
 else {
  $ddo{$option}{values}{$value} = $value;
 }
}


# Get option string from label

sub get_key {
 my ($options, $value) = @_;
 foreach my $key ( keys %$options ) {
  return $key if ($key ne 'tooltip' and $key ne 'string'
                                       and $options->{$key}{string} eq $value);
 }
 return FALSE;
}


# Get value string from combobox

sub get_value {
 my ($options, $option, $value) = @_;
 foreach my $key ( keys %{$options->{$option}{values}} ) {
  return $key if ($options->{$option}{values}{$key} eq $value);
 }
 return FALSE;
}


# Update undo/redo buffers before doing something

sub take_snapshot {

# Deep copy the tied data. Otherwise, very bad things happen.
 @undo_buffer = map { [ @$_ ] } @{ $slist->{data} };
 @undo_selection = $slist -> get_selected_indices;

# Unghost Undo/redo
 $uimanager->get_widget('/MenuBar/Edit/Undo') -> set_sensitive(TRUE);
 $uimanager->get_widget('/ToolBar/Undo') -> set_sensitive(TRUE);
}


# Put things back to last snapshot after updating redo buffer

sub undo {

# Deep copy the tied data. Otherwise, very bad things happen.
 @redo_buffer = map { [ @$_ ] } @{ $slist->{data} };
 @redo_selection = $slist -> get_selected_indices;

# Block slist signals whilst updating
 $slist -> get_model -> signal_handler_block($slist -> {signalid});
 @{$slist->{data}} = @undo_buffer;

# Unblock slist signals now finished
 $slist -> get_model -> signal_handler_unblock($slist -> {signalid});

# Reselect the pages to display the detail view
 $slist->select(@undo_selection);

# Update menus/buttons
 $uimanager->get_widget('/MenuBar/Edit/Undo') -> set_sensitive(FALSE);
 $uimanager->get_widget('/MenuBar/Edit/Redo') -> set_sensitive(TRUE);
 $uimanager->get_widget('/ToolBar/Undo') -> set_sensitive(FALSE);
 $uimanager->get_widget('/ToolBar/Redo') -> set_sensitive(TRUE);
}


# Put things back to last snapshot after updating redo buffer

sub unundo {

# Deep copy the tied data. Otherwise, very bad things happen.
 @undo_buffer = map { [ @$_ ] } @{ $slist->{data} };
 @undo_selection = $slist -> get_selected_indices;

# Block slist signals whilst updating
 $slist -> get_model -> signal_handler_block($slist -> {signalid});
 @{$slist->{data}} = @redo_buffer;

# Unblock slist signals now finished
 $slist -> get_model -> signal_handler_unblock($slist -> {signalid});

# Reselect the pages to display the detail view
 $slist->select(@redo_selection);

# Update menus/buttons
 $uimanager->get_widget('/MenuBar/Edit/Undo') -> set_sensitive(TRUE);
 $uimanager->get_widget('/MenuBar/Edit/Redo') -> set_sensitive(FALSE);
 $uimanager->get_widget('/ToolBar/Undo') -> set_sensitive(TRUE);
 $uimanager->get_widget('/ToolBar/Redo') -> set_sensitive(FALSE);
}


# Initialise IconFactory

sub init_icons {
 return if defined $IconFactory;

 $IconFactory = Gtk2::IconFactory->new();
 $IconFactory->add_default();

 foreach ( @_ ) {
  register_icon($_->[0], $_->[1]);
 }
}


# Add icons

sub register_icon {
 my ($stock_id, $path) = @_;

 return unless defined $IconFactory;

 my $icon;
 eval { $icon = Gtk2::Gdk::Pixbuf->new_from_file($path); };
 if ($@) {
  warn("Unable to load icon at `$path': $@");
 }
 else {
  my $set = Gtk2::IconSet->new_from_pixbuf($icon);
  $IconFactory->add($stock_id, $set);
 }
}


__END__

=head1 Name

gscan2pdf - A GUI to ease the process of producing a multipage PDF from a scan.
gscan2pdf should work on almost any Linux/BSD machine.

=for html <p align="center">
 <img src="https://sourceforge.net/dbimage.php?id=121788" border="1" width="632" height="480" alt="Screenshot" /><br />
 Screenshot: Main page v0.9.8</p>

=head1 Synopsis

=over

=item 1. Scan one or several pages in with File/Scan

=item 2. Create PDF of selected pages with File/Save PDF

=back

=head1 Description

Scanning is handled with SANE via scanimage.
PDF conversion is done by PDF::API2.
TIFF export is handled by libtiff (faster and smaller memory footprint for multipage files).

Perl is used for portability and ease of programming, with gtk2-perl for the GUI.
This should therefore work more or less out of the box on any system with Perl, gtk2-perl, scanimage and libtiff.

=head1 Download

gscan2pdf is available on Sourceforge (L<https://sourceforge.net/project/showfiles.php?group_id=174140&package_id=199621>).

=head2 Debian-based

If you are using a Debian-based system, just add the following line to your "F</etc/apt/sources.list>" file:

C<deb http://gscan2pdf.sourceforge.net/download/debian binary/>

If you are you are using Synaptic, then use menu I<Edit/Reload Package Information>, search for gscan2pdf in the package list, and lo and behold, you can install the nice shiny new version automatically.

From the command line:

C<apt-get update>

C<apt-get install gscan2pdf>

The help will require F<Gtk2::Ex::PodViewer>.
Rotating requires F<Imagemagick>.
Having installed gscan2pdf, locate the gscan2pdf entry in Synaptic, right-click it and you can install them under I<Recommends>.

=head2 RPMs

Download the rpm from Sourceforge, and then install it with C<rpm -i gscan2pdf-version.rpm>

=head2 From source

The source is hosted in the files section of the gscan2pdf project on Sourceforge (L<http://sourceforge.net/project/showfiles.php?group_id=174140>).

Having downloaded it, C<perl Makefile.PL>, will create the Makefile, C<make> will build the application and C<make install> will install it for you.
Note there is no C<make test> step.
To build the RPM and .deb packages, you will additionally need the rpm, devscripts, fakeroot, debhelper and gettext packages.

=head1 Reporting bugs

Before reporting bugs, please read the L<FAQs> section.

Please report any bugs found to the bugs page of the gscan2pdf project on Sourceforge (L<https://sourceforge.net/tracker/?group_id=174140&atid=868098>).

Please include the output of C<scanimage --help> with any new bug report.

=head1 Translations

gscan2pdf has already been partly translated several languages.
If you would like to contribute to an existing or new translation, please check out Rosetta: L<https://launchpad.net/products/gscan2pdf/trunk/+pots/gscan2pdf>

=head1 Menus

=head2 File

=head3 New

Clears the page list.

=head3 Import

Imports any format that imagemagick supports. PDFs will have their embedded images extracted and imported one per page.

=head3 Scan

Sets options before scanning via SANE.

=head4 Device

Chooses between available scanners.

=head4 # Pages

Selects the number of pages, or all pages to scan.

=head4 Source document

Selects between single sided or double sides pages.

This affects the page numbering.
Single sided scans are numbered consecutively.
Double sided scans are incremented (or decremented, see below) by 2, i.e. 1, 3, 5, etc..

=head4 Side to scan

If double sided is selected above, assuming a non-duplex scanner, i.e. a scanner that cannot automatically scan both sides of a page, this determines whether the page number is incremented or decremented by 2.

To scan both sides of three pages, i.e. 6 sides:

=over

=item 1. Select:

# Pages = 3 (or "all" if your scanner can detect when it is out of paper)

Double sided

Facing side

=item 2. Scans sides 1, 3 & 5.

=item 3. Put pile back with scanner ready to scan back of last page.

=item 4. Select:

# Pages = 3 (or "all" if your scanner can detect when it is out of paper)

Double sided

Reverse side

=item 5. Scans sides 6, 4 & 2.

=item 6. gscan2pdf automatically sorts the pages so that they appear in the correct order.

=back

=head4 Device-dependent options

These, naturally, depend on your scanner.
They can include

=over

=item Page size.

=item Mode (colour/black & white/greyscale)

=item Resolution (in dpi)

=item Batch-scan

Guarantees that a "no documents" condition will be returned after the last scanned page, to prevent endless flatbed scans after a batch scan.

=item Wait-for-button/Button-wait

After sending the scan command, wait until the button on the scanner is pressed before actually starting the scan process.

=item Source

Selects the document source.
Possible options can include Flatbed or ADF.
On some scanners, this is the only way of generating an out-of-documents signal.

=back

=head3 Save PDF

Saves the current, selected or all pages as a PDF.

=head4 Metadata

Metadata are information that are not visible when viewing the PDF, but are embedded in the file and so searchable and can be examined, typically with the "Properties" option of the PDF viewer.

The metadata are completely optional.

=head4 OCR output

The OCR output buffer is embedded as an annotation (pop-up note) in the PDF produced.
Unfortunately, Beagle does not index (i.e. search) the contents of annotation, as it relies on pdftotext.
Suggestions for a better way to embed the text are welcome.

=head3 Save TIFF

Saves the current, selected or all pages as a TIFF.

=head3 Save DjVu

Saves the current, selected or all pages as a DjVu.
Currently, only bitonal compression is available.
This is best suited to black and white scans and as such, produces better compression than PDF.
See L<http://www.djvuzone.org/> for more details.

=head3 Email as PDF

Attaches the current, selected or all pages as a PDF to a blank email.
This requires xdg-email, which is in the xdg-utils package.
If this is not present, the option is ghosted out.

=head2 Edit

=head3 Delete

Deletes the selected page.

=head3 Renumber

Renumbers the pages from 1..n.

Note that the page order can also be changed by drag and drop in the thumbnail view.

=head3 Select All

Selects all pages.

=head3 Frontend

gscan2pdf supports two frontends, scanimage and scanadf.
scanadf support was added when it was realised that scanadf works better with some scanners than scanimage.
However, to convert the output, the scanadf option requires imagemagick and if this is not present the option is ghosted out.
On Ubuntu Edgy, scanadf is in the sane package, not, like scanimage, in sane-utils.
If scanadf is not present, the option is obviously ghosted out.

=head2 View

=head3 Zoom 100%

Zooms to 1:1. How this appears depends on the desktop resolution.

=head3 Zoom to fit

Scales the view such that all the page is visible.

=head3 Zoom in

=head3 Zoom out

=head3 Rotate 90 clockwise

The rotate options require the package imagemagick and, if this is not present, are ghosted out.

=head3 Rotate 180

=head3 Rotate 90 anticlockwise

=head2 Tools

=head3 unpaper

unpaper (see L<http://unpaper.berlios.de/>) is a utility for cleaning up a scan.
At the moment, having selected whether the scan has a single or a double page, and which sides to use for the alignment, the scan will be deskewed (rotated) and centred.

=head3 OCR (Optical Character Recognition)

The gocr utility is used to process the current page.
The output is displayed and an option is given to save it to a text file.

There is an OCR output buffer for each page and is embedded both as an annotation (pop-up note) and as plain text behind the scanned image in the PDF produced.
This way, Beagle can index (i.e. search) the plain text, and the contents of the annotations, can be viewed in Acrobat Reader.

=head1 FAQs

=head2 Why isn't option xyz available in the scan window?

Possibly because SANE or your scanner doesn't support it.

If an option listed in the output of C<scanimage --help> that you would like to use isn't available, send me the output and I will look at implementing it.

=head2 I've only got an old flatbed scanner with no automatic sheetfeeder.
How do I scan a multipage document?

If you are lucky, you have an option like Wait-for-button or Button-wait, where the scanner will wait for you to press the scan button on the device before it starts the scan, allowing you to scan multiple pages without touching the computer.

Otherwise, you have to set the number of pages to scan to 1 and hit the scan button on the scan window for each page.

=head2 Why is option xyz ghosted out?

Probably because the package required for that option is not installed.
Email as PDF requires xdg-email (xdg-utils), scanadf and the rotate options require imagemagick.

=head2 Why doesn't Email to PDF work with Thunderbird?

Because xdg-email doesn't support creating new emails with attachments in Thunderbird.

=head1 See Also

Xsane

=head1 Author

Jeffrey Ratcliffe (ra28145 at users dot sourceforge dot net)

=head1 Thanks to

All the people who have sent patches, bugs and feedback.
Also to Sourceforge for hosting the project.

=for html <a href="http://sourceforge.net"><img alt="SourceForge.net Logo" align="right" height="31px" width="88px"
src="http://sourceforge.net/sflogo.php?group_id=174140&amp;type=1"></a><a href="http://sourceforge.net/donate/index.php?group_id=174140"><img src="http://sourceforge.net/images/project-support.jpg" width="88" height="32" border="0" alt="Support This Project"></a>

=cut
