#! /usr/bin/perl -w
#
# Copyright (C) 2008-2017 Alexis Bienvenue <paamc@passoire.fr>
#
# This file is part of Auto-Multiple-Choice
#
# Auto-Multiple-Choice is free software: you can redistribute it
# and/or modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation, either version 2 of
# the License, or (at your option) any later version.
#
# Auto-Multiple-Choice 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 Auto-Multiple-Choice.  If not, see
# <http://www.gnu.org/licenses/>.

use Getopt::Long;

use Gtk3 -init;

use Glib::Object::Introspection;

use XML::Simple;
use IO::File;
use IO::Select;
use POSIX qw/strftime/;
use Time::Local;
use Cwd;
use File::Spec::Functions qw/splitpath catpath splitdir catdir catfile rel2abs tmpdir/;
use File::Temp qw/ tempfile tempdir /;
use File::Copy;
use File::Path qw/remove_tree/;
use File::Find;
use Archive::Tar;
use Archive::Tar::File;
use Encode;
use Unicode::Normalize;
use I18N::Langinfo qw(langinfo CODESET);
use Locale::Language;
use Text::ParseWords;

use Module::Load;
use Module::Load::Conditional qw/check_install/;

use AMC::Path;
use AMC::Basic;
use AMC::State;
use AMC::Config;
use AMC::Data;
use AMC::DataModule::capture ':zone';
use AMC::DataModule::report ':const';
use AMC::Scoring;
use AMC::Gui::Prefs;
use AMC::Gui::Manuel;
use AMC::Gui::Association;
use AMC::Gui::Commande;
use AMC::Gui::Notes;
use AMC::Gui::Zooms;
use AMC::FileMonitor;
use AMC::Gui::WindowSize;

use utf8;

use Data::Dumper;

use constant {
    DOC_TITRE => 0,
    DOC_MAJ => 1,

    MEP_PAGE => 0,
    MEP_ID => 1,
    MEP_MAJ => 2,

    DIAG_ID => 0,
    DIAG_ID_BACK => 1,
    DIAG_MAJ => 2,
    DIAG_MAJ_NUM => 3,
    DIAG_EQM => 4,
    DIAG_EQM_BACK => 5,
    DIAG_DELTA => 6,
    DIAG_DELTA_BACK => 7,
    DIAG_ID_STUDENT => 8,
    DIAG_ID_PAGE => 9,
    DIAG_ID_COPY => 10,
    DIAG_SCAN_FILE => 11,

    INCONNU_FILE => 0,
    INCONNU_SCAN => 1,
    INCONNU_TIME => 2,
    INCONNU_TIME_N => 3,
    INCONNU_PREPROC => 4,

    PROJ_NOM => 0,
    PROJ_NOM_PLAIN => 1,
    PROJ_ICO => 2,

    MODEL_NOM => 0,
    MODEL_PATH => 1,
    MODEL_DESC => 2,

    COPIE_N => 0,

    TEMPLATE_FILES_PATH => 0,
    TEMPLATE_FILES_FILE => 1,

    EMAILS_SC => 0,
    EMAILS_NAME => 1,
    EMAILS_EMAIL => 2,
    EMAILS_ID => 3,
    EMAILS_STATUS => 4,

    ATTACHMENTS_FILE => 0,
    ATTACHMENTS_NAME => 1,
    ATTACHMENTS_FOREGROUND => 2,
  };

my $libnotify_error='';

Gtk3::IconTheme::get_default->prepend_search_path(amc_specdir('icons'));
Gtk3::Window::set_default_icon_list([map { Gtk3::IconTheme::get_default->load_icon("auto-multiple-choice",$_,"force-svg") } (8,16,32,48,64,128)]);

use_gettext;
use_amc_plugins();

POSIX::setlocale(&POSIX::LC_NUMERIC,"C");

my $debug=0;
my $debug_file='';
my $do_nothing=0;

my $profile='';
my $project_dir='';

GetOptions("debug!"=>\$debug,
	   "debug-file=s"=>\$debug_file,
	   "profile=s"=>\$profile,
	   "p=s"=>\$project_dir,
           "do-nothing!"=>\$do_nothing,
          );

sub set_debug_mode {
  my ($debug)=@_;
  set_debug($debug);
  if($debug) {
    my $date=strftime("%c",localtime());
    debug ('#' x 40);
    debug "# DEBUG - $date";
    debug ('#' x 40);
    debug "GUI module is located at ".__FILE__;
  }
}

if($debug || $debug_file) {
  set_debug_mode($debug_file || 'new');
  print "DEBUG ==> ".debug_file()."\n";
}

debug_pm_version("Gtk3");

my %w=();

my $glade_base=__FILE__;
$glade_base =~ s/\.p[ml]$/-/i;

my $home_dir=Glib::get_home_dir();
my $encodage_systeme=langinfo(CODESET());

my $shortcuts=AMC::Path::new(home_dir=>$home_dir);
my $config=AMC::Config::new(shortcuts=>$shortcuts,home_dir=>$home_dir,profile=>$profile);
my $prefs=AMC::Gui::Prefs::new(shortcuts=>$shortcuts,alternate_w=>\%w,config=>$config);

my $monitor=AMC::FileMonitor->new();

sub hex_color {
  my $s=shift;
  return(Gtk3::Gdk::Color::parse($s)->to_string());
}

my @export_modules=@{$config->{export_modules}};

# Reads filter plugins list

my @filter_modules=perl_module_search('AMC::Filter::register');
for my $m (@filter_modules) {
  load("AMC::Filter::register::$m");
}
@filter_modules=sort { "AMC::Filter::register::$a"->weight
			 <=> "AMC::Filter::register::$b"->weight }
  @filter_modules;

sub best_filter_for_file {
  my ($file)=@_;
  my $mmax='';
  my $max=-10;
  for my $m (@filter_modules) {
    my $c="AMC::Filter::register::$m"->claim($file);
    if($c>$max) {
      $max=$c;
      $mmax=$m;
    }
  }
  return($mmax);
}

# -----------------

## Notifications

sub notify_end_of_work {
  my ($action,$message)=@_;
  if($config->get('notify_'.$action)) {
    if($config->get('notify_desktop')) {
      if($libnotify_error) {
	debug "Notification ignored: $libnotify_error";
      } else {
	eval {
          my $notification=Notify::Notification->new('Auto Multiple Choice',$message,
                                                     '/usr/share/auto-multiple-choice/icons/auto-multiple-choice.svg');
          $notification->show;
        };
        $libnotify_error=$@;
      }
    }
    if($config->get('notify_command')) {
      my @cmd=map { s/[%]m/$message/g;s/[%]a/$action/g;$_; }
	quotewords('\s+',0,$config->get('notify_command'));
      if(commande_accessible($cmd[0])) {
	commande_parallele(@cmd);
      } else {
	debug "ERROR: command '$cmd[0]' not found when trying to notify";
      }
    }
  }
}

# Test whether the magick perl package is installed

sub test_magick {
  if(!magick_perl_module(1)) {
    my $dialog = Gtk3::MessageDialog
      ->new($w{'main_window'},
            'destroy-with-parent',
            'error','ok','');
    $dialog->set_markup(
			__"None of the perl modules <b>Graphics::Magick</b> and <b>Image::Magick</b> are installed: AMC won't work properly!"
		       );
    $dialog->run;
    $dialog->destroy;
  }
}

# tests if debian package auto-multiple-choice-common is installed but
# not auto-multiple-choice...

sub deb_is_installed {
  my ($package_name)=@_;
  my $v='';
  open(QUERY,"-|","dpkg-query","-W",'--showformat=${Version}\n',$package_name);
  while(<QUERY>) {
    $v=$1 if(/^\s*([^\s]+)\s*$/);
  }
  close(QUERY);
  return($v);
}

sub test_debian_amc {
  if(commande_accessible("dpkg-query")) {
    if(deb_is_installed("auto-multiple-choice-common") &&
       !deb_is_installed("auto-multiple-choice")) {
      debug "ERROR: auto-multiple-choice package not installed!";
      my $dialog = Gtk3::MessageDialog
	->new($w{'main_window'},
              'destroy-with-parent',
              'error','ok','');
      $dialog->set_markup(
			  __"The package <i>auto-multiple-choice-common</i> is installed, but not <i>auto-multiple-choice</i>.\n<b>AMC won't work properly until you install auto-multiple-choice package!</b>"
			  );
      $dialog->run;
      $dialog->destroy;
    }
  }
}

sub check_for_tmp_disk_space {
  my ($needs_mo)=@_;
  my $tmp_path=tmpdir();
  my $space=free_disk_mo($tmp_path);
  if(defined($space) && $space < $needs_mo) {
    my $dialog = Gtk3::MessageDialog
	->new($w{'main_window'},
              'destroy-with-parent',
              'error','ok','');
    $dialog->set_markup(
                        sprintf((__"There is too little space left in the temporary disk directory (%s). <b>Please clean this directory and try again.</b>"),
                                $tmp_path)
                       );
    $dialog->run;
    $dialog->destroy;
    return 0;
  }
  return 1;
}

# annulation apprentissage

sub annule_apprentissage {
    my $dialog = Gtk3::MessageDialog
	->new($w{'main_window'},
              'destroy-with-parent',
              'question','yes-no','');
    $dialog->set_markup(
# Explains that some dialogs are shown only to learn AMC, only once by default (first part).
			  __("Several dialogs try to help you be at ease handling AMC.")." ".
# Explains that some dialogs are shown only to learn AMC, only once by default (second part). %s will be replaced with the text "Show this message again next time" that is written along the checkbox allowing the user to keep these learning message next time.
			  sprintf(__"Unless you tick the \"%s\" box, they are shown only once.",
# Explains that some dialogs are shown only to learn AMC, only once by default. This is the message shown along the checkbox allowing the user to keep these learning message next time.
				  __"Show this message again next time")." ".
# Explains that some dialogs are shown only to learn AMC, only once by default (third part). If you answer YES here, all these dialogs will be shown again.
			  __"Do you want to forgot which dialogs you have already seen and ask to show all of them next time they should appear ?"
			  );
    my $reponse=$dialog->run;
    $dialog->destroy;
    if($reponse eq 'yes') {
	debug "Clearing learning states...";
	$config->set("state:apprentissage",{});
	$config->save();
    }
}

# Reset projects directory to standard one

$config->set('global:rep_projets',
             $config->get_absolute('projects_home'));

# Warn if Notify is not available
sub test_libnotify {
  my $initted=eval { Notify::is_initted() };
  if(!$initted) {
    eval {
      Glib::Object::Introspection->setup(basename => 'Notify', version => '0.7', package => 'Notify') if(!defined($initted));
      # Set application name for notifications
      Notify::init('Auto Multiple Choice');
    };
    $libnotify_error=$@;

    if($libnotify_error && $config->get('notify_desktop')) {
      debug "libnotify loading error: $libnotify_error";
      my $dialog = Gtk3::MessageDialog
	->new($w{'main_window'},
              'destroy-with-parent',
              'warning','ok','');
      $dialog->set_markup(
			  __"Please install <b>libnotify</b> to make desktop notifications available."
			 );
      $dialog->run;
      $dialog->destroy;
      $config->set('notify_desktop','');
    }
  }
}

# goes to a specific directory if the project directory is given as a
# command-line option

utf8::downgrade($project_dir);
if(-f $project_dir) {
  $project_dir =~ s/\/?options\.xml$//;
}
$project_dir =~ s/\/+$//;

if(-d $project_dir) {
  debug "Opening project from directory $project_dir ...";
  $project_dir=Cwd::realpath($project_dir);
  my ($v,$d,$f)=splitpath($project_dir);
  my $r=catpath($v,$d,'');
  $r =~ s/\/+$//;
  $config->set('rep_projets',$r);
  @ARGV=$f;
}

# creates projets and models directories if needed (if not present,
# Edit/Parameters can be disrupted)

for my $k (qw/rep_projets rep_modeles/) {
  my $path=$config->get($k);
  if(-e $path) {
    debug "WARNING: $path ($k) is not a directory!" if(!-d $path);
  } else {
    mkdir($path);
  }
}

sub set_projects_home {
  my ($p)=@_;

  $config->set('rep_projets',$p);
  $shortcuts->set(projects_path=>$p);
}

set_projects_home($config->get('rep_projets'));

#############################################################################

my %projet=();

sub bon_encodage {
    my ($type)=@_;
    return($config->get("encodage_$type")
	   || $config->get("defaut_encodage_$type")
	   || "UTF-8");
}

sub csv_build_0 {
  my ($k,@default)=@_;
  push @default,grep { $_ } map { s/^\s+//;s/\s+$//;$_; }
    split(/,+/,$config->get('csv_'.$k.'_headers'));
  return("(".join("|",@default).")");
}

sub csv_build_name {
  return(csv_build_0('surname','nom','surname').' '
	 .csv_build_0('name','prenom','name'));
}

sub id2file {
    my ($id,$prefix,$extension)=(@_);
    $id =~ s/\+//g;
    $id =~ s/\//-/g;
    return($config->get_absolute('cr')."/$prefix-$id.$extension");
}

sub is_local {
    my ($f,$proj)=@_;
    my $prefix=$config->get('rep_projets')."/";
    $prefix .= $projet{'nom'}."/" if($proj);
    utf8::downgrade($prefix);
    if(defined($f)) {
	return($f !~ /^[\/%]/
	       || $f =~ /^$prefix/
	       || $f =~ /[\%]PROJET\//);
    } else {
	return('');
    }
}

sub fich_options {
    my ($nom,$rp)=@_;
    $rp=$config->get('rep_projets') if(!$rp);
    $rp.="/$nom/options.xml";
    utf8::downgrade($rp);
    return($rp);
}

sub moteur_latex {
  return($config->get('moteur_latex_b')
         || $config->get('defaut_moteur_latex_b')
        );
}

sub read_glade {
  my ($main_widget,@widgets)=@_;
  my $g=Gtk3::Builder->new();
  my $glade_file=$glade_base.$main_widget.".glade";
  debug "Reading glade file ".$glade_file;
  $g->set_translation_domain('auto-multiple-choice');
  $g->add_from_file($glade_file);
  for my $i ($main_widget,@widgets) {
    $w{$i}=$g->get_object($i);
    if (ref($w{$i}) =~ /::(FileChooser|About)?Dialog$/
        && !$w{$i}->is_visible()) {
      debug "Found modal dialog: $i";
      $w{$i}->set_transient_for($w{'main_window'});
      $w{$i}->set_modal(1);
    }
    if ($w{$i}) {
      $w{$i}->set_name($i) if($i !~ /^(apropos)$/);
    } else {
      debug_and_stderr "WARNING: Object $i not found in $main_widget glade file.";
    }
  }
  $g->connect_signals(undef);
  return($g);
}

# As a workaround for Bug #93 these widgets are disabled when Preferences window is opened.
# Since Bug #93 only affects Ubuntu 12.04 LTS, this workaround can be safely removed when
# Ubuntu 12.04 LTS reaches its end of life date i.e. April 2017

my @widgets_disabled_when_preferences_opened=(qw/menu_popover/);

my @widgets_only_when_opened=(qw/cleanup_menu menu_projet_enreg menu_projet_modele/);

my $gui=read_glade('main_window',
		   qw/onglets_projet onglet_preparation
    documents_popover toggle_documents
    but_question but_solution but_indiv_solution but_catalog doc_line1
    prepare_docs prepare_layout prepare_src
    menu_popover header_bar
    state_layout state_layout_label
    state_docs state_docs_label
    state_unrecognized state_unrecognized_label
    state_marking state_marking_label state_assoc state_assoc_label
    state_overwritten state_overwritten_label
    button_edit_src
    button_unrecognized button_show_missing
    edition_latex
    onglet_notation onglet_saisie onglet_reports
    log_general commande avancement annulation button_mep_warnings
    liste_filename liste_edit liste_setfile liste_refresh
    menu_debug
    menu_columns
    toggle_column_updated toggle_column_mse toggle_column_sensitivity toggle_column_file
    diag_tree state_capture state_capture_label
    maj_bareme regroupement_corriges
    groupe_model
    pref_assoc_c_assoc_code pref_assoc_c_liste_key
    export_c_format_export
    export_c_export_sort export_cb_export_include_abs
    config_export_modules standard_export_options
    notation_c_regroupement_type notation_c_regroupement_compose
    pref_prep_s_nombre_copies pref_prep_c_filter
    /,@widgets_only_when_opened,@widgets_disabled_when_preferences_opened);

# Grid lines are not well-positioned in RTL environments, I don't know
# why... so I remove them.
if($w{'main_window'}->get_direction() eq 'rtl') {
    debug "RTL mode: removing vertical grids";
    for(qw/documents diag inconnu/) {
	my $w=$gui->get_object($_.'_tree');
	$w->set_grid_lines('horizontal') if($w);
    }
}

$w{'commande'}->hide();

sub debug_set {
    $debug=$w{'menu_debug'}->get_active;
    debug "DEBUG MODE : OFF" if(!$debug);
    set_debug_mode($debug);
    if($debug && !$do_nothing) {
	debug "DEBUG MODE : ON";

	my $dialog = Gtk3::MessageDialog
	    ->new($w{'main_window'},
		  'destroy-with-parent',
		  'info','ok',
# TRANSLATORS: Message when switching to debugging mode.
		  __("Debugging mode.")." "
# TRANSLATORS: Message when switching to debugging mode. %s will be replaced with the path of the log file.
		  .sprintf(__"Debugging informations will be written in file %s.",AMC::Basic::debug_file()));
	$dialog->run;
	$dialog->destroy;
    }
    Glib::Timeout->add
        (500,sub { $w{menu_popover}->hide(); return(0); });
}

$w{'menu_debug'}->set_active($debug);

sub open_menu {
  $w{menu_popover}->show_all();
}

# add doc list menu

my $docs_menu=Gtk3::Menu->new();

my @doc_langs=();

my $hdocdir=amc_specdir('doc/auto-multiple-choice')."/html/";
if(opendir(DOD,$hdocdir)) {
    push @doc_langs,map { s/auto-multiple-choice\.//;$_; } grep { /auto-multiple-choice\...(_..)?/ } readdir(DOD);
    closedir(DOD);
} else {
    debug("DOCUMENTATION : Can't open directory $hdocdir: $!");
}

# TRANSLATORS: One of the documentation languages.
my %ltext_loc=('French'=>__"French",
# TRANSLATORS: One of the documentation languages.
	       'English'=>__"English",
# TRANSLATORS: One of the documentation languages.
	       'Japanese'=>__"Japanese",
	       );
###

sub dialogue_apprentissage {
    my ($key,$type,$buttons,$force,@oo)=@_;
    my $resp='';
    $type='info' if(!$type);
    $buttons='ok' if(!$buttons);
    if($force || !$config->get("apprentissage/$key")) {
      my $garde;
      my $dialog = Gtk3::MessageDialog
	->new($w{'main_window'},
              'destroy-with-parent',
              $type,$buttons,'');
      $dialog->set_markup(@oo);

      if(!$force) {
	$garde=Gtk3::CheckButton->new(__"Show this message again next time");
	$garde->set_active(0);
	$garde->set_can_focus(0);

	$dialog->get_content_area()->add($garde);
      }

      $dialog->show_all();

      $resp=$dialog->run;

      if(!($force || $garde->get_active())) {
	debug "Learning : $key";
        $config->set("apprentissage/$key",1);
      }

      $dialog->destroy;
    }
    return($resp);
}

### COPIES

my $copies_store = Gtk3::ListStore->new ('Glib::String');

### FILES FOR TEMPLATE

my $template_files_store = Gtk3::TreeStore->new ('Glib::String',
						 'Glib::String');

### Unrecognized scans

my $inconnu_store = Gtk3::ListStore->new ('Glib::String','Glib::String',
					  'Glib::String','Glib::String',
					  'Glib::String');

$inconnu_store->set_sort_column_id(INCONNU_TIME_N,GTK_SORT_ASCENDING);

### modele EMAILS

my $emails_store=Gtk3::ListStore->new ('Glib::String',
				       'Glib::String',
				       'Glib::String',
				       'Glib::String',
				       'Glib::String',
				      );

### modele EMAILS

my $attachments_store=Gtk3::ListStore->new ('Glib::String',
					    'Glib::String',
					    'Glib::String',
					   );

### modele DIAGNOSTIQUE SAISIE

my $diag_store;

sub new_diagstore {
  $diag_store
    = Gtk3::ListStore->new ('Glib::String',
			    'Glib::String',
			    'Glib::String',
			    'Glib::String',
			    'Glib::String',
			    'Glib::String',
			    'Glib::String',
			    'Glib::String',
			    'Glib::String',
			    'Glib::String',
			    'Glib::String',
			    'Glib::String');
  $diag_store->set_sort_func(DIAG_EQM,\&sort_num,DIAG_EQM);
  $diag_store->set_sort_func(DIAG_DELTA,\&sort_num,DIAG_DELTA);
  $diag_store->set_sort_func(DIAG_SCAN_FILE,\&sort_string,DIAG_SCAN_FILE);
  $diag_store->set_sort_func(DIAG_ID,\&sort_from_columns,
			     [{'type'=>'n','col'=>DIAG_ID_STUDENT},
			      {'type'=>'n','col'=>DIAG_ID_COPY},
			      {'type'=>'n','col'=>DIAG_ID_PAGE},
			     ]);
}

sub sort_diagstore {
  $diag_store->set_sort_column_id(DIAG_ID,GTK_SORT_ASCENDING);
}

sub show_diagstore {
  $w{'diag_tree'}->set_model($diag_store);
}

new_diagstore();
sort_diagstore();
show_diagstore();

my %capture_column=();

$renderer=Gtk3::CellRendererText->new;
# TRANSLATORS: This is the title of the column containing student/copy identifier in the table showing the results of data captures.
$column = Gtk3::TreeViewColumn->new_with_attributes (__"identifier",
						     $renderer,
						     text=> DIAG_ID,
						     'background'=> DIAG_ID_BACK);
$column->set_sort_column_id(DIAG_ID);
$w{'diag_tree'}->append_column ($column);

$renderer=Gtk3::CellRendererText->new;
# TRANSLATORS: This is the title of the column containing data capture date/time in the table showing the results of data captures.
$capture_column{updated} = Gtk3::TreeViewColumn->new_with_attributes (__"updated",
						     $renderer,
						     text=> DIAG_MAJ);
$capture_column{updated}->set_sort_column_id(DIAG_MAJ_NUM);
$w{'diag_tree'}->append_column ($capture_column{updated});

$renderer=Gtk3::CellRendererText->new;
# TRANSLATORS: This is the title of the column containing Mean Square Error Distance (some kind of mean distance between the location of the four corner marks on the scan and the location where they should be if the scan was not distorted at all) in the table showing the results of data captures.
$capture_column{mse} = Gtk3::TreeViewColumn->new_with_attributes (__"MSE",
						     $renderer,
						     'text'=> DIAG_EQM,
						     'background'=> DIAG_EQM_BACK);
$capture_column{mse}->set_sort_column_id(DIAG_EQM);
$w{'diag_tree'}->append_column ($capture_column{mse});

$renderer=Gtk3::CellRendererText->new;
# TRANSLATORS: This is the title of the column containing so-called "sensitivity" (an indicator telling the user if the darkness ratio of some boxes on the page are very near the threshold. A great value tells that some darkness ratios are very near the threshold, so that the capture is very sensitive to the threshold. A small value is a good thing) in the table showing the results of data captures.
$capture_column{sensitivity} = Gtk3::TreeViewColumn->new_with_attributes (__"sensitivity",
						     $renderer,
						     'text'=> DIAG_DELTA,
						     'background'=> DIAG_DELTA_BACK);
$capture_column{sensitivity}->set_sort_column_id(DIAG_DELTA);
$w{'diag_tree'}->append_column ($capture_column{sensitivity});

$renderer=Gtk3::CellRendererText->new;
$capture_column{file} = Gtk3::TreeViewColumn->new_with_attributes (__"scan file",
                                                                   $renderer,
                                                                   'text'=> DIAG_SCAN_FILE);
$capture_column{file}->set_sort_column_id(DIAG_SCAN_FILE);
$w{'diag_tree'}->append_column ($capture_column{file});

$w{'diag_tree'}->get_selection->set_mode(GTK_SELECTION_MULTIPLE);

# columns to be shown...

sub toggle_column {
  my ($item)=@_;
  if($item->get_name() =~ /toggle_column_(.*)/) {
    my $type=$1;
    my $checked=$item->get_active();
    $capture_column{$type}->set_visible($checked);
  } else {
    debug "ERROR: unknown toggle_column name: ".$item->get_name();
  }
}

# Columns that should be hidden at startup:

for my $c (qw/updated file/) {
  $w{"toggle_column_".$c}->set_active(0);
}

# rajouter a partir de Encode::Supported
# TRANSLATORS: for encodings
my $encodages=[{qw/inputenc latin1 iso ISO-8859-1/,'txt'=>'ISO-8859-1 ('.__("Western Europe").')'},
# TRANSLATORS: for encodings
	       {qw/inputenc latin2 iso ISO-8859-2/,'txt'=>'ISO-8859-2 ('.__("Central Europe").')'},
# TRANSLATORS: for encodings
	       {qw/inputenc latin3 iso ISO-8859-3/,'txt'=>'ISO-8859-3 ('.__("Southern Europe").')'},
# TRANSLATORS: for encodings
	       {qw/inputenc latin4 iso ISO-8859-4/,'txt'=>'ISO-8859-4 ('.__("Northern Europe").')'},
# TRANSLATORS: for encodings
	       {qw/inputenc latin5 iso ISO-8859-5/,'txt'=>'ISO-8859-5 ('.__("Cyrillic").')'},
# TRANSLATORS: for encodings
	       {qw/inputenc latin9 iso ISO-8859-9/,'txt'=>'ISO-8859-9 ('.__("Turkish").')'},
# TRANSLATORS: for encodings
	       {qw/inputenc latin10 iso ISO-8859-10/,'txt'=>'ISO-8859-10 ('.__("Northern").')'},
# TRANSLATORS: for encodings
	       {qw/inputenc utf8x iso UTF-8/,'txt'=>'UTF-8 ('.__("Unicode").')'},
	       {qw/inputenc cp1252 iso cp1252/,'txt'=>'Windows-1252',
		alias=>['Windows-1252','Windows']},
# TRANSLATORS: for encodings
	       {qw/inputenc applemac iso MacRoman/,'txt'=>'Macintosh '.__"Western Europe"},
# TRANSLATORS: for encodings
	       {qw/inputenc macce iso MacCentralEurRoman/,'txt'=>'Macintosh '.__"Central Europe"},
	       ];

sub get_enc {
    my ($txt)=@_;
    for my $e (@$encodages) {
	return($e) if($e->{'inputenc'} =~ /^$txt$/i ||
		      $e->{'iso'} =~ /^$txt$/i);
	if($e->{'alias'}) {
	    for my $a (@{$e->{'alias'}}) {
		return($e) if($a =~ /^$txt$/i);
	    }
	}
    }
    return('');
}

# TRANSLATORS: you can omit the [...] part, just here to explain context
my $cb_model_vide_key=cb_model(''=>__p"(none) [No primary key found in association list]");
# TRANSLATORS: you can omit the [...] part, just here to explain context
my $cb_model_vide_code=cb_model(''=>__p"(none) [No code found in LaTeX file]");

my @printing_methods=();
for my $m ({name=>"CUPS",description=>"CUPS"},
	   {name=>"CUPSlp",description=>"CUPS (via lp)"},
	  ) {
  my $mod="AMC::Print::".lc($m->{name});
  load($mod);
  my $error=$mod->check_available();
  if(! $error) {
    push @printing_methods,$m->{name},$m->{description};
    if(!$config->get("methode_impression")) {
      $config->set("global:methode_impression",$m->{name});
      debug "Switching to printing method <$m->{name}>.";
    }
  } else {
    if($config->get("methode_impression") eq $m->{name}) {
      $config->set("global:methode_impression",'');
      debug "Printing method <$m->{name}> is not available: $error";
    }
  }
}
if(!$config->get("methode_impression")) {
  $config->set("global:methode_impression",'commande');
  debug "Switching to printing method <commande>.";
}

push @printing_methods,
# TRANSLATORS: One of the printing methods: use a command (This is not the command name itself). This is a menu entry.
  'commande',__("command"),
# TRANSLATORS: One of the printing methods: print to files. This is a menu entry.
  'file',__("to files")
  ;

# TRANSLATORS: One of the rounding method for marks. This is a menu entry.
my $rounding_store=cb_model('inf',__"floor",
# TRANSLATORS: One of the rounding method for marks. This is a menu entry.
                            'normal',__"rounding",
# TRANSLATORS: One of the rounding method for marks. This is a menu entry.
                            'sup',__"ceiling");

$prefs->store_register(
# TRANSLATORS: One option for decimal point: use a comma. This is a menu entry
    'delimiteur_decimal'=>cb_model(',',__", (comma)",
# TRANSLATORS: One option for decimal point: use a point. This is a menu entry.
				   '.',__". (dot)"),
    'note_arrondi'=>$rounding_store,
    'defaut_note_arrondi'=>$rounding_store,
    'methode_impression'=>cb_model(@printing_methods),
# TRANSLATORS: you can omit the [...] part, just here to explain context
    'sides'=>cb_model('one-sided',__p("one sided [No two-sided printing]"),
# TRANSLATORS: One of the two-side printing types. This is a menu entry.
		      'two-sided-long-edge',__"long edge",
# TRANSLATORS: One of the two-side printing types. This is a menu entry.
		      'two-sided-short-edge',__"short edge"),
    'encodage_latex'=>cb_model(map { $_->{'iso'}=>$_->{'txt'} }
			       (@$encodages)),
# TRANSLATORS: you can omit the [...] part, just here to explain context
    'manuel_image_type'=>cb_model('ppm'=>__p("(none) [No transitional image type (direct processing)]"),
				  'xpm'=>'XPM',
				  'gif'=>'GIF'),
    'liste_key'=>$cb_model_vide_key,
    'assoc_code'=>$cb_model_vide_code,
    'format_export'=>cb_model(map { $_=>"AMC::Export::register::$_"->name() } (@export_modules)),
    'filter'=>cb_model(map { $_=>"AMC::Filter::register::$_"->name() } (@filter_modules)),
# TRANSLATORS: One of the actions that can be done after exporting the marks. Here, do nothing more. This is a menu entry.
    'after_export'=>cb_model(""=>__"that's all",
# TRANSLATORS: One of the actions that can be done after exporting the marks. Here, open the exported file. This is a menu entry.
			     "file"=>__"open the file",
# TRANSLATORS: One of the actions that can be done after exporting the marks. Here, open the directory where the file is. This is a menu entry.
			     "dir"=>__"open the directory",
    ),
# TRANSLATORS: you can omit the [...] part, just here to explain context
    'annote_position'=>cb_model("none"=>__p("(none) [No annotation position (do not write anything)]"),
# TRANSLATORS: One of the possible location for questions scores on annotated completed answer sheet: in one margin. This is a menu entry.
				"marge"=>__"in one margin",
# TRANSLATORS: One of the possible location for questions scores on annotated completed answer sheet: in one of the two margins. This is a menu entry.
				"marges"=>__"in the margins",
# TRANSLATORS: One of the possible location for questions scores on annotated completed answer sheet: near the boxes. This is a menu entry.
				"case"=>__"near boxes",
# TRANSLATORS: One of the possible location for questions scores on annotated completed answer sheet: in the zones defined in the source file
				"zones"=>__"where defined in the source",
    ),
# TRANSLATORS: One of the possible sorting criteria for students in the exported spreadsheet with scores: the student name. This is a menu entry.
    'export_sort'=>cb_model("n"=>__"name",
# TRANSLATORS: One of the possible sorting criteria for students in the exported spreadsheet with scores: the student sheet number. This is a menu entry.
			    "i"=>__"exam copy number",
# TRANSLATORS: One of the possible sorting criteria for students in the exported spreadsheet with scores: the line where one can find this student in the students list file. This is a menu entry.
			    "l"=>__"line in students list",
# TRANSLATORS: you can omit the [...] part, just here to explain context
# One of the possible sorting criteria for students in the exported spreadsheet with scores: the student mark. This is a menu entry.
			    "m"=>__p("mark [student mark, for sorting]"),
			    ),
    'embedded_format'=>cb_model('png'=>'PNG',
				'jpeg'=>'JPEG'),
# TRANSLATORS: One of the possible way to group annotated answer sheets together to PDF files: make one PDF file per student, with all his pages. This is a menu entry.
    'regroupement_type'=>cb_model('STUDENTS'=>__"One file per student",
# TRANSLATORS: One of the possible way to group annotated answer sheets together to PDF files: make only one PDF with all students sheets. This is a menu entry.
				  'ALL'=>__"One file for all students",
    ),
# TRANSLATORS: One of the possible way to annotate answer sheets: here we only select pages where the student has written something (in separate answer sheet mode, these are the pages from the answer sheet and not the pages from the subject).
    'regroupement_compose'=>cb_model(0=>__"Only pages with answers",
# TRANSLATORS: One of the possible way to annotate answer sheets: here we take the pages were the students has nothing to write (often question pages from a subject with separate answer sheet option) from the subject.
				     1=>__"Question pages from subject",
# TRANSLATORS: One of the possible way to annotate answer sheets: here we take the pages were the students has nothing to write (often question pages from a subject with separate answer sheet option) from the correction.
				     2=>__"Question pages from correction",
    ),
# TRANLATORS: For which students do you want to annotate papers? This is a menu entry.
    'regroupement_copies'=>cb_model('ALL'=>__"All students",
# TRANLATORS: For which students do you want to annotate papers? This is a menu entry.
				    'SELECTED'=>__"Selected students",
				    ),
    'auto_capture_mode'=>cb_model(-1=>__"Please select...",
# TRANSLATORS: One of the ways exam was made: each student has a different answer sheet with a different copy number - no photocopy was made. This is a menu entry.
				  0=>__"Different answer sheets",
# TRANSLATORS: One of the ways exam was made: some students have the same exam subject, as some photocopies were made before distributing the subjects. This is a menu entry.
				  1=>__"Some answer sheets were photocopied"),
# TRANSLATORS: One of the ways to send mail: use sendmail command. This is a menu entry.
    'email_transport'=>cb_model('sendmail'=>__"sendmail",
# TRANSLATORS: One of the ways to send mail: use a SMTP server. This is a menu entry.
				'SMTP'=>__"SMTP"),
    'email_smtp_ssl'=>cb_model(0=>__p"None [SMTP security]",
# TRANSLATORS: SMTP security mode: None (nor SSL nor STARTTLS)
                               1=>'SSL',
                               'starttls'=>'STARTTLS'),
    'print_extract_with'=>cb_model('pdftk'=>'pdftk',
				   'gs'=>'gs (ghostscript)',
				   'qpdf'=>'qpdf',
				   ),
# TRANLATORS: One of the way to handle separate answer sheet when printing: standard (same as in the question pdf document). This is a menu entry.
		       'print_answersheet'=>cb_model(''=>__"Standard",
# TRANLATORS: One of the way to handle separate answer sheet when printing: print separately answer sheet and question. This is a menu entry.
						     'split'=>__"Separate answer sheet",
# TRANLATORS: One of the way to handle separate answer sheet when printing: print the answr sheet first. This is a menu entry.
						     'first'=>__"Answer sheet first"),
		      );

# TRANSLATORS: One of the signs that can be drawn on annotated answer sheets to tell if boxes are to be ticked or not, and if they were detected as ticked or not.
my $symbole_type_cb=cb_model("none"=>__"nothing",
# TRANSLATORS: One of the signs that can be drawn on annotated answer sheets to tell if boxes are to be ticked or not, and if they were detected as ticked or not.
			     "circle"=>__"circle",
# TRANSLATORS: One of the signs that can be drawn on annotated answer sheets to tell if boxes are to be ticked or not, and if they were detected as ticked or not. Here, a cross.
			     "mark"=>__"mark",
# TRANSLATORS: One of the signs that can be drawn on annotated answer sheets to tell if boxes are to be ticked or not, and if they were detected as ticked or not. Here, the box outline.
			     "box"=>__"box",
			     );

for my $k (qw/0_0 0_1 1_0 1_1/) {
  $prefs->store_register("symbole_".$k."_type"=>$symbole_type_cb);
}

# Add config GUI for export modules...

for my $m (@export_modules) {
  my $x="AMC::Export::register::$m"->build_config_gui(\%w,$prefs);
  if($x) {
    $w{'config_export_module_'.$m}=$x;
    $w{'config_export_modules'}->pack_start($x,0,0,0);
  }
}

### export

sub maj_export {
    my $old_format=$config->get('format_export');

    valide_options_for_domain('export','',@_);

    if($config->key_changed("export_sort")) {
      annotate_source_change($projet{'_capture'},1);
    }

    debug "Format : ".$config->get('format_export');

    for(@export_modules) {
      if($w{'config_export_module_'.$_}) {
	if($config->get('format_export') eq $_) {
	  $w{'config_export_module_'.$_}->show;
	} else {
	  $w{'config_export_module_'.$_}->hide;
	}
      }
    }

    my %hide=("AMC::Export::register::".$config->get('format_export'))
      ->hide();
    for (qw/standard_export_options/) {
      if($hide{$_}) {
	$w{$_}->hide();
      } else {
	$w{$_}->show();
      }
    }
}

sub exporte {

  maj_export();

  my $format=$config->get('format_export');
  my @options=();
  my $ext="AMC::Export::register::$format"->extension();
  if(!$ext) {
    $ext=lc($format);
  }
  my $type="AMC::Export::register::$format"->type();
  my $code=$config->get('code_examen');
  $code=$projet{'nom'} if(!$code);
  utf8::encode($code);
  my $output=$shortcuts->absolu('%PROJET/exports/'.$code.$ext);
  my @needs_module=();

  my %ofc="AMC::Export::register::$format"
    ->options_from_config($config);
  my $needs_catalog="AMC::Export::register::$format"
    ->needs_catalog($config);
  for(keys %ofc) {
    push @options,"--option-out",$_.'='.$ofc{$_};
  }
  push @needs_module,"AMC::Export::register::$format"->needs_module();

  if(@needs_module) {
    # teste si les modules necessaires sont disponibles

    my @manque=();

    for my $m (@needs_module) {
      if(!check_install(module=>$m)) {
        push @manque,$m;
      }
    }

    if(@manque) {
      debug 'Exporting to '.$format.': Needs perl modules '.join(', ',@manque);

      my $dialog = Gtk3::MessageDialog
        ->new($w{'main_window'},
              'destroy-with-parent',
              'error','ok',
              __("Exporting to '%s' needs some perl modules that are not installed: %s. Please install these modules or switch to another export format."),
              "AMC::Export::register::$format"->name(),join(', ',@manque)
             );
      $dialog->run;
      $dialog->destroy;

      return();
    }
  }

  # If some data involving detailed results (concerning some
  # particular answers) is required, then check that the catalog
  # document is prepared, or propose to build it.  This way, answers
  # will be identified by the character drawn in the boxes in the
  # catalog document. These characters default to A, B, C and so on,
  # but can be others if requested in the source file.

  if($needs_catalog) {
    if(!$projet{'_layout'}->nb_chars_transaction()) {
      my $dialog = Gtk3::MessageDialog
        ->new($w{'main_window'},
              'destroy-with-parent',
              'question','yes-no','');
      $dialog->set_markup(__"When referring to a particular answer in the export, the letter used will be the one found in the catalog. However, the catalog has not yet been built. Do you want to build it now?");
      $dialog->get_widget_for_response('yes')->get_style_context()->add_class("suggested-action");
      my $reponse=$dialog->run;
      $dialog->destroy;
      if($reponse eq 'yes') {
        update_catalog();
      }
    }
  }

  # wait for GUI update before going on with the table
  Glib::Idle->add(\&export_go,
                  {
                   format=>$format,output=>$output,
                   o=>\@options,type=>$type,
                  },
                  Glib::G_PRIORITY_LOW);
}

sub export_go {
  my ($opts)=@_;
  commande('commande'=>["auto-multiple-choice","export",
                        pack_args(
                                  "--debug",debug_file(),
                                  "--module",$opts->{format},
                                  "--data",$config->get_absolute('data'),
                                  "--useall",$config->get('export_include_abs'),
                                  "--sort",$config->get('export_sort'),
                                  "--fich-noms",$config->get_absolute('listeetudiants'),
                                  "--noms-encodage",bon_encodage('liste'),
                                  "--csv-build-name",csv_build_name(),
                                  ($config->get('annote_rtl') ? "--rtl" : "--no-rtl"),
                                  "--output",$opts->{output},
                                  @{$opts->{o}},
                                 ),
                       ],
           'texte'=>__"Exporting marks...",
           'progres.id'=>'export',
           'progres.pulse'=>0.01,
           'fin'=>\&export_done,
           'o'=>$opts,
          );
}

sub export_done {
  my ($c,%data)=@_;
  my $output=$c->{o}->{output};
  my $type=$c->{o}->{type};
  if(-f $output) {
    # shows export messages

    my $t=$c->higher_message_type();
    if($t) {
      my $dialog = Gtk3::MessageDialog
        ->new($w{'main_window'},
              'destroy-with-parent',
              ($t eq 'ERR' ? 'error' : $t eq 'WARN' ? 'warning' : 'info'),
              'ok',
              join("\n",$c->get_messages($t))
             );
      $dialog->run;
      $dialog->destroy;
    }

    if($config->get('after_export') eq 'file') {
      my $cmd = commande_accessible([$config->get($type.'_viewer'),
				     $config->get($type.'_editor'),
				     'xdg-open', 'open']);
      commande_parallele($cmd,$output) if($cmd);
    } elsif($config->get('after_export') eq 'dir') {
      view_dir($shortcuts->absolu('%PROJET/exports/'));
    }
  } else {
    my $dialog = Gtk3::MessageDialog
      ->new($w{'main_window'},
            'destroy-with-parent',
            'warning','ok',
            __"Export to %s did not work: file not created...",$output);
    $dialog->run;
    $dialog->destroy;
  }
}

## menu contextuel sur liste diagnostique -> visualisation zoom/page

# TRANSLATORS: One of the popup menu that appears when right-clicking on a page in the data capture diagnosis table. Choosing this entry, an image will be opened to see where the corner marks were detected.
my %diag_menu=(page=>{text=>__"page adjustment",icon=>'gtk-zoom-fit'},
# TRANSLATORS: One of the popup menu that appears when right-clicking on a page in the data capture diagnosis table. Choosing this entry, a window will be opened were the user can see all boxes on the scans and how they were filled by the students, and correct detection of ticked-or-not if needed.
	       zoom=>{text=>__"boxes zooms",icon=>'gtk-zoom-in'},
	       );

sub zooms_display {
    my ($student,$page,$copy,$forget_it)=@_;

    debug "Zooms view for ".pageids_string($student,$page,$copy)."...";
    my $zd=$shortcuts->absolu('%PROJET/cr/zooms');
    debug "Zooms directory $zd";
    if($w{'zooms_window'} &&
       $w{'zooms_window'}->actif) {
	$w{'zooms_window'}->page([$student,$page,$copy],$zd,$forget_it);
    } elsif(!$forget_it) {
	$w{'zooms_window'}=AMC::Gui::Zooms::new('seuil'=>$config->get('seuil'),
						'seuil_up'=>$config->get('seuil_up'),
						'n_cols'=>$config->get('zooms_ncols'),
						'zooms_dir'=>$zd,
						'page_id'=>[$student,$page,$copy],
						'size-prefs',$config,
						'encodage_interne'=>$config->get('encodage_interne'),
						'data'=>$projet{'_capture'},
						'cr-dir'=>$config->get_absolute('cr'),
						'list_view'=>$w{'diag_tree'},
						'global_options'=>$config,
						'prefs'=>$prefs,
	    );
    }
}

sub zooms_line_base {
  my ($forget_it)=@_;
  my @selected=$w{'diag_tree'}->get_selection->get_selected_rows;
  my $first_selected=$selected[0]->[0];
  if(defined($first_selected)) {
    my $iter=$diag_store->get_iter($first_selected);
    my $id=$diag_store->get($iter,DIAG_ID);
    zooms_display((map { $diag_store->get($iter,$_) } (DIAG_ID_STUDENT,
						       DIAG_ID_PAGE,
						       DIAG_ID_COPY)
		  ),$forget_it);
  }
}

sub zooms_line { zooms_line_base(1); }
sub zooms_line_open { zooms_line_base(0); }

sub layout_line {
  my @selected=$w{'diag_tree'}->get_selection->get_selected_rows;
  for my $s (@{$selected[0]}) {
    my $iter=$diag_store->get_iter($s);
    my @id=map { $diag_store->get($iter,$_); } 
      (DIAG_ID_STUDENT,DIAG_ID_PAGE,DIAG_ID_COPY);
    $projet{'_capture'}->begin_read_transaction('Layl');
    my $f=$config->get_absolute('cr').'/'
      .$projet{'_capture'}->get_layout_image(@id);
    $projet{'_capture'}->end_transaction('Layl');

    utf8::downgrade($f);
    commande_parallele($config->get('img_viewer'),$f) if(-f $f);
  }
}

sub delete_line {
  my @selected=$w{'diag_tree'}->get_selection->get_selected_rows;
  my $f;
  if(@{$selected[0]}) {
    my $dialog = Gtk3::MessageDialog
      ->new($w{'main_window'},
            'destroy-with-parent',
            'question','yes-no','');
    $dialog->set_markup(
			sprintf((__"You requested to delete all data capture results for %d page(s)"),1+$#{$selected[0]})."\n"
			.'<b>'.(__"All data and image files related to these pages will be deleted.")."</b>\n"
			.(__"Do you really want to continue?")
		       );
    $dialog->get_widget_for_response('yes')->get_style_context()->add_class("destructive-action");
    my $reponse=$dialog->run;
    $dialog->destroy;
    if($reponse eq 'yes') {
      my @iters=();
      $projet{'_capture'}->begin_transaction('rmAN');
      for my $s (@{$selected[0]}) {
	my $iter=$diag_store->get_iter($s);
	my @id=map { $diag_store->get($iter,$_); } 
	  (DIAG_ID_STUDENT,DIAG_ID_PAGE,DIAG_ID_COPY);
	debug "Removing data capture for ".pageids_string(@id);
	#
	# 1) get image files generated, and remove them
	#
	my $crdir=$config->get_absolute('cr');
	my @files=();
	#
	# scan file
	push @files,$shortcuts->absolu($projet{'_capture'}->get_scan_page(@id));
	#
	# layout image, in cr directory
	push @files,$crdir.'/'
	  .$projet{'_capture'}->get_layout_image(@id);
	#
	# annotated scan
	push @files,$crdir.'/corrections/jpg/'
	  .$projet{'_capture'}->get_annotated_page(@id);
	#
	# zooms
	push @files,map { $crdir.'/zooms/'.$_ } grep { defined($_) }
	  ($projet{'_capture'}->get_zones_images(@id,ZONE_BOX));
	#
	for (@files) {
	  if (-f $_) {
	    debug "Removing $_";
	    unlink($_);
	  }
	}
	#
	# 2) remove data from database
	#
	$projet{'_capture'}->delete_page_data(@id);

	if($config->get('auto_capture_mode') == 1) {
	  $projet{'_scoring'}->delete_scoring_data(@id[0,2]);
	  $projet{'_association'}->delete_association_data(@id[0,2]);
	}

	push @iters,$iter;
      }

      for(@iters) { $diag_store->remove($_); }
      update_analysis_summary();
      $projet{'_capture'}->end_transaction('rmAN');

      assoc_state();
    }
  }
}

$w{'diag_tree'}->signal_connect('button_release_event' =>
    sub {
	my ($self, $event) = @_;
	return 0 unless $event->button == 3;
	my ($path, $column, $cell_x, $cell_y) =
	    $w{'diag_tree'}->get_path_at_pos ($event->x, $event->y);
	if ($path) {
	    my $iter=$diag_store->get_iter($path);
	    my $id=[map { $diag_store->get($iter,$_) } (DIAG_ID_STUDENT,
							DIAG_ID_PAGE,
							DIAG_ID_COPY)];

	    my $menu = Gtk3::Menu->new;
	    my $c=0;
	    my @actions=('page');

	    # new zooms viewer

	    $projet{'_capture'}->begin_read_transaction('ZnIm');
	    my @bi=grep { -f $shortcuts->absolu('%PROJET/cr/zooms')."/".$_ }
	      $projet{'_capture'}->zone_images($id->[0],$id->[2],ZONE_BOX);
	    $projet{'_capture'}->end_transaction('ZnIm');

	    if(@bi) {
		$c++;
		my $item = Gtk3::ImageMenuItem->new($diag_menu{'zoom'}->{text});
		$item->set_image(Gtk3::Image->new_from_icon_name($diag_menu{'zoom'}->{icon},'menu'));
		$menu->append ($item);
		$item->show;
		$item->signal_connect (activate => sub {
		    my (undef, $sortkey) = @_;
		    zooms_display(@$id);
				       }, $_);
	    } else {
		push  @actions,'zoom';
	    }

	    # page viewer and old zooms viewer

	    foreach $a (@actions) {
	      my $f;
	      if($a eq 'page') {
		$projet{'_capture'}->begin_read_transaction('gLIm');
		$f=$config->get_absolute('cr').'/'
		  .$projet{'_capture'}->get_layout_image(@$id);
		$projet{'_capture'}->end_transaction('gLIm');
	      } else {
		$f=id2file($id,$a,'jpg');
	      }
	      if(-f $f) {
		$c++;
		my $item = Gtk3::ImageMenuItem->new($diag_menu{$a}->{text});
		$item->set_image(Gtk3::Image->new_from_icon_name($diag_menu{$a}->{icon},'menu'));
		$menu->append ($item);
		$item->show;
		$item->signal_connect (activate => sub {
					 my (undef, $sortkey) = @_;
					 debug "Looking at $f...";
					 commande_parallele($config->get('img_viewer'),$f);
				       }, $_);
	      }
	    }
	    $menu->popup (undef, undef, undef, undef,
			  $event->button, $event->time) if($c>0);
	    return 1; # stop propagation!

	}
    });

### Appel a des commandes externes -- log, annulation

my %les_commandes=();
my $cmd_id=0;

sub commande {
    my (@opts)=@_;
    $cmd_id++;

    my $c=AMC::Gui::Commande::new('avancement'=>$w{'avancement'},
				  'log'=>$w{'log_general'},
				  'finw'=>sub {
				      my $c=shift;
				      $w{'onglets_projet'}->set_sensitive(1);
				      $w{'commande'}->hide();
				      delete $les_commandes{$c->{'_cmdid'}};
				  },
				  @opts);

    $c->{'_cmdid'}=$cmd_id;
    $les_commandes{$cmd_id}=$c;

    $w{'onglets_projet'}->set_sensitive(0);
    $w{'commande'}->show();

    $c->open();
}

sub commande_annule {
    for (keys %les_commandes) { $les_commandes{$_}->quitte(); }
}

sub commande_parallele {
    my (@c)=(@_);
    if(commande_accessible($c[0])) {
	my $pid=fork();
	if($pid==0) {
	    debug "Command // [$$] : ".join(" ",@c);
	    exec(@c) ||
		debug "Exec $$ : error";
	    exit(0);
	}
    } else {
	my $dialog = Gtk3::MessageDialog
	    ->new($w{'main_window'},
                  'destroy-with-parent',
                  'error','ok','');
        $dialog->set_markup(sprintf(__"Following command could not be run: <b>%s</b>, perhaps due to a poor configuration?",$c[0]));
	$dialog->run;
	$dialog->destroy;

    }
}

### Actions des menus

my $proj_store;

sub projet_nouveau {
    liste_des_projets('cree'=>1);
}

sub projet_charge {
    liste_des_projets();
}

sub projet_gestion {
    liste_des_projets('gestion'=>1);
}

sub projects_list {
  # construit la liste des projets existants
  debug "Projects list: ".$w{'local_rep_projets'}
            .(utf8::is_utf8($w{'local_rep_projets'}) ? " (UTF8)" : "");
  if(-d $w{'local_rep_projets'}) {
    opendir(DIR, $w{'local_rep_projets'})
      || die "Error opening directory ".$w{'local_rep_projets'}." : $!";
    my @f=grep { ! /^\./ } readdir(DIR);
    closedir DIR;
    debug "F:".join(',',map { $_.":".(-d $w{'local_rep_projets'}."/".$_) } @f);

    my @projs = grep { ! /^\./ && -d $w{'local_rep_projets'}."/".$_ } @f;
    debug "[".$w{'local_rep_projets'}."] P:".join(',',@projs);
    return(@projs);
  }
}

sub projects_show {
  my ($p)=@_;
  $p=[projects_list()] if(!$p);

  $w{'projet_bouton_choose_directory'}
    ->set_tooltip_text(sprintf(__("Current directory: %s"),
                               Glib::filename_display_name($w{'local_rep_projets'})));

  if(!$w{'icon_project'}) {
    $w{'icon_project'} = Gtk3::IconTheme::get_default->load_icon("auto-multiple-choice",$config->get("project_icon_size") ,"force-svg");
    $w{'icon_project_dir'} = Gtk3::IconTheme::get_default->load_icon("gtk-no",$config->get("project_icon_size") ,"force-svg");
    $w{'icon_project'}=$w{'main_window'}->render_icon ('gtk-open', 'menu')
      if(!$w{'icon_project'});
  }

  $proj_store->clear;
  # $prefix is the <LTR> UTF8 character in RTL environments.
  my $prefix=($w{'main_window'}->get_direction() eq 'rtl' ?
	      decode("utf-8","\xe2\x80\x8e"): '');
  for my $proj_name (sort { $a cmp $b } @$p) {
    my $is_amc=(-f fich_options($proj_name,$w{'local_rep_projets'}));
    my $name_plain=$proj_name;
    my $label=$proj_name=Glib::filename_display_name($proj_name);
    $label=$prefix.$label if($proj_name =~ /^[0-9]*$/);
    debug "Directory: $label".(utf8::is_utf8($label) ? " (utf8)" : "")
      .($is_amc ? " (AMC)" : "");
    $proj_store->set($proj_store->append,
		     PROJ_NOM,$label,
		     PROJ_NOM_PLAIN,$name_plain,
		     PROJ_ICO,
		     ($is_amc ?
		      $w{'icon_project'} : $w{'icon_project_dir'} ));
  }
}

sub liste_des_projets {
    my %oo=(@_);

    $w{'local_rep_projets'}=$config->get('rep_projets');

    mkdir($w{'local_rep_projets'}) if(-d $w{'local_rep_projets'});

    my @projs=projects_list();

    if($#projs>=0 || $oo{'cree'}) {

	# fenetre pour demander le nom du projet

	my $gp=read_glade('choix_projet',
			  qw/label_etat label_action
            choix_projets_liste
	    projet_bouton_ouverture projet_bouton_creation
	    projet_bouton_supprime projet_bouton_annule
	    projet_bouton_annule_label projet_bouton_renomme
	    projet_bouton_clone
	    projet_bouton_mv_yes projet_bouton_mv_no
	    projet_bouton_choose_directory
	    projet_nom projet_nouveau_syntaxe projet_nouveau/);

	if($oo{'cree'}) {
	    $w{'projet_nouveau'}->show();
	    $w{'projet_bouton_creation'}->show();
	    $w{'projet_bouton_ouverture'}->hide();

	    $w{'label_etat'}->set_text(__"Existing projects:");

	    $w{'choix_projet'}->set_focus($w{'projet_nom'});

# TRANSLATORS: Window title when creating a new project.
	    $w{'choix_projet'}->set_title(__"New AMC project");
	}


	if($oo{'gestion'}) {
	    $w{'label_etat'}->set_text(__"Projects management:");
	    $w{'label_action'}->set_markup(__"Change project name:");
	    $w{'projet_bouton_ouverture'}->hide();
	    for (qw/supprime clone renomme/) {
		$w{'projet_bouton_'.$_}->show();
	    }
	    $w{'projet_bouton_annule_label'}->set_text(__"Back");

# TRANSLATORS: Window title when managing projects.
	    $w{'choix_projet'}->set_title(__"AMC projects management");
	}

	# mise a jour liste des projets dans la fenetre

	$proj_store = Gtk3::ListStore->new ('Glib::String','Glib::String',
					    'Gtk3::Gdk::Pixbuf');

	$w{'choix_projets_liste'}->set_model($proj_store);

	$w{'choix_projets_liste'}->set_text_column(PROJ_NOM);
	$w{'choix_projets_liste'}->set_pixbuf_column(PROJ_ICO);

	projects_show(\@projs);

        $w{'choix_projet'}->show();

    } else {
	my $dialog = Gtk3::MessageDialog
	    ->new($w{'main_window'},
		  'destroy-with-parent',
		  'info','ok',
		  __"You don't have any MC project in directory %s!",$config->get('rep_projets'));
	$dialog->run;
	$dialog->destroy;

    }
}

sub project_choose_directory {
  my $d=Gtk3::FileChooserDialog
    ->new(__("Choose directory"),
	  $w{'choix_projet'},'select-folder',
	  'gtk-cancel'=>'cancel',
	  'gtk-ok'=>'ok');
  $d->set_current_folder($w{'local_rep_projets'});
  my $r=$d->run;
  if($r eq 'ok') {
    my $rep=$d->get_filename || $d->get_current_directory;
    utf8::downgrade($rep);
    if(-d $rep) {
      $w{'local_rep_projets'}=$rep;
    }
  }
  $d->destroy();

  if($r eq 'ok') {
    projects_show();
  }
}

sub projet_gestion_check {
  my ($open_ok)=@_;

    # lequel ?

    my $sel=$w{'choix_projets_liste'}->get_selected_items()->[0];
    my $iter;
    my $proj;

    if($sel) {
	$iter=$proj_store->get_iter($sel);
	$proj=$proj_store->get($iter,PROJ_NOM_PLAIN) if($iter);
    }

    return('','') if(!$proj);

  return($proj,$iter) if($open_ok);

    # est-ce le projet en cours ?

    if($projet{'nom'} && $proj eq $projet{'nom'}) {
	my $dialog = Gtk3::MessageDialog
	    ->new($w{'choix_projet'},
		  'destroy-with-parent',
		  'error','ok',
		  __"You can't change project %s since it's open.",$proj);
	$dialog->run;
	$dialog->destroy;
	$w{'choix_projet'}->set_keep_above(1);
	$proj='';
    }

    return($proj,$iter);
}

sub projet_liste_clone {
  my ($proj,$iter)=projet_gestion_check(1);
  return if(!$proj);

  my $proj_clone=new_filename($shortcuts->absolu($w{'local_rep_projets'}).'/'.$proj);
  my (undef,undef,$proj_c) = splitpath($proj_clone);

  my $dialog = Gtk3::MessageDialog
    ->new($w{'choix_projet'},
          'destroy-with-parent',
          'warning','ok-cancel','');
  $dialog->set_markup(
		      sprintf(__("This will clone project <b>%s</b> to a new project <i>%s</i>."),
			      glib_filename($proj),glib_filename($proj_c)));
  my $r=$dialog->run;
  $dialog->destroy;
  if($r eq 'ok') {
    if(!project_copy($w{'local_rep_projets'}.'/'.$proj,
                     $proj_clone,$w{'choix_projet'})) {
      debug_and_stderr("ERROR (clone project): $!");
    }
    projects_show();
  }
}

my $nom_original='';
my $nom_original_iter='';

sub projet_liste_renomme {
    my ($proj,$iter)=projet_gestion_check();
    return if(!$proj);

    # ouverture zone :
    $w{'projet_nouveau'}->show();
    $w{'projet_nom'}->set_text(glib_filename($proj));

    $nom_original=$proj;
    $nom_original_iter=$iter;

    # boutons...
    for (qw/annule renomme clone supprime/) {
	$w{'projet_bouton_'.$_}->hide();
    }
    for (qw/mv_no mv_yes/) {
	$w{'projet_bouton_'.$_}->show();
    }
}

sub projet_renomme_fin {
    # fermeture zone :
    $w{'projet_nouveau'}->hide();

    # boutons...
    for (qw/annule renomme clone supprime/) {
	$w{'projet_bouton_'.$_}->show();
    }
    for (qw/mv_no mv_yes/) {
	$w{'projet_bouton_'.$_}->hide();
    }
}

sub projet_mv_yes {
  projet_renomme_fin();

  my $nom_nouveau=$w{'projet_nom'}->get_text();
  utf8::encode($nom_nouveau);

  return if($nom_nouveau eq $nom_original || !$nom_nouveau);

  if ($w{'local_rep_projets'}) {
    my $dir_original=$w{'local_rep_projets'}."/".$nom_original;
    utf8::downgrade($dir_original);
    if (-d $dir_original) {
      my $dir_nouveau=$w{'local_rep_projets'}."/".$nom_nouveau;
      utf8::downgrade($dir_nouveau);
      if (-d $dir_nouveau) {
        $w{'choix_projet'}->set_keep_above(0);
        my $dialog = Gtk3::MessageDialog
          ->new($w{'main_window'},
                'destroy-with-parent',
                'error','ok','');
        $dialog->set_markup(
                            # TRANSLATORS: Message when you want to create an AMC project with name xxx, but there already exists a directory in the projects directory with this name!
                            sprintf(__("Directory <i>%s</i> already exists, so you can't choose this name."),glib_filename($dir_nouveau)));
        $dialog->run;
        $dialog->destroy;
        $w{'choix_projet'}->set_keep_above(1);

        return;
      } else {
        # OK

        if (!move($dir_original,$dir_nouveau)) {
          debug_and_stderr("ERROR (move project): $!");
        }

        $proj_store->set($nom_original_iter,
                         PROJ_NOM,glib_filename($nom_nouveau),
                        );
        utf8::downgrade($nom_nouveau);
        $proj_store->set($nom_original_iter,
                         PROJ_NOM_PLAIN,$nom_nouveau,
                        );
      }
    } else {
      debug_and_stderr "No original directory";
    }
  } else {
    debug "No projects directory";
  }
}

sub projet_mv_no {
    projet_renomme_fin();
}

my @find_results=();

sub add_to_results {
  push @find_results,$File::Find::name;
}

sub project_copy {
  my ($src,$dest,$parent_window)=@_;
  utf8::downgrade($src);
  utf8::downgrade($dest);
  $parent_window=$w{'main_window'} if(!$parent_window);
  my $err='';
  if(!$err && -e $dest) {
    $err=__("Destination project directory already exists");
  }
  if(!$err) {
    @find_results=();
    find({wanted=>\&add_to_results,no_chdir=>1},
	 $src);
    my $total=1+$#find_results;
    if($total>0) {
      my $done=0;
      my $i=0;
      my $last_fraction=0;
      my $old_text=$w{'avancement'}->get_text();
      $w{'avancement'}->set_text(__"Copying project...");
      $w{'avancement'}->set_fraction(0);
      $w{'commande'}->show();
      for my $s (@find_results) {
	my $d=$s;
	$d =~ s:^$src:$dest:;
	if(-d $s) {
	  $done++ if(mkdir($d));
	} else {
	  $done++ if(copy($s,$d));
	}
	$i++;
	if($i/$total-$last_fraction>1/40) {
	  $last_fraction=$i/$total;
	  $w{'avancement'}->set_fraction($last_fraction);
	  Gtk3::main_iteration while ( Gtk3::events_pending );
	}
      }
      $w{'avancement'}->set_text($old_text);
      $w{'commande'}->hide();

      my $dialog = Gtk3::MessageDialog
	->new($parent_window,
	      'destroy-with-parent',
	      'info','ok',
	      __("Your project has been copied").
	      ($done!=$total ? " ".sprintf(__("(%d files out of %d)"),
					   $done,$total) : "")
	      ."."
	     );
      $dialog->run;
      $dialog->destroy;
    } else {
      $err=__("Source project directory not found");
    }
  }
  if($err) {
    my $dialog = Gtk3::MessageDialog
      ->new($parent_window,
	    'destroy-with-parent',
	    'error','ok',
	    __("An error occuried during project copy: %s."),
	    $err
	   );
    $dialog->run;
    $dialog->destroy;
    return(0);
  }
  return(1);
}

sub projet_liste_supprime {
    my ($proj,$iter)=projet_gestion_check();
    return if(!$proj);

    # on demande confirmation...
    $w{'choix_projet'}->set_keep_above(0);
    my $dialog = Gtk3::MessageDialog
	->new($w{'main_window'},
              'destroy-with-parent',
              'warning','ok-cancel','');
    $dialog->set_markup(
			  sprintf(__("You asked to remove project <b>%s</b>.")." "
				  .__("This will permanently erase all the files of this project, including the source file as well as all the files you put in the directory of this project, as the scans for example.")." "
				  .__("Is this really what you want?"),glib_filename($proj)));
    $dialog->get_widget_for_response('ok')->get_style_context()->add_class("destructive-action");
    my $reponse=$dialog->run;
    $dialog->destroy;
    $w{'choix_projet'}->set_keep_above(1);

    if($reponse ne 'ok') {
	return;
    }

    debug "Removing project $proj !";

    $proj_store->remove($iter);

    # suppression effective des fichiers...

    if($w{'local_rep_projets'}) {
      my $dir=$w{'local_rep_projets'}."/".$proj;
      utf8::downgrade($dir);
      if(-d $dir) {
        remove_tree($dir,{'verbose'=>0,'safe'=>1,'keep_root'=>0});
      } else {
        debug "No directory $dir";
      }
    } else {
      debug "No projects directory";
    }
}

sub projet_charge_ok {

    # ouverture projet deja existant

    my $sel_items=$w{'choix_projets_liste'}->get_selected_items();
    return if(!$sel_items);
    my $sel=$sel_items->[0];

    my $proj;

    if($sel) {
	$proj=$proj_store->get($proj_store->get_iter($sel),PROJ_NOM_PLAIN);
    }

    $w{'choix_projet'}->destroy();
    Gtk3::main_iteration while ( Gtk3::events_pending );

    if($proj) {
        my $reponse='yes';
        my $options_file=fich_options($proj,$w{'local_rep_projets'});
	if(! -f $options_file) {
          debug_and_stderr "Options file not found: ".$options_file
            .(utf8::is_utf8($options_file) ? " (UTF8)" : "");
	    my $dialog = Gtk3::MessageDialog
		->new($w{'main_window'},
                      'destroy-with-parent',
                      'warning','yes-no','');
            $dialog->set_markup(
				  sprintf(__("You selected directory <b>%s</b> as a project to open.")." "
					  .__("However, this directory does not seem to contain a project. Do you still want to try?"),$proj));
	    $reponse=$dialog->run;
	    $dialog->destroy;
	}
	if($reponse eq 'yes') {
	  quitte_projet() or return();

	  # If the project to open lies on a removable media, suggest
	  # to copy it first to the user standard projects directory:

	  if($w{'local_rep_projets'} =~ /^\/media\//
	    && $config->get_absolute('projects_home') !~ /^\/media\//) {
	    my $dialog = Gtk3::MessageDialog
		->new($w{'main_window'},
                      'destroy-with-parent',
                      'warning','yes-no','');
            $dialog->set_markup(
				  sprintf(__("You selected project <b>%s</b> from directory <i>%s</i>.")." "
					  .__("Do you want to copy this project to your projects directory before opening it?"),
					  $proj,$w{'local_rep_projets'}));
	    my $r=$dialog->run;
	    $dialog->destroy;
	    if($r eq 'yes') {
	      my $proj_dest=new_filename($config->get_absolute('projects_home').'/'.$proj);
	      if(project_copy($w{'local_rep_projets'}.'/'.$proj,
			      $proj_dest)) {
		(undef,undef,$proj_dest) = splitpath($proj_dest);
		$w{'local_rep_projets'}=$config->get_absolute('projects_home');
		$proj=$proj_dest;
	      }
	    }
	  }

	  # OK, now, open the project!

	  set_projects_home($w{'local_rep_projets'});
	  projet_ouvre($proj) ;
	}
    }
}

sub restricted_check {
  my ($text,$warning,$chars)=@_;
  my $nom=$text->get_text();
  if (!$config->get('nonascii_projectnames')) {
    if ($nom =~ s/[^$chars]//g) {
      $text->set_text($nom);
      $warning->show();

      my $col=Gtk3::Gdk::RGBA::parse('#FFC0C0');
      for (qw/normal active/) {
        $text->override_background_color($_,$col);
      }
      Glib::Timeout->add (500, sub {
                            for (qw/normal active/) {
                              $text->override_background_color($_,undef);
                            }
                            return 0;
                          });
    }
  }
}

sub projet_nom_verif {
    restricted_check($w{'projet_nom'},$w{'projet_nouveau_syntaxe'},"a-zA-Z0-9._+:-");
}

sub projet_charge_non {
  $w{'choix_projet'}->destroy();
}

sub projet_charge_nouveau {

    # creation nouveau projet

    my $proj=$w{'projet_nom'}->get_text();
    utf8::encode($proj);
    $w{'choix_projet'}->destroy();

    # existe deja ?

    if(-e $w{'local_rep_projets'}."/$proj") {

	my $dialog = Gtk3::MessageDialog
	    ->new($w{'main_window'},
                  'destroy-with-parent',
                  'error','ok','');
        $dialog->set_markup(
			      sprintf(__("The name <b>%s</b> is already used in the projects directory.")." "
				      .__"You must choose another name to create a project.",$proj));
	$dialog->run;
	$dialog->destroy;


    } else {
      quitte_projet() or return();

      set_projects_home($w{'local_rep_projets'});
      if(projet_ouvre($proj,1)) {
	projet_sauve();
      }

    }
}

sub projet_sauve {
    debug "Saving project...";

    $config->save();
}

sub projet_check_and_save {
  if($projet{'nom'}) {
    valide_options_notation();
    $config->save();
  }
}

### Actions des boutons de la partie DOCUMENTS

sub format_markup {
  my ($t)=@_;
  $t =~ s/\&/\&amp;/g;
  return($t);
}

sub mini {($_[0]<$_[1] ? $_[0] : $_[1])}

my %component_name=('latex_packages'=>__("LaTeX packages:"),
		    'commands'=>__("Commands:"),
		    'fonts'=>__("Fonts:"),
		    );

sub check_sty_version {
  my ($c)=@_;
  my $sty_v=$c->variable('styversion');
  $sty_v='unknown' if(!$sty_v);
  my $sty_p=$c->variable('stypath');
  my $amc_v='2018/12/29 v1.4.0 r:c6041a1';
  if($sty_p && $sty_v ne $amc_v) {
    my $dialog = Gtk3::MessageDialog
	    ->new($w{'main_window'},
                  'destroy-with-parent',
                  'warning','ok','');
    $dialog->set_markup
      (__"Your AMC version and LaTeX style file version differ. Even if the documents are properly generated, this can lead to many problems using AMC.".
       "\n".__"<b>Please check your installation to get matching AMC and LaTeX style file versions.</b>".
       "\n".sprintf(__"AMC version: %s\nsty version: %s\nsty path: %s",$amc_v,$sty_v,$sty_p)
      );
    $dialog->run;
    $dialog->destroy;
  }
}

sub update_document {
  my($mode,%opts)=@_;

  commande('commande'=>["auto-multiple-choice","prepare",
                        "--with",moteur_latex(),
                        "--filter",$config->get('filter'),
                        "--filtered-source",$config->get_absolute('filtered_source'),
                        "--debug",debug_file(),
                        "--out-sujet",$config->get_absolute('doc_question'),
                        "--out-corrige",$config->get_absolute('doc_solution'),
                        "--out-corrige-indiv",$config->get_absolute('doc_indiv_solution'),
                        "--out-catalog",$config->get_absolute('doc_catalog'),
                        "--out-calage",$config->get_absolute('doc_setting'),
                        "--mode",$mode,
                        "--n-copies",$config->get('nombre_copies'),
                        $config->get_absolute('texsrc'),
                        "--prefix",$shortcuts->absolu('%PROJET/'),
                        "--latex-stdout",
                        "--data",$config->get_absolute('data'),
                       ],
           'signal'=>2,
           'texte'=>__"Documents update...",
           'progres.id'=>'MAJ',
           'progres.pulse'=>0.01,
           'fin'=>\&end_of_document_update,
           'o'=>\%opts);
}

sub end_of_document_update {
  my ($c,%data)=@_;

  detecte_documents();

  if($data{cancelled}) {
    debug "Prepare documents: CANCELLED!";
    return();
  }

  check_sty_version($c);

  my @err=$c->erreurs();
  my @warn=$c->warnings();
  if (@err || @warn) {
    debug "Errors preparing documents!";
    notify_end_of_work('documents',
                       __"Problems while preparing documents");

    my $message=__("Problems while processing the source file.");
    if(!$c->{o}->{partial}) {
      $message.=" "
        .__("You have to correct the source file and re-run documents update.");
    }

    if(@err) {
      $message.="\n\n".__("<b>Errors</b>")."\n"
        .join("\n",map { format_markup($_) } (@err[0..mini(9,$#err)])).($#err>9 ? "\n\n<i>(".__("Only first ten errors written").")</i>": "");
    }
    if(@warn) {
      $message.="\n\n".__("<b>Warnings</b>")."\n"
        .join("\n",map { format_markup($_) } (@warn[0..mini(9,$#warn)])).($#warn>9 ? "\n\n<i>(".__("Only first ten warnings written").")</i>": "");
    }

    $message.="\n\n".
      # TRANSLATORS: Here, %s will be replaced with the translation of "Command output details", and refers to the small expandable part at the bottom of AMC main window, where one can see the output of the commands lauched by AMC.
      sprintf(__("See also the processing log in '%s' below."),
              # TRANSLATORS: Title of the small expandable part at the bottom of AMC main window, where one can see the output of the commands lauched by AMC.
              __"Command output details");
    $message.=" ".__("Use LaTeX editor or latex command for a precise diagnosis.") if($config->get('filter') eq 'latex');

    debug($message);
    my $dialog = Gtk3::MessageDialog
      ->new($w{'main_window'},
            'destroy-with-parent',
            'error','ok','');
    $dialog->set_markup($message);
    $dialog->run;
    $dialog->destroy;

    return();
  }

  notify_end_of_work('documents',
                     __"Documents have been prepared");

  if($c->{o}->{partial}) {
    debug "Partial: return";
    return();
  }

  # verif que tout y est

  my $ok=1;
  for (qw/question setting/) {
    $ok=0 if(! -f $config->get_absolute('doc_'.$_));
  }
  if ($ok) {

    debug "All documents are successfully generated";

    # set project option from filter requests

    my %vars=$c->variables;
    for my $k (keys %vars) {
      if ($k =~ /^project:(.*)/) {
        debug "Configuration: $k = $vars{$k}";
        $config->set($k,$vars{$k});
      }
    }

    # success message

    dialogue_apprentissage('MAJ_DOCS_OK','','',0,
                           __("Working documents successfully generated.")." "
                           # TRANSLATORS: Here, "them" refers to the working documents.
                           .__("You can take a look at them double-clicking on the list.")." "
                           # TRANSLATORS: Here, "they" refers to the working documents.
                           .__("If they are correct, proceed to layouts detection..."));

    # Try to guess the best place to write question
    # scores when annotating. This option can be
    # changed later in the Edit/Preferences window.
    my $ap='marges';
    if($c->variable('scorezones')) {
      $ap='zones';
    } elsif($c->variable('ensemble')) {
      $ap='cases';
    }
    $config->set('annote_position',$ap);

    my $ensemble=$c->variable('ensemble') && !$c->variable('outsidebox');
    if (($ensemble  || $c->variable('insidebox'))
        && $config->get('seuil')<0.4) {
      my $dialog = Gtk3::MessageDialog
        ->new($w{'main_window'},
              'destroy-with-parent',
              'question','yes-no','');
      $dialog->set_markup(
                          sprintf(($ensemble ?
                                   __("Your question has a separate answers sheet.")." "
                                   .__("In this case, letters are shown inside boxes.") :
                                   __("Your question is set to present labels inside the boxes to be ticked."))
                                  ." "
                                  # TRANSLATORS: Here, %s will be replaced with the translation of "darkness threshold".
                                  .__("For better ticking detection, ask students to fill out completely boxes, and choose parameter \"%s\" around 0.5 for this project.")." "
                                  .__("At the moment, this parameter is set to %.02f.")." "
                                  .__("Would you like to set it to 0.5?")
                                  # TRANSLATORS: This parameter is the ratio of dark pixels number over total pixels number inside box above which a box is considered to be ticked.
                                  ,__"darkness threshold",
                                  $config->get('seuil')) );
      my $reponse=$dialog->run;
      $dialog->destroy;
      if ($reponse eq 'yes') {
        $config->set('seuil',0.5);
        $config->set('seuil_up',1.0);
      }
    }
  }

}

sub doc_maj {
    my $sur=0;
    if($projet{'_capture'}->n_pages_transaction()>0) {
	my $dialog = Gtk3::MessageDialog
	    ->new($w{'main_window'},
                  'destroy-with-parent',
                  'warning','ok-cancel','');
        $dialog->set_markup(
			      __("Papers analysis was already made on the basis of the current working documents.")." "
			      .__("You already made the examination on the basis of these documents.")." "
			      .__("If you modify working documents, you will not be capable any more of analyzing the papers you have already distributed!")." "
			      .__("Do you wish to continue?")." "
			      .__("Click on OK to erase the former layouts and update working documents, or on Cancel to cancel this operation.")." "
			      ."<b>".__("To allow the use of an already printed question, cancel!")."</b>");
        $dialog->get_widget_for_response('ok')->get_style_context()->add_class("destructive-action");
	my $reponse=$dialog->run;
	$dialog->destroy;

	if($reponse ne 'ok') {
	    return(0);
	}

	$sur=1;
    }

    # deja des MEP fabriquees ?
    $projet{_layout}->begin_transaction('DMAJ');
    my $pc=$projet{_layout}->pages_count;
    $projet{_layout}->end_transaction('DMAJ');
    if($pc > 0) {
	if(!$sur) {
	    my $dialog = Gtk3::MessageDialog
              ->new($w{'main_window'},
                    'destroy-with-parent',
                    'question','ok-cancel','');
            $dialog->set_markup(
				  __("Layouts are already calculated for the current documents.")." "
				  .__("Updating working documents, the layouts will become obsolete and will thus be erased.")." "
				  .__("Do you wish to continue?")." "
				  .__("Click on OK to erase the former layouts and update working documents, or on Cancel to cancel this operation.")
                                ." <b>".__("To allow the use of an already printed question, cancel!")."</b>");
	    $dialog->get_widget_for_response('ok')->get_style_context()->add_class("destructive-action");
            my $reponse=$dialog->run;
	    $dialog->destroy;

	    if($reponse ne 'ok') {
		return(0);
	    }
	}

	clear_processing('mep:');
    }

    # new layout document : XY (from LaTeX)

    if($config->get('doc_setting') =~ /\.pdf$/) {
      $config->set_project_option_to_default('doc_setting','FORCE');
    }

    # check for filter dependencies

    my $filter_register=("AMC::Filter::register::".$config->get('filter'))
      ->new();

    my $check=$filter_register->check_dependencies();

    if(!$check->{'ok'}) {
      my $message=sprintf(__("To handle properly <i>%s</i> files, AMC needs the following components, that are currently missing:"),$filter_register->name())."\n";
      for my $k (qw/latex_packages commands fonts/) {
	if(@{$check->{$k}}) {
	  $message .= "<b>".$component_name{$k}."</b> ";
	  if($k eq 'fonts') {
	    $message.=join(', ',map { @{$_->{'family'}} } @{$check->{$k}});
	  } else {
	    $message.=join(', ',@{$check->{$k}});
	  }
	  $message.="\n";
	}
      }
      $message.=__("Install these components on your system and try again.");

      my $dialog = Gtk3::MessageDialog
	->new($w{'main_window'},
              'destroy-with-parent',
              'error','ok','');
      $dialog->set_markup($message);
      $dialog->run;
      $dialog->destroy;

      return(0);
    }

    # set options from filter:

    if($config->get('filter')) {
      $filter_register->set_oo($config);
      $filter_register->configure();
    }

    # remove pre-existing DOC-corrected.pdf (built by AMC-annotate)
    my $pdf_corrected=$shortcuts->absolu("DOC-corrected.pdf");
    if(-f $pdf_corrected) {
      debug "Removing pre-existing $pdf_corrected";
      unlink($pdf_corrected);
    }

    #
    my $mode_s='s[';
    $mode_s.='s' if($config->get('prepare_solution'));
    $mode_s.='c' if($config->get('prepare_catalog'));
    $mode_s.=']';
    $mode_s.='k' if($config->get('prepare_indiv_solution'));

    update_document($mode_s);
}

sub filter_details {
  my $gd=read_glade('filter_details',
		    qw/filter_text/);
  debug "Filter details: conf->details GUI";
  $prefs->transmet_pref($gd,prefix=>'filter_details',
                        root=>"project:");
  my $r=$w{'filter_details'}->run();
  if($r == 10) {
    $config->set_local_keys('filter');
    debug "Filter details: new value->local";
    $prefs->reprend_pref(prefix=>'filter_details',container=>'local');
    $w{'filter_details'}->destroy;
    debug "Filter details: local->main GUI";
    $prefs->transmet_pref($gui,prefix=>'pref_prep',
                          keys=>["local:filter"],container=>'project');
  } else {
    $w{'filter_details'}->destroy;
  }
}

sub filter_details_update {
  $config->set_local_keys('filter');
  $prefs->reprend_pref(prefix=>'filter_details',container=>'local');
  my $b=$w{'filter_text'}->get_buffer;
  if($config->get('local:filter')) {
    $b->set_text(("AMC::Filter::register::".$config->get('local:filter'))->description);
  } else {
    $b->set_text('');
  }
}

my $cups;
my $g_imprime;

sub nonnul {
    my $s=shift;
    $s =~ s/\000//g;
    return($s);
}

sub autre_imprimante {
  my %alias=();
  my %trouve=();

  my ($ok,$imp_iter)=$w{'imprimante'}->get_active_iter;
  if($ok) {
    my $i=$w{'imprimante'}->get_model->get($imp_iter,COMBO_ID);
    debug "Choix imprimante $i";

    $config->set("global:options_impression/printer",{})
      if(!$config->get("options_impression/printer"));
    my $printer_settings=$config->get("options_impression/printer");

    $printer_settings->{$i}={}
      if(!$printer_settings->{$i});

    $w{print_object}->printer_options_table($w{printing_options_table},
					   \%w,$prefs,
					   $i,$printer_settings->{$i});

    $prefs->transmet_pref($g_imprime,prefix=>'imp',
                          root=>'options_impression');
    $prefs->transmet_pref($g_imprime,prefix=>'printer',
                          root=>"options_impression/printer/$i");
  } else {
    debug "No printer choice!";
  }
}

sub project_printing_method {
  if($config->get('project:pdfform')) {
    return('file');
  } else {
    return($config->get("methode_impression"));
  }
}

sub project_extract_with {
  if($config->get('project:pdfform')) {
    if($config->get('print_extract_with') eq 'qpdf') {
      return('qpdf');
    } else {
    return('pdftk+NA');
    }
  } else {
    return($config->get("print_extract_with"));
  }
}

sub sujet_impressions {

    if(! -f $config->get_absolute('doc_question')) {
	my $dialog = Gtk3::MessageDialog
	    ->new($w{'main_window'},
                  'destroy-with-parent',
                  'error','ok','');
        $dialog->set_markup(
# TRANSLATORS: Message when the user required printing the question paper, but it is not present (probably the working documents have not been properly generated).
			      __"You don't have any question to print: please check your source file and update working documents first.");
	$dialog->run;
	$dialog->destroy;

	return();
    }

    $projet{'_layout'}->begin_read_transaction('PGCN');
    my $c=$projet{'_layout'}->pages_count;
    $projet{'_layout'}->end_transaction('PGCN');
    if($c==0) {
	my $dialog = Gtk3::MessageDialog
	    ->new($w{'main_window'},
                  'destroy-with-parent',
                  'error','ok','');
        $dialog->set_markup(
# TRANSLATORS: Message when AMC does not know about the subject pages that has been generated. Usualy this means that the layout computation step has not been made.
			      __("Question's pages are not detected.")." "
			      .__"Perhaps you forgot to compute layouts?");
	$dialog->run;
	$dialog->destroy;

	return();
    }

    my $method=project_printing_method();

    if($method =~ /^CUPS/) {

      	my $print_module="AMC::Print::".lc($method);
	load($print_module);

	# checks for availibility

	my $error=$print_module->check_available();
	if($error) {
	  my $dialog = Gtk3::MessageDialog
	    ->new($w{'main_window'},
		  'destroy-with-parent',
		  'error','ok',
		  sprintf(__("You chose the printing method '%s' but it is not available (%s). Please install the missing dependencies or switch to another printing method."),$method,$error));
	    $dialog->run;
	    $dialog->destroy;

	    return();
	}

	$w{print_object}=$print_module->
	  new(useful_options=>$config->get("printer_useful_options"));

	# check for a installed printer

	debug "Checking for at least one CUPS printer...";

	my $default_printer=$w{print_object}->default_printer();

	if(!$default_printer) {
	    my $dialog = Gtk3::MessageDialog
		->new($w{'main_window'},
		      'destroy-with-parent',
		      'error','ok',
		      __("You chose a printing method using CUPS but there are no configured printer in CUPS. Please configure some printer or switch to another printing method."));
	    $dialog->run;
	    $dialog->destroy;

	    return();
	}
      }

    debug "Printing dialog...";

    $g_imprime=read_glade('choix_pages_impression',
			  qw/arbre_choix_copies bloc_imprimante answersheet_box imprimante printing_options_table bloc_fichier/);

    $prefs->transmet_pref($g_imprime,prefix=>'impall',
                          root=>"options_impression");

    debug "Printing method: ".$method;

    if($method =~ /^CUPS/) {
	$w{'bloc_imprimante'}->show();

	# les imprimantes :

	my @printers = $w{print_object}->printers_list();
	debug "Printers : ".join(' ',map { $_->{name} } @printers);
	my $p_model=cb_model(map { $_->{name}=>$_->{description} } @printers);
	$w{'imprimante'}->set_model($p_model);
	if(! $config->get('imprimante')) {
	    my $defaut=$w{print_object}->default_printer;
	    if($defaut) {
		$config->set('imprimante',$defaut);
	    } else {
		$config->set('imprimante',$printers[0]->{name});
	    }
	}
	my $i=model_id_to_iter($p_model,COMBO_ID,$config->get('imprimante'));
	if($i) {
	  $w{'imprimante'}->set_active_iter($i);
	  # (this will call autre_imprimante and transmet_pref with
	  # the right options)
	} else {
	  # updates the values in the GUI from the general options
	  $prefs->transmet_pref($g_imprime,prefix=>'imp',
                                root=>'options_impression');
	}
    }

    if($method eq 'file') {
	$w{'bloc_imprimante'}->hide();
	$w{'bloc_fichier'}->show();

	$prefs->transmet_pref($g_imprime,prefix=>'impf',
                              root=>'options_impression');
    }

    $copies_store->clear();
    $projet{'_layout'}->begin_read_transaction('PRNT');
    my $row=0;
    for my $c ($projet{'_layout'}->students()) {
      $copies_store->insert_with_values($row++,COPIE_N,$c);
    }
    $projet{'_layout'}->end_transaction('PRNT');

    $w{'arbre_choix_copies'}->set_model($copies_store);

    my $renderer=Gtk3::CellRendererText->new;
# TRANSLATORS: This is the title of the column containing the paper's numbers (1,2,3,...) in the table showing all available papers, from which the user will choose those he wants to print.
    my $column = Gtk3::TreeViewColumn->new_with_attributes (__"papers",
							    $renderer,
							    text=> COPIE_N );
    $w{'arbre_choix_copies'}->append_column ($column);

    $w{'arbre_choix_copies'}->get_selection->set_mode("multiple");


    $w{'choix_pages_impression'}->show();
}

sub sujet_impressions_cancel {

  if(get_debug()) {
    $prefs->reprend_pref(prefix=>'imp');
    $Data::Dumper::Indent = 0;
    debug(Dumper($config->get('options_impression')));
  }

  $w{'choix_pages_impression'}->destroy;
}

sub options_strings {
  my ($o)=@_;
  return(map { $_."=".$o->{$_} }
	 grep { ! /^_/ && !/^(repertoire|print_answersheet)$/
		  && exists($o->{$_}) && $o->{$_} && !ref($o->{$_}) }
	 (keys %$o));
}

sub options_string {
  my (@oos)=@_;
  return(join(',',map { options_strings($_) } (@oos)));
}

sub sujet_impressions_ok {
    my $os='none';
    my @e=();

    my @selected=$w{'arbre_choix_copies'}->get_selection()->get_selected_rows();
    for my $i (@{$selected[0]}) {
	push @e,$copies_store->get($copies_store->get_iter($i),COPIE_N) if($i);
    }

    $prefs->reprend_pref(prefix=>'impall');

    my $method=project_printing_method();

    if($method =~ /^CUPS/) {
      my ($ok,$imp_iter)=$w{'imprimante'}->get_active_iter;
      my $i;
      if($ok) {
	$i=$w{'imprimante'}->get_model->get($imp_iter,COMBO_ID);
      } else {
	$i='default';
      }
      $config->set('imprimante',$i);

      $prefs->reprend_pref(prefix=>'imp');
      $prefs->reprend_pref(prefix=>'printer');

      $os=options_string($config->get("options_impression"),
                         $config->get("options_impression/printer")->{$i});

      debug("Printing options : $os");
    }

    if($method eq 'file') {
	$prefs->reprend_pref(prefix=>'impf');

	if(!$config->get('options_impression/repertoire')) {
	    debug "Print to file : no destination...";
	    $config->set('options_impression/repertoire','');
	} else {
	  my $path=$config->get_absolute('options_impression/repertoire');
	  mkdir($path) if(! -e $path);
	}
    }

    $w{'choix_pages_impression'}->destroy;

    debug "Printing: ".join(",",@e);

    if(!@e) {
	# No page selected:
	my $dialog = Gtk3::MessageDialog
	    ->new($w{'main_window'},
		  'destroy-with-parent',
		  'info','ok',
		  __("You did not select any exam to print..."));
	$dialog->run;
	$dialog->destroy;
	return();
    }

    if(1+$#e <= 10) {
      # Less than 10 pages selected: is it a mistake?

      $projet{'_layout'}->begin_read_transaction('pPFP');
      my $max_p=$projet{'_layout'}->max_enter();
      my $students=$projet{'_layout'}->students_count();
      $projet{'_layout'}->end_transaction('pPFP');

      if($max_p>1) {
	# Some sheets have more than one enter-page: multiple scans
	# are not supported...
	my $resp=dialogue_apprentissage('PRINT_FEW_PAGES',
					'warning','yes-no',$students<=10,
					__("You selected only a few sheets to print.")."\n".
					__("As students are requested to write on more than one page, you must create as many exam sheets as necessary for all your students, with different sheets numbers, and print them all.")." ".
					__("If you print one or several sheets and photocopy them to have enough for all the students, <b>you won't be able to continue with AMC!</b>")."\n".
					__("Do you want to print the selected sheets anyway?"),
				       );
	return() if($resp eq 'no');
      } elsif($students<=10) {
	if($config->get('auto_capture_mode') != 1) {
	  # This looks strange: a few sheets printed, a few sheets
	  # generated, and photocopy mode not selected yet. Ask the
	  # user if he wants to select this mode now.
	  my $dialog = Gtk3::MessageDialog
	    ->new($w{'main_window'},
                  'destroy-with-parent',
                  'question','yes-no','');
          $dialog->set_markup(
			      __("You selected only a few sheets to print.")."\n".
			      "<b>".__("Are you going to photocopy some printed subjects before giving them to the students?")."</b>\n".
			      __("If so, the corresponding option will be set for this project.")." ".
			      __("However, you will be able to change this when giving your first scans to AMC.")
			     );
	  my $reponse=$dialog->run;
	  $dialog->destroy;
	  my $mult=($reponse eq 'yes' ? 1 : 0);
          $config->set('auto_capture_mode',$mult);
	}
      }
    }

    if($config->get('options_impression/print_answersheet') eq 'first') {
      # This options needs pdftk!
      if($config->get('print_extract_with') !~ /(pdftk|qpdf)/) {
        my $found=0;
      EXTRACT: for my $cmd (qw/qpdf pdftk/) {
          if(commande_accessible($cmd)) {
            my $dialog = Gtk3::MessageDialog
              ->new($w{'main_window'},
                    'destroy-with-parent',
                    'info','ok','');
            $dialog->set_markup(
# TRANSLATORS: the two %s will be replaced by the translations of "Answer sheet first" and "Extracting method".
                                sprintf(__("You selected the '%s' option, that uses '%s', so the %s has been set to '%s' for you."),
                                        __("Answer sheet first"),$cmd,
                                        __("Extracting method"),$cmd)
                               );
            $dialog->run;
            $dialog->destroy;

            $config->set("print_extract_with",$cmd);
            $found=1;
            last EXTRACT;
          }
        }
        if(!$found) {
	  my $dialog = Gtk3::MessageDialog
	    ->new($w{'main_window'},
                  'destroy-with-parent',
                  'error','ok','');
          $dialog->set_markup(
			      sprintf(__("You selected the '%s' option, but this option needs 'qpdf' or 'pdftk' to be installed on your system. Please install one of these and try again."),
				      __"Answer sheet first")
			     );
	  $dialog->run;
	  $dialog->destroy;
	  return();
	}
      }
    }

    my $fh=File::Temp->new(TEMPLATE => "nums-XXXXXX",
			   TMPDIR => 1,
			   UNLINK=> 1);
    print $fh join("\n",@e)."\n";
    $fh->seek( 0, SEEK_END );

    my @o_answer=('--no-split','--no-answer-first');
    if($config->get('options_impression/print_answersheet') eq 'split') {
      @o_answer=('--split','--no-answer-first');
    } elsif($config->get('options_impression/print_answersheet') eq 'first') {
      @o_answer=('--answer-first','--no-split');
    }

    my $extract_with=project_extract_with();

    commande('commande'=>["auto-multiple-choice","imprime",
			  "--methode",$method,
			  "--imprimante",$config->get('imprimante'),
			  "--options",$os,
			  "--output",$config->get_absolute('options_impression/repertoire')."/sheet-%e.pdf",
			  @o_answer,
			  "--print-command",$config->get('print_command_pdf'),
			  "--sujet",$config->get_absolute('doc_question'),
			  "--data",$config->get_absolute('data'),
			  "--progression-id",'impression',
			  "--progression",1,
			  "--debug",debug_file(),
			  "--fich-numeros",$fh->filename,
			  "--extract-with",$extract_with,
			  ],
	     'signal'=>2,
	     'texte'=>__"Print papers one by one...",
	     'progres.id'=>'impression',
	     'o'=>{'fh'=>$fh,'etu'=>\@e,'printer'=>$config->get('imprimante'),
                   'method'=>$method},
	     'fin'=>sub {
		 my $c=shift;
		 close($c->{'o'}->{'fh'});
		 save_state_after_printing($c->{'o'});
	     },

	     );
}

sub save_state_after_printing {
    my $c=shift;
    my $st=AMC::State::new('directory'=>$shortcuts->absolu('%PROJET/'));

    $st->read();

    my @files=grep { -f $shortcuts->absolu($_) }
      map { $config->get('doc_'.$_) }
      (qw/question solution setting catalog/);
    push @files,$config->get_absolute('texsrc');

    push @files,$config->get_absolute('filtered_source')
      if(-f $config->get_absolute('filtered_source'));

    if(!$st->check_local_md5(@files)) {
	$st=AMC::State::new('directory'=>$shortcuts->absolu('%PROJET/'));
	$st->add_local_files(@files);
    }

    $st->add_print('printer'=>$c->{'printer'},
		   'method'=>$c->{'method'},
		   'content'=>join(',',@{$c->{'etu'}}));
    $st->write();

}

sub calcule_mep {
    if($config->get('doc_setting') !~ /\.xy$/) {
	# OLD STYLE WORKING DOCUMENTS... Not supported anymore: update!
	my $dialog = Gtk3::MessageDialog
	    ->new($w{'main_window'},
                  'destroy-with-parent',
                  'error', # message type
                  'ok', # which set of buttons?
                  '');
        $dialog->set_markup(
			      __("Working documents are in an old format, which is not supported anymore.")." <b>"
			      .__("Please generate again the working documents!")."</b>");
	$dialog->run;
	$dialog->destroy;

	return;
    }

    commande('commande'=>["auto-multiple-choice","meptex",
			  "--debug",debug_file(),
			  "--src",$config->get_absolute('doc_setting'),
			  "--progression-id",'MEP',
			  "--progression",1,
			  "--data",$config->get_absolute('data'),
			  ],
	     'texte'=>__"Detecting layouts...",
	     'progres.id'=>'MEP',
	     'fin'=>sub {
	       my ($c,%data)=@_;
	       detecte_mep();
	       if(!$data{cancelled}) {
		 $projet{'_layout'}->begin_read_transaction('PGCN');
		 my $c=$projet{'_layout'}->pages_count();
		 $projet{'_layout'}->end_transaction('PGCN');
		 if($c<1) {
		   # avertissement...
		   my $dialog = Gtk3::MessageDialog
		     ->new($w{'main_window'},
                           'destroy-with-parent',
                           'error', # message type
                           'ok', # which set of buttons?
                           '');
                   $dialog->set_markup(
				       __("No layout detected.")." "
				       .__("<b>Don't go through the examination</b> before fixing this problem, otherwise you won't be able to use AMC for correction."));
		   $dialog->run;
		   $dialog->destroy;

		 } else {
		   dialogue_apprentissage('MAJ_MEP_OK','','',0,
					  __("Layouts are detected.")." "
					  .sprintf(__"You can check all is correct clicking on button <i>%s</i> and looking at question pages to see if red boxes are well positioned.",__"Check layouts")." "
					  .__"Then you can proceed to printing and to examination.");
		 }
	       }
	     });
}

sub verif_mep {
    saisie_manuelle(0,0,1);
}

### Actions des boutons de la partie SAISIE

sub saisie_manuelle {
    my ($self,$event,$regarder)=@_;
    $projet{'_layout'}->begin_read_transaction('PGCN');
    my $c=$projet{'_layout'}->pages_count();
    $projet{'_layout'}->end_transaction('PGCN');
    if($c>0) {

      if(!$regarder) {
	# if auto_capture_mode is not set, ask the user...
	my $n=check_auto_capture_mode();
	if($config->get('auto_capture_mode')<0) {
	  my $gsa=read_glade('choose-mode',
			     qw/saisie_auto_c_auto_capture_mode
				button_capture_go/);
	  $w{saisie_auto_cb_allocate_ids}='';
	  $prefs->transmet_pref($gsa,prefix=>'saisie_auto',
                                root=>'project:');
	  my $ret=$w{'choose-mode'}->run();
	  if($ret==1) {
	    $prefs->reprend_pref(prefix=>'saisie_auto');
	    $w{'choose-mode'}->destroy;
	  } else {
	    $w{'choose-mode'}->destroy;
	    return();
	  }
	}
      }

      # go for capture

      my $gm=AMC::Gui::Manuel::new
	(
	 'multiple'=>$config->get('auto_capture_mode'),
	 'data-dir'=>$config->get_absolute('data'),
	 'project-dir'=>$shortcuts->absolu('%PROJET'),
	 'sujet'=>$config->get_absolute('doc_question'),
	 'etud'=>'',
	 'dpi'=>$config->get('saisie_dpi'),
	 'seuil'=>$config->get('seuil'),
	 'seuil_up'=>$config->get('seuil_up'),
	 'seuil_sens'=>$config->get('seuil_sens'),
	 'seuil_eqm'=>$config->get('seuil_eqm'),
	 'global'=>0,
	 'encodage_interne'=>$config->get('encodage_interne'),
	 'image_type'=>$config->get('manuel_image_type'),
	 'retient_m'=>1,
	 'editable'=>($regarder ? 0 : 1),
	 'en_quittant'=>($regarder ? '' : 
			 sub { detecte_analyse(); assoc_state(); }),
	 'size_monitor'=>{config=>$config,
			  key=>($regarder?'checklayout':'manual')
			  .'_window_size'},
         invalid_color_name=>$config->get("view_invalid_color"),
         empty_color_name=>$config->get("view_empty_color"),
	);
    } else {
	my $dialog = Gtk3::MessageDialog
	    ->new($w{'main_window'},
                  'destroy-with-parent',
                  'error','ok','');
        $dialog->set_markup(
			      __("No layout for this project.")." "
# TRANSLATORS: Here, the first %s will be replaced with "Layout detection" (a button title), and the second %s with "Preparation" (the tab title where one can find this button).
			      .sprintf(__("Please use button <i>%s</i> in <i>%s</i> before manual data capture."),
				       __"Layout detection",
				       __"Preparation"));
	$dialog->run;
	$dialog->destroy;
    }
}

sub check_auto_capture_mode {
  $projet{'_capture'}->begin_read_transaction('ckac');
  my $n=$projet{'_capture'}->n_copies;
  if($n>0 && $config->get('auto_capture_mode') <0) {
    # the auto_capture_mode (sheets photocopied or not) is not set,
    # but some capture has already been done. This looks weird, but
    # it can be the case if captures were made with an old AMC
    # version, or if project parameters have not been saved...
    # So we try to detect the correct value from the capture data.
    $config->set('auto_capture_mode',
                 ($projet{'_capture'}->n_photocopy() > 0 ? 1 : 0));
  }
  $projet{'_capture'}->end_transaction('ckac');
  return($n);
}

sub saisie_automatique {
    # mode can't be changed if data capture has been made already
    my $n=check_auto_capture_mode;
    $projet{'_capture'}->begin_read_transaction('adcM');
    my $mcopy=$projet{'_capture'}->max_copy_number()+1;
    $w{'saisie_auto_allocate_start'}=$mcopy;
    $projet{'_capture'}->end_transaction('adcM');

    my $gsa=read_glade('saisie_auto',
		       qw/copie_scans
			  saisie_auto_c_auto_capture_mode
			  saisie_auto_cb_allocate_ids
			  button_capture_go/);
    $w{'copie_scans'}->set_active(1);
    $prefs->transmet_pref($gsa,prefix=>'saisie_auto',
                          root=>'project:');
    $w{'saisie_auto_cb_allocate_ids'}->set_label(sprintf(__"Pre-allocate sheet ids from the page numbers, starting at %d",$mcopy));

    $w{'saisie_auto_c_auto_capture_mode'}->set_sensitive($n==0);

    $w{saisie_auto}->show();
}

sub saisie_auto_mode_update {
  $config->set_local_keys('auto_capture_mode');
  # the mode value (auto_capture_mode) has been updated.
  valide_options_for_domain('saisie_auto','local',@_);
  my $acm=$config->get('local:auto_capture_mode');
  $acm=-1 if(!defined($acm));
  $w{'button_capture_go'}->set_sensitive($acm>=0);
  if($w{saisie_auto_cb_allocate_ids}) {
    if($acm == 1) {
      $w{'saisie_auto_cb_allocate_ids'}->show();
    } else {
      $w{'saisie_auto_cb_allocate_ids'}->hide();
    }
  }
}

sub saisie_auto_annule {
    $w{'saisie_auto'}->destroy();
}

sub saisie_auto_info {
  my $dialog=Gtk3::MessageDialog
    ->new($w{'saisie_auto'},'destroy-with-parent','info','ok','');
  $dialog->set_markup(
                      __("Automatic data capture can be done in two different modes:")."\n"
                      ."<b>".
# TRANSLATORS: This is a title for the AMC mode where the distributed exam papers are all different (different paper numbers at the top) -- photocopy is not used.
		      __("Different answer sheets").
		      ".</b> ".
		      __("In the most robust one, you give a different exam (with a different exam number) to every student. You must not photocopy subjects before distributing them.")."\n"
		      ."<b>".
# TRANSLATORS: This is a title for the AMC mode where some answer sheets have been photocopied before being distributed to the students.
		      __("Some answer sheets were photocopied").
		      ".</b> ".
		      __("In the second one (which can be used only if answer sheets to be scanned have one page per candidate) you can photocopy answer sheets and give the same subject to different students.")."\n"
		      .__("After the first automatic capture, you can't switch to the other mode.")
		      );
  $dialog->run;
  $dialog->destroy;
}

sub analyse_call {
  my (%oo)=@_;

  # meeds a little tmp disk space (for zooms), and first decent space
  # for AMC-getimages...
  return() if(!check_for_tmp_disk_space($oo{getimages} ? 20 : 2));

  # make temporary file with the list of images to analyse
  my $fh=File::Temp->new(TEMPLATE => "liste-XXXXXX",
			 TMPDIR => 1,
			 UNLINK=> 1);
  print $fh join("\n",@{$oo{'f'}})."\n";
  $fh->seek( 0, SEEK_END );

  # first try to see if some of the files are PDF forms

  $oo{fh}=$fh;
  $oo{liste}=$fh->filename;

  commande(commande=>["auto-multiple-choice","read-pdfform",
                      "--progression-id",'analyse',
                      "--list",$fh->filename,
                      "--debug",debug_file(),
                      ($config->get('auto_capture_mode') ? "--multiple" : "--no-multiple"),
                      "--data",$config->get_absolute('data'),
                     ],
           signal=>2,
           o=>\%oo,
           fin=>sub {
             my ($c,%data)=@_;
             ${$c->{o}->{overwritten}}+=$c->variable('overwritten')
               if($c->{o}->{overwritten} && $c->variable('overwritten'));
             if(!$data{cancelled}) {
               analyse_call_images(%{$c->{o}});
             }
           },
           'progres.id'=>$oo{'progres'},
           );
}

sub analyse_call_images {
  my (%oo)=@_;

  # extract individual images scans

  if($oo{'getimages'}) {
    my @args=("--progression-id",'analyse',
	      "--list",$oo{fh}->filename,
	      "--debug",debug_file(),
	      "--vector-density",$config->get('vector_scan_density'),
	     );
    push @args,"--copy-to",$oo{'copy'} if($oo{'copy'});
    push @args,"--force-convert" if($config->get("force_convert"));
    $projet{_layout}->begin_transaction('Orie');
    my $orientation=$projet{_layout}->orientation();
    $projet{_layout}->end_transaction('Orie');
    push @args,"--orientation",$orientation if($orientation);

    debug "Target orientation: $orientation";

    commande('commande'=>["auto-multiple-choice","getimages",
			  @args],
	     'signal'=>2,
	     'progres.id'=>$oo{'progres'},
             'o'=>\%oo,
	     'fin'=>sub {
	       my ($c,%data)=@_;
	       if(!$data{cancelled}) {
		 analyse_call_go(%{$c->{o}});
	       }
	     },
	    );
  } else {
    analyse_call_go(%oo);
  }
}

sub analyse_call_go {
  my (%oo)=@_;
  my @args=("--debug",debug_file(),
	    ($config->get('auto_capture_mode') ? "--multiple" : "--no-multiple"),
	    "--tol-marque",$config->get('tolerance_marque_inf').','.$config->get('tolerance_marque_sup'),
	    "--prop",$config->get('box_size_proportion'),
	    "--bw-threshold",$config->get('bw_threshold'),
	    "--progression-id",'analyse',
	    "--progression",1,
	    "--n-procs",$config->get('n_procs'),
	    "--data",$config->get_absolute('data'),
	    "--projet",$shortcuts->absolu('%PROJET/'),
	    "--cr",$config->get_absolute('cr'),
	    "--liste-fichiers",$oo{'liste'},
	    ($config->get('ignore_red') ? "--ignore-red" : "--no-ignore-red"),
	    ($config->get('try_three') ? "--try-three" : "--no-try-three"),
	   );

  push @args,"--pre-allocate",$oo{'allocate'} if($oo{'allocate'});

  # Diagnostic image file ?

  if($oo{'diagnostic'}) {
    push @args,"--debug-image-dir",$shortcuts->absolu('%PROJET/cr/diagnostic');
    push @args,"--no-tag-overwritten";
  }

  # call AMC-analyse

  commande('commande'=>["auto-multiple-choice","analyse",
			@args],
	   'signal'=>2,
	   'texte'=>$oo{'text'},
	   'progres.id'=>$oo{'progres'},
	   'o'=>{'fh'=>$oo{'fh'},overwritten=>$oo{overwritten}},
	   fin=>sub {
             my ($c,%data)=@_;
             debug "Overall [SCAN] overwritten pages @".$c.": "
               .($c->variable('overwritten') || "(none)");
             ${$c->{o}->{overwritten}} += $c->variable('overwritten')
               if($c->{o}->{overwritten} && $c->variable('overwritten'));
             debug "Calling original <fin> hook from analyse_call_go";
             &{$oo{fin}}($c,%data)
               if($oo{fin});
           },
	  );
}

sub clean_gtk2_filenames {
  my @f=@_;
  return(
	 map {
           if(ref($_) eq 'ARRAY') {
             return(clean_gtk2_filenames(@$_));
           } else {
             if (utf8::is_utf8($_)) {
               $_= Glib->filename_from_unicode ($_);
             } $_;
           }
	 } @f);
}

sub glib_filename {
  my ($n)=@_;
  utf8::downgrade($n);
  return(Glib::filename_display_name($n));
}

sub glib_project_name {
  return(glib_filename($projet{'nom'}));
}

sub saisie_auto_ok {
  my @f=clean_gtk2_filenames(sort { $a cmp $b } 
			     ($w{'saisie_auto'}->get_filenames()));
    my $copie=$w{'copie_scans'}->get_active();

    $prefs->reprend_pref(prefix=>'saisie_auto');
    $w{'saisie_auto'}->destroy();
    Gtk3::main_iteration while ( Gtk3::events_pending );

    clear_old('diagnostic',
	      $shortcuts->absolu('%PROJET/cr/diagnostic'));

    $w{'annulation'}->set_sensitive(1);

    my $overwritten=0;

    analyse_call('f'=>\@f,
		 'getimages'=>1,
		 'copy'=>($copie ? $shortcuts->absolu('scans/') : ''),
		 'text'=>__("Automatic data capture..."),
		 'progres'=>'analyse',
		 'allocate'=>($config->get('allocate_ids') ?
			      $w{'saisie_auto_allocate_start'} : 0),
                 overwritten=>\$overwritten,
		 'fin'=>sub {
		     my ($c,%data)=@_;
		     close($c->{'o'}->{'fh'});
		     detecte_analyse('apprend'=>1);
		     assoc_state();
		     if(!$data{cancelled}) {
		       notify_end_of_work('capture',__"Automatic data capture has been completed");
		     }
                     my $ov=${$c->{o}->{overwritten}};
                     if($ov>0) {
                       my $dialog = Gtk3::MessageDialog
                         ->new($w{'main_window'},
                               'destroy-with-parent',
                               'warning','ok','');
                       $dialog->set_markup(
                                           sprintf(__"Some of the pages you submitted (%d of them) have already been processed before. Old data has been overwritten.",$ov)
                                          );
                       $dialog->run;
                       $dialog->destroy;
                     }
		 },
	);

}

sub choisit_liste {
    my $dial=read_glade('liste_dialog')
	->get_object('liste_dialog');

    my @f;
    if($config->get('listeetudiants')) {
	@f=splitpath($config->get_absolute('listeetudiants'));
    } else {
	@f=splitpath($shortcuts->absolu('%PROJET/'));
    }
    $f[2]='';

    $dial->set_current_folder(catpath(@f));

    my $ret=$dial->run();
    debug("Names list file choice [$ret]");

    my $file=$dial->get_filename();
    $dial->destroy();

    if($ret eq '1') {
	# file chosen
	debug("List: ".$file);
	valide_liste('set'=>$file);
    } elsif($ret eq '2') {
	# No list
	valide_liste('set'=>'');
    } else {
	# Cancel
    }
}

sub edite_liste {
    my $f=$config->get_absolute('listeetudiants');
    debug "Editing $f...";
    commande_parallele($config->get('txt_editor'),$f);
}

sub students_list_show {
  $w{'liste_refresh'}->show();
  $monitor->remove_key('type','StudentsList');
}

sub students_list_hide {
  $w{'liste_refresh'}->hide();
  $monitor->remove_key('type','StudentsList');
  $monitor->add_file($config->get_absolute('listeetudiants'),
		     \&students_list_show,
		     type=>'StudentsList')
    if($config->get('listeetudiants'));
}

sub valide_liste {
    my %oo=@_;
    debug "* valide_liste";

    if(defined($oo{'set'}) && !$oo{'nomodif'}) {
	$config->set('listeetudiants',$shortcuts->relatif($oo{'set'}));
    }

    my $fl=$config->get_absolute('listeetudiants');
    $fl='' if(!$config->get('listeetudiants'));

    my $fn=$fl;
    $fn =~ s/.*\///;

    # For proper markup rendering escape '<', '>' and '&' characters
    # in filename with \<, \gt;, and \&
    $fn=Glib::Markup::escape_text(glib_filename($fn));

    if($fl) {
        $w{'liste_filename'}->set_markup("<b>$fn</b>");
        $w{'liste_filename'}->set_tooltip_text(glib_filename($fl));
	for(qw/liste_edit/) {
	    $w{$_}->set_sensitive(1);
	}
    } else {
# TRANSLATORS: Names list file : (none)
      $w{'liste_filename'}->set_markup(__"(none)");
      $w{'liste_filename'}->set_tooltip_text('');
      for(qw/liste_edit/) {
        $w{$_}->set_sensitive(0);
      }
    }

    $projet{_students_list}=AMC::NamesFile::new($fl,
			      'encodage'=>bon_encodage('liste'),
			      'identifiant'=>csv_build_name(),
			      );
    my ($err,$errlig)=$projet{_students_list}->errors();

    if($err) {
      students_list_show();
	if(!$oo{'noinfo'}) {
	    my $dialog = Gtk3::MessageDialog
		->new($w{'main_window'},
                      'destroy-with-parent',
                      'error','ok','');
            $dialog->set_markup(
				  sprintf(__"Unsuitable names file: %d errors, first on line %d.",$err,$errlig));
	    $dialog->run;
	    $dialog->destroy;
	}
	$prefs->store_register('liste_key'=>$cb_model_vide_key);
    } else {
	# problems with ID (name/surname)
	my $e=$projet{_students_list}->problem('ID.empty');
	if($e>0) {
	    debug "NamesFile: $e empty IDs";
	    students_list_show();
	    my $dialog = Gtk3::MessageDialog
		->new($w{'main_window'},
                      'destroy-with-parent',
                      'warning','ok','');
            $dialog->set_markup(
# TRANSLATORS: Here, do not translate 'name' and 'surname' (except in french), as the column names in the students list file has to be named in english in order to be properly detected.
				  sprintf(__"Found %d empty names in names file <i>%s</i>. Check that <b>name</b> or <b>surname</b> column is present, and always filled.",$e,$fl)." ".
				  __"Edit the names file to correct it, and re-read.");
	    $dialog->run;
	    $dialog->destroy;
	} else {
	    my $d=$projet{_students_list}->problem('ID.dup');
	    if(@$d) {
		debug "NamesFile: duplicate IDs [".join(',',@$d)."]";
		if($#{$d}>8) {
		    @$d=(@{$d}[0..8],'(and more)');
		}
		students_list_show();
		my $dialog = Gtk3::MessageDialog
		    ->new($w{'main_window'},
                          'destroy-with-parent',
                          'warning','ok','');
                $dialog->set_markup(
				      sprintf(__"Found duplicate names: <i>%s</i>. Check that all names are different.",join(', ',@$d))." ".__"Edit the names file to correct it, and re-read.");
		$dialog->run;
		$dialog->destroy;
	    } else {
		# OK, no need to refresh
		students_list_hide();
	    }
	}
	# transmission liste des en-tetes
	my @heads=$projet{_students_list}->heads_for_keys();
	debug "sorted heads: ".join(",",@heads);
# TRANSLATORS: you can omit the [...] part, just here to explain context
	$prefs->store_register('liste_key'=>cb_model('',__p("(none) [No primary key found in association list]"),
					     map { ($_,$_) }
					     (@heads)));
    }
    $prefs->transmet_pref($gui,prefix=>'pref_assoc',
                          keys=>["project:liste_key"]);
    assoc_state();
}

### Actions des boutons de la partie NOTATION

sub check_possible_assoc {
    my ($code)=@_;
    if(! -s $config->get_absolute('listeetudiants')) {
	my $dialog = Gtk3::MessageDialog
	    ->new($w{'main_window'},
                  'destroy-with-parent',
                  'error','ok','');
        $dialog->set_markup(
# TRANSLATORS: Here, %s will be replaced with the name of the tab "Data capture".
			      sprintf(__"Before associating names to papers, you must choose a students list file in tab \"%s\".",
				      __"Data capture"));
	$dialog->run;
	$dialog->destroy;
    } elsif(!$config->get('liste_key')) {
	my $dialog = Gtk3::MessageDialog
	    ->new($w{'main_window'},
                  'destroy-with-parent',
                  'error','ok','');
        $dialog->set_markup(
			      __("Please choose a key from primary keys in students list before association."));
	$dialog->run;
	$dialog->destroy;
    } elsif($code && ! $config->get('assoc_code')) {
	my $dialog = Gtk3::MessageDialog
	    ->new($w{'main_window'},
                  'destroy-with-parent',
                  'error','ok','');
        $dialog->set_markup(
			      __("Please choose a code (made with LaTeX command \\AMCcodeGrid or equivalent) before automatic association."));
	$dialog->run;
	$dialog->destroy;
    } else {
	return(1);
    }
    return(0);
}

# manual association
sub associe {
    return() if(!check_possible_assoc(0));
    if(-f $config->get_absolute('listeetudiants')) {
      my $ga=AMC::Gui::Association::new('cr'=>$config->get_absolute('cr'),
					'data_dir'=>$config->get_absolute('data'),
					'liste'=>$config->get_absolute('listeetudiants'),
					'liste_key'=>$config->get('liste_key'),
					'identifiant'=>csv_build_name(),

					'fichier-liens'=>$config->get_absolute('association'),
					'global'=>0,
					'assoc-ncols'=>$config->get('assoc_ncols'),
					'encodage_liste'=>bon_encodage('liste'),
					'encodage_interne'=>$config->get('encodage_interne'),
					'rtl'=>$config->get('annote_rtl'),
					'fin'=>sub {
					  assoc_state();
					},
					'size_prefs'=>($config->get('conserve_taille') ? $config : ''),
				       );
      if($ga->{'erreur'}) {
	my $dialog = Gtk3::MessageDialog
	  ->new($w{'main_window'},
		'destroy-with-parent',
		'error','ok',
		$ga->{'erreur'});
	$dialog->run;
	$dialog->destroy;
      }
    } else {
	my $dialog = Gtk3::MessageDialog
	    ->new($w{'main_window'},
		  'destroy-with-parent',
		  'info','ok',
# TRANSLATORS: Here, %s will be replaced with "Students identification", which refers to a paragraph in the tab "Marking" from AMC main window.
		  sprintf(__"Before associating names to papers, you must choose a students list file in paragraph \"%s\".",
			  __"Students identification"));
	$dialog->run;
	$dialog->destroy;

    }
}

# automatic association
sub associe_auto {
    return() if(!check_possible_assoc(1));

    commande('commande'=>["auto-multiple-choice","association-auto",
			  pack_args("--data",$config->get_absolute('data'),
				    "--notes-id",$config->get('assoc_code'),
				    "--liste",$config->get_absolute('listeetudiants'),
				    "--liste-key",$config->get('liste_key'),
				    "--csv-build-name",csv_build_name(),
				    "--encodage-liste",bon_encodage('liste'),
				    "--debug",debug_file(),
				    ($config->get('assoc_code') eq '<preassoc>' ?
				     "--pre-association" : "--no-pre-association"),
				   ),
	     ],
	     'texte'=>__"Automatic association...",
	     'fin'=>sub {
	       my ($c,%data)=@_;
	       assoc_state();
	       assoc_resultat() if(!$data{cancelled});
	     },
	);
}

# automatic association finished : explain what to do after
sub assoc_resultat {
    my $mesg=1;

    $projet{'_association'}->begin_read_transaction('ARCC');
    my ($auto,$man,$both)=$projet{'_association'}->counts();
    $projet{'_association'}->end_transaction('ARCC');

    my $dialog=Gtk3::MessageDialog
      ->new($w{'main_window'},
            'destroy-with-parent',
            'info','ok','');
    $dialog->set_markup(
			sprintf(__("Automatic association completed: %d students recognized."),$auto).
# TRANSLATORS: Here %s and %s will be replaced with two parameters names: "Primary key from this list" and "Code name for automatic association".
			($auto==0 ? "\n<b>".sprintf(__("Please check \"%s\" and \"%s\" values and try again."),
						    __("Primary key from this list"),
						    __("Code name for automatic association"))."</b>" : "")
		       );
    $dialog->run;
    $dialog->destroy;

    dialogue_apprentissage('ASSOC_AUTO_OK','','',0,
			   __("Automatic association is now finished. You can ask for manual association to check that all is fine and, if necessary, read manually students names which have not been automatically identified.")) if($auto>0);
}

sub valide_options_correction {
  my ($ww,$o)=@_;
  my $name=$ww->get_name();
  debug "Options validation from $name";
  if(!$w{$name}) {
    debug "WARNING: Option validation failed, unknown name $name.";
  } else {
    $config->set("project:$name",$w{$name}->get_active() ? 1 : 0);
  }
}

sub valide_options_for_domain {
    my ($domain,$container,$widget,$user_data)=@_;
    $container='project' if(!$container);
    if($widget) {
	my $name=$widget->get_name();
	debug "<$domain> options validation for widget $name";

	if($name =~ /${domain}_[a-z]+_(.*)/) {
	    $prefs->reprend_pref(prefix=>$domain,keys=>["project:".$1],trace=>1,container=>$container);
	} else {
	  debug "Widget $name is not in domain <$domain>!";
	}
    } else {
	debug "<$domain> options validation: ALL";
	$prefs->reprend_pref(prefix=>$domain,container=>$container);
    }
}

sub valide_options_association {
  $previous_liste_key=$config->get('liste_key');
  valide_options_for_domain('pref_assoc','',@_);
}

sub valide_options_preparation {
    valide_options_for_domain('pref_prep','',@_);
}

sub filter_changed {
  my (@args)=@_;

  # check it is a different value...

  my $old_filter=$config->get('filter');

  debug "Filter changed callback / old=$old_filter";

  $config->set_local_keys();
  $config->set('local:filter',$old_filter);
  valide_options_for_domain('pref_prep','local',@_);
  my $new_filter=$config->get('local:filter');
  return if($old_filter eq $new_filter);

  debug "Filter changed -> ".$new_filter;

  # working document already built: ask for confirmation

  if(-f $config->get_absolute('doc_question')) {
    debug "Ask for confirmation";
    my $text;
    if($projet{'_capture'}->n_pages_transaction()>0) {
      $text=__("The working documents are already prepared with the current file format. If you change the file format, working documents and all other data for this project will be ereased.").' '
	.__("Do you wish to continue?")." "
	  .__("Click on Ok to erease old working documents and change file format, and on Cancel to get back to the same file format.")
	    ."\n<b>".__("To allow the use of an already printed question, cancel!")."</b>";
    } else {
      $text=__("The working documents are already prepared with the current file format. If you change the file format, working documents will be ereased.").' '
	.__("Do you wish to continue?")." "
	  .__("Click on Ok to erease old working documents and change file format, and on Cancel to get back to the same file format.");
    }
    my $dialog = Gtk3::MessageDialog
      ->new($w{'main_window'},
            'destroy-with-parent',
            'question','ok-cancel','');
    $dialog->set_markup($text);
    my $reponse=$dialog->run;
    $dialog->destroy;

    if($reponse eq 'cancel') {
      $prefs->transmet_pref($gui,prefix=>'pref_prep',
                            root=>'project:');
      return(0);
    }

    clear_processing('doc:');

  }

  valide_options_preparation(@args);

  # No source created: change source filename

  if(!-f $config->get_absolute('texsrc') || -z $config->get_absolute('texsrc')) {
    $config->set('project:texsrc','%PROJET/'.
      ("AMC::Filter::register::".$config->get('filter'))
	->default_filename());
    $w{button_edit_src}->set_tooltip_text(glib_filename($config->get_absolute('texsrc')));
  }

}

sub valide_options_notation {
    valide_options_for_domain('notation','',@_);
    if($config->key_changed("regroupement_compose")) {
      annotate_source_change($projet{'_capture'},1);
    }
    $w{'groupe_model'}->set_sensitive($config->get('regroupement_type') eq 'STUDENTS');
}

sub change_liste_key {
  valide_options_association();

  debug "New liste_key: ".$config->get('liste_key');
  if($projet{_students_list}->head_n_duplicates($config->get('liste_key'))) {
    debug "Invalid key: back to old value $previous_liste_key";

    my $dialog = Gtk3::MessageDialog
      ->new($w{'main_window'},
	    'destroy-with-parent',
	    'error','ok',
	    __"You can't choose column '%s' as a key in the students list, as it contains duplicates (value '%s')",
	    $config->get('liste_key'),
	    $projet{_students_list}->head_first_duplicate($config->get('liste_key')),
	   );
    $dialog->run;
    $dialog->destroy;


    $config->set('liste_key',
                 ($previous_liste_key ne $config->get('liste_key')
                  ? $previous_liste_key : ""));
    $prefs->transmet_pref($gui,prefix=>'pref_assoc',
                          keys=>['project:liste_key']);
    return();
  }
  if ($config->get('liste_key')) {

    $projet{'_association'}->begin_read_transaction('cLky');
    my $assoc_liste_key=$projet{'_association'}->variable('key_in_list');
    $assoc_liste_key='' if(!$assoc_liste_key);
    my ($auto,$man,$both)=$projet{'_association'}->counts();
    $projet{'_association'}->end_transaction('cLky');

    debug "Association [$assoc_liste_key] counts: AUTO=$auto MANUAL=$man BOTH=$both";

    if ($assoc_liste_key ne $config->get('liste_key')
	&& $auto+$man>0) {
      # liste_key has changed and some association has been
      # made with another liste_key

      if ($man>0) {
	# manual association work has been made

	my $dialog=Gtk3::MessageDialog
	  ->new($w{'main_window'},
                'destroy-with-parent',
                'warning','yes-no','');
        $dialog->set_markup(
			    sprintf(__("The primary key from the students list has been set to \"%s\", which is not the value from the association data."),$config->get('liste_key'))." ".
			    sprintf(__("Some manual association data has be found, which will be lost if the primary key is changed. Do you want to switch back to the primary key \"%s\" and keep association data?"),$assoc_liste_key)
			   );
        $dialog->get_widget_for_response('yes')->get_style_context()->add_class("suggested-action");
	my $resp=$dialog->run;
	$dialog->destroy;

	if ($resp eq 'no') {
	  # clears association data
	  clear_processing('assoc');
	  # automatic association run
	  if ($config->get('assoc_code') && $auto>0) {
	    associe_auto;
	  }
	} else {
	  $config->set('liste_key',$assoc_liste_key);
	  $prefs->transmet_pref($gui,prefix=>'pref_assoc',
                                keys=>['project:liste_key']);
	}
      } else {
	if ($config->get('assoc_code')) {
	  # only automated association, easy to re-run
	  my $dialog=Gtk3::MessageDialog
	    ->new($w{'main_window'},
                  'destroy-with-parent',
                  'info','ok','');
          $dialog->set_markup(
			      sprintf(__("The primary key from the students list has been set to \"%s\", which is not the value from the association data."),$config->get('liste_key'))." ".
			      __("Automatic papers/students association will be re-run to update the association data.")
			     );
	  $dialog->run;
	  $dialog->destroy;

	  clear_processing('assoc');
	  associe_auto();
	}
      }
    }
  }
  assoc_state();
}

sub voir_notes {
  $projet{'_scoring'}->begin_read_transaction('smMC');
  my $c=$projet{'_scoring'}->marks_count;
  $projet{'_scoring'}->end_transaction('smMC');
  if($c>0) {
    my $n=AMC::Gui::Notes::new
      (scoring=>$projet{'_scoring'},
       layout=>$projet{'_layout'},
       size_prefs=>($config->get('conserve_taille') ? $config : ''),
      );
  } else {
    my $dialog = Gtk3::MessageDialog
      ->new($w{'main_window'},
	    'destroy-with-parent',
	    'info','ok',
	    sprintf(__"Papers are not yet corrected: use button \"%s\".",
# TRANSLATORS: This is a button: "Mark" is here an action to be called by the user. When clicking this button, the user requests scores to be computed for all students.
		    __"Mark"));
    $dialog->run;
    $dialog->destroy;
  }
}

# Get the number of copies used to build the working documents
sub original_n_copies {
  my $n=$projet{'_layout'}->variable_transaction('build:ncopies');
  if(defined($n) && $n ne '') {
    $n=0 if($n eq 'default');
  } else {
    # Documents were built with an older AMC version: use value from GUI
    $n=$config->get('nombre_copies');
  }
  return($n);
}

sub noter {
  if($config->get('maj_bareme')) {
    my $mode="b";
    my $pdf_corrected=$config->get_absolute('doc_indiv_solution');
    if(-f $pdf_corrected) {
      debug "Removing pre-existing $pdf_corrected";
      unlink($pdf_corrected);
    }
    $mode.='k' if($config->get('prepare_indiv_solution'));

    commande('commande'=>["auto-multiple-choice","prepare",
			  "--out-corrige-indiv",$pdf_corrected,
			  "--n-copies",original_n_copies(),
			  "--with",moteur_latex(),
			  "--filter",$config->get('filter'),
			  "--filtered-source",$config->get_absolute('filtered_source'),
			  "--debug",debug_file(),
			  "--progression-id",'bareme',
			  "--progression",1,
			  "--data",$config->get_absolute('data'),
			  "--mode",$mode,
			  $config->get_absolute('texsrc'),
			 ],
	     'texte'=>__"Extracting marking scale...",
	     'fin'=>sub {
               my ($c,%data)=@_;
               check_sty_version($c);
               noter_postcorrect();
             },
	     'progres.id'=>'bareme');
  } else {
    noter_calcul('','');
  }
}

my $g_pcid;
my %postcorrect_ids=();
my $postcorrect_copy_0;
my $postcorrect_copy_1;
my $postcorrect_student_min;
my $postcorrect_student_max;


sub noter_postcorrect {
  my ($c,%data)=@_;

  detecte_documents();

  return if($data{cancelled});

    # check marking scale data: in PostCorrect mode, ask for a sheet
    # number to get right answers from...

    if($projet{'_scoring'}->variable_transaction('postcorrect_flag')) {

	debug "PostCorrect option ON";

	# gets available sheet ids

	%postcorrect_ids=();

	$projet{'_capture'}->begin_read_transaction('PCex');
	my $sth=$projet{'_capture'}->statement('studentCopies');
	$sth->execute;
	while(my $sc=$sth->fetchrow_hashref) {
	  $postcorrect_student_min=$sc->{'student'} if(!defined($postcorrect_student_min));
	  $postcorrect_ids{$sc->{'student'}}->{$sc->{'copy'}}=1;
	  $postcorrect_student_max=$sc->{'student'};
	}
	$projet{'_capture'}->end_transaction('PCex');

	# ask user for a choice

	$g_pcid=read_glade('choix_postcorrect',
			   qw/postcorrect_student postcorrect_copy
			      postcorrect_set_multiple
			      postcorrect_photo postcorrect_apply/);

	AMC::Gui::PageArea::add_feuille($w{'postcorrect_photo'});

	debug "Student range: $postcorrect_student_min,$postcorrect_student_max\n";
	$w{'postcorrect_student'}->set_range($postcorrect_student_min,$postcorrect_student_max);

	if($config->get('postcorrect_student')) {
	  for (qw/student copy/) {
	    $w{'postcorrect_'.$_}
	      ->set_value($config->get('postcorrect_'.$_));
	  }
	} else {
	  $w{'postcorrect_student'}->set_value($postcorrect_student_min);
	  my @c=sort { $a <=> $b } (keys %{$postcorrect_ids{$postcorrect_student_min}});
	  $w{'postcorrect_copy'}->set_value($c[0]);
	}

	$w{postcorrect_set_multiple}->set_active($config->get("postcorrect_set_multiple"));

        $w{choix_postcorrect}->show();

    } else {
	noter_calcul('','');
    }
}

sub postcorrect_change_copy {
    my $student=$w{'postcorrect_student'}->get_value();
    my $copy=$w{'postcorrect_copy'}->get_value();

    $w{'postcorrect_apply'}->set_sensitive($postcorrect_ids{$student}->{$copy});

    $projet{'_capture'}->begin_read_transaction('PCCN');
    my ($f)=$projet{'_capture'}->zone_images($student,$copy,ZONE_NAME);
    $projet{'_capture'}->end_transaction('PCCN');
    if(!defined($f)) {
      $f='' ;
    } else {
      $f=$config->get_absolute('cr')."/$f";
    }
    debug "Postcorrect name field image: $f";
    if(-f $f) {
      $w{'postcorrect_photo'}->set_content(image=>$f);
    } else {
      $w{'postcorrect_photo'}->set_content();
    }
}

sub postcorrect_change {
  my $student=$w{'postcorrect_student'}->get_value();

  my @c=sort { $a <=> $b } (keys %{$postcorrect_ids{$student}});
  $postcorrect_copy_0=$c[0];
  $postcorrect_copy_1=$c[$#c];

  debug "Postcorrect copy range for student $student: $c[0],$c[$#c]\n";
  $w{'postcorrect_copy'}->set_range($c[0],$c[$#c]);

  postcorrect_change_copy;
}

sub postcorrect_student_exists {
  my ($student)=@_;
  my @c=();
  @c=(keys %{$postcorrect_ids{$student}}) if($postcorrect_ids{$student});
  return($#c>=0 ? 1 : 0);
}

sub postcorrect_previous {
    my $student=$w{'postcorrect_student'}->get_value();
    my $copy=$w{'postcorrect_copy'}->get_value();

    $copy--;
    if($copy<$postcorrect_copy_0) {
      do { $student-- } while($student>=$postcorrect_student_min
			      && !postcorrect_student_exists($student));
      if($student>=$postcorrect_student_min) {
	$w{'postcorrect_student'}->set_value($student);
	$w{'postcorrect_copy'}->set_value(10000);
      }
    } else {
      $w{'postcorrect_copy'}->set_value($copy);
    }
}

sub postcorrect_next {
    my $student=$w{'postcorrect_student'}->get_value();
    my $copy=$w{'postcorrect_copy'}->get_value();

    $copy++;
    if($copy>$postcorrect_copy_1) {
      do { $student++ } while($student<=$postcorrect_student_max
			      && !postcorrect_student_exists($student));
      if($student<=$postcorrect_student_max) {
	$w{'postcorrect_student'}->set_value($student);
	$w{'postcorrect_copy'}->set_value(0);
      }
    } else {
      $w{'postcorrect_copy'}->set_value($copy);
    }
}

sub choix_postcorrect_cancel {
    $w{'choix_postcorrect'}->destroy();
}

sub choix_postcorrect_ok {
    my $student=$w{'postcorrect_student'}->get_value();
    my $copy=$w{'postcorrect_copy'}->get_value();
    my $mult=$w{postcorrect_set_multiple}->get_active();
    $w{'choix_postcorrect'}->destroy();

    $config->set('postcorrect_student',$student);
    $config->set('postcorrect_copy',$copy);
    $config->set('postcorrect_set_multiple',$mult);

    noter_calcul($student,$copy,$mult);
}

sub noter_calcul {

    my ($postcorrect_student,$postcorrect_copy,$postcorrect_set_multiple)=@_;

    debug "Using sheet $postcorrect_student:$postcorrect_copy to get correct answers"
	if($postcorrect_student);

    # computes marks.

    commande('commande'=>["auto-multiple-choice","note",
			  "--debug",debug_file(),
			  "--data",$config->get_absolute('data'),
			  "--seuil",$config->get('seuil'),
			  "--seuil-up",$config->get('seuil_up'),

			  "--grain",$config->get('note_grain'),
			  "--arrondi",$config->get('note_arrondi'),
			  "--notemax",$config->get('note_max'),
			  ($config->get('note_max_plafond') ? "--plafond" : "--no-plafond"),
			  "--notenull",$config->get('note_null'),
			  "--notemin",$config->get('note_min'),
			  "--postcorrect-student",$postcorrect_student,
			  "--postcorrect-copy",$postcorrect_copy,
			  ($postcorrect_set_multiple ?
			   "--postcorrect-set-multiple" :
			   "--no-postcorrect-set-multiple"),

			  "--progression-id",'notation',
			  "--progression",1,
			  ],
	     'signal'=>2,
	     'texte'=>__"Computing marks...",
	     'progres.id'=>'notation',
	     'fin'=>sub {
	       my ($c,%data)=@_;
	       notify_end_of_work('grading',
				  __"Grading has been completed")
		 if(!$data{cancelled});
	       noter_resultat();
	     },
	     );
}

sub noter_resultat {
  $projet{'_scoring'}->begin_read_transaction('MARK');
  my $avg=$projet{'_scoring'}->average_mark;

  my $ok;
  my $text;
  if(defined($avg)) {
    $ok='info';
# TRANSLATORS: This is the marks mean for all students.
    $text=sprintf(__"Mean: %.2f",$avg);
  } else {
    $ok='error';
    $text=__("No marks computed");
  }
  set_state('marking',$ok,$text);

  my @codes=$projet{'_scoring'}->codes;
  my $pre_assoc=$projet{'_layout'}->pre_association();

  $projet{'_scoring'}->end_transaction('MARK');

  debug "Codes : ".join(',',@codes);

# TRANSLATORS: you can omit the [...] part, just here to explain context
  my @cbs=(''=>__p("(none) [No code found in LaTeX file]"));
  if(my $el=get_enc($config->get('encodage_latex'))) {
    push @cbs,map { $_=>decode($el->{iso},$_) } (@codes);
  } else {
    push @cbs,map { $_=>$_ } (@codes);
  }
  if($pre_assoc) {
    push @cbs,'<preassoc>',__"Pre-association";
    debug "Adding pre-association item";
  }
  $prefs->store_register('assoc_code'=>cb_model(@cbs));
  $prefs->transmet_pref($gui,prefix=>'pref_assoc',
                        keys=>['project:assoc_code']);

  $w{'onglet_reports'}->set_sensitive(defined($avg));
}

sub assoc_state {
  my $i='question';
  my $t='';
  if(! -s $config->get_absolute('listeetudiants')) {
    $t=__"No students list file";
  } elsif(!$config->get('liste_key')) {
    $t=__"No primary key from students list file";
  } else {
    $projet{'_association'}->begin_read_transaction('ARST');
    my $mc=$projet{'_association'}->missing_count;
    $projet{'_association'}->end_transaction('ARST');
    if($mc) {
      $t=sprintf((__"Missing identification for %d answer sheets"),$mc);
    } else {
      $t=__"All completed answer sheets are associated with a student name";
      $i='info';
    }
  }
  set_state('assoc',$i,$t);
}

sub opt_symbole {
    my ($s)=@_;
    my $k=$s;

    $k =~ s/-/_/g;
    my $type=$config->get('symbole_'.$k.'_type','none');
    my $color=$config->get('symbole_'.$k.'_color','red');

    return("$s:$type/$color");
}

sub select_students {
  my ($id_file)=@_;

  # restore last setting
  my %ids=();
  if (open(IDS,$id_file)) {
    while (<IDS>) {
      chomp;
      $ids{$_}=1 if(/^[0-9]+(:[0-9]+)?$/);
    }
    close(IDS);
  }
  # dialog to let the user choose...
  my $gstud=read_glade('choose_students',
		       qw/choose_students_area students_instructions
			  students_select_list students_list_search/);
  my $lk=$config->get('liste_key');

  my $students_store=Gtk3::ListStore
    ->new('Glib::String','Glib::String','Glib::String',
	  'Glib::String','Glib::String',
	  'Glib::Boolean','Glib::Boolean');

  my $filtered=Gtk3::TreeModelFilter->new($students_store);
  $filtered->set_visible_column(5);
  my $filtered_sorted=Gtk3::TreeModelSort->new_with_model($filtered);

  $filtered_sorted->set_sort_func(0,\&sort_num,0);
  $filtered_sorted->set_sort_func(1,\&sort_string,1);

  $w{'students_list_store'}=$students_store;
  $w{'students_list_filtered'}=$filtered;
  $w{'students_list_filtered_sorted'}=$filtered_sorted;

  $w{'students_select_list'}->set_model($filtered_sorted);
  my $renderer=Gtk3::CellRendererText->new;
  my $column = Gtk3::TreeViewColumn->new_with_attributes (__"exam ID",
							  $renderer,
							  text=> 0);
  $column->set_sort_column_id(0);
  $w{'students_select_list'}->append_column ($column);

  if($lk) {
    $column = Gtk3::TreeViewColumn->new_with_attributes ($lk,
							 $renderer,
							 text=> 4);
    $column->set_sort_column_id(4);
    $filtered_sorted->set_sort_func(4,\&sort_string,4);
    $w{'students_select_list'}->append_column ($column);
  }

  $column = Gtk3::TreeViewColumn->new_with_attributes (__"student",
						       $renderer,
						       text=> 1);
  $column->set_sort_column_id(1);
  $w{'students_select_list'}->append_column ($column);

  $projet{'_capture'}->begin_read_transaction('gSLi');
  my $key=$projet{'_association'}->variable('key_in_list');
  my @selected_iters=();
  my $i=0;
  for my $sc ($projet{'_capture'}->student_copies) {
    my $id=$projet{'_association'}->get_real(@$sc);
    my ($name)=$projet{_students_list}->data($key,$id,test_numeric=>1);
    my $iter=$students_store->insert_with_values($i++,
						 0=>studentids_string(@$sc),
						 1=>$name->{'_ID_'},
						 2=>$sc->[0],3=>$sc->[1],
						 5=>1,
						 4=>($lk ? $name->{$lk} : ''),
						);
    push @selected_iters,$iter if($ids{studentids_string(@$sc)});
  }
  $projet{'_capture'}->end_transaction('gSLi');

  $w{'students_select_list'}->get_selection->set_mode(GTK_SELECTION_MULTIPLE);
  for (@selected_iters) {
    $w{'students_select_list'}->get_selection->select_iter
      ($filtered_sorted->convert_child_iter_to_iter
       ($filtered->convert_child_iter_to_iter($_)));
  }

  my $resp=$w{'choose_students'}->run;

  select_students_save_selected_state();

  my @k=();

  if($resp==1) {
    $students_store->foreach(sub {
			       my ($model,$path,$iter,$user)=@_;
			       push @k,[$students_store->get($iter,2,3)]
				 if($students_store->get($iter,6));
			       return(0);
			     });
  }

  $w{'choose_students'}->destroy;

  if ($resp==1) {
    open(IDS,">$id_file");
    for (@k) {
      print IDS studentids_string(@$_)."\n";
    }
    close(IDS);
  } else {
    return();
  }

  return(1);
}

sub select_students_save_selected_state {
  my $sel=$w{'students_select_list'}->get_selection;
  my $fs=$w{'students_list_filtered_sorted'};
  my $f=$w{'students_list_filtered'};
  my $s=$w{'students_list_store'};
  my @states=();
  $fs->foreach(sub {
		 my ($model,$path,$iter,$user)=@_;
		 push @states,[$f->convert_iter_to_child_iter
			       ($fs->convert_iter_to_child_iter($iter)),
			       $sel->iter_is_selected($iter)];
		 return(0);
	       });
  for my $row (@states) {
    $s->set($row->[0],6=>$row->[1]);
  }
}

sub select_students_recover_selected_state {
  my $sel=$w{'students_select_list'}->get_selection;
  my $f=$w{'students_list_filtered'};
  my $fs=$w{'students_list_filtered_sorted'};
  my $s=$w{'students_list_store'};
  $fs->foreach(sub {
		 my ($model,$path,$iter,$user)=@_;
		 if($s->get($f->convert_iter_to_child_iter
			    ($fs->convert_iter_to_child_iter($iter)),6)) {
		   $sel->select_iter($iter);
		 } else {
		   $sel->unselect_iter($iter);
		 }
		 return(0);
	       });
}

sub select_students_search {
  select_students_save_selected_state();
  my $pattern=$w{'students_list_search'}->get_text;
  my $s=$w{'students_list_store'};
  $s->foreach(sub {
		my ($model,$path,$iter,$user)=@_;
		my ($id,$n,$nb)=$s->get($iter,0,1,4);
		$s->set($iter,5=>
			((!$pattern)
			 || $id =~ /$pattern/i || (defined($n) && $n =~ /$pattern/i)
			 || (defined($nb) && $nb =~ /$pattern/i) ? 1 : 0));
		return(0);
	      });
  select_students_recover_selected_state();
}

sub select_students_all {
  $w{'students_list_search'}->set_text('');
}

sub annote_copies {
  my $id_file='';

  if($config->get('regroupement_copies') eq 'SELECTED') {
    # use a file in project directory to store students ids for which
    # sheets will be annotated
    $id_file=$shortcuts->absolu('%PROJET/selected-ids');
    return() if(!select_students($id_file));
  }

  my $single_output='';

  if($config->get('regroupement_type') eq 'ALL') {
    $single_output=($id_file ?
# TRANSLATORS: File name for single annotated answer sheets with only some selected students. Please use simple characters.
                   (__("Selected_students")).".pdf" :
# TRANSLATORS: File name for single annotated answer sheets with all students. Please use simple characters.
                   (__("All_students")).".pdf" );
  }

  commande('commande'=>["auto-multiple-choice","annotate",
			pack_args("--cr",$config->get_absolute('cr'),
				  "--project",$shortcuts->absolu('%PROJET/'),
				  "--projects",$shortcuts->absolu('%PROJETS/'),
				  "--data",$config->get_absolute('data'),
				  "--subject",$config->get_absolute('doc_question'),
				  "--corrected",$config->get_absolute('doc_indiv_solution'),
				  "--filename-model",$config->get('modele_regroupement'),
				  ($config->get('ascii_filenames')
                                   ?"--force-ascii":"--no-force-ascii"),
				  "--single-output",$single_output,
				  "--sort",$config->get('export_sort'),
				  "--id-file",$id_file,
				  "--debug",debug_file(),
				  "--progression-id",'annotate',
				  "--progression",1,
				  "--line-width",$config->get('symboles_trait'),
				  "--font-name",$config->get('annote_font_name'),
				  "--symbols",join(',',map { opt_symbole($_); } 
						   (qw/0-0 0-1 1-0 1-1/)),
				  ($config->get('symboles_indicatives')
                                   ?"--indicatives":"--no-indicatives"),
				  "--position",$config->get('annote_position'),
				  "--dist-to-box",$config->get('annote_ecart'),
				  "--n-digits",$config->get('annote_chsign'),
				  "--verdict",$config->get('verdict'),
				  "--verdict-question",$config->get('verdict_q'),
				  "--verdict-question-cancelled",$config->get('verdict_qc'),
				  "--names-file",$config->get_absolute('listeetudiants'),
				  "--names-encoding",bon_encodage('liste'),
				  "--csv-build-name",csv_build_name(),
				  ($config->get('annote_rtl') ? "--rtl" : "--no-rtl"),
				  "--changes-only",1,
				  "--sort",$config->get('export_sort'),
				  "--compose",$config->get('regroupement_compose'),
				  "--n-copies",original_n_copies(),
				  "--src",$config->get_absolute('texsrc'),
				  "--with",moteur_latex(),
				  "--filter",$config->get('filter'),
				  "--filtered-source",$config->get_absolute('filtered_source'),
				  "--embedded-max-size",$config->get('embedded_max_size'),
				  "--embedded-format",$config->get('embedded_format'),
				  "--embedded-jpeg-quality",$config->get('embedded_jpeg_quality'),
				 )
		       ],
	   'texte'=>__"Annotating papers...",
	   'progres.id'=>'annotate',
	   'fin'=>sub {
	     my ($c,%data);
	     notify_end_of_work('annotation',__"Annotations have been completed")
	       if(!$data{cancelled});
	   },
	  );
}

sub annotate_papers {

  valide_options_notation();
  maj_export();

  annote_copies;
}

sub view_dir {
    my ($dir)=@_;

    debug "Look at $dir";
    my $seq=0;
    my @c=map { $seq+=s/[%]d/$dir/g;utf8::downgrade($_);$_; } split(/\s+/,$config->get('dir_opener'));
    push @c,$dir if(!$seq);

    commande_parallele(@c);
}

sub open_exports_dir {
    view_dir($shortcuts->absolu('%PROJET/exports/'));
}

sub open_templates_dir {
    view_dir($config->get('rep_modeles'));
}

sub regarde_regroupements {
    view_dir($config->get_absolute('cr')."/corrections/pdf");
}

sub plugins_browse {
  view_dir($config->subdir("plugins"));
}

###

sub activate_apropos {
  my $gap=read_glade('apropos');
  $w{'apropos'}->run();
  $w{'apropos'}->destroy();
}

sub activate_doc {
    my ($w,$lang)=@_;

    if(!$lang) {
      my $n=$w->Gtk3::Buildable::get_name;
      if($n =~ /_([a-z]{2})$/) {
        $lang=$1;
      }
    }

    my $url='file://'.$hdocdir;
    $url.="auto-multiple-choice.$lang/index.html"
	if($lang && -f $hdocdir."auto-multiple-choice.$lang/index.html");

    my $seq=0;
    my @c=map { $seq+=s/[%]u/$url/g;$_; } split(/\s+/,$config->get('html_browser'));
    push @c,$url if(!$seq);
    @c=map { encode($encodage_systeme,$_); } @c;

    commande_parallele(@c);
}

###

###

sub change_methode_impression {
    if($w{'pref_x_print_command_pdf'}) {
	my $m='';
	my ($ok,$iter)=$w{'pref_c_methode_impression'}->get_active_iter;
        if($ok) {
	    $m=$w{'pref_c_methode_impression'}->get_model->get($iter,COMBO_ID);
	}
	$w{'pref_x_print_command_pdf'}->set_sensitive($m eq 'commande');
    }
}

sub edit_preferences {
    for my $k (@widgets_disabled_when_preferences_opened) {
      $w{$k}->set_sensitive(0);
    }

    my $gap=read_glade('edit_preferences',
		       qw/pref_projet_tous pref_projet_annonce pref_x_print_command_pdf pref_c_methode_impression email_group_sendmail email_group_SMTP/);

    $w{'edit_preferences_manager'}=$gap;

    if($config->get('conserve_taille')) {
      AMC::Gui::WindowSize::size_monitor
	  ($w{'edit_preferences'},{config=>$config,
				   key=>'preferences_window_size'});
    }

    # copy tooltip from note_* to defaut_note_*
    my $marking_prefs={min=>'x',max=>'x',null=>'x',grain=>'x',max_plafond=>'v',arrondi=>'c'};
    for my $k (keys %$marking_prefs) {
      $gap->get_object("pref_".$marking_prefs->{$k}."_defaut_note_$k")->set_tooltip_text
        ($gap->get_object("pref_projet_".$marking_prefs->{$k}."_note_$k")->get_tooltip_text);
    }

    # tableau type/couleurs pour correction

    $prefs->widget_store_clear(store=>'prefwindow');

    $prefs->transmet_pref($gap,store=>'prefwindow',prefix=>'pref',root=>'global:');
    $prefs->transmet_pref($gap,store=>'prefwindow_project',prefix=>'pref_projet',
                          root=>'project:') if($projet{'nom'});

    # projet ouvert ?
    if($projet{'nom'}) {
	$w{'pref_projet_annonce'}->set_label('<i>'.sprintf(__"Project \"%s\" preferences",$projet{'nom'}).'</i>.');
    } else {
	$w{'pref_projet_tous'}->set_sensitive(0);
	$w{'pref_projet_annonce'}->set_label('<i>'.__("Project preferences").'</i>');
    }

    # anavailable options, managed by the filter:
    if($config->get('filter')) {
      for my $k (("AMC::Filter::register::".$config->get('filter'))
		 ->forced_options()) {
      TYPES: for my $t (qw/c cb ce col f s t v x/) {
	  if(my $w=$gap->get_object("pref_projet_".$t."_".$k)) {
	    $w->set_sensitive(0);
	    last TYPES;
	  }
	}
      }
    }

    change_methode_impression();

    my $resp=$w{'edit_preferences'}->run();

    if($resp) {
      accepte_preferences();
    } else {
      closes_preferences();
    }
}

sub closes_preferences {
  $w{'edit_preferences'}->destroy();
  for my $k (@widgets_disabled_when_preferences_opened) {
      $w{$k}->set_sensitive(1);
    }
}

sub accepte_preferences {
  $prefs->reprend_pref(store=>'prefwindow',prefix=>'pref');
  $prefs->reprend_pref(store=>'prefwindow_project',prefix=>'pref_projet') if($projet{'nom'});

  my %pm;
  my %gm;
  my @dgm;
  my %labels;

  if ($projet{'nom'}) {
    %pm=map { $_=>1 } ($config->changed_keys('project'));
    %gm=map { $_=>1 } ($config->changed_keys('global'));
    @dgm=grep { /^defaut_/ } (keys %gm);

    for my $k (@dgm) {
      my $l=$w{'edit_preferences_manager'}->get_object('label_'.$k);
      $labels{$k}=$l->get_text() if($l);
      my $kp=$k;
      $kp =~ s/^defaut_//;
      $l=$w{'edit_preferences_manager'}->get_object('label_'.$kp);
      $labels{$kp}=$l->get_text() if($l);
    }
  }

  closes_preferences();

  if ($projet{'nom'}) {

    # Check if annotations are still valid (same options)

    my $changed=0;
    for(qw/annote_chsign symboles_trait
	   embedded_format embedded_max_size embedded_jpeg_quality
	   symboles_indicatives annote_font_name annote_ecart/) {
      $changed=1 if($gm{$_});
    }
    for my $tag (qw/0_0 0_1 1_0 1_1/) {
      $changed=1 if($gm{"symbole_".$tag."_type"} || $gm{"symbole_".$tag."_color"});
    }
    for(qw/annote_position verdict verdict_q annote_rtl/) {
      $changed=1 if($pm{$_});
    }

    if($changed) {
      annotate_source_change($projet{'_capture'},1);
    }

    # Look at modified default values...

    debug "Labels: ".join(',',keys %labels);

    for my $k (@dgm) {
      my $kp=$k;
      $kp =~ s/^defaut_//;

      debug "Test G:$k / P:$kp";
      if ((!$pm{$kp}) && ($config->get($kp) ne $config->get($k))) {
	# project option has NOT been modified, and the new
	# value of general default option is different from
	# project option. Ask the user for modifying also the
	# project option value
	$label_projet=$labels{$kp};
	$label_general=$labels{$k};

	debug "Ask user $label_general | $label_projet";

	if ($label_projet && $label_general) {
	  my $dialog = Gtk3::MessageDialog
	    ->new($w{'main_window'},
                  'destroy-with-parent',
                  'question','yes-no','');
          $dialog->set_markup(
			      sprintf(__("You modified \"<b>%s</b>\" value, which is the default value used when creating new projects. Do you want to change also \"<b>%s</b>\" for the opened <i>%s</i> project?"),
				      $label_general,$label_projet,$projet{'nom'}));
          $dialog->get_widget_for_response('yes')->get_style_context()->add_class("suggested-action");
	  my $reponse=$dialog->run;
	  $dialog->destroy;

	  debug "Reponse: $reponse";

	  if ($reponse eq 'yes') {
	    # change also project option value
	    $config->set($kp,$config->get($k));
	  }

	}
      }
    }
  }

  if ($projet{'nom'}) {
    for my $k (qw/note_null note_min note_max note_grain/) {
      my $v=$config->get($k);
      $v =~ s/\s+//g;
      $config->set($k,$v);
    }
  }

  if($config->key_changed("projects_home")
     && !$projet{'nom'}) {
    set_projects_home($config->get_absolute('projects_home'));
  }

  $config->test_commands();
  test_libnotify();

  if($config->key_changed("seuil") ||
     $config->key_changed("seuil_up")) {
    if ($projet{'_capture'}->n_pages_transaction()>0) {
      detecte_analyse();
    }
  }

  $config->save();
}


sub sauve_pref_generales {
  $config->save();
}

sub annule_preferences {
    debug "Canceling preferences modification";
    closes_preferences();
}

sub file_maj {
    my (@f)=@_;
    my $present=1;
    my $oldest=0;
    for my $file (@f) {
	if($file && -f $file) {
	    if(-r $file) {
		my @s=stat($file);
		$oldest=$s[9] if($s[9]>$oldest);
	    } else {
		return('UNREADABLE');
	    }
	} else {
	    return('NOTFOUND');
	}
    }
    return(format_date($oldest));
}

sub open_documents_popover {
  if($w{toggle_documents}->get_active()) {
    $w{documents_popover}->show_all();
  } else {
    $w{documents_popover}->hide();
  }
  return(1);
}

sub popover_hidden {
  $w{toggle_documents}->set_active(0) if($w{toggle_documents});
  return(1);
}

sub check_document {
  my ($filename,$k)=@_;
  debug("Document $filename ".(-f $filename ? "exists" : "NOT FOUND"));
  $w{'but_'.$k}->set_sensitive(-f $filename);
}

sub set_state {
  my ($k,$type,$message)=@_;
  if(defined($type) && $w{'state_'.$k}) {
    if($type eq 'none') {
      $w{'state_'.$k}->hide();
    } else {
      $w{'state_'.$k}->show();
      $w{'state_'.$k}->set_message_type($type);
    }
  }
  $w{'state_'.$k.'_label'}->set_text($message)
    if(defined($message) && $w{'state_'.$k.'_label'});
}

sub detecte_documents {
    check_document($config->get_absolute('doc_question'),'question');
    check_document($config->get_absolute('doc_solution'),'solution');
    check_document($config->get_absolute('doc_indiv_solution'),'indiv_solution');
    check_document($config->get_absolute('doc_catalog'),'catalog');
    my $s=file_maj(map { $config->get_absolute('doc_'.$_)
                       } (qw/question setting/));
    my $ok;
    if($s eq 'UNREADABLE') {
	$s=__("Working documents are not readable");
	$ok='error';
    } elsif($s eq 'NOTFOUND') {
	$s=__("No working documents");
	$ok='warning';
    } else {
        $s=__("Working documents last update:")." ".$s;
        $ok='info';
    }
    if($ok eq 'info') {
      $w{toggle_documents}->show();
    } else {
      $w{toggle_documents}->set_active(0);
      $w{toggle_documents}->hide();
    }
    set_state('docs',$ok,$s);
}

sub show_document {
    my ($sel)=@_;
    my $f=$config->get_absolute('doc_'.$sel);
    debug "Looking at $f...";
    commande_parallele($config->get('pdf_viewer'),$f);
}

sub show_question {
    show_document('question');
}

sub show_solution {
    show_document('solution');
}

sub show_indiv_solution {
    show_document('indiv_solution');
}

sub show_catalog {
    show_document('catalog');
}

sub update_catalog { update_document("C",partial=>1); }

sub update_solution { update_document("S",partial=>1); }

sub update_indiv_solution { update_document("k",partial=>1); }

sub detecte_mep {
    $projet{'_layout'}->begin_read_transaction('LAYO');
    $projet{'_mep_defauts'}={$projet{'_layout'}->defects()};
    my $c=$projet{'_layout'}->pages_count;
    $projet{'_layout'}->end_transaction('LAYO');
    my @def=(keys %{$projet{'_mep_defauts'}});
    if(@def) {
	$w{'button_mep_warnings'}->show();
    } else {
	$w{'button_mep_warnings'}->hide();
    }
    $w{'onglet_saisie'}->set_sensitive($c>0);
    my $s;
    my $ok;
    if($c<1) {
	$s=__("No layout");
	$ok='error';
    } else {
	$s=sprintf(__("Processed %d pages"),
		   $c);
	if(@def) {
	    $s.=", ".__("but some defects were detected.");
	    $ok=defects_class(@def);
	} else {
            $s.='.';
            $ok='info';
	}
    }
    set_state('layout',$ok,$s);
}

my %defect_text=
  (
   'NO_NAME'=>__("The \\namefield command is not used. Writing subjects without name field is not recommended"),
   'SEVERAL_NAMES'=>__("The \\namefield command is used several times for the same subject. This should not be the case, as each student should write his name only once"),
   'NO_BOX'=>__("No box to be ticked"),
   'DIFFERENT_POSITIONS'=>__("The corner marks and binary boxes are not at the same location on all pages"),
   'OUT_OF_PAGE'=>__("Some material has been placed out of the page. This is often a result of a multiple columns question group starting too close from the page bottom. In such a case, use \"needspace\"."),
  );

sub defects_class {
  my (@defects)=@_;
  my $w=0;
  my $e=0;
  for my $k (@defects) {
    if($k eq 'NO_NAME') {
      $w++;
    } else {
      $e++;
    }
  }
  return($e ? 'error' : $w ? 'question' : 'info');
}

sub mep_warnings {
    my $m='';
    my @def=(keys %{$projet{'_mep_defauts'}});
    if(@def) {
      $m=__("Some potential defects were detected for this subject. Correct them in the source and update the working documents.");
      for my $k (keys %defect_text) {
	my $dd=$projet{'_mep_defauts'}->{$k};
	if($dd) {
	  if($k eq 'DIFFERENT_POSITIONS') {
	    $m.="\n<b>".$defect_text{$k}."</b> ".
	      sprintf(__('(See for example pages %s and %s)'),
		      pageids_string($dd->{'student_a'},$dd->{'page_a'}),
		      pageids_string($dd->{'student_b'},$dd->{'page_b'})).'.';
          } elsif($k eq 'OUT_OF_PAGE') {
            $m.="\n<b>".$defect_text{$k}."</b> ".
	      sprintf(__('(Concerns %1$d pages, see for example page %2$s)'),
                      1+$#{$dd},
		      pageids_string($dd->[0]->{'student'},$dd->[0]->{'page'})).'.';
	  } else {
	    my @e=sort { $a <=> $b } (@{$dd});
	    if(@e) {
	      $m.="\n<b>".$defect_text{$k}."</b> ".
		sprintf(__('(Concerns %1$d exams, see for example sheet %2$d)'),1+$#e,$e[0]).'.';
	    }
	  }
	}
      }
    } else {
	# should not be possible to go there...
	return();
    }
    my $dialog = Gtk3::MessageDialog
	->new($w{'main_window'},
              'destroy-with-parent',
              'warning','ok','');
    $dialog->set_markup($m);
    $dialog->run;
    $dialog->destroy;

}

sub clear_processing {
  my ($steps)=@_;
  my $next='';
  my %s=();
  for my $k (qw/doc mep capture mark assoc/) {
    if($steps =~ /\b$k:/) {
      $next=1;
      $s{$k}=1;
    } elsif($next || $steps =~ /\b$k\b/) {
      $s{$k}=1;
    }
  }

  if($s{'doc'}) {
    for (qw/question solution setting catalog/) {
      my $f=$config->get_absolute('doc_'.$_);
      unlink($f) if(-f $f);
    }
    detecte_documents();
  }

  delete($s{'doc'});
  return() if(!%s);

  # data to remove...

  $projet{'_data'}->begin_transaction('CLPR');

  if($s{'mep'}) {
    $projet{_layout}->clear_all;
  }

  if($s{'capture'}) {
    $projet{_capture}->clear_all;
  }

  if($s{'mark'}) {
    $projet{'_scoring'}->clear_strategy;
    $projet{'_scoring'}->clear_score;
  }

  if($s{'assoc'}) {
    $projet{_association}->clear;
  }

  $projet{'_data'}->end_transaction('CLPR');

  # files to remove...

  if($s{'capture'}) {
    # remove zooms
    remove_tree($shortcuts->absolu('%PROJET/cr/zooms'),
		{'verbose'=>0,'safe'=>1,'keep_root'=>1});
    # remove namefield extractions and page layout image
    my $crdir=$shortcuts->absolu('%PROJET/cr');
    opendir(my $dh,$crdir);
    my @cap_files=grep { /^(name-|page-)/ } readdir($dh);
    closedir($dh);
    for(@cap_files) {
      unlink "$crdir/$_";
    }
  }

  # update gui...

  if($s{'mep'}) {
    detecte_mep();
  }
  if($s{'capture'}) {
    detecte_analyse();
  }
  if($s{'mark'}) {
    noter_resultat();
  }
  if($s{'assoc'}) {
    assoc_state();
  }
}

sub update_analysis_summary {
  my $n=$projet{'_capture'}->n_pages;

  my %r=$projet{'_capture'}->counts;

  $r{'npages'}=$n;

  my $failed_nb=$projet{'_capture'}
    ->sql_single($projet{'_capture'}->statement('failedNb'));

  my $ow=$projet{'_capture'}->n_overwritten ||0;

  $w{'onglet_notation'}->set_sensitive($n>0);

  # resume

  my $tt='';
  my $ok='info';
  if ($r{'incomplete'}) {
    $tt=sprintf(__"Data capture from %d complete papers and %d incomplete papers",$r{'complete'},$r{'incomplete'});
    $ok='error';
    $w{'button_show_missing'}->show();
  } elsif ($r{'complete'}) {
    $tt=sprintf(__("Data capture from %d complete papers"),$r{'complete'});
    $ok='info';
    $w{'button_show_missing'}->hide();
  } else {
    # TRANSLATORS: this text points out that no data capture has been made yet.
    $tt=sprintf(__"No data");
    $ok='error';
    $w{'button_show_missing'}->hide();
  }
  set_state('capture',$ok,$tt);

  if($ow>0) {
    set_state('overwritten','warning',sprintf(__"Overwritten pages: %d",$ow));
    $w{state_overwritten}->show();
  } else {
    $w{state_overwritten}->hide();
  }

  if ($failed_nb<=0) {
    if($r{'complete'}) {
      $tt=__"All scans were properly recognized.";
      $ok='none';
    } else {
      $tt="";
      $ok='none';
    }
  } else {
    $tt=sprintf(__"%d scans were not recognized.",$failed_nb);
    $ok='question';
  }
  set_state('unrecognized',$ok,$tt);

  return(\%r);
}

sub overwritten_clear {
  $projet{'_capture'}->begin_transaction('OWcl');
  $projet{'_capture'}->clear_overwritten();
  update_analysis_summary();
  $projet{'_capture'}->end_transaction('OWcl');
}

sub overwritten_look {
  my $go=read_glade('overwritten',
                    qw/overwritten_dialog overwritten_list/);
  my $olist=Gtk3::ListStore->new ('Glib::String','Glib::String','Glib::String');
  $w{overwritten_list}->set_model($olist);

  $w{overwritten_list}->append_column
    (Gtk3::TreeViewColumn->new_with_attributes
# TRANSLATORS: column title for the list of overwritten pages. This refers to the page from the question
     (__"Page",Gtk3::CellRendererText->new,
      text=> 0));
  $w{overwritten_list}->append_column
    (Gtk3::TreeViewColumn->new_with_attributes
# TRANSLATORS: column title for the list of overwritten pages. This refers to the number of times the page has been overwritten.
     (__"count",Gtk3::CellRendererText->new,
      text=> 1));
  $w{overwritten_list}->append_column
    (Gtk3::TreeViewColumn->new_with_attributes
# TRANSLATORS: column title for the list of overwritten pages. This refers to the date of the last data capture for the page.
     (__"Date",Gtk3::CellRendererText->new,
      text=> 2));
  for my $o (@{$projet{_capture}->overwritten_pages_transaction()}) {
    $olist->set($olist->append,
                0,pageids_string($o->{student},$o->{page},$o->{copy}),
                1,$o->{overwritten},
                2,format_date($o->{timestamp_auto}),
               );
  }

  $w{overwritten_list}->get_selection->set_mode(GTK_SELECTION_NONE);

  $w{overwritten_dialog}->run;
  $w{overwritten_dialog}->destroy;
}

sub detecte_analyse {
    my (%oo)=(@_);
    my $iter;
    my $row;

    new_diagstore();

    $w{'commande'}->show();
    my $av_text=$w{'avancement'}->get_text();
    my $frac;
    my $total;
    my $i;

    $projet{'_capture'}->begin_read_transaction('ADCP');

    my $summary=$projet{'_capture'}
      ->summaries('darkness_threshold'=>$config->get('seuil'),
		  'darkness_threshold_up'=>$config->get('seuil_up'),
		  'sensitivity_threshold'=>$config->get('seuil_sens'),
		  'mse_threshold'=>$config->get('seuil_eqm'));

    $total=$#{$summary}+1;
    $i=0;
    $frac=0;
    if($total>0) {
      $w{'avancement'}->set_text(__"Looking for analysis...");
      Gtk3::main_iteration while ( Gtk3::events_pending );
    }
    for my $p (@$summary) {
      $diag_store->insert_with_values
	($i,
	 DIAG_ID,pageids_string($p->{'student'},$p->{'page'},$p->{'copy'}),
	 DIAG_ID_STUDENT,$p->{'student'},
	 DIAG_ID_PAGE,$p->{'page'},
	 DIAG_ID_COPY,$p->{'copy'},
	 DIAG_ID_BACK,$p->{'color'},
	 DIAG_EQM,$p->{'mse_string'},
	 DIAG_EQM_BACK,$p->{'mse_color'},
	 DIAG_MAJ,format_date($p->{'timestamp'}),
	 DIAG_MAJ_NUM,$p->{'timestamp'},
	 DIAG_DELTA,$p->{'sensitivity_string'},
	 DIAG_DELTA_BACK,$p->{'sensitivity_color'},
         DIAG_SCAN_FILE,path_to_filename($p->{'src'}),
	);
      if($i/$total>=$frac+.05) {
	$frac=$i/$total;
	$w{'avancement'}->set_fraction($frac);
	Gtk3::main_iteration while ( Gtk3::events_pending );
      }
    }

    sort_diagstore();
    show_diagstore();

    $w{'avancement'}->set_text($av_text);
    $w{'avancement'}->set_fraction(0) if(!$oo{'interne'});
    $w{'commande'}->hide() if(!$oo{'interne'});
    Gtk3::main_iteration while ( Gtk3::events_pending );

    my $r=update_analysis_summary();

    $projet{'_capture'}->end_transaction('ADCP');

    # dialogue apprentissage :

    if($oo{'apprend'}) {
	dialogue_apprentissage('SAISIE_AUTO','','',0,
			       __("Automatic data capture now completed.")." "
			       .($r->{'incomplete'}>0 ? sprintf(__("It is not complete (missing pages from %d papers).")." ",$r->{'incomplete'}) : '')
			       .__("You can analyse data capture quality with some indicators values in analysis list:")
			       ."\n"
			       .sprintf(__"- <b>%s</b> represents positioning gap for the four corner marks. Great value means abnormal page distortion.",__"MSE")
			       ."\n"
			       .sprintf(__"- great values of <b>%s</b> are seen when darkness ratio is very close to the threshold for some boxes.",__"sensitivity")
			       ."\n"
			       .sprintf(__"You can also look at the scan adjustment (<i>%s</i>) and ticked and unticked boxes (<i>%s</i>) using right-click on lines from table <i>%s</i>.",__"page adjustment",__"boxes zooms",__"Diagnosis")
			       );
    }

}

sub show_missing_pages {
  $projet{'_capture'}->begin_read_transaction('cSMP');
  my %r=$projet{'_capture'}->counts;
  $projet{'_capture'}->end_transaction('cSMP');

  my $l='';
  my @sc=();
  for my $p (@{$r{'missing'}}) {
    if($sc[0] != $p->{'student'} || $sc[1] != $p->{'copy'}) {
      @sc=($p->{'student'},$p->{'copy'});
      $l.="\n";
    }
    $l.="  ".pageids_string($p->{'student'},
			   $p->{'page'},$p->{'copy'});
  }

  my $dialog = Gtk3::MessageDialog
    ->new
      ($w{'main_window'},
       'destroy-with-parent',
       'info','ok','');
  $dialog->set_markup(
       "<b>".(__"Pages that miss data capture to complete students sheets:")."</b>"
       .$l
      );
  $dialog->run;
  $dialog->destroy;
}

sub update_unrecognized {
  $projet{'_capture'}->begin_read_transaction('UNRC');
  my $failed=$projet{'_capture'}->dbh
    ->selectall_arrayref($projet{'_capture'}->statement('failedList'),
			 {Slice => {}});
  $projet{'_capture'}->end_transaction('UNRC');

  $inconnu_store->clear;
  for my $ff (@$failed) {
    my $iter=$inconnu_store->append;
    my $f=$ff->{'filename'};
    $f =~ s:.*/::;
    my (undef,undef,$scan_n)=splitpath($shortcuts->absolu($ff->{'filename'}));
    my $preproc_file=$shortcuts->absolu('%PROJET/cr/diagnostic')."/".$scan_n.".png";

    $inconnu_store->set($iter,
			INCONNU_SCAN,$f,
			INCONNU_FILE,$ff->{'filename'},
			INCONNU_TIME,format_date($ff->{'timestamp'}),
			INCONNU_TIME_N,$ff->{'timestamp'},
			INCONNU_PREPROC,$preproc_file,
		       );
  }
}

sub open_unrecognized {

  my $dialog=read_glade('unrecognized',
			qw/inconnu_tree scan_area preprocessed_area
			   inconnu_hpaned inconnu_vpaned
			   state_scanrecog state_scanrecog_label
                           unrecog_process_button
                           unrecog_delete_button
                           unrecog_next_button unrecog_previous_button
                           ur_frame_scan/);

  for (qw/scan preprocessed/) {
    AMC::Gui::PageArea::add_feuille($w{$_.'_area'});
  }

  $w{'inconnu_tree'}->set_model($inconnu_store);

  $renderer=Gtk3::CellRendererText->new;
  $column = Gtk3::TreeViewColumn->new_with_attributes ("scan",
						       $renderer,
						       text=> INCONNU_SCAN);
  $w{'inconnu_tree'}->append_column ($column);
  $column->set_sort_column_id(INCONNU_SCAN);

  $renderer=Gtk3::CellRendererText->new;
  $column = Gtk3::TreeViewColumn->new_with_attributes ("date",
						       $renderer,
						       text=> INCONNU_TIME);
  $w{'inconnu_tree'}->append_column ($column);
  $column->set_sort_column_id(INCONNU_TIME_N);

  update_unrecognized();

  $w{'inconnu_tree'}->get_selection->set_mode(GTK_SELECTION_MULTIPLE);
  $w{'inconnu_tree'}->get_selection->signal_connect("changed",\&unrecognized_line);
  $w{'inconnu_tree'}->get_selection->select_iter($inconnu_store->get_iter_first);

  $w{unrecognized}->show();
}

sub unrecognized_actions {
  my ($available)=@_;
  my %actions=(delete=>$available>0,
               process=>$available==1,
               next=>$available>=0,
               previous=>$available>=0,
              );
  for my $k (keys %actions) {
    if($actions{$k} > 0) {
      $w{'unrecog_'.$k.'_button'}->show;
    } else {
      $w{'unrecog_'.$k.'_button'}->hide;
    }
  }
}

sub unrecognized_line {
  if($inconnu_store->get_iter_first) {
    my @sel=$w{'inconnu_tree'}->get_selection->get_selected_rows;
    my $first_selected=$sel[0]->[0];
    my $iter='';
    if(defined($first_selected)) {
      $iter=$inconnu_store->get_iter($first_selected);
    }
    if($iter) {
      $w{'inconnu_tree'}->scroll_to_cell($first_selected,undef,0,0,0);
      my $scan=$shortcuts->absolu($inconnu_store->get($iter,INCONNU_FILE));
      if(-f $scan) {
        $w{'scan_area'}->set_content(image=>$scan);
      } else {
        debug_and_stderr "Scan not found: $scan";
        $w{'scan_area'}->set_content();
      }

      my $preproc=$inconnu_store->get($iter,INCONNU_PREPROC);
      utf8::downgrade($preproc);

      if(-f $preproc) {
        $w{'preprocessed_area'}->set_content(image=>$preproc);
      } else {
        $w{'preprocessed_area'}->set_content();
      }

      if($w{'scan_area'}->get_image) {
        my $scan_n=$scan;
        $scan_n =~ s:^.*/::;
        set_state('scanrecog','question',$scan_n);
      } else {
        set_state('scanrecog','error',
                  sprintf((__"Error loading scan %s"),$scan));
      }
      unrecognized_actions(-f $preproc ? 2 : 1);
    } else {
      $w{'scan_area'}->set_content();
      $w{'preprocessed_area'}->set_content();
      set_state('scanrecog','question',__"No scan selected");
      unrecognized_actions(0);
    }
  } else {
    # empty list
    set_state('scanrecog','info',__"No more unrecognized scans");
    $w{'scan_area'}->set_content();
    $w{'preprocessed_area'}->set_content();
    unrecognized_actions(-1);
  }
}

sub unrecognized_next {
  my ($widget,@sel)=@_;
  @sel=() if(!defined($sel[0]));
  if(!@sel) {
    @sel=$w{'inconnu_tree'}->get_selection->get_selected_rows;
    if($sel[0]) {
      @sel=@{$sel[0]};
    } else {
      @sel=();
    }
  }
  my $iter;
  my $ok=0;
  if(@sel && defined($sel[$#sel])) {
    $iter=$inconnu_store->get_iter($sel[$#sel]);
    $ok=$inconnu_store->iter_next($iter);
  }
  $iter=$inconnu_store->get_iter_first if(!$ok);

  $w{'inconnu_tree'}->get_selection->unselect_all;
  $w{'inconnu_tree'}->get_selection->select_iter($iter) if($iter);
}

sub unrecognized_prev {
  my @sel=$w{'inconnu_tree'}->get_selection->get_selected_rows;
  my $first_selected=$sel[0]->[0];
  my $iter;
  if(defined($first_selected)) {
    my $p=$inconnu_store->get_path($inconnu_store->get_iter($first_selected));
    if($p->prev) {
      $iter=$inconnu_store->get_iter($p);
    } else {
      $iter='';
    }
  }
  $iter=$inconnu_store->get_iter_first if(!$iter);

  $w{'inconnu_tree'}->get_selection->unselect_all;
  $w{'inconnu_tree'}->get_selection->select_iter($iter) if($iter);
}

sub unrecognized_delete {
  my @iters;
  my @sel=($w{'inconnu_tree'}->get_selection->get_selected_rows);
  @sel=@{$sel[0]};
  return if(!@sel);

  $projet{'_capture'}->begin_transaction('rmUN');
  for my $s (@sel) {
    my $iter=$inconnu_store->get_iter($s);
    my $file=$inconnu_store->get($iter,INCONNU_FILE);
    $projet{'_capture'}->statement('deleteFailed')->execute($file);
    unlink $shortcuts->absolu($file);
    push @iters,$iter;
  }
  unrecognized_next('',@sel);
  for(@iters) { $inconnu_store->remove($_); }
  update_analysis_summary();
  $projet{'_capture'}->end_transaction('rmUN');
}

sub analyse_diagnostic {
  my @sel=$w{'inconnu_tree'}->get_selection->get_selected_rows;
  my $first_selected=$sel[0]->[0];
  if(defined($first_selected)) {
    my $iter=$inconnu_store->get_iter($first_selected);
    my $scan=$shortcuts->absolu($inconnu_store->get($iter,INCONNU_FILE));
    my $diagnostic_file=$inconnu_store->get($iter,INCONNU_PREPROC);

    if(!-f $diagnostic_file) {
      analyse_call('f'=>[$scan],
		   'text'=>__("Making diagnostic image..."),
		   'progres'=>'diagnostic',
		   'diagnostic'=>1,
		   'fin'=>sub {
		     unrecognized_line();
		   },
		  );
    }
  }
}

sub set_source_tex {
    my ($importe)=@_;

    importe_source() if($importe);
    valide_source_tex();
}

sub valide_source_tex {
    debug "* valide_source_tex";

    $w{button_edit_src}->set_tooltip_text(glib_filename($config->get_absolute('texsrc')));

    if(!$config->get('filter')) {
      $config->set('filter',
                   best_filter_for_file($config->get_absolute('texsrc')));
    }

    detecte_documents();
}

my $modeles_store;

sub charge_modeles {
    my ($store,$parent,$rep)=@_;

    return if(! -d $rep);

    my @all;
    my @ms;
    my @subdirs;

    if(opendir(DIR,$rep)) {
	@all=readdir(DIR);
	@ms=grep { /\.tgz$/ && -f $rep."/$_" } @all;
	@subdirs=grep { -d $rep."/$_" && ! /^\./ } @all;
	closedir DIR;
    } else {
	debug("MODELS : Can't open directory $rep : $!");
    }

    for my $sd (sort { $a cmp $b } @subdirs) {
	my $nom=$sd;
	my $desc_text='';

	my $child = $store->append($parent);
	if(-f $rep."/$sd/directory.xml") {
	    my $d=XMLin($rep."/$sd/directory.xml");
	    $nom=$d->{'title'} if($d->{'title'});
	    $desc_text=$d->{'text'} if($d->{'text'});
	}
	$store->set($child,MODEL_NOM,$nom,
		    MODEL_PATH,'',
		    MODEL_DESC,$desc_text);
	charge_modeles($store,$child,$rep."/$sd");
    }

    for my $m (sort { $a cmp $b } @ms) {
	my $nom=$m;
	$nom =~ s/\.tgz$//i;
	my $desc_text=__"(no description)";
	my $tar=Archive::Tar->new();
        if($tar->read($rep."/$m")) {
          my @desc=grep { /description.xml$/ } ($tar->list_files());
          if($desc[0]) {
	    my $d=XMLin($tar->get_content($desc[0]),'SuppressEmpty'=>'');
	    $nom=$d->{'title'} if($d->{'title'});
	    $desc_text=$d->{'text'} if($d->{'text'});
          }
          debug "Adding model $m";
          debug "NAME=$nom DESC=$desc_text";
          $store->set($store->append($parent),
                      MODEL_NOM,$nom,
                      MODEL_PATH,$rep."/$m",
                      MODEL_DESC,$desc_text);
        } else {
          debug_and_stderr "WARNING: Could not read archive file \"$rep/$m\" ...";
        }
    }
}

sub modele_dispo {
    my $iter=$w{'modeles_liste'}->get_selection()->get_selected();
    if($iter) {
	$w{'model_choice_button'}->set_sensitive($modeles_store->get($iter,MODEL_PATH) ? 1 : 0);
    } else {
	debug "No iter for models selection";
    }
}

sub path_from_tree {
    my ($store,$view,$f)=@_;
    my $i=undef;

    return(undef) if(!$f);

    my $d='';

    for my $pp (split(m:/:,$f)) {
	my $ipar=$i;
	$d.='/' if($d);
	$d.=$pp;
	$i=model_id_to_iter($store,TEMPLATE_FILES_PATH,$d);
	if(!$i) {
	    $i=$store->append($ipar);
	    $store->set($i,TEMPLATE_FILES_PATH,$d,
			TEMPLATE_FILES_FILE,$pp);
	}
    }

    $view->expand_to_path($store->get_path($i));
    return($i);
}

sub template_add_file {
    my ($store,$view,$f)=@_;

    # removes local part

    my $p_dir=$shortcuts->absolu('%PROJET/');
    if($f =~ s:^\Q$p_dir\E::) {
	my $i=path_from_tree($store,$view,$f);
	return($i);
    } else {
	debug "Trying to add non local file: $f (local dir is $p_dir)";
	return(undef);
    }
}

sub make_template {

    if(!$projet{'nom'}) {
	debug "Make template: no opened project";
	return();
    }

    my $gt=read_glade('make_template',
		      qw/template_files_tree template_name template_file_name template_description template_file_name_warning mt_ok
			template_description_scroll template_files_scroll/);

    $template_files_store->clear;

    $w{'template_files_tree'}->set_model($template_files_store);
	my $renderer=Gtk3::CellRendererText->new;
# TRANSLATORS: This is a column title for the list of files to be included in a template being created.
	my $column = Gtk3::TreeViewColumn->new_with_attributes(__"file",
							       $renderer,
							       text=> TEMPLATE_FILES_FILE );
    $w{'template_files_tree'}->append_column ($column);
    $w{'template_files_tree'}->get_selection->set_mode("multiple");

    # Detects files to include

    template_add_file($template_files_store,$w{'template_files_tree'},
		      $config->get_absolute('texsrc'));
    template_add_file($template_files_store,$w{'template_files_tree'},
		      fich_options($projet{'nom'}));

    for (qw/description files/) {
      $w{'template_'.$_.'_scroll'}->set_policy('automatic','automatic');
    }

    # Waits for action

    $resp=$w{'make_template'}->run();

    if($resp eq "1") {

	projet_check_and_save();

	# Creates template

	my $tfile=$config->get('rep_modeles').'/'.$w{'template_file_name'}->get_text().".tgz";
	my $tar=Archive::Tar->new();
	$template_files_store->foreach(\&add_to_archive,[$tar]);

	# Description

	my $buf=$w{'template_description'}->get_buffer;

	my $desc='';
	my $writer=new XML::Writer(OUTPUT=>\$desc,ENCODING=>'utf-8');
	$writer->xmlDecl("UTF-8");
	$writer->startTag('description');
	$writer->dataElement('title',$w{'template_name'}->get_text());
	$writer->dataElement('text',$buf->get_text($buf->get_start_iter,$buf->get_end_iter,1));
	$writer->endTag('description');
	$writer->end();

	$tar->add_data('description.xml',encode_utf8($desc));

	$tar->write($tfile,COMPRESS_GZIP);
    }

    $w{'make_template'}->destroy;
}

sub add_to_archive {
    my ($store,$path,$iter,$data)=@_;
    my ($tar)=@$data;

    my $f=$store->get($iter,TEMPLATE_FILES_PATH);
    my $af=$shortcuts->absolu("%PROJET/$f");

    return(0) if($f eq 'description.xml');

    if(-f $af) {
	debug "Adding to template archive: $f\n";
	my $tf=Archive::Tar::File->new( file => $af);
	$tf->rename($f);
	$tar->add_files($tf);
    }

    return(0);
}

sub template_filename_verif {
    restricted_check($w{'template_file_name'},
		     $w{'template_file_name_warning'},"a-zA-Z0-9_+-");
    my $t=$w{'template_file_name'}->get_text();
    my $tfile=$config->get('rep_modeles').'/'.$t.".tgz";
    $w{'mt_ok'}->set_sensitive($t && !-e $tfile);
}

sub make_template_add {
    my $fs=Gtk3::FileSelection->new(__"Add files to template");
    $fs->set_filename($shortcuts->absolu('%PROJET/'));
    $fs->set_select_multiple(1);
    $fs->hide_fileop_buttons;

    my $err=0;
    my $resp=$fs->run();
    if($resp eq 'ok') {
	for my $f ($fs->get_selections()) {
	    $err++
		if(!defined(template_add_file($template_files_store,$w{'template_files_tree'},$f)));
	}
    }
    $fs->destroy();

    if($err) {
	my $dialog=Gtk3::MessageDialog
	    ->new($w{'make_template'},
                  'destroy-with-parent',
                  'error','ok','');
        $dialog->set_markup(
			      __("When making a template, you can only add files that are within the project directory."));
	$dialog->run();
	$dialog->destroy();
    }
}

sub make_template_del {
    my @i=();
    my @selected=$w{'template_files_tree'}->get_selection->get_selected_rows;
    for my $path (@{$selected[0]}) {
	push @i,$template_files_store->get_iter($path) if($path);
    }
    for(@i) {
	$template_files_store->remove($_);
    }
}

sub n_fich {
    my ($dir)=@_;

    if(opendir(NFICH,$dir)) {
	my @f=grep { ! /^(\.|__MACOSX)/ } readdir(NFICH);
	closedir(NFICH);

	return(1+$#f,"$dir/$f[0]");
    } else {
	debug("N_FICH : Can't open directory $dir : $!");
 	return(0);
    }
}

sub unzip_to_temp {
  my ($file)=@_;

  my $temp_dir = tempdir( DIR=>tmpdir(),CLEANUP => 1 );
  my $error=0;

  my @cmd;

  if($file =~ /\.zip$/i) {
    @cmd=("unzip","-d",$temp_dir,$file);
  } else {
    @cmd=("tar","-x","-v","-z","-f",$file,"-C",$temp_dir);
  }

  debug "Extracting archive files\nFROM: $file\nWITH: ".join(' ',@cmd);
  if(open(UNZIP,"-|",@cmd) ) {
    while(<UNZIP>) {
      debug $_;
    }
    close(UNZIP);
  } else {
    $error=$!;
  }

  return($temp_dir,$error);
}



sub source_latex_choisir {

    my %oo=@_;
    my $texsrc='';

    if(!$oo{'nom'}) {
	debug "ERR: Empty name for source_latex_choisir";
	return(0,'');
    }

    if(-e $config->get('rep_projets')."/".$oo{'nom'}) {
	debug "ERR: existing project directory $oo{'nom'} for source_latex_choisir";
	return(0,'');
    }

    my %bouton=();

    if($oo{'type'}) {
	$bouton{$oo{'type'}}=1;
    } else {

	# fenetre de choix du source latex

	my $gap=read_glade('source_latex_dialog');

	my $dialog=$gap->get_object('source_latex_dialog');

	my $reponse=$dialog->run();

	for(qw/new choix vide zip/) {
	    $bouton{$_}=$gap->get_object('sl_type_'.$_)->get_active();
	    debug "Bouton $_" if($bouton{$_});
	}

	$dialog->destroy();

	debug "RESPONSE=$reponse";

	return(0,'') if($reponse!=10);
    }

    # actions apres avoir choisi le type de source latex a utiliser

    if($bouton{'new'}) {

	# choix d'un modele

	$gap=read_glade('source_latex_modele',
			qw/modeles_liste modeles_description model_choice_button mlist_separation/);
        $reponse=$w{'source_latex_modele'}->show();

	$modeles_store = Gtk3::TreeStore->new('Glib::String',
					      'Glib::String',
					      'Glib::String');

	charge_modeles($modeles_store,undef,$config->get('rep_modeles'))
          if($config->get('rep_modeles'));

	charge_modeles($modeles_store,undef,amc_specdir('models'));

	$w{'modeles_liste'}->set_model($modeles_store);
	my $renderer=Gtk3::CellRendererText->new;
# TRANSLATORS: This is a column name for the list of available templates, when creating a new project based on a template.
	my $column = Gtk3::TreeViewColumn->new_with_attributes(__"template",
							       $renderer,
							       text=> MODEL_NOM );
	$w{'modeles_liste'}->append_column ($column);
	$w{'modeles_liste'}->get_selection->signal_connect("changed",\&source_latex_mmaj);

	$w{'mlist_separation'}->set_position(.5*$w{'mlist_separation'}->get_property('max-position'));

	$reponse=$w{'source_latex_modele'}->run();

	debug "Dialog modele : $reponse";

	# le modele est choisi : l'installer

	my $mod;

	if($reponse) {
	    my $iter=$w{'modeles_liste'}->get_selection()->get_selected();
	    $mod=$modeles_store->get($iter,MODEL_PATH) if($iter);
	}

	$w{'source_latex_modele'}->destroy();

	return(0,'') if($reponse!=10);

	if($mod) {
	    debug "Installing model $mod";
	    return(source_latex_choisir('type'=>'zip','fich'=>$mod,
					'decode'=>1,'nom'=>$oo{'nom'}));
	} else {
	    debug "No model";
	    return(0,'');
	}

    } elsif($bouton{'choix'}) {

	# choisir un fichier deja present

	$gap=read_glade('source_latex_choix');

	$w{'source_latex_choix'}->set_current_folder($home_dir);

	# default filter: all possible source files

	my $filtre_all=Gtk3::FileFilter->new();
	$filtre_all->set_name(__"All source files");
	for my $m (@filter_modules) {
	  for my $p ("AMC::Filter::register::$m"->file_patterns) {
	    $filtre_all->add_pattern($p);
	  }
	}
	$w{'source_latex_choix'}->add_filter($filtre_all);

	# filters for each filter module

	for my $m (@filter_modules) {
	  my $f=Gtk3::FileFilter->new();
# TRANSLATORS: This is the label of a choice in a menu to select only files that corresponds to a particular format (which can be LaTeX or Plain for example). %s will be replaced by the name of the format.
	  my @pat=();
	  for my $p ("AMC::Filter::register::$m"->file_patterns) {
	    push @pat,$p;
	    $f->add_pattern($p);
	  }
	  $f->set_name(sprintf(__("%s files"),
			       "AMC::Filter::register::$m"->name())
		       .' ('.join(', ',@pat).')');
	  $w{'source_latex_choix'}->add_filter($f);
	}

	#

	$reponse=$w{'source_latex_choix'}->run();

	my $f=$w{'source_latex_choix'}->get_filename();

	$w{'source_latex_choix'}->destroy();

	return(0,'') if($reponse!=10);

	$texsrc=$shortcuts->relatif($f,$oo{'nom'});
	debug "Source LaTeX $f";

    } elsif($bouton{'zip'}) {

	my $fich;

	if($oo{'fich'}) {
	    $fich=$oo{'fich'};
	} else {

	    # choisir un fichier ZIP

	    $gap=read_glade('source_latex_choix_zip');

	    $w{'source_latex_choix_zip'}->set_current_folder($home_dir);

	    my $filtre_zip=Gtk3::FileFilter->new();
	    $filtre_zip->set_name(__"Archive (zip, tgz)");
	    $filtre_zip->add_pattern("*.zip");
	    $filtre_zip->add_pattern("*.tar.gz");
	    $filtre_zip->add_pattern("*.tgz");
	    $filtre_zip->add_pattern("*.TGZ");
	    $filtre_zip->add_pattern("*.ZIP");
	    $w{'source_latex_choix_zip'}->add_filter($filtre_zip);

	    $reponse=$w{'source_latex_choix_zip'}->run();

	    $fich=$w{'source_latex_choix_zip'}->get_filename();

	    $w{'source_latex_choix_zip'}->destroy();

	    return(0,'') if($reponse!=10);
	}

	# cree un repertoire temporaire pour dezipper

	my ($temp_dir,$rv)=unzip_to_temp($fich);

	my ($n,$suivant)=n_fich($temp_dir);

	if($rv || $n==0) {
	    my $dialog = Gtk3::MessageDialog
		->new($w{'main_window'},
                      'destroy-with-parent',
                      'error','ok','');
            $dialog->set_markup(
				  sprintf(__"Nothing extracted from archive %s. Check it.",$fich));
	    $dialog->run;
	    $dialog->destroy;
	    return(0,'');
	} else {
	    # unzip OK
	    # vire les repertoires intermediaires :

	    while($n==1 && -d $suivant) {
		debug "Changing root directory : $suivant";
		$temp_dir=$suivant;
		($n,$suivant)=n_fich($temp_dir);
	    }

	    # bouge les fichiers la ou il faut

	    my $hd=$config->get('rep_projets')."/".$oo{'nom'};

	    mkdir($hd) if(! -e $hd);

	    my @archive_files;

	    if(opendir(MVR,$temp_dir)) {
		@archive_files=grep { ! /^\./ } readdir(MVR);
		closedir(MVR);
	    } else {
		debug("ARCHIVE : Can't open $temp_dir : $!");
	    }

	    my $latex;

	    for my $ff (@archive_files) {
		debug "Moving to project: $ff";
		if($ff =~ /\.tex$/i) {
		    $latex=$ff;
		    if($oo{'decode'}) {
			debug "Decoding $ff...";
			move("$temp_dir/$ff","$temp_dir/$ff.0enc");
			copy_latex("$temp_dir/$ff.0enc","$temp_dir/$ff");
		    }
		}
		if(system("mv","$temp_dir/$ff","$hd/$ff") != 0) {
		    debug "ERR: Move failed: $temp_dir/$ff --> $hd/$ff -- $!";
		    debug "(already exists)" if(-e "$hd/$ff");
		}
	    }

	    if($latex) {
		$texsrc="%PROJET/$latex";
		debug "LaTeX found : $latex";
	    }

	    return(2,$texsrc);
	}

    } elsif($bouton{'vide'}) {

      my $hd=$config->get('rep_projets')."/".$oo{'nom'};

      mkdir($hd) if(! -e $hd);

      $texsrc='source.tex';
      my $sl="$hd/$texsrc";

    } else {
      return(0,'');
    }

    return(1,$texsrc);

}

sub source_latex_mmaj {
    my $iter=$w{'modeles_liste'}->get_selection()->get_selected();
    my $desc='';

    $desc=$modeles_store->get($iter,MODEL_DESC) if($iter);
    $w{'modeles_description'}->get_buffer->set_text($desc);
}


# copie en changeant eventuellement d'encodage
sub copy_latex {
    my ($src,$dest)=@_;
    # 1) reperage du inputenc dans le source
    my $i='';
    open(SRC,$src);
  LIG: while(<SRC>) {
      s/%.*//;
      if(/\\usepackage\[([^\]]*)\]\{inputenc\}/) {
	  $i=$1;
	  last LIG;
      }
  }
    close(SRC);

    my $ie=get_enc($i);
    my $id=get_enc($config->get('encodage_latex'));
    if($ie && $id && $ie->{'iso'} ne $id->{'iso'}) {
	debug "Reencoding $ie->{'iso'} => $id->{'iso'}";
	open(SRC,"<:encoding($ie->{'iso'})",$src) or return('');
	open(DEST,">:encoding($id->{'iso'})",$dest) or close(SRC),return('');
	while(<SRC>) {
	    chomp;
	    s/\\usepackage\[([^\]]*)\]\{inputenc\}/\\usepackage[$id->{'inputenc'}]{inputenc}/;
	    print DEST "$_\n";
	}
	close(DEST);
	close(SRC);
	return(1);
    } else {
	return(copy($src,$dest));
    }
}

sub importe_source {
  my $file=$config->get('texsrc');
  utf8::downgrade($file);
    my ($fxa,$fxb,$fb) = splitpath($file);
    my $dest=$shortcuts->absolu($fb);

    # fichier deja dans le repertoire projet...
    return() if(is_local($file,1));

    if(-f $dest) {
	my $dialog = Gtk3::MessageDialog
	    ->new($w{'main_window'},
		  'destroy-with-parent',
		  'error','yes-no',
		  __("File %s already exists in project directory: do you want to replace it?")." "
		  .__("Click yes to replace it and loose pre-existing contents, or No to cancel source file import."),$fb);
	my $reponse=$dialog->run;
	$dialog->destroy;

	if($reponse eq 'no') {
	    return(0);
	}
    }

    if(copy_latex($config->get_absolute('texsrc'),$dest)) {
	$config->set('project:texsrc',$shortcuts->relatif($dest));
	set_source_tex();
	my $dialog = Gtk3::MessageDialog
	    ->new($w{'main_window'},
		  'destroy-with-parent',
		  'info','ok',
		  __("The source file has been copied to project directory.")." ".sprintf(__"You can now edit it with button \"%s\" or with any editor.",__"Edit source file"));
	$dialog->run;
	$dialog->destroy;
    } else {
	my $dialog = Gtk3::MessageDialog
	    ->new($w{'main_window'},
		  'destroy-with-parent',
		  'error','ok',
		  __"Error copying source file: %s",$!);
	$dialog->run;
	$dialog->destroy;
    }
}

sub edit_src {
    my $f=$config->get_absolute('texsrc');

    # create new one if necessary

    if(!-f $f) {
      debug "Creating new empty source file...";
      ("AMC::Filter::register::".$config->get('filter'))
	->default_content($f);
    }

    #

    debug "Editing $f...";
    my $editor=$config->get('txt_editor');
    if($config->get('filter')) {
      my $type=("AMC::Filter::register::".$config->get('filter'))
	->filetype();
      $editor=$config->get($type.'_editor')
        if($config->get($type.'_editor'));
    }
    commande_parallele($editor,$f);
}

sub valide_projet {
    set_source_tex();

    $projet{'_data'}=AMC::Data->new($config->get_absolute('data'),
				    'progress'=>\%w);
    for (qw/layout capture scoring association report/) {
      $projet{'_'.$_}=$projet{'_data'}->module($_);
    }

    $projet{_students_list}=AMC::NamesFile::new();

    detecte_mep();
    detecte_analyse('premier'=>1);

    debug "Correction options : MB".$config->get('maj_bareme');
    $w{'maj_bareme'}->set_active($config->get('maj_bareme'));

    $prefs->transmet_pref($gui,prefix=>'notation',
                          root=>'project:');

    $w{'header_bar'}->set_title(glib_project_name.' - '.
                                'Auto Multiple Choice');

    noter_resultat();

    valide_liste('noinfo'=>1,'nomodif'=>1);

    # options specific to some export module:
    $prefs->transmet_pref('',prefix=>'export',root=>'project:');
    # standard export options:
    $prefs->transmet_pref($gui,prefix=>'export',root=>'project:');

    $prefs->transmet_pref($gui,prefix=>'pref_prep',root=>'project:');

    for my $k (@widgets_only_when_opened) {
      $w{$k}->set_sensitive(1);
    }
}

sub cursor_wait {
  $w{cursor_watch}=Gtk3::Gdk::Cursor->new('GDK_WATCH')
    if(!$w{cursor_watch});
  $w{main_window}->get_window()->set_cursor($w{cursor_watch})
    if($w{main_window});
  Gtk3::main_iteration while ( Gtk3::events_pending );
}

sub cursor_standard {
  $w{main_window}->get_window()->set_cursor(undef)
    if($w{main_window});
  Gtk3::main_iteration while ( Gtk3::events_pending );
}

sub projet_ouvre {
    my ($proj,$deja)=(@_);

    my $new_source=0;

    # ouverture du projet $proj. Si $deja==1, alors il faut le creer

    if($proj) {
	my ($ok,$texsrc);

	quitte_projet();

	# choix fichier latex si nouveau projet...
	if($deja) {
	    ($ok,$texsrc)=source_latex_choisir('nom'=>$proj);
	    if(!$ok) {
	      cursor_standard;
	      return(0);
	    }
	    if($ok==1) {
		$new_source=1;
	    } elsif($ok==2) {
		$deja='';
	    }
	}

	cursor_wait;

	# creates project directory structure

	for my $sous ('',qw:cr cr/corrections cr/corrections/jpg cr/corrections/pdf cr/zooms cr/diagnostic data scans exports:) {
          my $rep=$config->get('rep_projets')."/$proj/$sous";
          utf8::downgrade($rep);
          if(! -x $rep) {
            debug "Creating directory $rep...";
            mkdir($rep);
          }
	}

	$projet{'nom'}=$proj;
	$shortcuts->set(project_name=>$proj);

        $config->open_project($proj);
        $config->set('project:texsrc',$texsrc)
          if($texsrc && !$config->get('project:texsrc'));

	$projet{'nom'}=$proj;

	$w{'onglets_projet'}->set_sensitive(1);

	valide_projet();

	set_source_tex(1) if($new_source);

	cursor_standard;

	return(1);
    }
  }

sub gui_no_project {
  for my $k (@widgets_only_when_opened) {
    $w{$k}->set_sensitive(0);
  }
}

sub quitte_projet {
  if ($projet{'nom'}) {

    maj_export();
    valide_options_notation();

    $config->close_project();

    %projet=();

    gui_no_project();
  }

  return(1);
}

sub quitter {
    quitte_projet() or return(1);

    if($config->get('conserve_taille')) {
      my ($x,$y)=$w{'main_window'}->get_size();
      $config->set("global:taille_x_main",$x);
      $config->set("global:taille_y_main",$y);
    }

    $config->save();

    Gtk3->main_quit;
}

sub bug_report {
    my $dialog = Gtk3::MessageDialog
	->new($w{'main_window'},
              'destroy-with-parent',
              'info','ok','');
    $dialog->set_markup(
			  __("In order to send a useful bug report, please attach the following documents:")."\n"
			  ."- ".__("an archive (in some compressed format, like ZIP, 7Z, TGZ...) containing the <b>project directory</b>, <b>scan files</b> and <b>configuration directory</b> (.AMC.d in home directory), so as to reproduce and analyse this problem.")."\n"
			  ."- ".__("the <b>log file</b> produced when the debugging mode (in Help menu) is checked. Please try to reproduce the bug with this mode activated.")."\n\n"
			  .sprintf(__("Bug reports can be filled at %s or sent to the address below."),
				   "<i>".__("AMC community site")."</i>",
				   )
	);
    my $ma=$dialog->get('message-area');
    my $web=Gtk3::LinkButton->new_with_label("http://project.auto-multiple-choice.net/projects/auto-multiple-choice/issues",__("AMC community site"));
    $ma->add($web);
    my $mail=Gtk3::LinkButton->new_with_label('mailto:paamc@passoire.fr',
					      'paamc@passoire.fr');
    $ma->add($mail);
    $ma->show_all();

    $dialog->run;
    $dialog->destroy;
}

#######################################

sub pref_change_delivery {
  $config->set_local_keys('email_transport');
  $prefs->reprend_pref(store=>'prefwindow',prefix=>'pref',container=>'local');
  my $transport=$config->get('local:email_transport');
  if($transport) {
    for my $k (qw/sendmail SMTP/) {
      $w{'email_group_'.$k}->set_sensitive($k eq $transport);
    }
  } else {
    debug "WARNING: could not retrieve email_transport!";
  }
}

my $email_sl;
my $email_key;
my $email_r;

sub project_email_name {
  my ($markup)=@_;
  my $pn=($config->get('nom_examen')
	  || $config->get('code_examen')
	  || $projet{'nom'});
  if($markup) {
    return($pn eq $projet{'nom'} ? "<b>$pn</b>" : $pn);
  } else {
    return($pn);
  }
}

sub email_attachment_addtolist {
  for my $f (@_) {
    if(ref($f) eq 'ARRAY') {
      email_attachment_addtolist(@$f);
    } else {
      my $name=$f;
      $name =~ s/.*\///;
      $attachments_store->set($attachments_store->append,
                              ATTACHMENTS_FILE,$shortcuts->absolu($f),
                              ATTACHMENTS_NAME,$name,
                              ATTACHMENTS_FOREGROUND,
                              (-f $shortcuts->absolu($f) ?
                               hex_color('black') :
                               hex_color('red')),
                             );
    }
  }
}

sub email_attachment_add {
  my $d=Gtk3::FileChooserDialog
    ->new(__("Attach file"),
	  $w{'main_window'},'open',
	  'gtk-cancel'=>'cancel',
	  'gtk-ok'=>'ok');
  $d->set_select_multiple(1);
  my $r=$d->run;
  if($r eq 'ok') {
    email_attachment_addtolist($d->get_filenames);
  }
  $d->destroy();
}

sub email_attachment_remove {
  my @selected=$w{'attachments_list'}->get_selection->get_selected_rows;
  for my $i (map { $attachments_store->get_iter($_); }
	     (@{$selected[0]}) ) {
    $attachments_store->remove($i) if($i);
  }
}

sub send_emails {

  # are there some annotated answer sheets to send?

  $projet{'_report'}->begin_read_transaction('emNU');
  my $n=$projet{'_report'}->type_count(REPORT_ANNOTATED_PDF);
  my $n_annotated=$projet{'_capture'}->annotated_count();
  $projet{'_report'}->end_transaction('emNU');

  if($n==0) {
    my $dialog = Gtk3::MessageDialog
      ->new($w{'main_window'},
            'destroy-with-parent',
            'error','ok',
            __("There are no annotated corrected answer sheets to send.")
            ." "
            .($n_annotated>0 ?
              __("Please group the annotated sheets to PDF files to be able to send them.") :
              __("Please annotate answer sheets and group them to PDF files to be able to send them.") )
           );
    $dialog->run;
    $dialog->destroy;

    return();
  }

  # check perl modules availibility

  my @needs_module=(qw/Email::Address Email::MIME
		       Email::Sender Email::Sender::Simple/);
  if($config->get('email_transport') eq 'sendmail') {
    push @needs_module,'Email::Sender::Transport::Sendmail';
  } elsif($config->get('email_transport') eq 'SMTP') {
    push @needs_module,'Email::Sender::Transport::SMTP';
  }
  my @manque=();
  for my $m (@needs_module) {
    if(!check_install(module=>$m)) {
      push @manque,$m;
    }
  }
  if(@manque) {
    debug 'Mailing: Needs perl modules '.join(', ',@manque);

    my $dialog = Gtk3::MessageDialog
      ->new($w{'main_window'},
            'destroy-with-parent',
            'error','ok','');
    $dialog->set_markup(
			sprintf(__("Sending emails requires some perl modules that are not installed: %s. Please install these modules and try again."),
			'<b>'.join(', ',@manque).'</b>')
		       );
    $dialog->run;
    $dialog->destroy;

    return();
  }

  # STARTTLS is only available with Email::Sender >= 1.300027
  # Warn in case it is not available

  if($config->get('email_smtp_ssl') =~ /[^01]/) {
    load "Email::Sender";
    load "version";
    if(version->parse($Email::Sender::VERSION)
       < version->parse("1.300027")) {
      my $dialog = Gtk3::MessageDialog
        ->new($w{'main_window'},
              'destroy-with-parent',
              'error','ok','');
      $dialog->set_markup(__("SMTP security mode \"STARTTLS\" is only available with Email::Sender version 1.300027 and over. Please install a newer version of this perl module or change SMTP security mode, and try again."));
      $dialog->run;
      $dialog->destroy;
      return();
    }
  }

  load Email::Address;

  # then check a correct sender address has been set

  my @sa=Email::Address->parse($config->get('email_sender'));

  if(!@sa) {
    my $message;
    if($config->get('email_sender')) {
      $message.=sprintf(__("The email address you entered (%s) is not correct."),
			$config->get('email_sender')).
	"\n".__"Please edit your preferences to correct your email address.";
    } else {
      $message.=__("You did not enter your email address.").
	"\n".__"Please edit the preferences to set your email address.";
    }
    my $dialog = Gtk3::MessageDialog
      ->new($w{'main_window'},
            'destroy-with-parent',
            'error','ok','');
    $dialog->set_markup($message);
    $dialog->run;
    $dialog->destroy;

    return();
  }

  # Now check (if applicable) that sendmail path is ok

  if($config->get('email_transport') eq 'sendmail'
     && $config->get('email_sendmail_path')
     && !-f $config->get('email_sendmail_path')) {
    my $dialog = Gtk3::MessageDialog
      ->new($w{'main_window'},
            'destroy-with-parent',
# TRANSLATORS: Do not translate the 'sendmail' word.
            'error','ok','');
    $dialog->set_markup(sprintf(__("The <i>sendmail</i> program cannot be found at the location you specified in the preferences (%s). Please update your configuration."),$config->get('email_sendmail_path')));
    $dialog->run;
    $dialog->destroy;

    return();
  }

  # find columns with emails in the students list file

  my %cols_email=$projet{_students_list}
    ->heads_count(sub { my @a=Email::Address->parse(@_);return(@a) });
  my @cols=grep { $cols_email{$_}>0 } (keys %cols_email);
  $prefs->store_register('email_col'=>
		 cb_model(map { $_=>$_ } (@cols)));

  if(!@cols) {
    my $dialog = Gtk3::MessageDialog
      ->new($w{'main_window'},
            'destroy-with-parent',
            'error','ok',
            __"No email addresses has been found in the students list file. You need to write the students addresses in a column of this file.");
    $dialog->run;
    $dialog->destroy;

    return();
  }

  # which is the best column ?

  my $nmax=0;
  my $col_max='';

  for(@cols) {
    if($cols_email{$_}>$nmax) {
      $nmax=$cols_email{$_};
      $col_max=$_;
    }
  }

  $config->set('project:email_col',$col_max)
    if(!$config->get('email_col'));

  # Then, open configuration window...
  my $gap=read_glade('mailing',
		     qw/emails_list email_dialog label_name
			attachments_list attachments_expander
			email_cb_email_use_html/);

  $w{'label_name'}->set_text(project_email_name());

  $w{'attachments_list'}->set_model($attachments_store);
  $renderer=Gtk3::CellRendererText->new;
# TRANSLATORS: This is the title of a column containing attachments file paths in a table showing all attachments, when sending them to the students by email.
  $column = Gtk3::TreeViewColumn->new_with_attributes (__"file",
						       $renderer,
						       text=> ATTACHMENTS_NAME,
						      'foreground'=> ATTACHMENTS_FOREGROUND,
						      );
  $w{'attachments_list'}->append_column ($column);

  $w{'attachments_list'}->set_tooltip_column(ATTACHMENTS_FILE);
  $w{'attachments_list'}->get_selection->set_mode(GTK_SELECTION_MULTIPLE);

  #

  $w{'emails_list'}->set_model($emails_store);
  $renderer=Gtk3::CellRendererText->new;
# TRANSLATORS: This is the title of a column containing copy numbers in a table showing all annotated answer sheets, when sending them to the students by email.
  $column = Gtk3::TreeViewColumn->new_with_attributes (__"copy",
						       $renderer,
						       text=> EMAILS_SC);
  $w{'emails_list'}->append_column ($column);
  $renderer=Gtk3::CellRendererText->new;
# TRANSLATORS: This is the title of a column containing students names in a table showing all annotated answer sheets, when sending them to the students by email.
  $column = Gtk3::TreeViewColumn->new_with_attributes (__"name",
						       $renderer,
						       text=> EMAILS_NAME);
  $w{'emails_list'}->append_column ($column);
  $renderer=Gtk3::CellRendererText->new;
# TRANSLATORS: This is the title of a column containing students email addresses in a table showing all annotated answer sheets, when sending them to the students by email.
  $column = Gtk3::TreeViewColumn->new_with_attributes (__"email",
						       $renderer,
						       text=> EMAILS_EMAIL);
  $w{'emails_list'}->append_column ($column);
  $renderer=Gtk3::CellRendererText->new;
# TRANSLATORS: This is the title of a column containing mailing status (not sent, already sent, failed) in a table showing all annotated answer sheets, when sending them to the students by email.
  $column = Gtk3::TreeViewColumn->new_with_attributes (__"status",
						       $renderer,
						       text=> EMAILS_STATUS);
  $w{'emails_list'}->append_column ($column);

  $projet{'_report'}->begin_read_transaction('emCC');
  $email_key=$projet{'_association'}->variable('key_in_list');
  $email_r=$projet{'_report'}->get_associated_type(REPORT_ANNOTATED_PDF);

  $emails_store->clear;
  for my $i (@$email_r) {
    my ($s)=$projet{_students_list}->data($email_key,$i->{id},test_numeric=>1);
    my @sc=$projet{'_association'}->real_back($i->{id});
    $emails_store->set($emails_store->append,
		       EMAILS_ID,$i->{id},
		       EMAILS_EMAIL,'',
		       EMAILS_NAME,$s->{'_ID_'},
		       EMAILS_SC,(defined($sc[0]) ? pageids_string(@sc) : "[".$i->{id}."]"),
                       EMAILS_STATUS,($i->{mail_status}==REPORT_MAIL_OK ? __("done") :
                                      $i->{mail_status}==REPORT_MAIL_FAILED ? __("failed") :
                                      ""),
		       );
  }
  $w{emails_failed}=[map { $_->{id} } grep {  $_->{mail_status}==REPORT_MAIL_FAILED } (@$email_r)];

  $projet{'_report'}->end_transaction('emCC');

  $w{'emails_list'}->get_selection->set_mode(GTK_SELECTION_MULTIPLE);
  $w{'emails_list'}->get_selection->select_all;

  $attachments_store->clear;
  email_attachment_addtolist(@{$config->get('email_attachment')});

  $w{'attachments_expander'}->set_expanded(@{$config->get('email_attachment')} ? 1 : 0);

  if($config->get('conserve_taille')) {
    AMC::Gui::WindowSize::size_monitor
	($w{'email_dialog'},{config=>$config,
			     key=>'mailing_window_size'});
  }

  $prefs->transmet_pref($gap,prefix=>'email',root=>'project:');
  my $resp=$w{'email_dialog'}->run;
  my @ids=();
  if($resp==1) {
    $prefs->reprend_pref(prefix=>'email');
    # get selection
    my @selected=$w{'emails_list'}->get_selection->get_selected_rows;
    @selected=@{$selected[0]};
    for my $i (@selected) {
      my $iter=$emails_store->get_iter($i);
      push @ids,$emails_store->get($iter,EMAILS_ID);
    }
    # get attachments filenames
    my @f=();
    my $iter=$attachments_store->get_iter_first;
    my $ok=defined($iter);
    while($ok) {
      push @f,$shortcuts->relatif($attachments_store->get($iter,ATTACHMENTS_FILE));
      $ok=$attachments_store->iter_next($iter);
    }
    if(@f) {
      $config->set('project:email_attachment',[@f]);
    } else {
      $config->set('project:email_attachment',[]);
    }
  }
  $w{'email_dialog'}->destroy;

  # are all attachments present?
  if($resp==1) {
    my @missing=grep { ! -f $shortcuts->absolu($_) } (@{$config->get('email_attachment')});
    if(@missing) {
      my $dialog = Gtk3::MessageDialog
	->new($w{'main_window'},
              'destroy-with-parent',
              'error','ok',
              __("Some files you asked to be attached to the emails are missing:")."\n".join("\n",@missing)."\n".
              __("Please create them or remove them from the list of attached files."));
      $dialog->run();
      $dialog->destroy();
      $resp=0;
    }
  }

  if($resp==1) {
    # writes the list of copies to send in a temporary file
    my $fh=File::Temp->new(TEMPLATE => "ids-XXXXXX",
			   TMPDIR => 1,
			   UNLINK=> 1);
    print $fh join("\n",@ids)."\n";
    $fh->seek( 0, SEEK_END );

    my @mailing_args=("--project",$shortcuts->absolu('%PROJET/'),
		      "--project-name",project_email_name(),
		      "--students-list",$config->get_absolute('listeetudiants'),
		      "--list-encoding",bon_encodage('liste'),
		      "--csv-build-name",csv_build_name(),
		      "--ids-file",$fh->filename,
		      "--email-column",$config->get('email_col'),
		      "--sender",$config->get('email_sender'),
		      "--subject",$config->get('email_subject'),
		      "--text",$config->get('email_text'),
		      "--text-content-type",
                      ($config->get('email_use_html') ? 'text/html' : 'text/plain'),
		      "--transport",$config->get('email_transport'),
		      "--sendmail-path",$config->get('email_sendmail_path'),
		      "--smtp-host",$config->get('email_smtp_host'),
		      "--smtp-port",$config->get('email_smtp_port'),
                      "--smtp-ssl",$config->get('email_smtp_ssl'),
                      "--smtp-user",$config->get('email_smtp_user'),
                      "--smtp-passwd-file",$config->passwd_file("SMTP"),
		      "--cc",$config->get('email_cc'),
		      "--bcc",$config->get('email_bcc'),
                      "--delay",$config->get('email_delay'),
		     );

    for(@{$config->get('email_attachment')}) {
      push @mailing_args,"--attach",$shortcuts->absolu($_);
    }

    commande('commande'=>["auto-multiple-choice","mailing",
			  pack_args(@mailing_args,
				    "--debug",debug_file(),
				    "--progression-id",'mailing',
				    "--progression",1,
                                    "--log",$shortcuts->absolu('mailing.log'),
				   ),
			 ],
	     'progres.id'=>'mailing',
	     'texte'=>__"Sending emails...",
	     'o'=>{'fh'=>$fh},
	     'fin'=>sub {
	       my ($c,%data)=@_;
	       close($c->{'o'}->{'fh'});

	       my $ok=$c->variable('OK') || 0;
	       my $failed=$c->variable('FAILED') || 0;
	       my @message;
	       push @message,"<b>".(__"Cancelled.")."</b>"
		 if($data{cancelled});
               push @message,"<b>".(__"SMTP authentication failed: check SMTP configuration and password.")."</b>"
		 if($c->variable('failed_auth'));
	       push @message,sprintf(__"%d message(s) has been sent.",$ok);
	       if($failed>0) {
		 push @message,"<b>".sprintf("%d message(s) could not be sent.",$failed)."</b>";
	       }
	       my $dialog = Gtk3::MessageDialog
		 ->new($w{'main_window'},
                       'destroy-with-parent',
                       ($failed>0 ? 'warning' : 'info'),'ok','');
               $dialog->set_markup(join("\n",@message));
	       $dialog->run;
	       $dialog->destroy;
	     },
	    );
  }
}

sub email_change_col {
  $config->set_local_keys('email_col');
  $prefs->reprend_pref(prefix=>'email',container=>'local');

  my $i=$emails_store->get_iter_first;
  my $ok=defined($i);
  while($ok) {
    my ($s)=$projet{_students_list}->data($email_key,$emails_store->get($i,EMAILS_ID),test_numeric=>1);
    $emails_store->set($i,EMAILS_EMAIL,$s->{$config->get('local:email_col')});
    $ok=$emails_store->iter_next($i);
  }
}

sub mailing_select_failed {
  my $select=$w{'emails_list'}->get_selection;
  my $model=$w{'emails_list'}->get_model();
  $select->unselect_all();
  for my $id (@{$w{emails_failed}}) {
    $select->select_iter(model_id_to_iter($model,EMAILS_ID,$id));
  }
}

sub mailing_set_project_name {
  my $dialog=Gtk3::Dialog
    ->new(__("Set exam name"),$w{''},
	  ['destroy-with-parent'],'gtk-cancel'=>'cancel','gtk-ok'=>'ok',
	  );
  $dialog->set_default_response ('ok');

  my $t=Gtk3::Grid->new();
  my $widget;
  $t->attach(Gtk3::Label->new(__"Examination name"),0,0,1,1);
  $widget=Gtk3::Entry->new();
  $w{'set_name_x_nom_examen'}=$widget;
  $t->attach($widget,1,0,1,1);
  $t->attach(Gtk3::Label->new(__"Code (short name) for examination"),0,1,1,1);
  $widget=Gtk3::Entry->new();
  $w{'set_name_x_code_examen'}=$widget;
  $t->attach($widget,1,1,1,1);

  $t->show_all;
  $dialog->get_content_area()->add ($t);

  $prefs->transmet_pref('',prefix=>'set_name',root=>'project:');

  my $response = $dialog->run;

  if($response eq 'ok') {
    $prefs->reprend_pref(prefix=>'set_name');
    $w{'label_name'}->set_text(project_email_name());
  }

  $dialog->destroy;
}

#######################################

sub choose_columns {
  my ($type)=@_;

  my $l=$config->get('export_'.$type.'_columns');

  my $i=1;
  my %selected=map { $_=>$i++ } (split(/,+/,$l));
  my %order=();
  @available=('student.copy','student.key','student.name',
	      $projet{_students_list}->heads());
  $i=0;
  for(@available) {
     if($selected{$_}) {
       $i=$selected{$_};
     } else {
       $i.='1';
     }
     $order{$_}=$i;
   }
  @available=sort { $order{$a} cmp $order{$b} } @available;

  my $gcol=read_glade('choose_columns',
		      qw/columns_list columns_instructions/);

  my $columns_store=Gtk3::ListStore->new('Glib::String','Glib::String');
  $w{'columns_list'}->set_model($columns_store);
  my $renderer=Gtk3::CellRendererText->new;
# TRANSLATORS: This is the title of a column containing all columns names from the students list file, when choosing which columns has to be exported to the spreadsheets.
  my $column = Gtk3::TreeViewColumn->new_with_attributes (__"column",
						       $renderer,
						       text=> 0);
  $w{'columns_list'}->append_column ($column);

  my @selected_iters=();
  for my $c (@available) {
    my $name=$c;
    $name=__("<full name>") if($c eq 'student.name');
    $name=__("<student identifier>") if($c eq 'student.key');
    $name=__("<student copy>") if($c eq 'student.copy');
    my $iter=$columns_store->append;
    $columns_store->set($iter,
			0,$name,
			1,$c);
    push @selected_iters,$iter if($selected{$c});
  }
  $w{'columns_list'}->set_reorderable(1);
  $w{'columns_list'}->get_selection->set_mode(GTK_SELECTION_MULTIPLE);
  for(@selected_iters) { $w{'columns_list'}->get_selection->select_iter($_); }

  my $resp=$w{'choose_columns'}->run;
  if($resp==1) {
    my @k=();
    my @s=$w{'columns_list'}->get_selection->get_selected_rows;
    for my $i (@{$s[0]}) {
      push @k,$columns_store->get($columns_store->get_iter($i),1) if($i);
    }
    $config->set('export_'.$type.'_columns',join(',',@k));
  }

  $w{'choose_columns'}->destroy;
}

sub choose_columns_current {
  choose_columns(lc($config->get('format_export')));
}

#######################################

# PLUGINS

sub plugins_add {
  my $d=Gtk3::FileChooserDialog
    ->new(__("Install an AMC plugin"),
	  $w{'main_window'},'open',
	  'gtk-cancel'=>'cancel',
	  'gtk-ok'=>'ok');
  my $filter=Gtk3::FileFilter->new();
  $filter->set_name(__"Plugins (zip, tgz)");
  for my $ext (qw/ZIP zip TGZ tgz tar.gz TAR.GZ/) {
    $filter->add_pattern("*.$ext");
  }
  $d->add_filter($filter);

  my $r=$d->run;
  if($r eq 'ok') {
    my $plugin=$d->get_filename;
    $d->destroy;

    # unzip in a temporary directory

    my ($temp_dir,$error)=unzip_to_temp($plugin);

    if($error) {
      my $dialog = Gtk3::MessageDialog
	->new($w{'main_window'},
              'destroy-with-parent',
              'error','ok',
              sprintf(__("An error occured while trying to extract files from the plugin archive: %s."),$error));
      $dialog->run;
      $dialog->destroy;
      return();
    }

    # checks validity

    my ($nf,$main)=n_fich($temp_dir);
    if($nf<1) {
      my $dialog = Gtk3::MessageDialog
	->new($w{'main_window'},
              'destroy-with-parent',
              'error','ok',
              __"Nothing extracted from the plugin archive. Check it.");
      $dialog->run;
      $dialog->destroy;
      return();
    }
    if($nf>1 || !-d $main) {
      my $dialog = Gtk3::MessageDialog
	->new($w{'main_window'},
              'destroy-with-parent',
              'error','ok',
              __"This is not a valid plugin, as it contains more than one directory at the first level.");
      $dialog->run;
      $dialog->destroy;
      return();
    }

    if(!-d "$main/perl/AMC") {
      my $dialog = Gtk3::MessageDialog
	->new($w{'main_window'},
              'destroy-with-parent',
              'error','ok',
              __"This is not a valid plugin, as it does not contain a perl/AMC subdirectory.");
      $dialog->run;
      $dialog->destroy;
      return();
    }

    my $name=$main;
    $name =~ s/.*\///;

    # already installed?

    if($name=~/[^.]/ && -e $config->subdir("plugins/$name")) {
      my $dialog = Gtk3::MessageDialog
	->new($w{'main_window'},
              'destroy-with-parent',
              'question','yes-no','');
      $dialog->set_markup(
			  sprintf(__("A plugin is already installed with the same name (%s). Do you want to delete the old one and overwrite?"),
				  "<b>$name</b>"));
      my $r=$dialog->run;
      $dialog->destroy;
      return if($r ne 'yes');

      remove_tree($config->subdir("plugins/$name"),{'verbose'=>0,'safe'=>1,'keep_root'=>0});
    }

    # go!

    debug "Installing plugin $name to ".$config->subdir("plugins");

    if(system('mv',$main,$config->subdir("plugins"))!=0) {
      my $dialog = Gtk3::MessageDialog
	->new($w{'main_window'},
              'destroy-with-parent',
              'error','ok',
              sprintf(__("Error while moving the plugin to the user plugin directory: %s"),$!));
      my $r=$dialog->run;
      $dialog->destroy;
      return();
    }

    my $dialog = Gtk3::MessageDialog
	->new($w{'main_window'},
              'destroy-with-parent',
              'info','ok',
              __"Please restart AMC before using the new plugin...");
      my $r=$dialog->run;
      $dialog->destroy;

  } else {
    $d->destroy;
  }
}

#######################################

sub file_size {
  my ($oo,@files)=@_;
  my $s=0;
  $oo->{recursive}--;
 FILE: for my $f (@files) {
    if(-f $f) {
      $s += -s $f;
    } elsif(-d $f) {
      if($oo->{recursive}>=0) {
	if(opendir(SDIR,$f)) {
	  my @dir_files=map { "$f/$_"; } 
	    grep { !$oo->{pattern} || /$oo->{pattern}/ }
	    grep { ! /^\.{1,2}$/ } readdir(SDIR);
	  closedir(SDIR);
	  $s += file_size({%$oo},@dir_files);
	}
      }
    }
  }
  return($s);
}

my @size_units=('k','M','G','T','P');

sub human_readable_size {
  my ($s)=@_;
  my $i=0;
  while($s>=1024) {
    $s /= 1024;
    $i++;
  }
  if($i==0) {
    return($s);
  } else {
    return(sprintf('%.3g%s',$s,$size_units[$i-1]));
  }
}

my @cleanup_components
  =(
    {id=>'zooms',
     short=>__("zooms"),
     text=>__("boxes images are extracted from the scans while processing automatic data capture. They can be removed if you don't plan to use the zooms dialog to check and correct boxes categorization. They can be recovered processing again automatic data capture from the same scans."),
     size=>sub {
       return(file_size({recursive=>5},$shortcuts->absolu('%PROJET/cr/zooms'))
	     + $projet{'_capture'}->zooms_total_size_transaction());
     },
     action=>sub {
       return(remove_tree($shortcuts->absolu('%PROJET/cr/zooms'),
			  {'verbose'=>0,'safe'=>1,'keep_root'=>1})
	     + $projet{'_capture'}->zooms_cleanup_transaction());
     },
    },
    {id=>'matching_reports',
     short=>__("layout reports"),
     text=>__("these images are intended to show how the corner marks have been recognized and positioned on the scans. They can be safely removed once the scans are known to be well-recognized. They can be recovered processing again automatic data capture from the same scans."),
     size=>sub {
       return(file_size({pattern=>'^page-',recursive=>1},
			$shortcuts->absolu('%PROJET/cr/')));
     },
     action=>sub {
       my $dir=$shortcuts->absolu('%PROJET/cr/');
       if(opendir(CRDIR,$dir)) {
	 my @files=map { "$dir/$_" } grep { /^page-/ } readdir(CRDIR);
	 closedir(CRDIR);
	 return(unlink(@files));
       } else { return(0); }
     },
    },
    {id=>'annotated_pages',
     short=>__("annotated pages"),
     text=>__("jpeg annotated pages are made before beeing assembled to PDF annotated files. They can safely be removed, and will be recovered automatically the next time annotation will be requested."),
     size=>sub {
       return(file_size({recursive=>5},$shortcuts->absolu('%PROJET/cr/corrections/jpg')));
     },
     action=>sub {
       return(remove_tree($shortcuts->absolu('%PROJET/cr/corrections/jpg'),
			  {'verbose'=>0,'safe'=>1,'keep_root'=>1}));
     },
    },
   );

sub table_sep {
  my ($t,$y,$x)=@_;
  my $sep=Gtk3::HSeparator->new();
  $t->attach($sep,0,$x,$y,$y+1,["expand","fill"],[],0,0);
}

sub cleanup_dialog {
  my %files;
  my %cb;

  my $gap=read_glade('cleanup');

  my $dialog=$gap->get_object('cleanup');
  my $notebook=$gap->get_object('components');
  for my $c (@cleanup_components) {
    my $t=$c->{text};
    my $s=undef;
    if($c->{size}) {
      $s=&{$c->{size}}();
      $t.= "\n".__("Total size of concerned files:")." ".
	human_readable_size($s);
    }
    my $label=Gtk3::Label->new($t);
    $label->set_justify('left');
    $label->set_max_width_chars(50);
    $label->set_line_wrap(1);
    $label->set_line_wrap_mode('word');

    my $check=Gtk3::CheckButton->new;
    $c->{check}=$check;
    my $short_label=Gtk3::Label->new($c->{short});
    $short_label->set_justify('center');
    $short_label->set_sensitive(!( defined($s) && $s==0));
    my $hb=Gtk3::HBox->new();
    $hb->pack_start($check,FALSE,FALSE,0);
    $hb->pack_start($short_label,TRUE,TRUE,0);
    $hb->show_all;

    $notebook->append_page_menu($label,$hb,undef);
  }
  $notebook->show_all;

  my $reponse=$dialog->run();

  for my $c (@cleanup_components) {
    $c->{active}=$c->{check}->get_active();
  }

  $dialog->destroy();
  Gtk3::main_iteration while ( Gtk3::events_pending );

  debug "RESPONSE=$reponse";

  return() if($reponse!=10);

  my $n=0;

  for my $c (@cleanup_components) {
    if($c->{active}) {
      debug "Removing ".$c->{id}." ...";
      $n+=&{$c->{action}};
    }
  }

  $dialog = Gtk3::MessageDialog
    ->new($w{'main_window'},
	  'destroy-with-parent',
	  'info','ok',
	  __("%s files were removed."),$n);
  $dialog->run;
  $dialog->destroy;
}

#######################################

exit 0 if($do_nothing);

#######################################

if($config->get('conserve_taille')
   && $config->get('taille_x_main') && $config->get('taille_y_main')) {
    $w{'main_window'}->resize($config->get('taille_x_main'),$config->get('taille_y_main'));
}

gui_no_project();

projet_ouvre($ARGV[0]);

#######################################
# For MacPorts with latexfree variant, for example

if("0" =~ /(1|true|yes)/i) {
    my $message='';
    if(!commande_accessible("kpsewhich")) {
	$message=sprintf(__("I don't find the command %s."),"kpsewhich")
	    .__("Perhaps LaTeX is not installed?");
    } else {
	if(!get_sty()) {
# TRANSLATORS: Do not translate 'auto-multiple-choice latex-link', which is a command to be typed on MacOsX
	    $message=__("The style file automultiplechoice.sty seems to be unreachable. Try to use command 'auto-multiple-choice latex-link' as root to fix this.");
	}
    }
    if($message) {
	my $dialog = Gtk3::MessageDialog
	    ->new($w{'main_window'},
		  'destroy-with-parent',
		  'error','ok',$message);
	$dialog->run;
	$dialog->destroy;
    }
}

my $css=Gtk3::CssProvider->new();
$css->load_from_data('
infobar.info, infobar.info button {
    background-color: #29A10F;
    background-image: none;
}
infobar.warning, infobar.warning button {
    background-color: #CD5600;
    background-image: none;
}
infobar.error, infobar.error button {
    background-color: #CD0500;
    background-image: none;
}
');

Gtk3::StyleContext::add_provider_for_screen
  ( $w{main_window}->get_screen(),
    $css, Gtk3::STYLE_PROVIDER_PRIORITY_APPLICATION);

$config->connect_to_window($w{main_window});

test_debian_amc();
test_libnotify();
test_magick();

Gtk3->main();

1;

__END__

