#!/usr/bin/perl

# scannerdrake $Id$
# Yves Duret <yduret at mandrakesoft.com>
# Till Kamppeter <till at mandrakesoft.com>
# Copyright (C) 2001-2004 MandrakeSoft
# 
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2, or (at your option)
# any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.

use lib qw(/usr/lib/libDrakX);
use strict;
use standalone;     #- warning, standalone must be loaded very first, for 'explanations'
use common;

use interactive;
use scanner;
use handle_configs;
use services;

my $companyname = "MandrakeSoft";
my $distroname = "Mandrake Linux";
my $shortdistroname = "Mandrake";
my $domainname = "mandrakesoft.com";

foreach (@ARGV) {
    /^--update-usbtable$/ and do { scanner::updateScannerDBfromUsbtable(); exit() };
    /^--update-sane=(.*)$/ and do { scanner::updateScannerDBfromSane($1); exit() };
    /^--manual$/ and $::Manual=1;
    /^--dynamic=(.*)$/ and do { dynamic(); exit() };
}

my $in = 'interactive'->vnew('su');
if (!files_exist('/usr/bin/scanimage',
		 '/usr/bin/xsane',
		 if_(files_exist("/usr/bin/gimp"),
		     "/usr/lib/gimp/1.2/plug-ins/xsane"))) {
    if (!$in->do_pkgs->install('sane-backends', 'xsane',
			       if_($in->do_pkgs->is_installed('gimp'),
				   'xsane-gimp'))) {
	$in->ask_warn(N("Error"),
		      N("Could not install the packages needed to set up a scanner with Scannerdrake.") . " " .
		      N("Scannerdrake will not be started now."));
	exit 0;
    }
}
if ($::Manual) { manual(); quit() }
my $wait = $in->wait_message(N("Scannerdrake"),
			     N("Searching for configured scanners ..."));
my @c = scanner::configured();
$wait = undef;
$wait = $in->wait_message(N("Scannerdrake"),
			     N("Searching for new scanners ..."));
my @f = scanner::detect(@c);
$wait = undef;
my $changed = 0;
@f and $changed = auto();
if ($changed) {
    my $_wait =
	$in->wait_message(N("Scannerdrake"),
			  N("Re-generating list of configured scanners ..."));
    @c = scanner::configured();
}
mainwindow(@c);
quit();

sub removeverticalbar {
    my ($s) = @_;
    $s =~ s/\|/ /g;
    my $searchmake = handle_configs::searchstr(first($s =~ /^\s*(\S+)\s+/));
    $s =~ s/($searchmake)\s*$searchmake/$1/;
    return $s; 
}

sub auto() {
    my $changed = 0;
    foreach (@f) {
	my $c = 0;
	if (member($_->{val}{DESCRIPTION}, keys %$scanner::scannerDB)) {
	    my $name = $_->{val}{DESCRIPTION};
	    $name =~ s/\s$//; # some HP entries have a trailing space, i will correct usbtable asap
	    if ($scanner::scannerDB->{$name}{flags}{unsupported}) {
		$in->ask_warn('Scannerdrake', N("The %s is not supported by this version of %s.", removeverticalbar($name), $distroname));
		next;
	    }
	    if ($in->ask_yesorno('Scannerdrake', N("%s found on %s, configure it automatically?", removeverticalbar($name), $_->{port}),1)) {
		$c = (tryConfScanner($name, $_->{port}, 
				     $_->{val}{vendor},
				     $_->{val}{id}) || 
		      manual($_->{port}, $_->{val}{vendor},
			     $_->{val}{id}, $name));
	    } else {
		$c = manual($_->{port}, $_->{val}{vendor},
			    $_->{val}{id}, $name);
	    }
	} else {
	    $in->ask_yesorno('Scannerdrake',
			     N("%s is not in the scanner database, configure it manually?", 
			       removeverticalbar($_->{val}{DESCRIPTION})),1)
		and $c =
		     manual($_->{port}, $_->{val}{vendor}, $_->{val}{id},
			    $_->{val}{DESCRIPTION});
	}
	$changed ||= $c;
    }
    return $changed;
}

sub manual {
    my ($port, $vendor, $product, $name) = @_;
    my $s = 
	$in->ask_from_treelist('Scannerdrake', 
			       N("Select a scanner model") .
			       if_($port || $name, N(" (")) .
			       if_($name, N("Detected model: %s",
					    removeverticalbar($name))) .
			       if_($port && $name, N(", ")) .
			       if_($port, N("Port: %s", $port)) .
			       if_($port || $name, N(")")),
			       '|', [' None', keys %$scanner::scannerDB],
			       '') or return 0;
    return 0 if $s eq ' None';
    if ($scanner::scannerDB->{$s}{flags}{unsupported}) {
	$in->ask_warn('Scannerdrake', N("The %s is not supported by this version of %s.", removeverticalbar($s), $distroname));
	return 0;
    }
    return tryConfScanner($s, $port, $vendor, $product);
}

sub dynamic() {
    @f = scanner::detect();
    my $name;
    foreach (@f) { 
	if (member($_->{val}{DESCRIPTION}, keys %$scanner::scannerDB)) {
	    $name = $_->{val}{DESCRIPTION};
	    $name =~ s/\s$//; #some HP entry have a trailing space, i will correct usbtable asap
	    if ($scanner::scannerDB->{$name}{flags}{unsupported}) {
		$in->ask_warn('Scannerdrake', N("The %s is not supported by this version of %s.", removeverticalbar($name), $distroname));
		next;
	    }
	    scanner::confScanner($name, $_->{port},
				 $_->{val}{vendor}, $_->{val}{id}, "");
	} else {
	    $in->ask_warn('Scannerdrake', N("The %s is not known by this version of Scannerdrake.", removeverticalbar($name))); 
	}
    }
}

sub installfirmware {
    my ($model) = @_;
    my $firmware;
    my $choice = N("Do not install firmware file");
    while (1) {
	# Tell user about firmware installation
	$in->ask_from('Scannerdrake',
		      N("It is possible that your %s needs its firmware to be uploaded everytime when it is turned on.", removeverticalbar($model)) . " " .
		      N("If this is the case, you can make this be done automatically.") . " " .
		      N("To do so, you need to supply the firmware file for your scanner so that it can be installed.") . " " .
		      N("You find the file on the CD or floppy coming with the scanner, on the manufacturer's home page, or on your Windows partition."),
		      [
		       { label => N("Install firmware file from"), 
			 val => \$choice,
			 list => [N("CD-ROM"),
				  N("Floppy Disk"), 
				  N("Other place"),
				  N("Do not install firmware file")], 
			 not_edit => 1, sort => 0 },
		       ],
		      ) or return "///";
	my $dir;
	if ($choice eq N("CD-ROM")) {
	    $dir = "/mnt/cdrom";
	} elsif ($choice eq N("Floppy Disk")) {
	    $dir = "/mnt/floppy";
	} elsif ($choice eq N("Other place")) {
	    $dir = "/mnt";
	} else {
	    return "";
	}
	# Let user select a firmware file from a floppy, hard disk, ...
        $firmware = $in->ask_file(N("Select firmware file"), "$dir");
	last if !$firmware || (-r $firmware);
	$in->ask_warn(N("Error"),
		      N("The firmware file %s does not exist or is unreadable!",
			$firmware));
	
    }
    # Install the firmware file in /usr/share/sane/firmware
    $firmware = scanner::installfirmware($firmware);
    return $firmware;
}

sub updatefirmware {
    my (@configured) = @_;
    my $firmware;
    my @scanners =
	map { 
	    $_->{val}{DESCRIPTION}
	} grep { 
	    $_->{val}{FIRMWARELINE}
	} @configured;
    my ($scannerchoice, $mediachoice);
    while (1) {
	# Tell user about firmware installation
	$in->ask_from('Scannerdrake',
		      ($#scanners > 0 ?
		       N("It is possible that your scanners need their firmware to be uploaded everytime when they are turned on.") :
		       N("It is possible that your %s needs its firmware to be uploaded everytime when it is turned on.", $scanners[0])) . " " .
		      N("If this is the case, you can make this be done automatically.") . " " .
		      ($#scanners > 0 ?
		       N("To do so, you need to supply the firmware files for your scanners so that it can be installed.") :
		       N("To do so, you need to supply the firmware file for your scanner so that it can be installed.")) . " " .
		       N("You find the file on the CD or floppy coming with the scanner, on the manufacturer's home page, or on your Windows partition.") . "\n" .
		      N("If you have already installed your scanner's firmware you can update the firmware here by supplying the new firmware file."),
		      [
		       { label => N("Install firmware for the"), 
			 val => \$scannerchoice,
			 list => \@scanners, 
			 not_edit => 1, sort => 1 },
		       { label => N("Install firmware file from"), 
			 val => \$mediachoice,
			 list => [N("CD-ROM"),
				  N("Floppy Disk"), 
				  N("Other place")], 
			 not_edit => 1, sort => 0 },
		       ],
		      ) or return 0;
	my $dir;
	if ($mediachoice eq N("CD-ROM")) {
	    $dir = "/mnt/cdrom";
	} elsif ($mediachoice eq N("Floppy Disk")) {
	    $dir = "/mnt/floppy";
	} elsif ($mediachoice eq N("Other place")) {
	    $dir = "/mnt";
	} else {
	    return 0;
	}
	# Let user select a firmware file from a floppy, hard disk, ...
        $firmware = $in->ask_file(N("Select firmware file for the %s",
				    $scannerchoice), "$dir");
	last if !$firmware || (-r $firmware);
	$in->ask_warn(N("Error"),
		      N("The firmware file %s does not exist or is unreadable!",
			$firmware));
	
    }

    return 0 if !$firmware;

    # Install the firmware file in /usr/share/sane/firmware
    $firmware = scanner::installfirmware($firmware);

    # Enter the path to the firmware in the appropriate config file
    foreach (@configured) {
	next if $_->{val}{DESCRIPTION} ne $scannerchoice; 
	my $backend = $_->{val}{BACKEND};
	my $firmwareline =$_->{val}{FIRMWARELINE};
	$firmwareline =~ s/\$FIRMWARE/$firmware/sg;
	scanner::setfirmware($backend, $firmwareline);
	last;
    }

    # Success message
    $in->ask_warn('Scannerdrake',
		  N("The firmware file for your %s was successfully installed.",
		    $scannerchoice));
    
    return 1;
}

sub tryConfScanner {
    # take care if interactive output is needed (unsupported, parallel..)
    my ($model, $port, $vendor, $product) = @_;
    if ($scanner::scannerDB->{$model}{flags}{unsupported}) {
	$in->ask_warn('Scannerdrake', N("The %s is unsupported", 
					removeverticalbar($model)));
	return 0;
    }
    if ($scanner::scannerDB->{$model}{server} =~ /(printerdrake|hpoj)/i) {
     	$in->ask_warn('Scannerdrake', N("The %s must be configured by printerdrake.\nYou can launch printerdrake from the %s Control Center in Hardware section.", removeverticalbar($model), $shortdistroname));
     	return 0;
    }
    if ($scanner::scannerDB->{$model}{ask} =~ /DEVICE/ || !$port) {
	$port ||= N("Auto-detect available ports");
	$in->ask_from('Scannerdrake',
		      N("Please select the device where your %s is attached", removeverticalbar($model)) . " " .
		      N("(Note: Parallel ports cannot be auto-detected)"),
		      [
		       { label => N("choose device"), 
			 val => \$port,
			 list => [N("Auto-detect available ports"),
				  '/dev/scanner', 
				  '/dev/usb/scanner0',
				  '/dev/usb/scanner1',
				  '/dev/usb/scanner2',
				  'libusb:001:001',
				  'libusb:001:002',
				  'libusb:001:003',
				  'libusb:001:004',
				  'libusb:001:005',
				  'libusb:001:006',
				  'libusb:001:007',
				  'libusb:001:008',
				  'libusb:001:009',
				  'libusb:001:010', 
				  '/dev/sg0',
				  '/dev/sg1',
				  '/dev/sg2',
				  '/dev/sg3',
				  '/dev/sg4',
				  '/dev/parport0',
				  '/dev/parport1',
				  '/dev/parport2',
				  '/dev/pt_drv',
				  '/dev/ttyS0',
				  '/dev/ttyS1',
				  '/dev/ttyS2'], 
			 not_edit => 0, sort => 0 },
		       ],
		      ) or return 0;
	if ($port eq N("Auto-detect available ports")) {
	    $wait = $in->wait_message(N("Scannerdrake"),
				      N("Searching for scanners ..."));
	    my @d = scanner::detect();
	    undef $wait;
	    my @list = map {
		$_->{port} . " (" .
		    removeverticalbar($_->{val}{DESCRIPTION}) . ")";
	    } @d;
	    $port ||= $list[0];
	    $in->ask_from('Scannerdrake',
			  N("Please select the device where your %s is attached", removeverticalbar($model)),
			  [
			   { label => N("choose device"), 
			     val => \$port,
			     list => \@list,
			     not_edit => 1, sort => 0 },
			   ],
			  ) or return 0;
	    $port =~ s/^\s*([^\(\s]*)\s*\(.*$/$1/;
	    foreach (@d) {
		next if $_->{port} ne $port;
		$vendor = $_->{val}{vendor};
		$product = $_->{val}{id};
		last;
	    }
	}
    }
    ($vendor, $product) = scanner::get_usb_ids_for_port($port);
    my $firmware;
    if (grep { /FIRMWARELINE/ } @{$scanner::scannerDB->{$model}{lines}} ) {
	$firmware = installfirmware($model);
	return 0 if $firmware eq "///";
    }
    scanner::confScanner($model, $port, $vendor, $product, $firmware);
    $in->ask_warn(N("Congratulations!"),
		  N("Your %s has been configured.\nYou may now scan documents using \"XSane\" from Multimedia/Graphics in the applications menu.", removeverticalbar($model)));
    return 1;
}

sub quit() {
    $in->exit(0);
}

sub mainwindow {
    my @configured = @_;
    # main loop
    my $maindone;
    while (!$maindone) {
	# Generate list of configured scanners
	my $msg = do {
	    if (@configured) {
		my @scannerlist = 
		    map {
			my $entry = $_->{val}{DESCRIPTION};
			if_($entry, "  -  $entry\n");
		    } @configured;
		if (@scannerlist) {
		    my $main_msg = 
			@scannerlist > 1 ?
			N_("The following scanners\n\n%s\nare available on your system.\n") :
			N_("The following scanner\n\n%s\nis available on your system.\n");
		    sprintf($main_msg, join('', @scannerlist));
		} else {
		    N("There are no scanners found which are available on your system.\n");
		}
	    } else {
		N("There are no scanners found which are available on your system.\n");
	    }
	};
	my $buttonclicked;
	#- Show dialog
	if ($in->ask_from_
	    (
	     { 
		 title => N("Scannerdrake"),
		 messages => $msg,
		 ok => "",
		 cancel => "",
	     },
	     [
	      { val => N("Search for new scanners"),
		type => 'button',
		clicked_may_quit => sub {
		    $buttonclicked = "autoadd";
		    1;
		} },
	      { val => N("Add a scanner manually"),
		type => 'button',
		clicked_may_quit => sub {
		    $buttonclicked = "manualadd";
		    1;
		} },
	      ( (grep { $_->{val}{FIRMWARELINE} } @configured) ?
		{ val => N("Install/Update firmware files"),
		  type => 'button',
		  clicked_may_quit => sub {
		      $buttonclicked = "firmware";
		      1;
		  } } : () ),
	      { val => N("Scanner sharing"),
		type => 'button',
		clicked_may_quit => sub {
		    $buttonclicked = "sharing";
		    1;
		} },
	      { val => N("Quit"),
		type => 'button',
		clicked_may_quit => sub {
		    $buttonclicked = "quit";
		    1;
		} },
	      ]
	     )
	    ) {
	    my $changed = 0;
	    if ($buttonclicked eq "autoadd") {
		# Do scanner auto-detection
		my $wait =
		    $in->wait_message(N("Scannerdrake"),
				      N("Searching for configured scanners ..."));
		@configured = scanner::configured();
		$wait = 
		    $in->wait_message(N("Scannerdrake"),
				      N("Searching for new scanners ..."));
		my @f = scanner::detect(@configured);
		$wait = undef;
		if (@f) { 
		    $changed = auto();
		}
	    } elsif ($buttonclicked eq "manualadd") {
		# Show dialogs to manually add a scanner
		$changed = manual();
	    } elsif ($buttonclicked eq "sharing") {
		# Show dialog to set up scanner sharing
		$changed = sharewindow(@configured);
	    } elsif ($buttonclicked eq "firmware") {
		# Show dialog to select the firmware file
		updatefirmware(@configured);
	    } elsif ($buttonclicked eq "quit") {
		# We have clicked "Quit"
		$maindone = 1;
	    }
	    if ($changed) {
		my $_wait =
		    $in->wait_message(N("Scannerdrake"),
				      N("Re-generating list of configured scanners ..."));
		@configured = scanner::configured();
	    }
	} else {
	    # Cancel clicked
	    $maindone = 1;
	}
    }
}

sub makeexportmenues {
    my @exports = @_;
    my %menuexports = map {
	($_ eq '+' ? N("All remote machines") : $_) => $_;
    } map {
	# Remove comments and blank lines
	(/^\s*($|#)/ ? () : chomp_($_));
    } @exports;
    my %menuexports_inv = reverse %menuexports;
    return (\%menuexports, \%menuexports_inv);
}

sub makeimportmenues {
    my @imports = @_;
    my %menuimports = map {
	($_ eq 'localhost' ? N("This machine") : $_) => $_;
    } map {
	# Remove comments and blank lines
	if_(!/^\s*($|#)/, chomp_($_));
    } @imports;
    my %menuimports_inv = reverse %menuimports;
    return (\%menuimports, \%menuimports_inv);
}

sub sharewindow {
    my @_configured = @_;
    # Read list of hosts to where to export the local scanners
    my @exports = cat_("/etc/sane.d/saned.conf");
    my ($menuexports, $menuexports_inv) =
	makeexportmenues(@exports);
    # Read list of hosts from where to import scanners
    my @imports = cat_("/etc/sane.d/net.conf");
    my ($menuimports, $menuimports_inv) =
	makeimportmenues(@imports);
    # Is saned running?
    my $sanedrunning = services::starts_on_boot("saned");
    my $oldsanedrunning = $sanedrunning;
    # Is the "net" SANE backend active
    my $netbackendactive = grep { /^\s*net\s*$/ }
      cat_("/etc/sane.d/dll.conf");
    my $oldnetbackendactive = $netbackendactive;
    # Set this to 1 to tell the caller that the list of locally available
    # scanners has changed (Here if the SANE client configuration has
    # changed)
    my $changed = 0;
    my $importschanged = 0;
    # main loop
    my $maindone;
    while (!$maindone) {
	my $buttonclicked;
	#- Show dialog
	if ($in->ask_from_
	    (
	     { 
		 title => N("Scannerdrake"),
		 messages => N("Here you can choose whether the scanners connected to this machine should be accessable by remote machines and by which remote machines.") .
                     N("You can also decide here whether scanners on remote machines should be made available on this machine."),
	     },
	     [
	      { text => N("The scanners on this machine are available to other computers"), type => 'bool',
		val => \$sanedrunning },
	      { val => N("Scanner sharing to hosts: ") .
		    (keys %$menuexports > 0 ?
		     (keys %$menuexports > 2 ?
		      join(", ", (keys %$menuexports)[0,1]) . " ..." :
		      join(", ", keys %$menuexports)) :
		     N("No remote machines")), 
		type => 'button',
		clicked_may_quit => sub {
		    $buttonclicked = "exports";
		    1;
		},
		disabled => sub {
		    !$sanedrunning;
		} },
	      { text => N("Use scanners on remote computers"),
		type => 'bool',
		val => \$netbackendactive },
	      { val => N("Use the scanners on hosts: ") .
		    (keys %$menuimports > 0 ?
		     (keys %$menuimports > 2 ?
		      join(", ", (keys %$menuimports)[0,1]) . " ..." :
		      join(", ", keys %$menuimports)) :
		     N("No remote machines")), 
		type => 'button',
		clicked_may_quit => sub {
		    $buttonclicked = "imports";
		    1;
		},
		disabled => sub {
		    !$netbackendactive;
		} },
	      ]
	     )
	    ) {
	    if ($buttonclicked eq "exports") {
		# Show dialog to add hosts to share scanners to
		my $subdone = 0;
		my $choice;
		while (!$subdone) {
		    my @list = keys %$menuexports;
		    # Entry should be edited when double-clicked
		    $buttonclicked = "edit";
		    $in->ask_from_
			(
			 { title => N("Sharing of local scanners"),
			   messages => N("These are the machines on which the locally connected scanner(s) should be available:"),
			   ok => "",
			   cancel => "",
		         },
			 # List the hosts
			 [ { val => \$choice, format => \&translate,
			     sort => 0, separator => "####",
			     tree_expanded => 1,
			     quit_if_double_click => 1,
			     allow_empty_list => 1,
			     list => \@list },
			   { val => N("Add host"), 
			     type => 'button',
			     clicked_may_quit => sub {
				 $buttonclicked = "add";
				 1; 
			     } },
			   { val => N("Edit selected host"), 
			     type => 'button',
			     clicked_may_quit => sub {
				 $buttonclicked = "edit";
				 1; 
			     },
			     disabled => sub {
				 return ($#list < 0);
			     } },
			   { val => N("Remove selected host"), 
			     type => 'button',
			     clicked_may_quit => sub {
				 $buttonclicked = "remove";
				 1; 
			     },
			     disabled => sub {
				 return ($#list < 0);
			     } },
			   { val => N("Done"), 
			     type => 'button',
			     clicked_may_quit => sub {
				 $buttonclicked = "";
				 $subdone = 1;
				 1; 
			     } },
			   ]
			 );
		    if ($buttonclicked eq "add" ||
			$buttonclicked eq "edit") {
			my ($hostchoice, $ip);
			if ($buttonclicked eq "add") {
			    # Use first entry as default for a new entry
			    $hostchoice = 
				N("Name/IP address of host:");
			} else {
			    if ($menuexports->{$choice} eq '+') {
				# Entry is "All hosts"
				$hostchoice = $choice;
			    } else {
				# Entry is a name/an IP address
				$hostchoice = 
				    N("Name/IP address of host:");
				$ip = $choice;
			    }
			}
			my @menu = (N("All remote machines"),
			            N("Name/IP address of host:"));
			# Show the dialog
			my $address;
			my $oldaddress = 
			    ($buttonclicked eq "edit" ?
			     $menuexports->{$choice} : "");
			if ($in->ask_from_
			    (
			     { title => N("Sharing of local scanners"),
			       messages => N("Choose the host on which the local scanners should be made available:"),
			       callbacks => {
				   complete => sub {
				       if ($hostchoice eq $menu[0]) {
					   $address = "+";
				       } elsif ($hostchoice eq $menu[1]) {
					   $address = $ip;
				       }
				       # Do not allow an empty address
				       if ($address !~ /\S/) {
					   $in->ask_warn(N("Error"), 
							 N("You must enter a host name or an IP address.\n"));
					   return (1,0);
				       }
				       # Strip off leading and trailing
				       # spaces
				       $address =~ s/^\s*(.*?)\s*$/$1/;
				       # Check whether item is duplicate
				       if ($address ne $oldaddress &&
					   member("$address\n",
						   @exports)) {
					   $in->ask_warn(N("Error"), 
							 N("This host is already in the list, it cannot be added again.\n"));
					   return (1,1);
				       }
				       return 0;
				   },
			       },
			   },
			     # List the host types
			     [ { val => \$hostchoice, format => \&translate,
				 type => 'list',
				 sort => 0,
				 list => \@menu },
			       { val => \$ip, 
				 disabled => sub {
				     $hostchoice ne 
					 N("Name/IP address of host:");
			         } },
			       ],
			     )) {
			    # OK was clicked, insert new item into the list
			    if ($buttonclicked eq "add") {
				handle_configs::set_directive(\@exports,
							      $address);
			    } else {
				handle_configs::replace_directive(\@exports,
								  $oldaddress,
								  $address);
			    }
			    # Refresh list of hosts
			    ($menuexports, $menuexports_inv) =
				makeexportmenues(@exports);
			    # Position the list cursor on the new/modified
			    # item
			    $choice = $menuexports_inv->{$address};
			}
		    } elsif ($buttonclicked eq "remove") {
			my $address = $menuexports->{$choice};
			handle_configs::remove_directive(\@exports,
							 $address);
			# Refresh list of hosts
			($menuexports, $menuexports_inv) =
			    makeexportmenues(@exports);
		    }
		}
	    } elsif ($buttonclicked eq "imports") {
		# Show dialog to add hosts on which the scanners should be
		# used
		my $subdone = 0;
		my $choice;
		while (!$subdone) {
		    my @list = keys %$menuimports;
		    # Entry should be edited when double-clicked
		    $buttonclicked = "edit";
		    $in->ask_from_
			(
			 { title => N("Usage of remote scanners"),
			   messages => N("These are the machines from which the scanners should be used:"),
			   ok => "",
			   cancel => "",
		         },
			 # List the hosts
			 [ { val => \$choice, format => \&translate,
			     sort => 0, separator => "####",
			     tree_expanded => 1,
			     quit_if_double_click => 1,
			     allow_empty_list => 1,
			     list => \@list },
			   { val => N("Add host"), 
			     type => 'button',
			     clicked_may_quit => sub {
				 $buttonclicked = "add";
				 1; 
			     } },
			   { val => N("Edit selected host"), 
			     type => 'button',
			     clicked_may_quit => sub {
				 $buttonclicked = "edit";
				 1; 
			     },
			     disabled => sub {
				 return ($#list < 0);
			     } },
			   { val => N("Remove selected host"), 
			     type => 'button',
			     clicked_may_quit => sub {
				 $buttonclicked = "remove";
				 1; 
			     },
			     disabled => sub {
				 return ($#list < 0);
			     } },
			   { val => N("Done"), 
			     type => 'button',
			     clicked_may_quit => sub {
				 $buttonclicked = "";
				 $subdone = 1;
				 1; 
			     } },
			   ]
			 );
		    if ($buttonclicked eq "add" ||
			$buttonclicked eq "edit") {
			my ($hostchoice, $ip);
			if ($buttonclicked eq "add") {
			    # Use first entry as default for a new entry
			    $hostchoice = 
				N("Name/IP address of host:");
			} else {
			    if ($menuimports->{$choice} eq 'localhost') {
				# Entry is "This machine"
				$hostchoice = $choice;
			    } else {
				# Entry is a name/an IP address
				$hostchoice = 
				    N("Name/IP address of host:");
				$ip = $choice;
			    }
			}
			my @menu = (N("This machine"),
			            N("Name/IP address of host:"));
			# Show the dialog
			my $address;
			my $oldaddress = 
			    ($buttonclicked eq "edit" ?
			     $menuimports->{$choice} : "");
			if ($in->ask_from_
			    (
			     { title => N("Sharing of local scanners"),
			       messages => N("Choose the host on which the local scanners should be made available:"),
			       callbacks => {
				   complete => sub {
				       if ($hostchoice eq $menu[0]) {
					   $address = 'localhost';
				       } elsif ($hostchoice eq $menu[1]) {
					   $address = $ip;
				       }
				       # Do not allow an empty address
				       if ($address !~ /\S/) {
					   $in->ask_warn(N("Error"), 
							 N("You must enter a host name or an IP address.\n"));
					   return (1,0);
				       }
				       # Strip off leading and trailing
				       # spaces
				       $address =~ s/^\s*(.*?)\s*$/$1/;
				       # Check whether item is duplicate
				       if ($address ne $oldaddress &&
					   member("$address\n",
						   @imports)) {
					   $in->ask_warn(N("Error"), 
							 N("This host is already in the list, it cannot be added again.\n"));
					   return (1,1);
				       }
				       return 0;
				   },
			       },
			   },
			     # List the host types
			     [ { val => \$hostchoice, format => \&translate,
				 type => 'list',
				 sort => 0,
				 list => \@menu },
			       { val => \$ip, 
				 disabled => sub {
				     $hostchoice ne 
					 N("Name/IP address of host:");
			         } },
			       ],
			     )) {
			    # OK was clicked, insert new item into the list
			    if ($buttonclicked eq "add") {
				handle_configs::set_directive(\@imports,
							      $address);
			    } else {
				handle_configs::replace_directive(\@imports,
								  $oldaddress,
								  $address);
			    }
			    $importschanged = 1;
			    # Refresh list of hosts
			    ($menuimports, $menuimports_inv) =
				makeimportmenues(@imports);
			    # Position the list cursor on the new/modified
			    # item
			    $choice = $menuimports_inv->{$address};
			}
		    } elsif ($buttonclicked eq "remove") {
			my $address = $menuimports->{$choice};
			handle_configs::remove_directive(\@imports,
							 $address);
			# Refresh list of hosts
			($menuimports, $menuimports_inv) =
			    makeimportmenues(@imports);
			$importschanged = 1;
		    }
		}
	    } else {
		# We have clicked "OK"
		$maindone = 1;
		if ($importschanged) {
		    $changed = 1;
		}
		# Write /etc/sane.d/saned.conf
		output("/etc/sane.d/saned.conf", @exports);
		# Write /etc/sane.d/net.conf
		output("/etc/sane.d/net.conf", @imports);
		# Turn on/off saned
		if ($sanedrunning != $oldsanedrunning) {
		    if ($sanedrunning) {
			# Make sure saned and xinetd is installed and 
			# running
			if (!files_exist('/usr/sbin/xinetd',
					 '/usr/sbin/saned')) {
			    if (!$in->do_pkgs->install('xinetd', 'saned')) {
				$in->ask_warn(N("Error"),
					      N("Could not install the packages needed to share your scanner(s).") . " " .
					      N("Your scanner(s) will not be available on the network."));
			    }
			}
			# Start saned and make sure that it gets started on
			# every boot
			services::start_service_on_boot("saned");
			services::start_service_on_boot("xinetd");
			services::restart("xinetd");
		    } else {
			# Stop saned and make sure that it does not get
			# started when booting
			services::do_not_start_service_on_boot("saned");
			services::restart("xinetd");
		    }
		}
		# Turn on/off "net" SANE backend
		if ($netbackendactive != $oldnetbackendactive) {
		    my @dllconf = cat_("/etc/sane.d/dll.conf");
		    if ($netbackendactive) {
			handle_configs::set_directive(\@dllconf, "net");
		    } else {
			handle_configs::comment_directive(\@dllconf, "net");
		    }
		    output("/etc/sane.d/dll.conf", @dllconf);
		    $changed = 1;
		}
	    }
	} else {
	    # Cancel clicked
	    $maindone = 1;
	}
    }
    return $changed;
}