Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- #!/usr/bin/perl
- eval 'exec /usr/bin/perl -S $0 ${1+"$@"}'
- if 0; # not running under some shell
- # gscan2pdf --- to aid the scan to PDF or DjVu process
- # Copyright (C) 2006--2013 Jeffrey Ratcliffe <[email protected]>
- # This program is free software: you can redistribute it and/or modify
- # it under the terms of the version 3 GNU General Public License as
- # published by the Free Software Foundation.
- # This program is distributed in the hope that it will be useful,
- # but WITHOUT ANY WARRANTY; without even the implied warranty of
- # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- # GNU General Public License for more details.
- # You should have received a copy of the GNU General Public License
- # along with this program. If not, see <http://www.gnu.org/licenses/>.
- # Release procedure:
- # Use
- # make tidy
- # TEST_AUTHOR=1 make test
- # immediately before release so as not to affect any patches
- # in between, and then consistently before each commit afterwards.
- # 0. Test scan in lineart, greyscale and colour.
- # 1. New screendump required? Print screen creates screenshot.png in Desktop.
- # Download new translations (https://translations.launchpad.net/gscan2pdf)
- # Upload .pot
- # Update translators in credits (https://launchpad.net/gscan2pdf/+topcontributors)
- # Check a locale with LC_ALL=de_DE LC_MESSAGES=de_DE LC_CTYPE=de_DE LANG=de_DE LANGUAGE=de_DE bin/gscan2pdf --log=log --locale=<wherever the locale directory is>
- # Check $version
- # Make appropriate updates to debian/changelog
- # 2. perl Makefile.PL
- # make rpmdist debdist
- # test dist sudo dpkg -i gscan2pdf_x.x.x_all.deb
- # 3. git status
- # git tag vx.x.x
- # git push --tags ssh://[email protected]/p/gscan2pdf/code master
- # 4. make remote-html
- # 5. create version directory in https://sourceforge.net/projects/gscan2pdf/files/gscan2pdf
- # make file_releases
- # 6. Freshmeat freshmeat.net/projects/gscan2pdf/releases/new (requires summary of changes)
- # 7. Launchpad (https://launchpad.net/gscan2pdf/trunk/+addrelease)
- # 8. make ppa
- # name the release -0~ppa1<release>, where release is precise, hardy, etc
- # debuild -S -sa, dput gscan2pdf-ppa .changes
- # https://launchpad.net/~jeffreyratcliffe/+archive
- # ftp-master (check contents with dpkg-deb --contents)
- use warnings;
- use strict;
- use feature "switch";
- use Gscan2pdf::Document;
- use Gscan2pdf::Frontend::Sane;
- use Gscan2pdf::Frontend::CLI;
- use Gscan2pdf::NetPBM;
- use Gscan2pdf::Scanner::Options;
- use Gscan2pdf::Dialog::Scan::Sane;
- use Gscan2pdf::Tesseract;
- use Gscan2pdf::Ocropus;
- use Gscan2pdf::Cuneiform;
- use Gscan2pdf::Unpaper;
- use Gscan2pdf::Dialog::Renumber;
- use Sane 0.05; # To get SANE_* enums
- use Gtk2::ImageView;
- use Goo::Canvas;
- use Gtk2::Ex::Simple::List;
- use Gtk2 -init; # Could just call init separately
- use Gtk2::Gdk::Keysyms;
- use Cwd; # To obtain current working directory
- use File::Basename; # Split filename into dir, file, ext
- use File::Copy;
- use File::Temp; # To create temporary files
- use Glib qw(TRUE FALSE); # To get TRUE and FALSE
- use Config::General 2.40;
- use PDF::API2;
- use Getopt::Long;
- use Set::IntSpan 1.10; # For size method for page numbering issues
- use Proc::Killfam;
- use Fcntl qw(:flock)
- ; # import LOCK_* constants to prevent us clobbering running instances
- use Log::Log4perl;
- use Try::Tiny;
- use Data::Dumper;
- # To sort out LC_NUMERIC and $SIG{CHLD}
- use POSIX qw(locale_h :signal_h :errno_h :sys_wait_h);
- use Locale::gettext 1.05; # For translations
- Glib::set_application_name('gscan2pdf');
- my $prog_name = Glib::get_application_name;
- my $version = '1.1.3';
- # 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/LANGUAGE/LC_MESSAGES/$prog_name.mo
- # or whatever is set by $d->dir([NEWDIR]);
- my $d = Locale::gettext->domain($prog_name);
- my $d_sane = Locale::gettext->domain('sane-backends');
- my $debug = FALSE;
- my ( $test, $help, $log, $log_level, @device,
- $locale, @device_list, $test_image );
- my @args = (
- 'device=s' => \@device,
- 'test=s%' => \$test,
- 'test-image=s' => \$test_image,
- 'locale=s' => \$locale,
- 'help' => \$help,
- 'log=s' => \$log,
- 'debug' => sub { $log_level = 'DEBUG'; $debug = TRUE },
- 'info' => sub { $log_level = 'INFO' },
- 'warn' => sub { $log_level = 'WARN' },
- 'error' => sub { $log_level = 'ERROR' },
- 'fatal' => sub { $log_level = 'FATAL' },
- 'version' => sub { warn "$prog_name $version\n"; exit 0 },
- );
- exit 1 unless ( GetOptions(@args) );
- $Sane::DEBUG = $debug;
- if ( not defined($log_level) ) {
- if ( defined $log ) {
- $log_level = 'DEBUG';
- $debug = TRUE;
- }
- else {
- $log_level = 'ERROR';
- }
- }
- my $log_conf = <<'EOS';
- log4perl.appender.Screen = Log::Log4perl::Appender::Screen
- log4perl.appender.Screen.layout = Log::Log4perl::Layout::SimpleLayout
- EOS
- if ( defined $log ) {
- $log_conf .= <<"EOS";
- log4perl.appender.Logfile = Log::Log4perl::Appender::File
- log4perl.appender.Logfile.filename = $log
- log4perl.appender.Logfile.mode = write
- log4perl.appender.Logfile.layout = Log::Log4perl::Layout::SimpleLayout
- log4perl.category = $log_level, Logfile, Screen
- EOS
- }
- else {
- $log_conf .= <<"EOS";
- log4perl.category = $log_level, Screen
- EOS
- }
- if ( defined $help ) {
- system("perldoc $0") == 0 or die $d->get('Error displaying help'), "\n";
- }
- Log::Log4perl::init( \$log_conf );
- my $logger = Log::Log4perl::get_logger();
- $logger->info("Starting $prog_name $version");
- $logger->info("Log level $log_level");
- if ( defined $locale ) {
- if ( $locale =~ /^\//x ) {
- $d->dir($locale);
- }
- else {
- $d->dir( getcwd . "/$locale" );
- }
- }
- for (@device) {
- push @device_list, { name => $_, label => $_ };
- }
- # Set LC_NUMERIC to C to prevent decimal commas (or anything else) confusing
- # scanimage
- setlocale( LC_NUMERIC, "C" );
- $logger->info( "Using ", setlocale(LC_CTYPE), " locale" );
- $logger->info( "Startup LC_NUMERIC ", setlocale(LC_NUMERIC) );
- # Read config file
- my $rc = "$ENV{'HOME'}/.$prog_name";
- $logger->info("Reading config from $ENV{'HOME'}/.$prog_name");
- system("touch $rc") unless ( -r $rc );
- my $conf;
- our %SETTING;
- try {
- $conf = Config::General->new(
- -ConfigFile => $rc,
- -SplitPolicy => 'equalsign',
- -UTF8 => 1,
- );
- }
- catch {
- $logger->error(
- $d->get(
- "Error: unable to load settings.\nBacking up settings\nReverting to defaults"
- )
- );
- move( $rc, "$rc.old" );
- }
- finally {
- %SETTING = $conf->getall unless (@_);
- };
- my %default_settings = (
- 'window_width' => 800,
- 'window_height' => 600,
- 'window_maximize' => TRUE,
- 'thumb panel' => 100,
- 'Page range' => 'all',
- 'layout' => 'single',
- 'downsample' => FALSE,
- 'downsample dpi' => 150,
- 'threshold tool' => 80,
- 'unsharp radius' => 0,
- 'unsharp sigma' => 1,
- 'unsharp amount' => 1,
- 'unsharp threshold' => 0.05,
- 'cache options' => TRUE,
- 'restore window' => TRUE,
- 'startup warning' => FALSE,
- 'date offset' => 0,
- 'pdf compression' => 'auto',
- 'quality' => 75,
- 'pages to scan' => 1,
- 'unpaper on scan' => TRUE,
- 'OCR on scan' => TRUE,
- 'frontend' => 'libsane-perl',
- 'rotate facing' => 0,
- 'rotate reverse' => 0,
- 'default filename' => '%a %y-%m-%d',
- 'scan prefix' => '',
- 'Blank threshold' => 0.005, # Blank page standard deviation threshold
- 'Dark threshold' => 0.12, # Dark page mean threshold
- 'ocr engine' => 'ocropus',
- 'OCR output' =>
- 'replace', # When a page is re-OCRed, replace old text with new text
- 'Paper' => {
- $d->get('A4') => {
- x => 210,
- y => 297,
- l => 0,
- t => 0,
- },
- $d->get('US Letter') => {
- x => 216,
- y => 279,
- l => 0,
- t => 0,
- },
- $d->get('US Legal') => {
- x => 216,
- y => 356,
- l => 0,
- t => 0,
- },
- },
- user_defined_tools => ['gimp %i']
- );
- delete $SETTING{frontend}
- if (
- defined $SETTING{frontend}
- and ( $SETTING{frontend} eq 'scanimage-perl'
- or $SETTING{frontend} eq 'scanadf-perl'
- or $SETTING{frontend} eq 'scanimage.pl'
- or $SETTING{frontend} eq
- 'scanadf.pl' ) # these frontends are just for testing purposes
- );
- for ( keys(%default_settings) ) {
- $SETTING{$_} = $default_settings{$_} unless ( defined $SETTING{$_} );
- }
- # Remove invalid paper sizes
- for my $paper ( keys %{ $SETTING{Paper} } ) {
- if ( $paper eq '<>' or $paper eq '</>' ) {
- delete $SETTING{Paper}{$paper};
- }
- else {
- for (qw(x y t l)) {
- unless ( defined $SETTING{Paper}{$paper}{$_} ) {
- delete $SETTING{Paper}{$paper};
- last;
- }
- }
- }
- }
- # Delete the options cache if there is a new version of SANE
- if (
- (
- defined( $SETTING{'SANE version'} )
- and $SETTING{'SANE version'} ne join( '.', Sane->get_version )
- )
- or ( defined( $SETTING{'libsane-perl version'} )
- and $SETTING{'libsane-perl version'} ne $Sane::VERSION )
- )
- {
- delete $SETTING{cache} if ( defined $SETTING{cache} );
- }
- $SETTING{'SANE version'} = join( '.', Sane->get_version );
- $SETTING{'libsane-perl version'} = $Sane::VERSION;
- # Set up test mode and make sure file has absolute path and is readable
- if ( keys %{$test} ) {
- $SETTING{frontend} = 'scanimage-perl';
- for my $file ( keys %{$test} ) {
- my $device = $test->{$file};
- delete $test->{$file};
- # Find a way of emulating the nonsense \n that some people seem to get
- $device =~ s/\\n/\n/g;
- $logger->debug("'$file','$device'");
- $file = getcwd . "/$file" if ( $file !~ /^\//x );
- if ( !-r $file ) {
- $logger->fatal( sprintf( $d->get("Cannot read file: %s"), $file ) );
- exit 1;
- }
- push @{ $test->{file} }, $file;
- $test->{output} = '' unless ( defined( $test->{output} ) );
- $test->{output} .=
- "'$#{$test->{file}}','$device','" . basename($file) . "'\n";
- }
- }
- # GetOptions leaves $test as a reference to an empty hash.
- else {
- undef $test;
- }
- if ( defined $test_image ) {
- $test_image = expand_tildes($test_image);
- if ( -r $test_image ) {
- $logger->info("Using test image $test_image");
- }
- else {
- $logger->fatal( sprintf( $d->get("Cannot read file: %s"), $test_image ) );
- exit 1;
- }
- }
- $logger->info("Perl version $^V");
- $logger->info("Glib-Perl version $Glib::VERSION");
- $logger->info( 'Built for Glib ' . join( '.', Glib->GET_VERSION_INFO ) );
- $logger->info( 'Running with Glib '
- . join( '.', Glib::major_version, Glib::minor_version, Glib::micro_version )
- );
- $logger->info("Gtk2-Perl version $Gtk2::VERSION");
- $logger->info( "Built for GTK " . join( '.', Gtk2->GET_VERSION_INFO ) );
- $logger->info( "Running with GTK " . join( '.', Gtk2->get_version_info ) );
- $logger->info( "Using GtkImageView version ",
- Gtk2::ImageView->library_version );
- $logger->info("Using Gtk2::ImageView version $Gtk2::ImageView::VERSION");
- $logger->info("Using PDF::API2 version $PDF::API2::VERSION");
- $logger->info( 'Using Sane version ' . join( '.', Sane->get_version ) );
- $logger->info("Using libsane-perl version $Sane::VERSION");
- $logger->debug( Dumper( \%SETTING ) ) if ($debug);
- # Just in case dependencies have changed, put put startup warning again
- if ( not defined( $SETTING{version} ) or $SETTING{version} ne $version ) {
- $SETTING{'startup warning'} = TRUE;
- delete $SETTING{cache} if ( defined $SETTING{cache} );
- }
- $SETTING{version} = $version;
- # Initialise thread handler
- Gscan2pdf::Document->setup($logger);
- # Initialise SANE frontends
- Gscan2pdf::Frontend::Sane->setup($logger);
- Gscan2pdf::Frontend::CLI->setup($logger);
- # Update list in Gscan2pdf::Document so that it can be used by get_resolution
- Gscan2pdf::Document->set_paper_sizes( $SETTING{Paper} );
- # Create icons for rotate buttons
- my $IconFactory = undef;
- my $iconpath;
- if ( -d '/usr/share/gscan2pdf' ) {
- $iconpath = '/usr/share/gscan2pdf';
- }
- else {
- $iconpath =
- '.'; # FIXME: make this a big cleverer, going one dir down from bin.
- }
- init_icons(
- [ 'rotate90', "$iconpath/stock-rotate-90.svg" ],
- [ 'rotate180', "$iconpath/stock-rotate-180.svg" ],
- [ 'rotate270', "$iconpath/stock-rotate-270.svg" ],
- [ 'scanner', "$iconpath/scanner.svg" ],
- [ 'pdf', "$iconpath/pdf.svg" ],
- [ 'selection', "$iconpath/stock-selection-all-16.png" ],
- );
- # Define application-wide variables here so that they can be referenced
- # in the menu callbacks
- my (
- $slist, $windowi, $windowe, $windows, $windows2, $windowh, $windowo, $windowrn,
- $windowu, $vboxd, $labeld, $combobd, $combobp, $vbox_manual_paper,
- @undo_buffer,
- @redo_buffer, @undo_selection, @redo_selection, %dependencies,
- %helperTag, $menubar, $toolbar,
- @ocr_engine, $bscanall, $bscannum, @clipboard, $windowd,
- $windowr, $view, $frames, $duplex, $windowp,
- $batch_scan, $print_settings, $windowc, $current_page,
- # Spin buttons on scan dialog
- $spin_buttons, $start, $spin_buttoni, $spin_buttonn,
- # Goo::Canvas for OCR output
- $canvas,
- # Scan button on scan dialog
- $scan_button,
- # List of PageRange widgets
- @prlist,
- # List of user-defined tools
- @user_defined_tools,
- # Spinbuttons for selector on crop dialog
- $sb_selector_x, $sb_selector_y, $sb_selector_w, $sb_selector_h,
- # dir below session dir
- $tmpdir,
- # session dir
- $session,
- # filehandle for session lockfile
- $lockfh,
- # Temp::File object for PDF to be emailed
- # Define here to make sure that it doesn't get deleted until the next email
- # is created or we quit
- $pdf,
- );
- 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
- ],
- [
- 'Open', 'gtk-open',
- $d->get('_Open'), '<control>o',
- $d->get('Open image file(s)'), \&open_dialog
- ],
- [
- 'Scan',
- 'scanner',
- $d->get('S_can'),
- '<control>g',
- $d->get('Scan document'),
- sub { $SETTING{frontend} eq 'libsane-perl' ? scan_dialog2() : scan_dialog() }
- ],
- [
- 'Save', 'gtk-save', $d->get('Save'), '<control>s',
- $d->get('Save'), \&save_dialog
- ],
- [
- 'Email as PDF', 'gtk-edit',
- $d->get('_Email as PDF'), '<control>e',
- $d->get('Attach as PDF to a new email'), \&email
- ],
- [
- 'Print', 'gtk-print', $d->get('_Print'), '<control>p',
- $d->get('Print'), \&print_dialog
- ],
- [
- 'Compress', undef,
- $d->get('_Compress temporary files'), undef,
- $d->get('Compress temporary files'), \&compress_temp
- ],
- [
- 'Quit', 'gtk-quit',
- $d->get('_Quit'), '<control>q',
- $d->get('Quit'), sub { Gtk2->main_quit if 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
- ],
- [
- 'Cut', 'gtk-cut',
- $d->get('Cu_t'), '<control>x',
- $d->get('Cut selection'), \&cut_selection
- ],
- [
- 'Copy', 'gtk-copy',
- $d->get('_Copy'), '<control>c',
- $d->get('Copy selection'), \©_selection
- ],
- [
- 'Paste', 'gtk-paste',
- $d->get('_Paste'), '<control>v',
- $d->get('Paste selection'), \&paste_selection
- ],
- [
- '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'), \&renumber_dialog
- ],
- [ 'Select', undef, $d->get('_Select') ],
- [
- 'Select All', 'gtk-select-all',
- $d->get('_All'), '<control>a',
- $d->get('Select all pages'), \&select_all
- ],
- [
- 'Select Odd', undef, $d->get('_Odd'), '<control>1',
- $d->get('Select all odd-numbered pages'),
- sub { select_odd_even(0); }
- ],
- [
- 'Select Even', undef, $d->get('_Even'), '<control>2',
- $d->get('Select all evenly-numbered pages'),
- sub { select_odd_even(1); }
- ],
- [
- 'Select Blank', 'gtk-select-blank',
- $d->get('_Blank'), '<control>b',
- $d->get('Select pages with low standard deviation'), \&analyse_select_blank
- ],
- [
- 'Select Dark', 'gtk-select-blank',
- $d->get('_Dark'), '<control>d',
- $d->get('Select dark pages'), \&analyse_select_dark
- ],
- [
- 'Select Modified',
- 'gtk-select-modified',
- $d->get('_Modified'),
- '<control>m',
- $d->get('Select modified pages since last OCR'),
- \&select_modified_since_ocr
- ],
- [
- 'Select No OCR', undef,
- $d->get('_No OCR'), undef,
- $d->get('Select pages with no OCR output'), \&select_no_ocr
- ],
- [
- 'Clear OCR', 'gtk-clear',
- $d->get('_Clear OCR'), undef,
- $d->get('Clear OCR output from selected pages'), \&clear_ocr
- ],
- [
- 'Properties', 'gtk-properties',
- $d->get('Propert_ies'), undef,
- $d->get('Edit image properties'), \&properties
- ],
- [
- 'Preferences', 'gtk-preferences',
- $d->get('Prefere_nces'), undef,
- $d->get('Edit preferences'), \&preferences
- ],
- # View menu
- [ 'View', undef, $d->get('_View') ],
- [
- 'Zoom 100', 'gtk-zoom-100',
- $d->get('Zoom _100%'), undef,
- $d->get('Zoom to 100%'), sub { $view->set_zoom(1.0); }
- ],
- [
- 'Zoom to fit', 'gtk-zoom-fit',
- $d->get('Zoom to _fit'), undef,
- $d->get('Zoom to fit'), sub { $view->set_fitting(TRUE); }
- ],
- [
- 'Zoom in', 'gtk-zoom-in',
- $d->get('Zoom _in'), 'plus',
- $d->get('Zoom in'), sub { $view->zoom_in; }
- ],
- [
- 'Zoom out', 'gtk-zoom-out',
- $d->get('Zoom _out'), 'minus',
- $d->get('Zoom out'), sub { $view->zoom_out; }
- ],
- [
- 'Rotate 90',
- 'rotate90',
- $d->get('Rotate 90 clockwise'),
- '<control><shift>R',
- $d->get('Rotate 90 clockwise'),
- sub { rotate( 90, [ indices2pages( $slist->get_selected_indices ) ] ) }
- ],
- [
- 'Rotate 180',
- 'rotate180',
- $d->get('Rotate 180'),
- '<control><shift>F',
- $d->get('Rotate 180'),
- sub { rotate( 180, [ indices2pages( $slist->get_selected_indices ) ] ) }
- ],
- [
- 'Rotate 270',
- 'rotate270',
- $d->get('Rotate 90 anticlockwise'),
- '<control><shift>C',
- $d->get('Rotate 90 anticlockwise'),
- sub { rotate( 270, [ indices2pages( $slist->get_selected_indices ) ] ) }
- ],
- # Tools menu
- [ 'Tools', undef, $d->get('_Tools') ],
- [
- 'Threshold', undef, $d->get('_Threshold'), undef,
- $d->get('Change each pixel above this threshold to black'),
- \&threshold
- ],
- [
- 'Negate', undef, $d->get('_Negate'), undef,
- $d->get('Converts black to white and vice versa'), \&negate
- ],
- [
- 'Unsharp', undef,
- $d->get('_Unsharp Mask'), undef,
- $d->get('Apply an unsharp mask'), \&unsharp
- ],
- [
- 'Crop', 'GTK_STOCK_LEAVE_FULLSCREEN', $d->get('_Crop'), undef,
- $d->get('Crop pages'), \&crop
- ],
- [
- 'unpaper', undef,
- $d->get('_Clean up'), undef,
- $d->get('Clean up scanned images with unpaper'), \&unpaper
- ],
- [
- 'OCR', undef, $d->get('_OCR'), undef,
- $d->get('Optical Character Recognition'), \&OCR
- ],
- [ 'User-defined', undef, $d->get('U_ser-defined') ],
- # 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 @image_tools = (
- [
- 'DraggerTool', 'gtk-refresh',
- $d->get('_Drag'), undef,
- $d->get('Use the hand tool'), 10
- ],
- [
- 'SelectorTool', 'selection',
- $d->get('_Select'), undef,
- $d->get('Use the rectangular selection tool'), 20
- ],
- [
- 'PainterTool', 'gtk-media-play',
- $d->get('_Paint'), undef,
- $d->get('Use the painter tool'), 30
- ]
- );
- # Declare the XML structure
- my $uimanager;
- my $ui = <<'EOS';
- <ui>
- <menubar name='MenuBar'>
- <menu action='File'>
- <menuitem action='New'/>
- <menuitem action='Open'/>
- <menuitem action='Scan'/>
- <menuitem action='Save'/>
- <menuitem action='Email as PDF'/>
- <menuitem action='Print'/>
- <separator/>
- <menuitem action='Compress'/>
- <separator/>
- <menuitem action='Quit'/>
- </menu>
- <menu action='Edit'>
- <menuitem action='Undo'/>
- <menuitem action='Redo'/>
- <separator/>
- <menuitem action='Cut'/>
- <menuitem action='Copy'/>
- <menuitem action='Paste'/>
- <menuitem action='Delete'/>
- <separator/>
- <menuitem action='Renumber'/>
- <menu action='Select'>
- <menuitem action='Select All'/>
- <menuitem action='Select Odd'/>
- <menuitem action='Select Even'/>
- <menuitem action='Select Blank'/>
- <menuitem action='Select Dark'/>
- <menuitem action='Select Modified'/>
- <menuitem action='Select No OCR'/>
- </menu>
- <menuitem action='Clear OCR'/>
- <separator/>
- <menuitem action='Properties'/>
- <separator/>
- <menuitem action='Preferences'/>
- </menu>
- <menu action='View'>
- <menuitem action = 'DraggerTool'/>
- <menuitem action = 'SelectorTool'/>
- <separator/>
- <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='Threshold'/>
- <menuitem action='Negate'/>
- <menuitem action='Unsharp'/>
- <menuitem action='Crop'/>
- <separator/>
- <menuitem action='unpaper'/>
- <menuitem action='OCR'/>
- <separator/>
- <menu action='User-defined'>
- </menu>
- </menu>
- <menu action='Help menu'>
- <menuitem action='Help'/>
- <menuitem action='About'/>
- </menu>
- </menubar>
- <toolbar name='ToolBar'>
- <toolitem action='New'/>
- <toolitem action='Open'/>
- <toolitem action='Scan'/>
- <toolitem action='Save'/>
- <toolitem action='Email as PDF'/>
- <toolitem action='Print'/>
- <separator/>
- <toolitem action='Undo'/>
- <toolitem action='Redo'/>
- <separator/>
- <toolitem action='Cut'/>
- <toolitem action='Copy'/>
- <toolitem action='Paste'/>
- <toolitem action='Delete'/>
- <separator/>
- <toolitem action='Renumber'/>
- <toolitem action='Select All'/>
- <separator/>
- <toolitem action = 'DraggerTool'/>
- <toolitem action = 'SelectorTool'/>
- <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 = 'DraggerTool'/>
- <menuitem action = 'SelectorTool'/>
- <separator/>
- <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='Cut'/>
- <menuitem action='Copy'/>
- <menuitem action='Paste'/>
- <menuitem action='Delete'/>
- <separator/>
- <menuitem action='Properties'/>
- </popup>
- <popup name='Thumb_Popup'>
- <menuitem action='Save'/>
- <menuitem action='Email as PDF'/>
- <menuitem action='Print'/>
- <separator/>
- <menuitem action='Renumber'/>
- <menuitem action='Select All'/>
- <menuitem action='Select Odd'/>
- <menuitem action='Select Even'/>
- <separator/>
- <menuitem action='Rotate 90'/>
- <menuitem action='Rotate 180'/>
- <menuitem action='Rotate 270'/>
- <separator/>
- <menuitem action='Cut'/>
- <menuitem action='Copy'/>
- <menuitem action='Paste'/>
- <menuitem action='Delete'/>
- <separator/>
- <menuitem action='Clear OCR'/>
- <separator/>
- <menuitem action='Properties'/>
- </popup>
- </ui>
- EOS
- # Create the window
- my $window = Gtk2::Window->new;
- $window->set_title("$prog_name v$version");
- $window->signal_connect(
- 'delete-event' => sub {
- if ( quit() ) {
- Gtk2->main_quit;
- }
- else {
- return TRUE;
- }
- }
- );
- # 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
- if ( $SETTING{'restore window'} ) {
- $window->set_default_size( $SETTING{'window_width'},
- $SETTING{'window_height'} );
- $window->move( $SETTING{'window_x'}, $SETTING{'window_y'} )
- if ( defined( $SETTING{'window_x'} ) and defined( $SETTING{'window_y'} ) );
- $window->maximize if ( $SETTING{'window_maximize'} );
- }
- try { $window->set_default_icon_from_file("$iconpath/gscan2pdf.svg"); }
- catch {
- $logger->warn("Unable to load icon `$iconpath/gscan2pdf.svg': $@");
- };
- my $main_vbox = Gtk2::VBox->new;
- $window->add($main_vbox);
- # Create the menu bar
- create_menu_bar();
- $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;
- $hpaned->set_position( $SETTING{'thumb panel'} );
- $main_vbox->pack_start( $hpaned, TRUE, TRUE, 0 );
- # Scrolled window for thumbnails
- my $scwin_thumbs = Gtk2::ScrolledWindow->new;
- # resize = FALSE to stop the panel expanding on being resized (Debian #507032)
- $hpaned->pack1( $scwin_thumbs, FALSE, TRUE );
- $scwin_thumbs->set_policy( 'automatic', 'automatic' );
- $scwin_thumbs->set_shadow_type('etched-in');
- # Set up a SimpleList
- $slist = Gscan2pdf::Document->new;
- my $target_entry = {
- target => 'Glib::Scalar', # some string representing the drag type
- flags => 'same-widget', # Gtk2::TargetFlags
- info => 1, # some app-defined integer identifier
- };
- $slist->drag_source_set( 'button1-mask', [ 'copy', 'move' ], $target_entry );
- $slist->drag_dest_set(
- [ 'motion', 'highlight' ],
- [ 'copy', 'move' ],
- $target_entry
- );
- $slist->signal_connect(
- 'drag-data-get' => sub {
- my ( $tree, $context, $sel ) = @_;
- $sel->set( $sel->target, 8, 'data' );
- }
- );
- $slist->signal_connect(
- 'drag-data-delete' => sub {
- my ( $tree, $context ) = @_;
- my $model = $tree->get_model;
- my @data = $tree->get_selection->get_selected_rows;
- for ( reverse @data ) {
- my $iter = $model->get_iter($_);
- my $info = $model->get( $iter, 0 );
- $model->remove($iter);
- }
- $tree->get_selection->unselect_all;
- }
- );
- $slist->signal_connect(
- 'drag-data-received' => sub {
- my ( $tree, $context, $x, $y, $sel ) = @_;
- my ( $path, $how ) = $tree->get_dest_row_at_pos( $x, $y );
- my $model = $tree->get_model;
- my $data = $sel->data or return;
- my $delete =
- $context->action == 'move'; ## no critic (ProhibitMismatchedOperators)
- my @rows = $tree->get_selection->get_selected_rows or return;
- my @data;
- for (@rows) {
- my $iter = $model->get_iter($_);
- my @info = $model->get($iter);
- my $suffix;
- $suffix = $1 if ( $info[2]->{filename} =~ /(\.\w*)$/x );
- my $new = File::Temp->new( DIR => $SETTING{session}, SUFFIX => $suffix );
- # quotes required to prevent File::Temp objects from being clobbered
- copy( "$info[2]->{filename}", "$new" )
- or show_message_dialog( $window, 'error', 'close',
- $d->get('Error copying page') );
- $info[2]->{filename} = $new;
- push @data, [@info];
- }
- # Block row-changed signal so that the list can be updated before the sort
- # takes over.
- $slist->get_model->signal_handler_block( $slist->{row_changed_signal} );
- if ($path) {
- if ( $how eq 'after' or $how eq 'into-or-after' ) {
- splice @{ $slist->{data} }, $path->to_string + 1, 0, @data;
- }
- else {
- splice @{ $slist->{data} }, $path->to_string, 0, @data;
- }
- }
- else {
- push @{ $slist->{data} }, @data;
- }
- $slist->renumber;
- $slist->get_model->signal_handler_unblock( $slist->{row_changed_signal} );
- # Update the start spinbutton if necessary
- update_start();
- $context->finish( 1, $delete, time );
- }
- );
- # Callback for dropped signal.
- $slist->signal_connect(
- drag_drop => sub {
- my ( $tree, $context, $x, $y, $when ) = @_;
- if ( my $targ = $context->targets ) {
- $tree->drag_get_data( $context, $targ, $when );
- return TRUE;
- }
- return FALSE;
- }
- );
- # If dragged below the bottom of the window, scroll it.
- $slist->signal_connect(
- 'drag-motion' => sub {
- my ( $tree, $context, $x, $y, $t ) = @_;
- my ( $path, $how ) = $tree->get_dest_row_at_pos( $x, $y ) or return;
- my $scroll = $tree->parent;
- # Add the marker showing the drop in the tree
- $tree->set_drag_dest_row( $path, $how );
- # Make move the default
- my @action;
- if ( $context->actions == 'copy' ) ## no critic (ProhibitMismatchedOperators)
- {
- @action = ('copy');
- }
- else {
- @action = ('move');
- }
- $context->status( @action, $t );
- my $adj = $scroll->get_vadjustment;
- my ( $value, $step ) = ( $adj->value, $adj->step_increment );
- if ( $y > $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 < $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 );
- # 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->{row_changed_signal} = $slist->get_model->signal_connect(
- 'row-changed' => sub {
- $slist->get_model->signal_handler_block( $slist->{row_changed_signal} );
- # Sort pages
- $slist->manual_sort_by_column(0);
- # And make sure there are no duplicates
- $slist->renumber;
- $slist->get_model->signal_handler_unblock( $slist->{row_changed_signal} );
- # Update the start spinbutton if necessary
- update_start();
- }
- );
- $scwin_thumbs->add($slist);
- # Notebook for detail view and OCR output
- my $vnotebook = Gtk2::Notebook->new;
- $hpaned->pack2( $vnotebook, TRUE, TRUE );
- # ImageView for detail view
- $view = Gtk2::ImageView->new;
- my $dragger = Gtk2::ImageView::Tool::Dragger->new($view);
- my $selector = Gtk2::ImageView::Tool::Selector->new($view);
- my $painter = Gtk2::ImageView::Tool::Painter->new($view);
- $vnotebook->append_page( $view, $d->get('Image') );
- $view->signal_connect(
- button_press_event => sub {
- my ( $widget, $event ) = @_;
- handle_clicks( $widget, $event );
- }
- );
- $view->signal_connect( button_release_event => \&handle_clicks );
- $view->signal_connect(
- 'zoom-changed' => sub {
- $canvas->set_scale( $view->get_zoom ) if ( defined $canvas );
- }
- );
- # Callback if the selection changes
- $selector->{selection_changed_signal} = $selector->signal_connect(
- 'selection-changed' => sub {
- my $sel = $selector->get_selection;
- if ( defined $sel ) {
- $SETTING{selection} = [ $sel->values ];
- if ( defined $sb_selector_x ) {
- $sb_selector_x->set_value( $SETTING{selection}[0] );
- $sb_selector_y->set_value( $SETTING{selection}[1] );
- $sb_selector_w->set_value( $SETTING{selection}[2] );
- $sb_selector_h->set_value( $SETTING{selection}[3] );
- }
- }
- }
- );
- # ScrolledWindow for Goo::Canvas for OCR output
- my $scwin_buffer = Gtk2::ScrolledWindow->new;
- $scwin_buffer->set_policy( 'automatic', 'automatic' );
- $scwin_buffer->set_shadow_type('etched-in');
- $vnotebook->append_page( $scwin_buffer, $d->get('OCR Output') );
- # Set up call back for list selection to update detail view
- $slist->{selection_changed_signal} = $slist->get_selection->signal_connect(
- changed => sub {
- my @page = $slist->get_selected_indices;
- # Display the new image
- $scwin_buffer->remove($_) for ( $scwin_buffer->get_children );
- if (@page) {
- my $path = Gtk2::TreePath->new_from_indices( $page[0] );
- $slist->scroll_to_cell($path);
- my $sel = $selector->get_selection;
- display_image( $slist->{data}[ $page[0] ][2] );
- $selector->set_selection($sel) if ( defined $sel );
- }
- else {
- $view->set_pixbuf(undef);
- undef $current_page;
- }
- # If no pages are selected, but some exist, PageRange widgets default to all
- if ( $#page == -1 and $#{ $slist->{data} } > -1 ) {
- $SETTING{'Page range'} = 'all';
- foreach (@prlist) {
- $_->set_active( $SETTING{'Page range'} );
- }
- }
- }
- );
- # _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} ) ## no critic (ProhibitPackageVars)
- ;
- delete_pages();
- return TRUE;
- }
- );
- # If defined in the config file, set the current directory
- $SETTING{'cwd'} = getcwd unless ( defined( $SETTING{'cwd'} ) );
- my $unpaper = Gscan2pdf::Unpaper->new( $SETTING{'unpaper options'} );
- # 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') },
- 'gain' => { string => $d->get('Gain') },
- 'contrast' => { string => $d->get('Contrast') },
- 'threshold' => { string => $d->get('Threshold') },
- 'speed' => {
- string => $d->get('Speed'),
- values => {
- 'yes' => $d->get('Yes'),
- 'no' => $d->get('No'),
- },
- },
- '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'),
- },
- },
- 'calibration-cache' => {
- string => $d->get('Cache calibration'),
- 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'),
- },
- },
- 'pagewidth' => { string => $d->get('Page Width') },
- 'pageheight' => { string => $d->get('Page Height') },
- 'page-width' => { string => $d->get('Page Width') },
- 'page-height' => { string => $d->get('Page Height') },
- 'overscan-top' => { string => $d->get('Top Overscan') },
- 'overscan-bottom' => { string => $d->get('Bottom Overscan') },
- 'adf_mode' => {
- string => $d->get('ADF Mode'),
- values => {
- 'Simplex' => $d->get('Simplex'),
- 'Duplex' => $d->get('Duplex'),
- },
- },
- 'adf-mode' => {
- string => $d->get('ADF Mode'),
- values => {
- 'Simplex' => $d->get('Simplex'),
- 'Duplex' => $d->get('Duplex'),
- },
- },
- );
- update_uimanager();
- # Look for crashed sessions
- if ( defined( $SETTING{TMPDIR} ) and $SETTING{TMPDIR} ne '' ) {
- $tmpdir = $SETTING{TMPDIR};
- }
- else {
- $tmpdir = File::Spec->tmpdir;
- }
- $logger->info("Checking $tmpdir for crashed sessions");
- my ( @sessions, @crashed, $selected ) =
- glob File::Spec->catfile( $tmpdir, 'gscan2pdf-????' );
- # Forget those used by running sessions
- for (@sessions) {
- if (
- open $lockfh,
- '>',
- File::Spec->catfile( $_, 'lockfile' ) and flock( $lockfh, LOCK_EX | LOCK_NB )
- )
- {
- push @crashed, $_;
- flock( $lockfh, LOCK_UN );
- close $lockfh;
- }
- }
- # Allow user to pick a crashed session to restore
- if (@crashed) {
- my $dialog = Gtk2::Dialog->new(
- $d->get('Pick crashed session to restore'),
- $window, 'modal',
- 'gtk-ok' => 'ok',
- 'gtk-cancel' => 'cancel'
- );
- my $label = Gtk2::Label->new( $d->get('Pick crashed session to restore') );
- $dialog->vbox->add($label);
- my $sessionlist = Gtk2::Ex::Simple::List->new( $d->get('Session') => 'text', );
- push @{ $sessionlist->{data} }, @crashed;
- $dialog->vbox->add($sessionlist);
- $dialog->show_all;
- ($selected) = $sessionlist->get_selected_indices if ( $dialog->run eq 'ok' );
- $dialog->destroy;
- if ( defined $selected ) {
- $session = $crashed[$selected];
- open $lockfh, '>', ## no critic (RequireBriefOpen)
- File::Spec->catfile( $session, 'lockfile' )
- or die "Cannot open lockfile\n";
- flock( $lockfh, LOCK_EX ) or die "Cannot lock file\n";
- open_session();
- }
- }
- # Create temporary directory if necessary
- unless ( defined $session ) {
- if ( defined( $SETTING{TMPDIR} ) and $SETTING{TMPDIR} ne '' ) {
- mkdir( $SETTING{TMPDIR} ) if ( not -d $SETTING{TMPDIR} );
- try {
- $session = File::Temp->newdir( 'gscan2pdf-XXXX', DIR => $SETTING{TMPDIR} );
- }
- catch {
- $session = File::Temp->newdir( 'gscan2pdf-XXXX', TMPDIR => 1 );
- $slist->set_dir($session);
- $logger->warn(
- sprintf(
- $d->get(
- "Warning: unable to use %s for temporary storage. Defaulting to %s instead."
- ),
- $SETTING{TMPDIR},
- dirname($session)
- )
- );
- };
- }
- else {
- $session = File::Temp->newdir( 'gscan2pdf-XXXX', TMPDIR => 1 );
- $slist->set_dir($session);
- }
- open $lockfh, '>', ## no critic (RequireBriefOpen)
- File::Spec->catfile( $session, 'lockfile' )
- or die "Cannot open lockfile\n";
- flock( $lockfh, LOCK_EX ) or die "Cannot lock file\n";
- $slist->save_session($session);
- $logger->info("Using $session for temporary files");
- }
- $window->show_all;
- # Progress bars below window
- my $phbox = Gtk2::HBox->new;
- $main_vbox->pack_end( $phbox, FALSE, FALSE, 0 );
- $phbox->show;
- my $shbox = Gtk2::HBox->new;
- $phbox->add($shbox);
- my $spbar = Gtk2::ProgressBar->new;
- $shbox->add($spbar);
- my $scbutton = Gtk2::Button->new;
- $scbutton->set_image( Gtk2::Image->new_from_stock( 'gtk-cancel', 'button' ) );
- $shbox->pack_end( $scbutton, FALSE, FALSE, 0 );
- my $thbox = Gtk2::HBox->new;
- $phbox->add($thbox);
- my $tpbar = Gtk2::ProgressBar->new;
- $thbox->add($tpbar);
- my $tcbutton = Gtk2::Button->new;
- $tcbutton->set_image( Gtk2::Image->new_from_stock( 'gtk-cancel', 'button' ) );
- $thbox->pack_end( $tcbutton, FALSE, FALSE, 0 );
- ### Subroutines
- # Create the menu bar, initialize its menus, and return the menu bar.
- sub create_menu_bar {
- # 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 );
- $actions_basic->add_radio_actions( \@image_tools, 10, \&change_image_tool_cb );
- # Add the actiongroup to the uimanager
- $uimanager->insert_action_group( $actions_basic, 0 );
- # add the basic XML description of the GUI
- $uimanager->add_ui_from_string($ui);
- # extract the menubar
- $menubar = $uimanager->get_widget('/MenuBar');
- $SETTING{user_defined_tools} = [ $SETTING{user_defined_tools} ]
- if ( ref( $SETTING{user_defined_tools} ) ne 'ARRAY' );
- for ( @{ $SETTING{user_defined_tools} } ) {
- my %tool = ( cmd => $_ );
- push @user_defined_tools, \%tool;
- add_user_defined_tool_menu( \%tool );
- }
- # Check for presence of various packages
- $dependencies{perlmagick} = try { require Image::Magick };
- $dependencies{imagemagick} = check_command('convert');
- $dependencies{scanadf} = check_command('scanadf');
- $dependencies{xdg} = check_command('xdg-email');
- $dependencies{gocr} = check_command('gocr');
- $dependencies{tesseract} = Gscan2pdf::Tesseract->setup($logger);
- $dependencies{ocropus} = Gscan2pdf::Ocropus->setup($logger);
- $dependencies{cuneiform} = Gscan2pdf::Cuneiform->setup($logger);
- $dependencies{djvu} = check_command('cjb2');
- $dependencies{unpaper} = Gscan2pdf::Unpaper->version;
- $dependencies{libtiff} = check_command('tiffcp');
- $logger->info("Found Image::Magick") if ( $dependencies{perlmagick} );
- $logger->info("Found ImageMagick") if ( $dependencies{imagemagick} );
- $logger->info("Found scanadf") if ( $dependencies{scanadf} );
- $logger->info("Found xdg-email") if ( $dependencies{xdg} );
- $logger->info("Found gocr") if ( $dependencies{gocr} );
- $logger->info("Found tesseract") if ( $dependencies{tesseract} );
- $logger->info("Found cuneiform v$dependencies{cuneiform}")
- if ( $dependencies{cuneiform} );
- $logger->info("Found cjb2 (djvu)") if ( $dependencies{djvu} );
- $logger->info("Found libtiff") if ( $dependencies{libtiff} );
- $logger->info("Found unpaper $dependencies{unpaper}")
- if ( $dependencies{unpaper} );
- # OCR engine options
- push @ocr_engine,
- [ 'gocr', $d->get('GOCR'), $d->get('Process image with GOCR.') ]
- if ( $dependencies{gocr} );
- push @ocr_engine,
- [
- 'tesseract', $d->get('Tesseract'),
- $d->get('Process image with Tesseract.')
- ],
- if ( $dependencies{tesseract} );
- push @ocr_engine,
- [ 'ocropus', $d->get('Ocropus'), $d->get('Process image with Ocropus.') ],
- if ( $dependencies{ocropus} );
- push @ocr_engine,
- [
- 'cuneiform', $d->get('Cuneiform'),
- $d->get('Process image with Cuneiform.')
- ],
- if ( $dependencies{cuneiform} );
- # Ghost save image item if imagemagick not available
- my $msg = '';
- $msg .= $d->get("Save image and Save as PDF both require imagemagick\n")
- unless ( $dependencies{imagemagick} );
- # Ghost save image item if libtiff not available
- $msg .= $d->get("Save image requires libtiff\n")
- unless ( $dependencies{libtiff} );
- # Ghost djvu item if cjb2 not available
- $msg .= $d->get("Save as DjVu requires djvulibre-bin\n")
- unless ( $dependencies{djvu} );
- # Ghost email item if xdg-email not available
- $msg .= $d->get("Email as PDF requires xdg-email\n")
- unless ( $dependencies{xdg} );
- # 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')->set_sensitive(FALSE);
- $uimanager->get_widget('/MenuBar/File/Email as PDF')->set_sensitive(FALSE);
- $uimanager->get_widget('/MenuBar/File/Print')->set_sensitive(FALSE);
- $uimanager->get_widget('/ToolBar/Save')->set_sensitive(FALSE);
- $uimanager->get_widget('/ToolBar/Email as PDF')->set_sensitive(FALSE);
- $uimanager->get_widget('/ToolBar/Print')->set_sensitive(FALSE);
- $uimanager->get_widget('/Thumb_Popup/Save')->set_sensitive(FALSE);
- $uimanager->get_widget('/Thumb_Popup/Email as PDF')->set_sensitive(FALSE);
- $uimanager->get_widget('/Thumb_Popup/Print')->set_sensitive(FALSE);
- $uimanager->get_widget('/MenuBar/Tools/Threshold')->set_sensitive(FALSE);
- $uimanager->get_widget('/MenuBar/Tools/Negate')->set_sensitive(FALSE);
- $uimanager->get_widget('/MenuBar/Tools/Unsharp')->set_sensitive(FALSE);
- $uimanager->get_widget('/MenuBar/Tools/Crop')->set_sensitive(FALSE);
- $uimanager->get_widget('/MenuBar/Tools/User-defined')->set_sensitive(FALSE);
- # a convenient place to put these
- $dependencies{pages} = -1;
- # Ghost rotations and unpaper if perlmagick not available
- unless ( $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 and unpaper support require perlmagick\n");
- }
- # Ghost unpaper item if unpaper not available
- unless ( $dependencies{unpaper} ) {
- $uimanager->get_widget('/MenuBar/Tools/unpaper')->set_sensitive(FALSE);
- $msg .= $d->get("unpaper missing\n");
- }
- # Ghost ocr item if gocr and tesseract not available
- if ( not $dependencies{gocr}
- and not $dependencies{tesseract}
- and not $dependencies{ocropus}
- and not $dependencies{cuneiform} )
- {
- $uimanager->get_widget('/MenuBar/Tools/OCR')->set_sensitive(FALSE);
- $msg .= $d->get("OCR requires gocr, tesseract, ocropus, or cuneiform\n");
- }
- # Put up warning if needed
- if ( $SETTING{'startup warning'} and $msg ne '' ) {
- my $dialog = Gtk2::Dialog->new( $d->get('Warning: missing packages'),
- $window, 'modal', '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
- $toolbar = $uimanager->get_widget('/ToolBar');
- # turn off labels
- my $settings = $toolbar->get_settings();
- $settings->set( 'gtk-toolbar-style', 'icons' ); # only icons
- return;
- }
- # ghost or unghost as necessary as # pages > 0 or not.
- sub update_uimanager {
- my @widgets = (
- '/MenuBar/View/DraggerTool',
- '/MenuBar/View/SelectorTool',
- '/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/Threshold',
- '/MenuBar/Tools/Negate',
- '/MenuBar/Tools/Unsharp',
- '/MenuBar/Tools/Crop',
- '/MenuBar/Tools/unpaper',
- '/MenuBar/Tools/OCR',
- '/MenuBar/Tools/User-defined',
- '/ToolBar/DraggerTool',
- '/ToolBar/SelectorTool',
- '/ToolBar/Zoom 100',
- '/ToolBar/Zoom to fit',
- '/ToolBar/Zoom in',
- '/ToolBar/Zoom out',
- '/ToolBar/Rotate 90',
- '/ToolBar/Rotate 180',
- '/ToolBar/Rotate 270',
- '/Detail_Popup/DraggerTool',
- '/Detail_Popup/SelectorTool',
- '/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{xdg} ) {
- $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);
- }
- if ( $dependencies{imagemagick} and $dependencies{libtiff} ) {
- $uimanager->get_widget('/MenuBar/File/Save')->set_sensitive(TRUE);
- $uimanager->get_widget('/ToolBar/Save')->set_sensitive(TRUE);
- $uimanager->get_widget('/Thumb_Popup/Save')->set_sensitive(TRUE);
- }
- $uimanager->get_widget('/MenuBar/File/Print')->set_sensitive(TRUE);
- $uimanager->get_widget('/ToolBar/Print')->set_sensitive(TRUE);
- $uimanager->get_widget('/Thumb_Popup/Print')->set_sensitive(TRUE);
- $dependencies{pages} = $#{ $slist->{data} };
- }
- }
- else {
- if ( $dependencies{pages} > -1 ) {
- if ( $dependencies{xdg} ) {
- $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;
- }
- if ( $dependencies{imagemagick} and $dependencies{libtiff} ) {
- $uimanager->get_widget('/MenuBar/File/Save')->set_sensitive(FALSE);
- $uimanager->get_widget('/ToolBar/Save')->set_sensitive(FALSE);
- $uimanager->get_widget('/Thumb_Popup/Save')->set_sensitive(FALSE);
- }
- $uimanager->get_widget('/MenuBar/File/Print')->set_sensitive(FALSE);
- $uimanager->get_widget('/ToolBar/Print')->set_sensitive(FALSE);
- $uimanager->get_widget('/Thumb_Popup/Print')->set_sensitive(FALSE);
- $windowi->hide if defined $windowi;
- $dependencies{pages} = $#{ $slist->{data} };
- }
- }
- # If the scan dialog has already been drawn, update the start page spinbutton
- update_start();
- return;
- }
- sub display_image {
- my ($page) = @_;
- $current_page = $page;
- # quotes required to prevent File::Temp object being clobbered
- my $pixbuf = Gtk2::Gdk::Pixbuf->new_from_file("$current_page->{filename}");
- $view->set_pixbuf($pixbuf);
- # Get image dimensions to constrain selector spinbuttons on crop dialog
- ( $current_page->{w}, $current_page->{h} ) =
- ( $pixbuf->get_width, $pixbuf->get_height );
- # Update the ranges on the crop dialog
- if ( defined($sb_selector_w) and defined($current_page) ) {
- $sb_selector_w->set_range( 0,
- $current_page->{w} - $sb_selector_x->get_value );
- $sb_selector_h->set_range( 0,
- $current_page->{h} - $sb_selector_y->get_value );
- $sb_selector_x->set_range( 0,
- $current_page->{w} - $sb_selector_w->get_value );
- $sb_selector_y->set_range( 0,
- $current_page->{h} - $sb_selector_h->get_value );
- $SETTING{selection}[0] = $sb_selector_x->get_value;
- $SETTING{selection}[1] = $sb_selector_y->get_value;
- $SETTING{selection}[2] = $sb_selector_w->get_value;
- $SETTING{selection}[3] = $sb_selector_h->get_value;
- $selector->set_selection(
- Gtk2::Gdk::Rectangle->new( @{ $SETTING{selection} } ) );
- }
- # Convert hocr output to Goo:Canvas if defined
- create_canvas($current_page) if ( defined $current_page->{hocr} );
- return;
- }
- sub create_canvas {
- my ($page) = @_;
- $current_page = $page;
- # Set up the canvas
- $canvas = Goo::Canvas->new;
- my $root = $canvas->get_root_item;
- unless ( defined $page->{w} ) {
- # quotes required to prevent File::Temp object being clobbered
- my $pixbuf = Gtk2::Gdk::Pixbuf->new_from_file("$page->{filename}");
- $page->{w} = $pixbuf->get_width;
- $page->{h} = $pixbuf->get_height;
- }
- $canvas->set_bounds( 0, 0, $page->{w}, $page->{h} );
- # Attach the text to the canvas
- for my $box ( $page->boxes ) {
- my ( $x1, $y1, $x2, $y2, $text ) = @$box;
- boxed_text(
- text => $text,
- x1 => $x1,
- y1 => $y1,
- x2 => $x2,
- y2 => $y2,
- size => abs( $y2 - $y1 )
- );
- }
- # Remove the existing contents
- $scwin_buffer->remove($_) for ( $scwin_buffer->get_children );
- $scwin_buffer->add($canvas);
- $canvas->set_scale( $view->get_zoom );
- $canvas->show;
- return;
- }
- # Deletes all scans after warning.
- sub new {
- # Check that all pages have been saved
- for ( my $i = 0 ; $i < @{ $slist->{data} } ; $i++ ) {
- if ( not $slist->{data}[$i][2]{saved} ) {
- my $response = show_message_dialog(
- $window,
- 'question',
- 'ok-cancel',
- $d->get(
- "Some pages have not been saved.\nDo you really want to clear all pages?")
- );
- if ( $response ne 'ok' ) {
- return FALSE;
- }
- else {
- last;
- }
- }
- }
- # Update undo/redo buffers
- take_snapshot();
- # Deselect everything to prevent error removing selected thumbs
- $slist->get_selection->unselect_all;
- # Depopulate the thumbnail list
- @{ $slist->{data} } = ();
- # Reset start page in scan dialog
- reset_start();
- update_uimanager();
- return;
- }
- # Create a file filter to show only supported file types in FileChooser dialog
- sub add_filter {
- my ( $file_chooser, $name, @file_extensions ) = @_;
- my $filter = Gtk2::FileFilter->new;
- for my $extension (@file_extensions) {
- my @filter_pattern;
- # Create case insensitive pattern
- for my $byte ( split //, $extension ) {
- push( @filter_pattern, '[' . uc($byte) . lc($byte) . ']' );
- }
- my $new_filter_pattern = join( '', @filter_pattern );
- $filter->add_pattern( '*.' . $new_filter_pattern );
- }
- my $types;
- for (@file_extensions) {
- if ( defined $types ) {
- $types .= ", *.$_";
- }
- else {
- $types = "*.$_";
- }
- }
- $filter->set_name("$name ($types)");
- $file_chooser->add_filter($filter);
- $filter = Gtk2::FileFilter->new;
- $filter->add_pattern('*');
- $filter->set_name('All files');
- $file_chooser->add_filter($filter);
- return;
- }
- sub open_session {
- my ($filename) = @_;
- $logger->info("Restoring session in $session");
- $slist->open_session( $session, $filename );
- update_uimanager();
- return;
- }
- # Helper function to set up thread progress bar
- sub setup_tpbar {
- my ( $thread, $process, $completed, $total, $pid ) = @_;
- if ( $total and defined($process) ) {
- $tpbar->set_text(
- sprintf(
- $d->get("Process %i of %i (%s)"), $completed + 1, $total, $process
- )
- );
- $tpbar->set_fraction( ( $completed + 0.5 ) / $total );
- $thbox->show_all;
- # Pass the signal back to:
- # 1. be able to cancel it when the process has finished
- # 2. flag that the progress bar has been set up
- # and avoid the race condition where the callback is entered before the $completed and $total variables have caught up
- return $tcbutton->signal_connect(
- clicked => sub {
- $slist->cancel($pid);
- $thbox->hide;
- }
- );
- }
- return;
- }
- # Helper function to update thread progress bar
- sub update_tpbar {
- my (%options) = @_;
- if ( $options{total} ) {
- if ( defined $options{process_name} ) {
- $options{process_name} .= " - $options{message}"
- if ( defined $options{message} );
- $tpbar->set_text(
- sprintf(
- $d->get("Process %i of %i (%s)"),
- $options{jobs_completed} + 1, $options{jobs_total},
- $options{process_name}
- )
- );
- }
- else {
- $tpbar->set_text(
- sprintf(
- $d->get("Process %i of %i"),
- $options{jobs_completed} + 1,
- $options{jobs_total}
- )
- );
- }
- if ( defined $options{progress} ) {
- $tpbar->set_fraction(
- ( $options{jobs_completed} + $options{progress} ) / $options{jobs_total} );
- }
- else {
- $tpbar->set_fraction(
- ( $options{jobs_completed} + 0.5 ) / $options{jobs_total} );
- }
- $thbox->show_all;
- return TRUE;
- }
- return;
- }
- # Throw up file selector and open selected file
- sub open_dialog {
- # cd back to cwd to get filename
- chdir $SETTING{'cwd'};
- my $file_chooser = Gtk2::FileChooserDialog->new(
- $d->get('Open image'),
- $window, 'open',
- 'gtk-cancel' => 'cancel',
- 'gtk-ok' => 'ok'
- );
- $file_chooser->set_select_multiple(TRUE);
- $file_chooser->set_default_response('ok');
- $file_chooser->set_current_folder( $SETTING{'cwd'} );
- add_filter( $file_chooser, $d->get('Image files'),
- 'jpg', 'png', 'pnm', 'gif', 'tif', 'tiff', 'pdf', 'djvu', 'ps', 'gs2p' );
- if ( 'ok' eq $file_chooser->run ) {
- # cd back to tempdir to import
- chdir $session;
- # Update undo/redo buffers
- take_snapshot();
- my @filename = $file_chooser->get_filenames;
- $file_chooser->destroy;
- # Update cwd
- $SETTING{'cwd'} = dirname( $filename[0] );
- for (@filename) {
- my ( $signal, $pid );
- $pid = $slist->get_file_info(
- path => $_,
- queued_callback => sub {
- return update_tpbar(@_);
- },
- started_callback => sub {
- my ( $thread, $process, $completed, $total ) = @_;
- $signal = setup_tpbar( $thread, $process, $completed, $total, $pid );
- return TRUE if ( defined $signal );
- },
- finished_callback => sub {
- my ( $info, $pending ) = @_;
- if ( $info->{format} eq 'session file' ) {
- open_session( $info->{path} );
- }
- elsif ( $info->{pages} > 1 ) {
- my $windowq = Gscan2pdf::Dialog->new(
- 'transient-for' => $window,
- title => $d->get('Pages to extract'),
- border_width => $border_width
- );
- my $vbox = $windowq->get('vbox');
- my $hbox = Gtk2::HBox->new;
- $vbox->pack_start( $hbox, TRUE, TRUE, 0 );
- my $label = Gtk2::Label->new( $d->get('First page to extract') );
- $hbox->pack_start( $label, FALSE, FALSE, 0 );
- my $spinbuttonf =
- Gtk2::SpinButton->new_with_range( 1, $info->{pages}, 1 );
- $hbox->pack_end( $spinbuttonf, FALSE, FALSE, 0 );
- $hbox = Gtk2::HBox->new;
- $vbox->pack_start( $hbox, TRUE, TRUE, 0 );
- $label = Gtk2::Label->new( $d->get('Last page to extract') );
- $hbox->pack_start( $label, FALSE, FALSE, 0 );
- my $spinbuttonl =
- Gtk2::SpinButton->new_with_range( 1, $info->{pages}, 1 );
- $spinbuttonl->set_value( $info->{pages} );
- $hbox->pack_end( $spinbuttonl, FALSE, FALSE, 0 );
- # HBox for buttons
- my $hboxb = Gtk2::HBox->new;
- $vbox->pack_start( $hboxb, FALSE, TRUE, 0 );
- # OK button
- my $obutton = Gtk2::Button->new_from_stock('gtk-ok');
- $hboxb->pack_start( $obutton, TRUE, TRUE, 0 );
- $obutton->signal_connect(
- clicked => sub {
- $windowq->destroy;
- import_file( $info, $spinbuttonf->get_value, $spinbuttonl->get_value );
- }
- );
- # Cancel button
- my $cbutton = Gtk2::Button->new_from_stock('gtk-cancel');
- $hboxb->pack_start( $cbutton, TRUE, TRUE, 0 );
- $cbutton->signal_connect(
- clicked => sub {
- $windowq->destroy;
- return;
- }
- );
- $windowq->show_all;
- }
- else {
- import_file( $info, 1, 1 );
- }
- },
- error_callback => sub {
- my ($message) = @_;
- show_message_dialog( $window, 'error', 'close', $message );
- }
- );
- }
- }
- else {
- $file_chooser->destroy;
- }
- # cd back to tempdir
- chdir $session;
- return;
- }
- # Wrapper for $slist->import_file
- sub import_file {
- my ( $info, $first_page, $last_page ) = @_;
- my ( $signal, $pid );
- $pid = $slist->import_file(
- info => $info,
- first => $first_page,
- last => $last_page,
- queued_callback => sub {
- return update_tpbar(@_);
- },
- started_callback => sub {
- my ( $thread, $process, $completed, $total ) = @_;
- $signal = setup_tpbar( $thread, $process, $completed, $total, $pid );
- return TRUE if ( defined $signal );
- },
- running_callback => sub {
- return update_tpbar(@_);
- },
- finished_callback => sub {
- my ( $new_page, $pending ) = @_;
- $thbox->hide_all unless $pending;
- $tcbutton->signal_handler_disconnect($signal) if ( defined $signal );
- update_uimanager();
- },
- error_callback => sub {
- my ($message) = @_;
- show_message_dialog( $window, 'error', 'close', $message );
- }
- );
- return;
- }
- # Add a frame and radio buttons to $vbox,
- sub add_page_range {
- my ($vbox) = @_;
- my $frame = Gtk2::Frame->new( $d->get('Page Range') );
- $vbox->pack_start( $frame, FALSE, FALSE, 0 );
- my $pr = Gscan2pdf::PageRange->new;
- $pr->set_active( $SETTING{'Page range'} )
- if ( defined $SETTING{'Page range'} );
- $pr->signal_connect(
- changed => sub {
- $SETTING{'Page range'} = $pr->get_active;
- }
- );
- $frame->add($pr);
- push @prlist, $pr;
- return;
- }
- # 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]{filename};
- my $i = 1;
- while ( $i < @{ $slist->{data} } ) {
- $pagelist = $pagelist . " " . $slist->{data}[$i][2]{filename};
- ++$i;
- }
- }
- elsif ( $SETTING{'Page range'} eq 'selected' ) {
- my @page = $slist->get_selected_indices;
- $n = $#page;
- $pagelist = $slist->{data}[ $page[0] ][2]{filename};
- my $i = 1;
- while ( $i < @page ) {
- $pagelist = $pagelist . " " . $slist->{data}[ $page[$i] ][2]{filename};
- ++$i;
- }
- }
- return ( $pagelist, $n );
- }
- # Add PDF options to $vbox
- sub add_pdf_metadata {
- my ($vbox) = @_;
- # Frame for metadata
- my $frame = Gtk2::Frame->new( $d->get('Metadata') );
- $vbox->pack_start( $frame, TRUE, TRUE, 0 );
- my $vboxm = Gtk2::VBox->new;
- $vboxm->set_border_width($border_width);
- $frame->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 );
- my ( $day, $month, $year ) =
- ( localtime( time + $SETTING{'date offset'} * 24 * 60 * 60 ) )[ 3, 4, 5 ];
- $year += 1900;
- $month += 1;
- my $button =
- Gtk2::Button->new( sprintf( "%04d-%02d-%02d", $year, $month, $day ) );
- $button->signal_connect(
- clicked => sub {
- my $window_date = Gscan2pdf::Dialog->new(
- 'transient-for' => $windowi,
- title => $d->get('Select Date'),
- border_width => $border_width
- );
- my $vbox_date = $window_date->get('vbox');
- $window_date->set_resizable(FALSE);
- my $calendar = Gtk2::Calendar->new;
- $calendar->select_day($day);
- $calendar->select_month( $month - 1, $year );
- $calendar->signal_connect(
- day_selected_double_click => sub {
- ( $year, $month, $day ) = $calendar->get_date;
- $month += 1;
- $button->set_label( sprintf( "%04d-%02d-%02d", $year, $month, $day ) );
- use Time::Local;
- $SETTING{'date offset'} = int(
- ( timelocal( 0, 0, 0, $day, $month - 1, $year ) - time ) / 60 / 60 / 24 );
- $window_date->destroy;
- }
- );
- $vbox_date->pack_start( $calendar, TRUE, TRUE, 0 );
- my $today = Gtk2::Button->new( $d->get('Today') );
- $today->signal_connect(
- clicked => sub {
- ( $day, $month, $year ) = ( localtime() )[ 3, 4, 5 ];
- $year += 1900;
- $calendar->select_day($day);
- $calendar->select_month( $month, $year );
- }
- );
- $vbox_date->pack_start( $today, TRUE, TRUE, 0 );
- $window_date->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;
- return;
- }
- sub get_pdf_metadata {
- my %h;
- $h{Author} = $SETTING{author} if defined $SETTING{author};
- my ( $day, $month, $year ) =
- ( localtime( time + $SETTING{'date offset'} * 24 * 60 * 60 ) )[ 3, 4, 5 ];
- $year += 1900;
- $month += 1;
- $h{CreationDate} =
- sprintf( "D:%4i%02i%02i000000+00'00'", $year, $month, $day );
- $h{ModDate} = sprintf( "D:%4i%02i%02i000000+00'00'", $year, $month, $day );
- $h{Creator} = "$prog_name 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;
- }
- # Draw text on the canvas with a box around it
- sub boxed_text {
- my (%options) = @_;
- my $root = $canvas->get_root_item;
- my $g = Goo::Canvas::Group->new($root);
- $g->translate( $options{x1}, $options{y1} );
- # draw the rect first to make sure the text goes on top and receives any mouse clicks
- my $rect = Goo::Canvas::Rect->new(
- $g, 0, 0,
- $options{x2} - $options{x1},
- $options{y2} - $options{y1}
- );
- my $text =
- Goo::Canvas::Text->new( $g, $options{text}, 0, 0,
- $options{x2} - $options{x1},
- 'nw', 'height' => $options{size}, );
- # clicking text box produces a dialog to edit the text
- $text->signal_connect(
- 'button-press-event' => sub {
- my ( $widget, $target, $ev ) = @_;
- my $dialog = Gtk2::Dialog->new(
- $d->get('Editing text') . "...", $window,
- 'modal',
- 'gtk-ok' => 'ok',
- 'gtk-cancel' => 'cancel'
- );
- my $textview = Gtk2::TextView->new;
- my $textbuffer = $textview->get_buffer;
- $textbuffer->set( text => $widget->get('text') );
- $dialog->vbox->add($textview);
- $dialog->set_default_response('ok');
- $dialog->show_all;
- if ( $dialog->run eq 'ok' ) {
- $widget->set( text => $textbuffer->get('text') );
- canvas2hocr();
- }
- $dialog->destroy;
- $canvas->pointer_ungrab( $widget, $ev->time );
- return TRUE;
- }
- );
- # $rect->signal_connect(
- # 'button-press-event' => sub {
- # my ( $widget, $target, $ev ) = @_;
- # print "rect button-press-event\n";
- # # return TRUE;
- # }
- # );
- # $g->signal_connect(
- # 'button-press-event' => sub {
- # my ( $widget, $target, $ev ) = @_;
- # print "group $widget button-press-event\n";
- # my $n = $widget->get_n_children;
- # for ( my $i = 0 ; $i < $n ; $i++ ) {
- # my $item = $widget->get_child($i);
- # if ( $item->isa('Goo::Canvas::Text') ) {
- # print "contains $item\n", $item->get('text'), "\n";
- # last;
- # }
- # }
- # # return TRUE;
- # }
- # );
- return;
- }
- # Convert the canvas into hocr
- sub canvas2hocr {
- my ( $x, $y, $w, $h ) = $canvas->get_bounds;
- $current_page->{hocr} = <<"EOS";
- <!DOCTYPE html
- PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN
- http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
- <html xmlns="http://www.w3.org/1999/xhtml">
- <head>
- <meta content="ocr_line ocr_page" name="ocr-capabilities"/>
- <meta content="en" name="ocr-langs"/>
- <meta content="Latn" name="ocr-scripts"/>
- <meta content="" name="ocr-microformats"/>
- <title>OCR Output</title>
- </head>
- <body>
- <div class="ocr_page" title="bbox $x $y $w $h>
- <p class="ocr_par">
- EOS
- my $root = $canvas->get_root_item;
- for my $i ( 0 .. $root->get_n_children - 1 ) {
- my $group = $root->get_child($i);
- my $bounds = $group->get_bounds;
- my ( $x1, $y1, $x2, $y2 ) =
- ( $bounds->x1 + 1, $bounds->y1 + 1, $bounds->x2 - 1, $bounds->y2 - 1 );
- # my $matrix = $group->get_transform; FIXME: use this as soon as Goo::Canvas 0.07 is in generally available
- # my ( $x1, $y1 ) = ( $matrix->x0, $matrix->y0 );
- # my ( $x2, $y2, $text );
- my ($text);
- for my $i ( 0 .. $group->get_n_children - 1 ) {
- my $item = $group->get_child($i);
- if ( $item->isa('Goo::Canvas::Rect') ) {
- # ( $x2, $y2 ) = ( $x1 + $item->get('width'), $y1 + $item->get('height') );
- }
- elsif ( $item->isa('Goo::Canvas::Text') ) {
- $text = $item->get('text');
- }
- $current_page->{hocr} .=
- " <span class=\"ocr_line\" title=\"bbox $x1 $y1 $x2 $y2\">$text</span>\n"
- if ( defined($x2) and defined($text) );
- }
- }
- $current_page->{hocr} .= "</p></div></body></html>";
- return;
- }
- # 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'),
- $windowi, 'save',
- 'gtk-cancel' => 'cancel',
- 'gtk-save' => 'ok'
- );
- my ( $dday, $dmonth, $dyear ) =
- ( localtime( time + $SETTING{'date offset'} * 24 * 60 * 60 ) )[ 3, 4, 5 ];
- $dyear += 1900;
- $dmonth = sprintf( "%02d", $dmonth + 1 );
- $dday = sprintf( "%02d", $dday );
- my ( $tday, $tmonth, $tyear ) = ( localtime(time) )[ 3, 4, 5 ];
- $tyear += 1900;
- $tmonth = sprintf( "%02d", $tmonth + 1 );
- $tday = sprintf( "%02d", $tday );
- my $filename = $SETTING{'default filename'};
- $filename =~ s/%a/$SETTING{author}/g;
- $filename =~ s/%t/$SETTING{title}/g;
- $filename =~ s/%y/$dyear/g;
- $filename =~ s/%Y/$tyear/g;
- $filename =~ s/%m/$dmonth/g;
- $filename =~ s/%M/$tmonth/g;
- $filename =~ s/%d/$dday/g;
- $filename =~ s/%D/$tday/g;
- $file_chooser->set_current_name($filename);
- $file_chooser->set_default_response('ok');
- $file_chooser->set_current_folder( $SETTING{'cwd'} );
- add_filter( $file_chooser, $d->get('PDF files'), 'pdf' );
- $file_chooser->set_do_overwrite_confirmation(TRUE);
- $file_chooser->signal_connect(
- response => sub {
- my ( $dialog, $response ) = @_;
- $logger->debug("PDF save filename dialog returned $response");
- if ( $response eq 'ok' ) {
- $filename = $file_chooser->get_filename;
- if ( $filename !~ /\.pdf$/ix ) {
- $filename = $filename . '.pdf';
- if ( -f $filename ) {
- # File exists; get the file chooser to ask the user to confirm.
- $file_chooser->set_filename($filename);
- # Give the name change time to take effect.
- Glib::Idle->add( sub { $file_chooser->response('ok'); } );
- return;
- }
- }
- # Check that the file can be written
- if ( not -w dirname($filename) or ( -f $filename and not -w $filename ) ) {
- show_message_dialog( $file_chooser, 'error', 'close',
- sprintf( $d->get("File %s is read-only"), $filename ) );
- return;
- }
- # Update cwd
- $SETTING{'cwd'} = dirname($filename);
- # Compile list of pages
- my @list_of_pages;
- my @pagelist = $slist->get_page_index( $SETTING{'Page range'} );
- for (@pagelist) {
- push @list_of_pages, $slist->{data}[$_][2];
- }
- # Compile options
- my %options = (
- compression => $SETTING{'pdf compression'},
- downsample => $SETTING{downsample},
- 'downsample dpi' => $SETTING{'downsample dpi'},
- quality => $SETTING{quality},
- font => $SETTING{'pdf font'},
- );
- # Create the PDF
- $logger->debug("Started saving $filename");
- my ( $signal, $pid );
- $pid = $slist->save_pdf(
- path => $filename,
- list_of_pages => \@list_of_pages,
- metadata => get_pdf_metadata(),
- options => \%options,
- queued_callback => sub {
- return update_tpbar(@_);
- },
- started_callback => sub {
- my ( $thread, $process, $completed, $total ) = @_;
- $signal = setup_tpbar( $thread, $process, $completed, $total, $pid );
- return TRUE if ( defined $signal );
- },
- running_callback => sub {
- return update_tpbar(@_);
- },
- finished_callback => sub {
- my ( $new_page, $pending ) = @_;
- $thbox->hide_all unless $pending;
- $tcbutton->signal_handler_disconnect($signal) if ( defined $signal );
- mark_pages(@pagelist);
- system("xdg-open \"$filename\" &")
- if ( defined( $SETTING{'view files toggle'} )
- and $SETTING{'view files toggle'} );
- $logger->debug("Finished saving $filename");
- },
- error_callback => sub {
- my ($message) = @_;
- show_message_dialog( $window, 'error', 'close', $message );
- }
- );
- $windowi->hide if defined $windowi;
- }
- $file_chooser->destroy;
- }
- );
- $file_chooser->show;
- # cd back to tempdir
- chdir $session;
- return;
- }
- # Set up quality spinbutton here so that it can be shown or hidden by callback
- sub add_quality_spinbutton {
- my ($vbox) = @_;
- my $hbox = Gtk2::HBox->new;
- $vbox->pack_start( $hbox, TRUE, TRUE, 0 );
- my $label = Gtk2::Label->new( $d->get('JPEG Quality') );
- $hbox->pack_start( $label, FALSE, FALSE, 0 );
- my $spinbutton = Gtk2::SpinButton->new_with_range( 1, 100, 1 );
- $spinbutton->set_value( $SETTING{'quality'} );
- $hbox->pack_end( $spinbutton, FALSE, FALSE, 0 );
- return ( $hbox, $spinbutton );
- }
- sub add_pdf_compression {
- my ($vbox) = @_;
- # Downsample options
- my $hboxd = Gtk2::HBox->new;
- $vbox->pack_start( $hboxd, FALSE, FALSE, 0 );
- my $button = Gtk2::CheckButton->new( $d->get('Downsample to') );
- $button->set_active(TRUE);
- $hboxd->pack_start( $button, FALSE, FALSE, 0 );
- my $spinbutton = Gtk2::SpinButton->new_with_range( 9, 2400, 1 );
- $spinbutton->set_value( $SETTING{'downsample dpi'} );
- my $label = Gtk2::Label->new( $d->get('PPI') );
- $hboxd->pack_end( $label, FALSE, FALSE, 0 );
- $hboxd->pack_end( $spinbutton, FALSE, FALSE, 0 );
- $button->signal_connect(
- toggled => sub {
- if ( $button->get_active ) {
- $spinbutton->set_sensitive(TRUE);
- }
- else {
- $spinbutton->set_sensitive(FALSE);
- }
- }
- );
- $button->set_active( $SETTING{'downsample'} );
- # Compression options
- my @compression = (
- [
- 'auto', $d->get('Automatic'),
- $d->get('Let gscan2pdf which type of compression to use.')
- ],
- [
- 'lzw', $d->get('LZW'),
- $d->get('Compress output with Lempel-Ziv & Welch encoding.')
- ],
- [ 'zip', $d->get('Zip'), $d->get('Compress output with deflate encoding.') ],
- [
- 'packbits', $d->get('Packbits'),
- $d->get('Compress output with Packbits encoding.')
- ],
- # g3 and 4 give an error message
- # [ '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.') ],
- [ 'png', $d->get('PNG'), $d->get('Compress output with PNG encoding.') ],
- [ 'jpg', $d->get('JPEG'), $d->get('Compress output with JPEG encoding.') ],
- [
- 'none', $d->get('None'),
- $d->get('Use no compression algorithm on output.')
- ],
- );
- # Compression ComboBox
- my $hbox = Gtk2::HBox->new;
- $vbox->pack_start( $hbox, TRUE, TRUE, 0 );
- $label = Gtk2::Label->new( $d->get('Compression') );
- $hbox->pack_start( $label, FALSE, FALSE, 0 );
- # Set up quality spinbutton here so that it can be shown or hidden by callback
- my ( $hboxq, $spinbuttonq ) = add_quality_spinbutton($vbox);
- my $combob = combobox_from_array(@compression);
- $combob->signal_connect(
- changed => sub {
- if ( $compression[ $combob->get_active ][0] eq 'jpg' ) {
- $hboxq->show_all;
- }
- else {
- $hboxq->hide_all;
- }
- }
- );
- combobox_set_active( $combob, $SETTING{'pdf compression'}, @compression );
- $hbox->pack_end( $combob, FALSE, FALSE, 0 );
- return ( $button, $spinbutton, $combob, $hboxq, $spinbuttonq, @compression );
- }
- # Display page selector and on save a fileselector.
- sub save_dialog {
- # $SETTING{'Page range'} = 'selected' if $SETTING{'RMB'};
- if ( defined $windowi ) {
- $windowi->present;
- return;
- }
- $windowi = Gscan2pdf::Dialog->new(
- 'transient-for' => $window,
- title => $d->get('Save'),
- 'hide-on-delete' => TRUE,
- border_width => $border_width
- );
- my $vbox = $windowi->get('vbox');
- # Frame for page range
- add_page_range($vbox);
- # Image type ComboBox
- my $hboxi = Gtk2::HBox->new;
- $vbox->pack_start( $hboxi, TRUE, TRUE, 0 );
- my $label = Gtk2::Label->new( $d->get('Image type') );
- $hboxi->pack_start( $label, FALSE, FALSE, 0 );
- my @type = (
- [ 'pdf', $d->get('PDF'), $d->get('Portable Document Format') ],
- [ 'gif', $d->get('GIF'), $d->get('CompuServe graphics interchange format') ],
- [
- 'jpg', $d->get('JPEG'),
- $d->get('Joint Photographic Experts Group JFIF format')
- ],
- [ 'png', $d->get('PNG'), $d->get('Portable Network Graphics') ],
- [ 'pnm', $d->get('PNM'), $d->get('Portable anymap') ],
- [ 'ps', $d->get('PS'), $d->get('Postscript') ],
- [ 'tif', $d->get('TIFF'), $d->get('Tagged Image File Format') ],
- [ 'txt', $d->get('Text'), $d->get('Plain text') ],
- [ 'session', $d->get('Session'), $d->get('gscan2pdf session file') ],
- );
- push @type, [ 'djvu', $d->get('DjVu'), $d->get('Deja Vu') ]
- if $dependencies{djvu};
- my @tiff_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 rather than jpg needed here because tiffcp uses -c jpeg
- [ '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, FALSE, FALSE, 0 );
- $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 ( $hboxtq, $spinbuttontq ) = add_quality_spinbutton($vbox);
- $label = Gtk2::Label->new( $d->get('tiff') );
- $hboxtq->pack_start( $label, FALSE, FALSE, 0 );
- # Fill compression ComboBox
- my $combobtc = combobox_from_array(@tiff_compression);
- $combobtc->signal_connect(
- changed => sub {
- if ( $tiff_compression[ $combobtc->get_active ][0] eq 'jpeg' ) {
- $hboxtq->show_all;
- }
- else {
- $hboxtq->hide_all;
- $windowi->resize( 100, 100 ); # Doesn't matter that 200x200 is too small
- }
- }
- );
- combobox_set_active( $combobtc, $SETTING{'tiff compression'},
- @tiff_compression );
- $hboxc->pack_end( $combobtc, FALSE, FALSE, 0 );
- # PDF options
- my $vboxp = Gtk2::VBox->new;
- $vbox->pack_start( $vboxp, FALSE, FALSE, 0 );
- my ( $entrya, $entryt, $entrys, $entryk ) = add_pdf_metadata($vboxp);
- # Compression options
- my ( $buttond, $spinbuttond, $combob, $hboxpq, $spinbuttonpq,
- @pdf_compression ) = add_pdf_compression($vboxp);
- # Font for non-ASCII text
- my $scwinf = Gtk2::ScrolledWindow->new;
- $scwinf->set_policy( 'automatic', 'automatic' );
- $vbox->pack_start( $scwinf, TRUE, TRUE, 0 );
- my $hboxf = Gtk2::HBox->new;
- $scwinf->add_with_viewport($hboxf);
- $scwinf->get_child->set_shadow_type('none');
- $label = Gtk2::Label->new( $d->get('Font for non-ASCII text') );
- $hboxf->pack_start( $label, FALSE, FALSE, 0 );
- my @fonts;
- my ($stdout) = Gscan2pdf::Document::open_three('fc-list : family style file');
- for ( split /\n/, $stdout ) {
- if (/ttf:\ /x) {
- my ( $file, $family, $style ) = split ':';
- chomp $style;
- $family =~ s/^\ //x;
- $style =~ s/^style=//x;
- $style =~ s/,.*$//x;
- my $font = "$family $style";
- push @fonts, [ $file, $font, $font ];
- $SETTING{'pdf font'} = $file
- if ( not defined( $SETTING{'pdf font'} )
- and $font eq 'Times New Roman Regular' );
- }
- }
- @fonts = sort { $a->[1] cmp $b->[1] } @fonts;
- my $combof = combobox_from_array(@fonts);
- combobox_set_active( $combof, $SETTING{'pdf font'}, @fonts );
- $hboxf->pack_start( $combof, FALSE, FALSE, 0 );
- # Fill image type ComboBox
- my $combobi = combobox_from_array(@type);
- $combobi->signal_connect(
- changed => sub {
- if ( $type[ $combobi->get_active ][0] eq 'pdf' ) {
- $vboxp->show_all;
- $hboxf->show_all;
- if ( $pdf_compression[ $combob->get_active ][0] eq 'jpg' ) {
- $hboxpq->show_all;
- }
- else {
- $hboxpq->hide_all;
- $windowi->resize( 100, 100 ); # Doesn't matter that 200x200 is too small
- }
- }
- else {
- $vboxp->hide_all;
- $hboxf->hide_all;
- $windowi->resize( 100, 100 ); # Doesn't matter that 200x200 is too small
- }
- if ($type[ $combobi->get_active ][0] eq 'tif'
- or $type[ $combobi->get_active ][0] eq 'ps' )
- {
- $hboxc->show_all;
- }
- else {
- $hboxc->hide_all;
- $windowi->resize( 100, 100 ); # Doesn't matter that 200x200 is too small
- }
- if (
- $type[ $combobi->get_active ][0] eq 'jpg'
- or ( $type[ $combobi->get_active ][0] eq 'tif'
- and $tiff_compression[ $combobtc->get_active ][0] eq 'jpeg' )
- )
- {
- $hboxtq->show_all;
- }
- else {
- $hboxtq->hide_all;
- $windowi->resize( 100, 100 ); # Doesn't matter that 200x200 is too small
- }
- }
- );
- combobox_set_active( $combobi, $SETTING{'image type'}, @type );
- $hboxi->pack_end( $combobi, 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 image type, compression and quality
- $SETTING{'image type'} = $type[ $combobi->get_active ][0];
- given ( $SETTING{'image type'} ) {
- when ('pdf') {
- # dig out the compression
- $SETTING{'downsample'} = $buttond->get_active;
- $SETTING{'downsample dpi'} = $spinbuttond->get_value;
- $SETTING{'pdf compression'} = $pdf_compression[ $combob->get_active ][0];
- $SETTING{'quality'} = $spinbuttonpq->get_value;
- $SETTING{'pdf font'} = $fonts[ $combof->get_active ][0];
- update_PDF_settings( $entrya, $entryt, $entrys, $entryk );
- save_PDF();
- }
- when ('djvu') {
- save_djvu();
- }
- when ('tif') {
- $SETTING{'tiff compression'} =
- $tiff_compression[ $combobtc->get_active ][0];
- $SETTING{'quality'} = $spinbuttontq->get_value;
- # cd back to cwd to save
- chdir $SETTING{'cwd'};
- # Set up file selector
- my $file_chooser = Gtk2::FileChooserDialog->new(
- $d->get('TIFF filename'),
- $windowi, 'save',
- 'gtk-cancel' => 'cancel',
- 'gtk-save' => 'ok'
- );
- $file_chooser->set_default_response('ok');
- $file_chooser->set_current_folder( $SETTING{'cwd'} );
- add_filter( $file_chooser, $d->get('Image files'),
- $SETTING{'image type'} );
- $file_chooser->set_do_overwrite_confirmation(TRUE);
- $file_chooser->signal_connect(
- response => sub {
- my ( $dialog, $response ) = @_;
- if ( $response eq 'ok' ) {
- my $filename = $file_chooser->get_filename;
- if ( $filename !~ /\.tif$/ix ) {
- $filename = $filename . '.tif';
- if ( -f $filename ) {
- # File exists; get the file chooser to ask the user to confirm.
- $file_chooser->set_filename($filename);
- # Give the name change time to take effect.
- Glib::Idle->add( sub { $file_chooser->response('ok'); } );
- return;
- }
- }
- # Check that the file can be written
- if ( not -w dirname($filename)
- or ( -f $filename and not -w $filename ) )
- {
- show_message_dialog( $file_chooser, 'error', 'close',
- sprintf( $d->get("File %s is read-only"), $filename ) );
- return;
- }
- # Update cwd
- $SETTING{'cwd'} = dirname($filename);
- $windowi->hide if defined $windowi;
- save_TIFF($filename);
- }
- $file_chooser->destroy;
- }
- );
- $file_chooser->show;
- # cd back to tempdir
- chdir $session;
- }
- when ('txt') {
- # cd back to cwd to save
- chdir $SETTING{'cwd'};
- # Set up file selector
- my $file_chooser = Gtk2::FileChooserDialog->new(
- $d->get('Text filename'),
- $windowi, 'save',
- 'gtk-cancel' => 'cancel',
- 'gtk-save' => 'ok'
- );
- $file_chooser->set_default_response('ok');
- $file_chooser->set_current_folder( $SETTING{'cwd'} );
- $file_chooser->set_do_overwrite_confirmation(TRUE);
- add_filter( $file_chooser, $d->get('Text files'), 'txt' );
- $file_chooser->signal_connect(
- response => sub {
- my ( $dialog, $response ) = @_;
- if ( $response eq 'ok' ) {
- my $filename = $file_chooser->get_filename;
- if ( $filename !~ /\.txt$/ix ) {
- $filename = $filename . '.txt';
- if ( -f $filename ) {
- # File exists; get the file chooser to ask the user to confirm.
- $file_chooser->set_filename($filename);
- # Give the name change time to take effect.
- Glib::Idle->add( sub { $file_chooser->response('ok'); } );
- return;
- }
- }
- # Check that the file can be written
- if ( not -w dirname($filename)
- or ( -f $filename and not -w $filename ) )
- {
- show_message_dialog( $file_chooser, 'error', 'close',
- sprintf( $d->get("File %s is read-only"), $filename ) );
- return;
- }
- # Update cwd
- $SETTING{'cwd'} = dirname($filename);
- # Compile list of pages
- my @list_of_pages;
- my @pagelist = $slist->get_page_index( $SETTING{'Page range'} );
- for (@pagelist) {
- push @list_of_pages, $slist->{data}[$_][2];
- }
- my ( $signal, $pid );
- $pid = $slist->save_text(
- path => $filename,
- list_of_pages => \@list_of_pages,
- queued_callback => sub {
- return update_tpbar(@_);
- },
- started_callback => sub {
- my ( $thread, $process, $completed, $total ) = @_;
- $signal = setup_tpbar( $thread, $process, $completed, $total, $pid );
- return TRUE if ( defined $signal );
- },
- running_callback => sub {
- return update_tpbar(@_);
- },
- finished_callback => sub {
- my ( $new_page, $pending ) = @_;
- $thbox->hide_all unless $pending;
- $tcbutton->signal_handler_disconnect($signal) if ( defined $signal );
- mark_pages(@pagelist);
- system("xdg-open \"$filename\" &")
- if ( defined( $SETTING{'view files toggle'} )
- and $SETTING{'view files toggle'} );
- $logger->debug("Finished saving $filename");
- },
- error_callback => sub {
- my ($message) = @_;
- show_message_dialog( $window, 'error', 'close', $message );
- }
- );
- $windowi->hide if defined $windowi;
- }
- $file_chooser->destroy;
- }
- );
- $file_chooser->show;
- # cd back to tempdir
- chdir $session;
- }
- when ('ps') {
- $SETTING{'tiff compression'} =
- $tiff_compression[ $combobtc->get_active ][0];
- $SETTING{'quality'} = $spinbuttontq->get_value;
- # cd back to cwd to save
- chdir $SETTING{'cwd'};
- # Set up file selector
- my $file_chooser = Gtk2::FileChooserDialog->new(
- $d->get('PS filename'),
- $windowi, 'save',
- 'gtk-cancel' => 'cancel',
- 'gtk-save' => 'ok'
- );
- $file_chooser->set_default_response('ok');
- $file_chooser->set_current_folder( $SETTING{'cwd'} );
- add_filter( $file_chooser, $d->get('Postscript files'), 'ps' );
- $file_chooser->set_do_overwrite_confirmation(TRUE);
- $file_chooser->signal_connect(
- response => sub {
- my ( $dialog, $response ) = @_;
- if ( $response eq 'ok' ) {
- my $filename = $file_chooser->get_filename;
- if ( $filename !~ /\.ps$/ix ) {
- $filename = $filename . '.ps';
- if ( -f $filename ) {
- # File exists; get the file chooser to ask the user to confirm.
- $file_chooser->set_filename($filename);
- # Give the name change time to take effect.
- Glib::Idle->add( sub { $file_chooser->response('ok'); } );
- return;
- }
- }
- # Check that the file can be written
- if ( not -w dirname($filename)
- or ( -f $filename and not -w $filename ) )
- {
- show_message_dialog( $file_chooser, 'error', 'close',
- sprintf( $d->get("File %s is read-only"), $filename ) );
- return;
- }
- # Update cwd
- $SETTING{'cwd'} = dirname($filename);
- # Create the PS
- my $tif = File::Temp->new( DIR => $session, SUFFIX => '.tif' );
- $windowi->hide if defined $windowi;
- save_TIFF( $tif->filename, $filename );
- }
- $file_chooser->destroy;
- }
- );
- $file_chooser->show;
- # cd back to tempdir
- chdir $session;
- }
- when ('session') {
- # cd back to cwd to save
- chdir $SETTING{'cwd'};
- # Set up file selector
- my $file_chooser = Gtk2::FileChooserDialog->new(
- $d->get('gscan2pdf session filename'),
- $windowi, 'save',
- 'gtk-cancel' => 'cancel',
- 'gtk-save' => 'ok'
- );
- $file_chooser->set_default_response('ok');
- $file_chooser->set_current_folder( $SETTING{'cwd'} );
- add_filter( $file_chooser, $d->get('gscan2pdf session files'), 'gs2p' );
- $file_chooser->set_do_overwrite_confirmation(TRUE);
- $file_chooser->signal_connect(
- response => sub {
- my ( $dialog, $response ) = @_;
- if ( $response eq 'ok' ) {
- my $filename = $file_chooser->get_filename;
- if ( $filename !~ /\.gs2p$/ix ) {
- $filename = $filename . '.gs2p';
- if ( -f $filename ) {
- # File exists; get the file chooser to ask the user to confirm.
- $file_chooser->set_filename($filename);
- # Give the name change time to take effect.
- Glib::Idle->add( sub { $file_chooser->response('ok'); } );
- return;
- }
- }
- # Check that the file can be written
- if ( not -w dirname($filename)
- or ( -f $filename and not -w $filename ) )
- {
- show_message_dialog( $file_chooser, 'error', 'close',
- sprintf( $d->get("File %s is read-only"), $filename ) );
- return;
- }
- # Update cwd
- $SETTING{'cwd'} = dirname($filename);
- $slist->save_session( $session, $filename );
- }
- $file_chooser->destroy;
- }
- );
- $file_chooser->show;
- # cd back to tempdir
- chdir $session;
- }
- default {
- save_image();
- }
- }
- }
- );
- # Cancel button
- my $cbutton = Gtk2::Button->new_from_stock('gtk-cancel');
- $hboxb->pack_end( $cbutton, FALSE, FALSE, 0 );
- $cbutton->signal_connect( clicked => sub { $windowi->hide; } );
- $windowi->show_all;
- $hboxpq->hide_all if ( $pdf_compression[ $combob->get_active ][0] ne 'jpg' );
- $hboxtq->hide_all
- if (
- $type[ $combobi->get_active ][0] ne 'jpg'
- or ( $type[ $combobi->get_active ][0] eq 'tif'
- and $tiff_compression[ $combobtc->get_active ][0] ne 'jpg' )
- );
- $hboxc->hide_all
- if ( $type[ $combobi->get_active ][0] ne 'tif'
- and $type[ $combobi->get_active ][0] ne 'ps' );
- if ( $type[ $combobi->get_active ][0] ne 'pdf' ) {
- $vboxp->hide_all;
- $hboxf->hide_all;
- }
- $windowi->resize( 100, 100 ); # Doesn't matter that 200x200 is too small
- return;
- }
- sub file_exists {
- my ( $file_chooser, $filename ) = @_;
- if ( not -w dirname($filename) or ( -f $filename and not -w $filename ) ) {
- show_message_dialog( $file_chooser, 'error', 'close',
- sprintf( $d->get("File %s is read-only"), $filename ) );
- return TRUE;
- }
- elsif ( -e $filename ) {
- my $response =
- show_message_dialog( $file_chooser, 'question', 'ok-cancel',
- sprintf( $d->get("File %s exists.\nReally overwrite?"), $filename ) );
- return TRUE if ( $response ne 'ok' );
- }
- return FALSE;
- }
- sub save_image {
- # cd back to cwd to save
- chdir $SETTING{'cwd'};
- # Set up file selector
- my $file_chooser = Gtk2::FileChooserDialog->new(
- $d->get('Image filename'),
- $windowi, 'save',
- 'gtk-cancel' => 'cancel',
- 'gtk-save' => 'ok'
- );
- $file_chooser->set_default_response('ok');
- $file_chooser->set_current_folder( $SETTING{'cwd'} );
- add_filter( $file_chooser, $d->get('Image files'),
- 'jpg', 'png', 'pnm', 'gif', 'tif', 'tiff', 'pdf', 'djvu', 'ps' );
- if ( 'ok' eq $file_chooser->run ) {
- my $filename = $file_chooser->get_filename;
- # Update cwd
- $SETTING{'cwd'} = dirname($filename);
- # cd back to tempdir
- chdir $session;
- # Compile list of pages
- my @list_of_pages;
- my @pagelist = $slist->get_page_index( $SETTING{'Page range'} );
- for (@pagelist) {
- push @list_of_pages, $slist->{data}[$_][2];
- }
- if ( @list_of_pages > 1 ) {
- my $w = length( scalar @list_of_pages );
- for ( my $i = 1 ; $i <= @list_of_pages ; $i++ ) {
- my $current_filename = sprintf "${filename}_%0${w}d.$SETTING{'image type'}",
- $i;
- return if ( file_exists( $file_chooser, $current_filename ) );
- }
- $filename = "${filename}_%0${w}d.$SETTING{'image type'}";
- }
- else {
- $filename = $filename . ".$SETTING{'image type'}"
- if ( $filename !~ /\.$SETTING{'image type'}$/ix );
- return if ( file_exists( $file_chooser, $filename ) );
- }
- # Create the image
- $logger->debug("Started saving $filename");
- my ( $signal, $pid );
- $pid = $slist->save_image(
- path => $filename,
- list_of_pages => \@list_of_pages,
- queued_callback => sub {
- return update_tpbar(@_);
- },
- started_callback => sub {
- my ( $thread, $process, $completed, $total ) = @_;
- $signal = setup_tpbar( $thread, $process, $completed, $total, $pid );
- return TRUE if ( defined $signal );
- },
- running_callback => sub {
- return update_tpbar(@_);
- },
- finished_callback => sub {
- my ( $new_page, $pending ) = @_;
- $thbox->hide_all unless $pending;
- $tcbutton->signal_handler_disconnect($signal) if ( defined $signal );
- mark_pages(@pagelist);
- system("xdg-open \"$filename\" &")
- if ( defined( $SETTING{'view files toggle'} )
- and $SETTING{'view files toggle'} );
- $logger->debug("Finished saving $filename");
- },
- error_callback => sub {
- my ($message) = @_;
- show_message_dialog( $window, 'error', 'close', $message );
- }
- );
- $windowi->hide if defined $windowi;
- }
- $file_chooser->destroy;
- return;
- }
- sub save_TIFF {
- my ( $filename, $ps ) = @_;
- # Compile list of pages
- my @list_of_pages;
- my @pagelist = $slist->get_page_index( $SETTING{'Page range'} );
- for (@pagelist) {
- push @list_of_pages, $slist->{data}[$_][2];
- }
- # Compile options
- my %options = (
- compression => $SETTING{'tiff compression'},
- quality => $SETTING{quality},
- );
- my ( $signal, $pid );
- $pid = $slist->save_tiff(
- path => $filename,
- list_of_pages => \@list_of_pages,
- options => \%options,
- ps => $ps,
- queued_callback => sub {
- return update_tpbar(@_);
- },
- started_callback => sub {
- my ( $thread, $process, $completed, $total ) = @_;
- $signal = setup_tpbar( $thread, $process, $completed, $total, $pid );
- return TRUE if ( defined $signal );
- },
- running_callback => sub {
- return update_tpbar(@_);
- },
- finished_callback => sub {
- my ( $new_page, $pending ) = @_;
- $thbox->hide_all unless $pending;
- $tcbutton->signal_handler_disconnect($signal) if ( defined $signal );
- mark_pages(@pagelist);
- my $file = defined($ps) ? $ps : $filename;
- system("xdg-open \"$filename\" &")
- if ( defined( $SETTING{'view files toggle'} )
- and $SETTING{'view files toggle'} );
- $logger->debug("Finished saving $filename");
- },
- error_callback => sub {
- my ($message) = @_;
- show_message_dialog( $window, 'error', 'close', $message );
- }
- );
- return;
- }
- 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'),
- $windowi, 'save',
- 'gtk-cancel' => 'cancel',
- 'gtk-save' => 'ok'
- );
- $file_chooser->set_default_response('ok');
- $file_chooser->set_current_folder( $SETTING{'cwd'} );
- add_filter( $file_chooser, $d->get('DjVu files'), 'djvu' );
- $file_chooser->set_do_overwrite_confirmation(TRUE);
- $file_chooser->signal_connect(
- response => sub {
- my ( $dialog, $response ) = @_;
- if ( $response eq 'ok' ) {
- my $filename = $file_chooser->get_filename;
- if ( $filename !~ /\.djvu$/ix ) {
- $filename = $filename . '.djvu';
- if ( -f $filename ) {
- # File exists; get the file chooser to ask the user to confirm.
- $file_chooser->set_filename($filename);
- # Give the name change time to take effect.
- Glib::Idle->add( sub { $file_chooser->response('ok'); } );
- return;
- }
- }
- # Check that the file can be written
- if ( not -w dirname($filename) or ( -f $filename and not -w $filename ) ) {
- show_message_dialog( $file_chooser, 'error', 'close',
- sprintf( $d->get("File %s is read-only"), $filename ) );
- return;
- }
- # Update cwd
- $SETTING{'cwd'} = dirname($filename);
- # Compile list of pages
- my @list_of_pages;
- my @pagelist = $slist->get_page_index( $SETTING{'Page range'} );
- for (@pagelist) {
- push @list_of_pages, $slist->{data}[$_][2];
- }
- # cd back to tempdir
- chdir $session;
- # Create the DjVu
- $logger->debug("Started saving $filename");
- my ( $signal, $pid );
- $pid = $slist->save_djvu(
- path => $filename,
- list_of_pages => \@list_of_pages,
- queued_callback => sub {
- return update_tpbar(@_);
- },
- started_callback => sub {
- my ( $thread, $process, $completed, $total ) = @_;
- $signal = setup_tpbar( $thread, $process, $completed, $total, $pid );
- return TRUE if ( defined $signal );
- },
- running_callback => sub {
- return update_tpbar(@_);
- },
- finished_callback => sub {
- my ( $new_page, $pending ) = @_;
- $thbox->hide_all unless $pending;
- $tcbutton->signal_handler_disconnect($signal) if ( defined $signal );
- mark_pages(@pagelist);
- system("xdg-open \"$filename\" &")
- if ( defined( $SETTING{'view files toggle'} )
- and $SETTING{'view files toggle'} );
- $logger->debug("Finished saving $filename");
- },
- error_callback => sub {
- my ($message) = @_;
- show_message_dialog( $window, 'error', 'close', $message );
- }
- );
- $windowi->hide if defined $windowi;
- }
- $file_chooser->destroy;
- }
- );
- $file_chooser->show;
- # cd back to tempdir
- chdir $session;
- return;
- }
- # Display page selector and email.
- sub email {
- if ( defined $windowe ) {
- $windowe->present;
- return;
- }
- # $SETTING{'Page range'} = 'selected' if $SETTING{'RMB'};
- $windowe = Gscan2pdf::Dialog->new(
- 'transient-for' => $window,
- title => $d->get('Email as PDF'),
- 'hide-on-delete' => TRUE,
- border_width => $border_width
- );
- my $vbox = $windowe->get('vbox');
- # PDF options
- my ( $entrya, $entryt, $entrys, $entryk ) = add_pdf_metadata($vbox);
- # Frame for page range
- add_page_range($vbox);
- # Compression options
- my ( $buttond, $spinbuttond, $combob, $hboxq, $spinbuttonq, @compression ) =
- add_pdf_compression($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 {
- # Set options
- update_PDF_settings( $entrya, $entryt, $entrys, $entryk );
- # Compile list of pages
- my @list_of_pages;
- my @pagelist = $slist->get_page_index( $SETTING{'Page range'} );
- for (@pagelist) {
- push @list_of_pages, $slist->{data}[$_][2];
- }
- # dig out the compression
- $SETTING{'downsample'} = $buttond->get_active;
- $SETTING{'downsample dpi'} = $spinbuttond->get_value;
- $SETTING{'pdf compression'} = $compression[ $combob->get_active ][0];
- $SETTING{'quality'} = $spinbuttonq->get_value;
- # Compile options
- my %options = (
- compression => $SETTING{'pdf compression'},
- downsample => $SETTING{downsample},
- 'downsample dpi' => $SETTING{'downsample dpi'},
- quality => $SETTING{quality},
- font => $SETTING{'pdf font'},
- );
- $pdf = File::Temp->new( DIR => $session, SUFFIX => '.pdf' );
- # Check for thunderbird
- my ($client);
- if ( defined( $ENV{KDE_FULL_SESSION} ) and $ENV{KDE_FULL_SESSION} eq 'true' )
- {
- ($client) = Gscan2pdf::Document::open_three(
- "kreadconfig --file emaildefaults --group PROFILE_Default --key EmailClient| cut -d ' ' -f 1"
- );
- }
- elsif ( defined( $ENV{GNOME_DESKTOP_SESSION_ID} )
- and $ENV{GNOME_DESKTOP_SESSION_ID} ne '' )
- {
- ($client) = Gscan2pdf::Document::open_three(
- "gconftool --get /desktop/gnome/url-handlers/mailto/command | cut -d ' ' -f 1"
- );
- }
- my $mua_string;
- if ( $client =~ /thunderbird/ ) {
- $mua_string = "thunderbird -compose attachment=file://$pdf";
- }
- else {
- $mua_string = "xdg-email --attach $pdf 'x\@y'";
- }
- # Create the PDF
- my ( $signal, $pid );
- $pid = $slist->save_pdf(
- path => $pdf->filename,
- list_of_pages => \@list_of_pages,
- metadata => get_pdf_metadata(),
- options => \%options,
- queued_callback => sub {
- return update_tpbar(@_);
- },
- started_callback => sub {
- my ( $thread, $process, $completed, $total ) = @_;
- $signal = setup_tpbar( $thread, $process, $completed, $total, $pid );
- return TRUE if ( defined $signal );
- },
- running_callback => sub {
- return update_tpbar(@_);
- },
- finished_callback => sub {
- my ( $new_page, $pending ) = @_;
- $thbox->hide_all unless $pending;
- $tcbutton->signal_handler_disconnect($signal) if ( defined $signal );
- mark_pages(@pagelist);
- system("xdg-open \"$pdf\" &")
- if ( defined( $SETTING{'view files toggle'} )
- and $SETTING{'view files toggle'} );
- $logger->info($mua_string);
- show_message_dialog( $window, 'error', 'close',
- $d->get('Error creating email') )
- if ( system($mua_string) );
- },
- error_callback => sub {
- my ($message) = @_;
- show_message_dialog( $window, 'error', 'close', $message );
- }
- );
- $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;
- $hboxq->hide_all if ( $compression[ $combob->get_active ][0] ne 'jpg' );
- return;
- }
- # Scan
- sub scan_dialog {
- if ( defined $windows ) {
- $windows->present;
- return;
- }
- # scan pop-up window
- $windows = Gscan2pdf::Dialog->new(
- 'transient-for' => $window,
- title => $d->get('Scan Document'),
- 'hide-on-delete' => TRUE,
- border_width => $border_width
- );
- my $vbox = $windows->get('vbox');
- # HBox for devices
- my $hboxd = Gtk2::HBox->new;
- $vbox->pack_start( $hboxd, FALSE, FALSE, 0 );
- # Notebook to collate options
- my $notebook = Gtk2::Notebook->new;
- $notebook->set_scrollable(TRUE);
- $vbox->pack_start( $notebook, TRUE, TRUE, 0 );
- # Notebook page 1
- my $vbox1 = Gtk2::VBox->new;
- $notebook->append_page( $vbox1, $d->get('Page Options') );
- # Frame for # pages
- my $framen = Gtk2::Frame->new( $d->get('# Pages') );
- $vbox1->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
- $bscanall = Gtk2::RadioButton->new( undef, $d->get('All') );
- $tooltips->set_tip( $bscanall, $d->get('Scan all pages') );
- $vboxn->pack_start( $bscanall, TRUE, TRUE, 0 );
- $bscanall->signal_connect(
- clicked => sub {
- $batch_scan->set_active(0) if ( defined $batch_scan );
- }
- );
- # Entry button
- my $hboxn = Gtk2::HBox->new;
- $vboxn->pack_start( $hboxn, TRUE, TRUE, 0 );
- $bscannum = Gtk2::RadioButton->new( $bscanall->get_group, "#:" );
- $tooltips->set_tip( $bscannum, $d->get('Set number of pages to scan') );
- $hboxn->pack_start( $bscannum, FALSE, FALSE, 0 );
- # Number of pages
- $spin_buttonn = Gtk2::SpinButton->new_with_range( 1, 999, 1 );
- $tooltips->set_tip( $spin_buttonn, $d->get('Set number of pages to scan') );
- $hboxn->pack_end( $spin_buttonn, FALSE, FALSE, 0 );
- # Set default
- if ( $SETTING{'pages to scan'} eq 'all' ) {
- $bscanall->set_active(TRUE);
- }
- else {
- $bscannum->set_active(TRUE);
- $spin_buttonn->set_value( $SETTING{'pages to scan'} );
- }
- # Toggle to switch between basic and extended modes
- my $checkx = Gtk2::CheckButton->new( $d->get('Extended page numbering') );
- $vbox1->pack_start( $checkx, FALSE, FALSE, 0 );
- # Frame for extended mode
- my $framex = Gtk2::Frame->new( $d->get('Page number') );
- $vbox1->pack_start( $framex, FALSE, FALSE, 0 );
- my $vboxx = Gtk2::VBox->new;
- $vboxx->set_border_width($border_width);
- $framex->add($vboxx);
- # SpinButton for starting page number
- my $hboxxs = Gtk2::HBox->new;
- $vboxx->pack_start( $hboxxs, FALSE, FALSE, 0 );
- my $labelxs = Gtk2::Label->new( $d->get('Start') );
- $hboxxs->pack_start( $labelxs, FALSE, FALSE, 0 );
- $start = 1;
- $spin_buttons = Gtk2::SpinButton->new_with_range( 1, 99999, 1 );
- $hboxxs->pack_end( $spin_buttons, FALSE, FALSE, 0 );
- # SpinButton for page number increment
- my $hboxi = Gtk2::HBox->new;
- $vboxx->pack_start( $hboxi, FALSE, FALSE, 0 );
- my $labelxi = Gtk2::Label->new( $d->get('Increment') );
- $hboxi->pack_start( $labelxi, FALSE, FALSE, 0 );
- $spin_buttoni = Gtk2::SpinButton->new_with_range( -99, 99, 1 );
- my $step = 1;
- $spin_buttoni->set_value($step);
- $hboxi->pack_end( $spin_buttoni, FALSE, FALSE, 0 );
- $spin_buttoni->signal_connect(
- 'value-changed' => sub {
- $spin_buttoni->set_value( -$step ) if ( $spin_buttoni->get_value == 0 );
- $step = $spin_buttoni->get_value;
- }
- );
- # Check whether the start page exists
- $spin_buttons->signal_connect( 'value-changed' => \&update_start );
- # Setting this here to fire callback running update_start
- $spin_buttons->set_value($start);
- # Callback on changing number of pages
- $spin_buttonn->signal_connect(
- 'value-changed' => sub {
- $bscannum->set_active(TRUE); # Set the radiobutton active
- my $n = $spin_buttonn->get_value;
- $batch_scan->set_active(0) if ( defined($batch_scan) and $n > 1 );
- # Check that there is room in the list for the number of pages
- update_number();
- }
- );
- # Frame for standard mode
- $frames = Gtk2::Frame->new( $d->get('Source document') );
- $vbox1->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 );
- $buttons->signal_connect(
- clicked => sub {
- $spin_buttoni->set_value(1);
- }
- );
- # Double sided button
- my $buttond =
- Gtk2::RadioButton->new( $buttons->get_group, $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 );
- my $combobs = Gtk2::ComboBox->new_text;
- 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
- if ( $combobs->get_active == 0 ) {
- $spin_buttoni->set_value(2);
- }
- else {
- $spin_buttoni->set_value(-2);
- }
- if ( $#{ $slist->{data} } > -1 ) {
- $spin_buttons->set_value( $slist->{data}[ $#{ $slist->{data} } ][0] + 1 );
- }
- else {
- $spin_buttons->set_value(1);
- }
- }
- );
- $tooltips->set_tip( $combobs,
- $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( $combobs, FALSE, FALSE, 0 );
- # Have to put the double-sided callback here to reference page side
- $buttond->signal_connect(
- clicked => sub {
- if ( $combobs->get_active == 0 ) {
- $spin_buttoni->set_value(2);
- }
- else {
- $spin_buttoni->set_value(-2);
- }
- if ( $#{ $slist->{data} } > -1 ) {
- $spin_buttons->set_value( $slist->{data}[ $#{ $slist->{data} } ][0] + 1 );
- }
- else {
- $spin_buttons->set_value(1);
- }
- }
- );
- # Have to put the extended pagenumber checkbox here to reference simple controls
- $checkx->signal_connect(
- toggled => sub {
- if ( $checkx->get_active ) {
- $frames->hide_all;
- $framex->show_all;
- }
- else {
- if ( $spin_buttoni->get_value == 1 ) {
- $buttons->set_active(TRUE);
- }
- elsif ( $spin_buttoni->get_value > 0 ) {
- $buttond->set_active(TRUE);
- $combobs->set_active(0);
- }
- else {
- $buttond->set_active(TRUE);
- $combobs->set_active(1);
- }
- $frames->show_all;
- $framex->hide_all;
- }
- }
- );
- my (
- $rotate, $side, $side2, $tesslang, $cflang,
- $rbutton, $r2button, $ubutton, $obutton, $comboboxs,
- $comboboxs2, $comboboxr, $comboboxr2, $comboboxe, $comboboxtl,
- $comboboxcl, $hboxtl, $hboxcl
- ) = add_postprocessing_options($vbox1);
- my @rotate = @$rotate;
- @side = @$side;
- my @side2 = @$side2;
- my @tesslang = @$tesslang;
- my @cflang = @$cflang;
- # Notebook page 2
- my $vbox2 = Gtk2::VBox->new;
- $notebook->append_page( $vbox2, $d->get('Scan Options') );
- # Frame for device-dependent options
- my $framed = Gtk2::Frame->new( $d->get('Device-dependent options') );
- $vbox2->pack_start( $framed, FALSE, FALSE, 0 );
- $vboxd = Gtk2::VBox->new;
- $vboxd->set_border_width($border_width);
- $framed->add($vboxd);
- # Scan profiles
- my $framesp = Gtk2::Frame->new( $d->get('Scan profiles') );
- $vbox2->pack_start( $framesp, FALSE, FALSE, 0 );
- my $vboxsp = Gtk2::VBox->new;
- $vboxsp->set_border_width($border_width);
- $framesp->add($vboxsp);
- my $combobsp = Gtk2::ComboBox->new_text;
- foreach my $profile ( keys %{ $SETTING{profile} } ) {
- $combobsp->append_text($profile);
- }
- $combobsp->signal_connect(
- changed => sub {
- my $profile = $combobsp->get_active_text;
- if ( defined $profile ) {
- $logger->debug( "Setting profile $profile: ",
- Dumper( $SETTING{profile}{$profile} ) );
- if ( ref( $SETTING{profile}{$profile} ) eq 'HASH' ) {
- while ( my ( $key, $value ) = each %{ $SETTING{profile}{$profile} } ) {
- $SETTING{$key} = $value;
- }
- }
- elsif ( ref( $SETTING{profile}{$profile} ) eq 'ARRAY' ) {
- for ( @{ $SETTING{profile}{$profile} } ) {
- my ( $name, $value ) = each %$_;
- $SETTING{$name} = $value;
- }
- }
- rescan_options( $device_list[ $combobd->get_active ]{name} )
- if ( defined $combobd );
- }
- }
- );
- if ( defined $SETTING{'default profile'} ) {
- Gscan2pdf::Dialog::Scan::Sane::set_combobox_by_text( $combobsp,
- $SETTING{'default profile'} );
- }
- elsif ( Gscan2pdf::Dialog::Scan::Sane::num_rows_combobox($combobsp) > -1 ) {
- $combobsp->set_active(0);
- }
- $combobsp->set_active(
- Gscan2pdf::Dialog::Scan::Sane::num_rows_combobox($combobsp) );
- $vboxsp->pack_start( $combobsp, FALSE, FALSE, 0 );
- my $hboxsp = Gtk2::HBox->new;
- $vboxsp->pack_end( $hboxsp, FALSE, FALSE, 0 );
- # Save button
- my $vbutton = Gtk2::Button->new_from_stock('gtk-save');
- $vbutton->signal_connect(
- clicked => sub {
- my $dialog = Gtk2::Dialog->new(
- $d->get('Name of scan profile'), $windows,
- 'destroy-with-parent',
- 'gtk-save' => 'ok',
- 'gtk-cancel' => 'cancel'
- );
- my $hbox = Gtk2::HBox->new;
- my $label = Gtk2::Label->new( $d->get('Name of scan profile') );
- $hbox->pack_start( $label, FALSE, FALSE, 0 );
- my $entry = Gtk2::Entry->new;
- $entry->set_activates_default(TRUE);
- $hbox->pack_end( $entry, TRUE, TRUE, 0 );
- $dialog->vbox->add($hbox);
- $dialog->set_default_response('ok');
- $dialog->show_all;
- if ( $dialog->run eq 'ok' and $entry->get_text !~ /^\s*$/x ) {
- my $profile = $entry->get_text;
- $SETTING{'default profile'} = $profile;
- my %options = walk_options_tree();
- $combobsp->append_text($profile);
- $SETTING{profile}{$profile} = \%options;
- $combobsp->set_active(
- Gscan2pdf::Dialog::Scan::Sane::num_rows_combobox($combobsp) );
- }
- $dialog->destroy;
- }
- );
- $hboxsp->pack_start( $vbutton, TRUE, TRUE, 0 );
- # Delete button
- my $dbutton = Gtk2::Button->new_from_stock('gtk-delete');
- $dbutton->signal_connect(
- clicked => sub {
- my $i = $combobsp->get_active;
- if ( $i > -1 ) {
- delete $SETTING{profile}{ $combobsp->get_active_text };
- $combobsp->remove_text($i);
- my $n = Gscan2pdf::Dialog::Scan::Sane::num_rows_combobox($combobsp);
- $i = $n if ( $i > $n );
- $combobsp->set_active($i) if ( $i > -1 );
- }
- }
- );
- $hboxsp->pack_start( $dbutton, FALSE, FALSE, 0 );
- # HBox for buttons
- my $hboxb = Gtk2::HBox->new;
- $vbox->pack_end( $hboxb, FALSE, FALSE, 0 );
- # Scan button
- $scan_button = Gtk2::Button->new( $d->get('Scan') );
- $hboxb->pack_start( $scan_button, TRUE, TRUE, 0 );
- $scan_button->signal_connect(
- clicked => sub {
- # Update undo/redo buffers
- take_snapshot();
- # Get selected device
- $SETTING{device} = $device_list[ $combobd->get_active ]{name};
- # ignore artificial paper size option
- my %options = walk_options_tree();
- delete $options{'Paper size'} if ( defined $options{'Paper size'} );
- # Get selected number of pages
- my $npages;
- if ( $bscannum->get_active ) {
- $SETTING{'pages to scan'} = $spin_buttonn->get_value;
- $npages = $SETTING{'pages to scan'};
- }
- else {
- $SETTING{'pages to scan'} = 'all';
- if ( $step < 0 ) {
- $npages =
- $slist->pages_possible( $spin_buttons->get_value,
- $spin_buttoni->get_value );
- }
- else {
- $npages = 0;
- }
- }
- $start = $spin_buttons->get_value;
- $step = $spin_buttoni->get_value;
- if ( not $checkx->get_active and $start == 1 and $step < 0 ) {
- show_message_dialog( $windows, 'error', 'cancel',
- $d->get('Must scan facing pages first') );
- return TRUE;
- }
- $SETTING{'rotate facing'} = 0;
- $SETTING{'rotate reverse'} = 0;
- if ( $rbutton->get_active ) {
- if ( $side[ $comboboxs->get_active ]->[0] eq 'both' ) {
- $SETTING{'rotate facing'} = $rotate[ $comboboxr->get_active ]->[0];
- $SETTING{'rotate reverse'} = $SETTING{'rotate facing'};
- }
- elsif ( $side[ $comboboxs->get_active ]->[0] eq 'facing' ) {
- $SETTING{'rotate facing'} = $rotate[ $comboboxr->get_active ]->[0];
- }
- else {
- $SETTING{'rotate reverse'} = $rotate[ $comboboxr->get_active ]->[0];
- }
- if ( $r2button->get_active ) {
- if ( $side2[ $comboboxs2->get_active ]->[0] eq 'facing' ) {
- $SETTING{'rotate facing'} = $rotate[ $comboboxr2->get_active ]->[0];
- }
- else {
- $SETTING{'rotate reverse'} = $rotate[ $comboboxr2->get_active ]->[0];
- }
- }
- }
- $logger->info("rotate facing $SETTING{'rotate facing'}");
- $logger->info("rotate reverse $SETTING{'rotate reverse'}");
- my $rotate_facing = $SETTING{'rotate facing'};
- my $rotate_reverse = $SETTING{'rotate reverse'};
- $logger->info( $duplex ? "" : "non-", "duplex mode" );
- if ( not $duplex ) {
- if ( $step > 0 ) {
- $rotate_reverse = $SETTING{'rotate facing'};
- }
- else {
- $rotate_facing = $SETTING{'rotate reverse'};
- }
- }
- $logger->info("rotate_facing $rotate_facing");
- $logger->info("rotate_reverse $rotate_reverse");
- $SETTING{'unpaper on scan'} = $ubutton->get_active;
- $logger->info("unpaper $SETTING{'unpaper on scan'}");
- $SETTING{'OCR on scan'} = $obutton->get_active;
- $logger->info("OCR $SETTING{'OCR on scan'}");
- if ( $SETTING{'OCR on scan'} ) {
- $SETTING{'ocr engine'} = $ocr_engine[ $comboboxe->get_active ]->[0];
- $SETTING{'ocr language'} = $tesslang[ $comboboxtl->get_active ]->[0]
- if ( $SETTING{'ocr engine'} eq 'tesseract'
- or $SETTING{'ocr engine'} eq 'ocropus' );
- $SETTING{'ocr language'} = $cflang[ $comboboxcl->get_active ]->[0]
- if ( $SETTING{'ocr engine'} eq 'cuneiform' );
- }
- scan_pages(
- device => $SETTING{device},
- npages => $npages,
- offset => $start - $step,
- step => $step,
- rotate_facing => $rotate_facing,
- rotate_reverse => $rotate_reverse,
- unpaper => $SETTING{'unpaper on scan'},
- ocr => $SETTING{'OCR on scan'},
- device_options => \%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; } );
- if ($test) {
- parse_device_list( $test->{output} );
- populate_device_list($hboxd);
- }
- elsif ( not @device ) {
- get_devices($hboxd);
- }
- else {
- populate_device_list($hboxd);
- }
- # Show window
- $windows->show_all;
- $framex->hide_all;
- $hboxtl->hide_all
- if (
- defined($hboxtl)
- and not( $ocr_engine[ $comboboxe->get_active ]->[0] eq 'tesseract'
- or $ocr_engine[ $comboboxe->get_active ]->[0] eq 'ocropus' )
- );
- $hboxcl->hide_all
- if ( defined($hboxcl)
- and not( $ocr_engine[ $comboboxe->get_active ]->[0] eq 'cuneiform' ) );
- # Has to be done after showing the window, otherwise the window doesn't get centred.
- if ($test) {
- $scan_button->grab_focus;
- }
- else {
- $scan_button->set_sensitive(FALSE);
- }
- return;
- }
- sub add_postprocessing_options {
- my ($vbox) = @_;
- # Frame for post-processing
- my $framep = Gtk2::Frame->new( $d->get('Post-processing') );
- $vbox->pack_start( $framep, FALSE, FALSE, 0 );
- my $vboxp = Gtk2::VBox->new;
- $vboxp->set_border_width($border_width);
- $framep->add($vboxp);
- # Rotate
- my $hboxr = Gtk2::HBox->new;
- $vboxp->pack_start( $hboxr, FALSE, FALSE, 0 );
- my $rbutton = Gtk2::CheckButton->new( $d->get('Rotate') );
- $tooltips->set_tip( $rbutton, $d->get('Rotate image after scanning') );
- $hboxr->pack_start( $rbutton, TRUE, TRUE, 0 );
- my @side = (
- [ 'both', $d->get('Both sides'), $d->get('Both sides.') ],
- [ 'facing', $d->get('Facing side'), $d->get('Facing side.') ],
- [ 'reverse', $d->get('Reverse side'), $d->get('Reverse side.') ],
- );
- my $comboboxs = combobox_from_array(@side);
- $tooltips->set_tip( $comboboxs, $d->get('Select side to rotate') );
- $hboxr->pack_start( $comboboxs, TRUE, TRUE, 0 );
- my @rotate = (
- [ 90, $d->get('90'), $d->get('Rotate image 90 degrees clockwise.') ],
- [ 180, $d->get('180'), $d->get('Rotate image 180 degrees clockwise.') ],
- [ 270, $d->get('270'), $d->get('Rotate image 90 degrees anticlockwise.') ],
- );
- my $comboboxr = combobox_from_array(@rotate);
- $tooltips->set_tip( $comboboxr, $d->get('Select direction of rotation') );
- $hboxr->pack_end( $comboboxr, TRUE, TRUE, 0 );
- $hboxr = Gtk2::HBox->new;
- $vboxp->pack_start( $hboxr, FALSE, FALSE, 0 );
- my $r2button = Gtk2::CheckButton->new( $d->get('Rotate') );
- $tooltips->set_tip( $r2button, $d->get('Rotate image after scanning') );
- $hboxr->pack_start( $r2button, TRUE, TRUE, 0 );
- my @side2;
- my $comboboxs2 = Gtk2::ComboBox->new_text;
- $tooltips->set_tip( $comboboxs2, $d->get('Select side to rotate') );
- $hboxr->pack_start( $comboboxs2, TRUE, TRUE, 0 );
- my $comboboxr2 = combobox_from_array(@rotate);
- $tooltips->set_tip( $comboboxr2, $d->get('Select direction of rotation') );
- $hboxr->pack_end( $comboboxr2, TRUE, TRUE, 0 );
- $rbutton->signal_connect(
- toggled => sub {
- if ( $rbutton->get_active ) {
- $hboxr->set_sensitive(TRUE)
- if ( $side[ $comboboxs->get_active ]->[0] ne 'both' );
- }
- else {
- $hboxr->set_sensitive(FALSE);
- }
- }
- );
- $comboboxs->signal_connect(
- changed => sub {
- if ( $side[ $comboboxs->get_active ]->[0] eq 'both' ) {
- $hboxr->set_sensitive(FALSE);
- $r2button->set_active(FALSE);
- }
- else {
- $hboxr->set_sensitive(TRUE) if ( $rbutton->get_active );
- # Empty combobox
- while ( $comboboxs2->get_active > -1 ) {
- $comboboxs2->remove_text(0);
- $comboboxs2->set_active(0);
- }
- @side2 = ();
- foreach (@side) {
- push @side2, $_
- unless ( $_->[0] eq 'both'
- or $_->[0] eq $side[ $comboboxs->get_active ]->[0] );
- }
- $comboboxs2->append_text( $side2[0]->[1] );
- $comboboxs2->set_active(0);
- }
- }
- );
- # In case it isn't set elsewhere
- combobox_set_active( $comboboxr2, 90, @rotate );
- if ( $SETTING{'rotate facing'} or $SETTING{'rotate reverse'} ) {
- $rbutton->set_active(TRUE);
- }
- if ( $SETTING{'rotate facing'} == $SETTING{'rotate reverse'} ) {
- combobox_set_active( $comboboxs, 'both', @side );
- combobox_set_active( $comboboxr, $SETTING{'rotate facing'}, @rotate );
- }
- elsif ( $SETTING{'rotate facing'} ) {
- combobox_set_active( $comboboxs, 'facing', @side );
- combobox_set_active( $comboboxr, $SETTING{'rotate facing'}, @rotate );
- if ( $SETTING{'rotate reverse'} ) {
- $r2button->set_active(TRUE);
- combobox_set_active( $comboboxs2, 'reverse', @side2 );
- combobox_set_active( $comboboxr2, $SETTING{'rotate reverse'}, @rotate );
- }
- }
- else {
- combobox_set_active( $comboboxs, 'reverse', @side );
- combobox_set_active( $comboboxr, $SETTING{'rotate reverse'}, @rotate );
- }
- # CheckButton for unpaper
- my $hboxu = Gtk2::HBox->new;
- $vboxp->pack_start( $hboxu, FALSE, FALSE, 0 );
- my $ubutton = Gtk2::CheckButton->new( $d->get('Clean up images') );
- $tooltips->set_tip( $ubutton,
- $d->get('Clean up scanned images with unpaper') );
- $hboxu->pack_start( $ubutton, TRUE, TRUE, 0 );
- if ( not $dependencies{unpaper} ) {
- $ubutton->set_sensitive(FALSE);
- $ubutton->set_active(FALSE);
- }
- elsif ( $SETTING{'unpaper on scan'} ) {
- $ubutton->set_active(TRUE);
- }
- my $button = Gtk2::Button->new( $d->get('Options') );
- $tooltips->set_tip( $button, $d->get('Set unpaper options') );
- $hboxu->pack_end( $button, TRUE, TRUE, 0 );
- $button->signal_connect(
- clicked => sub {
- my $windowuo = Gscan2pdf::Dialog->new(
- 'transient-for' => $window,
- title => $d->get('unpaper options'),
- border_width => $border_width
- );
- my $vbox1 = $windowuo->get('vbox');
- $unpaper->add_options($vbox1);
- # HBox for buttons
- my $hboxb = Gtk2::HBox->new;
- $vbox1->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 $SETTING
- $SETTING{'unpaper options'} = $unpaper->get_options;
- $windowuo->destroy;
- }
- );
- # Cancel button
- my $cbutton = Gtk2::Button->new_from_stock('gtk-cancel');
- $hboxb->pack_end( $cbutton, FALSE, FALSE, 0 );
- $cbutton->signal_connect( clicked => sub { $windowuo->destroy; } );
- $windowuo->show_all;
- }
- );
- # CheckButton for OCR
- my $hboxo = Gtk2::HBox->new;
- $vboxp->pack_start( $hboxo, FALSE, FALSE, 0 );
- my $obutton = Gtk2::CheckButton->new( $d->get('OCR scanned pages') );
- $tooltips->set_tip( $obutton, $d->get('OCR scanned pages') );
- if ( not $dependencies{gocr}
- and not $dependencies{tesseract}
- and not $dependencies{ocropus}
- and not $dependencies{cuneiform} )
- {
- $hboxo->set_sensitive(FALSE);
- $obutton->set_active(FALSE);
- }
- elsif ( $SETTING{'OCR on scan'} ) {
- $obutton->set_active(TRUE);
- }
- $hboxo->pack_start( $obutton, TRUE, TRUE, 0 );
- my $comboboxe = combobox_from_array(@ocr_engine);
- $tooltips->set_tip( $comboboxe, $d->get('Select OCR engine') );
- $hboxo->pack_end( $comboboxe, TRUE, TRUE, 0 );
- my ( $comboboxtl, $hboxtl, @tesslang, $comboboxcl, $hboxcl, @cflang );
- if ( $dependencies{tesseract} ) {
- ( $hboxtl, $comboboxtl, @tesslang ) = add_tess_languages($vboxp);
- $comboboxe->signal_connect(
- changed => sub {
- if ($ocr_engine[ $comboboxe->get_active ]->[0] eq 'tesseract'
- or $ocr_engine[ $comboboxe->get_active ]->[0] eq 'ocropus' )
- {
- $hboxtl->show_all;
- }
- else {
- $hboxtl->hide_all;
- }
- }
- );
- if ( $dependencies{cuneiform} ) {
- ( $hboxcl, $comboboxcl, @cflang ) = add_cf_languages($vboxp);
- $comboboxe->signal_connect(
- changed => sub {
- if ( $ocr_engine[ $comboboxe->get_active ]->[0] eq 'cuneiform' ) {
- $hboxcl->show_all;
- }
- else {
- $hboxcl->hide_all;
- }
- }
- );
- }
- $hboxtl->set_sensitive(FALSE) unless ( $obutton->get_active );
- $obutton->signal_connect(
- toggled => sub {
- if ( $obutton->get_active ) {
- $hboxtl->set_sensitive(TRUE);
- }
- else {
- $hboxtl->set_sensitive(FALSE);
- }
- }
- );
- }
- combobox_set_active( $comboboxe, $SETTING{'ocr engine'}, @ocr_engine );
- return (
- \@rotate, \@side, \@side2, \@tesslang, \@cflang,
- $rbutton, $r2button, $ubutton, $obutton, $comboboxs,
- $comboboxs2, $comboboxr, $comboboxr2, $comboboxe, $comboboxtl,
- $comboboxcl, $hboxtl, $hboxcl
- );
- }
- # Run scanimage --formatted-device-list
- sub get_devices {
- my $hboxd = shift;
- # Set up ProgressBar
- my $pbar = Gtk2::ProgressBar->new;
- $pbar->set_pulse_step(.1);
- $pbar->set_text( $d->get('Fetching list of devices') );
- $hboxd->pack_start( $pbar, TRUE, TRUE, 0 );
- $pbar->show;
- Gscan2pdf::Frontend::CLI->get_devices(
- prefix => $SETTING{'scan prefix'},
- running_callback => sub { $pbar->pulse },
- finished_callback => sub {
- my ($device_list) = @_;
- $pbar->destroy;
- if ( $#{$device_list} == -1 ) {
- $windows->destroy;
- undef $windows;
- show_message_dialog( $window, 'error', 'close',
- $d->get('No devices found') );
- return;
- }
- # Note any duplicate device names and delete if necessary
- my %seen;
- my $i = 0;
- while ( $i < @$device_list ) {
- $seen{ $device_list->[$i]{name} }++;
- if ( $seen{ $device_list->[$i]{name} } > 1 ) {
- splice @$device_list, $i, 1;
- }
- else {
- $i++;
- }
- }
- # Note any duplicate model names and add the device if necessary
- undef %seen;
- for (@$device_list) {
- $_->{model} = $_->{name} unless ( defined $_->{model} );
- $seen{ $_->{model} }++;
- }
- for (@$device_list) {
- if ( defined $_->{vendor} ) {
- $_->{label} = "$_->{vendor} $_->{model}";
- }
- else {
- $_->{label} = $_->{model};
- }
- $_->{label} .= " on $_->{name}" if ( $seen{ $_->{model} } > 1 );
- }
- if (@device_list) { # Update combobox
- $i = 0;
- while ( $i < @device_list ) {
- if ( @$device_list
- and $i < @$device_list
- and $device_list[$i]{label} ne $device_list->[$i]{label} )
- {
- $combobd->insert_text( $i, $device_list->[$i]{label} );
- $combobd->remove_text($i);
- }
- $i++;
- }
- while ( $i < @$device_list ) {
- $combobd->append_text( $device_list->[ $i++ ]{label} );
- }
- @device_list = @{$device_list};
- set_device();
- $combobd->show;
- $labeld->show;
- }
- else { # New combobox
- @device_list = @{$device_list};
- populate_device_list($hboxd);
- }
- # # If device not set by config and there is a default device, then set it
- # $SETTING{device} = $1
- # if ( not defined( $SETTING{device} )
- # and defined($output)
- # and $output =~ /default\ device\ is\ `(.*)'/x );
- }
- );
- return;
- }
- # Scan
- sub scan_dialog2 {
- if ( defined $windows2 ) {
- $windows2->present;
- return;
- }
- # If device not set by config and there is a default device, then set it
- $SETTING{device} = $ENV{'SANE_DEFAULT_DEVICE'}
- if ( not defined( $SETTING{device} )
- and defined( $ENV{'SANE_DEFAULT_DEVICE'} ) );
- $SETTING{'pages to scan'} = 0 if ( $SETTING{'pages to scan'} eq 'all' );
- # scan pop-up window
- $windows2 = Gscan2pdf::Dialog::Scan::Sane->new(
- 'transient-for' => $window,
- title => $d->get('Scan Document'),
- border_width => $border_width,
- logger => $logger,
- dir => $session,
- 'hide-on-delete' => TRUE,
- 'num-pages' => $SETTING{'pages to scan'},
- 'page-number-start' => $#{ $slist->{data} } > -1
- ? $slist->{data}[ $#{ $slist->{data} } ][0] + 1
- : 1,
- );
- # Can't set the device when creating the window, as the list does not exist then
- $windows2->signal_connect(
- 'changed-device-list' => sub {
- my ( $widget, $device_list ) = @_;
- if ( defined $device_list ) {
- # Only set default device if it hasn't been specified on the command line
- # and it is in the the device list
- if ( defined( $SETTING{device} ) and not @device_list ) {
- for (@$device_list) {
- if ( $SETTING{device} eq $_->{name} ) {
- $windows2->set( 'device', $SETTING{device} );
- return;
- }
- }
- }
- $windows2->set( 'device', $device_list->[0]{name} );
- }
- }
- );
- # Update default device
- $windows2->signal_connect(
- 'changed-device' => sub {
- my ( $widget, $device ) = @_;
- $SETTING{device} = $device
- if ( defined($device) and $device ne '' );
- }
- );
- # Set default
- $windows2->signal_connect(
- 'changed-num-pages' => sub {
- ( my $widget, $SETTING{'pages to scan'} ) = @_;
- # Check that there is room in the list for the number of pages
- update_number();
- }
- );
- $windows2->signal_connect(
- 'changed-page-number-start' => sub {
- my ( $widget, $value ) = @_;
- $windows2->set( 'max-pages',
- $slist->pages_possible( $value, $windows2->get('page-number-increment') ) );
- }
- );
- $windows2->signal_connect(
- 'changed-page-number-increment' => sub {
- my ( $widget, $step ) = @_;
- $windows2->set( 'max-pages',
- $slist->pages_possible( $windows2->get('page-number-start'), $step ) );
- }
- );
- $windows2->signal_connect(
- 'changed-side-to-scan' => sub {
- my ( $widget, $side ) = @_;
- if ( $#{ $slist->{data} } > -1 ) {
- $windows2->set( 'page-number-start',
- $slist->{data}[ $#{ $slist->{data} } ][0] + 1 );
- }
- else {
- $windows2->set( 'page-number-start', 1 );
- }
- }
- );
- my $signal;
- $windows2->signal_connect(
- 'started-process' => sub {
- my ( $widget, $message ) = @_;
- $spbar->set_fraction(0);
- $spbar->set_text($message);
- $shbox->show_all;
- $signal = $scbutton->signal_connect(
- clicked => sub {
- $windows2->cancel_scan;
- }
- );
- }
- );
- $windows2->signal_connect(
- 'changed-progress' => sub {
- my ( $widget, $progress, $message ) = @_;
- if ( $progress == -1 ) {
- $spbar->pulse;
- }
- else {
- $spbar->set_fraction($progress);
- }
- $spbar->set_text($message) if ( defined $message );
- }
- );
- $windows2->signal_connect(
- 'finished-process' => sub {
- my ( $widget, $process, $button_signal ) = @_;
- $scbutton->signal_handler_disconnect($button_signal)
- if ( defined $button_signal );
- $shbox->hide;
- $windows2->scan_options( $SETTING{device} )
- if ( $SETTING{'cycle sane handle'} and $process eq 'scan_pages' );
- },
- $signal
- );
- $windows2->signal_connect(
- 'process-error' => sub {
- my ( $widget, $msg ) = @_;
- $scbutton->signal_handler_disconnect($signal) if ( defined $signal );
- $shbox->hide;
- show_message_dialog( $windows2, 'error', 'close', $d_sane->get($msg) );
- }
- );
- $windows2->signal_connect(
- 'changed-scan-option' => sub {
- my ( $widget, $option, $value ) = @_;
- # Note resolution
- $SETTING{resolution} = $value
- if ( $option eq SANE_NAME_SCAN_RESOLUTION );
- }
- );
- # Profiles
- my @profiles = keys %{ $SETTING{profile} };
- foreach my $profile (@profiles) {
- $windows2->add_profile( $profile, $SETTING{profile}{$profile} );
- }
- $windows2->signal_connect(
- 'changed-profile' => sub {
- my ( $widget, $profile ) = @_;
- $SETTING{'default profile'} = $profile;
- }
- );
- $windows2->signal_connect(
- 'added-profile' => sub {
- my ( $widget, $name, $profile ) = @_;
- $SETTING{profile}{$name} = $profile;
- }
- );
- $windows2->signal_connect(
- 'removed-profile' => sub {
- my ( $widget, $profile ) = @_;
- delete $SETTING{profile}{$profile};
- }
- );
- # Update the default profile when the scan options change
- $windows2->signal_connect(
- 'changed-current-scan-options' => sub {
- my ( $widget, $option_array ) = @_;
- $SETTING{'default-scan-options'} = $option_array;
- }
- );
- # Can't set the paper or profile until the options have been loaded
- $windows2->signal_connect(
- 'reloaded-scan-options' => sub {
- $windows2->set( 'paper-formats', $SETTING{Paper} );
- $windows2->set( 'paper', $SETTING{'Paper size'} )
- if ( defined $SETTING{'Paper size'} );
- if ( defined $SETTING{'default profile'} ) {
- $windows2->set( 'profile', $SETTING{'default profile'} );
- }
- elsif ( defined $SETTING{'default-scan-options'} ) {
- $windows2->set( 'current-scan-options', $SETTING{'default-scan-options'} );
- }
- elsif (@profiles) {
- $windows2->set( 'profile', $profiles[0] );
- }
- }
- );
- $windows2->signal_connect(
- 'changed-paper-formats' => sub {
- my ( $widget, $formats ) = @_;
- $SETTING{Paper} = $formats;
- }
- );
- $windows2->signal_connect(
- 'changed-paper' => sub {
- my ( $widget, $paper ) = @_;
- $SETTING{'Paper size'} = $paper;
- }
- );
- my $vbox = $windows2->get('vbox');
- my (
- $rotate, $side, $side2, $tesslang, $cflang,
- $rbutton, $r2button, $ubutton, $obutton, $comboboxs,
- $comboboxs2, $comboboxr, $comboboxr2, $comboboxe, $comboboxtl,
- $comboboxcl, $hboxtl, $hboxcl
- ) = add_postprocessing_options($vbox);
- my @rotate = @$rotate;
- my @side = @$side;
- my @side2 = @$side2;
- my @tesslang = @$tesslang;
- my @cflang = @$cflang;
- # Show window
- $windows2->show_all;
- $hboxtl->hide_all
- if (
- defined($hboxtl)
- and not( $ocr_engine[ $comboboxe->get_active ]->[0] eq 'tesseract'
- or $ocr_engine[ $comboboxe->get_active ]->[0] eq 'ocropus' )
- );
- $hboxcl->hide_all
- if ( defined($hboxcl)
- and not( $ocr_engine[ $comboboxe->get_active ]->[0] eq 'cuneiform' ) );
- $windows2->signal_connect(
- 'new-scan' => sub {
- my ( $widget, $n ) = @_;
- # Update undo/redo buffers
- take_snapshot();
- $SETTING{'rotate facing'} = 0;
- $SETTING{'rotate reverse'} = 0;
- if ( $rbutton->get_active ) {
- if ( $side[ $comboboxs->get_active ]->[0] eq 'both' ) {
- $SETTING{'rotate facing'} = $rotate[ $comboboxr->get_active ]->[0];
- $SETTING{'rotate reverse'} = $SETTING{'rotate facing'};
- }
- elsif ( $side[ $comboboxs->get_active ]->[0] eq 'facing' ) {
- $SETTING{'rotate facing'} = $rotate[ $comboboxr->get_active ]->[0];
- }
- else {
- $SETTING{'rotate reverse'} = $rotate[ $comboboxr->get_active ]->[0];
- }
- if ( $r2button->get_active ) {
- if ( $side2[ $comboboxs2->get_active ]->[0] eq 'facing' ) {
- $SETTING{'rotate facing'} = $rotate[ $comboboxr2->get_active ]->[0];
- }
- else {
- $SETTING{'rotate reverse'} = $rotate[ $comboboxr2->get_active ]->[0];
- }
- }
- }
- $logger->info("rotate facing $SETTING{'rotate facing'}");
- $logger->info("rotate reverse $SETTING{'rotate reverse'}");
- $SETTING{'unpaper on scan'} = $ubutton->get_active;
- $logger->info("unpaper $SETTING{'unpaper on scan'}");
- $SETTING{'OCR on scan'} = $obutton->get_active;
- $logger->info("OCR $SETTING{'OCR on scan'}");
- if ( $SETTING{'OCR on scan'} ) {
- $SETTING{'ocr engine'} = $ocr_engine[ $comboboxe->get_active ]->[0];
- $SETTING{'ocr language'} = $tesslang[ $comboboxtl->get_active ]->[0]
- if ( $SETTING{'ocr engine'} eq 'tesseract'
- or $SETTING{'ocr engine'} eq 'ocropus' );
- $SETTING{'ocr language'} = $cflang[ $comboboxcl->get_active ]->[0]
- if ( $SETTING{'ocr engine'} eq 'cuneiform' );
- }
- # If the scan can't be loaded then blow the scanning dialog away and
- # show an error
- $rotate = $n % 2 ? $SETTING{'rotate facing'} : $SETTING{'rotate reverse'};
- if ( defined $test_image ) {
- import_scan(
- filename => $test_image,
- page => $n,
- delete => FALSE,
- rotate => $rotate,
- unpaper => $SETTING{'unpaper on scan'},
- ocr => $SETTING{'OCR on scan'}
- );
- }
- else {
- $SETTING{resolution} =
- defined( $SETTING{resolution} )
- ? $SETTING{resolution}
- : $Gscan2pdf::Document::POINTS_PER_INCH;
- $logger->info("Importing scan with resolution=$SETTING{resolution}");
- import_scan(
- filename => File::Spec->catdir( $session, "out$n.pnm" ),
- page => $n,
- resolution => $SETTING{resolution},
- delete => TRUE,
- rotate => $rotate,
- unpaper => $SETTING{'unpaper on scan'},
- ocr => $SETTING{'OCR on scan'}
- );
- }
- }
- );
- if (@device_list) {
- $windows2->set( 'device-list', \@device_list );
- }
- else {
- $windows2->get_devices;
- }
- return;
- }
- sub walk_options_tree {
- # Get device-specific options
- my %options;
- if ( defined $vbox_manual_paper ) {
- foreach my $hbox ( $vboxd->get_children, $vbox_manual_paper->get_children ) {
- my $key;
- if ( $hbox->isa('Gtk2::HBox') and $hbox->sensitive ) {
- foreach my $widget ( $hbox->get_children ) {
- if ( $widget->isa('Gtk2::Label') ) {
- $key = get_key( \%ddo, $widget->get_label );
- }
- elsif ( $widget->isa('Gtk2::ComboBox') ) {
- if ( $key eq 'Paper size' ) { ## no critic (ProhibitDeepNests)
- $SETTING{$key} = $widget->get_active_text;
- }
- else {
- $SETTING{$key} = get_value( \%ddo, $key, $widget->get_active_text );
- }
- $options{$key} = $SETTING{$key};
- }
- elsif ( $widget->isa('Gtk2::SpinButton') ) {
- $options{$key} = $widget->get_value;
- $SETTING{$key} = $options{$key};
- }
- }
- }
- }
- }
- $logger->debug( Dumper( \%options ) );
- return %options;
- }
- sub populate_device_list {
- my ($hboxd) = @_;
- # device list
- $labeld = Gtk2::Label->new( $d->get('Device') );
- $hboxd->pack_start( $labeld, FALSE, FALSE, 0 );
- $combobd = Gtk2::ComboBox->new_text;
- # read the model names into the combobox
- foreach (@device_list) {
- $combobd->append_text( $_->{label} );
- }
- $combobd->append_text( $d->get('Rescan for devices') ) unless ($test);
- # flags whether already run or not
- my $run = FALSE;
- $combobd->signal_connect(
- changed => sub {
- my $index = $combobd->get_active;
- if ( $index > $#device_list ) {
- $combobd->hide;
- $labeld->hide;
- get_devices($hboxd);
- }
- else {
- # only delete the mode setting if switching devices, not on first run
- delete $SETTING{mode} if ($run);
- $run = TRUE;
- rescan_options( $device_list[$index]{name} );
- $vboxd->show_all;
- }
- }
- );
- $tooltips->set_tip( $combobd,
- $d->get('Sets the device to be used for the scan') );
- $hboxd->pack_end( $combobd, FALSE, FALSE, 0 );
- # If device in settings then set it
- set_device();
- $hboxd->show_all;
- return;
- }
- sub set_device {
- my $o;
- if ( defined $SETTING{device} ) {
- for ( my $i = 0 ; $i < @device_list ; $i++ ) {
- $o = $i if ( $SETTING{device} eq $device_list[$i]{name} );
- }
- }
- unless ( defined $o ) {
- $o = 0;
- delete $SETTING{mode};
- }
- # Set the device dependent devices after the number of pages to scan so that
- # the source button callback can ghost the all button
- # This then fires the callback, updating the options, so no need to do it further down.
- $combobd->set_active($o);
- return;
- }
- # Called either from changed-value signal of spinbutton,
- # or row-changed signal of simplelist
- sub update_start {
- return unless ( defined($spin_buttons) or defined($windows2) );
- my ( $value, $step );
- if ( defined $windows2 ) {
- $value = $windows2->get('page-number-start');
- $start = $windows2->get('page-number-start') unless ( defined $start );
- $step = $value - $start;
- $step = $windows2->get('page-number-increment') if ( $step == 0 );
- }
- else {
- $value = $spin_buttons->get_value;
- $step = $value - $start;
- $step = $spin_buttoni->get_value if ( $step == 0 );
- }
- my $exists = TRUE;
- my $i = $step > 0 ? 0 : $#{ $slist->{data} };
- $start = $value;
- while ($exists) {
- if ($i < 0
- or $i > $#{ $slist->{data} }
- or ( $slist->{data}[$i][0] > $value and $step > 0 )
- or ( $slist->{data}[$i][0] < $value and $step < 0 ) )
- {
- $exists = FALSE;
- }
- elsif ( $slist->{data}[$i][0] == $value ) {
- $value += $step;
- if ( $value < 1 ) {
- $value = 1;
- $step = 1;
- }
- }
- else {
- $i += $step > 0 ? 1 : -1;
- }
- }
- $spin_buttons->set_value($value)
- if ( defined($spin_buttons) and $start != $value );
- $windows2->set( 'page-number-start', $value ) if ( defined $windows2 );
- $start = $value;
- update_number();
- return;
- }
- # Update the number of pages to scan spinbutton if necessary
- sub update_number {
- return unless ( defined($spin_buttons) or defined($windows2) );
- if ( defined $windows2 ) {
- my $n = $slist->pages_possible(
- $windows2->get('page-number-start'),
- $windows2->get('page-number-increment')
- );
- $windows2->set( 'num-pages', $n )
- if ( $n > 0 and $n < $windows2->get('num-pages') );
- }
- else {
- my $n =
- $slist->pages_possible( $spin_buttons->get_value,
- $spin_buttoni->get_value );
- $spin_buttonn->set_value($n) if ( $n > 0 and $n < $spin_buttonn->get_value );
- }
- return;
- }
- # Carry out the scan with scanimage or scanadf and the options passed.
- sub scan_pages {
- my (%options) = @_;
- # Make sure we are in temp directory
- chdir $session;
- if ($test) {
- my $rotate = 1 % 2 ? $options{rotate_facing} : $options{rotate_reverse};
- my $index = import_scan(
- filename => $test_image,
- page => 1 * $options{step} + $options{offset},
- resolution => $SETTING{resolution},
- delete => FALSE,
- rotate => $rotate,
- unpaper => $options{unpaper},
- ocr => $options{ocr}
- );
- }
- else {
- my $signal;
- my @options = hash2options( %{ $options{device_options} } );
- Gscan2pdf::Frontend::CLI->scan_pages(
- prefix => $SETTING{'scan prefix'},
- frontend => $SETTING{frontend},
- device => $options{device},
- npages => $options{npages},
- options => \@options,
- started_callback => sub {
- $spbar->set_text( $d->get('Scanning') );
- $spbar->set_pulse_step(.1);
- $shbox->show_all;
- $signal = $scbutton->signal_connect(
- clicked => sub {
- Gscan2pdf::Frontend::CLI->cancel_scan;
- }
- );
- },
- running_callback => sub {
- my ( $fraction, $text ) = @_;
- if ( defined $fraction ) {
- if ( $fraction < 0 ) {
- $spbar->pulse;
- }
- else {
- $spbar->set_fraction($fraction);
- }
- }
- $spbar->set_text($text) if ( defined $text );
- },
- new_page_callback => sub {
- my ($path) = @_;
- my $id;
- $id = $1 if ( $path =~ /^out(\d+).pnm/x );
- # If the scan can't be loaded then blow the scanning dialog away and
- # show an error
- my $rotate = $id % 2 ? $options{rotate_facing} : $options{rotate_reverse};
- my $index = import_scan(
- filename => $path,
- page => $id * $options{step} + $options{offset},
- resolution => $SETTING{resolution},
- delete => TRUE,
- rotate => $rotate,
- unpaper => $options{unpaper},
- ocr => $options{ocr}
- );
- },
- error_callback => sub {
- my ($text) = @_;
- show_message_dialog( $windows, 'error', 'close', $text );
- },
- finished_callback => sub {
- $scbutton->signal_handler_disconnect($signal);
- $shbox->hide;
- },
- );
- }
- return;
- }
- # Take a hash of options and push them onto an array
- sub hash2options {
- my %options = @_;
- my ( @options, $key, $value );
- # Make sure mode is first in case of mode-dependent options
- if ( defined( $options{mode} ) ) {
- push @options, "--mode='$options{mode}'";
- delete $options{mode};
- }
- while ( ( $key, $value ) = each(%options) ) {
- if ( $key =~ /^[xylt]$/x ) {
- push @options, "-$key $value";
- }
- else {
- push @options, "--$key='$value'";
- }
- }
- return @options;
- }
- sub post_process_scan {
- my ( $pages, $rotate, $unpaper_flag, $ocr ) = @_;
- if ($rotate) {
- rotate(
- $rotate, $pages,
- sub { # finished callback
- my ($page) = @_;
- post_process_scan( $page, undef, $unpaper_flag, $ocr );
- }
- );
- }
- elsif ($unpaper_flag) {
- unpaper_page(
- $pages,
- $unpaper->get_cmdline,
- sub { # finished callback
- my ($page) = @_;
- post_process_scan( $page, undef, undef, $ocr );
- }
- );
- }
- elsif ($ocr) {
- ocr_page($pages);
- }
- return;
- }
- # Take new scan and display it
- sub import_scan {
- my %options = @_;
- # Interface to frontend
- open my $fh, '<', $options{filename} ## no critic (RequireBriefOpen)
- or die "can't open $options{filename}: $!\n";
- # Read without blocking
- my $size = 0;
- Glib::IO->add_watch(
- fileno($fh),
- [ 'in', 'hup' ],
- sub {
- my ( $fileno, $condition ) = @_;
- if ( $condition & 'in' ) { # bit field operation. >= would also work
- if ( $size == 0 ) {
- $size = Gscan2pdf::NetPBM::file_size_from_header( $options{filename} );
- $logger->info("Header suggests $size");
- return Glib::SOURCE_CONTINUE if ( $size == 0 );
- close($fh);
- }
- my $filesize = -s $options{filename};
- $logger->info("Expecting $size, found $filesize");
- if ( $size > $filesize ) {
- my $pad = $size - $filesize;
- open( my $fh, ">>", $options{filename} )
- or die "cannot open >> $options{filename}: $!\n";
- my $data = '';
- for ( 1 .. $pad * 8 ) {
- $data .= '1';
- }
- printf $fh pack( sprintf( "b%d", length $data ), $data );
- close $fh;
- $logger->info("Padded $pad bytes");
- }
- my $gpage = Gscan2pdf::Page->new(
- filename => $options{filename},
- resolution => $options{resolution},
- format => 'Portable anymap',
- delete => $options{delete},
- dir => $session,
- window => $window,
- );
- my $index = $slist->add_page( $gpage, $options{page}, &update_uimanager );
- if ( $index == -1 ) {
- show_message_dialog( $windows, 'error', 'close',
- $d->get('Unable to load image') );
- }
- else {
- update_uimanager();
- post_process_scan( [$gpage], $options{rotate}, $options{unpaper},
- $options{ocr} );
- }
- return Glib::SOURCE_REMOVE;
- }
- return Glib::SOURCE_CONTINUE;
- }
- );
- return;
- }
- # print
- sub print_dialog {
- chdir $SETTING{'cwd'};
- my $print_op = Gtk2::PrintOperation->new;
- $print_op->set_print_settings($print_settings)
- if ( defined $print_settings );
- $print_op->signal_connect(
- begin_print => sub {
- my ( $op, $context ) = @_;
- my $settings = $op->get_print_settings;
- my $pages = $settings->get('print-pages');
- my @page_list;
- if ( $pages eq 'ranges' ) {
- my $page_set = Set::IntSpan->new;
- my $ranges = $settings->get('page-ranges');
- for ( split ',', $ranges ) {
- $page_set->I($_);
- }
- for ( 0 .. $#{ $slist->{data} } ) {
- push @page_list, $_ if ( $page_set->member( $slist->{data}[$_][0] ) );
- }
- }
- else {
- @page_list = ( 0 .. $#{ $slist->{data} } );
- }
- $op->set_n_pages( scalar(@page_list) );
- }
- );
- $print_op->signal_connect(
- draw_page => sub {
- my ( $op, $context, $page_number ) = @_;
- my $cr = $context->get_cairo_context;
- # Context dimensions
- my $pwidth = $context->get_width;
- my $pheight = $context->get_height;
- # Image dimensions
- my $pixbuf = Gtk2::Gdk::Pixbuf->new_from_file(
- "$slist->{data}[$page_number][2]{filename}")
- ; # quotes required to prevent File::Temp object being clobbered
- my $iwidth = $pixbuf->get_width;
- my $iheight = $pixbuf->get_height;
- # Scale context to fit image
- my $scale = $pwidth / $iwidth;
- $scale = $pheight / $iheight if ( $pheight / $iheight < $scale );
- $cr->scale( $scale, $scale );
- # Set source pixbuf
- Gtk2::Gdk::Cairo::Context::set_source_pixbuf( $cr, $pixbuf, 0, 0 );
- # Paint
- $cr->paint;
- return;
- }
- );
- my $res = $print_op->run( 'print-dialog', $window );
- $print_settings = $print_op->get_print_settings if ( $res eq 'apply' );
- chdir $session;
- return;
- }
- # Cut the selection
- sub cut_selection {
- if ( $slist->has_focus ) {
- copy_selection();
- delete_pages();
- }
- # elsif ($textview -> has_focus) {
- # my $clipboard = Gtk2::Clipboard->get(Gtk2::Gdk->SELECTION_CLIPBOARD);
- # $textbuffer->cut_clipboard ($clipboard, TRUE)
- # }
- return;
- }
- # Copy the selection
- sub copy_selection {
- if ( $slist->has_focus ) {
- undef @clipboard;
- my @pages = $slist->get_selected_indices;
- for my $page (@pages) {
- my @copy = map { [@$_] } $slist->{data}[$page];
- push @clipboard, @copy;
- }
- }
- # elsif ($textview -> has_focus) {
- # my $clipboard = Gtk2::Clipboard->get(Gtk2::Gdk->SELECTION_CLIPBOARD);
- # $textbuffer->copy_clipboard ($clipboard)
- # }
- return;
- }
- # Paste the selection
- sub paste_selection {
- # We only add pages if the list of thumbnails has the focus
- if ( $window->get_focus eq $slist ) {
- my @page = $slist->get_selected_indices;
- # Create a new image file for each page in the clipboard
- for (@clipboard) {
- my $suffix;
- $suffix = $1 if ( $_->[2]{filename} =~ /(\.\w*)$/x );
- my $new = File::Temp->new( DIR => $session, SUFFIX => $suffix );
- # stringify filename to prevent copy from mangling it
- unless ( copy( "$_->[2]{filename}", $new ) ) {
- show_message_dialog( $window, 'error', 'close',
- $d->get( sprintf("Error pasting image - %s"), $! ) );
- return;
- }
- $_->[2]{filename} = $new;
- }
- # Block the row-changed signal whilst adding the scan (row) and sorting it.
- $slist->get_model->signal_handler_block( $slist->{row_changed_signal} );
- # If a page is selected, then insert the pasted page immediately afterwards
- if (@page) {
- splice @{ $slist->{data} }, $page[0] + 1, 0, @clipboard;
- @page = ( $page[0] + 1 );
- }
- # Otherwise append the page to the end
- else {
- push @{ $slist->{data} }, @clipboard;
- @page = ( $#{ $slist->{data} } - $#clipboard );
- }
- $slist->renumber;
- $slist->get_model->signal_handler_unblock( $slist->{row_changed_signal} );
- # Select new page, deselecting others. This fires the select callback,
- # displaying the page
- $slist->get_selection->unselect_all;
- $slist->select(@page);
- update_uimanager();
- }
- # elsif ($textview -> has_focus) {
- # my $clipboard = Gtk2::Clipboard->get(Gtk2::Gdk->SELECTION_CLIPBOARD);
- # $textbuffer->paste_clipboard ($clipboard, undef, TRUE)
- # }
- return;
- }
- # Delete the selected scans
- sub delete_pages {
- # Update undo/redo buffers
- take_snapshot();
- my @pages = $slist->get_selected_indices;
- my @page = @pages;
- $slist->get_selection->signal_handler_block(
- $slist->{selection_changed_signal} );
- while (@pages) {
- splice @{ $slist->{data} }, $pages[0], 1;
- @pages = $slist->get_selected_indices;
- }
- $slist->get_selection->signal_handler_unblock(
- $slist->{selection_changed_signal} );
- # Select nearest page to last current page
- if ( @{ $slist->{data} } and @page ) {
- # Select just the first one
- @page = ( $page[0] );
- $page[0] = $#{ $slist->{data} } if ( $page[0] > $#{ $slist->{data} } );
- $slist->select(@page);
- }
- # Select nothing
- elsif ( @{ $slist->{data} } ) {
- $slist->select;
- }
- # No pages left, and having blocked the selection_changed_signal, we've got to clear the image
- else {
- $view->set_pixbuf(undef);
- $scwin_buffer->remove($_) for ( $scwin_buffer->get_children );
- }
- # Reset start page in scan dialog
- reset_start();
- update_uimanager();
- return;
- }
- # Reset start page number after delete or new
- sub reset_start {
- if ( defined $spin_buttons ) {
- if ( $#{ $slist->{data} } > -1 ) {
- my $start_page = $spin_buttons->get_value;
- my $step = $spin_buttoni->get_value;
- $spin_buttons->set_value( $slist->{data}[ $#{ $slist->{data} } ][0] + $step )
- if ( $start_page > $slist->{data}[ $#{ $slist->{data} } ][0] + $step );
- }
- else {
- $spin_buttons->set_value(1);
- }
- }
- elsif ( defined $windows2 ) {
- if ( $#{ $slist->{data} } > -1 ) {
- my $start_page = $windows2->get('page-number-start');
- my $step = $windows2->get('page-number-increment');
- $windows2->set( 'page-number-start',
- $slist->{data}[ $#{ $slist->{data} } ][0] + $step )
- if ( $start_page > $slist->{data}[ $#{ $slist->{data} } ][0] + $step );
- }
- else {
- $windows2->set( 'page-number-start', 1 );
- }
- }
- return;
- }
- # Select all scans
- sub select_all {
- # if ($textview -> has_focus) {
- # my ($start, $end) = $textbuffer->get_bounds;
- # $textbuffer->select_range ($start, $end);
- # }
- # else {
- $slist->get_selection->select_all;
- # }
- return;
- }
- # Select all odd(0) or even(1) scans
- sub select_odd_even {
- my $odd = shift;
- my @selection;
- for ( 0 .. $#{ $slist->{data} } ) {
- push @selection, $_ if ( $slist->{data}[$_][0] % 2 xor $odd );
- }
- $slist->get_selection->unselect_all;
- $slist->select(@selection);
- return;
- }
- sub select_modified_since_ocr {
- my @selection;
- foreach my $page ( 0 .. $#{ $slist->{data} } ) {
- my $dirty_time = $slist->{data}[$page][2]{dirty_time};
- my $ocr_flag = $slist->{data}[$page][2]{ocr_flag};
- my $ocr_time = $slist->{data}[$page][2]{ocr_time};
- $dirty_time = defined($dirty_time) ? $dirty_time : 0;
- $ocr_time = defined($ocr_time) ? $ocr_time : 0;
- push @selection, $_ if ( $ocr_flag and ( $ocr_time le $dirty_time ) );
- }
- $slist->get_selection->unselect_all;
- $slist->select(@selection);
- return;
- }
- # Select pages with no ocr output
- sub select_no_ocr {
- my @selection;
- for ( 0 .. $#{ $slist->{data} } ) {
- push @selection, $_ unless ( defined $slist->{data}[$_][2]{hocr} );
- }
- $slist->get_selection->unselect_all;
- $slist->select(@selection);
- return;
- }
- # Clear the OCR output from selected pages
- sub clear_ocr {
- # Update undo/redo buffers
- take_snapshot();
- # Remove the existing canvas
- $scwin_buffer->remove($_) for ( $scwin_buffer->get_children );
- my @selection = $slist->get_selected_indices;
- for (@selection) {
- delete $slist->{data}[$_][2]{hocr};
- }
- return;
- }
- # Analyse and select blank pages
- sub analyse_select_blank {
- analyse( 1, 0 );
- return;
- }
- # Select blank pages
- sub select_blank_pages {
- $slist->get_selection->unselect_all;
- foreach my $page ( 0 .. $#{ $slist->{data} } ) {
- $logger->info("smp page: $page");
- my $dirty_time = $slist->{data}[$page][2]{dirty_time};
- my $analyse_time = $slist->{data}[$page][2]{analyse_time};
- $dirty_time = defined($dirty_time) ? $dirty_time : 0;
- $analyse_time = defined($analyse_time) ? $analyse_time : 0;
- if ( $analyse_time le $dirty_time ) {
- $logger->warn(
- $d->get(
- sprintf(
- "Page %d probably needs to be re-Analysed. Try Update or Analyse.",
- $page + 1 )
- )
- );
- next;
- }
- #compare Std Dev to threshold
- if ( $slist->{data}[$page][2]{std_dev} <= $SETTING{'Blank threshold'} ) {
- $slist->select($page);
- $logger->info("Selecting blank page");
- }
- $logger->info( "StdDev: "
- . $slist->{data}[$page][2]{std_dev}
- . " threshold: "
- . $SETTING{'Blank threshold'} );
- }
- return;
- }
- # Analyse and select dark pages
- sub analyse_select_dark {
- analyse( 0, 1 );
- return;
- }
- # Select dark pages
- sub select_dark_pages {
- $slist->get_selection->unselect_all;
- $logger->info("Checking for dark pages now, is analysis done?");
- foreach my $page ( 0 .. $#{ $slist->{data} } ) {
- $logger->info("smp page: $page");
- my $dirty_time = $slist->{data}[$page][2]{dirty_time};
- my $analyse_time = $slist->{data}[$page][2]{analyse_time};
- $dirty_time = defined($dirty_time) ? $dirty_time : 0;
- $analyse_time = defined($analyse_time) ? $analyse_time : 0;
- if ( $analyse_time le $dirty_time ) {
- $logger->warn(
- $d->get(
- sprintf(
- "Page %d probably needs to be re-Analysed. Try Update or Analyse.",
- $page + 1 )
- )
- );
- next;
- }
- #compare Mean to threshold
- if ( $slist->{data}[$page][2]{mean} <= $SETTING{'Dark threshold'} ) {
- $slist->select($page);
- $logger->info("Selecting dark page");
- }
- }
- return;
- }
- # Display about dialog
- sub about {
- use utf8;
- my $about = Gtk2::AboutDialog->new;
- # Gtk2::AboutDialog->set_url_hook ($func, $data=undef);
- # Gtk2::AboutDialog->set_email_hook ($func, $data=undef);
- $about->set_program_name($prog_name);
- $about->set_version($version);
- my $authors = <<'EOS';
- Frederik Elwert
- Klaus Ethgen
- Andy Fingerhut
- Leon Fisk
- John Goerzen
- David Hampton
- Sascha Hunold
- Matthijs Kooijman
- Chris Mayo
- Torsten Schönfeld
- Roy Shahbazian
- Jarl Stefansson
- Jakub Wilk
- EOS
- $about->set_authors( "Jeff Ratcliffe\n\n"
- . $d->get('Patches gratefully received from:')
- . "\n$authors" );
- $about->set_comments( $d->get('To aid the scan-to-PDF process') );
- $about->set_copyright( $d->get('Copyright 2006--2013 Jeffrey Ratcliffe') );
- my $licence = <<'EOS';
- gscan2pdf --- to aid the scan to PDF or DjVu process
- Copyright 2006 -- 2013 Jeffrey Ratcliffe <[email protected]>
- This program is free software: you can redistribute it and/or modify
- it under the terms of the version 3 GNU General Public License as
- published by the Free Software Foundation.
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
- You should have received a copy of the GNU General Public License
- along with this program. If not, see <http://www.gnu.org/licenses/>.
- EOS
- $about->set_license($licence);
- $about->set_website('http://gscan2pdf.sf.net');
- my $translators =
- <<'EOS'; # inverted commas required around EOS because of UTF-8 in $translators
- Constantine Mousafiris
- Shushi Kurose
- vishal goswami
- Prescott_SK
- Diego Verga
- Herczeg Zsolt
- stavros kakaliantis
- Zbyněk Schwarz
- Wang Dianjin
- Wojciech Górnaś
- Arthur Rodrigues
- Utku BERBEROĞLU
- Dennis Baudys
- tzem
- Daniel Dietrich
- Daniel
- Adolfo Jayme Barrientos
- Christoph Steinbrink
- Igor Zubarev
- EOS
- $about->set_translator_credits($translators);
- $about->set_artists('lodp');
- $about->run;
- $about->destroy;
- return;
- }
- # Check that a command exists
- sub check_command {
- return system("which $_[0] >/dev/null 2>/dev/null") == 0 ? TRUE : FALSE;
- }
- # Rescan device-dependent scan options
- sub rescan_options {
- my ($device) = @_;
- # Ghost the scan button whilst options being updated
- $scan_button->set_sensitive(FALSE) if ( defined $scan_button );
- # Empty $vboxd first
- foreach ( $vboxd->get_children ) {
- $_->destroy;
- }
- # Get the options from the cache if defined
- my $mode;
- if ( defined $SETTING{mode} ) {
- $mode = $SETTING{mode};
- }
- else {
- $mode = 'default';
- }
- if ( $SETTING{'cache options'} and defined $SETTING{cache}{$device}{$mode} ) {
- my $options = options2hash( $SETTING{cache}{$device}{$mode} );
- my $response = 'cancel';
- unless ( $options->num_options ) {
- $response = show_message_dialog(
- $window,
- 'question',
- 'ok-cancel',
- $d->get(
- "The options cache seems to be empty. Reload device-dependent options?")
- );
- }
- if ( $response eq 'cancel' ) {
- parse_options( $device, $options );
- return;
- }
- }
- if ($test) {
- my $i = 0;
- $i++ while ( $device[$i] ne $device );
- # check if we have output for the mode
- my $filename = $test->{file}[$i];
- $filename .= ".$SETTING{mode}"
- if ( defined( $SETTING{mode} ) and -e "$filename.$SETTING{mode}" );
- # Slurp it from file
- my $output = Gscan2pdf::Document::slurp($filename);
- $vboxd->hide_all; # merely here for consistency with normal operation
- parse_options( $device, options2hash($output) );
- }
- else {
- # 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;
- Gscan2pdf::Frontend::CLI->find_scan_options(
- prefix => $SETTING{'scan prefix'},
- frontend => $SETTING{frontend},
- device => $device,
- mode => $SETTING{mode},
- running_callback => sub {
- $pbar->pulse;
- },
- finished_callback => sub {
- my ($output) = @_;
- $pbar->destroy;
- $vboxd->hide_all;
- $logger->info($output);
- parse_options( $device, options2hash($output) );
- },
- error_callback => sub {
- my ($message) = @_;
- show_message_dialog( $window, 'error', 'close', $message );
- $pbar->destroy;
- $vboxd->hide_all;
- $logger->warn($message);
- },
- );
- }
- return;
- }
- sub parse_options {
- my ( $device, $options ) = @_;
- # Dig out the paper sizes
- my (
- $x, $y, $l, $t,
- $h, $w, $spin_buttonx, $spin_buttony,
- $spin_buttonl, $spin_buttont, $spin_buttonh, $spin_buttonw,
- );
- $x = $options->by_name('x')->{constraint}{max}
- if ( defined $options->by_name('x') );
- $y = $options->by_name('y')->{constraint}{max}
- if ( defined $options->by_name('y') );
- $l = $options->by_name('l')->{constraint}{max}
- if ( defined $options->by_name('l') );
- $t = $options->by_name('t')->{constraint}{max}
- if ( defined $options->by_name('t') );
- # Set device-dependent options
- # Dummy entries so that something is returned:
- %ddo = (
- 'Paper size' => { string => $d->get('Paper size'), },
- 'x' => { string => $d->get('Width') },
- 'y' => { string => $d->get('Height') },
- 'l' => { string => $d->get('Left') },
- 't' => { string => $d->get('Top') },
- );
- 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( $ddo{'Paper size'}{string} );
- $hboxp->pack_start( $labelp, FALSE, FALSE, 0 );
- $combobp = Gtk2::ComboBox->new_text;
- $combobp->append_text( $d->get('Manual') );
- $combobp->append_text( $d->get('Edit') );
- $tooltips->set_tip( $combobp, $d->get('Selects or edits the paper size') );
- $hboxp->pack_end( $combobp, FALSE, FALSE, 0 );
- # Define manual paper here to reference it in callback
- $vbox_manual_paper = Gtk2::VBox->new;
- $vboxd->pack_start( $vbox_manual_paper, FALSE, FALSE, 0 );
- }
- # Add remaining options
- for ( 0 .. $options->num_options - 1 ) {
- my $option = $options->by_index($_);
- if ( defined( $pddo{ $option->{name} } ) ) {
- my ( $widget, $hbox ) = add_widget_from_hash($option);
- if ( $option->{name} =~ /^page-?width$/x ) {
- $w = $option->{constraint}{max};
- $spin_buttonw = $widget;
- $vbox_manual_paper->pack_start( $hbox, TRUE, TRUE, 0 );
- }
- elsif ( $option->{name} =~ /^page-?height$/x ) {
- $h = $option->{constraint}{max};
- $spin_buttonh = $widget;
- $vbox_manual_paper->pack_start( $hbox, TRUE, TRUE, 0 );
- }
- else {
- $vboxd->pack_start( $hbox, TRUE, TRUE, 0 );
- }
- $batch_scan = $widget if ( $option->{name} eq 'batch-scan' );
- # If an ADF isn't selected, then we don't want to scan all pages
- if ( $option->{name} eq 'source' ) {
- $widget->signal_connect(
- changed => sub {
- my $text = $widget->get_active_text;
- if ( defined($text)
- and get_value( \%ddo, $option->{name}, $text ) =~ /(flatbed|normal)/ix )
- {
- $bscanall->set_sensitive(FALSE);
- $bscannum->set_active(TRUE);
- }
- else {
- $bscanall->set_sensitive(TRUE);
- if ( defined($text)
- and get_value( \%ddo, $option->{name}, $text ) =~
- /(ADF|Automatic\ Document\ Feeder)/ix )
- {
- $batch_scan->set_active(0) if ( defined $batch_scan );
- }
- }
- if ( defined($text)
- and get_value( \%ddo, $option->{name}, $text ) =~ /duplex/i )
- {
- $duplex = TRUE;
- $frames->set_sensitive(FALSE);
- }
- else {
- $duplex = FALSE;
- $frames->set_sensitive(TRUE);
- }
- }
- );
- }
- # If the mode is changed, update the options
- if ( $option->{name} eq 'mode' ) {
- $widget->signal_connect(
- changed => sub {
- $SETTING{ $option->{name} } =
- get_value( \%ddo, $option->{name}, $widget->get_active_text );
- update_options($device);
- }
- );
- }
- }
- }
- # Add paper size after rest of options in case of pagewidth|height
- if ( defined($x) and defined($y) ) {
- $x = $w if ( defined $w );
- $y = $h if ( defined $h );
- my @info = (
- {
- label => $d->get('Width'),
- tooltip => $d->get('Width of scan area'),
- letter => 'x',
- dimension => $x
- },
- {
- label => $d->get('Height'),
- tooltip => $d->get('Height of scan area'),
- letter => 'y',
- dimension => $y
- },
- {
- label => $d->get('Left'),
- tooltip => $d->get('Top-left x position of scan area'),
- letter => 'l',
- dimension => $l
- },
- {
- label => $d->get('Top'),
- tooltip => $d->get('Top-left y position of scan area'),
- letter => 't',
- dimension => $t
- }
- );
- ( $spin_buttonx, $spin_buttony, $spin_buttonl, $spin_buttont ) =
- add_spinbutton( $vbox_manual_paper, @info );
- $combobp->signal_connect(
- changed => sub {
- if ( $combobp->get_active_text eq $d->get('Edit') ) {
- edit_paper($options);
- }
- elsif ( $combobp->get_active_text eq $d->get('Manual') ) {
- $vbox_manual_paper->show_all;
- }
- else {
- my $paper = $combobp->get_active_text;
- $spin_buttonx->set_value( $SETTING{Paper}{$paper}{x} );
- $spin_buttony->set_value( $SETTING{Paper}{$paper}{y} );
- $spin_buttont->set_value( $SETTING{Paper}{$paper}{t} );
- $spin_buttonl->set_value( $SETTING{Paper}{$paper}{l} );
- if ( defined($spin_buttonh) and defined($spin_buttonw) ) {
- $spin_buttonw->set_value( $SETTING{Paper}{$paper}{x} );
- $spin_buttonh->set_value( $SETTING{Paper}{$paper}{y} );
- }
- Glib::Idle->add( sub { $vbox_manual_paper->hide; } );
- }
- }
- );
- add_paper( $combobp, $options );
- }
- # Show window
- $vboxd->show_all;
- # Give the GUI a chance to catch up before resizing.
- Glib::Idle->add( sub { $windows->resize( 100, 100 ); } );
- Gscan2pdf::Dialog::Scan::Sane::set_combobox_by_text( $combobp,
- $SETTING{'Paper size'} );
- # Unghost the scan button
- if ( defined $scan_button ) {
- $scan_button->set_sensitive(TRUE);
- $scan_button->grab_focus;
- }
- return;
- }
- # take an option from the options hash and create the appropriate widget
- sub add_widget_from_hash {
- my ($option) = @_;
- # Dig out of possible options
- $ddo{ $option->{name} }{string} = $pddo{ $option->{name} }{string};
- # HBox for option
- my $hbox = Gtk2::HBox->new;
- $hbox->set_sensitive(FALSE) if ( $option->{default} =~ /inactive/ );
- # Label
- my $label = Gtk2::Label->new( $ddo{ $option->{name} }{string} );
- $hbox->pack_start( $label, FALSE, FALSE, 0 );
- # Widget
- my $widget;
- # SpinButton
- if ( defined $option->{constraint}{max} ) {
- my $step = 1;
- $step = $option->{constraint}{step}
- if ( defined $option->{constraint}{step} );
- $widget = Gtk2::SpinButton->new_with_range( $option->{constraint}{min},
- $option->{constraint}{max}, $step );
- # Set the default
- $widget->set_value( $option->{default} )
- unless ( $option->{default} =~ /inactive/ );
- }
- # ComboBox
- else {
- $widget = Gtk2::ComboBox->new_text;
- my $index = default2index( $option->{default}, @{ $option->{values} } );
- foreach ( @{ $option->{values} } ) {
- add_to_options( $option->{name}, $_ );
- $widget->append_text( $ddo{ $option->{name} }{values}{$_} );
- }
- # Set the default
- $widget->set_active($index) if ( defined $index );
- }
- $hbox->pack_end( $widget, FALSE, FALSE, 0 );
- $tooltips->set_tip( $widget, $option->{tip} );
- return ( $widget, $hbox );
- }
- # Helper function to get manual paper size spinbuttons on dialog
- sub add_spinbutton {
- my ( $vbox, @info ) = @_;
- my @stack;
- for (@info) {
- my $hbox = Gtk2::HBox->new;
- $vbox->pack_start( $hbox, FALSE, FALSE, 0 );
- my $label = Gtk2::Label->new( $_->{label} );
- $hbox->pack_start( $label, FALSE, FALSE, 0 );
- my $spinbutton = Gtk2::SpinButton->new_with_range( 0, $_->{dimension}, 1 );
- $SETTING{ $_->{letter} } = $_->{dimension}
- unless ( defined $SETTING{ $_->{letter} } );
- $spinbutton->set_value( $SETTING{ $_->{letter} } );
- $tooltips->set_tip( $spinbutton, $_->{tooltip} );
- $hbox->pack_end( $spinbutton, FALSE, FALSE, 0 );
- push @stack, $spinbutton;
- }
- return @stack;
- }
- # Paper editor
- sub edit_paper {
- my ($options) = @_;
- if ( defined $windowd ) {
- $windowd->present;
- return;
- }
- $windowd = Gscan2pdf::Dialog->new(
- 'transient-for' => $windows,
- title => $d->get('Edit paper size'),
- border_width => $border_width
- );
- my $vbox1 = $windowd->get('vbox');
- # Buttons for SimpleList
- my $hboxl = Gtk2::HBox->new;
- $vbox1->pack_start( $hboxl, FALSE, FALSE, 0 );
- my $vboxb = Gtk2::VBox->new;
- $hboxl->pack_start( $vboxb, FALSE, FALSE, 0 );
- my $dbutton = Gtk2::Button->new_from_stock('gtk-add');
- $vboxb->pack_start( $dbutton, TRUE, FALSE, 0 );
- my $rbutton = Gtk2::Button->new_from_stock('gtk-remove');
- $vboxb->pack_end( $rbutton, TRUE, FALSE, 0 );
- # Set up a SimpleList
- my $paper_list = Gtk2::Ex::Simple::List->new(
- $d->get('Name') => 'text',
- $d->get('Width') => 'int',
- $d->get('Height') => 'int',
- $d->get('Left') => 'int',
- $d->get('Top') => 'int'
- );
- for ( keys %{ $SETTING{Paper} } ) {
- push @{ $paper_list->{data} },
- [
- $_, $SETTING{Paper}{$_}{x}, $SETTING{Paper}{$_}{y},
- $SETTING{Paper}{$_}{l}, $SETTING{Paper}{$_}{t}
- ];
- }
- # Set everything to be editable
- for ( 0 .. 4 ) {
- $paper_list->set_column_editable( $_, TRUE );
- }
- $paper_list->get_column(0)->set_sort_column_id(0);
- # Add button callback
- $dbutton->signal_connect(
- clicked => sub {
- my @rows = $paper_list->get_selected_indices;
- $rows[0] = 0 unless (@rows);
- my $name = $paper_list->{data}[ $rows[0] ][0];
- my $paper_version = 2;
- my $i = 0;
- while ( $i < @{ $paper_list->{data} } ) {
- if ( $paper_list->{data}[$i][0] eq "$name ($paper_version)" ) {
- ++$paper_version;
- $i = 0;
- }
- else {
- ++$i;
- }
- }
- my @line = [
- "$name ($paper_version)",
- $paper_list->{data}[ $rows[0] ][1],
- $paper_list->{data}[ $rows[0] ][2],
- $paper_list->{data}[ $rows[0] ][3],
- $paper_list->{data}[ $rows[0] ][4]
- ];
- splice @{ $paper_list->{data} }, $rows[0] + 1, 0, @line;
- }
- );
- # Remove button callback
- $rbutton->signal_connect(
- clicked => sub {
- my @rows = $paper_list->get_selected_indices;
- if ( $#rows == $#{ $paper_list->{data} } ) {
- show_message_dialog( $windowd, 'error', 'close',
- $d->get('Cannot delete all paper sizes') );
- }
- else {
- while (@rows) {
- splice @{ $paper_list->{data} }, shift(@rows), 1;
- }
- }
- }
- );
- # Set-up the callback to check that no two Names are the same
- $paper_list->get_model->signal_connect(
- 'row-changed' => sub {
- my ( $model, $path, $iter ) = @_;
- for ( my $i = 0 ; $i < @{ $paper_list->{data} } ; $i++ ) {
- if ( $i != $path->to_string
- and $paper_list->{data}[ $path->to_string ][0] eq
- $paper_list->{data}[$i][0] )
- {
- my $name = $paper_list->{data}[ $path->to_string ][0];
- my $paper_version = 2;
- if ( $name =~ /(.*)\ \((\d+)\)/x ) {
- $name = $1;
- $paper_version = $2 + 1;
- }
- $paper_list->{data}[ $path->to_string ][0] = "$name ($paper_version)";
- return;
- }
- }
- }
- );
- $hboxl->pack_end( $paper_list, FALSE, FALSE, 0 );
- # Buttons
- my $hboxb = _hbox_with_apply_cancel_buttons(
- sub {
- delete $SETTING{Paper};
- for ( my $i = 0 ; $i < @{ $paper_list->{data} } ; $i++ ) {
- $SETTING{Paper}{ $paper_list->{data}[$i][0] }{x} =
- $paper_list->{data}[$i][1];
- $SETTING{Paper}{ $paper_list->{data}[$i][0] }{y} =
- $paper_list->{data}[$i][2];
- $SETTING{Paper}{ $paper_list->{data}[$i][0] }{l} =
- $paper_list->{data}[$i][3];
- $SETTING{Paper}{ $paper_list->{data}[$i][0] }{t} =
- $paper_list->{data}[$i][4];
- }
- # Update list in Gscan2pdf::Document so that it can be used by get_resolution
- # FIXME: make sure that this is updated for all scan dialogs
- Gscan2pdf::Document->set_paper_sizes( $SETTING{Paper} );
- $combobp->remove_text(0) while ( $combobp->get_active > 1 );
- my @ignored = add_paper( $combobp, $options );
- show_message_dialog(
- $windowd,
- 'warning',
- 'close',
- $d->get(
- 'The following paper sizes are too big to be scanned by the selected device:'
- )
- . ' '
- . join( ', ', @ignored )
- ) if (@ignored);
- my @rows = $paper_list->get_selected_indices;
- $rows[0] = 0 unless (@rows);
- $SETTING{'Paper size'} = $paper_list->{data}[ $rows[0] ][0];
- Gscan2pdf::Dialog::Scan::Sane::set_combobox_by_text( $combobp,
- $SETTING{'Paper size'} );
- $windowd->hide;
- },
- sub {
- my @rows = $paper_list->get_selected_indices;
- $rows[0] = 0 unless (@rows);
- $SETTING{'Paper size'} = $paper_list->{data}[ $rows[0] ][0];
- Gscan2pdf::Dialog::Scan::Sane::set_combobox_by_text( $combobp,
- $SETTING{'Paper size'} );
- $windowd->hide;
- }
- );
- $vbox1->pack_start( $hboxb, FALSE, FALSE, 0 );
- $windowd->show_all;
- return;
- }
- # Add paper size to combobox if scanner large enough
- sub add_paper {
- my ( $combobox, $options ) = @_;
- my $tolerance = 1;
- my (@ignored);
- for ( keys %{ $SETTING{Paper} } ) {
- if ( $options->supports_paper( $SETTING{Paper}{$_}, $tolerance ) ) {
- $combobox->prepend_text($_);
- }
- else {
- push @ignored, $_;
- }
- }
- return @ignored;
- }
- # Update device-dependent scan options having selected a new mode
- sub update_options {
- my ($device) = @_;
- # Empty $vboxd first
- foreach ( $vboxd->get_children ) {
- $_->hide;
- }
- my $mode;
- if ( defined $SETTING{mode} ) {
- $mode = $SETTING{mode};
- }
- else {
- $mode = 'default';
- }
- if ( $SETTING{'cache options'} and defined $SETTING{cache}{$device}{$mode} ) {
- my $options = options2hash( $SETTING{cache}{$device}{$mode} );
- my $response = 'cancel';
- unless ( $options->num_options ) {
- $response = show_message_dialog(
- $window,
- 'question',
- 'ok-cancel',
- $d->get(
- "The options cache seems to be empty. Reload device-dependent options?")
- );
- }
- if ( $response eq 'cancel' ) {
- update_options_hash($options);
- return;
- }
- }
- if ($test) {
- my $i = 0;
- $i++ while ( $device[$i] ne $device );
- # check if we have output for the new mode
- my $filename = $test->{file}[$i];
- $filename .= ".$SETTING{mode}" if ( -e "$filename.$SETTING{mode}" );
- # Slurp it from file
- my $output = Gscan2pdf::Document::slurp($filename);
- $vboxd->hide_all; # merely here for consistency with normal operation
- update_options_hash( options2hash($output) );
- }
- else {
- # 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 );
- $pbar->show;
- Gscan2pdf::Frontend::CLI->find_scan_options(
- prefix => $SETTING{'scan prefix'},
- frontend => $SETTING{frontend},
- device => $device,
- mode => $SETTING{mode},
- running_callback => sub {
- $pbar->pulse;
- },
- finished_callback => sub {
- my ($output) = @_;
- $pbar->destroy;
- $vboxd->hide_all;
- $logger->info($output);
- update_options_hash( options2hash($output) );
- }
- );
- }
- return;
- }
- # return the options, updated with the user defaults
- sub options2hash {
- my ($output) = @_;
- my $options = Gscan2pdf::Scanner::Options->new_from_data($output);
- $logger->debug( Dumper($options) );
- for ( 0 .. $options->num_options - 1 ) {
- my $option = $options->by_index($_);
- # Set default from config
- if (
- defined( $SETTING{ $option->{name} } )
- and $option->{default} ne 'inactive'
- # only set the default if it is an option
- and (
- defined( $option->{constraint}{max} )
- or defined(
- default2index( $SETTING{ $option->{name} }, @{ $option->{values} } )
- )
- )
- )
- {
- $option->{default} = $SETTING{ $option->{name} };
- }
- # in the case that we've switched devices and the new device doesn't support
- # the new mode
- elsif ( $option->{name} eq 'mode' ) {
- delete $SETTING{ $option->{name} };
- }
- }
- # Cache options
- my $mode;
- if ( defined $SETTING{mode} ) {
- $mode = $SETTING{mode};
- }
- else {
- $mode = 'default';
- }
- # We could cache the hash, but we'd have to turn off MergeDuplicateOptions
- $SETTING{cache}{ $SETTING{device} }{$mode} = $output
- if ( $SETTING{'cache options'} and defined $SETTING{device} );
- return $options;
- }
- # walk the widget tree and update them from the hash
- sub update_options_hash {
- my ($options) = @_;
- my ( $hboxm, $hboxp );
- foreach my $hbox ( $vboxd->get_children ) {
- my $key;
- if ( $hbox->isa('Gtk2::HBox') ) {
- foreach my $widget ( $hbox->get_children ) {
- my $opt;
- $opt = $options->by_name($key) if ( defined $key );
- if ( $widget->isa('Gtk2::Label') ) { ## no critic (ProhibitCascadingIfElse)
- $key = get_key( \%ddo, $widget->get_label );
- }
- elsif (
- $widget->isa('Gtk2::ComboBox')
- # to prevent recursion
- and $key ne 'mode'
- and $key ne 'Paper size'
- and defined($opt)
- and defined( $opt->{values} )
- )
- {
- # Empty the list
- $widget->get_model->clear;
- # Fill it again
- my $index = default2index( $opt->{val}, @{ $opt->{constraint} } );
- foreach ( @{ $opt->{constraint} } ) {
- # in case the option was new and therefore not in ddo before
- add_to_options( $key, $_ );
- $widget->append_text( $ddo{$key}{values}{$_} );
- }
- # Set the default
- $widget->set_active($index) if ( defined $index );
- # Set the ghosting
- if ( $opt->{val} =~ /inactive/ ) {
- $hbox->set_sensitive(FALSE);
- }
- else {
- $hbox->set_sensitive(TRUE);
- }
- $hbox->show_all;
- }
- elsif ( $key eq 'mode' ) {
- $hboxm = $hbox;
- }
- elsif ( $key eq 'Paper size' ) {
- $hboxp = $hbox;
- }
- elsif ( $widget->isa('Gtk2::SpinButton')
- and defined($opt)
- and defined( $opt->{constraint}{max} ) )
- {
- $widget->set_range( $opt->{constraint}{min}, $opt->{constraint}{max} );
- # Set the default
- if ( $opt->{val} =~ /inactive/ ) {
- $hbox->set_sensitive(FALSE);
- }
- else {
- $widget->set_value( $opt->{val} );
- $hbox->set_sensitive(TRUE);
- }
- $hbox->show_all;
- }
- # If an option no longer available, make it insensitive so that it doesn't
- # get passed to scanimage or scanadf
- else {
- $hbox->set_sensitive(FALSE);
- }
- }
- # Delete option from hash to see at the end which are left
- $options->delete_by_name($key) if ( defined $options->by_name($key) );
- }
- }
- # Add any new options
- for ( 0 .. $options->num_options - 1 ) {
- my $option = $options->by_index($_);
- if (
- defined($option)
- and defined( $pddo{ $option->{name} } )
- and $option->{name} !~ /^page-?(?:width|height)/x
- )
- {
- my ( $widget, $hbox ) = add_widget_from_hash($option);
- $vboxd->pack_start( $hbox, TRUE, TRUE, 0 );
- $hbox->show_all;
- }
- }
- $vbox_manual_paper->show_all
- if ( $combobp->get_active_text eq $d->get('Manual') );
- $hboxm->show_all if ( defined $hboxm );
- $hboxp->show_all if ( defined $hboxp );
- $vboxd->show;
- return;
- }
- # return the position that a string occurs in an array
- sub default2index {
- my ( $default, @array ) = @_;
- for ( my $i = 0 ; $i < @array ; $i++ ) {
- return $i if ( $array[$i] eq $default );
- }
- return;
- }
- # Dialog for renumber
- sub renumber_dialog {
- if ( defined $windowrn ) {
- $windowrn->present;
- return;
- }
- $windowrn = Gscan2pdf::Dialog::Renumber->new(
- 'transient-for' => $window,
- document => $slist,
- 'hide-on-delete' => FALSE,
- border_width => $border_width
- );
- # Update undo/redo buffers
- $windowrn->signal_connect(
- 'before-renumber' => sub {
- take_snapshot();
- }
- );
- $windowrn->show_all;
- return;
- }
- # Helper function to convert an array of indices into an array of Gscan2pdf::Page objects
- sub indices2pages {
- my @indices = @_;
- my @pages;
- for (@indices) {
- push @pages, $slist->{data}[$_][2];
- }
- return @pages;
- }
- # Rotate selected images
- sub rotate {
- my ( $angle, $pagelist, $callback ) = @_;
- # Update undo/redo buffers
- take_snapshot();
- for my $page ( @{$pagelist} ) {
- my ( $signal, $pid );
- $pid = $slist->rotate(
- angle => $angle,
- page => $page,
- queued_callback => sub {
- return update_tpbar(@_);
- },
- started_callback => sub {
- my ( $thread, $process, $completed, $total ) = @_;
- $signal = setup_tpbar( $thread, $process, $completed, $total, $pid );
- return TRUE if ( defined $signal );
- },
- finished_callback => sub {
- my ( $new_page, $pending ) = @_;
- $callback->($new_page) if $callback;
- $thbox->hide_all unless $pending;
- $tcbutton->signal_handler_disconnect($signal) if ( defined $signal );
- },
- error_callback => sub {
- my ($message) = @_;
- show_message_dialog( $window, 'error', 'close', $message );
- },
- display_callback => sub {
- my ($new_page) = @_;
- display_image($new_page);
- },
- );
- }
- return;
- }
- # Analyse selected images
- sub analyse {
- my ( $select_blank, $select_dark ) = @_;
- # Update undo/redo buffers
- take_snapshot();
- foreach my $i ( 0 .. $#{ $slist->{data} } ) {
- my $dirty_time = $slist->{data}[$i][2]{dirty_time};
- my $analyse_time = $slist->{data}[$i][2]{analyse_time};
- $dirty_time = defined($dirty_time) ? $dirty_time : 0;
- $analyse_time = defined($analyse_time) ? $analyse_time : 0;
- if ( $analyse_time le $dirty_time ) {
- $logger->info(
- "Updating: $slist->{data}[$i][0] analyse_time: $analyse_time dirty_time: $dirty_time"
- );
- my ( $signal, $pid );
- $pid = $slist->analyse(
- page => $slist->{data}[$i][2],
- queued_callback => sub {
- return update_tpbar(@_);
- },
- started_callback => sub {
- my ( $thread, $process, $completed, $total ) = @_;
- $signal = setup_tpbar( $thread, $process, $completed, $total, $pid );
- return TRUE if ( defined $signal );
- },
- finished_callback => sub {
- my ( $new_page, $pending ) = @_;
- $thbox->hide_all unless $pending;
- $tcbutton->signal_handler_disconnect($signal) if ( defined $signal );
- select_blank_pages() if $select_blank;
- select_dark_pages() if $select_dark;
- },
- error_callback => sub {
- my ($message) = @_;
- show_message_dialog( $window, 'error', 'close', $message );
- },
- );
- }
- }
- return;
- }
- # Handle right-clicks
- sub handle_clicks {
- my ( $widget, $event ) = @_;
- # $SETTING{'RMB'} = ($event->button == 3);
- #warn "rmb $SETTING{'RMB'}\n";
- if ( $event->button == 3 ) {
- if ( $widget->isa('Gtk2::ImageView') ) { # main image
- $uimanager->get_widget('/Detail_Popup')
- ->popup( undef, undef, undef, undef, $event->button, $event->time );
- }
- else { # Thumbnail simplelist
- $uimanager->get_widget('/Thumb_Popup')
- ->popup( undef, undef, undef, undef, $event->button, $event->time );
- }
- # block event propagation
- return TRUE;
- }
- # allow event propagation
- return FALSE;
- }
- # Display page selector and on apply threshold accordingly
- sub threshold {
- # $SETTING{'Page range'} = 'selected' if $SETTING{'RMB'};
- my $windowt = Gscan2pdf::Dialog->new(
- 'transient-for' => $window,
- title => $d->get('Threshold'),
- border_width => $border_width
- );
- my $vbox = $windowt->get('vbox');
- # Frame for page range
- add_page_range($vbox);
- # SpinButton for threshold
- my $hboxt = Gtk2::HBox->new;
- $vbox->pack_start( $hboxt, FALSE, TRUE, 0 );
- my $label = Gtk2::Label->new( $d->get('Threshold') );
- $hboxt->pack_start( $label, FALSE, TRUE, 0 );
- my $labelp = Gtk2::Label->new( $d->get('%') );
- $hboxt->pack_end( $labelp, FALSE, TRUE, 0 );
- my $spinbutton = Gtk2::SpinButton->new_with_range( 0, 100, 1 );
- $spinbutton->set_value( $SETTING{'threshold tool'} );
- $hboxt->pack_end( $spinbutton, FALSE, TRUE, 0 );
- # HBox for buttons
- my $hboxb = _hbox_with_apply_cancel_buttons(
- sub {
- # Update undo/redo buffers
- take_snapshot();
- $SETTING{'threshold tool'} = $spinbutton->get_value;
- my @pagelist = $slist->get_page_index( $SETTING{'Page range'} );
- my $page = 0;
- for my $i (@pagelist) {
- $page++;
- my ( $signal, $pid );
- $pid = $slist->threshold(
- threshold => $SETTING{'threshold tool'},
- page => $slist->{data}[$i][2],
- queued_callback => sub {
- return update_tpbar(@_);
- },
- started_callback => sub {
- my ( $thread, $process, $completed, $total ) = @_;
- $signal = setup_tpbar( $thread, $process, $completed, $total, $pid );
- return TRUE if ( defined $signal );
- },
- finished_callback => sub {
- my ( $new_page, $pending ) = @_;
- $thbox->hide_all unless $pending;
- $tcbutton->signal_handler_disconnect($signal) if ( defined $signal );
- },
- error_callback => sub {
- my ($message) = @_;
- show_message_dialog( $window, 'error', 'close', $message );
- },
- display_callback => sub {
- my ($new_page) = @_;
- display_image($new_page);
- },
- );
- }
- },
- sub { $windowt->destroy }
- );
- $vbox->pack_start( $hboxb, FALSE, TRUE, 0 );
- $windowt->show_all;
- return;
- }
- # Display page selector and on apply negate accordingly
- sub negate {
- # $SETTING{'Page range'} = 'selected' if $SETTING{'RMB'};
- my $windowt = Gscan2pdf::Dialog->new(
- 'transient-for' => $window,
- title => $d->get('Negate'),
- border_width => $border_width
- );
- my $vbox = $windowt->get('vbox');
- # Frame for page range
- add_page_range($vbox);
- # HBox for buttons
- my $hboxb = _hbox_with_apply_cancel_buttons(
- sub {
- # Update undo/redo buffers
- take_snapshot();
- my @pagelist = $slist->get_page_index( $SETTING{'Page range'} );
- for my $i (@pagelist) {
- my ( $signal, $pid );
- $pid = $slist->negate(
- page => $slist->{data}[$i][2],
- queued_callback => sub {
- return update_tpbar(@_);
- },
- started_callback => sub {
- my ( $thread, $process, $completed, $total ) = @_;
- $signal = setup_tpbar( $thread, $process, $completed, $total, $pid );
- return TRUE if ( defined $signal );
- },
- finished_callback => sub {
- my ( $new_page, $pending ) = @_;
- $thbox->hide_all unless $pending;
- $tcbutton->signal_handler_disconnect($signal) if ( defined $signal );
- },
- error_callback => sub {
- my ($message) = @_;
- show_message_dialog( $window, 'error', 'close', $message );
- },
- display_callback => sub {
- my ($new_page) = @_;
- display_image($new_page);
- },
- );
- }
- },
- sub { $windowt->destroy }
- );
- $vbox->pack_start( $hboxb, FALSE, TRUE, 0 );
- $windowt->show_all;
- return;
- }
- # Display page selector and on apply unsharp accordingly
- sub unsharp {
- # $SETTING{'Page range'} = 'selected' if $SETTING{'RMB'};
- my $windowum = Gscan2pdf::Dialog->new(
- 'transient-for' => $window,
- title => $d->get('Unsharp mask'),
- border_width => $border_width
- );
- my $vbox = $windowum->get('vbox');
- # Frame for page range
- add_page_range($vbox);
- # SpinButton for radius
- my $hbox = Gtk2::HBox->new;
- $vbox->pack_start( $hbox, FALSE, TRUE, 0 );
- my $label = Gtk2::Label->new( $d->get('Radius') );
- $hbox->pack_start( $label, FALSE, TRUE, 0 );
- $label = Gtk2::Label->new( $d->get('pixels') );
- $hbox->pack_end( $label, FALSE, TRUE, 0 );
- my $spinbuttonr = Gtk2::SpinButton->new_with_range( 0, 100, 1 );
- $spinbuttonr->set_value( $SETTING{'unsharp radius'} );
- $tooltips->set_tip(
- $spinbuttonr,
- $d->get(
- 'The radius of the Gaussian, in pixels, not counting the center pixel (0 = automatic).'
- )
- );
- $hbox->pack_end( $spinbuttonr, FALSE, TRUE, 0 );
- # SpinButton for sigma
- $hbox = Gtk2::HBox->new;
- $vbox->pack_start( $hbox, FALSE, TRUE, 0 );
- $label = Gtk2::Label->new( $d->get('Sigma') );
- $hbox->pack_start( $label, FALSE, TRUE, 0 );
- $label = Gtk2::Label->new( $d->get('pixels') );
- $hbox->pack_end( $label, FALSE, TRUE, 0 );
- my $spinbuttons = Gtk2::SpinButton->new_with_range( 0, 5, .1 );
- $spinbuttons->set_value( $SETTING{'unsharp sigma'} );
- $tooltips->set_tip( $spinbuttons,
- $d->get('The standard deviation of the Gaussian.') );
- $hbox->pack_end( $spinbuttons, FALSE, TRUE, 0 );
- # SpinButton for amount
- $hbox = Gtk2::HBox->new;
- $vbox->pack_start( $hbox, FALSE, TRUE, 0 );
- $label = Gtk2::Label->new( $d->get('Amount') );
- $hbox->pack_start( $label, FALSE, TRUE, 0 );
- $label = Gtk2::Label->new( $d->get('%') );
- $hbox->pack_end( $label, FALSE, TRUE, 0 );
- my $spinbuttona = Gtk2::SpinButton->new_with_range( 0, 100, 1 );
- $spinbuttona->set_value( $SETTING{'unsharp amount'} );
- $tooltips->set_tip(
- $spinbuttona,
- $d->get(
- 'The percentage of the difference between the original and the blur image that is added back into the original.'
- )
- );
- $hbox->pack_end( $spinbuttona, FALSE, TRUE, 0 );
- # SpinButton for threshold
- $hbox = Gtk2::HBox->new;
- $vbox->pack_start( $hbox, FALSE, TRUE, 0 );
- $label = Gtk2::Label->new( $d->get('Threshold') );
- $hbox->pack_start( $label, FALSE, TRUE, 0 );
- my $spinbuttont = Gtk2::SpinButton->new_with_range( 0, 1, 0.01 );
- $spinbuttont->set_value( $SETTING{'unsharp threshold'} );
- $tooltips->set_tip(
- $spinbuttont,
- $d->get(
- 'The threshold, as a fraction of QuantumRange, needed to apply the difference amount.'
- )
- );
- $hbox->pack_end( $spinbuttont, FALSE, TRUE, 0 );
- # HBox for buttons
- my $hboxb = _hbox_with_apply_cancel_buttons(
- sub {
- # Update undo/redo buffers
- take_snapshot();
- $SETTING{'unsharp radius'} = $spinbuttonr->get_value;
- $SETTING{'unsharp sigma'} = $spinbuttons->get_value;
- $SETTING{'unsharp amount'} = $spinbuttona->get_value;
- $SETTING{'unsharp threshold'} = $spinbuttont->get_value;
- my @pagelist = $slist->get_page_index( $SETTING{'Page range'} );
- for my $i (@pagelist) {
- my ( $signal, $pid );
- $pid = $slist->unsharp(
- page => $slist->{data}[$i][2],
- radius => $SETTING{'unsharp radius'},
- sigma => $SETTING{'unsharp sigma'},
- amount => $SETTING{'unsharp amount'},
- threshold => $SETTING{'unsharp threshold'},
- queued_callback => sub {
- return update_tpbar(@_);
- },
- started_callback => sub {
- my ( $thread, $process, $completed, $total ) = @_;
- $signal = setup_tpbar( $thread, $process, $completed, $total, $pid );
- return TRUE if ( defined $signal );
- },
- finished_callback => sub {
- my ( $new_page, $pending ) = @_;
- $thbox->hide_all unless $pending;
- $tcbutton->signal_handler_disconnect($signal) if ( defined $signal );
- },
- error_callback => sub {
- my ($message) = @_;
- show_message_dialog( $window, 'error', 'close', $message );
- },
- display_callback => sub {
- my ($new_page) = @_;
- display_image($new_page);
- },
- );
- }
- },
- sub { $windowum->destroy }
- );
- $windowum->show_all;
- return;
- }
- # Callback for change Gtk2::ImageView::Tool
- sub change_image_tool_cb {
- my ( $action, $current ) = @_;
- my $value = $current->get_current_value();
- my $tool = $selector;
- if ( $value == 10 ) {
- $tool = $dragger;
- }
- elsif ( $value == 30 ) {
- $tool = $painter;
- }
- $view->set_tool($tool);
- if ( $value == 20
- and defined( $SETTING{selection} )
- and @{ $SETTING{selection} } == 4 )
- {
- $selector->signal_handler_block( $selector->{selection_changed_signal} );
- $selector->set_selection(
- Gtk2::Gdk::Rectangle->new( @{ $SETTING{selection} } ) );
- $selector->signal_handler_unblock( $selector->{selection_changed_signal} );
- }
- return;
- }
- # Display page selector and on apply crop accordingly
- sub crop {
- if ( defined $windowc ) {
- $windowc->present;
- return;
- }
- # $SETTING{'Page range'} = 'selected' if $SETTING{'RMB'};
- $windowc = Gscan2pdf::Dialog->new(
- 'transient-for' => $window,
- title => $d->get('Crop'),
- 'hide-on-delete' => TRUE,
- border_width => $border_width
- );
- my $vbox = $windowc->get('vbox');
- # Frame for page range
- add_page_range($vbox);
- # SpinButton for x
- my $hbox = Gtk2::HBox->new;
- $vbox->pack_start( $hbox, FALSE, TRUE, 0 );
- my $label = Gtk2::Label->new( $d->get('x') );
- $hbox->pack_start( $label, FALSE, TRUE, 0 );
- $label = Gtk2::Label->new( $d->get('pixels') );
- $hbox->pack_end( $label, FALSE, TRUE, 0 );
- $sb_selector_x = Gtk2::SpinButton->new_with_range( 0, $current_page->{w}, 1 );
- $tooltips->set_tip( $sb_selector_x,
- $d->get('The x-position of the left hand edge of the crop.') );
- $hbox->pack_end( $sb_selector_x, FALSE, TRUE, 0 );
- # SpinButton for y
- $hbox = Gtk2::HBox->new;
- $vbox->pack_start( $hbox, FALSE, TRUE, 0 );
- $label = Gtk2::Label->new( $d->get('y') );
- $hbox->pack_start( $label, FALSE, TRUE, 0 );
- $label = Gtk2::Label->new( $d->get('pixels') );
- $hbox->pack_end( $label, FALSE, TRUE, 0 );
- $sb_selector_y = Gtk2::SpinButton->new_with_range( 0, $current_page->{h}, 1 );
- $tooltips->set_tip( $sb_selector_y,
- $d->get('The y-position of the top edge of the crop.') );
- $hbox->pack_end( $sb_selector_y, FALSE, TRUE, 0 );
- # SpinButton for w
- $hbox = Gtk2::HBox->new;
- $vbox->pack_start( $hbox, FALSE, TRUE, 0 );
- $label = Gtk2::Label->new( $d->get('Width') );
- $hbox->pack_start( $label, FALSE, TRUE, 0 );
- $label = Gtk2::Label->new( $d->get('pixels') );
- $hbox->pack_end( $label, FALSE, TRUE, 0 );
- $sb_selector_w = Gtk2::SpinButton->new_with_range( 0, $current_page->{w}, 1 );
- $tooltips->set_tip( $sb_selector_w, $d->get('The width of the crop.') );
- $hbox->pack_end( $sb_selector_w, FALSE, TRUE, 0 );
- # SpinButton for h
- $hbox = Gtk2::HBox->new;
- $vbox->pack_start( $hbox, FALSE, TRUE, 0 );
- $label = Gtk2::Label->new( $d->get('Height') );
- $hbox->pack_start( $label, FALSE, TRUE, 0 );
- $label = Gtk2::Label->new( $d->get('pixels') );
- $hbox->pack_end( $label, FALSE, TRUE, 0 );
- $sb_selector_h = Gtk2::SpinButton->new_with_range( 0, $current_page->{h}, 1 );
- $tooltips->set_tip( $sb_selector_h, $d->get('The height of the crop.') );
- $hbox->pack_end( $sb_selector_h, FALSE, TRUE, 0 );
- # Callbacks if the spinbuttons change
- $sb_selector_x->signal_connect(
- 'value-changed' => sub {
- $SETTING{selection}[0] = $sb_selector_x->get_value;
- $sb_selector_w->set_range( 0, $current_page->{w} - $SETTING{selection}[0] );
- update_selector();
- }
- );
- $sb_selector_y->signal_connect(
- 'value-changed' => sub {
- $SETTING{selection}[1] = $sb_selector_y->get_value;
- $sb_selector_h->set_range( 0, $current_page->{h} - $SETTING{selection}[1] );
- update_selector();
- }
- );
- $sb_selector_w->signal_connect(
- 'value-changed' => sub {
- $SETTING{selection}[2] = $sb_selector_w->get_value;
- $sb_selector_x->set_range( 0, $current_page->{w} - $SETTING{selection}[2] );
- update_selector();
- }
- );
- $sb_selector_h->signal_connect(
- 'value-changed' => sub {
- $SETTING{selection}[3] = $sb_selector_h->get_value;
- $sb_selector_y->set_range( 0, $current_page->{h} - $SETTING{selection}[3] );
- update_selector();
- }
- );
- $sb_selector_x->set_value( $SETTING{selection}[0] )
- if ( defined $SETTING{selection}[0] );
- $sb_selector_y->set_value( $SETTING{selection}[1] )
- if ( defined $SETTING{selection}[1] );
- $sb_selector_w->set_value( $SETTING{selection}[2] )
- if ( defined $SETTING{selection}[2] );
- $sb_selector_h->set_value( $SETTING{selection}[3] )
- if ( defined $SETTING{selection}[3] );
- # HBox for buttons
- my $hboxb = _hbox_with_apply_cancel_buttons(
- sub {
- return unless ( @{ $SETTING{selection} } );
- # Update undo/redo buffers
- take_snapshot();
- my @pagelist = $slist->get_page_index( $SETTING{'Page range'} );
- for my $i (@pagelist) {
- my ( $signal, $pid );
- $pid = $slist->crop(
- page => $slist->{data}[$i][2],
- x => $SETTING{selection}[0],
- y => $SETTING{selection}[1],
- w => $SETTING{selection}[2],
- h => $SETTING{selection}[3],
- queued_callback => sub {
- return update_tpbar(@_);
- },
- started_callback => sub {
- my ( $thread, $process, $completed, $total ) = @_;
- $signal = setup_tpbar( $thread, $process, $completed, $total, $pid );
- return TRUE if ( defined $signal );
- },
- finished_callback => sub {
- my ( $new_page, $pending ) = @_;
- $thbox->hide_all unless $pending;
- $tcbutton->signal_handler_disconnect($signal) if ( defined $signal );
- },
- error_callback => sub {
- my ($message) = @_;
- show_message_dialog( $window, 'error', 'close', $message );
- },
- display_callback => sub {
- my ($new_page) = @_;
- display_image($new_page);
- },
- );
- }
- },
- sub { $windowc->hide }
- );
- $vbox->pack_start( $hboxb, FALSE, TRUE, 0 );
- $windowc->show_all;
- return;
- }
- sub update_selector {
- my $sel = $selector->get_selection;
- $selector->signal_handler_block( $selector->{selection_changed_signal} );
- $selector->set_selection(
- Gtk2::Gdk::Rectangle->new( @{ $SETTING{selection} } ) )
- if ( defined $sel );
- $selector->signal_handler_unblock( $selector->{selection_changed_signal} );
- return;
- }
- # Run a user-defined tool on the selected images
- sub user_defined_tool {
- my ($action) = @_;
- my @pages = $slist->get_selected_indices;
- # Update undo/redo buffers
- take_snapshot();
- my $cmd = $action->get('label');
- for my $i (@pages) {
- my ( $signal, $pid );
- $pid = $slist->user_defined(
- page => $slist->{data}[$i][2],
- command => $cmd,
- queued_callback => sub {
- return update_tpbar(@_);
- },
- started_callback => sub {
- my ( $thread, $process, $completed, $total ) = @_;
- $signal = setup_tpbar( $thread, $process, $completed, $total, $pid );
- return TRUE if ( defined $signal );
- },
- finished_callback => sub {
- my ( $new_page, $pending ) = @_;
- $thbox->hide_all unless $pending;
- $tcbutton->signal_handler_disconnect($signal) if ( defined $signal );
- },
- error_callback => sub {
- my ($message) = @_;
- show_message_dialog( $window, 'error', 'close', $message );
- },
- display_callback => sub {
- my ($new_page) = @_;
- display_image($new_page);
- },
- );
- }
- return;
- }
- # guess from which window the sub was called
- sub get_parent {
- my ( $w1, $w2, $w3 ) = @_;
- if ( defined($w1) and $w1->visible ) {
- return $w1;
- }
- elsif ( defined($w2) and $w2->visible ) {
- return $w2;
- }
- else {
- return $w3;
- }
- return;
- }
- # queue $page to be processed by unpaper
- sub unpaper_page {
- my ( $pages, $options, $callback ) = @_;
- $options = '' unless ( defined $options );
- # Update undo/redo buffers
- take_snapshot();
- for my $pageobject ( @{$pages} ) {
- my ( $signal, $pid );
- $pid = $slist->unpaper(
- page => $pageobject,
- options => $options,
- queued_callback => sub {
- return update_tpbar(@_);
- },
- started_callback => sub {
- my ( $thread, $process, $completed, $total ) = @_;
- $signal = setup_tpbar( $thread, $process, $completed, $total, $pid );
- return TRUE if ( defined $signal );
- },
- finished_callback => sub {
- my ( $new_page, $pending ) = @_;
- $callback->($new_page) if $callback;
- $thbox->hide_all unless $pending;
- $tcbutton->signal_handler_disconnect($signal) if ( defined $signal );
- },
- error_callback => sub {
- my ($message) = @_;
- show_message_dialog( $window, 'error', 'close', $message );
- },
- display_callback => sub {
- my ($new_page) = @_;
- display_image($new_page);
- },
- );
- }
- return;
- }
- # Run unpaper to clean up scan.
- sub unpaper {
- if ( defined $windowu ) {
- $windowu->present;
- return;
- }
- $windowu = Gscan2pdf::Dialog->new(
- 'transient-for' => $window,
- title => $d->get('unpaper'),
- 'hide-on-delete' => TRUE,
- border_width => $border_width
- );
- my $vbox = $windowu->get('vbox');
- $unpaper->add_options($vbox);
- # Frame for page range
- 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();
- # Update $SETTING
- $SETTING{'unpaper options'} = $unpaper->get_options;
- # run unpaper
- my @pages =
- indices2pages( $slist->get_page_index( $SETTING{'Page range'} ) );
- unpaper_page( \@pages, $unpaper->get_cmdline );
- $windowu->hide;
- }
- );
- # 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;
- return;
- }
- # Add $page to the OCR stack, setting it off if not running.
- sub ocr_page {
- my ($pages) = @_;
- for my $page ( @{$pages} ) {
- my ( $signal, $pid );
- if ( $SETTING{'ocr engine'} eq 'gocr' ) {
- $pid = $slist->gocr(
- page => $page,
- queued_callback => sub {
- return update_tpbar(@_);
- },
- started_callback => sub {
- my ( $thread, $process, $completed, $total ) = @_;
- $signal = setup_tpbar( $thread, $process, $completed, $total, $pid );
- return TRUE if ( defined $signal );
- },
- finished_callback => sub {
- my ( $new_page, $pending ) = @_;
- $thbox->hide_all unless $pending;
- $tcbutton->signal_handler_disconnect($signal) if ( defined $signal );
- },
- error_callback => sub {
- my ($message) = @_;
- show_message_dialog( $window, 'error', 'close', $message );
- },
- display_callback => sub {
- my ($new_page) = @_;
- my @page = $slist->get_selected_indices;
- create_canvas($new_page)
- if ( @page and $new_page == $slist->{data}[ $page[0] ][2] );
- }
- );
- }
- elsif ( $SETTING{'ocr engine'} eq 'tesseract' ) {
- $pid = $slist->tesseract(
- page => $page,
- language => $SETTING{'ocr language'},
- queued_callback => sub {
- return update_tpbar(@_);
- },
- started_callback => sub {
- my ( $thread, $process, $completed, $total ) = @_;
- $signal = setup_tpbar( $thread, $process, $completed, $total, $pid );
- return TRUE if ( defined $signal );
- },
- finished_callback => sub {
- my ( $new_page, $pending ) = @_;
- $thbox->hide_all unless $pending;
- $tcbutton->signal_handler_disconnect($signal) if ( defined $signal );
- },
- error_callback => sub {
- my ($message) = @_;
- show_message_dialog( $window, 'error', 'close', $message );
- },
- display_callback => sub {
- my ($new_page) = @_;
- my @page = $slist->get_selected_indices;
- create_canvas($new_page)
- if ( @page and $new_page == $slist->{data}[ $page[0] ][2] );
- }
- );
- }
- elsif ( $SETTING{'ocr engine'} eq 'ocropus' ) {
- $pid = $slist->ocropus(
- page => $page,
- language => $SETTING{'ocr language'},
- queued_callback => sub {
- return update_tpbar(@_);
- },
- started_callback => sub {
- my ( $thread, $process, $completed, $total ) = @_;
- $signal = setup_tpbar( $thread, $process, $completed, $total, $pid );
- return TRUE if ( defined $signal );
- },
- finished_callback => sub {
- my ( $new_page, $pending ) = @_;
- $thbox->hide_all unless $pending;
- $tcbutton->signal_handler_disconnect($signal) if ( defined $signal );
- },
- error_callback => sub {
- my ($message) = @_;
- show_message_dialog( $window, 'error', 'close', $message );
- },
- display_callback => sub {
- my ($new_page) = @_;
- my @page = $slist->get_selected_indices;
- create_canvas($new_page)
- if ( @page and $new_page == $slist->{data}[ $page[0] ][2] );
- }
- );
- }
- else { # cuneiform
- $pid = $slist->cuneiform(
- page => $page,
- language => $SETTING{'ocr language'},
- queued_callback => sub {
- return update_tpbar(@_);
- },
- started_callback => sub {
- my ( $thread, $process, $completed, $total ) = @_;
- $signal = setup_tpbar( $thread, $process, $completed, $total, $pid );
- return TRUE if ( defined $signal );
- },
- finished_callback => sub {
- my ( $new_page, $pending ) = @_;
- $thbox->hide_all unless $pending;
- $tcbutton->signal_handler_disconnect($signal) if ( defined $signal );
- },
- error_callback => sub {
- my ($message) = @_;
- show_message_dialog( $window, 'error', 'close', $message );
- },
- display_callback => sub {
- my ($new_page) = @_;
- my @page = $slist->get_selected_indices;
- create_canvas($new_page)
- if ( @page and $new_page == $slist->{data}[ $page[0] ][2] );
- }
- );
- }
- }
- return;
- }
- # Create a combobox from an array and set the default
- sub combobox_from_array {
- my (@array) = @_;
- # Fill ComboBox
- my $combobox = Gtk2::ComboBox->new_text;
- foreach (@array) {
- $combobox->append_text( $_->[1] );
- }
- return $combobox;
- }
- # Create a combobox from an array and set the default
- sub combobox_set_active {
- my ( $combobox, $default, @array ) = @_;
- # Fill ComboBox
- my $i = 0;
- my $o;
- if ( defined($default) ) {
- foreach (@array) {
- $o = $i if ( defined( $_->[0] ) and $_->[0] eq $default );
- ++$i;
- }
- }
- $o = 0 unless ( defined $o );
- $combobox->set_active($o);
- return;
- }
- # Add hbox for tesseract languages
- sub add_tess_languages {
- my ($vbox) = @_;
- my $hbox = Gtk2::HBox->new;
- $vbox->pack_start( $hbox, FALSE, FALSE, 0 );
- my $label = Gtk2::Label->new( $d->get('Language to recognise') );
- $hbox->pack_start( $label, TRUE, TRUE, 0 );
- # Tesseract language files
- my @tesslang;
- for ( sort { $a cmp $b } keys %{ Gscan2pdf::Tesseract->languages } ) {
- push @tesslang, [ $_, $d->get( ${ Gscan2pdf::Tesseract->languages }{$_} ) ];
- }
- # If there are no language files, then we have tesseract-1.0, i.e. English
- unless (@tesslang) {
- push @tesslang, [ undef, $d->get('English') ];
- $logger->info("No tesseract languages found");
- }
- my $combobox = combobox_from_array(@tesslang);
- combobox_set_active( $combobox, $SETTING{'ocr language'}, @tesslang );
- $hbox->pack_end( $combobox, TRUE, TRUE, 0 );
- return $hbox, $combobox, @tesslang;
- }
- # Add hbox for cuneiform languages
- sub add_cf_languages {
- my ($vbox) = @_;
- my $hbox = Gtk2::HBox->new;
- $vbox->pack_start( $hbox, FALSE, FALSE, 0 );
- my $label = Gtk2::Label->new( $d->get('Language to recognise') );
- $hbox->pack_start( $label, TRUE, TRUE, 0 );
- # Tesseract language files
- my @lang;
- for ( sort { $a cmp $b } keys %{ Gscan2pdf::Cuneiform->languages } ) {
- push @lang, [ $_, $d->get( ${ Gscan2pdf::Cuneiform->languages }{$_} ) ];
- }
- my $combobox = combobox_from_array(@lang);
- combobox_set_active( $combobox, $SETTING{'ocr language'}, @lang );
- $hbox->pack_end( $combobox, TRUE, TRUE, 0 );
- return $hbox, $combobox, @lang;
- }
- # Run OCR on current page and display result
- sub OCR {
- if ( defined $windowo ) {
- $windowo->present;
- return;
- }
- $windowo = Gscan2pdf::Dialog->new(
- 'transient-for' => $window,
- title => $d->get('OCR'),
- 'hide-on-delete' => TRUE,
- border_width => $border_width
- );
- my $vbox = $windowo->get('vbox');
- # OCR engine selection
- my $hboxe = Gtk2::HBox->new;
- $vbox->pack_start( $hboxe, TRUE, TRUE, 0 );
- my $label = Gtk2::Label->new( $d->get('OCR Engine') );
- $hboxe->pack_start( $label, FALSE, FALSE, 0 );
- my $combobe = combobox_from_array(@ocr_engine);
- combobox_set_active( $combobe, $SETTING{'ocr engine'}, @ocr_engine );
- $hboxe->pack_end( $combobe, FALSE, FALSE, 0 );
- my ( $comboboxtl, $hboxtl, @tesslang, $comboboxcl, $hboxcl, @cflang );
- if ( $dependencies{tesseract} ) {
- ( $hboxtl, $comboboxtl, @tesslang ) = add_tess_languages($vbox);
- $combobe->signal_connect(
- changed => sub {
- if ($ocr_engine[ $combobe->get_active ]->[0] eq 'tesseract'
- or $ocr_engine[ $combobe->get_active ]->[0] eq 'ocropus' )
- {
- $hboxtl->show_all;
- }
- else {
- $hboxtl->hide_all;
- }
- }
- );
- }
- if ( $dependencies{cuneiform} ) {
- ( $hboxcl, $comboboxcl, @cflang ) = add_cf_languages($vbox);
- $combobe->signal_connect(
- changed => sub {
- if ( $ocr_engine[ $combobe->get_active ]->[0] eq 'cuneiform' ) {
- $hboxcl->show_all;
- }
- else {
- $hboxcl->hide_all;
- }
- }
- );
- }
- # Frame for page range
- 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 {
- $SETTING{'ocr engine'} = $ocr_engine[ $combobe->get_active ]->[0];
- $SETTING{'ocr language'} = $tesslang[ $comboboxtl->get_active ]->[0]
- if ( $SETTING{'ocr engine'} eq 'tesseract'
- or $SETTING{'ocr engine'} eq 'ocropus' );
- $SETTING{'ocr language'} = $cflang[ $comboboxcl->get_active ]->[0]
- if ( $SETTING{'ocr engine'} eq 'cuneiform' );
- # fill $pagelist with filenames depending on which radiobutton is active
- my @pages =
- indices2pages( $slist->get_page_index( $SETTING{'Page range'} ) );
- if ( not @pages ) {
- show_message_dialog( $windowo, 'error', 'close',
- $d->get('No page selected') );
- return;
- }
- ocr_page( \@pages );
- $windowo->hide;
- }
- );
- # 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;
- $hboxtl->hide_all
- if (
- defined($hboxtl)
- and not( $ocr_engine[ $combobe->get_active ]->[0] eq 'tesseract'
- or $ocr_engine[ $combobe->get_active ]->[0] eq 'ocropus' )
- );
- $hboxcl->hide_all
- if ( defined($hboxcl)
- and not( $ocr_engine[ $combobe->get_active ]->[0] eq 'cuneiform' ) );
- return;
- }
- # Remove temporary files, note window state, save settings and quit.
- sub quit {
- # Check that all pages have been saved
- for ( my $i = 0 ; $i < @{ $slist->{data} } ; $i++ ) {
- if ( not $slist->{data}[$i][2]{saved} ) {
- my $response =
- show_message_dialog( $window, 'question', 'ok-cancel',
- $d->get("Some pages have not been saved.\nDo you really want to quit?") );
- if ( $response ne 'ok' ) {
- return FALSE;
- }
- else {
- last;
- }
- }
- }
- # Make sure that we are back in the start directory, otherwise we can't delete
- # the temp dir.
- chdir $SETTING{'cwd'};
- # Remove temporary files (for some reason File::Temp wasn't doing its job here)
- unlink <$session/*>;
- rmdir $session;
- # 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;
- # delete $SETTING{'RMB'};
- # Write config file
- $conf->save_file( $rc, \%SETTING );
- $logger->info("Wrote config to $ENV{'HOME'}/.$prog_name");
- kill_threads();
- kill_subs();
- $logger->debug("Quitting");
- return TRUE;
- }
- # kill all threads
- sub kill_threads {
- Gscan2pdf::Frontend::Sane->quit();
- Gscan2pdf::Document->quit();
- return;
- }
- # View POD
- sub view_pod {
- if ( defined $windowh ) {
- $windowh->present;
- return;
- }
- try { require Gtk2::Ex::PodViewer }
- catch {
- show_message_dialog(
- $window, 'error', 'close',
- sprintf(
- $d->get(
- "The help viewer requires module Gtk2::Ex::PodViewer\n"
- . "Alternatively, try: %s %s\n\n"
- ),
- $prog_name,
- "--help"
- )
- );
- 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;
- return;
- }
- # Remove formatting characters
- sub strippod {
- my $text = shift;
- $text =~ s/B<([^<]*)>/$1/gx;
- $text =~ s/E<gt>/>/gx;
- return $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;
- }
- return;
- }
- # Get option string from label
- sub get_key {
- my ( $options, $value ) = @_;
- foreach ( keys %$options ) {
- return $_ if ( $options->{$_}{string} eq $value );
- }
- return;
- }
- # Get value string from combobox
- sub get_value {
- my ( $options, $option, $value ) = @_;
- foreach ( keys %{ $options->{$option}{values} } ) {
- return $_ if ( $options->{$option}{values}{$_} eq $value );
- }
- return;
- }
- # 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;
- $logger->debug( Dumper( \@undo_buffer ) );
- # Unghost Undo/redo
- $uimanager->get_widget('/MenuBar/Edit/Undo')->set_sensitive(TRUE);
- $uimanager->get_widget('/ToolBar/Undo')->set_sensitive(TRUE);
- # Save session
- $slist->save_session($session);
- return;
- }
- # Put things back to last snapshot after updating redo buffer
- sub undo {
- $logger->info("Undoing");
- # Deep copy the tied data. Otherwise, very bad things happen.
- @redo_buffer = map { [@$_] } @{ $slist->{data} };
- @redo_selection = $slist->get_selected_indices;
- $logger->debug("redo_selection, undo_selection:");
- $logger->debug( Dumper( \@redo_selection, \@undo_selection ) );
- $logger->debug("redo_buffer, undo_buffer:");
- $logger->debug( Dumper( \@redo_buffer, \@undo_buffer ) );
- # Block slist signals whilst updating
- $slist->get_model->signal_handler_block( $slist->{row_changed_signal} );
- @{ $slist->{data} } = @undo_buffer;
- # Unblock slist signals now finished
- $slist->get_model->signal_handler_unblock( $slist->{row_changed_signal} );
- # Reselect the pages to display the detail view
- $slist->select(@undo_selection);
- # Update menus/buttons
- update_uimanager();
- $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);
- return;
- }
- # Put things back to last snapshot after updating redo buffer
- sub unundo {
- $logger->info("Redoing");
- # Deep copy the tied data. Otherwise, very bad things happen.
- @undo_buffer = map { [@$_] } @{ $slist->{data} };
- @undo_selection = $slist->get_selected_indices;
- $logger->debug("redo_selection, undo_selection:");
- $logger->debug( Dumper( \@redo_selection, \@undo_selection ) );
- $logger->debug("redo_buffer, undo_buffer:");
- $logger->debug( Dumper( \@redo_buffer, \@undo_buffer ) );
- # Block slist signals whilst updating
- $slist->get_model->signal_handler_block( $slist->{row_changed_signal} );
- @{ $slist->{data} } = @redo_buffer;
- # Unblock slist signals now finished
- $slist->get_model->signal_handler_unblock( $slist->{row_changed_signal} );
- # Reselect the pages to display the detail view
- $slist->select(@redo_selection);
- # Update menus/buttons
- update_uimanager();
- $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);
- return;
- }
- # Initialise IconFactory
- sub init_icons {
- my @icons = @_;
- return if defined $IconFactory;
- $IconFactory = Gtk2::IconFactory->new();
- $IconFactory->add_default();
- foreach (@icons) {
- register_icon( $_->[0], $_->[1] );
- }
- return;
- }
- # Add icons
- sub register_icon {
- my ( $stock_id, $path ) = @_;
- return unless defined $IconFactory;
- my $icon;
- try { $icon = Gtk2::Gdk::Pixbuf->new_from_file($path); }
- catch {
- $logger->warn("Unable to load icon `$path': $_");
- return;
- };
- $IconFactory->add( $stock_id, Gtk2::IconSet->new_from_pixbuf($icon) );
- return;
- }
- # We should clean up after ourselves so that we don't
- # leave dead processes flying around.
- sub kill_subs {
- foreach ( keys %helperTag ) {
- killfam 'TERM', ($_);
- $logger->info("Sending TERM signal to PID $_ and its children");
- }
- return;
- }
- # marked page list as saved
- sub mark_pages {
- my @pages = @_;
- $slist->get_model->signal_handler_block( $slist->{row_changed_signal} );
- foreach (@pages) {
- $slist->{data}[$_][2]{saved} = TRUE;
- }
- $slist->get_model->signal_handler_unblock( $slist->{row_changed_signal} );
- return;
- }
- # Convert all files in temp that are not jpg, png, or tiff to png,
- sub compress_temp {
- return
- if (
- show_message_dialog(
- $window, 'question',
- 'ok-cancel', $d->get('This operation cannot be undone. Are you sure?')
- ) ne 'ok'
- );
- @undo_buffer = ();
- @undo_selection = ();
- $uimanager->get_widget('/MenuBar/Edit/Undo')->set_sensitive(FALSE);
- $uimanager->get_widget('/ToolBar/Undo')->set_sensitive(FALSE);
- for ( @{ $slist->{data} } ) {
- my ( $signal, $pid );
- $pid = $slist->to_png(
- page => $_->[2],
- queued_callback => sub {
- return update_tpbar(@_);
- },
- started_callback => sub {
- my ( $thread, $process, $completed, $total ) = @_;
- $signal = setup_tpbar( $thread, $process, $completed, $total, $pid );
- return TRUE if ( defined $signal );
- },
- finished_callback => sub {
- my ( $new_page, $pending ) = @_;
- $thbox->hide_all unless $pending;
- $tcbutton->signal_handler_disconnect($signal) if ( defined $signal );
- },
- error_callback => sub {
- my ($message) = @_;
- show_message_dialog( $window, 'error', 'close', $message );
- },
- );
- }
- return;
- }
- # Expand tildes in the filename
- sub expand_tildes {
- my ($filename) = @_;
- $filename =~ s{ ^ ~ ( [^/]* ) } {
- $1 ? (getpwnam($1))[7] : ( $ENV{HOME} || $ENV{LOGDIR} || (getpwuid($>))[7] )
- }ex;
- return $filename;
- }
- # Preferences dialog
- sub preferences { ## no critic (ProhibitExcessComplexity)
- if ( defined $windowr ) {
- $windowr->present;
- return;
- }
- $windowr = Gscan2pdf::Dialog->new(
- 'transient-for' => $window,
- title => $d->get('Preferences'),
- 'hide-on-delete' => TRUE,
- border_width => $border_width
- );
- my $vbox = $windowr->get('vbox');
- # Frontends
- my $hbox = Gtk2::HBox->new;
- $vbox->pack_start( $hbox, TRUE, TRUE, 0 );
- my $label = Gtk2::Label->new( $d->get('Frontend') );
- $hbox->pack_start( $label, FALSE, FALSE, 0 );
- my @frontends = (
- [
- 'libsane-perl', $d->get('libsane-perl'),
- $d->get('Scan using the perl bindings for SANE.')
- ],
- [
- 'scanimage', $d->get('scanimage'),
- $d->get('Scan using the scanimage frontend.')
- ],
- [
- 'scanimage-perl',
- $d->get('scanimage-perl'),
- $d->get('Scan using the scanimage-perl frontend.')
- ],
- [
- 'scanadf-perl', $d->get('scanadf-perl'),
- $d->get('Scan using the scanadf-perl frontend.')
- ],
- );
- push @frontends,
- [
- 'scanadf', $d->get('scanadf'),
- $d->get('Scan using the scanadf frontend.')
- ],
- if ( $dependencies{scanadf} );
- my $combob = combobox_from_array(@frontends);
- my $cbcsh =
- Gtk2::CheckButton->new_with_label( $d->get('Cycle SANE handle after scan') );
- $combob->signal_connect(
- changed => sub {
- if ( $frontends[ $combob->get_active ][0] eq 'libsane-perl' ) {
- $cbcsh->set_sensitive(TRUE);
- }
- else {
- $cbcsh->set_sensitive(FALSE);
- }
- }
- );
- combobox_set_active( $combob, $SETTING{frontend}, @frontends );
- $hbox->pack_end( $combob, TRUE, TRUE, 0 );
- # Cycle SANE handle after scan
- $cbcsh->set_active( $SETTING{'cycle sane handle'} )
- if ( defined $SETTING{'cycle sane handle'} );
- $vbox->pack_start( $cbcsh, TRUE, TRUE, 0 );
- # Default filename
- $hbox = Gtk2::HBox->new;
- $vbox->pack_start( $hbox, TRUE, TRUE, 0 );
- $label = Gtk2::Label->new( $d->get('Default filename for PDFs') );
- $hbox->pack_start( $label, FALSE, FALSE, 0 );
- my $fileentry = Gtk2::Entry->new;
- $tooltips->set_tip(
- $fileentry,
- $d->get(
- "\%a\t author\n\%t\t title\n\%y\t document's year\n\%Y\t today's year\n\%m\t document's month\n\%M\t today's month\n\%d\t document's day\n\%D\t today's day"
- )
- );
- $hbox->add($fileentry);
- $fileentry->set_text( $SETTING{'default filename'} )
- if defined( $SETTING{'default filename'} );
- # scan command prefix
- $hbox = Gtk2::HBox->new;
- $vbox->pack_start( $hbox, TRUE, TRUE, 0 );
- $label = Gtk2::Label->new( $d->get('Scan command prefix') );
- $hbox->pack_start( $label, FALSE, FALSE, 0 );
- my $preentry = Gtk2::Entry->new;
- $hbox->add($preentry);
- $preentry->set_text( $SETTING{'scan prefix'} )
- if defined( $SETTING{'scan prefix'} );
- # Restore window setting
- my $cbw = Gtk2::CheckButton->new_with_label(
- $d->get('Restore window settings on startup') );
- $cbw->set_active(TRUE) if ( $SETTING{'restore window'} );
- $vbox->pack_start( $cbw, TRUE, TRUE, 0 );
- # Cache options?
- my $cbc = Gtk2::CheckButton->new_with_label(
- $d->get('Cache device-dependent options') );
- $cbc->set_active(TRUE) if ( $SETTING{'cache options'} );
- $vbox->pack_start( $cbc, TRUE, TRUE, 0 );
- # Clear options cache
- my $button =
- Gtk2::Button->new( $d->get('Clear device-dependent options cache') );
- $vbox->pack_start( $button, TRUE, TRUE, 0 );
- $button->signal_connect(
- clicked => sub {
- delete $SETTING{cache} if ( defined $SETTING{cache} );
- }
- );
- # Temporary directory settings
- $hbox = Gtk2::HBox->new;
- $vbox->pack_start( $hbox, TRUE, TRUE, 0 );
- $label = Gtk2::Label->new( $d->get('Temporary directory') );
- $hbox->pack_start( $label, FALSE, FALSE, 0 );
- my $tmpentry = Gtk2::Entry->new;
- $hbox->add($tmpentry);
- $tmpentry->set_text( dirname($session) );
- $button = Gtk2::Button->new( $d->get('Browse') );
- $button->signal_connect(
- clicked => sub {
- my $file_chooser = Gtk2::FileChooserDialog->new(
- $d->get('Select temporary directory'),
- $windowr, 'select-folder',
- 'gtk-cancel' => 'cancel',
- 'gtk-ok' => 'ok'
- );
- $file_chooser->set_current_folder( $tmpentry->get_text );
- if ( 'ok' eq $file_chooser->run ) {
- $tmpentry->set_text( $file_chooser->get_filename );
- }
- $file_chooser->destroy;
- }
- );
- $hbox->pack_end( $button, TRUE, TRUE, 0 );
- # Blank page standard deviation threshold
- $hbox = Gtk2::HBox->new;
- $vbox->pack_start( $hbox, TRUE, TRUE, 0 );
- $label = Gtk2::Label->new( $d->get('Blank threshold') );
- $hbox->pack_start( $label, FALSE, FALSE, 0 );
- my $spinbuttonb = Gtk2::SpinButton->new_with_range( 0, 1, 0.001 );
- $spinbuttonb->set_value( $SETTING{'Blank threshold'} );
- $tooltips->set_tip( $spinbuttonb,
- $d->get('Threshold used for selecting blank pages') );
- $hbox->add($spinbuttonb);
- # Dark page mean threshold
- $hbox = Gtk2::HBox->new;
- $vbox->pack_start( $hbox, TRUE, TRUE, 0 );
- $label = Gtk2::Label->new( $d->get('Dark threshold') );
- $hbox->pack_start( $label, FALSE, FALSE, 0 );
- my $spinbuttond = Gtk2::SpinButton->new_with_range( 0, 1, 0.01 );
- $spinbuttond->set_value( $SETTING{'Dark threshold'} );
- $tooltips->set_tip( $spinbuttond,
- $d->get('Threshold used for selecting dark pages') );
- $hbox->add($spinbuttond);
- # OCR output
- $hbox = Gtk2::HBox->new;
- $vbox->pack_start( $hbox, TRUE, TRUE, 0 );
- $label = Gtk2::Label->new( $d->get('OCR output') );
- $hbox->pack_start( $label, FALSE, FALSE, 0 );
- my @array = (
- [
- 'replace',
- $d->get('Replace'),
- $d->get(
- 'Replace the contents of the text buffer with that from the OCR output.')
- ],
- [
- 'prepend', $d->get('Prepend'),
- $d->get('Prepend the OCR output to the text buffer.')
- ],
- [
- 'append', $d->get('Append'),
- $d->get('Append the OCR output to the text buffer.')
- ],
- );
- my $comboo = combobox_from_array(@array);
- combobox_set_active( $comboo, $SETTING{'OCR output'}, @array );
- $hbox->pack_end( $comboo, TRUE, TRUE, 0 );
- # User-defined tools
- my $frame = Gtk2::Frame->new( $d->get('User-defined tools') );
- $vbox->pack_start( $frame, TRUE, TRUE, 0 );
- my $vboxt = Gtk2::VBox->new;
- $vboxt->set_border_width($border_width);
- $frame->add($vboxt);
- for my $tool (@user_defined_tools) {
- add_user_defined_tool_entry( $vboxt, $tool );
- }
- my $abutton = Gtk2::Button->new_from_stock('gtk-add');
- $vboxt->pack_start( $abutton, TRUE, TRUE, 0 );
- $abutton->signal_connect(
- clicked => sub {
- my %tool = ( cmd => 'my-tool %i %o' );
- add_user_defined_tool_menu( \%tool );
- add_user_defined_tool_entry( $vboxt, \%tool );
- $vboxt->reorder_child( $abutton, -1 );
- $SETTING{user_defined_tools} = list_user_defined_tools($vboxt);
- }
- );
- # View saved files
- my $cbv = Gtk2::CheckButton->new_with_label( $d->get('View files on saving') );
- $cbv->set_active( $SETTING{'view files toggle'} )
- if ( defined $SETTING{'view files toggle'} );
- $vbox->pack_start( $cbv, TRUE, TRUE, 0 );
- # Apply button
- $hbox = _hbox_with_apply_cancel_buttons(
- sub {
- $windowr->hide;
- if ( $SETTING{frontend} ne $frontends[ $combob->get_active ][0] ) {
- $SETTING{frontend} = $frontends[ $combob->get_active ][0];
- if ( $SETTING{frontend} eq 'libsane-perl' ) {
- undef $windows if ( defined $windows );
- }
- else {
- undef $windows2 if ( defined $windows2 );
- rescan_options( $device_list[ $combobd->get_active ]{name} )
- if ( defined $windows );
- }
- }
- $SETTING{'cycle sane handle'} = $cbcsh->get_active;
- $SETTING{'default filename'} = $fileentry->get_text;
- $SETTING{'scan prefix'} = $preentry->get_text;
- $SETTING{'cache options'} = $cbc->get_active;
- delete $SETTING{cache}
- if ( defined $SETTING{cache} and not $SETTING{'cache options'} );
- $SETTING{'restore window'} = $cbw->get_active;
- my @tmpdirs = File::Spec->splitdir($session);
- pop @tmpdirs; # Remove the top level
- my $tmp = File::Spec->catdir(@tmpdirs);
- # Expand tildes in the filename
- my $newdir = expand_tildes( $tmpentry->get_text );
- if ( $newdir ne $tmp ) {
- $SETTING{TMPDIR} = $newdir;
- show_message_dialog(
- $window,
- 'warning',
- 'close',
- $d->get(
- 'You will have to restart gscanp2df for changes to the temporary directory to take effect.'
- )
- );
- }
- $SETTING{'Blank threshold'} = $spinbuttonb->get_value;
- $SETTING{'Dark threshold'} = $spinbuttond->get_value;
- $SETTING{'OCR output'} = $array[ $comboo->get_active ][0];
- # Store viewer preferences
- $SETTING{'view files toggle'} = $cbv->get_active;
- },
- sub {
- $windowr->hide;
- }
- );
- $vbox->pack_start( $hbox, TRUE, TRUE, 0 );
- $windowr->show_all;
- return;
- }
- sub _hbox_with_apply_cancel_buttons {
- my ( $apply_callback, $cancel_callback ) = @_;
- my $hbox = Gtk2::HBox->new;
- # Apply button
- my $button = Gtk2::Button->new_from_stock('gtk-apply');
- $hbox->pack_start( $button, TRUE, TRUE, 0 );
- $button->signal_connect( clicked => $apply_callback );
- # Cancel button
- $button = Gtk2::Button->new_from_stock('gtk-cancel');
- $hbox->pack_end( $button, TRUE, TRUE, 0 );
- $button->signal_connect( clicked => $cancel_callback );
- return $hbox;
- }
- # Return list of user-defined tools
- sub list_user_defined_tools {
- my ($vbox) = @_;
- my @list;
- for my $hbox ( $vbox->get_children ) {
- if ( $hbox->isa('Gtk2::HBox') ) {
- for my $widget ( $hbox->get_children ) {
- push @list, $widget->get_text if ( $widget->isa('Gtk2::Entry') );
- }
- }
- }
- return \@list;
- }
- # Add user-defined tool menu item
- sub add_user_defined_tool_menu {
- my ($tool) = @_;
- my $action_group = Gtk2::ActionGroup->new("actions");
- $tool->{name} = "$action_group";
- my $tool_ui = <<"EOS";
- <ui>
- <menubar name='MenuBar'>
- <menu action='Tools'>
- <menu action='User-defined'>
- <menuitem action='$tool->{name}'/>
- </menu>
- </menu>
- </menubar>
- </ui>
- EOS
- # add the basic XML description of the GUI
- $tool->{merge_id} = $uimanager->add_ui_from_string($tool_ui);
- my @action_item = (
- # Fields for each action item:
- # [name, stock_id, value, label, accelerator, tooltip, callback]
- [ $tool->{name}, undef, $tool->{cmd}, undef, undef, \&user_defined_tool ],
- );
- $action_group->add_actions( \@action_item, undef );
- # Add the actiongroup to the uimanager
- $uimanager->insert_action_group( $action_group, 0 );
- $tool->{action_group} = $action_group;
- return;
- }
- # Add user-defined tool entry
- sub add_user_defined_tool_entry {
- my ( $vbox, $tool ) = @_;
- my $hbox = Gtk2::HBox->new;
- $vbox->pack_start( $hbox, TRUE, TRUE, 0 );
- my $entry = Gtk2::Entry->new;
- $entry->set_text( $tool->{cmd} );
- $entry->signal_connect(
- changed => sub {
- my $action = $tool->{action_group}->get_action( $tool->{name} );
- $action->set( 'label', $entry->get_text );
- $SETTING{user_defined_tools} = list_user_defined_tools($vbox);
- () # this callback must return either 2 or 0 items.
- }
- );
- $tooltips->set_tip(
- $entry,
- $d->get(
- "Use \%i and \%o for the input and output filenames respectively, or a single \%i if the image is to be modified in-place.\n\nThe other variable available is:\n\n\%r resolution"
- )
- );
- $hbox->pack_start( $entry, TRUE, TRUE, 0 );
- my $button = Gtk2::Button->new;
- $button->set_image( Gtk2::Image->new_from_stock( 'gtk-delete', 'button' ) );
- $button->signal_connect(
- clicked => sub {
- $uimanager->remove_ui( $tool->{merge_id} );
- $hbox->destroy;
- $SETTING{user_defined_tools} = list_user_defined_tools($vbox);
- }
- );
- $hbox->pack_end( $button, FALSE, FALSE, 0 );
- $hbox->show_all;
- return;
- }
- sub properties {
- if ( defined $windowp ) {
- $windowp->present;
- return;
- }
- $windowp = Gscan2pdf::Dialog->new(
- 'transient-for' => $window,
- title => $d->get('Properties'),
- 'hide-on-delete' => TRUE,
- border_width => $border_width
- );
- my $vbox = $windowp->get('vbox');
- my $hbox = Gtk2::HBox->new;
- $vbox->pack_start( $hbox, TRUE, TRUE, 0 );
- my $label = Gtk2::Label->new( $d->get('Resolution') );
- $hbox->pack_start( $label, FALSE, FALSE, 0 );
- my $entry = Gtk2::Entry->new;
- $entry->set_width_chars(4);
- $entry->set_activates_default(TRUE);
- $entry->signal_connect(
- 'insert-text' => sub {
- my ( $widget, $string, $len, $position ) = @_;
- # only allow a valid positive real number
- for ( split '', $string ) {
- if (( $_ ne '.' and ( $_ lt '0' or $_ gt '9' ) )
- or ( $_ eq '.' and $entry->get_text =~ /\./ ) )
- {
- $entry->signal_stop_emission_by_name('insert-text');
- last;
- }
- }
- () # this callback must return either 2 or 0 items.
- }
- );
- $entry->set_text( get_selected_properties() );
- $slist->get_selection->signal_connect(
- changed => sub {
- $entry->set_text( get_selected_properties() );
- }
- );
- $hbox->pack_end( $entry, TRUE, TRUE, 0 );
- $hbox = Gtk2::HBox->new;
- $vbox->pack_start( $hbox, TRUE, TRUE, 0 );
- # Apply button
- my $button = Gtk2::Button->new_from_stock('gtk-apply');
- $button->can_default(TRUE);
- $windowp->set_default($button);
- $hbox->pack_start( $button, TRUE, TRUE, 0 );
- $button->signal_connect(
- clicked => sub {
- $windowp->hide;
- my $resolution = $entry->get_text;
- $slist->get_model->signal_handler_block( $slist->{row_changed_signal} );
- for ( $slist->get_selected_indices ) {
- $slist->{data}[$_][2]{resolution} = $resolution;
- }
- $slist->get_model->signal_handler_unblock( $slist->{row_changed_signal} );
- }
- );
- # Cancel button
- $button = Gtk2::Button->new_from_stock('gtk-cancel');
- $hbox->pack_end( $button, TRUE, TRUE, 0 );
- $button->signal_connect(
- clicked => sub {
- $windowp->hide;
- }
- );
- $windowp->show_all;
- return;
- }
- # Helper function for properties()
- sub get_selected_properties {
- my @page = $slist->get_selected_indices;
- my $resolution = '';
- $resolution = $slist->{data}[ shift @page ][2]{resolution} if ( @page > 0 );
- for (@page) {
- if ( $slist->{data}[$_][2]{resolution} != $resolution ) {
- $resolution = '';
- last;
- }
- }
- # round the value to a sensible number of significant figures
- return $resolution eq '' ? '' : sprintf( "%g", $resolution );
- }
- # Helper function to display a message dialog, wait for a response, and return it
- sub show_message_dialog {
- my ( $parent, $type, $buttons, $text ) = @_;
- my $dialog =
- Gtk2::MessageDialog->new( $parent, 'destroy-with-parent', $type, $buttons,
- $text );
- my $response = $dialog->run;
- $dialog->destroy;
- return $response;
- }
- Gtk2->main;
- __END__
- =encoding utf8
- =head1 Name
- gscan2pdf - A GUI to produce PDFs or DjVus from scanned documents
- =for html <p align="center">
- <img src="http://sourceforge.net/dbimage.php?id=249848" border="1" width="632"
- height="480" alt="Screenshot" /><br/>Screenshot: Main page v0.9.30</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
- =back
- =head1 Description
- gscan2pdf has the following command-line options:
- =over
- =item --device=<device>
- Specifies the device to use, instead of getting the list of devices from via the SANE API.
- This can be useful if the scanner is on a remote computer which is not broadcasting its existence.
- =item --help
- Displays this help page and exits.
- =item --log=<log file>
- Specifies a file to store logging messages.
- =item --(debug|info|warn|error|fatal)
- Defines the log level. If a log file is specified, this defaults to 'debug', otherwise 'warn'.
- =item --version
- Displays the program version and exits.
- =back
- 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).
- =head1 Download
- gscan2pdf is available on Sourceforge
- (L<http://sourceforge.net/projects/gscan2pdf/files/gscan2pdf/>).
- =head2 Debian-based
- If you are using Debian, you should find that sid has the latest version already
- packaged.
- If you are using a Ubuntu-based system, just add the following line to your
- "F</etc/apt/sources.list>" file:
- C<deb http://ppa.launchpad.net/jeffreyratcliffe/ubuntu E<lt>releaseE<gt> main>
- C<deb-src http://ppa.launchpad.net/jeffreyratcliffe/ubuntu E<lt>releaseE<gt> main>
- where C<E<lt>releaseE<gt>> is the version of Ubuntu you are using.
- 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>
- If you add the key for my ppa to your list of trusted keys, then you will no longer get
- the "not authenticated" warnings. Fetch the key:
- C<gpg --keyserver keyserver.ubuntu.com --recv-keys 5BC3E400>
- Then add it to the apt keyring:
- C<gpg --export --armor 5BC3E400 | sudo apt-key add ->
- =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/projects/gscan2pdf/files/>).
- =head2 From the repository
- gscan2pdf uses Git for its Revision Control System. You can browse the
- tree at L<http://sourceforge.net/p/gscan2pdf/code/>.
- Git users can clone the complete tree with
- C<git clone git://git.code.sf.net/p/gscan2pdf/code>
- =head1 Building gscan2pdf from source
- Having downloaded the source either from a Sourceforge file release, or from the
- Git repository, unpack it if necessary with
- C<tar xvfz gscan2pdf-x.x.x.tar.gz
- cd gscan2pdf-x.x.x>
- C<perl Makefile.PL>, will create the Makefile.
- There is a C<make test>, but this is not machine-dependent, and therefore really
- just for my benefit to make sure I haven't broken the device-dependent options
- parsing routine.
- You can install directly from the source with C<make install>, but building the
- appropriate package for your distribution should be as straightforward as
- C<make debdist> or C<make rpmdist>. However, you will
- additionally need the rpm, devscripts, fakeroot, debhelper and gettext packages.
- =head1 Dependencies
- The list below looks daunting, but all packages are available from any
- reasonable up-to-date distribution. If you are using Synaptic, having installed
- gscan2pdf, locate the gscan2pdf entry in Synaptic, right-click it and you can
- install them under I<Recommends>. Note also that the library names given below
- are the Debian/Ubuntu ones. Those distributions using RPM typically use
- perl(module) where Debian has libmodule-perl.
- =over
- =item Required
- =over
- =item libgtk2.0-0 (>= 2.4)
- The GTK+ graphical user interface library.
- =item libglib-perl (>= 1.100-1)
- Perl interface to the GLib and GObject libraries
- =item libgtk2-perl (>= 1:1.043-1)
- Perl interface to the 2.x series of the Gimp Toolkit library
- =item libgtk2-imageview-perl
- Perl bindings to the gtkimageview widget.
- See L<http://trac.bjourne.webfactional.com/>
- =item libgtk2-ex-simple-list-perl
- A simple interface to Gtk2's complex MVC list widget
- =item liblocale-gettext-perl (>= 1.05)
- Using libc functions for internationalization in Perl
- =item libpdf-api2-perl
- provides the functions for creating PDF documents in Perl
- =item libsane
- API library for scanners
- =item libsane-perl
- Perl bindings for libsane.
- =item libset-intspan-perl
- manages sets of integers
- =item libtiff-tools
- TIFF manipulation and conversion tools
- =item Imagemagick
- Image manipulation programs
- =item perlmagick
- A perl interface to the libMagick graphics routines
- =item sane-utils
- API library for scanners -- utilities.
- =back
- =item Optional
- =over
- =item sane
- scanner graphical frontends. Only required for the scanadf frontend.
- =item libgtk2-ex-podviewer-perl
- Perl Gtk2 widget for displaying Plain Old Documentation (POD). Not required if
- you don't need the gscan2pdf documentation (which is anyway repeated on the
- website).
- =item unpaper
- post-processing tool for scanned pages. See L<http://unpaper.berlios.de/>.
- =item xdg-utils
- Desktop integration utilities from freedesktop.org. Required for Email as PDF.
- See L<http://portland.freedesktop.org/wiki/>
- =item djvulibre-bin
- Utilities for the DjVu image format. See L<http://djvu.sourceforge.net/>
- =item gocr
- A command line OCR. See L<http://jocr.sourceforge.net/>.
- =item tesseract
- A command line OCR. See L<http://code.google.com/p/tesseract-ocr/>
- =item ocropus
- A command line OCR. See L<http://code.google.com/p/ocropus/>
- =item cuneiform
- A command line OCR. See L<http://launchpad.net/cuneiform-linux>
- =back
- =back
- =head1 Support
- There are two mailing lists for gscan2pdf:
- =over
- =item gscan2pdf-announce
- A low-traffic list for announcements, mostly of new releases. You can subscribe
- at L<http://lists.sourceforge.net/lists/listinfo/gscan2pdf-announce>
- =item gscan2pdf-help
- General support, questions, etc.. You can subscribe at
- L<http://lists.sourceforge.net/lists/listinfo/gscan2pdf-help>
- =back
- =head1 Reporting bugs
- Before reporting bugs, please read the L<"FAQs"> section.
- Please report any bugs found, preferably against the Debian package[1][2].
- You do not need to be a Debian user, or set up an account to do this.
- =over
- =item 1. http://packages.debian.org/sid/gscan2pdf
- =item 2. http://www.debian.org/Bugs/
- =back
- Alternatively, there is a bug tracker for the gscan2pdf project on
- Sourceforge (L<http://sourceforge.net/p/gscan2pdf/_list/tickets?source=navbar>).
- Please include the log file created by C<gscan2pdf --log=log> 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://translations.launchpad.net/gscan2pdf>
- Note that the translations for the scanner options are taken
- directly from sane-backends. If you would like to contribute to these, you can
- do so either at contact the sane-devel mailing list
- ([email protected]) and have a look at the po/ directory in
- the source code L<http://www.sane-project.org/cvs.html>.
- Alternatively, Ubuntu has its own translation project. For the 9.04 release, the
- translations are available at
- L<https://translations.launchpad.net/ubuntu/jaunty/+source/sane-backends/+pots/sane-backends>
- =head1 Menus
- =head2 File
- =head3 New
- Clears the page list.
- =head3 Open
- Opens 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 PPI)
- =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
- Saves the selected or all pages as a PDF, DjVu, TIFF, PNG, JPEG, PNM or
- GIF.
- =head4 PDF 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, but can also be used to generate the
- filename see preferences for details.
- =head4 DjVu
- Both black and white, and colour images produce better
- compression than PDF. See L<http://www.djvuzone.org/> for more details.
- =head3 Email as PDF
- Attaches the 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.
- =head3 Print
- Prints the selected or all pages.
- =head3 Compress temporary files
- If your temporary ($TMPDIR) directory is getting full, this function can be useful -
- compressing all images at LZW-compressed TIFFs. These require much less space than
- the PNM files that are typically produced by SANE or by importing a PDF.
- =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
- The select menus can be used to select, all, even, odd, blank, dark or modified
- pages. Selecting blank or dark pages runs imagemagick to make the decision.
- Selecting modified pages selects those which have modified by threshold,
- unsharp, etc., since the last OCR run was made.
- =head3 Preferences
- The preferences menu item allows the control of the default behviour of various
- functions. Most of these are self-explanatory.
- =head4 Frontend
- gscan2pdf supports two frontends, scanimage and scanadf.
- scanadf support was added when it was realised that scanadf works better than
- scanimage with some scanners. On Debian-based systems, scanadf is in the sane package,
- not, like scanimage, in sane-utils. If scanadf is not present, the option is
- obviously ghosted out.
- In 0.9.27, Perl bindings for SANE were introduced and two further frontends,
- scanimage-perl and scanadf-perl (scanimage and scanadf transliterated from C into
- Perl) were added.
- =head4 Default filename for PDF files
- The following variables are available, which are replaced by the corresponding
- metadata:
- %a author
- %t title
- %y document's year
- %Y today's year
- %m document's month
- %M today's month
- %d document's day
- %D today's day
- =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 Threshold
- Changes all pixels darker than the given value to black; all others become
- white.
- =head3 Unsharp mask
- The unsharp option sharpens an image. The image is convolved with a Gaussian
- operator of the given radius and standard deviation (sigma). For reasonable
- results, radius should be larger than sigma. Use a radius of 0 to have the
- method select a suitable radius.
- =head3 Crop
- =head3 unpaper
- unpaper (see L<http://unpaper.berlios.de/>) is a utility for cleaning up a scan.
- =head3 OCR (Optical Character Recognition)
- The gocr, tesseract, ocropus or cuneiform utilities are used to produce text from
- an image.
- There is an OCR output buffer for each page and is embedded as
- plain text behind the scanned image in the PDF
- produced. This way, Beagle can index (i.e. search) the plain text.
- In DjVu files, the OCR output buffer is embedded in the hidden text layer.
- Thus these can also be indexed by Beagle.
- There is an interesting review of OCR software at
- L<http://web.archive.org/web/20080529012847/http://groundstate.ca/ocr>.
- An important conclusion was that 400ppi is necessary for decent results.
- Up to v2.04, the only way to tell which languages were available to tesseract
- was to look for the language files. Therefore, gscan2pdf checks the path
- returned by:
- tesseract '' '' -l ''
- If there are no language files in the above location, then gscan2pdf
- assumes that tesseract v1.0 is installed, which had no language files.
- =head3 Variables for user-defined tools
- The following variables are available:
- %i input filename
- %o output filename
- %r resolution
- An image can be modified in-place by just specifying %i.
- =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), unpaper and the rotate options
- require imagemagick.
- =head2 Why can I not scan from the flatbed of my HP scanner?
- Generally for HP scanners with an ADF, to scan from the flatbed, you should
- set "# Pages" to "1", and possibly "Batch scan" to "No".
- =head2 When I update gscan2pdf using the Update Manager in Ubuntu, why is the list of changes never displayed?
- As far as I can tell, this is pulled from changelogs.ubuntu.com, and therefore
- only the changelogs from official Ubuntu builds are displayed.
- =head2 Why can gscan2pdf not find my scanner?
- If your scanner is not connected directly to the machine on which you are
- running gscan2pdf and you have not installed the SANE daemon, saned,
- gscan2pdf cannot automatically find it. In this case, you can specify the
- scanner device on the command line:
- C<gscan2pdf --device <device>>
- =head1 See Also
- Xsane
- http://scantailor.sourceforge.net/
- =head1 Author
- Jeffrey Ratcliffe (ra28145 at users dot sf dot net)
- =head1 Thanks to
- =over
- =item *
- all the people who have sent patches, translations, bugs and feedback.
- =item *
- the GTK2 project for a most excellent graphics toolkit.
- =item *
- the Gtk2-Perl project for their superb Perl bindings for GTK2.
- =item *
- The SANE project for scanner access
- =item *
- Björn Lindqvist for the gtkimageview widget
- =item *
- Sourceforge for hosting the project.
- =back
- =for html <hr />
- <a href="http://sourceforge.net/projects/gscan2pdf">
- <img src="http://sflogo.sourceforge.net/sflogo.php?group_id=174140&type=14" width="150" height="40" alt="Get gscan2pdf at SourceForge.net. Fast, secure and Free Open Source software downloads" /></a>
- <a href="http://sourceforge.net/p/gscan2pdf/donate/">
- <img src="http://sourceforge.net/images/project-support.jpg" width="88" height="32" border="0" alt="Support This Project"></a>
- =cut
Advertisement
Add Comment
Please, Sign In to add comment