AMC-gui.pl

AMC-gui.pl file - Anirvan Sarkar, 12/27/2012 04:20 pm

Download (198.5 kB)

 
1
#! /usr/bin/perl -w
2
#
3
# Copyright (C) 2008-2012 Alexis Bienvenue <paamc@passoire.fr>
4
#
5
# This file is part of Auto-Multiple-Choice
6
#
7
# Auto-Multiple-Choice is free software: you can redistribute it
8
# and/or modify it under the terms of the GNU General Public License
9
# as published by the Free Software Foundation, either version 2 of
10
# the License, or (at your option) any later version.
11
#
12
# Auto-Multiple-Choice is distributed in the hope that it will be
13
# useful, but WITHOUT ANY WARRANTY; without even the implied warranty
14
# of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15
# General Public License for more details.
16
#
17
# You should have received a copy of the GNU General Public License
18
# along with Auto-Multiple-Choice.  If not, see
19
# <http://www.gnu.org/licenses/>.
20
21
use Getopt::Long;
22
23
use Gtk2 -init;
24
use XML::Simple;
25
use IO::File;
26
use IO::Select;
27
use POSIX qw/strftime/;
28
use Time::Local;
29
use File::Spec::Functions qw/splitpath catpath splitdir catdir catfile rel2abs tmpdir/;
30
use File::Temp qw/ tempfile tempdir /;
31
use File::Copy;
32
use File::Path qw/remove_tree/;
33
use File::Find;
34
use Archive::Tar;
35
use Archive::Tar::File;
36
use Encode;
37
use Unicode::Normalize;
38
use I18N::Langinfo qw(langinfo CODESET);
39
use Locale::Language;
40
41
use Module::Load;
42
use Module::Load::Conditional qw/check_install/;
43
44
use AMC::Basic;
45
use AMC::State;
46
use AMC::Data;
47
use AMC::DataModule::capture ':zone';
48
use AMC::DataModule::report ':const';
49
use AMC::Scoring;
50
use AMC::Gui::Manuel;
51
use AMC::Gui::Association;
52
use AMC::Gui::Commande;
53
use AMC::Gui::Notes;
54
use AMC::Gui::Zooms;
55
use AMC::FileMonitor;
56
use AMC::Gui::WindowSize;
57
58
use Data::Dumper;
59
60
use constant {
61
    DOC_TITRE => 0,
62
    DOC_MAJ => 1,
63
64
    MEP_PAGE => 0,
65
    MEP_ID => 1,
66
    MEP_MAJ => 2,
67
68
    DIAG_ID => 0,
69
    DIAG_ID_BACK => 1,
70
    DIAG_MAJ => 2,
71
    DIAG_EQM => 3,
72
    DIAG_EQM_BACK => 4,
73
    DIAG_DELTA => 5,
74
    DIAG_DELTA_BACK => 6,
75
    DIAG_ID_STUDENT => 7,
76
    DIAG_ID_PAGE => 8,
77
    DIAG_ID_COPY => 9,
78
79
    INCONNU_FILE => 0,
80
    INCONNU_SCAN => 1,
81
    INCONNU_TIME => 2,
82
    INCONNU_TIME_N => 3,
83
    INCONNU_PREPROC => 4,
84
85
    PROJ_NOM => 0,
86
    PROJ_ICO => 1,
87
88
    MODEL_NOM => 0,
89
    MODEL_PATH => 1,
90
    MODEL_DESC => 2,
91
92
    COPIE_N => 0,
93
94
    TEMPLATE_FILES_PATH => 0,
95
    TEMPLATE_FILES_FILE => 1,
96
97
    EMAILS_SC => 0,
98
    EMAILS_NAME => 1,
99
    EMAILS_EMAIL => 2,
100
    EMAILS_ID => 3,
101
102
    ATTACHMENTS_FILE => 0,
103
    ATTACHMENTS_NAME => 1,
104
    ATTACHMENTS_FOREGROUND => 2,
105
};
106
107
Gtk2::IconTheme->get_default->prepend_search_path(amc_specdir('icons'));
108
Gtk2::Window->set_default_icon_list(map { Gtk2::IconTheme->get_default->load_icon("auto-multiple-choice",$_,"force-svg") } (8,16,32,48,64,128));
109
110
use_gettext;
111
use_amc_plugins();
112
113
my $debug=0;
114
my $debug_file='';
115
116
my $profile='';
117
118
GetOptions("debug!"=>\$debug,
119
	   "debug-file=s"=>\$debug_file,
120
	   "profile=s"=>\$profile,
121
	   );
122
123
sub set_debug_mode {
124
  my ($debug)=@_;
125
  set_debug($debug);
126
  if($debug) {
127
    my $date=strftime("%c",localtime());
128
    debug ('#' x 40);
129
    debug "# DEBUG - $date";
130
    debug ('#' x 40);
131
    debug "GUI module is located at ".__FILE__;
132
  }
133
}
134
135
if($debug_file) {
136
    $debug=$debug_file;
137
}
138
139
if($debug) {
140
    set_debug_mode($debug);
141
    print "DEBUG ==> ".AMC::Basic::debug_file()."\n";
142
}
143
144
debug_pm_version("Gtk2");
145
146
my $glade_base=__FILE__;
147
$glade_base =~ s/\.p[ml]$/-/i;
148
149
my $home_dir=Glib::get_home_dir();
150
151
my $o_file='';
152
my $o_dir=amc_user_confdir();
153
my $state_file="$o_dir/state.xml";
154
155
my $monitor=AMC::FileMonitor->new();
156
157
# Gets system encoding
158
my $encodage_systeme=langinfo(CODESET());
159
$encodage_systeme='UTF-8' if(!$encodage_systeme);
160
161
sub hex_color {
162
    my $s=shift;
163
    return(Gtk2::Gdk::Color->parse($s)->to_string());
164
}
165
166
my %w=();
167
168
# Default general options, to be used when not set in the main options
169
# file
170
171
my %o_defaut=('pdf_viewer'=>['commande',
172
			     'evince','acroread','gpdf','okular','xpdf',
173
			     ],
174
	      'img_viewer'=>['commande',
175
			     'eog','ristretto','gpicview','mirage','gwenview',
176
			     ],
177
	      'csv_viewer'=>['commande',
178
			     'gnumeric','kspread','libreoffice','localc','oocalc',
179
			     ],
180
	      'ods_viewer'=>['commande',
181
			     'libreoffice','localc','oocalc',
182
			     ],
183
	      'xml_viewer'=>['commande',
184
			     'gedit','kedit','kwrite','mousepad','leafpad',
185
			     ],
186
	      'tex_editor'=>['commande',
187
			     'texmaker','kile','gummi','emacs','gedit','kedit','kwrite','mousepad','leafpad',
188
			     ],
189
	      'txt_editor'=>['commande',
190
			     'gedit','kedit','kwrite','mousepad','emacs','leafpad',
191
			     ],
192
	      'html_browser'=>['commande',
193
			       'sensible-browser %u',
194
			       'firefox %u',
195
			       'galeon %u',
196
			       'konqueror %u',
197
			       'dillo %u',
198
			       'chromium %u',
199
			       ],
200
	      'dir_opener'=>['commande',
201
			     'nautilus --no-desktop file://%d',
202
			     'pcmanfm %d',
203
			     'Thunar %d',
204
			     'konqueror file://%d',
205
			     'dolphin %d',
206
			     ],
207
	      'print_command_pdf'=>['commande',
208
				    'cupsdoprint %f','lpr %f',
209
				    ],
210
# TRANSLATORS: directory name for projects. This directory will be created (if needed) in the home directory of the user. Please use only alphanumeric characters, and - or _. No accentuated characters.
211
	      'rep_projets'=>$home_dir.'/'.__"MC-Projects",
212
	      'projects_home'=>$home_dir.'/'.__"MC-Projects",
213
	      'rep_modeles'=>$o_dir."/Models",
214
	      'seuil_eqm'=>3.0,
215
	      'seuil_sens'=>8.0,
216
	      'saisie_dpi'=>150,
217
	      'vector_scan_density'=>250,
218
	      'n_procs'=>0,
219
	      'delimiteur_decimal'=>',',
220
	      'defaut_encodage_liste'=>'UTF-8',
221
	      'encodage_interne'=>'UTF-8',
222
	      'defaut_encodage_csv'=>'UTF-8',
223
	      'encodage_latex'=>'',
224
	      'defaut_moteur_latex_b'=>'pdflatex',
225
	      'defaut_seuil'=>0.15,
226
	      'taille_max_correction'=>'1000x1500',
227
	      'assoc_window_size'=>'',
228
	      'mailing_window_size'=>'',
229
	      'preferences_window_size'=>'',
230
	      'checklayout_window_size'=>'',
231
	      'manual_window_size'=>'',
232
	      'qualite_correction'=>'150',
233
	      'conserve_taille'=>1,
234
	      'methode_impression'=>'CUPS',
235
	      'imprimante'=>'',
236
	      'options_impression'=>{'sides'=>'two-sided-long-edge',
237
				     'number-up'=>1,
238
				     'repertoire'=>'/tmp',
239
				     'split'=>'',
240
				     },
241
	      'manuel_image_type'=>'xpm',
242
	      'assoc_ncols'=>4,
243
	      'zooms_ncols'=>4,
244
	      'tolerance_marque_inf'=>0.2,
245
	      'tolerance_marque_sup'=>0.2,
246
	      'box_size_proportion'=>0.8,
247
	      'bw_threshold'=>0.6,
248
	      'ignore_red'=>0,
249
250
	      'symboles_trait'=>2,
251
	      'symboles_indicatives'=>'',
252
	      'symbole_0_0_type'=>'none',
253
	      'symbole_0_0_color'=>hex_color('black'),
254
	      'symbole_0_1_type'=>'circle',
255
	      'symbole_0_1_color'=>hex_color('red'),
256
	      'symbole_1_0_type'=>'mark',
257
	      'symbole_1_0_color'=>hex_color('red'),
258
	      'symbole_1_1_type'=>'mark',
259
	      'symbole_1_1_color'=>hex_color('blue'),
260
261
	      'annote_ps_nl'=>60,
262
	      'annote_ecart'=>5.5,
263
	      'annote_chsign'=>4,
264
265
	      'ascii_filenames'=>'',
266
267
	      'defaut_annote_rtl'=>'',
268
# TRANSLATORS: This is the default text to be written on the top of the first page of each paper when annotating. From this string, %s will be replaced with the student final mark, %m with the maximum mark he can obtain, %S with the student total score, and %M with the maximum score the student can obtain.
269
	      'defaut_verdict'=>"%(ID)\n".__("Mark: %s/%m (total score: %S/%M)"),
270
	      'defaut_verdict_q'=>"\"%"."s/%"."m\"",
271
272
	      'zoom_window_height'=>400,
273
	      'zoom_window_factor'=>1.0,
274
275
	      'email_sender'=>'',
276
	      'email_cc'=>'',
277
	      'email_bcc'=>'',
278
	      'email_transport'=>'sendmail',
279
	      'email_sendmail_path'=>['commande',
280
				      '/usr/sbin/sendmail','/usr/bin/sendmail',
281
				      '/sbin/sendmail','/bin/sendmail'],
282
	      'email_smtp_host'=>'smtp',
283
	      'email_smtp_port'=>25,
284
# TRANSLATORS: Subject of the emails which can be sent to the students to give them their annotated completed answer sheet.
285
	      'defaut_email_subject'=>__"Exam result",
286
# TRANSLATORS: Body text of the emails which can be sent to the students to give them their annotated completed answer sheet.
287
	      'defaut_email_text'=>__"Please find enclosed your annotated completed answer sheet.\nRegards.",
288
289
	      'csv_surname_headers'=>'',
290
	      'csv_name_headers'=>'',
291
	      );
292
293
# MacOSX universal command to open files or directories : /usr/bin/open
294
if(lc($^O) eq 'darwin') {
295
    for my $k (qw/pdf_viewer img_viewer csv_viewer ods_viewer xml_viewer tex_editor txt_editor dir_opener/) {
296
	$o_defaut{$k}=['commande','/usr/bin/open','open'];
297
    }
298
    $o_defaut{'html_browser'}=['commande','/usr/bin/open %u','open %u'];
299
}
300
301
# Default options for projects
302
303
my %projet_defaut=('texsrc'=>'',
304
		   'data'=>'data',
305
		   'cr'=>'cr',
306
		   'listeetudiants'=>'',
307
		   'notes'=>'notes.xml',
308
		   'seuil'=>'',
309
		   'encodage_csv'=>'',
310
		   'encodage_liste'=>'',
311
		   'maj_bareme'=>1,
312
		   'docs'=>['DOC-sujet.pdf','DOC-corrige.pdf','DOC-calage.xy'],
313
		   'filter'=>'',
314
		   'filtered_source'=>'DOC-filtered.tex',
315
316
		   'modele_regroupement'=>'',
317
		   'regroupement_compose'=>'',
318
		   'regroupement_type'=>'STUDENTS',
319
		   'regroupement_copies'=>'ALL',
320
321
		   'note_min'=>'',
322
		   'note_max'=>20,
323
		   'note_max_plafond'=>1,
324
		   'note_grain'=>"0.5",
325
		   'note_arrondi'=>'inf',
326
327
		   'liste_key'=>'',
328
		   'assoc_code'=>'',
329
330
		   'moteur_latex_b'=>'',
331
332
		   'nom_examen'=>'',
333
		   'code_examen'=>'',
334
335
		   'nombre_copies'=>0,
336
337
		   'postcorrect_student'=>0,
338
		   'postcorrect_copy'=>0,
339
340
		   '_modifie'=>1,
341
		   '_modifie_ok'=>0,
342
343
		   'format_export'=>'CSV',
344
345
		   'after_export'=>'file',
346
		   'export_include_abs'=>'',
347
348
		   'annote_position'=>'marges',
349
350
		   'verdict'=>'',
351
		   'verdict_q'=>'',
352
		   'annote_rtl'=>'',
353
354
		   'export_sort'=>'n',
355
356
		   'auto_capture_mode'=>-1,
357
		   'allocate_ids'=>0,
358
359
		   'email_col'=>'',
360
		   'email_subject'=>"",
361
		   'email_text'=>"",
362
		   'email_attachment'=>[],
363
		   );
364
365
# Add default project options for each export module:
366
367
my @export_modules=perl_module_search('AMC::Export::register');
368
for my $m (@export_modules) {
369
  load("AMC::Export::register::$m");
370
  my %d="AMC::Export::register::$m"->options_default;
371
  for(keys %d) {
372
    $projet_defaut{$_}=$d{$_};
373
  }
374
}
375
@export_modules=sort { "AMC::Export::register::$a"->weight
376
			 <=> "AMC::Export::register::$b"->weight }
377
  @export_modules;
378
379
# Reads filter plugins list
380
381
my @filter_modules=perl_module_search('AMC::Filter::register');
382
for my $m (@filter_modules) {
383
  load("AMC::Filter::register::$m");
384
}
385
@filter_modules=sort { "AMC::Filter::register::$a"->weight
386
			 <=> "AMC::Filter::register::$b"->weight }
387
  @filter_modules;
388
389
sub best_filter_for_file {
390
  my ($file)=@_;
391
  my $mmax='';
392
  my $max=-10;
393
  for my $m (@filter_modules) {
394
    my $c="AMC::Filter::register::$m"->claim($file);
395
    if($c>$max) {
396
      $max=$c;
397
      $mmax=$m;
398
    }
399
  }
400
  return($mmax);
401
}
402
403
# -----------------
404
405
my %o=();
406
my %state=();
407
408
# Test whether all commands defined to open/view files are
409
# reachable. If not, the user is warned...
410
411
sub test_commandes {
412
    my ($dont_warn)=@_;
413
    my @pasbon=();
414
    my @missing=();
415
    for my $c (grep { /_(viewer|editor|opener)$/ } keys(%o)) {
416
	my $nc=$o{$c};
417
	$nc =~ s/\s.*//;
418
	if(!commande_accessible($nc)) {
419
	    debug "Missing command [$c]: $nc";
420
	    push @missing,$c;
421
	    push @pasbon,$nc;
422
	}
423
    }
424
    if(@pasbon && !$dont_warn) {
425
	my $dialog = Gtk2::MessageDialog
426
	    ->new_with_markup($w{'main_window'},
427
			      'destroy-with-parent',
428
			      'warning','ok',
429
# TRANSLATORS: Message (first part) when some of the commands that are given in the preferences cannot be found.
430
			      __("Some commands allowing to open documents can't be found:")
431
			      ." ".join(", ",map { "<b>$_</b>"; } @pasbon).". "
432
# TRANSLATORS: Message (second part) when some of the commands that are given in the preferences cannot be found.
433
			      .__("Please check its correct spelling and install missing software.")." "
434
# TRANSLATORS: Message (third part) when some of the commands that are given in the preferences cannot be found. The %s will be replaced with the name of the menu entry "Preferences" and the name of the menu "Edit".
435
			      .sprintf(__"You can change used commands following <i>%s</i> from menu <i>%s</i>.",
436
# TRANSLATORS: "Preferences" menu
437
				       __"Preferences",
438
# TRANSLATORS: "Edit" menu
439
				       __"Edit"));
440
	$dialog->run;
441
	$dialog->destroy;
442
    }
443
    return(@missing);
444
}
445
446
# Creates general options directory if not present
447
448
if(! -d $o_dir) {
449
    mkdir($o_dir) or die "Error creating $o_dir : $!";
450
451
    # gets older verions (<=0.254) main configuration file and move it
452
    # to the new location
453
454
    if(-f $home_dir.'/.AMC.xml') {
455
	debug "Moving old configuration file";
456
	move($home_dir.'/.AMC.xml',$o_dir."/cf.default.xml");
457
    }
458
}
459
460
for my $o_sub (qw/plugins/) {
461
  mkdir("$o_dir/$o_sub") if(! -d "$o_dir/$o_sub");
462
}
463
464
#
465
466
sub sub_modif {
467
  my ($opts)=@_;
468
  my ($m,$mo)=($opts->{'_modifie'},$opts->{'_modifie_ok'});
469
  for my $k (keys %$opts) {
470
    if(ref($opts->{$k}) eq 'HASH') {
471
      $m.=','.$opts->{$k}->{'_modifie'} if($opts->{$k}->{'_modifie'});
472
      $mo=1 if($opts->{$k}->{'_modifie_ok'});
473
    }
474
  }
475
  return($m,$mo);
476
}
477
478
# Read/write options XML files
479
480
sub pref_xx_lit {
481
    my ($fichier)=@_;
482
    if((! -f $fichier) || -z $fichier) {
483
	return();
484
    } else {
485
	return(%{XMLin($fichier,SuppressEmpty => '',ForceArray=>['docs','email_attachment'])});
486
    }
487
}
488
489
sub pref_xx_ecrit {
490
    my ($data,$key,$fichier)=@_;
491
    if(open my $fh,">:encoding(utf-8)",$fichier) {
492
	XMLout($data,
493
	       "XMLDecl"=>'<?xml version="1.0" encoding="UTF-8" standalone="yes"?>',
494
	       "RootName"=>$key,'NoAttr'=>1,
495
	       "OutputFile" => $fh,
496
	       );
497
	close $fh;
498
	return(0);
499
    } else {
500
	return(1);
501
    }
502
}
503
504
# Read/write running state
505
506
sub sauve_state {
507
    if($state{'_modifie'} || $state{'_modifie_ok'}) {
508
	debug "Saving state...";
509
510
	if(pref_xx_ecrit(\%state,'AMCState',$state_file)) {
511
	    my $dialog = Gtk2::MessageDialog
512
		->new($w{'main_window'},
513
		      'destroy-with-parent',
514
		      'error','ok',
515
# TRANSLATORS: Error writing one of the configuration files (global or project). The first %s will be replaced with the path of that file, and the second with the error text.
516
		      __"Error writing state file %s: %s",
517
		      $state_file,$!);
518
	    $dialog->run;
519
	    $dialog->destroy;
520
	} else {
521
	    $state{'_modifie'}=0;
522
	    $state{'_modifie_ok'}=0;
523
	}
524
    }
525
}
526
527
# annulation apprentissage
528
529
sub annule_apprentissage {
530
    my $dialog = Gtk2::MessageDialog
531
	->new_with_markup($w{'main_window'},
532
			  'destroy-with-parent',
533
			  'question','yes-no',
534
# Explains that some dialogs are shown only to learn AMC, only once by default (first part).
535
			  __("Several dialogs try to help you be at ease handling AMC.")." ".
536
# 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.
537
			  sprintf(__"Unless you tick the \"%s\" box, they are shown only once.",
538
# 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.
539
				  __"Show this message again next time")." ".
540
# 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.
541
			  __"Do you want to forgot which dialogs you have already seen and ask to show all of them next time they should appear ?"
542
			  );
543
    my $reponse=$dialog->run;
544
    $dialog->destroy;
545
    if($reponse eq 'yes') {
546
	debug "Clearing learning states...";
547
	$state{'apprentissage'}={};
548
	$state{'_modifie'}=1;
549
	sauve_state();
550
    }
551
}
552
553
# Read the state file
554
555
if(-r $state_file) {
556
    %state=pref_xx_lit($state_file);
557
    $state{'apprentissage'}={} if(!$state{'apprentissage'});
558
}
559
560
$state{'_modifie'}=0;
561
$state{'_modifie_ok'}=0;
562
563
# gets the last used profile
564
565
if(!$state{'profile'}) {
566
    $state{'profile'}='default';
567
    $state{'_modifie'}=1;
568
}
569
570
# sets new profile if given as a command argument
571
572
if($profile && $profile ne $state{'profile'}) {
573
    $state{'profile'}=$profile;
574
    $state{'_modifie'}=1;
575
}
576
577
sauve_state();
578
579
debug "Profile : $state{'profile'}";
580
581
$o_file=$o_dir."/cf.".$state{'profile'}.".xml";
582
583
# Read general options ...
584
585
if(-r $o_file) {
586
    %o=pref_xx_lit($o_file);
587
}
588
589
sub set_option_to_default {
590
    my ($key,$subkey,$force)=@_;
591
    if($subkey) {
592
	if($force || ! exists($o{$key}->{$subkey})) {
593
	    $o{$key}->{$subkey}=$o_defaut{$key}->{$subkey};
594
	    debug "New sub-global parameter : $key/$subkey = $o{$key}->{$subkey}";
595
	}
596
    } else {
597
	if($force || ! exists($o{$key})) {
598
	    # set to default
599
	    if(ref($o_defaut{$key}) eq 'ARRAY') {
600
		my ($type,@valeurs)=@{$o_defaut{$key}};
601
		if($type eq 'commande') {
602
		  UC: for my $c (@valeurs) {
603
		      if(commande_accessible($c)) {
604
			  $o{$key}=$c;
605
			  last UC;
606
		      }
607
		  }
608
		    if(!$o{$key}) {
609
			debug "No available command for option $key: using the first one";
610
			$o{$key}=$valeurs[0];
611
		    }
612
		} else {
613
		    debug "ERR: unknown option type : $type";
614
		}
615
	    } elsif(ref($o_defaut{$key}) eq 'HASH') {
616
		$o{$key}={%{$o_defaut{$key}}};
617
	    } else {
618
		$o{$key}=$o_defaut{$key};
619
		$o{$key}=$encodage_systeme if($key =~ /^encodage_/ && !$o{$key});
620
	    }
621
	    debug "New global parameter : $key = $o{$key}" if($o{$key});
622
	} else {
623
	    # already defined option: go with sub-options if any
624
	    if(ref($o_defaut{$key}) eq 'HASH') {
625
		for my $kk (keys %{$o_defaut{$key}}) {
626
		    set_option_to_default($key,$kk,$force);
627
		}
628
	    }
629
	}
630
    }
631
}
632
633
# sets undefined options to default value
634
635
for my $k (keys %o_defaut) {
636
    set_option_to_default($k);
637
}
638
639
# for unexisting commands options, see if we can find an available one
640
# from the default list
641
642
for my $k (test_commandes(1)) {
643
    set_option_to_default($k,'','FORCE');
644
}
645
646
# Clears modified flag for the options
647
648
$o{'_modifie'}=0;
649
$o{'_modifie_ok'}=0;
650
651
# some options were renamed to defaut_* between 0.226 and 0.227
652
653
for(qw/encodage_liste encodage_csv/) {
654
    if($o{"$_"} && ! $o{"defaut_$_"}) {
655
	$o{"defaut_$_"}=$o{"$_"};
656
	$o{'_modifie'}=1;
657
    }
658
}
659
660
# Replace old (pre 0.280) rep_modeles value with new one
661
662
if($o{'rep_modeles'} eq '/usr/share/doc/auto-multiple-choice/exemples') {
663
    $o{'rep_modeles'}=$o_defaut{'rep_modeles'};
664
    $o{'_modifie'}=1;
665
}
666
667
# Internal encoding _must_ be UTF-8, for XML::Writer (used by
668
# AMC::Gui::Association for example) to work
669
if($o{'encodage_interne'} ne 'UTF-8') {
670
    $o{'encodage_interne'}='UTF-8';
671
    $o{'_modifie'}=1;
672
}
673
674
# Reset projects directory to standard one
675
676
$o{'rep_projets'}=absolu($o{'projects_home'});
677
678
# creates projets and models directories if needed (if not present,
679
# Edit/Parameters can be disrupted)
680
681
for my $k (qw/rep_projets rep_modeles/) {
682
  my $path=$o{$k};
683
  if(-e $path) {
684
    debug "WARNING: $path ($k) is not a directory!" if(!-d $path);
685
  } else {
686
    mkdir($path);
687
  }
688
}
689
690
#############################################################################
691
692
my %projet=();
693
694
sub bon_encodage {
695
    my ($type)=@_;
696
    return($projet{'options'}->{"encodage_$type"}
697
	   || $o{"defaut_encodage_$type"}
698
	   || $o{"encodage_$type"}
699
	   || $o_defaut{"defaut_encodage_$type"}
700
	   || $o_defaut{"encodage_$type"}
701
	   || "UTF-8");
702
}
703
704
sub csv_build_0 {
705
  my ($k,@default)=@_;
706
  push @default,grep { $_ } map { s/^\s+//;s/\s+$//;$_; }
707
    split(/,+/,$o{'csv_'.$k.'_headers'});
708
  return("(".join("|",@default).")");
709
}
710
711
sub csv_build_name {
712
  return(csv_build_0('surname','nom','surname').' '
713
	 .csv_build_0('name','prenom','name'));
714
}
715
716
sub raccourcis {
717
    my ($proj)=@_;
718
    my %pathes;
719
    $proj=$projet{'nom'} if(!defined($proj));
720
    $proj='' if(!defined($proj));
721
    if($proj eq '<HOME>') {
722
      %pathes=('%HOME',$home_dir);
723
    } else {
724
      %pathes=('%PROJETS'=>$o{'rep_projets'},
725
	       '%HOME',$home_dir,
726
	       ''=>'%PROJETS',
727
	      );
728
      if($proj) {
729
	$pathes{'%PROJET'}=$o{'rep_projets'}."/".$proj;
730
	$pathes{''}='%PROJET';
731
      }
732
    }
733
    return(\%pathes);
734
}
735
736
sub absolu {
737
    my ($f,$proj)=@_;
738
    return($f) if(!defined($f));
739
    return(proj2abs(raccourcis($proj),$f));
740
}
741
742
sub relatif {
743
    my ($f,$proj)=@_;
744
    return($f) if(!defined($f));
745
    return(abs2proj(raccourcis($proj),$f));
746
}
747
748
sub id2file {
749
    my ($id,$prefix,$extension)=(@_);
750
    $id =~ s/\+//g;
751
    $id =~ s/\//-/g;
752
    return(absolu($projet{'options'}->{'cr'})."/$prefix-$id.$extension");
753
}
754
755
sub is_local {
756
    my ($f,$proj)=@_;
757
    my $prefix=$o{'rep_projets'}."/";
758
    $prefix .= $projet{'nom'}."/" if($proj);
759
    if(defined($f)) {
760
	return($f !~ /^[\/%]/
761
	       || $f =~ /^$prefix/
762
	       || $f =~ /[\%]PROJET\//);
763
    } else {
764
	return('');
765
    }
766
}
767
768
sub fich_options {
769
    my ($nom,$rp)=@_;
770
    $rp=$o{'rep_projets'} if(!$rp);
771
    return "$rp/$nom/options.xml";
772
}
773
774
sub moteur_latex {
775
    my $m=$projet{'options'}->{'moteur_latex_b'};
776
    $m=$o{'defaut_moteur_latex_b'} if(!$m);
777
    $m=$o_defaut{'defaut_moteur_latex_b'} if(!$m);
778
    return($m);
779
}
780
781
sub read_glade {
782
    my ($main_widget,@widgets)=@_;
783
    my $g=Gtk2::Builder->new();
784
    my $glade_file=$glade_base.$main_widget.".glade";
785
    debug "Reading glade file ".$glade_file;
786
    $g->set_translation_domain('auto-multiple-choice');
787
    $g->add_from_file($glade_file);
788
    for ($main_widget,@widgets) {
789
	$w{$_}=$g->get_object($_);
790
	if($w{$_}) {
791
	    $w{$_}->set_name($_) if(!/^(apropos)$/);
792
	} else {
793
	    debug "WARNING: Object $_ not found in $main_widget glade file.";
794
	}
795
    }
796
    $g->connect_signals(undef);
797
    return($g);
798
}
799
800
my @widgets_only_when_opened=(qw/cleanup_menu menu_projet_enreg menu_projet_modele/);
801
802
my $gui=read_glade('main_window',
803
		   qw/onglets_projet onglet_preparation preparation_etats
804
    but_question but_solution
805
    prepare_docs prepare_layout prepare_src
806
    state_layout state_src state_docs state_unrecognized state_marking state_assoc
807
    button_unrecognized button_show_missing
808
    edition_latex
809
    onglet_notation onglet_saisie onglet_reports
810
    log_general commande avancement annulation button_mep_warnings
811
    liste_filename liste_path liste_edit liste_setfile liste_refresh
812
    doc_menu menu_debug
813
    diag_tree state_capture
814
    maj_bareme regroupement_corriges
815
    groupe_model
816
    pref_assoc_c_assoc_code pref_assoc_c_liste_key
817
    export_c_format_export
818
    export_c_export_sort export_cb_export_include_abs
819
    config_export_modules standard_export_options
820
    notation_c_regroupement_type notation_cb_regroupement_compose
821
    pref_prep_s_nombre_copies pref_prep_c_filter
822
    /,@widgets_only_when_opened);
823
824
# Grid lines are not well-positioned in RTL environments, I don't know
825
# why... so I remove them.
826
if($w{'main_window'}->get_direction() eq 'rtl') {
827
    debug "RTL mode: removing vertical grids";
828
    for(qw/documents diag inconnu/) {
829
	my $w=$gui->get_object($_.'_tree');
830
	$w->set_grid_lines('horizontal') if($w);
831
    }
832
}
833
834
$w{'commande'}->hide();
835
836
sub debug_set {
837
    $debug=$w{'menu_debug'}->get_active;
838
    debug "DEBUG MODE : OFF" if(!$debug);
839
    set_debug_mode($debug);
840
    if($debug) {
841
	debug "DEBUG MODE : ON";
842
843
	my $dialog = Gtk2::MessageDialog
844
	    ->new($w{'main_window'},
845
		  'destroy-with-parent',
846
		  'info','ok',
847
# TRANSLATORS: Messahe when switching to debugging mode.
848
		  __("Debugging mode.")." "
849
# TRANSLATORS: Messahe when switching to debugging mode. %s will be replaced with the path of the log file.
850
		  .sprintf(__"Debugging informations will be written in file %s.",AMC::Basic::debug_file()));
851
	$dialog->run;
852
	$dialog->destroy;
853
    }
854
}
855
856
$w{'menu_debug'}->set_active($debug);
857
858
# add doc list menu
859
860
my $docs_menu=Gtk2::Menu->new();
861
862
my @doc_langs=();
863
864
my $hdocdir=amc_specdir('doc/auto-multiple-choice')."/html/";
865
if(opendir(DOD,$hdocdir)) {
866
    push @doc_langs,map { s/auto-multiple-choice\.//;$_; } grep { /auto-multiple-choice\...(_..)?/ } readdir(DOD);
867
    closedir(DOD);
868
} else {
869
    debug("DOCUMENTATION : Can't open directory $hdocdir: $!");
870
}
871
872
# TRANSLATORS: One of the documentation languages.
873
my %ltext_loc=('French'=>__"French",
874
# TRANSLATORS: One of the documentation languages.
875
	       'English'=>__"English",
876
# TRANSLATORS: One of the documentation languages.
877
	       'Japanese'=>__"Japanese",
878
	       );
879
880
for my $l (@doc_langs) {
881
    my $ltext;
882
    $ltext=code2language($l);
883
    $ltext=$l if(! $ltext);
884
    $ltext=$ltext_loc{$ltext} if($ltext_loc{$ltext});
885
    my $m=Gtk2::ImageMenuItem->new_with_label($ltext);
886
    my $it=Gtk2::IconTheme->new();
887
    my ($taille,undef)=Gtk2::IconSize->lookup('menu');
888
    $it->prepend_search_path($hdocdir."auto-multiple-choice.$l");
889
    my $ii=$it->lookup_icon("flag",$taille ,"force-svg");
890
    if($ii) {
891
	$m->set_image(Gtk2::Image->new_from_pixbuf($it->load_icon("flag",$taille ,"force-svg")));
892
    }
893
    $m->signal_connect("activate",\&activate_doc,$l);
894
    $docs_menu->append($m);
895
}
896
897
$docs_menu->show_all();
898
899
$w{'doc_menu'}->set_submenu($docs_menu);
900
901
# make state entries with same background color as around...
902
$col=$w{'prepare_src'}->style()->bg('prelight');
903
for my $s (qw/normal insensitive/) {
904
  for my $k (qw/src docs layout capture marking unrecognized assoc/) {
905
    $w{'state_'.$k}->modify_base($s,$col);
906
  }
907
}
908
909
###
910
911
sub dialogue_apprentissage {
912
    my ($key,$type,$buttons,$force,@oo)=@_;
913
    my $resp='';
914
    $type='info' if(!$type);
915
    $buttons='ok' if(!$buttons);
916
    if($force || !$state{'apprentissage'}->{$key}) {
917
      my $garde;
918
      my $dialog = Gtk2::MessageDialog
919
	->new_with_markup($w{'main_window'},
920
			  'destroy-with-parent',
921
			  $type,$buttons,
922
			  @oo);
923
924
      if(!$force) {
925
	$garde=Gtk2::CheckButton->new(__"Show this message again next time");
926
	$garde->set_active(0);
927
	$garde->can_focus(0);
928
929
	$dialog->get_content_area()->add($garde);
930
      }
931
932
      $dialog->show_all();
933
934
      $resp=$dialog->run;
935
936
      if(!($force || $garde->get_active())) {
937
	debug "Learning : $key";
938
	$state{'apprentissage'}->{$key}=1;
939
	$state{'_modifie'}=1;
940
	sauve_state();
941
      }
942
943
      $dialog->destroy;
944
    }
945
    return($resp);
946
}
947
948
### COPIES
949
950
my $copies_store = Gtk2::ListStore->new ('Glib::String');
951
952
### FILES FOR TEMPLATE
953
954
my $template_files_store = Gtk2::TreeStore->new ('Glib::String',
955
						 'Glib::String');
956
957
### Unrecognized scans
958
959
my $inconnu_store = Gtk2::ListStore->new ('Glib::String','Glib::String',
960
					  'Glib::String','Glib::String',
961
					  'Glib::String');
962
963
$inconnu_store->set_sort_column_id(INCONNU_TIME_N,GTK_SORT_ASCENDING);
964
965
### modele EMAILS
966
967
my $emails_store=Gtk2::ListStore->new ('Glib::String',
968
				       'Glib::String',
969
				       'Glib::String',
970
				       'Glib::String',
971
				      );
972
973
### modele EMAILS
974
975
my $attachments_store=Gtk2::ListStore->new ('Glib::String',
976
					    'Glib::String',
977
					    'Glib::String',
978
					   );
979
980
### modele DIAGNOSTIQUE SAISIE
981
982
my $diag_store;
983
984
sub new_diagstore {
985
  $diag_store
986
    = Gtk2::ListStore->new ('Glib::String',
987
			    'Glib::String',
988
			    'Glib::String',
989
			    'Glib::String',
990
			    'Glib::String',
991
			    'Glib::String',
992
			    'Glib::String',
993
			    'Glib::String',
994
			    'Glib::String',
995
			    'Glib::String');
996
  $diag_store->set_sort_func(DIAG_EQM,\&sort_num,DIAG_EQM);
997
  $diag_store->set_sort_func(DIAG_DELTA,\&sort_num,DIAG_DELTA);
998
  $diag_store->set_sort_func(DIAG_ID,\&sort_from_columns,
999
			     [{'type'=>'n','col'=>DIAG_ID_STUDENT},
1000
			      {'type'=>'n','col'=>DIAG_ID_COPY},
1001
			      {'type'=>'n','col'=>DIAG_ID_PAGE},
1002
			     ]);
1003
}
1004
1005
sub sort_diagstore {
1006
  $diag_store->set_sort_column_id(DIAG_ID,GTK_SORT_ASCENDING);
1007
}
1008
1009
sub show_diagstore {
1010
  $w{'diag_tree'}->set_model($diag_store);
1011
}
1012
1013
new_diagstore();
1014
sort_diagstore();
1015
show_diagstore();
1016
1017
$renderer=Gtk2::CellRendererText->new;
1018
# TRANSLATORS: This is the title of the column containing student/copy identifier in the table showing the results of data captures.
1019
$column = Gtk2::TreeViewColumn->new_with_attributes (__"identifier",
1020
						     $renderer,
1021
						     text=> DIAG_ID,
1022
						     'background'=> DIAG_ID_BACK);
1023
$column->set_sort_column_id(DIAG_ID);
1024
$w{'diag_tree'}->append_column ($column);
1025
1026
$renderer=Gtk2::CellRendererText->new;
1027
# TRANSLATORS: This is the title of the column containing data capture date/time in the table showing the results of data captures.
1028
$column = Gtk2::TreeViewColumn->new_with_attributes (__"updated",
1029
						     $renderer,
1030
						     text=> DIAG_MAJ);
1031
$w{'diag_tree'}->append_column ($column);
1032
1033
$renderer=Gtk2::CellRendererText->new;
1034
# 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.
1035
$column = Gtk2::TreeViewColumn->new_with_attributes (__"MSE",
1036
						     $renderer,
1037
						     'text'=> DIAG_EQM,
1038
						     'background'=> DIAG_EQM_BACK);
1039
$column->set_sort_column_id(DIAG_EQM);
1040
$w{'diag_tree'}->append_column ($column);
1041
1042
$renderer=Gtk2::CellRendererText->new;
1043
# 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.
1044
$column = Gtk2::TreeViewColumn->new_with_attributes (__"sensitivity",
1045
						     $renderer,
1046
						     'text'=> DIAG_DELTA,
1047
						     'background'=> DIAG_DELTA_BACK);
1048
$column->set_sort_column_id(DIAG_DELTA);
1049
$w{'diag_tree'}->append_column ($column);
1050
1051
$w{'diag_tree'}->get_selection->set_mode(GTK_SELECTION_MULTIPLE);
1052
1053
# rajouter a partir de Encode::Supported
1054
# TRANSLATORS: for encodings
1055
my $encodages=[{qw/inputenc latin1 iso ISO-8859-1/,'txt'=>'ISO-8859-1 ('.__("Western Europe").')'},
1056
# TRANSLATORS: for encodings
1057
	       {qw/inputenc latin2 iso ISO-8859-2/,'txt'=>'ISO-8859-2 ('.__("Central Europe").')'},
1058
# TRANSLATORS: for encodings
1059
	       {qw/inputenc latin3 iso ISO-8859-3/,'txt'=>'ISO-8859-3 ('.__("Southern Europe").')'},
1060
# TRANSLATORS: for encodings
1061
	       {qw/inputenc latin4 iso ISO-8859-4/,'txt'=>'ISO-8859-4 ('.__("Northern Europe").')'},
1062
# TRANSLATORS: for encodings
1063
	       {qw/inputenc latin5 iso ISO-8859-5/,'txt'=>'ISO-8859-5 ('.__("Cyrillic").')'},
1064
# TRANSLATORS: for encodings
1065
	       {qw/inputenc latin9 iso ISO-8859-9/,'txt'=>'ISO-8859-9 ('.__("Turkish").')'},
1066
# TRANSLATORS: for encodings
1067
	       {qw/inputenc latin10 iso ISO-8859-10/,'txt'=>'ISO-8859-10 ('.__("Northern").')'},
1068
# TRANSLATORS: for encodings
1069
	       {qw/inputenc utf8x iso UTF-8/,'txt'=>'UTF-8 ('.__("Unicode").')'},
1070
	       {qw/inputenc cp1252 iso cp1252/,'txt'=>'Windows-1252',
1071
		alias=>['Windows-1252','Windows']},
1072
# TRANSLATORS: for encodings
1073
	       {qw/inputenc applemac iso MacRoman/,'txt'=>'Macintosh '.__"Western Europe"},
1074
# TRANSLATORS: for encodings
1075
	       {qw/inputenc macce iso MacCentralEurRoman/,'txt'=>'Macintosh '.__"Central Europe"},
1076
	       ];
1077
1078
sub get_enc {
1079
    my ($txt)=@_;
1080
    for my $e (@$encodages) {
1081
	return($e) if($e->{'inputenc'} =~ /^$txt$/i ||
1082
		      $e->{'iso'} =~ /^$txt$/i);
1083
	if($e->{'alias'}) {
1084
	    for my $a (@{$e->{'alias'}}) {
1085
		return($e) if($a =~ /^$txt$/i);
1086
	    }
1087
	}
1088
    }
1089
    return('');
1090
}
1091
1092
# TRANSLATORS: you can omit the [...] part, just here to explain context
1093
my $cb_model_vide_key=cb_model(''=>__p"(none) [No primary key found in association list]");
1094
# TRANSLATORS: you can omit the [...] part, just here to explain context
1095
my $cb_model_vide_code=cb_model(''=>__p"(none) [No code found in LaTeX file]");
1096
1097
my %cb_stores=(
1098
# TRANSLATORS: One option for decimal point: use a comma. This is a menu entry
1099
    'delimiteur_decimal'=>cb_model(',',__", (comma)",
1100
# TRANSLATORS: One option for decimal point: use a point. This is a menu entry.
1101
				   '.',__". (dot)"),
1102
# TRANSLATORS: One of the rounding method for marks. This is a menu entry.
1103
    'note_arrondi'=>cb_model('inf',__"floor",
1104
# TRANSLATORS: One of the rounding method for marks. This is a menu entry.
1105
			     'normal',__"rounding",
1106
# TRANSLATORS: One of the rounding method for marks. This is a menu entry.
1107
			     'sup',__"ceiling"),
1108
    'methode_impression'=>cb_model('CUPS','CUPS',
1109
# TRANSLATORS: One of the printing methods: use a command (This is not the command name itself). This is a menu entry.
1110
				   'commande',__"command",
1111
# TRANSLATORS: One of the printing methods: print to files. This is a menu entry.
1112
				   'file'=>__"to files"),
1113
# TRANSLATORS: you can omit the [...] part, just here to explain context
1114
    'sides'=>cb_model('one-sided',__p("one sided [No two-sided printing]"),
1115
# TRANSLATORS: One of the two-side printing types. This is a menu entry.
1116
		      'two-sided-long-edge',__"long edge",
1117
# TRANSLATORS: One of the two-side printing types. This is a menu entry.
1118
		      'two-sided-short-edge',__"short edge"),
1119
    'encodage_latex'=>cb_model(map { $_->{'iso'}=>$_->{'txt'} }
1120
			       (@$encodages)),
1121
# TRANSLATORS: you can omit the [...] part, just here to explain context
1122
    'manuel_image_type'=>cb_model('ppm'=>__p("(none) [No transitional image type (direct processing)]"),
1123
				  'xpm'=>'XPM',
1124
				  'gif'=>'GIF'),
1125
    'liste_key'=>$cb_model_vide_key,
1126
    'assoc_code'=>$cb_model_vide_code,
1127
    'format_export'=>cb_model(map { $_=>"AMC::Export::register::$_"->name() } (@export_modules)),
1128
    'filter'=>cb_model(map { $_=>"AMC::Filter::register::$_"->name() } (@filter_modules)),
1129
# TRANSLATORS: One of the actions that can be done after exporting the marks. Here, do nothing more. This is a menu entry.
1130
    'after_export'=>cb_model(""=>__"that's all",
1131
# TRANSLATORS: One of the actions that can be done after exporting the marks. Here, open the exported file. This is a menu entry.
1132
			     "file"=>__"open the file",
1133
# 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.
1134
			     "dir"=>__"open the directory",
1135
    ),
1136
# TRANSLATORS: you can omit the [...] part, just here to explain context
1137
    'annote_position'=>cb_model("none"=>__p("(none) [No annotation position (do not write anything)]"),
1138
# TRANSLATORS: One of the possible location for questions scores on annotated completed answer sheet: in one margin. This is a menu entry.
1139
				"marge"=>__"in one margin",
1140
# 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.
1141
				"marges"=>__"in the margins",
1142
# TRANSLATORS: One of the possible location for questions scores on annotated completed answer sheet: near the boxes. This is a menu entry.
1143
				"case"=>__"near boxes",
1144
    ),
1145
# TRANSLATORS: One of the possible sorting criteria for students in the exported spreadsheet with scores: the student name. This is a menu entry.
1146
    'export_sort'=>cb_model("n"=>__"name",
1147
# TRANSLATORS: One of the possible sorting criteria for students in the exported spreadsheet with scores: the student sheet number. This is a menu entry.
1148
			    "i"=>__"sheet number",
1149
# 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.
1150
			    "l"=>__"line in students list",
1151
# TRANSLATORS: you can omit the [...] part, just here to explain context
1152
# One of the possible sorting criteria for students in the exported spreadsheet with scores: the student mark. This is a menu entry.
1153
			    "m"=>__p("mark [student mark, for sorting]"),
1154
			    ),
1155
# 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.
1156
    'regroupement_type'=>cb_model('STUDENTS'=>__"One file per student",
1157
# 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.
1158
				  'ALL'=>__"One file for all students",
1159
    ),
1160
# TRANLATORS: For which students do you want to annotate papers? This is a menu entry.
1161
    'regroupement_copies'=>cb_model('ALL'=>__"All students",
1162
# TRANLATORS: For which students do you want to annotate papers? This is a menu entry.
1163
				    'SELECTED'=>__"Selected students",
1164
				    ),
1165
    'auto_capture_mode'=>cb_model(-1=>__"Please select...",
1166
# 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.
1167
				  0=>__"Different answer sheets",
1168
# 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.
1169
				  1=>__"Some answer sheets were photocopied"),
1170
# TRANSLATORS: One of the ways to send mail: use sendmail command. This is a menu entry.
1171
    'email_transport'=>cb_model('sendmail'=>__"sendmail",
1172
# TRANSLATORS: One of the ways to send mail: use a SMTP server. This is a menu entry.
1173
				'SMTP'=>__"SMTP"),
1174
    );
1175
1176
# 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.
1177
my $symbole_type_cb=cb_model("none"=>__"nothing",
1178
# 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.
1179
			     "circle"=>__"circle",
1180
# 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.
1181
			     "mark"=>__"mark",
1182
# 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.
1183
			     "box"=>__"box",
1184
			     );
1185
1186
for my $k (qw/0_0 0_1 1_0 1_1/) {
1187
    $cb_stores{"symbole_".$k."_type"}=$symbole_type_cb;
1188
}
1189
1190
# Add config GUI for export modules...
1191
1192
for my $m (@export_modules) {
1193
  my $x="AMC::Export::register::$m"->build_config_gui(\%w,\%cb_stores);
1194
  if($x) {
1195
    $w{'config_export_module_'.$m}=$x;
1196
    $w{'config_export_modules'}->pack_start($x,0,0,0);
1197
  }
1198
}
1199
1200
### export
1201
1202
sub maj_export {
1203
    my $old_format=$projet{'options'}->{'format_export'};
1204
1205
    valide_options_for_domain('export','',@_);
1206
1207
    if($projet{'options'}->{'_modifie'} =~ /\bexport_sort\b/) {
1208
      $projet{'_report'}->begin_transaction('SMch');
1209
      $projet{'_report'}->variable('grouped_uptodate',-3);
1210
      $projet{'_report'}->end_transaction('SMch');
1211
    }
1212
1213
    debug "Format : ".$projet{'options'}->{'format_export'};
1214
1215
    for(@export_modules) {
1216
      if($w{'config_export_module_'.$_}) {
1217
	if($projet{'options'}->{'format_export'} eq $_) {
1218
	  $w{'config_export_module_'.$_}->show;
1219
	} else {
1220
	  $w{'config_export_module_'.$_}->hide;
1221
	}
1222
      }
1223
    }
1224
1225
    my %hide=("AMC::Export::register::".$projet{'options'}->{'format_export'})
1226
      ->hide();
1227
    for (qw/standard_export_options/) {
1228
      if($hide{$_}) {
1229
	$w{$_}->hide();
1230
      } else {
1231
	$w{$_}->show();
1232
      }
1233
    }
1234
}
1235
1236
sub exporte {
1237
1238
  maj_export();
1239
1240
    my $format=$projet{'options'}->{'format_export'};
1241
    my @options=();
1242
    my $ext="AMC::Export::register::$format"->extension();
1243
    if(!$ext) {
1244
	$ext=lc($format);
1245
    }
1246
    my $type="AMC::Export::register::$format"->type();
1247
    my $code=$projet{'options'}->{'code_examen'};
1248
    $code=$projet{'nom'} if(!$code);
1249
    my $output=absolu('%PROJET/exports/'.$code.$ext);
1250
    my @needs_module=();
1251
1252
    my %ofc="AMC::Export::register::$format"
1253
      ->options_from_config($projet{'options'},\%o,\%o_defaut);
1254
    for(keys %ofc) {
1255
      push @options,"--option-out",$_.'='.$ofc{$_};
1256
    }
1257
    push @needs_module,"AMC::Export::register::$format"->needs_module();
1258
1259
    if(@needs_module) {
1260
	# teste si les modules necessaires sont disponibles
1261
1262
	my @manque=();
1263
1264
	for my $m (@needs_module) {
1265
	    if(!check_install(module=>$m)) {
1266
		push @manque,$m;
1267
	    }
1268
	}
1269
1270
	if(@manque) {
1271
	    debug 'Exporting to '.$format.': Needs perl modules '.join(', ',@manque);
1272
1273
	    my $dialog = Gtk2::MessageDialog
1274
	      ->new($w{'main_window'},
1275
		    'destroy-with-parent',
1276
		    'error','ok',
1277
		    __("Exporting to '%s' needs some perl modules that are not installed: %s. Please install these modules or switch to another export format."),
1278
		    "AMC::Export::register::$format"->name(),join(', ',@manque)
1279
		   );
1280
	    $dialog->run;
1281
	    $dialog->destroy;
1282
1283
	    return();
1284
	}
1285
    }
1286
1287
    commande('commande'=>["auto-multiple-choice","export",
1288
			  pack_args(
1289
				    "--debug",debug_file(),
1290
				    "--module",$format,
1291
				    "--data",absolu($projet{'options'}->{'data'}),
1292
				    "--useall",$projet{'options'}->{'export_include_abs'},
1293
				    "--sort",$projet{'options'}->{'export_sort'},
1294
				    "--fich-noms",absolu($projet{'options'}->{'listeetudiants'}),
1295
				    "--noms-encodage",bon_encodage('liste'),
1296
				    "--csv-build-name",csv_build_name(),
1297
				    ($projet{'options'}->{'annote_rtl'} ? "--rtl" : "--no-rtl"),
1298
				    "--output",$output,
1299
				    @options
1300
				   ),
1301
			 ],
1302
	     'texte'=>__"Exporting marks...",
1303
	     'progres.id'=>'export',
1304
	     'progres.pulse'=>0.01,
1305
	     'fin'=>sub {
1306
	       my $c=shift;
1307
	       if(-f $output) {
1308
		 # shows export messages
1309
1310
		 my $t=$c->higher_message_type();
1311
		 if($t) {
1312
		   my $dialog = Gtk2::MessageDialog
1313
		     ->new($w{'main_window'},
1314
			   'destroy-with-parent',
1315
			   ($t eq 'ERR' ? 'error' : $t eq 'WARN' ? 'warning' : 'info'),
1316
			   'ok',
1317
			   join("\n",$c->get_messages($t))
1318
			   );
1319
		   $dialog->run;
1320
		   $dialog->destroy;
1321
		 }
1322
1323
		 if($projet{'options'}->{'after_export'} eq 'file') {
1324
		   commande_parallele($o{$type.'_viewer'},$output)
1325
		     if($o{$type.'_viewer'});
1326
		 } elsif($projet{'options'}->{'after_export'} eq 'dir') {
1327
		   view_dir(absolu('%PROJET/exports/'));
1328
		 }
1329
	       } else {
1330
		 my $dialog = Gtk2::MessageDialog
1331
		   ->new($w{'main_window'},
1332
			 'destroy-with-parent',
1333
			 'warning','ok',
1334
			 __"Export to %s did not work: file not created...",$output);
1335
		 $dialog->run;
1336
		 $dialog->destroy;
1337
	       }
1338
	     }
1339
	     );
1340
}
1341
1342
## menu contextuel sur liste diagnostique -> visualisation zoom/page
1343
1344
# 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.
1345
my %diag_menu=(page=>{text=>__"page adjustment",icon=>'gtk-zoom-fit'},
1346
# 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.
1347
	       zoom=>{text=>__"boxes zooms",icon=>'gtk-zoom-in'},
1348
	       );
1349
1350
sub zooms_display {
1351
    my ($student,$page,$copy,$forget_it)=@_;
1352
1353
    debug "Zooms view for ".pageids_string($student,$page,$copy)."...";
1354
    my $zd=absolu('%PROJET/cr/zooms');
1355
    debug "Zooms directory $zd";
1356
    if($w{'zooms_window'} &&
1357
       $w{'zooms_window'}->actif) {
1358
	$w{'zooms_window'}->page([$student,$page,$copy],$zd,$forget_it);
1359
    } elsif(!$forget_it) {
1360
	$w{'zooms_window'}=AMC::Gui::Zooms::new('seuil'=>$projet{'options'}->{'seuil'},
1361
						'n_cols'=>$o{'zooms_ncols'},
1362
						'zooms_dir'=>$zd,
1363
						'page_id'=>[$student,$page,$copy],
1364
						'size-prefs',\%o,
1365
						'encodage_interne'=>$o{'encodage_interne'},
1366
						'data'=>$projet{'_capture'},
1367
						'cr-dir'=>absolu($projet{'options'}->{'cr'}),
1368
						'list_view'=>$w{'diag_tree'},
1369
	    );
1370
    }
1371
}
1372
1373
sub zooms_line_base {
1374
  my ($forget_it)=@_;
1375
  my @selected=$w{'diag_tree'}->get_selection->get_selected_rows;
1376
  if(@selected) {
1377
    my $iter=$diag_store->get_iter($selected[0]);
1378
    my $id=$diag_store->get($iter,DIAG_ID);
1379
    zooms_display((map { $diag_store->get($iter,$_) } (DIAG_ID_STUDENT,
1380
						       DIAG_ID_PAGE,
1381
						       DIAG_ID_COPY)
1382
		  ),$forget_it);
1383
  }
1384
}
1385
1386
sub zooms_line { zooms_line_base(1); }
1387
sub zooms_line_open { zooms_line_base(0); }
1388
1389
sub layout_line {
1390
  my @selected=$w{'diag_tree'}->get_selection->get_selected_rows;
1391
  for my $s (@selected) {
1392
    my $iter=$diag_store->get_iter($s);
1393
    my @id=map { $diag_store->get($iter,$_); } 
1394
      (DIAG_ID_STUDENT,DIAG_ID_PAGE,DIAG_ID_COPY);
1395
    $projet{'_capture'}->begin_read_transaction('Layl');
1396
    my $f=absolu($projet{'options'}->{'cr'}).'/'
1397
      .$projet{'_capture'}->get_layout_image(@id);
1398
    $projet{'_capture'}->end_transaction('Layl');
1399
1400
    commande_parallele($o{'img_viewer'},$f) if(-f $f);
1401
  }
1402
}
1403
1404
sub delete_line {
1405
  my @selected=$w{'diag_tree'}->get_selection->get_selected_rows;
1406
  my $f;
1407
  if(@selected) {
1408
    my $dialog = Gtk2::MessageDialog
1409
      ->new_with_markup($w{'main_window'},
1410
			'destroy-with-parent',
1411
			'question','yes-no',
1412
			sprintf((__"You requested to delete all data capture results for %d page(s)"),1+$#selected)."\n"
1413
			.'<b>'.(__"All data and image files related to these pages will be deleted.")."</b>\n"
1414
			.(__"Do you really want to continue?")
1415
		       );
1416
    my $reponse=$dialog->run;
1417
    $dialog->destroy;
1418
    if($reponse eq 'yes') {
1419
      my @iters=();
1420
      $projet{'_capture'}->begin_transaction('rmAN');
1421
      for my $s (@selected) {
1422
	my $iter=$diag_store->get_iter($s);
1423
	my @id=map { $diag_store->get($iter,$_); } 
1424
	  (DIAG_ID_STUDENT,DIAG_ID_PAGE,DIAG_ID_COPY);
1425
	debug "Removing data capture for ".pageids_string(@id);
1426
	#
1427
	# 1) get image files generated, and remove them
1428
	#
1429
	my $crdir=absolu($projet{'options'}->{'cr'});
1430
	my @files=();
1431
	#
1432
	# scan file
1433
	push @files,absolu($projet{'_capture'}->get_scan_page(@id));
1434
	#
1435
	# layout image, in cr directory
1436
	push @files,$crdir.'/'
1437
	  .$projet{'_capture'}->get_layout_image(@id);
1438
	#
1439
	# annotated scan
1440
	push @files,$crdir.'/corrections/jpg/'
1441
	  .$projet{'_capture'}->get_annotated_page(@id);
1442
	#
1443
	# zooms
1444
	push @files,map { $crdir.'/zooms/'.$_ } grep { defined($_) }
1445
	  ($projet{'_capture'}->get_zones_images(@id,ZONE_BOX));
1446
	#
1447
	for (@files) {
1448
	  if (-f $_) {
1449
	    debug "Removing $_";
1450
	    unlink($_);
1451
	  }
1452
	}
1453
	#
1454
	# 2) remove data from database
1455
	#
1456
	$projet{'_capture'}->delete_page_data(@id);
1457
1458
	if($projet{'options'}->{'auto_capture_mode'} == 1) {
1459
	  $projet{'_scoring'}->delete_scoring_data(@id[0,2]);
1460
	  $projet{'_association'}->delete_association_data(@id[0,2]);
1461
	}
1462
1463
	push @iters,$iter;
1464
      }
1465
1466
      for(@iters) { $diag_store->remove($_); }
1467
      update_analysis_summary();
1468
      $projet{'_capture'}->end_transaction('rmAN');
1469
1470
      assoc_state();
1471
    }
1472
  }
1473
}
1474
1475
$w{'diag_tree'}->signal_connect('button_release_event' =>
1476
    sub {
1477
	my ($self, $event) = @_;
1478
	return 0 unless $event->button == 3;
1479
	my ($path, $column, $cell_x, $cell_y) =
1480
	    $w{'diag_tree'}->get_path_at_pos ($event->x, $event->y);
1481
	if ($path) {
1482
	    my $iter=$diag_store->get_iter($path);
1483
	    my $id=[map { $diag_store->get($iter,$_) } (DIAG_ID_STUDENT,
1484
							DIAG_ID_PAGE,
1485
							DIAG_ID_COPY)];
1486
1487
	    my $menu = Gtk2::Menu->new;
1488
	    my $c=0;
1489
	    my @actions=('page');
1490
1491
	    # new zooms viewer
1492
1493
	    $projet{'_capture'}->begin_read_transaction('ZnIm');
1494
	    my @bi=grep { -f absolu('%PROJET/cr/zooms')."/".$_ }
1495
	      $projet{'_capture'}->zone_images($id->[0],$id->[2],ZONE_BOX);
1496
	    $projet{'_capture'}->end_transaction('ZnIm');
1497
1498
	    if(@bi) {
1499
		$c++;
1500
		my $item = Gtk2::ImageMenuItem->new($diag_menu{'zoom'}->{text});
1501
		$item->set_image(Gtk2::Image->new_from_icon_name($diag_menu{'zoom'}->{icon},'menu'));
1502
		$menu->append ($item);
1503
		$item->show;
1504
		$item->signal_connect (activate => sub {
1505
		    my (undef, $sortkey) = @_;
1506
		    zooms_display(@$id);
1507
				       }, $_);
1508
	    } else {
1509
		push  @actions,'zoom';
1510
	    }
1511
1512
	    # page viewer and old zooms viewer
1513
1514
	    foreach $a (@actions) {
1515
	      my $f;
1516
	      if($a eq 'page') {
1517
		$projet{'_capture'}->begin_read_transaction('gLIm');
1518
		$f=absolu($projet{'options'}->{'cr'}).'/'
1519
		  .$projet{'_capture'}->get_layout_image(@$id);
1520
		$projet{'_capture'}->end_transaction('gLIm');
1521
	      } else {
1522
		$f=id2file($id,$a,'jpg');
1523
	      }
1524
	      if(-f $f) {
1525
		$c++;
1526
		my $item = Gtk2::ImageMenuItem->new($diag_menu{$a}->{text});
1527
		$item->set_image(Gtk2::Image->new_from_icon_name($diag_menu{$a}->{icon},'menu'));
1528
		$menu->append ($item);
1529
		$item->show;
1530
		$item->signal_connect (activate => sub {
1531
					 my (undef, $sortkey) = @_;
1532
					 debug "Looking at $f...";
1533
					 commande_parallele($o{'img_viewer'},$f);
1534
				       }, $_);
1535
	      }
1536
	    }
1537
	    $menu->popup (undef, undef, undef, undef,
1538
			  $event->button, $event->time) if($c>0);
1539
	    return 1; # stop propagation!
1540
1541
	}
1542
    });
1543
1544
### Appel a des commandes externes -- log, annulation
1545
1546
my %les_commandes=();
1547
my $cmd_id=0;
1548
1549
sub commande {
1550
    my (@opts)=@_;
1551
    $cmd_id++;
1552
1553
    my $c=AMC::Gui::Commande::new('avancement'=>$w{'avancement'},
1554
				  'log'=>$w{'log_general'},
1555
				  'finw'=>sub {
1556
				      my $c=shift;
1557
				      $w{'onglets_projet'}->set_sensitive(1);
1558
				      $w{'commande'}->hide();
1559
				      delete $les_commandes{$c->{'_cmdid'}};
1560
				  },
1561
				  @opts);
1562
1563
    $c->{'_cmdid'}=$cmd_id;
1564
    $les_commandes{$cmd_id}=$c;
1565
1566
    $w{'onglets_projet'}->set_sensitive(0);
1567
    $w{'commande'}->show();
1568
1569
    $c->open();
1570
}
1571
1572
sub commande_annule {
1573
    for (keys %les_commandes) { $les_commandes{$_}->quitte(); }
1574
}
1575
1576
sub commande_parallele {
1577
    my (@c)=(@_);
1578
    if(commande_accessible($c[0])) {
1579
	my $pid=fork();
1580
	if($pid==0) {
1581
	    debug "Command // [$$] : ".join(" ",@c);
1582
	    exec(@c) ||
1583
		debug "Exec $$ : error";
1584
	    exit(0);
1585
	}
1586
    } else {
1587
	my $dialog = Gtk2::MessageDialog
1588
	    ->new_with_markup($w{'main_window'},
1589
			      'destroy-with-parent',
1590
			      'error','ok',
1591
			      sprintf(__"Following command could not be run: <b>%s</b>, perhaps due to a poor configuration?",$c[0]));
1592
	$dialog->run;
1593
	$dialog->destroy;
1594
1595
    }
1596
}
1597
1598
### Actions des menus
1599
1600
my $proj_store;
1601
1602
sub projet_nouveau {
1603
    liste_des_projets('cree'=>1);
1604
}
1605
1606
sub projet_charge {
1607
    liste_des_projets();
1608
}
1609
1610
sub projet_gestion {
1611
    liste_des_projets('gestion'=>1);
1612
}
1613
1614
sub projects_list {
1615
  # construit la liste des projets existants
1616
  if(-d $w{'local_rep_projets'}) {
1617
    opendir(DIR, $w{'local_rep_projets'})
1618
      || die "Error opening directory ".$w{'local_rep_projets'}." : $!";
1619
    my @f=map { decode("utf-8",$_); } readdir(DIR);
1620
    debug "F:".join(',',map { $_.":".(-d $w{'local_rep_projets'}."/".$_) } @f);
1621
1622
    my @projs = grep { ! /^\./ && -d $w{'local_rep_projets'}."/".$_ } @f;
1623
    closedir DIR;
1624
    debug "[".$w{'local_rep_projets'}."] P:".join(',',@projs);
1625
    return(@projs);
1626
  }
1627
}
1628
1629
sub projects_show {
1630
  my ($p)=@_;
1631
  $p=[projects_list()] if(!$p);
1632
1633
  $w{'projet_bouton_choose_directory'}
1634
    ->set_tooltip_text(sprintf(__("Current directory: %s"),$w{'local_rep_projets'}));
1635
1636
  if(!$w{'icon_project'}) {
1637
    my ($taille,undef)=Gtk2::IconSize->lookup('menu');
1638
    $w{'icon_project'} = Gtk2::IconTheme->get_default->load_icon("auto-multiple-choice",$taille ,"force-svg");
1639
    $w{'icon_project_dir'} = Gtk2::IconTheme->get_default->load_icon("gtk-no",$taille ,"force-svg");
1640
    $w{'icon_project'}=$w{'main_window'}->render_icon ('gtk-open', 'menu')
1641
      if(!$w{'icon_project'});
1642
  }
1643
1644
  $proj_store->clear;
1645
  for (sort { $a cmp $b } @$p) {
1646
    $proj_store->set($proj_store->append,
1647
		     PROJ_NOM,$_,
1648
		     PROJ_ICO,
1649
		     (-f fich_options($_,$w{'local_rep_projets'}) ?
1650
		      $w{'icon_project'} : $w{'icon_project_dir'} ));
1651
  }
1652
}
1653
1654
sub liste_des_projets {
1655
    my %oo=(@_);
1656
1657
    mkdir($o{'rep_projets'}) if(-d $o{'rep_projets'});
1658
1659
    $w{'local_rep_projets'}=$o{'rep_projets'};
1660
1661
    my @projs=projects_list();
1662
1663
    if($#projs>=0 || $oo{'cree'}) {
1664
1665
	# fenetre pour demander le nom du projet
1666
1667
	my $gp=read_glade('choix_projet',
1668
			  qw/label_etat label_action
1669
            choix_projets_liste
1670
	    projet_bouton_ouverture projet_bouton_creation
1671
	    projet_bouton_supprime projet_bouton_annule
1672
	    projet_bouton_annule_label projet_bouton_renomme
1673
	    projet_bouton_clone
1674
	    projet_bouton_mv_yes projet_bouton_mv_no
1675
	    projet_bouton_choose_directory
1676
	    projet_nom projet_nouveau_syntaxe projet_nouveau/);
1677
1678
	if($oo{'cree'}) {
1679
	    $w{'projet_nouveau'}->show();
1680
	    $w{'projet_bouton_creation'}->show();
1681
	    $w{'projet_bouton_ouverture'}->hide();
1682
1683
	    $w{'label_etat'}->set_text(__"Existing projects:");
1684
1685
	    $w{'choix_projet'}->set_focus($w{'projet_nom'});
1686
	    $w{'projet_nom_style'} = $w{'projet_nom'}->get_modifier_style->copy;
1687
1688
# TRANSLATORS: Window title when creating a new project.
1689
	    $w{'choix_projet'}->set_title(__"New AMC project");
1690
	}
1691
1692
1693
	if($oo{'gestion'}) {
1694
	    $w{'label_etat'}->set_text(__"Projects management:");
1695
	    $w{'label_action'}->set_markup(__"Change project name:");
1696
	    $w{'projet_bouton_ouverture'}->hide();
1697
	    for (qw/supprime clone renomme/) {
1698
		$w{'projet_bouton_'.$_}->show();
1699
	    }
1700
	    $w{'projet_bouton_annule_label'}->set_text(__"Back");
1701
1702
# TRANSLATORS: Window title when managing projects.
1703
	    $w{'choix_projet'}->set_title(__"AMC projects management");
1704
	}
1705
1706
	# mise a jour liste des projets dans la fenetre
1707
1708
	$proj_store = Gtk2::ListStore->new ('Glib::String',
1709
					    'Gtk2::Gdk::Pixbuf');
1710
1711
	$w{'choix_projets_liste'}->set_model($proj_store);
1712
1713
	$w{'choix_projets_liste'}->set_text_column(PROJ_NOM);
1714
	$w{'choix_projets_liste'}->set_pixbuf_column(PROJ_ICO);
1715
1716
	projects_show(\@projs);
1717
1718
	# attendons l'action de l'utilisateur (fonctions projet_charge_*)...
1719
1720
	$w{'choix_projet'}->set_keep_above(1);
1721
1722
    } else {
1723
	my $dialog = Gtk2::MessageDialog
1724
	    ->new($w{'main_window'},
1725
		  'destroy-with-parent',
1726
		  'info','ok',
1727
		  __"You don't have any MC project in directory %s!",$o{'rep_projets'});
1728
	$dialog->run;
1729
	$dialog->destroy;
1730
1731
    }
1732
}
1733
1734
sub project_choose_directory {
1735
  my $d=Gtk2::FileChooserDialog
1736
    ->new(__("Choose directory"),
1737
	  $w{'choix_projet'},'select-folder',
1738
	  'gtk-cancel'=>'cancel',
1739
	  'gtk-ok'=>'ok');
1740
  $d->set_current_folder($w{'local_rep_projets'});
1741
  my $r=$d->run;
1742
  if($r eq 'ok') {
1743
    my $rep=$d->get_filename || $d->get_current_directory;
1744
    if(-d $rep) {
1745
      $w{'local_rep_projets'}=$rep;
1746
    }
1747
  }
1748
  $d->destroy();
1749
1750
  if($r eq 'ok') {
1751
    projects_show();
1752
  }
1753
}
1754
1755
sub projet_gestion_check {
1756
  my ($open_ok)=@_;
1757
1758
    # lequel ?
1759
1760
    my $sel=$w{'choix_projets_liste'}->get_selected_items();
1761
    my $iter;
1762
    my $proj;
1763
1764
    if($sel) {
1765
	$iter=$proj_store->get_iter($sel);
1766
	$proj=$proj_store->get($iter,PROJ_NOM) if($iter);
1767
    }
1768
1769
    return('','') if(!$proj);
1770
1771
  return($proj,$iter) if($open_ok);
1772
1773
    # est-ce le projet en cours ?
1774
1775
    if($projet{'nom'} && $proj eq $projet{'nom'}) {
1776
	my $dialog = Gtk2::MessageDialog
1777
	    ->new($w{'choix_projet'},
1778
		  'destroy-with-parent',
1779
		  'error','ok',
1780
		  __"You can't change project %s since it's open.",$proj);
1781
	$dialog->run;
1782
	$dialog->destroy;
1783
	$w{'choix_projet'}->set_keep_above(1);
1784
	$proj='';
1785
    }
1786
1787
    return($proj,$iter);
1788
}
1789
1790
sub projet_liste_clone {
1791
  my ($proj,$iter)=projet_gestion_check(1);
1792
  return if(!$proj);
1793
1794
  my $proj_clone=new_filename(absolu($w{'local_rep_projets'}).'/'.$proj);
1795
  my (undef,undef,$proj_c) = splitpath($proj_clone);
1796
1797
  my $dialog = Gtk2::MessageDialog
1798
    ->new_with_markup($w{'choix_projet'},
1799
		      'destroy-with-parent',
1800
		      'warning','ok-cancel',
1801
		      sprintf(__("This will clone project <b>%s</b> to a new project <i>%s</i>."),
1802
			      $proj,$proj_c));
1803
  my $r=$dialog->run;
1804
  $dialog->destroy;
1805
  if($r eq 'ok') {
1806
    project_copy($w{'local_rep_projets'}.'/'.$proj,
1807
		 $proj_clone,$w{'choix_projet'});
1808
    projects_show();
1809
  }
1810
}
1811
1812
my $nom_original='';
1813
my $nom_original_iter='';
1814
1815
sub projet_liste_renomme {
1816
    my ($proj,$iter)=projet_gestion_check();
1817
    return if(!$proj);
1818
1819
    # ouverture zone :
1820
    $w{'projet_nouveau'}->show();
1821
    $w{'projet_nom'}->set_text($proj);
1822
1823
    $nom_original=$proj;
1824
    $nom_original_iter=$iter;
1825
1826
    # boutons...
1827
    for (qw/annule renomme supprime/) {
1828
	$w{'projet_bouton_'.$_}->hide();
1829
    }
1830
    for (qw/mv_no mv_yes/) {
1831
	$w{'projet_bouton_'.$_}->show();
1832
    }
1833
}
1834
1835
sub projet_renomme_fin {
1836
    # fermeture zone :
1837
    $w{'projet_nouveau'}->hide();
1838
1839
    # boutons...
1840
    for (qw/annule renomme supprime/) {
1841
	$w{'projet_bouton_'.$_}->show();
1842
    }
1843
    for (qw/mv_no mv_yes/) {
1844
	$w{'projet_bouton_'.$_}->hide();
1845
    }
1846
}
1847
1848
sub projet_mv_yes {
1849
    projet_renomme_fin();
1850
1851
    my $nom_nouveau=$w{'projet_nom'}->get_text();
1852
1853
    return if($nom_nouveau eq $nom_original || !$nom_nouveau);
1854
1855
    if($w{'local_rep_projets'}) {
1856
	my $dir_original=$w{'local_rep_projets'}."/".$nom_original;
1857
	if(-d $dir_original) {
1858
	    my $dir_nouveau=$w{'local_rep_projets'}."/".$nom_nouveau;
1859
	    if(-d $dir_nouveau) {
1860
		$w{'choix_projet'}->set_keep_above(0);
1861
		my $dialog = Gtk2::MessageDialog
1862
		    ->new_with_markup($w{'main_window'},
1863
				      'destroy-with-parent',
1864
				      'error','ok',
1865
# 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!
1866
				      sprintf(__("Directory <i>%s</i> already exists, so you can't choose this name."),$dir_nouveau));
1867
		$dialog->run;
1868
		$dialog->destroy;
1869
		$w{'choix_projet'}->set_keep_above(1);
1870
1871
		return;
1872
	    } else {
1873
		# OK
1874
1875
		move($dir_original,$dir_nouveau);
1876
1877
		$proj_store->set($nom_original_iter,
1878
				 PROJ_NOM,$nom_nouveau,
1879
				 );
1880
	    }
1881
	} else {
1882
	    debug "No original directory";
1883
	}
1884
    } else {
1885
	debug "No projects directory";
1886
    }
1887
}
1888
1889
sub projet_mv_no {
1890
    projet_renomme_fin();
1891
}
1892
1893
my @find_results=();
1894
1895
sub add_to_results {
1896
  push @find_results,$File::Find::name;
1897
}
1898
1899
sub project_copy {
1900
  my ($src,$dest,$parent_window)=@_;
1901
  $parent_window=$w{'main_window'} if(!$parent_window);
1902
  my $err='';
1903
  if(!$err && -e $dest) {
1904
    $err=__("Destination project directory already exists");
1905
  }
1906
  if(!$err) {
1907
    @find_results=();
1908
    find({wanted=>\&add_to_results,no_chdir=>1},
1909
	 $src);
1910
    my $total=1+$#find_results;
1911
    if($total>0) {
1912
      my $done=0;
1913
      my $i=0;
1914
      my $last_fraction=0;
1915
      my $old_text=$w{'avancement'}->get_text();
1916
      $w{'avancement'}->set_text(__"Copying project...");
1917
      $w{'avancement'}->set_fraction(0);
1918
      $w{'commande'}->show();
1919
      for my $s (@find_results) {
1920
	my $d=$s;
1921
	$d =~ s:^$src:$dest:;
1922
	if(-l $s) {
1923
	  $done+=symlink readlink($s),$d;
1924
	} elsif(-d $s) {
1925
	  $done++ if(mkdir($d));
1926
	} else {
1927
	  $done++ if(copy($s,$d));
1928
	}
1929
	$i++;
1930
	if($i/$total-$last_fraction>1/40) {
1931
	  $last_fraction=$i/$total;
1932
	  $w{'avancement'}->set_fraction($last_fraction);
1933
	  Gtk2->main_iteration while ( Gtk2->events_pending );
1934
	}
1935
      }
1936
      $w{'avancement'}->set_text($old_text);
1937
      $w{'commande'}->hide();
1938
1939
      my $dialog = Gtk2::MessageDialog
1940
	->new($parent_window,
1941
	      'destroy-with-parent',
1942
	      'info','ok',
1943
	      __("Your project has been copied").
1944
	      ($done!=$total ? " ".sprintf(__("(%d files out of %d)"),
1945
					   $done,$total) : "")
1946
	      ."."
1947
	     );
1948
      $dialog->run;
1949
      $dialog->destroy;
1950
    } else {
1951
      $err=__("Source project directory not found");
1952
    }
1953
  }
1954
  if($err) {
1955
    my $dialog = Gtk2::MessageDialog
1956
      ->new($parent_window,
1957
	    'destroy-with-parent',
1958
	    'error','ok',
1959
	    __("An error occuried during project copy: %s."),
1960
	    $err
1961
	   );
1962
    $dialog->run;
1963
    $dialog->destroy;
1964
    return(0);
1965
  }
1966
  return(1);
1967
}
1968
1969
sub projet_liste_supprime {
1970
    my ($proj,$iter)=projet_gestion_check();
1971
    return if(!$proj);
1972
1973
    # on demande confirmation...
1974
    $w{'choix_projet'}->set_keep_above(0);
1975
    my $dialog = Gtk2::MessageDialog
1976
	->new_with_markup($w{'main_window'},
1977
			  'destroy-with-parent',
1978
			  'warning','ok-cancel',
1979
			  sprintf(__("You asked to remove project <b>%s</b>.")." "
1980
				  .__("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.")." "
1981
				  .__("Is this really what you want?"),$proj));
1982
    my $reponse=$dialog->run;
1983
    $dialog->destroy;
1984
    $w{'choix_projet'}->set_keep_above(1);
1985
1986
    if($reponse ne 'ok') {
1987
	return;
1988
    }
1989
1990
    debug "Removing project $proj !";
1991
1992
    $proj_store->remove($iter);
1993
1994
    # suppression effective des fichiers...
1995
1996
    if($w{'local_rep_projets'}) {
1997
	my $dir=$w{'local_rep_projets'}."/".$proj;
1998
	if(-d $dir) {
1999
	    remove_tree($dir,{'verbose'=>0,'safe'=>1,'keep_root'=>0});
2000
	} else {
2001
	    debug "No directory $dir";
2002
	}
2003
    } else {
2004
	debug "No projects directory";
2005
    }
2006
}
2007
2008
sub projet_charge_ok {
2009
2010
    # ouverture projet deja existant
2011
2012
    my $sel=$w{'choix_projets_liste'}->get_selected_items();
2013
    my $proj;
2014
2015
    if($sel) {
2016
	$proj=$proj_store->get($proj_store->get_iter($sel),PROJ_NOM);
2017
    }
2018
2019
    $w{'choix_projet'}->destroy();
2020
2021
    if($proj) {
2022
	my $reponse='yes';
2023
	if(! -f fich_options($proj,$w{'local_rep_projets'})) {
2024
	    my $dialog = Gtk2::MessageDialog
2025
		->new_with_markup($w{'main_window'},
2026
				  'destroy-with-parent',
2027
				  'warning','yes-no',
2028
				  sprintf(__("You selected directory <b>%s</b> as a project to open.")." "
2029
					  .__("However, this directory does not seem to contain a project. Dou you still want to try?"),$proj));
2030
	    $reponse=$dialog->run;
2031
	    $dialog->destroy;
2032
	}
2033
	if($reponse eq 'yes') {
2034
	  # If the project to open lies on a removable media, suggest
2035
	  # to copy it first to the user standard projects directory:
2036
2037
	  if($w{'local_rep_projets'} =~ /^\/media\//
2038
	    && absolu($o{'projects_home'}) !~ /^\/media\//) {
2039
	    my $dialog = Gtk2::MessageDialog
2040
		->new_with_markup($w{'main_window'},
2041
				  'destroy-with-parent',
2042
				  'warning','yes-no',
2043
				  sprintf(__("You selected project <b>%s</b> from directory <i>%s</i>.")." "
2044
					  .__("Do you want to copy this project to your projects directory before opening it?"),
2045
					  $proj,$w{'local_rep_projets'}));
2046
	    my $r=$dialog->run;
2047
	    $dialog->destroy;
2048
	    if($r eq 'yes') {
2049
	      my $proj_dest=new_filename(absolu($o{'projects_home'}).'/'.$proj);
2050
	      if(project_copy($w{'local_rep_projets'}.'/'.$proj,
2051
			      $proj_dest)) {
2052
		(undef,undef,$proj_dest) = splitpath($proj_dest);
2053
		$w{'local_rep_projets'}=absolu($o{'projects_home'});
2054
		$proj=$proj_dest;
2055
	      }
2056
	    }
2057
	  }
2058
2059
	  # OK, now, open the project!
2060
2061
	  $o{'rep_projets'}=$w{'local_rep_projets'};
2062
	  projet_ouvre($proj) ;
2063
	}
2064
    }
2065
}
2066
2067
sub restricted_check {
2068
    my ($text,$style,$warning,$chars)=@_;
2069
    my $nom=$text->get_text();
2070
    if($nom =~ s/[^$chars]//g) {
2071
	$text->set_text($nom);
2072
	$warning->show();
2073
2074
	for(qw/normal active/) {
2075
	    $text->modify_base($_,Gtk2::Gdk::Color->parse('#FFC0C0'));
2076
	}
2077
	Glib::Timeout->add (500, sub {
2078
	    $text->modify_style($style);
2079
	    return 0;
2080
	});
2081
    }
2082
}
2083
2084
sub projet_nom_verif {
2085
    restricted_check($w{'projet_nom'},$w{'projet_nom_style'},$w{'projet_nouveau_syntaxe'},"a-zA-Z0-9._+:-");
2086
}
2087
2088
sub projet_charge_nouveau {
2089
2090
    # creation nouveau projet
2091
2092
    my $proj=$w{'projet_nom'}->get_text();
2093
    $w{'choix_projet'}->destroy();
2094
2095
    # existe deja ?
2096
2097
    if(-e $w{'local_rep_projets'}."/$proj") {
2098
2099
	my $dialog = Gtk2::MessageDialog
2100
	    ->new_with_markup($w{'main_window'},
2101
			      'destroy-with-parent',
2102
			      'error','ok',
2103
			      sprintf(__("The name <b>%s</b> is already used in the projects directory.")." "
2104
				      .__"You must choose another name to create a project.",$proj));
2105
	$dialog->run;
2106
	$dialog->destroy;
2107
2108
2109
    } else {
2110
2111
      $o{'rep_projets'}=$w{'local_rep_projets'};
2112
      if(projet_ouvre($proj,1)) {
2113
	projet_sauve();
2114
      }
2115
2116
    }
2117
}
2118
2119
sub projet_charge_non {
2120
    $w{'choix_projet'}->destroy();
2121
}
2122
2123
sub projet_sauve {
2124
    debug "Saving project...";
2125
    my $of=fich_options($projet{'nom'});
2126
    my $po={%{$projet{'options'}}};
2127
2128
    for(qw/listeetudiants/) {
2129
	$po->{$_}=relatif($po->{$_});
2130
    }
2131
2132
    if(pref_xx_ecrit($po,'projetAMC',$of)) {
2133
	my $dialog = Gtk2::MessageDialog
2134
	    ->new($w{'main_window'},
2135
		  'destroy-with-parent',
2136
		  'error','ok',
2137
		  __"Error writing to options file %s: %s",$of,$!);
2138
	$dialog->run;
2139
	$dialog->destroy;
2140
    } else {
2141
	$projet{'options'}->{'_modifie'}=0;
2142
	$projet{'options'}->{'_modifie_ok'}=0;
2143
    }
2144
}
2145
2146
sub projet_check_and_save {
2147
    if($projet{'nom'}) {
2148
	valide_options_notation();
2149
	my ($m,$mo)=sub_modif($projet{'options'});
2150
	if($m || $mo) {
2151
	    projet_sauve();
2152
	}
2153
    }
2154
}
2155
2156
### Actions des boutons de la partie DOCUMENTS
2157
2158
sub format_markup {
2159
  my ($t)=@_;
2160
  $t =~ s/\&/\&amp;/g;
2161
  return($t);
2162
}
2163
2164
sub mini {($_[0]<$_[1] ? $_[0] : $_[1])}
2165
2166
my %component_name=('latex_packages'=>__("LaTeX packages:"),
2167
		    'commands'=>__("Commands:"),
2168
		    'fonts'=>__("Fonts:"),
2169
		    );
2170
2171
sub set_project_option {
2172
  my ($name,$value)=@_;
2173
  my $old=$projet{'options'}->{$name};
2174
  $projet{'options'}->{$name}=$value;
2175
  $projet{'options'}->{'_modifie'}.=','.$name if($value ne $old);
2176
}
2177
2178
2179
sub doc_maj {
2180
    my $sur=0;
2181
    if($projet{'_capture'}->n_pages_transaction()>0) {
2182
	my $dialog = Gtk2::MessageDialog
2183
	    ->new_with_markup($w{'main_window'},
2184
			      'destroy-with-parent',
2185
			      'warning','ok-cancel',
2186
			      __("Papers analysis was already made on the basis of the current working documents.")." "
2187
			      .__("You already made the examination on the basis of these documents.")." "
2188
			      .__("If you modify working documents, you will not be capable any more of analyzing the papers you have already distributed!")." "
2189
			      .__("Do you wish to continue?")." "
2190
			      .__("Click on Validate to erase the former layouts and update working documents, or on Cancel to cancel this operation.")." "
2191
			      ."<b>".__("To allow the use of an already printed question, cancel!")."</b>");
2192
	my $reponse=$dialog->run;
2193
	$dialog->destroy;
2194
2195
	if($reponse eq 'cancel') {
2196
	    return(0);
2197
	}
2198
2199
	$sur=1;
2200
    }
2201
2202
    # deja des MEP fabriquees ?
2203
    $projet{_layout}->begin_transaction('DMAJ');
2204
    my $pc=$projet{_layout}->pages_count;
2205
    $projet{_layout}->end_transaction('DMAJ');
2206
    if($pc > 0) {
2207
	if(!$sur) {
2208
	    my $dialog = Gtk2::MessageDialog
2209
		->new_with_markup($w{'main_window'},
2210
				  'destroy-with-parent',
2211
				  'question','ok-cancel',
2212
				  __("Layouts are already calculated for the current documents.")." "
2213
				  .__("Updating working documents, the layouts will become obsolete and will thus be erased.")." "
2214
				  .__("Do you wish to continue?")." "
2215
				  .__("Click on Validate to erase the former layouts and update working documents, or on Cancel to cancel this operation.")
2216
				  ." <b>".__("To allow the use of an already printed question, cancel!")."</b>");
2217
	    my $reponse=$dialog->run;
2218
	    $dialog->destroy;
2219
2220
	    if($reponse eq 'cancel') {
2221
		return(0);
2222
	    }
2223
	}
2224
2225
	clear_processing('mep:');
2226
    }
2227
2228
    # new layout document : XY (from LaTeX)
2229
2230
    if($projet{'options'}->{'docs'}->[2] =~ /\.pdf$/) {
2231
	$projet{'options'}->{'docs'}->[2]=$projet_defaut{'docs'}->[2];
2232
	$projet{'options'}->{'_modifie'}=1;
2233
    }
2234
2235
    # check for filter dependencies
2236
2237
    my $filter_register=("AMC::Filter::register::".$projet{'options'}->{'filter'})
2238
      ->new();
2239
2240
    my $check=$filter_register->check_dependencies();
2241
2242
    if(!$check->{'ok'}) {
2243
      my $message=sprintf(__("To handle properly <i>%s</i> files, AMC needs the following components, that are currently missing:"),$filter_register->name())."\n";
2244
      for my $k (qw/latex_packages commands fonts/) {
2245
	if(@{$check->{$k}}) {
2246
	  $message .= "<b>".$component_name{$k}."</b> ";
2247
	  if($k eq 'fonts') {
2248
	    $message.=join(', ',map { @{$_->{'family'}} } @{$check->{$k}});
2249
	  } else {
2250
	    $message.=join(', ',@{$check->{$k}});
2251
	  }
2252
	  $message.="\n";
2253
	}
2254
      }
2255
      $message.=__("Install these components on your system and try again.");
2256
2257
      my $dialog = Gtk2::MessageDialog
2258
	->new_with_markup($w{'main_window'},
2259
			  'destroy-with-parent',
2260
			  'error','ok',$message);
2261
      $dialog->run;
2262
      $dialog->destroy;
2263
2264
      return(0);
2265
    }
2266
2267
    # set options from filter:
2268
2269
    if($projet{'options'}->{'filter'}) {
2270
      $filter_register->set_oo($projet{'options'});
2271
      $filter_register->configure();
2272
    }
2273
2274
    #
2275
    commande('commande'=>["auto-multiple-choice","prepare",
2276
			  "--with",moteur_latex(),
2277
			  "--filter",$projet{'options'}->{'filter'},
2278
			  "--filtered-source",absolu($projet{'options'}->{'filtered_source'}),
2279
			  "--debug",debug_file(),
2280
			  "--out-sujet",absolu($projet{'options'}->{'docs'}->[0]),
2281
			  "--out-corrige",absolu($projet{'options'}->{'docs'}->[1]),
2282
			  "--out-calage",absolu($projet{'options'}->{'docs'}->[2]),
2283
			  "--mode","s",
2284
			  "--n-copies",$projet{'options'}->{'nombre_copies'},
2285
			  absolu($projet{'options'}->{'texsrc'}),
2286
			  "--prefix",absolu('%PROJET/'),
2287
			  "--latex-stdout",
2288
			  ],
2289
	     'signal'=>2,
2290
	     'texte'=>__"Documents update...",
2291
	     'progres.id'=>'MAJ',
2292
	     'progres.pulse'=>0.01,
2293
	     'fin'=>sub {
2294
		 my $c=shift;
2295
		 my @err=$c->erreurs();
2296
		 if(@err) {
2297
		   my $message=__("Errors while processing the source file.")
2298
		     ." "
2299
		       .__("You have to correct the source file and re-run documents update.")." "
2300
# 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.
2301
			 .sprintf(__("See the processing log in '%s' below."),
2302
# 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.
2303
				  __"Command output details");
2304
		   $message.=" ".__("Use LaTeX editor or latex command for a precise diagnosis.") if($projet{'options'}->{'filter'} eq 'latex');
2305
		   $message.="\n\n".join("\n",map { format_markup($_) } (@err[0..mini(9,$#err)])).($#err>9 ? "\n\n<i>(".__("Only first ten errors written").")</i>": "");
2306
2307
		     my $dialog = Gtk2::MessageDialog
2308
			 ->new_with_markup($w{'main_window'},
2309
					   'destroy-with-parent',
2310
					   'error','ok',
2311
					   $message);
2312
		     $dialog->run;
2313
		     $dialog->destroy;
2314
		 } else {
2315
		     # verif que tout y est
2316
2317
		     my $ok=1;
2318
		     for(0..2) {
2319
			 $ok=0 if(! -f absolu($projet{'options'}->{'docs'}->[$_]));
2320
		     }
2321
		     if($ok) {
2322
2323
		       # set project option from filter requests
2324
2325
		       my %vars=$c->variables;
2326
		       for my $k (keys %vars) {
2327
			 if($k =~ /^project:(.*)/) {
2328
			   set_project_option($1,$vars{$k});
2329
			 }
2330
		       }
2331
2332
		       # success message
2333
2334
		       dialogue_apprentissage('MAJ_DOCS_OK','','',0,
2335
					      __("Working documents successfully generated.")." "
2336
# TRANSLATORS: Here, "them" refers to the working documents.
2337
					      .__("You can take a look at them double-clicking on the list.")." "
2338
# TRANSLATORS: Here, "they" refers to the working documents.
2339
					      .__("If they are correct, proceed to layouts detection..."));
2340
		     }
2341
		 }
2342
2343
		 my $ap=($c->variable('ensemble') ? 'case' : 'marges');
2344
		 $projet{'options'}->{'_modifie'}=1
2345
		     if($projet{'options'}->{'annote_position'} ne $ap);
2346
		 $projet{'options'}->{'annote_position'}=$ap;
2347
2348
		 my $ensemble=$c->variable('ensemble') && !$c->variable('outsidebox');
2349
		 if(($ensemble  || $c->variable('insidebox'))
2350
		    && $projet{'options'}->{'seuil'}<0.4) {
2351
		     my $dialog = Gtk2::MessageDialog
2352
			 ->new_with_markup($w{'main_window'},
2353
					   'destroy-with-parent',
2354
					   'question','yes-no',
2355
					   sprintf(($ensemble ?
2356
						    __("Your question has a separate answers page.")." "
2357
						    .__("In this case, letters are shown inside boxes.") :
2358
						   __("Your question is set to present labels inside the boxes to be ticked."))
2359
						   ." "
2360
# TRANSLATORS: Here, %s will be replaced with the translation of "darkness threshold".
2361
						   .__("For better ticking detection, ask students to fill out completely boxes, and choose parameter \"%s\" around 0.5 for this project.")." "
2362
						   .__("At the moment, this parameter is set to %.02f.")." "
2363
						   .__("Would you like to set it to 0.5?")
2364
# 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.
2365
						   ,__"darkness threshold",
2366
						   $projet{'options'}->{'seuil'}) );
2367
		     my $reponse=$dialog->run;
2368
		     $dialog->destroy;
2369
		     if($reponse eq 'yes') {
2370
			 $projet{'options'}->{'seuil'}=0.5;
2371
			 $projet{'options'}->{'_modifie'}=1;
2372
		     }
2373
		 }
2374
		 detecte_documents();
2375
	     });
2376
2377
}
2378
2379
sub filter_details {
2380
  my $gd=read_glade('filter_details',
2381
		    qw/filter_text/);
2382
  debug "Filter details: conf->details GUI";
2383
  transmet_pref($gd,'filter_details',$projet{'options'});
2384
  my $r=$w{'filter_details'}->run();
2385
  if($r == 10) {
2386
    my %oo=('filter'=>'');
2387
    debug "Filter details: new value->local";
2388
    reprend_pref('filter_details',\%oo);
2389
    $w{'filter_details'}->destroy;
2390
    debug "Filter details: local->main GUI";
2391
    transmet_pref($gui,'pref_prep',\%oo);
2392
  } else {
2393
    $w{'filter_details'}->destroy;
2394
  }
2395
}
2396
2397
sub filter_details_update {
2398
  my %oo=('filter'=>'');
2399
  reprend_pref('filter_details',\%oo);
2400
  my $b=$w{'filter_text'}->get_buffer;
2401
  if($oo{'filter'}) {
2402
    $b->set_text(("AMC::Filter::register::".$oo{'filter'})->description);
2403
  } else {
2404
    $b->set_text('');
2405
  }
2406
}
2407
2408
my $cups;
2409
my $g_imprime;
2410
2411
sub nonnul {
2412
    my $s=shift;
2413
    $s =~ s/\000//g;
2414
    return($s);
2415
}
2416
2417
sub autre_imprimante {
2418
    my $i=$w{'imprimante'}->get_model->get($w{'imprimante'}->get_active_iter,COMBO_ID);
2419
    debug "Choix imprimante $i";
2420
    my $ppd=$cups->getPPD($i);
2421
2422
    my %alias=();
2423
    my %trouve=();
2424
2425
    debug "Looking for staple option...";
2426
2427
  CHOIX: for my $i (qw/StapleLocation/) {
2428
      my $oi=$ppd->getOption($i);
2429
2430
      $alias{$i}='agrafe';
2431
2432
      if(%$oi) {
2433
	  my $k=nonnul($oi->{'keyword'});
2434
	  debug "$i -> KEYWORD $k";
2435
	  my $ok=$o{'options_impression'}->{$k};
2436
	  my @possibilites=(map { (nonnul($_->{'choice'}),
2437
				   nonnul($_->{'text'})) }
2438
			    (@{$oi->{'choices'}}));
2439
	  my %ph=(@possibilites);
2440
	  $cb_stores{'agrafe'}=cb_model(@possibilites);
2441
	  if(!$ok || !$ph{$ok}) {
2442
	    debug "Unknown stapling option $ok" if($ok);
2443
	    debug "Setting stapling option to default: "
2444
	      .nonnul($oi->{'defchoice'});
2445
	    $o{'options_impression'}->{$k}=nonnul($oi->{'defchoice'});
2446
	  }
2447
2448
	  $alias{$k}='agrafe';
2449
	  $trouve{'agrafe'}=$k;
2450
2451
	  last CHOIX;
2452
      }
2453
  }
2454
    if(!$trouve{'agrafe'}) {
2455
	debug "No possible staple";
2456
2457
# TRANSLATORS: There is a menu for choosing stapling or not from the printer. When stapling is not available on the chosen printer, this is the only offered choice.
2458
	$cb_stores{'agrafe'}=cb_model(''=>__"(not supported)");
2459
	$w{'imp_c_agrafe'}->set_model($cb_stores{'agrafe'});
2460
    }
2461
2462
    transmet_pref($g_imprime,'imp',$o{'options_impression'},
2463
		  \%alias);
2464
}
2465
2466
sub sujet_impressions {
2467
2468
    if(! -f absolu($projet{'options'}->{'docs'}->[0])) {
2469
	my $dialog = Gtk2::MessageDialog
2470
	    ->new_with_markup($w{'main_window'},
2471
			      'destroy-with-parent',
2472
			      'error','ok',
2473
# TRANSLATORS: Message when the user required printing the question paper, but it is not present (probably the working documents have not been properly generated).
2474
			      __"You don't have any question to print: please check your source file and update working documents first.");
2475
	$dialog->run;
2476
	$dialog->destroy;
2477
2478
	return();
2479
    }
2480
2481
    $projet{'_layout'}->begin_read_transaction('PGCN');
2482
    my $c=$projet{'_layout'}->pages_count;
2483
    $projet{'_layout'}->end_transaction('PGCN');
2484
    if($c==0) {
2485
	my $dialog = Gtk2::MessageDialog
2486
	    ->new_with_markup($w{'main_window'},
2487
			      'destroy-with-parent',
2488
			      'error','ok',
2489
# 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.
2490
			      __("Question's pages are not detected.")." "
2491
			      .__"Perhaps you forgot to compute layouts?");
2492
	$dialog->run;
2493
	$dialog->destroy;
2494
2495
	return();
2496
    }
2497
2498
    debug "Choosing pages to print...";
2499
2500
    if($o{'methode_impression'} eq 'CUPS') {
2501
	# teste si le paquet Net::CUPS est disponible
2502
2503
	my @manque=();
2504
2505
	for my $m ("Net::CUPS","Net::CUPS::PPD") {
2506
	    if(check_install(module=>$m)) {
2507
		load($m);
2508
	    } else {
2509
		push @manque,$m;
2510
	    }
2511
	}
2512
2513
	if(@manque) {
2514
	    debug 'Printing with CUPS: Needs perl modules '.join(', ',@manque);
2515
2516
	    my $dialog = Gtk2::MessageDialog
2517
		->new($w{'main_window'},
2518
		      'destroy-with-parent',
2519
		      'error','ok',
2520
		      __("Printing with method '%s' needs some perl modules that are not installed: %s. Please install these modules or switch to another printing method."),
2521
		      __"CUPS",join(', ',@manque)
2522
		      );
2523
	    $dialog->run;
2524
	    $dialog->destroy;
2525
2526
	    return();
2527
	}
2528
2529
	# verifie aussi qu'il y a au moins une imprimante configuree
2530
2531
	my $sc=Net::CUPS->new();
2532
	my @pl = $sc->getDestinations();
2533
2534
	if(!@pl) {
2535
	    my $dialog = Gtk2::MessageDialog
2536
		->new($w{'main_window'},
2537
		      'destroy-with-parent',
2538
		      'error','ok',
2539
		      __("You chose printing method '%s' but there are no configured printer in CUPS. Please configure some printer or switch to another printing method."),
2540
		      __"CUPS"
2541
		      );
2542
	    $dialog->run;
2543
	    $dialog->destroy;
2544
2545
	    return();
2546
	}
2547
    }
2548
2549
    $g_imprime=read_glade('choix_pages_impression',
2550
			  qw/arbre_choix_copies bloc_imprimante imprimante imp_c_agrafe bloc_fichier/);
2551
2552
    transmet_pref($g_imprime,'impall',$o{'options_impression'});
2553
2554
    if($o{'methode_impression'} eq 'CUPS') {
2555
	$w{'bloc_imprimante'}->show();
2556
2557
	$cups=Net::CUPS->new();
2558
2559
	# les imprimantes :
2560
2561
	my @printers = $cups->getDestinations();
2562
	debug "Printers : ".join(' ',map { $_->getName() } @printers);
2563
	my $p_model=cb_model(map { ($_->getName(),$_->getDescription() || $_->getName()) } @printers);
2564
	$w{'imprimante'}->set_model($p_model);
2565
	if(! $o{'imprimante'}) {
2566
	    my $defaut=$cups->getDestination();
2567
	    if($defaut) {
2568
		$o{'imprimante'}=$defaut->getName();
2569
	    } else {
2570
		$o{'imprimante'}=$printers[0]->getName();
2571
	    }
2572
	}
2573
	my $i=model_id_to_iter($p_model,COMBO_ID,$o{'imprimante'});
2574
	if($i) {
2575
	  $w{'imprimante'}->set_active_iter($i);
2576
	  # (this will call autre_imprimante and transmet_pref with
2577
	  # the right options)
2578
	} else {
2579
	  # updates the values in the GUI from the general options
2580
	  transmet_pref($g_imprime,'imp',$o{'options_impression'});
2581
	}
2582
    }
2583
2584
    if($o{'methode_impression'} eq 'file') {
2585
	$w{'bloc_imprimante'}->hide();
2586
	$w{'bloc_fichier'}->show();
2587
2588
	transmet_pref($g_imprime,'impf',$o{'options_impression'});
2589
    }
2590
2591
    $copies_store->clear();
2592
    $projet{'_layout'}->begin_read_transaction('PRNT');
2593
    for my $c ($projet{'_layout'}->students()) {
2594
	$copies_store->set($copies_store->append(),COPIE_N,$c);
2595
    }
2596
    $projet{'_layout'}->end_transaction('PRNT');
2597
2598
    $w{'arbre_choix_copies'}->set_model($copies_store);
2599
2600
    my $renderer=Gtk2::CellRendererText->new;
2601
# 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.
2602
    my $column = Gtk2::TreeViewColumn->new_with_attributes (__"papers",
2603
							    $renderer,
2604
							    text=> COPIE_N );
2605
    $w{'arbre_choix_copies'}->append_column ($column);
2606
2607
    $w{'arbre_choix_copies'}->get_selection->set_mode("multiple");
2608
2609
}
2610
2611
sub sujet_impressions_cancel {
2612
2613
    if(get_debug()) {
2614
	reprend_pref('imp',$o{'options_impression'});
2615
	debug(Dumper($o{'options_impression'}));
2616
    }
2617
2618
    $w{'choix_pages_impression'}->destroy;
2619
}
2620
2621
sub sujet_impressions_ok {
2622
    my $os='none';
2623
    my @e=();
2624
2625
    for my $i ($w{'arbre_choix_copies'}->get_selection()->get_selected_rows() ) {
2626
	push @e,$copies_store->get($copies_store->get_iter($i),COPIE_N);
2627
    }
2628
2629
    reprend_pref('impall',$o{'options_impression'});
2630
2631
    if($o{'methode_impression'} eq 'CUPS') {
2632
	my $i=$w{'imprimante'}->get_model->get($w{'imprimante'}->get_active_iter,COMBO_ID);
2633
	if($i ne $o{'imprimante'}) {
2634
	    $o{'imprimante'}=$i;
2635
	    $o{'_modifie'}=1;
2636
	}
2637
2638
	reprend_pref('imp',$o{'options_impression'});
2639
2640
	if($o{'options_impression'}->{'_modifie'}) {
2641
	    $o{'_modifie'}=1;
2642
	    delete $o{'options_impression'}->{'_modifie'};
2643
	}
2644
2645
	$os=join(',',map { $_."=".$o{'options_impression'}->{$_} }
2646
		 grep { $o{'options_impression'}->{$_} }
2647
		 (keys %{$o{'options_impression'}}) );
2648
2649
	debug("Printing options : $os");
2650
    }
2651
2652
    if($o{'methode_impression'} eq 'file') {
2653
	reprend_pref('impf',$o{'options_impression'});
2654
2655
	if($o{'options_impression'}->{'_modifie'}) {
2656
	    $o{'_modifie'}=1;
2657
	    delete $o{'options_impression'}->{'_modifie'};
2658
	}
2659
2660
	if(!$o{'options_impression'}->{'repertoire'}) {
2661
	    debug "Print to file : no destionation...";
2662
	    $o{'options_impression'}->{'repertoire'}='';
2663
	} else {
2664
	  my $path=absolu($o{'options_impression'}->{'repertoire'});
2665
	  mkdir($path) if(! -e $path);
2666
	}
2667
    }
2668
2669
    $w{'choix_pages_impression'}->destroy;
2670
2671
    debug "Printing: ".join(",",@e);
2672
2673
    if(!@e) {
2674
	# No page selected:
2675
	my $dialog = Gtk2::MessageDialog
2676
	    ->new($w{'main_window'},
2677
		  'destroy-with-parent',
2678
		  'info','ok',
2679
		  __("You did not select any sheet to print..."));
2680
	$dialog->run;
2681
	$dialog->destroy;
2682
	return();
2683
    }
2684
2685
    if(1+$#e <= 10) {
2686
      # Less than 10 pages selected: is it a mistake?
2687
2688
      $projet{'_layout'}->begin_read_transaction('pPFP');
2689
      my $max_p=$projet{'_layout'}->max_enter();
2690
      my $students=$projet{'_layout'}->students_count();
2691
      $projet{'_layout'}->end_transaction('pPFP');
2692
2693
      if($max_p>1) {
2694
	# Some sheets have more than one enter-page: multiple scans
2695
	# are not supported...
2696
	my $resp=dialogue_apprentissage('PRINT_FEW_PAGES',
2697
					'warning','yes-no',$students<=10,
2698
					__("You selected only a few sheets to print.")."\n".
2699
					__("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.")." ".
2700
					__("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".
2701
					__("Do you want to print the selected sheets anyway?"),
2702
				       );
2703
	return() if($resp eq 'no');
2704
      } elsif($students<=10) {
2705
	if($projet{'options'}->{'auto_capture_mode'} != 1) {
2706
	  # This looks strange: a few sheets printed, a few sheets
2707
	  # generated, and photocopy mode not selected yet. Ask the
2708
	  # user if he wants to select this mode now.
2709
	  my $dialog = Gtk2::MessageDialog
2710
	    ->new_with_markup($w{'main_window'},
2711
			      'destroy-with-parent',
2712
			      'question','yes-no',
2713
			      __("You selected only a few sheets to print.")."\n".
2714
			      "<b>".__("Are you going to photocopy some printed subjects before giving them to the students?")."</b>\n".
2715
			      __("If so, the corresponding option will be set for this project.")." ".
2716
			      __("However, you will be able to change this when giving your first scans to AMC.")
2717
			     );
2718
	  my $reponse=$dialog->run;
2719
	  $dialog->destroy;
2720
	  my $mult=($reponse eq 'yes' ? 1 : 0);
2721
	  if($mult != $projet{'options'}->{'auto_capture_mode'}) {
2722
	    $projet{'options'}->{'auto_capture_mode'}=$mult;
2723
	    $projet{'options'}->{'_modifie_ok'}=1;
2724
	  }
2725
	}
2726
      }
2727
    }
2728
2729
    my $fh=File::Temp->new(TEMPLATE => "nums-XXXXXX",
2730
			   TMPDIR => 1,
2731
			   UNLINK=> 1);
2732
    print $fh join("\n",@e)."\n";
2733
    $fh->seek( 0, SEEK_END );
2734
2735
    commande('commande'=>["auto-multiple-choice","imprime",
2736
			  "--methode",$o{'methode_impression'},
2737
			  "--imprimante",$o{'imprimante'},
2738
			  "--options",$os,
2739
			  "--output",absolu($o{'options_impression'}->{'repertoire'})."/sheet-%e.pdf",
2740
			  ($o{'options_impression'}->{'split'} ? "--split" : "--no-split"),
2741
			  "--print-command",$o{'print_command_pdf'},
2742
			  "--sujet",absolu($projet{'options'}->{'docs'}->[0]),
2743
			  "--data",absolu($projet{'options'}->{'data'}),
2744
			  "--progression-id",'impression',
2745
			  "--progression",1,
2746
			  "--debug",debug_file(),
2747
			  "--fich-numeros",$fh->filename,
2748
			  ],
2749
	     'signal'=>2,
2750
	     'texte'=>__"Print papers one by one...",
2751
	     'progres.id'=>'impression',
2752
	     'o'=>{'fh'=>$fh,'etu'=>\@e,'printer'=>$o{'imprimante'},'method'=>$o{'methode_impression'}},
2753
	     'fin'=>sub {
2754
		 my $c=shift;
2755
		 close($c->{'o'}->{'fh'});
2756
		 save_state_after_printing($c->{'o'});
2757
	     },
2758
2759
	     );
2760
}
2761
2762
sub save_state_after_printing {
2763
    my $c=shift;
2764
    my $st=AMC::State::new('directory'=>absolu('%PROJET/'));
2765
2766
    $st->read();
2767
2768
    my @files=(@{$projet{'options'}->{'docs'}},
2769
	       absolu($projet{'options'}->{'texsrc'}));
2770
2771
    push @files,absolu($projet{'options'}->{'filtered_source'})
2772
      if(-f absolu($projet{'options'}->{'filtered_source'}));
2773
2774
    if(!$st->check_local_md5(@files)) {
2775
	$st=AMC::State::new('directory'=>absolu('%PROJET/'));
2776
	$st->add_local_files(@files);
2777
    }
2778
2779
    $st->add_print('printer'=>$c->{'printer'},
2780
		   'method'=>$c->{'method'},
2781
		   'content'=>join(',',@{$c->{'etu'}}));
2782
    $st->write();
2783
2784
}
2785
2786
sub calcule_mep {
2787
    if($projet{'options'}->{'docs'}->[2] !~ /\.xy$/) {
2788
	# OLD STYLE WORKING DOCUMENTS... Not supported anymore: update!
2789
	my $dialog = Gtk2::MessageDialog
2790
	    ->new_with_markup($w{'main_window'},
2791
			      'destroy-with-parent',
2792
			      'error', # message type
2793
			      'ok', # which set of buttons?
2794
			      __("Working documents are in an old format, which is not supported anymore.")." <b>"
2795
			      .__("Please generate again the working documents!")."</b>");
2796
	$dialog->run;
2797
	$dialog->destroy;
2798
2799
	return;
2800
    }
2801
2802
    commande('commande'=>["auto-multiple-choice","meptex",
2803
			  "--debug",debug_file(),
2804
			  "--src",absolu($projet{'options'}->{'docs'}->[2]),
2805
			  "--progression-id",'MEP',
2806
			  "--progression",1,
2807
			  "--data",absolu($projet{'options'}->{'data'}),
2808
			  ],
2809
	     'texte'=>__"Detecting layouts...",
2810
	     'progres.id'=>'MEP',
2811
	     'fin'=>sub {
2812
		 detecte_mep();
2813
		 $projet{'_layout'}->begin_read_transaction('PGCN');
2814
		 my $c=$projet{'_layout'}->pages_count();
2815
		 $projet{'_layout'}->end_transaction('PGCN');
2816
		 if($c<1) {
2817
		     # avertissement...
2818
		     my $dialog = Gtk2::MessageDialog
2819
			 ->new_with_markup($w{'main_window'},
2820
					   'destroy-with-parent',
2821
					   'error', # message type
2822
					   'ok', # which set of buttons?
2823
					   __("No layout detected.")." "
2824
					   .__("<b>Don't go through the examination</b> before fixing this problem, otherwise you won't be able to use AMC for correction."));
2825
		     $dialog->run;
2826
		     $dialog->destroy;
2827
2828
		 } else {
2829
		     dialogue_apprentissage('MAJ_MEP_OK','','',0,
2830
					    __("Layouts are detected.")." "
2831
					    .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")." "
2832
					    .__"Then you can proceed to printing and to examination.");
2833
		 }
2834
	     });
2835
}
2836
2837
sub verif_mep {
2838
    saisie_manuelle(0,0,1);
2839
}
2840
2841
### Actions des boutons de la partie SAISIE
2842
2843
sub saisie_manuelle {
2844
    my ($self,$event,$regarder)=@_;
2845
    $projet{'_layout'}->begin_read_transaction('PGCN');
2846
    my $c=$projet{'_layout'}->pages_count();
2847
    $projet{'_layout'}->end_transaction('PGCN');
2848
    if($c>0) {
2849
2850
      if(!$regarder) {
2851
	# if auto_capture_mode is not set, ask the user...
2852
	my $n=check_auto_capture_mode();
2853
	if($projet{'options'}->{'auto_capture_mode'}<0) {
2854
	  my $gsa=read_glade('choose-mode',
2855
			     qw/saisie_auto_c_auto_capture_mode
2856
				button_capture_go/);
2857
2858
	  transmet_pref($gsa,'saisie_auto',$projet{'options'});
2859
	  my $ret=$w{'choose-mode'}->run();
2860
	  if($ret==1) {
2861
	    reprend_pref('saisie_auto',$projet{'options'});
2862
	    $w{'choose-mode'}->destroy;
2863
	  } else {
2864
	    $w{'choose-mode'}->destroy;
2865
	    return();
2866
	  }
2867
	}
2868
      }
2869
2870
      # go for capture
2871
2872
      my $gm=AMC::Gui::Manuel::new
2873
	(
2874
	 'multiple'=>$projet{'options'}->{'auto_capture_mode'},
2875
	 'data-dir'=>absolu($projet{'options'}->{'data'}),
2876
	 'project-dir'=>absolu('%PROJET'),
2877
	 'sujet'=>absolu($projet{'options'}->{'docs'}->[0]),
2878
	 'etud'=>'',
2879
	 'dpi'=>$o{'saisie_dpi'},
2880
	 'seuil'=>$projet{'options'}->{'seuil'},
2881
	 'seuil_sens'=>$o{'seuil_sens'},
2882
	 'seuil_eqm'=>$o{'seuil_eqm'},
2883
	 'global'=>0,
2884
	 'encodage_interne'=>$o{'encodage_interne'},
2885
	 'image_type'=>$o{'manuel_image_type'},
2886
	 'retient_m'=>1,
2887
	 'editable'=>($regarder ? 0 : 1),
2888
	 'en_quittant'=>($regarder ? '' : 
2889
			 sub { detecte_analyse(); assoc_state(); }),
2890
	 'size_monitor'=>{env=>\%o,
2891
			  key=>($regarder?'checklayout':'manual')
2892
			  .'_window_size'},
2893
	);
2894
    } else {
2895
	my $dialog = Gtk2::MessageDialog
2896
	    ->new_with_markup($w{'main_window'},
2897
			      'destroy-with-parent',
2898
			      'error','ok',
2899
			      __("No layout for this project.")." "
2900
# 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).
2901
			      .sprintf(__("Please use button <i>%s</i> in <i>%s</i> before manual data capture."),
2902
				       __"Layout detection",
2903
				       __"Preparation"));
2904
	$dialog->run;
2905
	$dialog->destroy;
2906
    }
2907
}
2908
2909
sub check_auto_capture_mode {
2910
  $projet{'_capture'}->begin_read_transaction('ckac');
2911
  my $n=$projet{'_capture'}->n_copies;
2912
  if($n>0 && $projet{'options'}->{'auto_capture_mode'} <0) {
2913
    # the auto_capture_mode (sheets photocopied or not) is not set,
2914
    # but some capture has already been done. This looks weird, but
2915
    # it can be the case if captures were made with an old AMC
2916
    # version, or if project parameters have not been saved...
2917
    # So we try to detect the correct value from the capture data.
2918
    $projet{'options'}->{'auto_capture_mode'}=
2919
      ($projet{'_capture'}->n_photocopy() > 0 ? 1 : 0);
2920
  }
2921
  $projet{'_capture'}->end_transaction('ckac');
2922
  return($n);
2923
}
2924
2925
sub saisie_automatique {
2926
    # mode can't be changed if data capture has been made already
2927
    my $n=check_auto_capture_mode;
2928
    $projet{'_capture'}->begin_read_transaction('adcM');
2929
    my $mcopy=$projet{'_capture'}->max_copy_number()+1;
2930
    $w{'saisie_auto_allocate_start'}=$mcopy;
2931
    $projet{'_capture'}->end_transaction('adcM');
2932
2933
    my $gsa=read_glade('saisie_auto',
2934
		       qw/copie_scans
2935
			  saisie_auto_c_auto_capture_mode
2936
			  saisie_auto_cb_allocate_ids
2937
			  button_capture_go/);
2938
    $w{'copie_scans'}->set_active(1);
2939
    transmet_pref($gsa,'saisie_auto',$projet{'options'});
2940
    $w{'saisie_auto_cb_allocate_ids'}->set_label(sprintf(__"Pre-allocate sheet ids from the page numbers, starting at %d",$mcopy));
2941
2942
    $w{'saisie_auto_c_auto_capture_mode'}->set_sensitive($n==0);
2943
}
2944
2945
sub saisie_auto_mode_update {
2946
  my %o=('auto_capture_mode'=>undef);
2947
  # the mode value (auto_capture_mode) has been updated.
2948
  valide_options_for_domain('saisie_auto',\%o,@_);
2949
  $o{'auto_capture_mode'}=-1 if(!defined($o{'auto_capture_mode'}));
2950
  $w{'button_capture_go'}->set_sensitive($o{'auto_capture_mode'}>=0);
2951
  if($o{'auto_capture_mode'}==1) {
2952
    $w{'saisie_auto_cb_allocate_ids'}->show();
2953
  } else {
2954
    $w{'saisie_auto_cb_allocate_ids'}->hide();
2955
  }
2956
}
2957
2958
sub saisie_auto_annule {
2959
    $w{'saisie_auto'}->destroy();
2960
}
2961
2962
sub saisie_auto_info {
2963
  my $dialog=Gtk2::MessageDialog
2964
    ->new_with_markup($w{'saisie_auto'},'destroy-with-parent','info','ok',
2965
		      __("Automatic data capture can be done in two different modes:")."\n"
2966
		      ."<b>".
2967
# 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.
2968
		      __("Different answer sheets").
2969
		      ".</b> ".
2970
		      __("In the most robust one, you give a different sheet (with a different sheet number) to every student. You must not photocopy subjects before distributing them.")."\n"
2971
		      ."<b>".
2972
# TRANSLATORS: This is a title for the AMC mode where some answer sheets have been photocopied before being distributed to the students.
2973
		      __("Some answer sheets were photocopied").
2974
		      ".</b> ".
2975
		      __("In the second one (which can be used only if answer sheets to be scanned has one page) you can photocopy answer sheets and give the same subject to different students.")."\n"
2976
		      .__("After the first automatic capture, you can't switch to the other mode.")
2977
		      );
2978
  $dialog->run;
2979
  $dialog->destroy;
2980
}
2981
2982
sub analyse_call {
2983
  my (%oo)=@_;
2984
  # make temporary file with the list of images to analyse
2985
2986
  my $fh=File::Temp->new(TEMPLATE => "liste-XXXXXX",
2987
			 TMPDIR => 1,
2988
			 UNLINK=> 1);
2989
  print $fh join("\n",@{$oo{'f'}})."\n";
2990
  $fh->seek( 0, SEEK_END );
2991
2992
  if($oo{'getimages'}) {
2993
    my @args=("--progression-id",'analyse',
2994
	      "--list",$fh->filename,
2995
	      "--debug",debug_file(),
2996
	      "--vector-density",$o{'vector_scan_density'},
2997
	     );
2998
    push @args,"--copy-to",$oo{'copy'} if($oo{'copy'});
2999
    $projet{_layout}->begin_transaction('Orie');
3000
    my $orientation=$projet{_layout}->orientation();
3001
    $projet{_layout}->end_transaction('Orie');
3002
    push @args,"--orientation",$orientation if($orientation);
3003
3004
    debug "Target orientation: $orientation";
3005
3006
    commande('commande'=>["auto-multiple-choice","getimages",
3007
			  @args],
3008
	     'signal'=>2,
3009
	     'progres.id'=>$oo{'progres'},
3010
	     'fin'=>sub {
3011
	       analyse_call_go('liste'=>$fh->filename,'fh'=>$fh,%oo);
3012
	     },
3013
	    );
3014
  } else {
3015
    analyse_call_go('liste'=>$fh->filename,'fh'=>$fh,%oo);
3016
  }
3017
}
3018
3019
sub analyse_call_go {
3020
  my (%oo)=@_;
3021
  my @args=("--debug",debug_file(),
3022
	    ($projet{'options'}->{'auto_capture_mode'} ? "--multiple" : "--no-multiple"),
3023
	    "--tol-marque",$o{'tolerance_marque_inf'}.','.$o{'tolerance_marque_sup'},
3024
	    "--prop",$o{'box_size_proportion'},
3025
	    "--bw-threshold",$o{'bw_threshold'},
3026
	    "--progression-id",'analyse',
3027
	    "--progression",1,
3028
	    "--n-procs",$o{'n_procs'},
3029
	    "--data",absolu($projet{'options'}->{'data'}),
3030
	    "--projet",absolu('%PROJET/'),
3031
	    "--cr",absolu($projet{'options'}->{'cr'}),
3032
	    "--liste-fichiers",$oo{'liste'},
3033
	    ($o{'ignore_red'} ? "--ignore-red" : "--no-ignore-red"),
3034
	   );
3035
3036
  push @args,"--pre-allocate",$oo{'allocate'} if($oo{'allocate'});
3037
3038
  # Diagnostic image file ?
3039
3040
  if($oo{'diagnostic'}) {
3041
    push @args,"--debug-image-dir",absolu('%PROJET/cr/diagnostic');
3042
  }
3043
3044
  # call AMC-analyse
3045
3046
  commande('commande'=>["auto-multiple-choice","analyse",
3047
			@args],
3048
	   'signal'=>2,
3049
	   'texte'=>$oo{'text'},
3050
	   'progres.id'=>$oo{'progres'},
3051
	   'o'=>{'fh'=>$oo{'fh'}},
3052
	   'fin'=>$oo{'fin'},
3053
	  );
3054
}
3055
3056
sub saisie_auto_ok {
3057
    my @f=sort { $a cmp $b } ($w{'saisie_auto'}->get_filenames());
3058
    my $copie=$w{'copie_scans'}->get_active();
3059
3060
    reprend_pref('saisie_auto',$projet{'options'});
3061
    $w{'saisie_auto'}->destroy();
3062
    Gtk2->main_iteration while ( Gtk2->events_pending );
3063
3064
    clear_old('diagnostic',
3065
	      absolu('%PROJET/cr/diagnostic'));
3066
3067
    $w{'annulation'}->set_sensitive(1);
3068
3069
    analyse_call('f'=>\@f,
3070
		 'getimages'=>1,
3071
		 'copy'=>($copie ? absolu('scans/') : ''),
3072
		 'text'=>__("Automatic data capture..."),
3073
		 'progres'=>'analyse',
3074
		 'allocate'=>($projet{'options'}->{'allocate_ids'} ?
3075
			      $w{'saisie_auto_allocate_start'} : 0),
3076
		 'fin'=>sub {
3077
		     my $c=shift;
3078
		     close($c->{'o'}->{'fh'});
3079
		     detecte_analyse('apprend'=>1);
3080
		     assoc_state();
3081
		 },
3082
	);
3083
3084
}
3085
3086
sub choisit_liste {
3087
    my $dial=read_glade('liste_dialog')
3088
	->get_object('liste_dialog');
3089
3090
    my @f;
3091
    if($projet{'options'}->{'listeetudiants'}) {
3092
	@f=splitpath(absolu($projet{'options'}->{'listeetudiants'}));
3093
    } else {
3094
	@f=splitpath(absolu('%PROJET/'));
3095
    }
3096
    $f[2]='';
3097
3098
    $dial->set_current_folder(catpath(@f));
3099
3100
    my $ret=$dial->run();
3101
    debug("Names list file choice [$ret]");
3102
3103
    my $file=$dial->get_filename();
3104
    $dial->destroy();
3105
3106
    if($ret eq '1') {
3107
	# file chosen
3108
	debug("List: ".$file);
3109
	valide_liste('set'=>$file);
3110
    } elsif($ret eq '2') {
3111
	# No list
3112
	valide_liste('set'=>'');
3113
    } else {
3114
	# Cancel
3115
    }
3116
}
3117
3118
sub edite_liste {
3119
    my $f=absolu($projet{'options'}->{'listeetudiants'});
3120
    debug "Editing $f...";
3121
    commande_parallele($o{'txt_editor'},$f);
3122
}
3123
3124
sub students_list_show {
3125
  $w{'liste_refresh'}->show();
3126
  $monitor->remove_key('type','StudentsList');
3127
}
3128
3129
sub students_list_hide {
3130
  $w{'liste_refresh'}->hide();
3131
  $monitor->remove_key('type','StudentsList');
3132
  $monitor->add_file(absolu($projet{'options'}->{'listeetudiants'}),
3133
		     \&students_list_show,
3134
		     type=>'StudentsList')
3135
    if($projet{'options'}->{'listeetudiants'});
3136
}
3137
3138
sub valide_liste {
3139
    my %oo=@_;
3140
    debug "* valide_liste";
3141
3142
    if(defined($oo{'set'}) && !$oo{'nomodif'}) {
3143
	$projet{'options'}->{'listeetudiants'}=relatif($oo{'set'});
3144
	$projet{'options'}->{'_modifie'}=1;
3145
    }
3146
3147
    my $fl=absolu($projet{'options'}->{'listeetudiants'});
3148
    $fl='' if(!$projet{'options'}->{'listeetudiants'});
3149
3150
    my $fn=$fl;
3151
    $fn =~ s/.*\///;
3152
3153
    if($fl) {
3154
	$w{'liste_filename'}->set_markup("<b>$fn</b>");
3155
	for(qw/liste_path liste_edit/) {
3156
	    $w{$_}->set_sensitive(1);
3157
	}
3158
    } else {
3159
# TRANSLATORS: Names list file : (none)
3160
	$w{'liste_filename'}->set_markup(__"(none)");
3161
	for(qw/liste_path liste_edit/) {
3162
	    $w{$_}->set_sensitive(0);
3163
	}
3164
    }
3165
3166
    $projet{_students_list}=AMC::NamesFile::new($fl,
3167
			      'encodage'=>bon_encodage('liste'),
3168
			      'identifiant'=>csv_build_name(),
3169
			      );
3170
    my ($err,$errlig)=$projet{_students_list}->errors();
3171
3172
    if($err) {
3173
      students_list_show();
3174
	if(!$oo{'noinfo'}) {
3175
	    my $dialog = Gtk2::MessageDialog
3176
		->new_with_markup($w{'main_window'},
3177
				  'destroy-with-parent',
3178
				  'error','ok',
3179
				  sprintf(__"Unsuitable names file: %d errors, first on line %d.",$err,$errlig));
3180
	    $dialog->run;
3181
	    $dialog->destroy;
3182
	}
3183
	$cb_stores{'liste_key'}=$cb_model_vide_key;
3184
    } else {
3185
	# problems with ID (name/surname)
3186
	my $e=$projet{_students_list}->problem('ID.empty');
3187
	if($e>0) {
3188
	    debug "NamesFile: $e empty IDs";
3189
	    students_list_show();
3190
	    my $dialog = Gtk2::MessageDialog
3191
		->new_with_markup($w{'main_window'},
3192
				  'destroy-with-parent',
3193
				  'warning','ok',
3194
# 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.
3195
				  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)." ".
3196
				  __"Edit the names file to correct it, and re-read.");
3197
	    $dialog->run;
3198
	    $dialog->destroy;
3199
	} else {
3200
	    my $d=$projet{_students_list}->problem('ID.dup');
3201
	    if(@$d) {
3202
		debug "NamesFile: duplicate IDs [".join(',',@$d)."]";
3203
		if($#{$d}>8) {
3204
		    @$d=(@{$d}[0..8],'(and more)');
3205
		}
3206
		students_list_show();
3207
		my $dialog = Gtk2::MessageDialog
3208
		    ->new_with_markup($w{'main_window'},
3209
				      'destroy-with-parent',
3210
				      'warning','ok',
3211
				      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.");
3212
		$dialog->run;
3213
		$dialog->destroy;
3214
	    } else {
3215
		# OK, no need to refresh
3216
		students_list_hide();
3217
	    }
3218
	}
3219
	# transmission liste des en-tetes
3220
	my @heads=$projet{_students_list}->heads_for_keys();
3221
	debug "sorted heads: ".join(",",@heads);
3222
# TRANSLATORS: you can omit the [...] part, just here to explain context
3223
	$cb_stores{'liste_key'}=cb_model('',__p("(none) [No primary key found in association list]"),
3224
					 map { ($_,$_) }
3225
					 (@heads));
3226
    }
3227
    transmet_pref($gui,'pref_assoc',$projet{'options'},{},{'liste_key'=>1});
3228
    assoc_state();
3229
}
3230
3231
### Actions des boutons de la partie NOTATION
3232
3233
sub check_possible_assoc {
3234
    my ($code)=@_;
3235
    if(! -s absolu($projet{'options'}->{'listeetudiants'})) {
3236
	my $dialog = Gtk2::MessageDialog
3237
	    ->new_with_markup($w{'main_window'},
3238
			      'destroy-with-parent',
3239
			      'error','ok',
3240
# TRANSLATORS: Here, %s will be replaced with the name of the tab "Data capture".
3241
			      sprintf(__"Before associating names to papers, you must choose a students list file in tab \"%s\".",
3242
				      __"Data capture"));
3243
	$dialog->run;
3244
	$dialog->destroy;
3245
    } elsif(!$projet{'options'}->{'liste_key'}) {
3246
	my $dialog = Gtk2::MessageDialog
3247
	    ->new_with_markup($w{'main_window'},
3248
			      'destroy-with-parent',
3249
			      'error','ok',
3250
			      __("Please choose a key from primary keys in students list before association."));
3251
	$dialog->run;
3252
	$dialog->destroy;
3253
    } elsif($code && ! $projet{'options'}->{'assoc_code'}) {
3254
	my $dialog = Gtk2::MessageDialog
3255
	    ->new_with_markup($w{'main_window'},
3256
			      'destroy-with-parent',
3257
			      'error','ok',
3258
			      __("Please choose a code (made with LaTeX command \\AMCcode) before automatic association."));
3259
	$dialog->run;
3260
	$dialog->destroy;
3261
    } else {
3262
	return(1);
3263
    }
3264
    return(0);
3265
}
3266
3267
# manual association
3268
sub associe {
3269
    return() if(!check_possible_assoc(0));
3270
    if(-f absolu($projet{'options'}->{'listeetudiants'})) {
3271
      my $ga=AMC::Gui::Association::new('cr'=>absolu($projet{'options'}->{'cr'}),
3272
					'data_dir'=>absolu($projet{'options'}->{'data'}),
3273
					'liste'=>absolu($projet{'options'}->{'listeetudiants'}),
3274
					'liste_key'=>$projet{'options'}->{'liste_key'},
3275
					'identifiant'=>csv_build_name(),
3276
3277
					'fichier-liens'=>absolu($projet{'options'}->{'association'}),
3278
					'global'=>0,
3279
					'assoc-ncols'=>$o{'assoc_ncols'},
3280
					'encodage_liste'=>bon_encodage('liste'),
3281
					'encodage_interne'=>$o{'encodage_interne'},
3282
					'rtl'=>$projet{'options'}->{'annote_rtl'},
3283
					'fin'=>sub {
3284
					  assoc_state();
3285
					},
3286
					'size_prefs'=>($o{'conserve_taille'} ? \%o : ''),
3287
				       );
3288
      if($ga->{'erreur'}) {
3289
	my $dialog = Gtk2::MessageDialog
3290
	  ->new($w{'main_window'},
3291
		'destroy-with-parent',
3292
		'error','ok',
3293
		$ga->{'erreur'});
3294
	$dialog->run;
3295
	$dialog->destroy;
3296
      }
3297
    } else {
3298
	my $dialog = Gtk2::MessageDialog
3299
	    ->new($w{'main_window'},
3300
		  'destroy-with-parent',
3301
		  'info','ok',
3302
# TRANSLATORS: Here, %s will be replaced with "Students identification", which refers to a paragraph in the tab "Marking" from AMC main window.
3303
		  sprintf(__"Before associating names to papers, you must choose a students list file in paragraph \"%s\".",
3304
			  __"Students identification"));
3305
	$dialog->run;
3306
	$dialog->destroy;
3307
3308
    }
3309
}
3310
3311
# automatic association
3312
sub associe_auto {
3313
    return() if(!check_possible_assoc(1));
3314
3315
    commande('commande'=>["auto-multiple-choice","association-auto",
3316
			  pack_args("--data",absolu($projet{'options'}->{'data'}),
3317
				    "--notes-id",$projet{'options'}->{'assoc_code'},
3318
				    "--liste",absolu($projet{'options'}->{'listeetudiants'}),
3319
				    "--liste-key",$projet{'options'}->{'liste_key'},
3320
				    "--csv-build-name",csv_build_name(),
3321
				    "--encodage-liste",bon_encodage('liste'),
3322
				    "--debug",debug_file(),
3323
				    ($projet{'options'}->{'assoc_code'} eq '<preassoc>' ?
3324
				     "--pre-association" : "--no-pre-association"),
3325
				   ),
3326
	     ],
3327
	     'texte'=>__"Automatic association...",
3328
	     'fin'=>sub {
3329
		 assoc_state();
3330
		 assoc_resultat();
3331
	     },
3332
	);
3333
}
3334
3335
# automatic association finished : explain what to do after
3336
sub assoc_resultat {
3337
    my $mesg=1;
3338
3339
    $projet{'_association'}->begin_read_transaction('ARCC');
3340
    my ($auto,$man,$both)=$projet{'_association'}->counts();
3341
    $projet{'_association'}->end_transaction('ARCC');
3342
3343
    my $dialog=Gtk2::MessageDialog
3344
      ->new_with_markup($w{'main_window'},
3345
			'destroy-with-parent',
3346
			'info','ok',
3347
			sprintf(__("Automatic association completed: %d students recognized."),$auto).
3348
# TRANSLATORS: Here %s and %s will be replaced with two parameters names: "Primary key from this list" and "Code name for automatic association".
3349
			($auto==0 ? "\n<b>".sprintf(__("Please check \"%s\" and \"%s\" values and try again."),
3350
						    __("Primary key from this list"),
3351
						    __("Code name for automatic association"))."</b>" : "")
3352
		       );
3353
    $dialog->run;
3354
    $dialog->destroy;
3355
3356
    dialogue_apprentissage('ASSOC_AUTO_OK','','',0,
3357
			   __("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);
3358
}
3359
3360
sub valide_cb {
3361
    my ($var,$cb)=@_;
3362
    my $cbc=$cb->get_active();
3363
    if($cbc xor $$var) {
3364
	$$var=$cbc;
3365
	$projet{'options'}->{'_modifie'}=1;
3366
	debug "* valide_cb";
3367
    }
3368
}
3369
3370
sub valide_options_correction {
3371
    my ($ww,$o)=@_;
3372
    my $name=$ww->get_name();
3373
    debug "Options validation from $name";
3374
    if(!$w{$name}) {
3375
	debug "WARNING: Option validation failed, unknown name $name.";
3376
    } else {
3377
	valide_cb(\$projet{'options'}->{$name},$w{$name});
3378
    }
3379
}
3380
3381
sub valide_options_for_domain {
3382
    my ($domain,$oo,$widget,$user_data)=@_;
3383
    $oo=$projet{'options'} if(!$oo);
3384
    if($widget) {
3385
	my $name=$widget->get_name();
3386
	debug "<$domain> options validation for widget $name";
3387
3388
	if($name =~ /${domain}_[a-z]+_(.*)/) {
3389
	    reprend_pref($domain,$oo,'',{$1=>1});
3390
	} else {
3391
	  debug "Widget $name is not in domain <$domain>!";
3392
	}
3393
    } else {
3394
	debug "<$domain> options validation: ALL";
3395
	reprend_pref($domain,$oo);
3396
    }
3397
}
3398
3399
sub valide_options_association {
3400
  $previous_liste_key=$projet{'options'}->{'liste_key'};
3401
  valide_options_for_domain('pref_assoc','',@_);
3402
}
3403
3404
sub valide_options_preparation {
3405
    valide_options_for_domain('pref_prep','',@_);
3406
}
3407
3408
sub filter_changed {
3409
  my (@args)=@_;
3410
3411
  # check it is a different value...
3412
3413
  debug "Filter changed callback / options=".$projet{'options'}->{'filter'};
3414
3415
  my %oo=('filter'=>$projet{'options'}->{'filter'});
3416
  valide_options_for_domain('pref_prep',\%oo,@_);
3417
  return if(!$oo{'_modifie'});
3418
3419
  debug "Filter changed -> ".$oo{'filter'};
3420
3421
  # working document already built: ask for confirmation
3422
3423
  if(-f absolu($projet{'options'}->{'docs'}->[0])) {
3424
    debug "Ask for confirmation";
3425
    my $text;
3426
    if($projet{'_capture'}->n_pages_transaction()>0) {
3427
      $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.").' '
3428
	.__("Do you wish to continue?")." "
3429
	  .__("Click on Ok to erease old working documents and change file format, and on Cancel to get back to the same file format.")
3430
	    ."\n<b>".__("To allow the use of an already printed question, cancel!")."</b>";
3431
    } else {
3432
      $text=__("The working documents are already prepared with the current file format. If you change the file format, working documents will be ereased.").' '
3433
	.__("Do you wish to continue?")." "
3434
	  .__("Click on Ok to erease old working documents and change file format, and on Cancel to get back to the same file format.");
3435
    }
3436
    my $dialog = Gtk2::MessageDialog
3437
      ->new_with_markup($w{'main_window'},
3438
			'destroy-with-parent',
3439
			'question','ok-cancel',$text);
3440
    my $reponse=$dialog->run;
3441
    $dialog->destroy;
3442
3443
    if($reponse eq 'cancel') {
3444
      transmet_pref($gui,'pref_prep',$projet{'options'});
3445
      return(0);
3446
    }
3447
3448
    clear_processing('doc:');
3449
3450
  }
3451
3452
  valide_options_preparation(@args);
3453
3454
  # No source created: change source filename
3455
3456
  if(!-f absolu($projet{'options'}->{'texsrc'})) {
3457
    $projet{'options'}->{'texsrc'}='%PROJET/'.
3458
      ("AMC::Filter::register::".$projet{'options'}->{'filter'})
3459
	->default_filename();
3460
    $w{'state_src'}->set_text(absolu($projet{'options'}->{'texsrc'}));
3461
  }
3462
3463
}
3464
3465
sub valide_options_notation {
3466
    valide_options_for_domain('notation','',@_);
3467
    if($projet{'options'}->{'_modifie'} =~ /\bregroupement_compose\b/) {
3468
      $projet{'_report'}->begin_transaction('RCch');
3469
      $projet{'_report'}->variable('grouped_uptodate',-3);
3470
      $projet{'_report'}->end_transaction('RCch');
3471
    }
3472
    $w{'groupe_model'}->set_sensitive($projet{'options'}->{'regroupement_type'} eq 'STUDENTS');
3473
}
3474
3475
sub change_liste_key {
3476
  debug "New liste_key: ".$projet{'options'}->{'liste_key'};
3477
  if($projet{_students_list}->head_n_duplicates($projet{'options'}->{'liste_key'})) {
3478
    debug "Invalid key: back to old value $previous_liste_key";
3479
3480
    my $dialog = Gtk2::MessageDialog
3481
      ->new($w{'main_window'},
3482
	    'destroy-with-parent',
3483
	    'error','ok',
3484
	    __"You can't choose column '%s' as a key in the students list, as it contains duplicates (value '%s')",
3485
	    $projet{'options'}->{'liste_key'},
3486
	    $projet{_students_list}->head_first_duplicate($projet{'options'}->{'liste_key'}),
3487
	   );
3488
    $dialog->run;
3489
    $dialog->destroy;
3490
3491
3492
    $projet{'options'}->{'liste_key'}=
3493
      ($previous_liste_key ne $projet{'options'}->{'liste_key'}
3494
       ? $previous_liste_key : "");
3495
    transmet_pref($gui,'pref_assoc',$projet{'options'},{},{'liste_key'=>1});
3496
    return();
3497
  }
3498
  if ($projet{'options'}->{'liste_key'}) {
3499
3500
    $projet{'_association'}->begin_read_transaction('cLky');
3501
    my $assoc_liste_key=$projet{'_association'}->variable('key_in_list');
3502
    $assoc_liste_key='' if(!$assoc_liste_key);
3503
    my ($auto,$man,$both)=$projet{'_association'}->counts();
3504
    $projet{'_association'}->end_transaction('cLky');
3505
3506
    debug "Association [$assoc_liste_key] counts: AUTO=$auto MANUAL=$man BOTH=$both";
3507
3508
    if ($assoc_liste_key ne $projet{'options'}->{'liste_key'}
3509
	&& $auto+$man>0) {
3510
      # liste_key has changed and some association has been
3511
      # made with another liste_key
3512
3513
      if ($man>0) {
3514
	# manual association work has been made
3515
3516
	my $dialog=Gtk2::MessageDialog
3517
	  ->new_with_markup($w{'main_window'},
3518
			    'destroy-with-parent',
3519
			    'warning','yes-no',
3520
			    sprintf(__("The primary key from the students list has been set to \"%s\", which is not the value from the association data."),$projet{'options'}->{'liste_key'})." ".
3521
			    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)
3522
			   );
3523
	my $resp=$dialog->run;
3524
	$dialog->destroy;
3525
3526
	if ($resp eq 'no') {
3527
	  # clears association data
3528
	  clear_processing('assoc');
3529
	  # automatic association run
3530
	  if ($projet{'options'}->{'assoc_code'} && $auto>0) {
3531
	    associe_auto;
3532
	  }
3533
	} else {
3534
	  $projet{'options'}->{'liste_key'}=$assoc_liste_key;
3535
	  transmet_pref($gui,'pref_assoc',$projet{'options'},{},{'liste_key'=>1});
3536
	}
3537
      } else {
3538
	if ($projet{'options'}->{'assoc_code'}) {
3539
	  # only automated association, easy to re-run
3540
	  my $dialog=Gtk2::MessageDialog
3541
	    ->new_with_markup($w{'main_window'},
3542
			      'destroy-with-parent',
3543
			      'info','ok',
3544
			      sprintf(__("The primary key from the students list has been set to \"%s\", which is not the value from the association data."),$projet{'options'}->{'liste_key'})." ".
3545
			      __("Automatic papers/students association will be re-run to update the association data.")
3546
			     );
3547
	  $dialog->run;
3548
	  $dialog->destroy;
3549
3550
	  clear_processing('assoc');
3551
	  associe_auto();
3552
	}
3553
      }
3554
    }
3555
  }
3556
  assoc_state();
3557
}
3558
3559
sub voir_notes {
3560
  $projet{'_scoring'}->begin_read_transaction('smMC');
3561
  my $c=$projet{'_scoring'}->marks_count;
3562
  $projet{'_scoring'}->end_transaction('smMC');
3563
  if($c>0) {
3564
    my $n=AMC::Gui::Notes::new('scoring'=>$projet{'_scoring'});
3565
  } else {
3566
    my $dialog = Gtk2::MessageDialog
3567
      ->new($w{'main_window'},
3568
	    'destroy-with-parent',
3569
	    'info','ok',
3570
	    sprintf(__"Papers are not yet corrected: use button \"%s\".",
3571
# 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.
3572
		    __"Mark"));
3573
    $dialog->run;
3574
    $dialog->destroy;
3575
  }
3576
}
3577
3578
sub noter {
3579
    if($projet{'options'}->{'maj_bareme'}) {
3580
	my $n_copies=$projet{'options'}->{'nombre_copies'};
3581
	$projet{'_layout'}->begin_read_transaction('STUD');
3582
	my @mep_etus=$projet{'_layout'}->students();
3583
	$projet{'_layout'}->end_transaction('STUD');
3584
	my $n_mep=$mep_etus[$#mep_etus];
3585
	if($n_copies<$n_mep) {
3586
	    # number of requested copies is less than the number of
3587
	    # prepared copies: check that it is sufficient (look at
3588
	    # greater copy number within scans), and use a larger
3589
	    # one if not.
3590
	    debug "Requested $n_copies copies for scoring strategy, but has $n_mep layouts";
3591
	    my @students=($projet{'_capture'}->students_transaction());
3592
	    my $n_an=$students[$#students];
3593
	    debug "Max used copy number: $n_an";
3594
	    $n_copies=$n_an if($n_an>$n_copies);
3595
	}
3596
	commande('commande'=>["auto-multiple-choice","prepare",
3597
			      "--n-copies",$n_copies,
3598
			      "--with",moteur_latex(),
3599
			      "--filter",$projet{'options'}->{'filter'},
3600
			      "--filtered-source",absolu($projet{'options'}->{'filtered_source'}),
3601
			      "--debug",debug_file(),
3602
			      "--progression-id",'bareme',
3603
			      "--progression",1,
3604
			      "--data",absolu($projet{'options'}->{'data'}),
3605
			      "--mode","b",
3606
			      absolu($projet{'options'}->{'texsrc'}),
3607
			      ],
3608
		 'texte'=>__"Extracting marking scale...",
3609
		 'fin'=>\&noter_postcorrect,
3610
		 'progres.id'=>'bareme');
3611
    } else {
3612
	noter_calcul('','');
3613
    }
3614
}
3615
3616
my $g_pcid;
3617
my %postcorrect_ids=();
3618
my $postcorrect_copy_0;
3619
my $postcorrect_copy_1;
3620
my $postcorrect_student_min;
3621
my $postcorrect_student_max;
3622
3623
3624
sub noter_postcorrect {
3625
3626
    # check marking scale data: in PostCorrect mode, ask for a sheet
3627
    # number to get right answers from...
3628
3629
    if($projet{'_scoring'}->variable_transaction('postcorrect_flag')) {
3630
3631
	debug "PostCorrect option ON";
3632
3633
	# gets available sheet ids
3634
3635
	%postcorrect_ids=();
3636
3637
	$projet{'_capture'}->begin_read_transaction('PCex');
3638
	my $sth=$projet{'_capture'}->statement('studentCopies');
3639
	$sth->execute;
3640
	while(my $sc=$sth->fetchrow_hashref) {
3641
	  $postcorrect_student_min=$sc->{'student'} if(!defined($postcorrect_student_min));
3642
	  $postcorrect_ids{$sc->{'student'}}->{$sc->{'copy'}}=1;
3643
	  $postcorrect_student_max=$sc->{'student'};
3644
	}
3645
	$projet{'_capture'}->end_transaction('PCex');
3646
3647
	# ask user for a choice
3648
3649
	$g_pcid=read_glade('choix_postcorrect',
3650
			   qw/postcorrect_student postcorrect_copy
3651
			      postcorrect_photo postcorrect_apply/);
3652
3653
	AMC::Gui::PageArea::add_feuille($w{'postcorrect_photo'});
3654
	$w{'postcorrect_photo'}
3655
	  ->signal_connect('expose_event'=>\&AMC::Gui::PageArea::expose_drawing);
3656
3657
	debug "Student range: $postcorrect_student_min,$postcorrect_student_max\n";
3658
	$w{'postcorrect_student'}->set_range($postcorrect_student_min,$postcorrect_student_max);
3659
3660
	if($projet{'options'}->{'postcorrect_student'}) {
3661
	  for (qw/student copy/) {
3662
	    $w{'postcorrect_'.$_}
3663
	      ->set_value($projet{'options'}->{'postcorrect_'.$_});
3664
	  }
3665
	} else {
3666
	  $w{'postcorrect_student'}->set_value($postcorrect_student_min);
3667
	}
3668
3669
    } else {
3670
	noter_calcul('','');
3671
    }
3672
}
3673
3674
sub postcorrect_change_copy {
3675
    my $student=$w{'postcorrect_student'}->get_value();
3676
    my $copy=$w{'postcorrect_copy'}->get_value();
3677
3678
    $w{'postcorrect_apply'}->set_sensitive($postcorrect_ids{$student}->{$copy});
3679
3680
    $projet{'_capture'}->begin_read_transaction('PCCN');
3681
    my ($f)=$projet{'_capture'}->zone_images($student,$copy,ZONE_NAME);
3682
    $projet{'_capture'}->end_transaction('PCCN');
3683
    if(!defined($f)) {
3684
      $f='' ;
3685
    } else {
3686
      $f=absolu($projet{'options'}->{'cr'})."/$f";
3687
    }
3688
    debug "Postcorrect name field image: $f";
3689
    if(-f $f) {
3690
      $w{'postcorrect_photo'}->set_image($f);
3691
    } else {
3692
      $w{'postcorrect_photo'}->set_image('NONE');
3693
    }
3694
}
3695
3696
sub postcorrect_change {
3697
  my $student=$w{'postcorrect_student'}->get_value();
3698
3699
  my @c=sort { $a <=> $b } (keys %{$postcorrect_ids{$student}});
3700
  $postcorrect_copy_0=$c[0];
3701
  $postcorrect_copy_1=$c[$#c];
3702
3703
  debug "Postcorrect copy range for student $student: $c[0],$c[$#c]\n";
3704
  $w{'postcorrect_copy'}->set_range($c[0],$c[$#c]);
3705
3706
  postcorrect_change_copy;
3707
}
3708
3709
sub postcorrect_previous {
3710
    my $student=$w{'postcorrect_student'}->get_value();
3711
    my $copy=$w{'postcorrect_copy'}->get_value();
3712
3713
    $copy--;
3714
    if($copy<$postcorrect_copy_0) {
3715
      $student--;
3716
      if($student>=$postcorrect_student_min) {
3717
	$w{'postcorrect_student'}->set_value($student);
3718
	$w{'postcorrect_copy'}->set_value(10000);
3719
      }
3720
    } else {
3721
      $w{'postcorrect_copy'}->set_value($copy);
3722
    }
3723
}
3724
3725
sub postcorrect_next {
3726
    my $student=$w{'postcorrect_student'}->get_value();
3727
    my $copy=$w{'postcorrect_copy'}->get_value();
3728
3729
    $copy++;
3730
    if($copy>$postcorrect_copy_1) {
3731
      $student++;
3732
      if($student<=$postcorrect_student_max) {
3733
	$w{'postcorrect_student'}->set_value($student);
3734
	$w{'postcorrect_copy'}->set_value(0);
3735
      }
3736
    } else {
3737
      $w{'postcorrect_copy'}->set_value($copy);
3738
    }
3739
}
3740
3741
sub choix_postcorrect_cancel {
3742
    $w{'choix_postcorrect'}->destroy();
3743
}
3744
3745
sub choix_postcorrect_ok {
3746
    my $student=$w{'postcorrect_student'}->get_value();
3747
    my $copy=$w{'postcorrect_copy'}->get_value();
3748
    $w{'choix_postcorrect'}->destroy();
3749
3750
    if( $student != $projet{'options'}->{'postcorrect_student'}
3751
      || $copy !=$projet{'options'}->{'postcorrect_copy'} ) {
3752
	$projet{'options'}->{'postcorrect_student'}=$student;
3753
	$projet{'options'}->{'postcorrect_copy'}=$copy;
3754
	$projet{'options'}->{'_modifie_ok'}=1;
3755
    }
3756
3757
    noter_calcul($student,$copy);
3758
}
3759
3760
sub noter_calcul {
3761
3762
    my ($postcorrect_student,$postcorrect_copy)=@_;
3763
3764
    debug "Using sheet $postcorrect_student:$postcorrect_copy to get correct answers"
3765
	if($postcorrect_student);
3766
3767
    # computes marks.
3768
3769
    commande('commande'=>["auto-multiple-choice","note",
3770
			  "--debug",debug_file(),
3771
			  "--data",absolu($projet{'options'}->{'data'}),
3772
			  "--seuil",$projet{'options'}->{'seuil'},
3773
3774
			  "--grain",$projet{'options'}->{'note_grain'},
3775
			  "--arrondi",$projet{'options'}->{'note_arrondi'},
3776
			  "--notemax",$projet{'options'}->{'note_max'},
3777
			  "--plafond",$projet{'options'}->{'note_max_plafond'},
3778
			  "--notemin",$projet{'options'}->{'note_min'},
3779
			  "--postcorrect-student",$postcorrect_student,
3780
			  "--postcorrect-copy",$postcorrect_copy,
3781
3782
			  "--encodage-interne",$o{'encodage_interne'},
3783
			  "--progression-id",'notation',
3784
			  "--progression",1,
3785
			  ],
3786
	     'signal'=>2,
3787
	     'texte'=>__"Computing marks...",
3788
	     'progres.id'=>'notation',
3789
	     'fin'=>sub {
3790
		 noter_resultat();
3791
	     },
3792
	     );
3793
}
3794
3795
sub noter_resultat {
3796
  $projet{'_scoring'}->begin_read_transaction('MARK');
3797
  my $avg=$projet{'_scoring'}->average_mark;
3798
3799
  if(defined($avg)) {
3800
    state_image('marking','gtk-yes');
3801
# TRANSLATORS: This is the marks mean for all students.
3802
    $w{'state_marking'}->set_text(sprintf(__"Mean: %.2f",$avg));
3803
  } else {
3804
    state_image('marking','gtk-dialog-error');
3805
    $w{'state_marking'}->set_text(__("No marks computed"));
3806
  }
3807
3808
  my @codes=$projet{'_scoring'}->codes;
3809
  my $pre_assoc=$projet{'_layout'}->pre_association();
3810
3811
  $projet{'_scoring'}->end_transaction('MARK');
3812
3813
  debug "Codes : ".join(',',@codes);
3814
# TRANSLATORS: you can omit the [...] part, just here to explain context
3815
  my @cbs=(''=>__p("(none) [No code found in LaTeX file]"),
3816
	   map { $_=>decode($o{'encodage_latex'},$_) }
3817
	   (@codes));
3818
  push @cbs,'<preassoc>',__"Pre-association"
3819
    if($pre_assoc);
3820
  $cb_stores{'assoc_code'}=cb_model(@cbs);
3821
  transmet_pref($gui,'pref_assoc',$projet{'options'},{},{'assoc_code'=>1});
3822
3823
  $w{'onglet_reports'}->set_sensitive(defined($avg));
3824
}
3825
3826
sub assoc_state {
3827
  my $i='gtk-dialog-question';
3828
  my $t='';
3829
  if(! -s absolu($projet{'options'}->{'listeetudiants'})) {
3830
    $t=__"No students list file";
3831
  } elsif(!$projet{'options'}->{'liste_key'}) {
3832
    $t=__"No primary key from students list file";
3833
  } else {
3834
    $projet{'_association'}->begin_read_transaction('ARST');
3835
    my $mc=$projet{'_association'}->missing_count;
3836
    $projet{'_association'}->end_transaction('ARST');
3837
    if($mc) {
3838
      $t=sprintf((__"Missing identification for %d answer sheets"),$mc);
3839
    } else {
3840
      $t=__"All completed answer sheets are associated with a student name";
3841
      $i='gtk-yes';
3842
    }
3843
  }
3844
  state_image('assoc',$i);
3845
  $w{'state_assoc'}->set_text($t);
3846
}
3847
3848
sub opt_symbole {
3849
    my ($s)=@_;
3850
    my $k=$s;
3851
    my $type='none';
3852
    my $color='red';
3853
3854
    $k =~ s/-/_/g;
3855
    $type=$o{'symbole_'.$k.'_type'} if(defined($o{'symbole_'.$k.'_type'}));
3856
    $color=$o{'symbole_'.$k.'_color'} if(defined($o{'symbole_'.$k.'_color'}));
3857
3858
    return("$s:$type/$color");
3859
}
3860
3861
sub select_students {
3862
  my ($id_file)=@_;
3863
3864
  # restore last setting
3865
  my %ids=();
3866
  if (open(IDS,$id_file)) {
3867
    while (<IDS>) {
3868
      chomp;
3869
      $ids{$_}=1 if(/^[0-9]+(:[0-9]+)?$/);
3870
    }
3871
    close(IDS);
3872
  }
3873
  # dialog to let the user choose...
3874
  my $gstud=read_glade('choose_students',
3875
		       qw/choose_students_area students_instructions
3876
			  students_select_list students_list_search/);
3877
  my $lk=$projet{'options'}->{'liste_key'};
3878
3879
  $col=$w{'choose_students'}->style()->bg('prelight');
3880
  for my $s (qw/normal insensitive/) {
3881
    for my $k (qw/students_instructions/) {
3882
      $w{$k}->modify_base($s,$col);
3883
    }
3884
  }
3885
  my $students_store=Gtk2::ListStore
3886
    ->new('Glib::String','Glib::String','Glib::String',
3887
	  'Glib::String','Glib::String',
3888
	  'Glib::Boolean','Glib::Boolean');
3889
  my $filter=Gtk2::TreeModelFilter->new($students_store);
3890
  $filter->set_visible_column(5);
3891
  $w{'students_list_store'}=$students_store;
3892
  $w{'students_list_filter'}=$filter;
3893
3894
  $w{'students_select_list'}->set_model($filter);
3895
  my $renderer=Gtk2::CellRendererText->new;
3896
  my $column = Gtk2::TreeViewColumn->new_with_attributes (__"sheet ID",
3897
							  $renderer,
3898
							  text=> 0);
3899
  $column->set_sort_column_id(0);
3900
  $w{'students_select_list'}->append_column ($column);
3901
3902
  if($lk) {
3903
    $column = Gtk2::TreeViewColumn->new_with_attributes ($lk,
3904
							 $renderer,
3905
							 text=> 4);
3906
    $column->set_sort_column_id(4);
3907
    $w{'students_select_list'}->append_column ($column);
3908
  }
3909
3910
  $column = Gtk2::TreeViewColumn->new_with_attributes (__"student",
3911
						       $renderer,
3912
						       text=> 1);
3913
  $column->set_sort_column_id(1);
3914
  $w{'students_select_list'}->append_column ($column);
3915
3916
  $projet{'_capture'}->begin_read_transaction('gSLi');
3917
  my $key=$projet{'_association'}->variable('key_in_list');
3918
  my @selected_iters=();
3919
  for my $sc ($projet{'_capture'}->student_copies) {
3920
    my $id=$projet{'_association'}->get_real(@$sc);
3921
    my $iter=$students_store->append;
3922
    my ($name)=$projet{_students_list}->data($key,$id);
3923
    $students_store->set($iter,
3924
			 0=>studentids_string(@$sc),
3925
			 1=>$name->{'_ID_'},
3926
			 2=>$sc->[0],3=>$sc->[1],
3927
			 5=>1,
3928
			);
3929
    $students_store->set($iter,4=>$name->{$lk}) if($lk);
3930
    push @selected_iters,$iter if($ids{studentids_string(@$sc)});
3931
  }
3932
  $projet{'_capture'}->end_transaction('gSLi');
3933
3934
  $w{'students_select_list'}->get_selection->set_mode(GTK_SELECTION_MULTIPLE);
3935
  for (@selected_iters) {
3936
    $w{'students_select_list'}->get_selection->select_iter($filter->convert_child_iter_to_iter($_));
3937
  }
3938
3939
  my $resp=$w{'choose_students'}->run;
3940
3941
  select_students_save_selected_state();
3942
3943
  my @k=();
3944
3945
  if($resp==1) {
3946
    $students_store->foreach(sub {
3947
			       my ($model,$path,$iter,$user)=@_;
3948
			       push @k,[$students_store->get($iter,2,3)]
3949
				 if($students_store->get($iter,6));
3950
			     });
3951
  }
3952
3953
  $w{'choose_students'}->destroy;
3954
3955
  if ($resp==1) {
3956
    open(IDS,">$id_file");
3957
    for (@k) {
3958
      print IDS studentids_string(@$_)."\n";
3959
    }
3960
    close(IDS);
3961
  } else {
3962
    return();
3963
  }
3964
3965
  return(1);
3966
}
3967
3968
sub select_students_save_selected_state {
3969
  my $sel=$w{'students_select_list'}->get_selection;
3970
  my $f=$w{'students_list_filter'};
3971
  my $s=$w{'students_list_store'};
3972
  $f->foreach(sub {
3973
		my ($model,$path,$iter,$user)=@_;
3974
		$s->set($f->convert_iter_to_child_iter($iter),
3975
			6=>$sel->iter_is_selected($iter));
3976
	      });
3977
}
3978
3979
sub select_students_recover_selected_state {
3980
  my $sel=$w{'students_select_list'}->get_selection;
3981
  my $f=$w{'students_list_filter'};
3982
  my $s=$w{'students_list_store'};
3983
  $f->foreach(sub {
3984
		my ($model,$path,$iter,$user)=@_;
3985
		if($s->get($f->convert_iter_to_child_iter($iter),6)) {
3986
		  $sel->select_iter($iter);
3987
		} else {
3988
		  $sel->unselect_iter($iter);
3989
		}
3990
	      });
3991
}
3992
3993
sub select_students_search {
3994
  select_students_save_selected_state();
3995
  my $pattern=$w{'students_list_search'}->get_text;
3996
  my $s=$w{'students_list_store'};
3997
  $s->foreach(sub {
3998
		my ($model,$path,$iter,$user)=@_;
3999
		my ($id,$n,$nb)=$s->get($iter,0,1,4);
4000
		$s->set($iter,5=>
4001
			((!$pattern)
4002
			 || $id =~ /$pattern/i || $n =~ /$pattern/i
4003
			 || $nb =~ /$pattern/i ? 1 : 0));
4004
		return(0);
4005
	      });
4006
  select_students_recover_selected_state();
4007
}
4008
4009
sub select_students_all {
4010
  $w{'students_list_search'}->set_text('');
4011
}
4012
4013
sub annote_copies {
4014
  my ($and_regroupe)=@_;
4015
  my $id_file='';
4016
4017
  if($projet{'options'}->{'regroupement_copies'} eq 'SELECTED') {
4018
    # use a file in project directory to store students ids for which
4019
    # sheets will be annotated
4020
    $id_file=absolu('%PROJET/selected-ids');
4021
    return() if(!select_students($id_file));
4022
  }
4023
4024
  commande('commande'=>["auto-multiple-choice","annote",
4025
			pack_args("--debug",debug_file(),
4026
				  "--progression-id",'annote',
4027
				  "--progression",1,
4028
				  "--projet",absolu('%PROJET/'),
4029
				  "--projets",absolu('%PROJETS/'),
4030
				  "--ch-sign",$o{'annote_chsign'},
4031
				  "--cr",absolu($projet{'options'}->{'cr'}),
4032
				  "--data",absolu($projet{'options'}->{'data'}),
4033
				  "--id-file",$id_file,
4034
				  "--taille-max",$o{'taille_max_correction'},
4035
				  "--qualite",$o{'qualite_correction'},
4036
				  "--line-width",$o{'symboles_trait'},
4037
4038
				  "--indicatives",$o{'symboles_indicatives'},
4039
				  "--symbols",join(',',map { opt_symbole($_); } (qw/0-0 0-1 1-0 1-1/)),
4040
				  "--position",$projet{'options'}->{'annote_position'},
4041
				  "--pointsize-nl",$o{'annote_ps_nl'},
4042
				  "--ecart",$o{'annote_ecart'},
4043
				  "--verdict",$projet{'options'}->{'verdict'},
4044
				  "--verdict-question",$projet{'options'}->{'verdict_q'},
4045
				  "--fich-noms",absolu($projet{'options'}->{'listeetudiants'}),
4046
				  "--noms-encodage",bon_encodage('liste'),
4047
				  "--csv-build-name",csv_build_name(),
4048
				  ($projet{'options'}->{'annote_rtl'} ? "--rtl" : "--no-rtl"),
4049
				  "--changes-only",
4050
				 )
4051
		       ],
4052
	   'texte'=>__"Annotating papers...",
4053
	   'progres.id'=>'annote',
4054
	   'fin'=>sub {
4055
	     my $c=shift;
4056
	     my $n=$c->variable('n_processed');
4057
	     regroupement_go($n>0,$id_file) if($and_regroupe);
4058
	   },
4059
	  );
4060
}
4061
4062
sub annotate_papers {
4063
4064
  valide_options_notation();
4065
4066
  annote_copies(1);
4067
}
4068
4069
sub regroupement_go {
4070
  my ($from_annotate,$id_file)=@_;
4071
4072
  maj_export();
4073
4074
  my $single_output='';
4075
4076
  if($projet{'options'}->{'regroupement_type'} eq 'ALL') {
4077
    $single_output=($id_file ?
4078
# TRANSLATORS: File name for single annotated answer sheets with only some selected students. Please use simple characters.
4079
		    (__("Selected_students")).".pdf" :
4080
# TRANSLATORS: File name for single annotated answer sheets with all students. Please use simple characters.
4081
		    (__("All_students")).".pdf" );
4082
  }
4083
4084
  my $type=($single_output ? REPORT_SINGLE_ANNOTATED_PDF : REPORT_ANNOTATED_PDF);
4085
4086
  # Looks if a rename is sufficient, or if one has to group again JPEG files:
4087
4088
  my $group=($from_annotate ? 'some annotated pages were rebuilt' : '');
4089
  if(!$group) {
4090
    $projet{'_report'}->begin_read_transaction('RgFO');
4091
    $group='reports missing'
4092
      if(!$single_output &&
4093
	 ($projet{'_report'}->type_count($type)
4094
	  != $projet{'_capture'}->n_copies() ));
4095
    $group='no report'
4096
      if(!$group && $single_output && $projet{'_report'}->type_count($type)<1);
4097
    $group='reports not found'
4098
      if(!$group && !$projet{'_report'}->all_there($type,absolu('%PROJET/')));
4099
    $group='type changed'
4100
      if(!$group &&
4101
	 $projet{'_report'}->variable('last_group_type') != $type);
4102
    $group='not up to date'
4103
      if(!$group &&
4104
	 $projet{'_report'}->variable('grouped_uptodate')<=0);
4105
    $group='selected only'
4106
      if(!$group && $id_file);
4107
    $projet{'_report'}->end_transaction('RgFO');
4108
  }
4109
4110
  debug "Group? $group\n";
4111
4112
  if($group) {
4113
    # Group JPG annotated pages.
4114
    commande('commande'=>
4115
	     ["auto-multiple-choice","regroupe",
4116
	      pack_args(
4117
			"--debug",debug_file(),
4118
			"--id-file",$id_file,
4119
			($projet{'options'}->{'regroupement_compose'} ? "--compose" : "--no-compose"),
4120
			"--projet",absolu('%PROJET'),
4121
			"--sujet",absolu($projet{'options'}->{'docs'}->[0]),
4122
			"--data",absolu($projet{'options'}->{'data'}),
4123
			"--tex-src",absolu($projet{'options'}->{'texsrc'}),
4124
			"--with",moteur_latex(),
4125
			"--filter",$projet{'options'}->{'filter'},
4126
			"--filtered-source",absolu($projet{'options'}->{'filtered_source'}),
4127
			"--n-copies",$projet{'options'}->{'nombre_copies'},
4128
			"--progression-id",'regroupe',
4129
			"--progression",1,
4130
			"--modele",$projet{'options'}->{'modele_regroupement'},
4131
			"--fich-noms",absolu($projet{'options'}->{'listeetudiants'}),
4132
			"--noms-encodage",bon_encodage('liste'),
4133
			"--csv-build-name",csv_build_name(),
4134
			"--single-output",$single_output,
4135
			"--sort",$projet{'options'}->{'export_sort'},
4136
			($id_file ? "--no-register" : "--register"),
4137
			($o{'ascii_filenames'} ? "--force-ascii" : "--no-force-ascii"),
4138
		       )
4139
	     ],
4140
	     'signal'=>2,
4141
	     'clear'=>'',
4142
	     'texte'=>__"Grouping students annotated pages together...",
4143
	     'progres.id'=>'regroupe',
4144
	    );
4145
  } else {
4146
    # Only rename correct old files
4147
    commande('commande'=>
4148
	     ["auto-multiple-choice","regroupe",
4149
	      pack_args(
4150
			"--debug",debug_file(),
4151
			"--id-file",$id_file,
4152
			"--rename",
4153
			"--projet",absolu('%PROJET'),
4154
			"--data",absolu($projet{'options'}->{'data'}),
4155
			"--progression-id",'regroupe',
4156
			"--progression",1,
4157
			"--modele",$projet{'options'}->{'modele_regroupement'},
4158
			"--fich-noms",absolu($projet{'options'}->{'listeetudiants'}),
4159
			"--noms-encodage",bon_encodage('liste'),
4160
			"--csv-build-name",csv_build_name(),
4161
			"--single-output",$single_output,
4162
			($o{'ascii_filenames'} ? "--force-ascii" : "--no-force-ascii"),
4163
		       )
4164
	     ],
4165
	     'signal'=>2,
4166
	     'clear'=>'',
4167
	     'texte'=>__"Renaming annotated files with new model...",
4168
	     'progres.id'=>'regroupe',
4169
	    );
4170
  }
4171
}
4172
4173
sub view_dir {
4174
    my ($dir)=@_;
4175
4176
    debug "Look at $dir";
4177
    my $seq=0;
4178
    my @c=map { $seq+=s/[%]d/$dir/g;$_; } split(/\s+/,$o{'dir_opener'});
4179
    push @c,$dir if(!$seq);
4180
    # nautilus attend des arguments dans l'encodage specifie par LANG & co.
4181
    @c=map { encode($encodage_systeme,$_); } @c;
4182
4183
    commande_parallele(@c);
4184
}
4185
4186
sub open_exports_dir {
4187
    view_dir(absolu('%PROJET/exports/'));
4188
}
4189
4190
sub open_templates_dir {
4191
    view_dir($o{'rep_modeles'});
4192
}
4193
4194
sub regarde_regroupements {
4195
    view_dir(absolu($projet{'options'}->{'cr'})."/corrections/pdf");
4196
}
4197
4198
sub plugins_browse {
4199
  view_dir("$o_dir/plugins");
4200
}
4201
4202
###
4203
4204
sub activate_apropos {
4205
    my $gap=read_glade('apropos');
4206
}
4207
4208
sub close_apropos {
4209
    $w{'apropos'}->destroy();
4210
}
4211
4212
sub activate_doc {
4213
    my ($w,$lang)=@_;
4214
4215
    #print STDERR "$w / $lang\n";
4216
4217
    my $url='file://'.$hdocdir;
4218
    $url.="auto-multiple-choice.$lang/index.html"
4219
	if($lang && -f $hdocdir."auto-multiple-choice.$lang/index.html");
4220
4221
    my $seq=0;
4222
    my @c=map { $seq+=s/[%]u/$url/g;$_; } split(/\s+/,$o{'html_browser'});
4223
    push @c,$url if(!$seq);
4224
    @c=map { encode($encodage_systeme,$_); } @c;
4225
4226
    commande_parallele(@c);
4227
}
4228
4229
###
4230
4231
###
4232
4233
sub find_object {
4234
  my ($gap,$prefix,$type,$ta,$t,$keep)=@_;
4235
  if(!$keep) {
4236
    my $ww=$gap->get_object($prefix.$type.$ta);
4237
    if($ww) {
4238
      $w{$prefix.$type.$ta}=$ww;
4239
      $w{$prefix.$type.$t}=$ww;
4240
    }
4241
  }
4242
  return($w{$prefix.$type.$ta});
4243
}
4244
4245
# transmet les preferences vers les widgets correspondants
4246
# _c_ combo box (menu)
4247
# _cb_ check button
4248
# _ce_ combo box entry
4249
# _col_ color chooser
4250
# _f_ file name
4251
# _s_ spin button
4252
# _t_ text
4253
# _v_ check button
4254
# _x_ one line text
4255
4256
sub transmet_pref {
4257
    my ($gap,$prefixe,$h,$alias,$seulement,$update)=@_;
4258
4259
    for my $t (keys %$h) {
4260
	if(!$seulement || $seulement->{$t}) {
4261
	my $ta=$t;
4262
	$ta=$alias->{$t} if($alias->{$t});
4263
4264
	if($wp=find_object($gap,$prefixe,'_t_',$ta,$t,$update)) {
4265
	    $wp->get_buffer->set_text($h->{$t});
4266
	}
4267
	if($wp=find_object($gap,$prefixe,'_x_',$ta,$t,$update)) {
4268
	    $wp->set_text($h->{$t});
4269
	}
4270
	if($wp=find_object($gap,$prefixe,'_f_',$ta,$t,$update)) {
4271
	  my $path=$h->{$t};
4272
	  if($t =~ /^projects_/) {
4273
	    $path=absolu($path,'<HOME>');
4274
	  } elsif($t !~ /^rep_/) {
4275
	    $path=absolu($path);
4276
	  }
4277
	  if($wp->get_action =~ /-folder$/i) {
4278
	    mkdir($path) if(!-e $path);
4279
	    $wp->set_current_folder($path);
4280
	  } else {
4281
	    $wp->set_filename($path);
4282
	  }
4283
	}
4284
	if($wp=find_object($gap,$prefixe,'_v_',$ta,$t,$update)) {
4285
	    $wp->set_active($h->{$t});
4286
	}
4287
	if($wp=find_object($gap,$prefixe,'_s_',$ta,$t,$update)) {
4288
	    $wp->set_value($h->{$t});
4289
	}
4290
	if($wp=find_object($gap,$prefixe,'_col_',$ta,$t,$update)) {
4291
	    $wp->set_color(Gtk2::Gdk::Color->parse($h->{$t}));
4292
	}
4293
	if($wp=find_object($gap,$prefixe,'_cb_',$ta,$t,$update)) {
4294
	    $wp->set_active($h->{$t});
4295
	}
4296
	if($wp=find_object($gap,$prefixe,'_c_',$ta,$t,$update)) {
4297
	    if($cb_stores{$ta}) {
4298
	      debug "CB_STORE($t) ALIAS $ta modifie ($t=>$h->{$t})";
4299
	      $wp->set_model($cb_stores{$ta});
4300
	      my $i=model_id_to_iter($wp->get_model,COMBO_ID,$h->{$t});
4301
	      if($i) {
4302
		debug("[$t] find $i",
4303
		      " -> ".$cb_stores{$ta}->get($i,COMBO_TEXT));
4304
		$wp->set_active_iter($i);
4305
	      }
4306
	    } else {
4307
	      $w{$prefixe.'_c_'.$t}='';
4308
	      debug "no CB_STORE for $ta";
4309
	      $wp->set_active($h->{$t});
4310
	    }
4311
	}
4312
	if($wp=find_object($gap,$prefixe,'_ce_',$ta,$t,$update)) {
4313
	    if($cb_stores{$ta}) {
4314
		debug "CB_STORE($t) ALIAS $ta changed";
4315
		$wp->set_model($cb_stores{$ta});
4316
	    }
4317
	    my @we=grep { my (undef,$pr)=$_->class_path();$pr =~ /(yrtnE|Entry)/ } ($wp->get_children());
4318
	    if(@we) {
4319
		$we[0]->set_text($h->{$t});
4320
		$w{$prefixe.'_x_'.$t}=$we[0];
4321
	    } else {
4322
		print STDERR $prefixe.'_ce_'.$t." : cannot find text widget\n";
4323
	    }
4324
	}
4325
	debug "Key $t --> $ta : ".(defined($wp) ? "found widget $wp" : "NONE");
4326
    }}
4327
}
4328
4329
# met a jour les preferences depuis les widgets correspondants
4330
sub reprend_pref {
4331
    my ($prefixe,$h,$oprefix,$seulement)=@_;
4332
    $h->{'_modifie'}=($h->{'_modifie'} ? 1 : '');
4333
4334
    debug "Restricted search: ".join(',',keys %$seulement)
4335
      if($seulement);
4336
    for my $t (keys %$h) {
4337
      if(!$seulement || $seulement->{$t}) {
4338
	my $tgui=$t;
4339
	$tgui =~ s/$oprefix$// if($oprefix);
4340
	debug "Looking for widget <$tgui> in domain <$prefixe>";
4341
	my $n;
4342
	my $wp=$w{$prefixe.'_x_'.$tgui};
4343
	if($wp) {
4344
	    $n=$wp->get_text();
4345
	    $h->{'_modifie'}.=",$t" if($h->{$t} ne $n);
4346
	    $h->{$t}=$n;
4347
	}
4348
	$wp=$w{$prefixe.'_t_'.$tgui};
4349
	if($wp) {
4350
	    my $buf=$wp->get_buffer;
4351
	    $n=$buf->get_text($buf->get_start_iter,$buf->get_end_iter,1);
4352
	    $h->{'_modifie'}.=",$t" if($h->{$t} ne $n);
4353
	    $h->{$t}=$n;
4354
	}
4355
	$wp=$w{$prefixe.'_f_'.$tgui};
4356
	if($wp) {
4357
	    if($wp->get_action =~ /-folder$/i) {
4358
		if(-d $wp->get_filename()) {
4359
		    $n=$wp->get_filename();
4360
		} else {
4361
		    $n=$wp->get_current_folder();
4362
		}
4363
	    } else {
4364
		$n=$wp->get_filename();
4365
	    }
4366
	    if($tgui =~ /^projects_/) {
4367
	      $n=relatif($n,'<HOME>');
4368
	    } elsif($tgui !~ /^rep_/) {
4369
	      $n=relatif($n);
4370
	    }
4371
	    $h->{'_modifie'}.=",$t" if($h->{$t} ne $n);
4372
	    $h->{$t}=$n;
4373
	}
4374
	$wp=$w{$prefixe.'_v_'.$tgui};
4375
	if($wp) {
4376
	    $n=$wp->get_active();
4377
	    $h->{'_modifie'}.=",$t" if($h->{$t} ne $n);
4378
	    $h->{$t}=$n;
4379
	}
4380
	$wp=$w{$prefixe.'_s_'.$tgui};
4381
	if($wp) {
4382
	    $n=$wp->get_value();
4383
	    $h->{'_modifie'}.=",$t" if($h->{$t} ne $n);
4384
	    $h->{$t}=$n;
4385
	}
4386
	$wp=$w{$prefixe.'_col_'.$tgui};
4387
	if($wp) {
4388
	    $n=$wp->get_color()->to_string();
4389
	    $h->{'_modifie'}.=",$t" if($h->{$t} ne $n);
4390
	    $h->{$t}=$n;
4391
	}
4392
	$wp=$w{$prefixe.'_cb_'.$tgui};
4393
	if($wp) {
4394
	    $n=$wp->get_active();
4395
	    $h->{'_modifie'}.=",$t" if($h->{$t} ne $n);
4396
	    $h->{$t}=$n;
4397
	}
4398
	$wp=$w{$prefixe.'_c_'.$tgui};
4399
	if($wp) {
4400
	  debug "Found combobox";
4401
	  if($wp->get_model) {
4402
	    if($wp->get_active_iter) {
4403
	      $n=$wp->get_model->get($wp->get_active_iter,COMBO_ID);
4404
	    } else {
4405
	      debug "No active iter for combobox ".$prefixe.'_c_'.$tgui;
4406
	      $n='';
4407
	    }
4408
	  } else {
4409
	    $n=$wp->get_active();
4410
	  }
4411
	  $h->{'_modifie'}.=",$t" if($h->{$t} ne $n);
4412
	  $h->{$t}=$n;
4413
	}
4414
      } else {
4415
	debug "Skip widget <$t>";
4416
      }
4417
    }
4418
4419
    debug "Changes : $h->{'_modifie'}";
4420
}
4421
4422
sub change_methode_impression {
4423
    if($w{'pref_x_print_command_pdf'}) {
4424
	my $m='';
4425
	if($w{'pref_c_methode_impression'}->get_active_iter) {
4426
	    $m=$w{'pref_c_methode_impression'}->get_model->get($w{'pref_c_methode_impression'}->get_active_iter,COMBO_ID);
4427
	}
4428
	$w{'pref_x_print_command_pdf'}->set_sensitive($m eq 'commande');
4429
    }
4430
}
4431
4432
sub edit_preferences {
4433
    my $gap=read_glade('edit_preferences',
4434
		       qw/pref_projet_tous pref_projet_annonce pref_x_print_command_pdf pref_c_methode_impression symboles_tree email_group_sendmail email_group_SMTP/);
4435
4436
    $w{'edit_preferences_manager'}=$gap;
4437
4438
    if($o{'conserve_taille'}) {
4439
      AMC::Gui::WindowSize::size_monitor
4440
	  ($w{'edit_preferences'},{env=>\%o,
4441
				   key=>'preferences_window_size'});
4442
    }
4443
4444
    # tableau type/couleurs pour correction
4445
4446
    for my $t (grep { /^pref(_projet)?_[xfcv]_/ } (keys %w)) {
4447
	delete $w{$t};
4448
    }
4449
    transmet_pref($gap,'pref',\%o);
4450
    transmet_pref($gap,'pref_projet',$projet{'options'}) if($projet{'nom'});
4451
4452
    # projet ouvert ?
4453
    if($projet{'nom'}) {
4454
	$w{'pref_projet_annonce'}->set_label('<i>'.sprintf(__"Project \"%s\" preferences",$projet{'nom'}).'</i>.');
4455
    } else {
4456
	$w{'pref_projet_tous'}->set_sensitive(0);
4457
	$w{'pref_projet_annonce'}->set_label('<i>'.__("Project preferences").'</i>');
4458
    }
4459
4460
    # anavailable options, managed by the filter:
4461
    if($projet{'options'}->{'filter'}) {
4462
      for my $k (("AMC::Filter::register::".$projet{'options'}->{'filter'})
4463
		 ->forced_options()) {
4464
      TYPES: for my $t (qw/c cb ce col f s t v x/) {
4465
	  if(my $w=$gap->get_object("pref_projet_".$t."_".$k)) {
4466
	    $w->set_sensitive(0);
4467
	    last TYPES;
4468
	  }
4469
	}
4470
      }
4471
    }
4472
4473
    change_methode_impression();
4474
4475
    my $resp=$w{'edit_preferences'}->run();
4476
4477
    if($resp) {
4478
      accepte_preferences();
4479
    } else {
4480
      closes_preferences();
4481
    }
4482
}
4483
4484
sub closes_preferences {
4485
  $w{'edit_preferences'}->destroy();
4486
}
4487
4488
sub accepte_preferences {
4489
  reprend_pref('pref',\%o);
4490
  reprend_pref('pref_projet',$projet{'options'}) if($projet{'nom'});
4491
4492
  my %pm;
4493
  my %gm;
4494
  my @dgm;
4495
  my %labels;
4496
4497
  if ($projet{'nom'}) {
4498
    %pm=map { $_=>1 } (split(/,/,$projet{'options'}->{'_modifie'}));
4499
    %gm=map { $_=>1 } (split(/,/,$o{'_modifie'}));
4500
    @dgm=grep { /^defaut_/ } (keys %gm);
4501
4502
    for my $k (@dgm) {
4503
      my $l=$w{'edit_preferences_manager'}->get_object('label_'.$k);
4504
      $labels{$k}=$l->get_text() if($l);
4505
      my $kp=$k;
4506
      $kp =~ s/^defaut_//;
4507
      $l=$w{'edit_preferences_manager'}->get_object('label_'.$kp);
4508
      $labels{$kp}=$l->get_text() if($l);
4509
    }
4510
  }
4511
4512
  closes_preferences();
4513
4514
  if ($projet{'nom'}) {
4515
4516
    # Check if annotations are still valid (same options)
4517
4518
    my $changed=0;
4519
    for(qw/annote_chsign taille_max_correction qualite_correction symboles_trait
4520
	   symboles_indicatives annote_ps_nl annote_ecart/) {
4521
      $changed=1 if($gm{$_});
4522
    }
4523
    for my $tag (qw/0_0 0_1 1_0 1_1/) {
4524
      $changed=1 if($gm{"symbole_".$tag."_type"} || $gm{"symbole_".$tag."_color"});
4525
    }
4526
    for(qw/annote_position verdict verdict_q annote_rtl/) {
4527
      $changed=1 if($pm{$_});
4528
    }
4529
4530
    if($changed) {
4531
      $projet{'_capture'}->begin_transaction('APch');
4532
      $projet{'_capture'}->variable('annotate_source_change',time());
4533
      $projet{'_capture'}->end_transaction('APch');
4534
    }
4535
4536
    # Look at modified default values...
4537
4538
    debug "Modified (general): $o{'_modifie'}";
4539
    debug "Modified (project): ".$projet{'options'}->{'_modifie'};
4540
    debug "Labels: ".join(',',keys %labels);
4541
4542
    for my $k (@dgm) {
4543
      my $kp=$k;
4544
      $kp =~ s/^defaut_//;
4545
4546
      debug "Test G:$k / P:$kp";
4547
      if ((!$pm{$kp}) && ($projet{'options'}->{$kp} ne $o{$k})) {
4548
	# project option has NOT been modified, and the new
4549
	# value of general default option is different from
4550
	# project option. Ask the user for modifying also the
4551
	# project option value
4552
	$label_projet=$labels{$kp};
4553
	$label_general=$labels{$k};
4554
4555
	debug "Ask user $label_general | $label_projet";
4556
4557
	if ($label_projet && $label_general) {
4558
	  my $dialog = Gtk2::MessageDialog
4559
	    ->new_with_markup($w{'main_window'},
4560
			      'destroy-with-parent',
4561
			      'question','yes-no',
4562
			      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?"),
4563
				      $label_general,$label_projet,$projet{'nom'}));
4564
	  my $reponse=$dialog->run;
4565
	  $dialog->destroy;
4566
4567
	  debug "Reponse: $reponse";
4568
4569
	  if ($reponse eq 'yes') {
4570
	    # change also project option value
4571
	    $projet{'options'}->{$kp}=$o{$k};
4572
	    $projet{'options'}->{'_modifie'}.=",$kp";
4573
	  }
4574
4575
	}
4576
      }
4577
    }
4578
  }
4579
4580
  if ($projet{'nom'}) {
4581
    for my $k (qw/note_min note_max note_grain/) {
4582
      $projet{'options'}->{$k} =~ s/\s+//g;
4583
    }
4584
  }
4585
4586
  if(defined($o{'_modifie'})
4587
     && $o{'_modifie'} =~ /\bprojects_home\b/
4588
     && !$projet{'nom'}) {
4589
    $o{'rep_projets'}=absolu($o{'projects_home'});
4590
  }
4591
4592
  sauve_pref_generales();
4593
4594
  test_commandes();
4595
4596
  if (defined($projet{'options'}->{'_modifie'})
4597
      && $projet{'options'}->{'_modifie'} =~ /\bseuil\b/) {
4598
    if ($projet{'_capture'}->n_pages_transaction()>0) {
4599
      # mise a jour de la liste diagnostic
4600
      detecte_analyse();
4601
    }
4602
  }
4603
}
4604
4605
4606
sub sauve_pref_generales {
4607
    debug "Saving general preferences...";
4608
4609
    if(pref_xx_ecrit(\%o,'AMC',$o_file)) {
4610
	my $dialog = Gtk2::MessageDialog
4611
	    ->new($w{'main_window'},
4612
		  'destroy-with-parent',
4613
		  'error','ok',
4614
		  __"Error writing to options file %s: %s"
4615
		  ,$o_file,$!);
4616
	$dialog->run;
4617
	$dialog->destroy;
4618
    } else {
4619
	$o{'_modifie'}=0;
4620
    }
4621
}
4622
4623
sub annule_preferences {
4624
    debug "Canceling preferences modification";
4625
    closes_preferences();
4626
}
4627
4628
sub file_maj {
4629
    my (@f)=@_;
4630
    my $present=1;
4631
    my $oldest=0;
4632
    for my $file (@f) {
4633
	if($file && -f $file) {
4634
	    if(-r $file) {
4635
		my @s=stat($file);
4636
		$oldest=$s[9] if($s[9]>$oldest);
4637
	    } else {
4638
		return('UNREADABLE');
4639
	    }
4640
	} else {
4641
	    return('NOTFOUND');
4642
	}
4643
    }
4644
    return(decode('UTF-8',strftime("%x %X",localtime($oldest))));
4645
}
4646
4647
sub check_document {
4648
    my ($filename,$k)=@_;
4649
    $w{'but_'.$k}->set_sensitive(-f $filename);
4650
}
4651
4652
sub state_image {
4653
  my ($k,$stock)=@_;
4654
  $w{'stateimg_'.$k}->set_from_stock($stock,
4655
				     GTK_ICON_SIZE_BUTTON)
4656
    if($w{'stateimg_'.$k});
4657
  $w{'state_'.$k}->set_icon_from_stock(GTK_ENTRY_ICON_PRIMARY,$stock)
4658
    if($w{'state_'.$k});
4659
}
4660
4661
sub detecte_documents {
4662
    check_document(absolu($projet{'options'}->{'docs'}->[0]),'question');
4663
    check_document(absolu($projet{'options'}->{'docs'}->[1]),'solution');
4664
    my $s=file_maj(map { absolu($projet{'options'}->{'docs'}->[$_])
4665
		   } (0..2));
4666
    my $ok='gtk-yes';
4667
    if($s eq 'UNREADABLE') {
4668
	$s=__("Working documents are not readable");
4669
	$ok='gtk-dialog-error';
4670
    } elsif($s eq 'NOTFOUND') {
4671
	$s=__("No working documents");
4672
	$ok='gtk-dialog-error';
4673
    } else {
4674
	$s=__("Working documents last update:")." ".$s;
4675
    }
4676
    state_image('docs',$ok);
4677
    $w{'state_docs'}->set_text($s);
4678
}
4679
4680
sub show_document {
4681
    my ($sel)=@_;
4682
    my $f=absolu($projet{'options'}->{'docs'}->[$sel]);
4683
    debug "Looking at $f...";
4684
    commande_parallele($o{'pdf_viewer'},absolu($projet{'options'}->{'docs'}->[$sel]));
4685
}
4686
4687
sub show_question {
4688
    show_document(0);
4689
}
4690
4691
sub show_solution {
4692
    show_document(1);
4693
}
4694
4695
sub detecte_mep {
4696
    $projet{'_layout'}->begin_read_transaction('LAYO');
4697
    $projet{'_mep_defauts'}={$projet{'_layout'}->defects()};
4698
    my $c=$projet{'_layout'}->pages_count;
4699
    $projet{'_layout'}->end_transaction('LAYO');
4700
    my @def=(keys %{$projet{'_mep_defauts'}});
4701
    if(@def) {
4702
	$w{'button_mep_warnings'}->show();
4703
    } else {
4704
	$w{'button_mep_warnings'}->hide();
4705
    }
4706
    $w{'onglet_saisie'}->set_sensitive($c>0);
4707
    my $s;
4708
    my $ok='gtk-yes';
4709
    if($c<1) {
4710
	$s=__("No layout");
4711
	$ok='gtk-dialog-error';
4712
    } else {
4713
	$s=sprintf(__("Processed %d pages"),
4714
		   $c);
4715
	if(@def) {
4716
	    $s.=", ".__("but some defects were detected.");
4717
	    $ok='gtk-dialog-question';
4718
	} else {
4719
	    $s.='.';
4720
	}
4721
    }
4722
    $w{'state_layout'}->set_text($s);
4723
    state_image('layout',$ok);
4724
}
4725
4726
my %defect_text=
4727
  (
4728
   'NO_NAME'=>__("The \\namefield command is not used. Writing subjects without name field is not recommended"),
4729
   '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"),
4730
   'NO_BOX'=>__("No box to be ticked"),
4731
   'DIFFERENT_POSITIONS'=>__("The corner marks and binary boxes are not at the same location on all pages"),
4732
  );
4733
4734
sub mep_warnings {
4735
    my $m='';
4736
    my @def=(keys %{$projet{'_mep_defauts'}});
4737
    if(@def) {
4738
      $m=__("Some potential defects were detected for this subject. Correct them in the source and update the working documents.");
4739
      for my $k (keys %defect_text) {
4740
	my $dd=$projet{'_mep_defauts'}->{$k};
4741
	if($dd) {
4742
	  if($k eq 'DIFFERENT_POSITIONS') {
4743
	    $m.="\n<b>".$defect_text{$k}."</b> ".
4744
	      sprintf(__('(See for exemple pages %s and %s)'),
4745
		      pageids_string($dd->{'student_a'},$dd->{'page_a'}),
4746
		      pageids_string($dd->{'student_b'},$dd->{'page_b'})).'.';
4747
	  } else {
4748
	    my @e=sort { $a <=> $b } (@{$dd});
4749
	    if(@e) {
4750
	      $m.="\n<b>".$defect_text{$k}."</b> ".
4751
		sprintf(__('(Concerns %1$d sheets, see for exemple sheet %2$d)'),1+$#e,$e[0]).'.';
4752
	    }
4753
	  }
4754
	}
4755
      }
4756
    } else {
4757
	# should not be possible to go there...
4758
	return();
4759
    }
4760
    my $dialog = Gtk2::MessageDialog
4761
	->new_with_markup($w{'main_window'},
4762
			  'destroy-with-parent',
4763
			  'warning','ok',
4764
			  $m
4765
	);
4766
    $dialog->run;
4767
    $dialog->destroy;
4768
4769
}
4770
4771
sub clear_processing {
4772
  my ($steps)=@_;
4773
  my $next='';
4774
  my %s=();
4775
  for my $k (qw/doc mep capture mark assoc/) {
4776
    if($steps =~ /\b$k:/) {
4777
      $next=1;
4778
      $s{$k}=1;
4779
    } elsif($next || $steps =~ /\b$k\b/) {
4780
      $s{$k}=1;
4781
    }
4782
  }
4783
4784
  if($s{'doc'}) {
4785
    for (0,1,2) {
4786
      my $f=absolu($projet{'options'}->{'docs'}->[$_]);
4787
      unlink($f) if(-f $f);
4788
    }
4789
    detecte_documents();
4790
  }
4791
4792
  delete($s{'doc'});
4793
  return() if(!%s);
4794
4795
  # data to remove...
4796
4797
  $projet{'_data'}->begin_transaction('CLPR');
4798
4799
  if($s{'mep'}) {
4800
    $projet{_layout}->clear_all;
4801
  }
4802
4803
  if($s{'capture'}) {
4804
    $projet{_capture}->clear_all;
4805
  }
4806
4807
  if($s{'mark'}) {
4808
    $projet{'_scoring'}->clear_strategy;
4809
    $projet{'_scoring'}->clear_score;
4810
  }
4811
4812
  if($s{'assoc'}) {
4813
    $projet{_association}->clear;
4814
  }
4815
4816
  $projet{'_data'}->end_transaction('CLPR');
4817
4818
  # files to remove...
4819
4820
  if($s{'capture'}) {
4821
    # remove zooms
4822
    remove_tree(absolu('%PROJET/cr/zooms'),
4823
		{'verbose'=>0,'safe'=>1,'keep_root'=>1});
4824
    # remove namefield extractions and page layout image
4825
    my $crdir=absolu('%PROJET/cr');
4826
    opendir(my $dh,$crdir);
4827
    my @cap_files=grep { /^(name-|page-)/ } readdir($dh);
4828
    closedir($dh);
4829
    for(@cap_files) {
4830
      unlink "$crdir/$_";
4831
    }
4832
  }
4833
4834
  # update gui...
4835
4836
  if($s{'mep'}) {
4837
    detecte_mep();
4838
  }
4839
  if($s{'capture'}) {
4840
    detecte_analyse();
4841
  }
4842
  if($s{'mark'}) {
4843
    noter_resultat();
4844
  }
4845
  if($s{'assoc'}) {
4846
    assoc_state();
4847
  }
4848
}
4849
4850
sub update_analysis_summary {
4851
  my $n=$projet{'_capture'}->n_pages;
4852
4853
  my %r=$projet{'_capture'}->counts;
4854
4855
  $r{'npages'}=$n;
4856
4857
  my $failed_nb=$projet{'_capture'}
4858
    ->sql_single($projet{'_capture'}->statement('failedNb'));
4859
4860
  $w{'onglet_notation'}->set_sensitive($n>0);
4861
4862
  # resume
4863
4864
  my $tt='';
4865
  if ($r{'incomplete'}) {
4866
    $tt=sprintf(__"Data capture from %d complete papers and %d incomplete papers",$r{'complete'},$r{'incomplete'});
4867
    state_image('capture','gtk-dialog-error');
4868
    $w{'button_show_missing'}->show();
4869
  } elsif ($r{'complete'}) {
4870
    $tt=sprintf(__("Data capture from %d complete papers"),$r{'complete'});
4871
    state_image('capture','gtk-yes');
4872
    $w{'button_show_missing'}->hide();
4873
  } else {
4874
    # TRANSLATORS: this text points out that no data capture has been made yet.
4875
    $tt=sprintf(__"No data");
4876
    state_image('capture','gtk-dialog-error');
4877
    $w{'button_show_missing'}->hide();
4878
  }
4879
  $w{'state_capture'}->set_text($tt);
4880
4881
  if ($failed_nb<=0) {
4882
    if($r{'complete'}) {
4883
      $tt=__"All scans were properly recognized.";
4884
      state_image('unrecognized','gtk-yes');
4885
    } else {
4886
      $tt="";
4887
      state_image('unrecognized',undef);
4888
    }
4889
    $w{'button_unrecognized'}->hide();
4890
  } else {
4891
    $tt=sprintf(__"%d scans were not recognized.",$failed_nb);
4892
    state_image('unrecognized','gtk-dialog-question');
4893
    $w{'button_unrecognized'}->show();
4894
  }
4895
  $w{'state_unrecognized'}->set_text($tt);
4896
4897
  return(\%r);
4898
}
4899
4900
sub detecte_analyse {
4901
    my (%oo)=(@_);
4902
    my $iter;
4903
    my $row;
4904
4905
    new_diagstore();
4906
4907
    $w{'commande'}->show();
4908
    my $av_text=$w{'avancement'}->get_text();
4909
    my $frac;
4910
    my $total;
4911
    my $i;
4912
4913
    $projet{'_capture'}->begin_read_transaction('ADCP');
4914
4915
    my $summary=$projet{'_capture'}
4916
      ->summaries('darkness_threshold'=>$projet{'options'}->{'seuil'},
4917
		  'sensitivity_threshold'=>$o{'seuil_sens'},
4918
		  'mse_threshold'=>$o{'seuil_eqm'});
4919
4920
    $total=$#{$summary}+1;
4921
    $i=0;
4922
    $frac=0;
4923
    if($total>0) {
4924
      $w{'avancement'}->set_text(__"Looking for analysis...");
4925
      Gtk2->main_iteration while ( Gtk2->events_pending );
4926
    }
4927
    for my $p (@$summary) {
4928
      $diag_store->insert_with_values
4929
	($i,
4930
	 DIAG_ID,pageids_string($p->{'student'},$p->{'page'},$p->{'copy'}),
4931
	 DIAG_ID_STUDENT,$p->{'student'},
4932
	 DIAG_ID_PAGE,$p->{'page'},
4933
	 DIAG_ID_COPY,$p->{'copy'},
4934
	 DIAG_ID_BACK,$p->{'color'},
4935
	 DIAG_EQM,$p->{'mse_string'},
4936
	 DIAG_EQM_BACK,$p->{'mse_color'},
4937
	 DIAG_MAJ,format_date($p->{'timestamp'}),
4938
	 DIAG_DELTA,$p->{'sensitivity_string'},
4939
	 DIAG_DELTA_BACK,$p->{'sensitivity_color'},
4940
	);
4941
      if($i/$total>=$frac+.05) {
4942
	$frac=$i/$total;
4943
	$w{'avancement'}->set_fraction($frac);
4944
	Gtk2->main_iteration while ( Gtk2->events_pending );
4945
      }
4946
    }
4947
4948
    sort_diagstore();
4949
    show_diagstore();
4950
4951
    $w{'avancement'}->set_text($av_text);
4952
    $w{'avancement'}->set_fraction(0) if(!$oo{'interne'});
4953
    $w{'commande'}->hide() if(!$oo{'interne'});
4954
    Gtk2->main_iteration while ( Gtk2->events_pending );
4955
4956
    my $r=update_analysis_summary();
4957
4958
    $projet{'_capture'}->end_transaction('ADCP');
4959
4960
    # dialogue apprentissage :
4961
4962
    if($oo{'apprend'}) {
4963
	dialogue_apprentissage('SAISIE_AUTO','','',0,
4964
			       __("Automatic data capture now completed.")." "
4965
			       .($r->{'incomplet'}>0 ? sprintf(__("It is not complete (missing pages from %d papers).")." ",$r->{'incomplet'}) : '')
4966
			       .__("You can analyse data capture quality with some indicators values in analysis list:")
4967
			       ."\n"
4968
			       .sprintf(__"- <b>%s</b> represents positioning gap for the four corner marks. Great value means abnormal page distortion.",__"MSE")
4969
			       ."\n"
4970
			       .sprintf(__"- great values of <b>%s</b> are seen when darkness ratio is very close to the threshold for some boxes.",__"sensitivity")
4971
			       ."\n"
4972
			       .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")
4973
			       );
4974
    }
4975
4976
}
4977
4978
sub show_missing_pages {
4979
  $projet{'_capture'}->begin_read_transaction('cSMP');
4980
  my %r=$projet{'_capture'}->counts;
4981
  $projet{'_capture'}->end_transaction('cSMP');
4982
4983
  my $l='';
4984
  my @sc=();
4985
  for my $p (@{$r{'missing'}}) {
4986
    if($sc[0] != $p->{'student'} || $sc[1] != $p->{'copy'}) {
4987
      @sc=($p->{'student'},$p->{'copy'});
4988
      $l.="\n";
4989
    }
4990
    $l.="  ".pageids_string($p->{'student'},
4991
			   $p->{'page'},$p->{'copy'});
4992
  }
4993
4994
  my $dialog = Gtk2::MessageDialog
4995
    ->new_with_markup
4996
      ($w{'main_window'},
4997
       'destroy-with-parent',
4998
       'info','ok',
4999
       "<b>".(__"Pages that miss data capture to complete students sheets:")."</b>"
5000
       .$l
5001
      );
5002
  $dialog->run;
5003
  $dialog->destroy;
5004
}
5005
5006
sub update_unrecognized {
5007
  $projet{'_capture'}->begin_read_transaction('UNRC');
5008
  my $failed=$projet{'_capture'}->dbh
5009
    ->selectall_arrayref($projet{'_capture'}->statement('failedList'),
5010
			 {Slice => {}});
5011
  $projet{'_capture'}->end_transaction('UNRC');
5012
5013
  $inconnu_store->clear;
5014
  for my $ff (@$failed) {
5015
    my $iter=$inconnu_store->append;
5016
    my $f=$ff->{'filename'};
5017
    $f =~ s:.*/::;
5018
    my (undef,undef,$scan_n)=splitpath(absolu($ff->{'filename'}));
5019
    my $preproc_file=absolu('%PROJET/cr/diagnostic')."/".$scan_n.".png";
5020
    $inconnu_store->set($iter,
5021
			INCONNU_SCAN,$f,
5022
			INCONNU_FILE,$ff->{'filename'},
5023
			INCONNU_TIME,format_date($ff->{'timestamp'}),
5024
			INCONNU_TIME_N,$ff->{'timestamp'},
5025
			INCONNU_PREPROC,$preproc_file,
5026
		       );
5027
  }
5028
}
5029
5030
sub open_unrecognized {
5031
5032
  my $dialog=read_glade('unrecognized',
5033
			qw/inconnu_tree scan_area preprocessed_area
5034
			   inconnu_hpaned inconnu_vpaned
5035
			   main_recog state_scanrecog ur_frame_scan/);
5036
5037
  # make state entries with same background color as around...
5038
  my $col=$w{'ur_frame_scan'}->style()->bg('normal');
5039
  for my $s (qw/normal insensitive/) {
5040
    for my $k (qw/scanrecog/) {
5041
      $w{'state_'.$k}->modify_base($s,$col);
5042
    }
5043
  }
5044
5045
  for (qw/scan preprocessed/) {
5046
    AMC::Gui::PageArea::add_feuille($w{$_.'_area'});
5047
    $w{$_.'_area'}->signal_connect('expose_event'=>\&AMC::Gui::PageArea::expose_drawing);
5048
  }
5049
5050
  $w{'inconnu_tree'}->set_model($inconnu_store);
5051
5052
  $renderer=Gtk2::CellRendererText->new;
5053
  $column = Gtk2::TreeViewColumn->new_with_attributes ("scan",
5054
						       $renderer,
5055
						       text=> INCONNU_SCAN);
5056
  $w{'inconnu_tree'}->append_column ($column);
5057
  $column->set_sort_column_id(INCONNU_SCAN);
5058
5059
  $renderer=Gtk2::CellRendererText->new;
5060
  $column = Gtk2::TreeViewColumn->new_with_attributes ("date",
5061
						       $renderer,
5062
						       text=> INCONNU_TIME);
5063
  $w{'inconnu_tree'}->append_column ($column);
5064
  $column->set_sort_column_id(INCONNU_TIME_N);
5065
5066
  update_unrecognized();
5067
5068
  $w{'inconnu_tree'}->get_selection->set_mode(GTK_SELECTION_MULTIPLE);
5069
  $w{'inconnu_tree'}->get_selection->signal_connect("changed",\&unrecognized_line);
5070
  $w{'inconnu_tree'}->get_selection->select_iter($inconnu_store->get_iter_first);
5071
5072
  $w{'inconnu_vpaned'}->child1_shrink(0);
5073
5074
  $w{'inconnu_hpaned'}->child1_resize(1);
5075
  $w{'inconnu_hpaned'}->child2_resize(1);
5076
}
5077
5078
sub unrecognized_line {
5079
  my @sel=$w{'inconnu_tree'}->get_selection->get_selected_rows;
5080
  my $iter='';
5081
  if(@sel && defined($sel[0])) {
5082
    $iter=$inconnu_store->get_iter($sel[0]);
5083
  }
5084
  if($iter) {
5085
    $w{'inconnu_tree'}->scroll_to_cell($sel[0]);
5086
    my $scan=absolu($inconnu_store->get($iter,INCONNU_FILE));
5087
    if(-f $scan) {
5088
      $w{'scan_area'}->set_image($scan);
5089
    } else {
5090
      debug_and_stderr "Scan not found: $scan";
5091
      $w{'scan_area'}->set_image('NONE');
5092
    }
5093
5094
    my $preproc=$inconnu_store->get($iter,INCONNU_PREPROC);
5095
    if(-f $preproc) {
5096
      $w{'preprocessed_area'}->set_image($preproc);
5097
    } else {
5098
      $w{'preprocessed_area'}->set_image('NONE');
5099
    }
5100
5101
    if($w{'scan_area'}->get_image) {
5102
      my $scan_n=$scan;
5103
      $scan_n =~ s:^.*/::;
5104
      state_image('scanrecog','gtk-dialog-question');
5105
      $w{'state_scanrecog'}->set_text($scan_n);
5106
    } else {
5107
      state_image('scanrecog','gtk-dialog-error');
5108
      $w{'state_scanrecog'}->set_text(sprintf((__"Error loading scan %s"),$scan));
5109
    }
5110
  } else {
5111
    $w{'scan_area'}->set_image('NONE');
5112
    $w{'preprocessed_area'}->set_image('NONE');
5113
    state_image('scanrecog','gtk-dialog-question');
5114
    $w{'state_scanrecog'}->set_text(__"No scan selected");
5115
  }
5116
}
5117
5118
sub unrecognized_next {
5119
  my (@sel)=@_;
5120
  @sel=$w{'inconnu_tree'}->get_selection->get_selected_rows
5121
    if(!@sel);
5122
  my $iter;
5123
  if(@sel && defined($sel[$#sel])) {
5124
    $iter=$inconnu_store->iter_next($inconnu_store->get_iter($sel[$#sel]));
5125
  }
5126
  $iter=$inconnu_store->get_iter_first if(!$iter);
5127
5128
  $w{'inconnu_tree'}->get_selection->unselect_all;
5129
  $w{'inconnu_tree'}->get_selection->select_iter($iter) if($iter);
5130
}
5131
5132
sub unrecognized_prev {
5133
  my @sel=$w{'inconnu_tree'}->get_selection->get_selected_rows;
5134
  my $iter;
5135
  if(@sel && defined($sel[0])) {
5136
    my $p=$inconnu_store->get_path($inconnu_store->get_iter($sel[0]));
5137
    if($p->prev) {
5138
      $iter=$inconnu_store->get_iter($p);
5139
    } else {
5140
      $iter='';
5141
    }
5142
  }
5143
  $iter=$inconnu_store->get_iter_first if(!$iter);
5144
5145
  $w{'inconnu_tree'}->get_selection->unselect_all;
5146
  $w{'inconnu_tree'}->get_selection->select_iter($iter) if($iter);
5147
}
5148
5149
sub unrecognized_delete {
5150
  my @iters;
5151
  my @sel=($w{'inconnu_tree'}->get_selection->get_selected_rows);
5152
  return if(!@sel);
5153
5154
  $projet{'_capture'}->begin_transaction('rmUN');
5155
  for my $s (@sel) {
5156
    my $iter=$inconnu_store->get_iter($s);
5157
    my $file=$inconnu_store->get($iter,INCONNU_FILE);
5158
    $projet{'_capture'}->statement('deleteFailed')->execute($file);
5159
    unlink absolu($file);
5160
    push @iters,$iter;
5161
  }
5162
  unrecognized_next(@sel);
5163
  for(@iters) { $inconnu_store->remove($_); }
5164
  update_analysis_summary();
5165
  $projet{'_capture'}->end_transaction('rmUN');
5166
}
5167
5168
sub analyse_diagnostic {
5169
  my @sel=$w{'inconnu_tree'}->get_selection->get_selected_rows;
5170
  if(@sel) {
5171
    my $iter=$inconnu_store->get_iter($sel[0]);
5172
    my $scan=absolu($inconnu_store->get($iter,INCONNU_FILE));
5173
    my $diagnostic_file=$inconnu_store->get($iter,INCONNU_PREPROC);
5174
5175
    if(!-f $diagnostic_file) {
5176
      analyse_call('f'=>[$scan],
5177
		   'text'=>__("Making diagnostic image..."),
5178
		   'progres'=>'diagnostic',
5179
		   'diagnostic'=>1,
5180
		   'fin'=>sub {
5181
		     unrecognized_line();
5182
		   },
5183
		  );
5184
    }
5185
  }
5186
}
5187
5188
sub set_source_tex {
5189
    my ($importe)=@_;
5190
5191
    importe_source() if($importe);
5192
    valide_source_tex();
5193
}
5194
5195
sub liste_montre_nom {
5196
    my $dialog = Gtk2::MessageDialog
5197
	->new($w{'main_window'},
5198
	      'destroy-with-parent',
5199
	      'info','ok',
5200
	      __"Names list file for this project is:\n%s",
5201
	      ($projet{'options'}->{'listeetudiants'} ? absolu($projet{'options'}->{'listeetudiants'}) : __"(no file)" ));
5202
    $dialog->run;
5203
    $dialog->destroy;
5204
}
5205
5206
sub valide_source_tex {
5207
    $projet{'options'}->{'_modifie'}=1;
5208
    debug "* valide_source_tex";
5209
5210
    $w{'state_src'}->set_text(absolu($projet{'options'}->{'texsrc'}));
5211
5212
    if(!$projet{'options'}->{'filter'}) {
5213
      $projet{'options'}->{'filter'}=
5214
	best_filter_for_file(absolu($projet{'options'}->{'texsrc'}));
5215
    }
5216
5217
    detecte_documents();
5218
}
5219
5220
my $modeles_store;
5221
5222
sub charge_modeles {
5223
    my ($store,$parent,$rep)=@_;
5224
5225
    return if(! -d $rep);
5226
5227
    my @all;
5228
    my @ms;
5229
    my @subdirs;
5230
5231
    if(opendir(DIR,$rep)) {
5232
	@all=readdir(DIR);
5233
	@ms=grep { /\.tgz$/ && -f $rep."/$_" } @all;
5234
	@subdirs=grep { -d $rep."/$_" && ! /^\./ } @all;
5235
	closedir DIR;
5236
    } else {
5237
	debug("MODELS : Can't open directory $rep : $!");
5238
    }
5239
5240
    for my $sd (sort { $a cmp $b } @subdirs) {
5241
	my $nom=$sd;
5242
	my $desc_text='';
5243
5244
	my $child = $store->append($parent);
5245
	if(-f $rep."/$sd/directory.xml") {
5246
	    my $d=XMLin($rep."/$sd/directory.xml");
5247
	    $nom=$d->{'title'} if($d->{'title'});
5248
	    $desc_text=$d->{'text'} if($d->{'text'});
5249
	}
5250
	$store->set($child,MODEL_NOM,$nom,
5251
		    MODEL_PATH,'',
5252
		    MODEL_DESC,$desc_text);
5253
	charge_modeles($store,$child,$rep."/$sd");
5254
    }
5255
5256
    for my $m (sort { $a cmp $b } @ms) {
5257
	my $child = $store->append($parent);
5258
5259
	my $nom=$m;
5260
	$nom =~ s/\.tgz$//i;
5261
	my $desc_text=__"(no description)";
5262
	my $tar=Archive::Tar->new($rep."/$m");
5263
	my @desc=grep { /description.xml$/ } ($tar->list_files());
5264
	if($desc[0]) {
5265
	    my $d=XMLin($tar->get_content($desc[0]),'SuppressEmpty'=>'');
5266
	    $nom=$d->{'title'} if($d->{'title'});
5267
	    $desc_text=$d->{'text'} if($d->{'text'});
5268
	}
5269
	debug "Adding model $m";
5270
	debug "NAME=$nom DESC=$desc_text";
5271
	$store->set($child,
5272
		    MODEL_NOM,$nom,
5273
		    MODEL_PATH,$rep."/$m",
5274
		    MODEL_DESC,$desc_text);
5275
    }
5276
}
5277
5278
sub modele_dispo {
5279
    my $iter=$w{'modeles_liste'}->get_selection()->get_selected();
5280
    if($iter) {
5281
	$w{'model_choice_button'}->set_sensitive($modeles_store->get($iter,MODEL_PATH) ? 1 : 0);
5282
    } else {
5283
	debug "No iter for models selection";
5284
    }
5285
}
5286
5287
sub path_from_tree {
5288
    my ($store,$view,$f)=@_;
5289
    my $i=undef;
5290
5291
    return(undef) if(!$f);
5292
5293
    my $d='';
5294
5295
    for my $pp (split(m:/:,$f)) {
5296
	my $ipar=$i;
5297
	$d.='/' if($d);
5298
	$d.=$pp;
5299
	$i=model_id_to_iter($store,TEMPLATE_FILES_PATH,$d);
5300
	if(!$i) {
5301
	    $i=$store->append($ipar);
5302
	    $store->set($i,TEMPLATE_FILES_PATH,$d,
5303
			TEMPLATE_FILES_FILE,$pp);
5304
	}
5305
    }
5306
5307
    $view->expand_to_path($store->get_path($i));
5308
    return($i);
5309
}
5310
5311
sub template_add_file {
5312
    my ($store,$view,$f)=@_;
5313
5314
    # removes local part
5315
5316
    my $p_dir=absolu('%PROJET/');
5317
    if($f =~ s:^\Q$p_dir\E::) {
5318
	my $i=path_from_tree($store,$view,$f);
5319
	return($i);
5320
    } else {
5321
	debug "Trying to add non local file: $f (local dir is $p_dir)";
5322
	return(undef);
5323
    }
5324
}
5325
5326
sub make_template {
5327
5328
    if(!$projet{'nom'}) {
5329
	debug "Make template: no opened project";
5330
	return();
5331
    }
5332
5333
    my $gt=read_glade('make_template',
5334
		      qw/template_files_tree template_name template_file_name template_description template_file_name_warning mt_ok
5335
			template_description_scroll template_files_scroll/);
5336
5337
    $w{'template_file_name_style'} = $w{'template_file_name'}->get_modifier_style->copy;
5338
5339
    $template_files_store->clear;
5340
5341
    $w{'template_files_tree'}->set_model($template_files_store);
5342
	my $renderer=Gtk2::CellRendererText->new;
5343
# TRANSLATORS: This is a column title for the list of files to be included in a template being created.
5344
	my $column = Gtk2::TreeViewColumn->new_with_attributes(__"file",
5345
							       $renderer,
5346
							       text=> TEMPLATE_FILES_FILE );
5347
    $w{'template_files_tree'}->append_column ($column);
5348
    $w{'template_files_tree'}->get_selection->set_mode("multiple");
5349
5350
    # Detects files to include
5351
5352
    template_add_file($template_files_store,$w{'template_files_tree'},
5353
		      absolu($projet{'options'}->{'texsrc'}));
5354
    template_add_file($template_files_store,$w{'template_files_tree'},
5355
		      fich_options($projet{'nom'}));
5356
5357
    for (qw/description files/) {
5358
      $w{'template_'.$_.'_scroll'}->set_policy('automatic','automatic');
5359
    }
5360
5361
    # Waits for action
5362
5363
    $resp=$w{'make_template'}->run();
5364
5365
    if($resp eq "1") {
5366
5367
	projet_check_and_save();
5368
5369
	# Creates template
5370
5371
	my $tfile=$o{'rep_modeles'}.'/'.$w{'template_file_name'}->get_text().".tgz";
5372
	my $tar=Archive::Tar->new();
5373
	$template_files_store->foreach(\&add_to_archive,[$tar]);
5374
5375
	# Description
5376
5377
	my $buf=$w{'template_description'}->get_buffer;
5378
5379
	my $desc='';
5380
	my $writer=new XML::Writer(OUTPUT=>\$desc,ENCODING=>'utf-8');
5381
	$writer->xmlDecl("UTF-8");
5382
	$writer->startTag('description');
5383
	$writer->dataElement('title',$w{'template_name'}->get_text());
5384
	$writer->dataElement('text',$buf->get_text($buf->get_start_iter,$buf->get_end_iter,1));
5385
	$writer->endTag('description');
5386
	$writer->end();
5387
5388
	$tar->add_data('description.xml',encode_utf8($desc));
5389
5390
	$tar->write($tfile,COMPRESS_GZIP);
5391
    }
5392
5393
    $w{'make_template'}->destroy;
5394
}
5395
5396
sub add_to_archive {
5397
    my ($store,$path,$iter,$data)=@_;
5398
    my ($tar)=@$data;
5399
5400
    my $f=$store->get($iter,TEMPLATE_FILES_PATH);
5401
    my $af=absolu("%PROJET/$f");
5402
5403
    return(0) if($f eq 'description.xml');
5404
5405
    if(-f $af) {
5406
	debug "Adding to template archive: $f\n";
5407
	my $tf=Archive::Tar::File->new( file => $af);
5408
	$tf->rename($f);
5409
	$tar->add_files($tf);
5410
    }
5411
5412
    return(0);
5413
}
5414
5415
sub template_filename_verif {
5416
    restricted_check($w{'template_file_name'},$w{'template_file_name_style'},
5417
		     $w{'template_file_name_warning'},"a-zA-Z0-9_+-");
5418
    my $t=$w{'template_file_name'}->get_text();
5419
    my $tfile=$o{'rep_modeles'}.'/'.$t.".tgz";
5420
    $w{'mt_ok'}->set_sensitive($t && !-e $tfile);
5421
}
5422
5423
sub make_template_add {
5424
    my $fs=Gtk2::FileSelection->new(__"Add files to template");
5425
    $fs->set_filename(absolu('%PROJET/'));
5426
    $fs->set_select_multiple(1);
5427
    $fs->hide_fileop_buttons;
5428
5429
    my $err=0;
5430
    my $resp=$fs->run();
5431
    if($resp eq 'ok') {
5432
	for my $f ($fs->get_selections()) {
5433
	    $err++
5434
		if(!defined(template_add_file($template_files_store,$w{'template_files_tree'},$f)));
5435
	}
5436
    }
5437
    $fs->destroy();
5438
5439
    if($err) {
5440
	my $dialog=Gtk2::MessageDialog
5441
	    ->new_with_markup($w{'make_template'},
5442
			      'destroy-with-parent',
5443
			      'error','ok',
5444
			      __("When making a template, you can only add files that are within the project directory."));
5445
	$dialog->run();
5446
	$dialog->destroy();
5447
    }
5448
}
5449
5450
sub make_template_del {
5451
    my @i=();
5452
    for my $path ($w{'template_files_tree'}->get_selection->get_selected_rows) {
5453
	push @i,$template_files_store->get_iter($path);
5454
    }
5455
    for(@i) {
5456
	$template_files_store->remove($_);
5457
    }
5458
}
5459
5460
sub n_fich {
5461
    my ($dir)=@_;
5462
5463
    if(opendir(NFICH,$dir)) {
5464
	my @f=grep { ! /^\./ } readdir(NFICH);
5465
	closedir(NFICH);
5466
5467
	return(1+$#f,"$dir/$f[0]");
5468
    } else {
5469
	debug("N_FICH : Can't open directory $dir : $!");
5470
 	return(0);
5471
    }
5472
}
5473
5474
sub unzip_to_temp {
5475
  my ($file)=@_;
5476
5477
  my $temp_dir = tempdir( DIR=>tmpdir(),CLEANUP => 1 );
5478
  my $error=0;
5479
5480
  my @cmd;
5481
5482
  if($file =~ /\.zip$/i) {
5483
    @cmd=("unzip","-d",$temp_dir,$file);
5484
  } else {
5485
    @cmd=("tar","-x","-v","-z","-f",$file,"-C",$temp_dir);
5486
  }
5487
5488
  debug "Extracting archive files\nFROM: $file\nWITH: ".join(' ',@cmd);
5489
  if(open(UNZIP,"-|",@cmd) ) {
5490
    while(<UNZIP>) {
5491
      debug $_;
5492
    }
5493
    close(UNZIP);
5494
  } else {
5495
    $error=$!;
5496
  }
5497
5498
  return($temp_dir,$error);
5499
}
5500
5501
5502
5503
sub source_latex_choisir {
5504
5505
    my %oo=@_;
5506
    my $texsrc='';
5507
5508
    if(!$oo{'nom'}) {
5509
	debug "ERR: Empty name for source_latex_choisir";
5510
	return(0,'');
5511
    }
5512
5513
    if(-e $o{'rep_projets'}."/".$oo{'nom'}) {
5514
	debug "ERR: existing project directory $oo{'nom'} for source_latex_choisir";
5515
	return(0,'');
5516
    }
5517
5518
    my %bouton=();
5519
5520
    if($oo{'type'}) {
5521
	$bouton{$oo{'type'}}=1;
5522
    } else {
5523
5524
	# fenetre de choix du source latex
5525
5526
	my $gap=read_glade('source_latex_dialog');
5527
5528
	my $dialog=$gap->get_object('source_latex_dialog');
5529
5530
	my $reponse=$dialog->run();
5531
5532
	for(qw/new choix vide zip/) {
5533
	    $bouton{$_}=$gap->get_object('sl_type_'.$_)->get_active();
5534
	    debug "Bouton $_" if($bouton{$_});
5535
	}
5536
5537
	$dialog->destroy();
5538
5539
	debug "RESPONSE=$reponse";
5540
5541
	return(0,'') if($reponse!=10);
5542
    }
5543
5544
    # actions apres avoir choisi le type de source latex a utiliser
5545
5546
    if($bouton{'new'}) {
5547
5548
	# choix d'un modele
5549
5550
	$gap=read_glade('source_latex_modele',
5551
			qw/modeles_liste modeles_description model_choice_button mlist_separation/);
5552
5553
	$modeles_store = Gtk2::TreeStore->new('Glib::String',
5554
					      'Glib::String',
5555
					      'Glib::String');
5556
5557
	charge_modeles($modeles_store,undef,$o{'rep_modeles'}) if($o{'rep_modeles'});
5558
5559
	charge_modeles($modeles_store,undef,amc_specdir('models'));
5560
5561
	$w{'modeles_liste'}->set_model($modeles_store);
5562
	my $renderer=Gtk2::CellRendererText->new;
5563
# TRANSLATORS: This is a column name for the list of available templates, when creating a new project based on a template.
5564
	my $column = Gtk2::TreeViewColumn->new_with_attributes(__"template",
5565
							       $renderer,
5566
							       text=> MODEL_NOM );
5567
	$w{'modeles_liste'}->append_column ($column);
5568
	$w{'modeles_liste'}->get_selection->signal_connect("changed",\&source_latex_mmaj);
5569
5570
	$w{'mlist_separation'}->set_position(.5*$w{'mlist_separation'}->get_property('max-position'));
5571
	$w{'mlist_separation'}->child1_resize(1);
5572
	$w{'mlist_separation'}->child2_resize(1);
5573
5574
	$reponse=$w{'source_latex_modele'}->run();
5575
5576
	debug "Dialog modele : $reponse";
5577
5578
	# le modele est choisi : l'installer
5579
5580
	my $mod;
5581
5582
	if($reponse) {
5583
	    my $iter=$w{'modeles_liste'}->get_selection()->get_selected();
5584
	    $mod=$modeles_store->get($iter,MODEL_PATH) if($iter);
5585
	}
5586
5587
	$w{'source_latex_modele'}->destroy();
5588
5589
	return(0,'') if($reponse!=10);
5590
5591
	if($mod) {
5592
	    debug "Installing model $mod";
5593
	    return(source_latex_choisir('type'=>'zip','fich'=>$mod,
5594
					'decode'=>1,'nom'=>$oo{'nom'}));
5595
	} else {
5596
	    debug "No model";
5597
	    return(0,'');
5598
	}
5599
5600
    } elsif($bouton{'choix'}) {
5601
5602
	# choisir un fichier deja present
5603
5604
	$gap=read_glade('source_latex_choix');
5605
5606
	$w{'source_latex_choix'}->set_current_folder($home_dir);
5607
5608
	# default filter: all possible source files
5609
5610
	my $filtre_all=Gtk2::FileFilter->new();
5611
	$filtre_all->set_name(__"All source files");
5612
	for my $m (@filter_modules) {
5613
	  for my $p ("AMC::Filter::register::$m"->file_patterns) {
5614
	    $filtre_all->add_pattern($p);
5615
	  }
5616
	}
5617
	$w{'source_latex_choix'}->add_filter($filtre_all);
5618
5619
	# filters for each filter module
5620
5621
	for my $m (@filter_modules) {
5622
	  my $f=Gtk2::FileFilter->new();
5623
# 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.
5624
	  my @pat=();
5625
	  for my $p ("AMC::Filter::register::$m"->file_patterns) {
5626
	    push @pat,$p;
5627
	    $f->add_pattern($p);
5628
	  }
5629
	  $f->set_name(sprintf(__("%s files"),
5630
			       "AMC::Filter::register::$m"->name())
5631
		       .' ('.join(', ',@pat).')');
5632
	  $w{'source_latex_choix'}->add_filter($f);
5633
	}
5634
5635
	#
5636
5637
	$reponse=$w{'source_latex_choix'}->run();
5638
5639
	my $f=$w{'source_latex_choix'}->get_filename();
5640
5641
	$w{'source_latex_choix'}->destroy();
5642
5643
	return(0,'') if($reponse!=10);
5644
5645
	$texsrc=relatif($f,$oo{'nom'});
5646
	debug "Source LaTeX $f";
5647
5648
    } elsif($bouton{'zip'}) {
5649
5650
	my $fich;
5651
5652
	if($oo{'fich'}) {
5653
	    $fich=$oo{'fich'};
5654
	} else {
5655
5656
	    # choisir un fichier ZIP
5657
5658
	    $gap=read_glade('source_latex_choix_zip');
5659
5660
	    $w{'source_latex_choix_zip'}->set_current_folder($home_dir);
5661
5662
	    my $filtre_zip=Gtk2::FileFilter->new();
5663
	    $filtre_zip->set_name(__"Archive (zip, tgz)");
5664
	    $filtre_zip->add_pattern("*.zip");
5665
	    $filtre_zip->add_pattern("*.tar.gz");
5666
	    $filtre_zip->add_pattern("*.tgz");
5667
	    $filtre_zip->add_pattern("*.TGZ");
5668
	    $filtre_zip->add_pattern("*.ZIP");
5669
	    $w{'source_latex_choix_zip'}->add_filter($filtre_zip);
5670
5671
	    $reponse=$w{'source_latex_choix_zip'}->run();
5672
5673
	    $fich=$w{'source_latex_choix_zip'}->get_filename();
5674
5675
	    $w{'source_latex_choix_zip'}->destroy();
5676
5677
	    return(0,'') if($reponse!=10);
5678
	}
5679
5680
	# cree un repertoire temporaire pour dezipper
5681
5682
	my ($temp_dir,$rv)=unzip_to_temp($fich);
5683
5684
	my ($n,$suivant)=n_fich($temp_dir);
5685
5686
	if($rv || $n==0) {
5687
	    my $dialog = Gtk2::MessageDialog
5688
		->new_with_markup($w{'main_window'},
5689
				  'destroy-with-parent',
5690
				  'error','ok',
5691
				  sprintf(__"Nothing extracted from archive %s. Check it.",$fich));
5692
	    $dialog->run;
5693
	    $dialog->destroy;
5694
	    return(0,'');
5695
	} else {
5696
	    # unzip OK
5697
	    # vire les repertoires intermediaires :
5698
5699
	    while($n==1 && -d $suivant) {
5700
		debug "Changing root directory : $suivant";
5701
		$temp_dir=$suivant;
5702
		($n,$suivant)=n_fich($temp_dir);
5703
	    }
5704
5705
	    # bouge les fichiers la ou il faut
5706
5707
	    my $hd=$o{'rep_projets'}."/".$oo{'nom'};
5708
5709
	    mkdir($hd) if(! -e $hd);
5710
5711
	    my @archive_files;
5712
5713
	    if(opendir(MVR,$temp_dir)) {
5714
		@archive_files=grep { ! /^\./ } readdir(MVR);
5715
		closedir(MVR);
5716
	    } else {
5717
		debug("ARCHIVE : Can't open $temp_dir : $!");
5718
	    }
5719
5720
	    my $latex;
5721
5722
	    for my $ff (@archive_files) {
5723
		debug "Moving to project: $ff";
5724
		if($ff =~ /\.tex$/i) {
5725
		    $latex=$ff;
5726
		    if($oo{'decode'}) {
5727
			debug "Decoding $ff...";
5728
			move("$temp_dir/$ff","$temp_dir/$ff.0enc");
5729
			copy_latex("$temp_dir/$ff.0enc","$temp_dir/$ff");
5730
		    }
5731
		}
5732
		if(system("mv","$temp_dir/$ff","$hd/$ff") != 0) {
5733
		    debug "ERR: Move failed: $temp_dir/$ff --> $hd/$ff -- $!";
5734
		    debug "(already exists)" if(-e "$hd/$ff");
5735
		}
5736
	    }
5737
5738
	    if($latex) {
5739
		$texsrc="%PROJET/$latex";
5740
		debug "LaTeX found : $latex";
5741
	    }
5742
5743
	    return(2,$texsrc);
5744
	}
5745
5746
    } elsif($bouton{'vide'}) {
5747
5748
      my $hd=$o{'rep_projets'}."/".$oo{'nom'};
5749
5750
      mkdir($hd) if(! -e $hd);
5751
5752
      $texsrc='source.tex';
5753
      my $sl="$hd/$texsrc";
5754
5755
    } else {
5756
      return(0,'');
5757
    }
5758
5759
    return(1,$texsrc);
5760
5761
}
5762
5763
sub source_latex_mmaj {
5764
    my $iter=$w{'modeles_liste'}->get_selection()->get_selected();
5765
    my $desc='';
5766
5767
    $desc=$modeles_store->get($iter,MODEL_DESC) if($iter);
5768
    $w{'modeles_description'}->get_buffer->set_text($desc);
5769
}
5770
5771
5772
# copie en changeant eventuellement d'encodage
5773
sub copy_latex {
5774
    my ($src,$dest)=@_;
5775
    # 1) reperage du inputenc dans le source
5776
    my $i='';
5777
    open(SRC,$src);
5778
  LIG: while(<SRC>) {
5779
      s/%.*//;
5780
      if(/\\usepackage\[([^\]]*)\]\{inputenc\}/) {
5781
	  $i=$1;
5782
	  last LIG;
5783
      }
5784
  }
5785
    close(SRC);
5786
5787
    my $ie=get_enc($i);
5788
    my $id=get_enc($o{'encodage_latex'});
5789
    if($ie && $id && $ie->{'iso'} ne $id->{'iso'}) {
5790
	debug "Reencoding $ie->{'iso'} => $id->{'iso'}";
5791
	open(SRC,"<:encoding($ie->{'iso'})",$src) or return('');
5792
	open(DEST,">:encoding($id->{'iso'})",$dest) or close(SRC),return('');
5793
	while(<SRC>) {
5794
	    chomp;
5795
	    s/\\usepackage\[([^\]]*)\]\{inputenc\}/\\usepackage[$id->{'inputenc'}]{inputenc}/;
5796
	    print DEST "$_\n";
5797
	}
5798
	close(DEST);
5799
	close(SRC);
5800
	return(1);
5801
    } else {
5802
	return(copy($src,$dest));
5803
    }
5804
}
5805
5806
sub importe_source {
5807
    my ($fxa,$fxb,$fb) = splitpath($projet{'options'}->{'texsrc'});
5808
    my $dest=absolu($fb);
5809
5810
    # fichier deja dans le repertoire projet...
5811
    return() if(is_local($projet{'options'}->{'texsrc'},1));
5812
5813
    if(-f $dest) {
5814
	my $dialog = Gtk2::MessageDialog
5815
	    ->new($w{'main_window'},
5816
		  'destroy-with-parent',
5817
		  'error','yes-no',
5818
		  __("File %s already exists in project directory: do you wnant to replace it?")." "
5819
		  .__("Click yes to replace it and loose pre-existing contents, or No to cancel source file import."),$fb);
5820
	my $reponse=$dialog->run;
5821
	$dialog->destroy;
5822
5823
	if($reponse eq 'no') {
5824
	    return(0);
5825
	}
5826
    }
5827
5828
    if(copy_latex(absolu($projet{'options'}->{'texsrc'}),$dest)) {
5829
	$projet{'options'}->{'texsrc'}=relatif($dest);
5830
	set_source_tex();
5831
	my $dialog = Gtk2::MessageDialog
5832
	    ->new($w{'main_window'},
5833
		  'destroy-with-parent',
5834
		  'info','ok',
5835
		  __("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"));
5836
	$dialog->run;
5837
	$dialog->destroy;
5838
    } else {
5839
	my $dialog = Gtk2::MessageDialog
5840
	    ->new($w{'main_window'},
5841
		  'destroy-with-parent',
5842
		  'error','ok',
5843
		  __"Error copying source file: %s",$!);
5844
	$dialog->run;
5845
	$dialog->destroy;
5846
    }
5847
}
5848
5849
sub edit_src {
5850
    my $f=absolu($projet{'options'}->{'texsrc'});
5851
5852
    # create new one if necessary
5853
5854
    if(!-f $f) {
5855
      debug "Creating new empty source file...";
5856
      ("AMC::Filter::register::".$projet{'options'}->{'filter'})
5857
	->default_content($f);
5858
    }
5859
5860
    #
5861
5862
    debug "Editing $f...";
5863
    my $editor=$o{'txt_editor'};
5864
    if($projet{'options'}->{'filter'}) {
5865
      my $type=("AMC::Filter::register::".$projet{'options'}->{'filter'})
5866
	->filetype();
5867
      $editor=$o{$type.'_editor'} if($o{$type.'_editor'});
5868
    }
5869
    commande_parallele($editor,$f);
5870
}
5871
5872
sub valide_projet {
5873
    set_source_tex();
5874
5875
    $projet{'_data'}=AMC::Data->new(absolu($projet{'options'}->{'data'}),
5876
				    'progress'=>\%w);
5877
    for (qw/layout capture scoring association report/) {
5878
      $projet{'_'.$_}=$projet{'_data'}->module($_);
5879
    }
5880
5881
    $projet{_students_list}=AMC::NamesFile::new();
5882
5883
    detecte_mep();
5884
    detecte_analyse('premier'=>1);
5885
5886
    debug "Correction options : MB".$projet{'options'}->{'maj_bareme'};
5887
    $w{'maj_bareme'}->set_active($projet{'options'}->{'maj_bareme'});
5888
5889
    transmet_pref($gui,'notation',$projet{'options'});
5890
5891
    $w{'main_window'}->set_title($projet{'nom'}.' - '.
5892
				 'Auto Multiple Choice');
5893
5894
    noter_resultat();
5895
5896
    valide_liste('noinfo'=>1,'nomodif'=>1);
5897
5898
    transmet_pref($gui,'export',$projet{'options'});
5899
    transmet_pref($gui,'pref_prep',$projet{'options'});
5900
5901
    for my $k (@widgets_only_when_opened) {
5902
      $w{$k}->set_sensitive(1);
5903
    }
5904
}
5905
5906
sub projet_ouvre {
5907
    my ($proj,$deja)=(@_);
5908
5909
    my $new_source=0;
5910
5911
    # ouverture du projet $proj. Si $deja==1, alors il faut le creer
5912
5913
    if($proj) {
5914
	my ($ok,$texsrc);
5915
5916
	# choix fichier latex si nouveau projet...
5917
	if($deja) {
5918
	    ($ok,$texsrc)=source_latex_choisir('nom'=>$proj);
5919
	    if(!$ok) {
5920
		return(0);
5921
	    }
5922
	    if($ok==1) {
5923
		$new_source=1;
5924
	    } elsif($ok==2) {
5925
		$deja='';
5926
	    }
5927
	}
5928
5929
	quitte_projet();
5930
	$projet{'nom'}=$proj;
5931
	$projet{'options'}->{'texsrc'}=$texsrc;
5932
5933
	if(!$deja) {
5934
5935
	    if(-f fich_options($proj)) {
5936
		debug "Reading options for project $proj...";
5937
5938
		$projet{'options'}={pref_xx_lit(fich_options($proj))};
5939
5940
		# pour effacer des trucs en trop venant d'un bug anterieur...
5941
		for(keys %{$projet{'options'}}) {
5942
		    delete($projet{'options'}->{$_})
5943
			if($_ !~ /^ext_/ && !exists($projet_defaut{$_}));
5944
		}
5945
5946
		# Old style CSV ticked option
5947
		if($projet{'cochees'} && !$projet{'ticked'}) {
5948
		  $projet{'ticked'}='01';
5949
		  delete($projet{'cochees'});
5950
		}
5951
5952
		debug "Read options:",
5953
		  Dumper(\%projet);
5954
	    } else {
5955
		debug "No options file...";
5956
	    }
5957
	}
5958
5959
	$projet{'nom'}=$proj;
5960
5961
	# creation du repertoire et des sous-repertoires de projet
5962
5963
	for my $sous ('',qw:cr cr/corrections cr/corrections/jpg cr/corrections/pdf cr/zooms cr/diagnostic data scans exports:) {
5964
	    my $rep=$o{'rep_projets'}."/$proj/$sous";
5965
	    if(! -x $rep) {
5966
		debug "Creating directory $rep...";
5967
		mkdir($rep);
5968
	    }
5969
	}
5970
5971
	# recuperation des options par defaut si elles ne sont pas encore definies dans la conf du projet
5972
5973
	for my $k (keys %projet_defaut) {
5974
	    if(! exists($projet{'options'}->{$k})) {
5975
		if($o{'defaut_'.$k}) {
5976
		    $projet{'options'}->{$k}=$o{'defaut_'.$k};
5977
		    debug "New parameter (default) : $k";
5978
		} else {
5979
		    $projet{'options'}->{$k}=$projet_defaut{$k};
5980
		    debug "New parameter : $k";
5981
		}
5982
	    }
5983
	}
5984
5985
	$w{'onglets_projet'}->set_sensitive(1);
5986
5987
	valide_projet();
5988
5989
	$projet{'options'}->{'_modifie'}='';
5990
5991
	set_source_tex(1) if($new_source);
5992
5993
	return(1);
5994
    }
5995
}
5996
5997
sub quitte_projet {
5998
    if($projet{'nom'}) {
5999
6000
      maj_export();
6001
	valide_options_notation();
6002
6003
	my ($m,$mo)=sub_modif($projet{'options'});
6004
6005
	if($m || $mo) {
6006
	    my $save=1;
6007
	    if($m) {
6008
		my $dialog = Gtk2::MessageDialog
6009
		    ->new_with_markup($w{'main_window'},
6010
				      'destroy-with-parent',
6011
				      'question','yes-no',
6012
				      sprintf(__"You did not save project <i>%s</i> options, which have been modified: do you want to save them before leaving?",$projet{'nom'}));
6013
		my $reponse=$dialog->run;
6014
		$dialog->destroy;
6015
6016
		if($reponse ne 'yes') {
6017
		    $save='';
6018
		}
6019
	    }
6020
	    projet_sauve() if($save);
6021
	}
6022
6023
	%projet=();
6024
6025
      for my $k (@widgets_only_when_opened) {
6026
	$w{$k}->set_sensitive(0);
6027
      }
6028
    }
6029
}
6030
6031
sub quitter {
6032
    quitte_projet();
6033
6034
    if($o{'conserve_taille'}) {
6035
	my ($x,$y)=$w{'main_window'}->get_size();
6036
	if(!$o{'taille_x_main'} || !$o{'taille_y_main'}
6037
	   || $x != $o{'taille_x_main'} || $y != $o{'taille_y_main'}) {
6038
	    $o{'taille_x_main'}=$x;
6039
	    $o{'taille_y_main'}=$y;
6040
	    $o{'_modifie_ok'}=1;
6041
	}
6042
    }
6043
6044
    my ($m,$mo)=sub_modif(\%o);
6045
6046
    if($m || $mo) {
6047
      my $save=1;
6048
      if($m) {
6049
	my $dialog = Gtk2::MessageDialog
6050
	  ->new_with_markup($w{'main_window'},
6051
			    'destroy-with-parent',
6052
			    'question','yes-no',
6053
			    __"You did not save main options, which have been modified: do you want to save them before leaving?");
6054
	my $reponse=$dialog->run;
6055
	$dialog->destroy;
6056
	$save=0 if($reponse ne 'yes');
6057
      }
6058
6059
      if($save) {
6060
	sauve_pref_generales();
6061
      }
6062
    }
6063
6064
    Gtk2->main_quit;
6065
}
6066
6067
sub bug_report {
6068
    my $dialog = Gtk2::MessageDialog
6069
	->new_with_markup($w{'main_window'},
6070
			  'destroy-with-parent',
6071
			  'info','ok',
6072
			  __("In order to send a useful bug report, please attach the following documents:")."\n"
6073
			  ."- ".__("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"
6074
			  ."- ".__("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"
6075
			  .sprintf(__("Bug reports can be filled at %s or sent to the address below."),
6076
				   "<i>".__("AMC community site")."</i>",
6077
				   )
6078
	);
6079
    my $ma=$dialog->get('message-area');
6080
    my $web=Gtk2::LinkButton->new_with_label("http://project.auto-multiple-choice.net/projects/auto-multiple-choice/issues",__("AMC community site"));
6081
    $ma->add($web);
6082
    my $mail=Gtk2::LinkButton->new_with_label('mailto:paamc@passoire.fr',
6083
					      'paamc@passoire.fr');
6084
    $ma->add($mail);
6085
    $ma->show_all();
6086
6087
    $dialog->run;
6088
    $dialog->destroy;
6089
}
6090
6091
#######################################
6092
6093
sub pref_change_delivery {
6094
  my %oo=('email_transport'=>'');
6095
  reprend_pref('pref',\%oo);
6096
  for my $k (qw/sendmail SMTP/) {
6097
    $w{'email_group_'.$k}->set_sensitive($k eq $oo{'email_transport'});
6098
  }
6099
}
6100
6101
my $email_sl;
6102
my $email_key;
6103
my $email_r;
6104
6105
sub project_email_name {
6106
  my ($markup)=@_;
6107
  my $pn=($projet{'options'}->{'nom_examen'}
6108
	  || $projet{'options'}->{'code_examen'}
6109
	  || $projet{'nom'});
6110
  if($markup) {
6111
    return($pn eq $projet{'nom'} ? "<b>$pn</b>" : $pn);
6112
  } else {
6113
    return($pn);
6114
  }
6115
}
6116
6117
sub email_attachment_addtolist {
6118
  for my $f (@_) {
6119
    my $name=$f;
6120
    $name =~ s/.*\///;
6121
    $attachments_store->set($attachments_store->append,
6122
			    ATTACHMENTS_FILE,absolu($f),
6123
			    ATTACHMENTS_NAME,$name,
6124
			    ATTACHMENTS_FOREGROUND,
6125
			    (-f absolu($f) ?
6126
			     hex_color('black') :
6127
			     hex_color('red')),
6128
			   );
6129
  }
6130
}
6131
6132
sub email_attachment_add {
6133
  my $d=Gtk2::FileChooserDialog
6134
    ->new(__("Attach file"),
6135
	  $w{'main_window'},'open',
6136
	  'gtk-cancel'=>'cancel',
6137
	  'gtk-ok'=>'ok');
6138
  $d->set_select_multiple(1);
6139
  my $r=$d->run;
6140
  if($r eq 'ok') {
6141
    email_attachment_addtolist($d->get_filenames);
6142
  }
6143
  $d->destroy();
6144
}
6145
6146
sub email_attachment_remove {
6147
  for my $i (map { $attachments_store->get_iter($_); }
6148
	     ($w{'attachments_list'}->get_selection->get_selected_rows)) {
6149
    $attachments_store->remove($i);
6150
  }
6151
}
6152
6153
sub send_emails {
6154
6155
  # are there some annotated answer sheets to send?
6156
6157
  $projet{'_report'}->begin_read_transaction('emNU');
6158
  my $n=$projet{'_report'}->type_count(REPORT_ANNOTATED_PDF);
6159
  my $n_annotated=$projet{'_capture'}->annotated_count();
6160
  $projet{'_report'}->end_transaction('emNU');
6161
6162
  if($n==0) {
6163
    my $dialog = Gtk2::MessageDialog
6164
      ->new_with_markup($w{'main_window'},
6165
			'destroy-with-parent',
6166
			'error','ok',
6167
			__("There are no annotated corrected answer sheets to send.")
6168
			." "
6169
			.($n_annotated>0 ?
6170
			  __("Please group the annotated sheets to PDF files to be able to send them.") :
6171
			  __("Please annotate answer sheets and group them to PDF files to be able to send them.") )
6172
		       );
6173
    $dialog->run;
6174
    $dialog->destroy;
6175
6176
    return();
6177
  }
6178
6179
  # check perl modules availibility
6180
6181
  my @needs_module=(qw/Email::Address Email::MIME
6182
		       Email::Sender Email::Sender::Simple/);
6183
  if($o{'email_transport'} eq 'sendmail') {
6184
    push @needs_module,'Email::Sender::Transport::Sendmail';
6185
  } elsif($o{'email_transport'} eq 'SMTP') {
6186
    push @needs_module,'Email::Sender::Transport::SMTP';
6187
  }
6188
  my @manque=();
6189
  for my $m (@needs_module) {
6190
    if(!check_install(module=>$m)) {
6191
      push @manque,$m;
6192
    }
6193
  }
6194
  if(@manque) {
6195
    debug 'Mailing: Needs perl modules '.join(', ',@manque);
6196
6197
    my $dialog = Gtk2::MessageDialog
6198
      ->new_with_markup($w{'main_window'},
6199
			'destroy-with-parent',
6200
			'error','ok',
6201
			sprintf(__("Sending emails requires some perl modules that are not installed: %s. Please install these modules and try again."),
6202
			'<b>'.join(', ',@manque).'</b>')
6203
		       );
6204
    $dialog->run;
6205
    $dialog->destroy;
6206
6207
    return();
6208
  }
6209
6210
  load Email::Address;
6211
6212
  # then check a correct sender address has been set
6213
6214
  my @sa=Email::Address->parse($o{'email_sender'});
6215
6216
  if(!@sa) {
6217
    my $message;
6218
    if($o{'email_sender'}) {
6219
      $message.=sprintf(__("The email address you entered (%s) is not correct."),
6220
			$o{'email_sender'}).
6221
	"\n".__"Please edit your preferencies to correct your email address.";
6222
    } else {
6223
      $message.=__("You did not enter your email address.").
6224
	"\n".__"Please edit the preferencies to set your email address.";
6225
    }
6226
    my $dialog = Gtk2::MessageDialog
6227
      ->new_with_markup($w{'main_window'},
6228
			'destroy-with-parent',
6229
			'error','ok',$message);
6230
    $dialog->run;
6231
    $dialog->destroy;
6232
6233
    return();
6234
  }
6235
6236
  # Now check (if applicable) that sendmail path is ok
6237
6238
  if($o{'email_transport'} eq 'sendmail'
6239
     && $o{'email_sendmail_path'}
6240
     && !-f $o{'email_sendmail_path'}) {
6241
    my $dialog = Gtk2::MessageDialog
6242
      ->new_with_markup($w{'main_window'},
6243
			'destroy-with-parent',
6244
# TRANSLATORS: Do not translate the 'sendmail' word.
6245
			'error','ok',sprintf(__("The <i>sendmail</i> program cannot be found at the location you specified in the preferencies (%s). Please update your configuration."),$o{'email_sendmail_path'}));
6246
    $dialog->run;
6247
    $dialog->destroy;
6248
6249
    return();
6250
  }
6251
6252
  # find columns with emails in the students list file
6253
6254
  my %cols_email=$projet{_students_list}
6255
    ->heads_count(sub { my @a=Email::Address->parse(@_);return(@a) });
6256
  my @cols=grep { $cols_email{$_}>0 } (keys %cols_email);
6257
  $cb_stores{'email_col'}=
6258
    cb_model(map { $_=>$_ } (@cols));
6259
6260
  if(!@cols) {
6261
    my $dialog = Gtk2::MessageDialog
6262
      ->new_with_markup($w{'main_window'},
6263
			'destroy-with-parent',
6264
			'error','ok',
6265
		       __"No email addresses has been found in the students list file. You need to write the students addresses in a column of this file.");
6266
    $dialog->run;
6267
    $dialog->destroy;
6268
6269
    return();
6270
  }
6271
6272
  # which is the best column ?
6273
6274
  my $nmax=0;
6275
  my $col_max='';
6276
6277
  for(@cols) {
6278
    if($cols_email{$_}>$nmax) {
6279
      $nmax=$cols_email{$_};
6280
      $col_max=$_;
6281
    }
6282
  }
6283
6284
  $projet{'options'}->{'email_col'}=$col_max
6285
    if(!$projet{'options'}->{'email_col'});
6286
6287
  # Then, open configuration window...
6288
  my $gap=read_glade('mailing',
6289
		     qw/emails_list email_dialog label_name
6290
			attachments_list attachments_expander/);
6291
6292
  $w{'label_name'}->set_text(project_email_name());
6293
6294
  $w{'attachments_list'}->set_model($attachments_store);
6295
  $renderer=Gtk2::CellRendererText->new;
6296
# 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.
6297
  $column = Gtk2::TreeViewColumn->new_with_attributes (__"file",
6298
						       $renderer,
6299
						       text=> ATTACHMENTS_NAME,
6300
						      'foreground'=> ATTACHMENTS_FOREGROUND,
6301
						      );
6302
  $w{'attachments_list'}->append_column ($column);
6303
6304
  $w{'attachments_list'}->set_tooltip_column(ATTACHMENTS_FILE);
6305
  $w{'attachments_list'}->get_selection->set_mode(GTK_SELECTION_MULTIPLE);
6306
6307
  #
6308
6309
  $w{'emails_list'}->set_model($emails_store);
6310
  $renderer=Gtk2::CellRendererText->new;
6311
# 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.
6312
  $column = Gtk2::TreeViewColumn->new_with_attributes (__"copy",
6313
						       $renderer,
6314
						       text=> EMAILS_SC);
6315
  $w{'emails_list'}->append_column ($column);
6316
  $renderer=Gtk2::CellRendererText->new;
6317
# 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.
6318
  $column = Gtk2::TreeViewColumn->new_with_attributes (__"name",
6319
						       $renderer,
6320
						       text=> EMAILS_NAME);
6321
  $w{'emails_list'}->append_column ($column);
6322
  $renderer=Gtk2::CellRendererText->new;
6323
# 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.
6324
  $column = Gtk2::TreeViewColumn->new_with_attributes (__"email",
6325
						       $renderer,
6326
						       text=> EMAILS_EMAIL);
6327
  $w{'emails_list'}->append_column ($column);
6328
6329
  $projet{'_report'}->begin_read_transaction('emCC');
6330
  $email_key=$projet{'_association'}->variable('key_in_list');
6331
  $email_r=$projet{'_report'}->get_associated_type(REPORT_ANNOTATED_PDF);
6332
6333
  $emails_store->clear;
6334
  for my $i (@$email_r) {
6335
    my ($s)=$projet{_students_list}->data($email_key,$i->{'id'});
6336
    $emails_store->set($emails_store->append,
6337
		       EMAILS_ID,$i->{'id'},
6338
		       EMAILS_EMAIL,'',
6339
		       EMAILS_NAME,$s->{'_ID_'},
6340
		       EMAILS_SC,pageids_string($projet{'_association'}->real_back($i->{'id'})),
6341
		       );
6342
  }
6343
6344
  $projet{'_report'}->end_transaction('emCC');
6345
6346
  $w{'emails_list'}->get_selection->set_mode(GTK_SELECTION_MULTIPLE);
6347
  $w{'emails_list'}->get_selection->select_all;
6348
6349
  $attachments_store->clear;
6350
  email_attachment_addtolist(@{$projet{'options'}->{'email_attachment'}});
6351
6352
  $w{'attachments_expander'}->set_expanded(@{$projet{'options'}->{'email_attachment'}} ? 1 : 0);
6353
6354
  if($o{'conserve_taille'}) {
6355
    AMC::Gui::WindowSize::size_monitor
6356
	($w{'email_dialog'},{env=>\%o,
6357
			     key=>'mailing_window_size'});
6358
  }
6359
6360
  transmet_pref($gap,'email',$projet{'options'});
6361
  my $resp=$w{'email_dialog'}->run;
6362
  my @ids=();
6363
  if($resp==1) {
6364
    reprend_pref('email',$projet{'options'});
6365
    # get selection
6366
    my @selected=$w{'emails_list'}->get_selection->get_selected_rows;
6367
    for my $i (@selected) {
6368
      my $iter=$emails_store->get_iter($i);
6369
      push @ids,$emails_store->get($iter,EMAILS_ID);
6370
    }
6371
    # get attachments filenames
6372
    my @f=();
6373
    my $iter=$attachments_store->get_iter_first;
6374
    while($iter) {
6375
      push @f,relatif($attachments_store->get($iter,ATTACHMENTS_FILE));
6376
      $iter=$attachments_store->iter_next($iter);
6377
    }
6378
    if(@f) {
6379
      $projet{'options'}->{'email_attachment'}=[@f];
6380
    } else {
6381
      delete($projet{'options'}->{'email_attachment'});
6382
    }
6383
  }
6384
  $w{'email_dialog'}->destroy;
6385
6386
  # are all attachments present?
6387
  if($resp==1) {
6388
    my @missing=grep { ! -f absolu($_) } (@{$projet{'options'}->{'email_attachment'}});
6389
    if(@missing) {
6390
      my $dialog = Gtk2::MessageDialog
6391
	->new_with_markup($w{'main_window'},
6392
			  'destroy-with-parent',
6393
			  'error','ok',
6394
			  __("Some files you asked to be attached to the emails are missing:")."\n".join("\n",@missing)."\n".
6395
			  __("Please create them or remove them from the list of attached files."));
6396
      $dialog->run();
6397
      $dialog->destroy();
6398
      $resp=0;
6399
    }
6400
  }
6401
6402
  if($resp==1) {
6403
    # writes the list of copies to send in a temporary file
6404
    my $fh=File::Temp->new(TEMPLATE => "ids-XXXXXX",
6405
			   TMPDIR => 1,
6406
			   UNLINK=> 1);
6407
    print $fh join("\n",@ids)."\n";
6408
    $fh->seek( 0, SEEK_END );
6409
6410
    my @mailing_args=("--project",absolu('%PROJET/'),
6411
		      "--project-name",project_email_name(),
6412
		      "--students-list",absolu($projet{'options'}->{'listeetudiants'}),
6413
		      "--list-encoding",bon_encodage('liste'),
6414
		      "--csv-build-name",csv_build_name(),
6415
		      "--ids-file",$fh->filename,
6416
		      "--email-column",$projet{'options'}->{'email_col'},
6417
		      "--sender",$o{'email_sender'},
6418
		      "--subject",$projet{'options'}->{'email_subject'},
6419
		      "--text",$projet{'options'}->{'email_text'},
6420
		      "--transport",$o{'email_transport'},
6421
		      "--sendmail-path",$o{'email_sendmail_path'},
6422
		      "--smtp-host",$o{'email_smtp_host'},
6423
		      "--smtp-port",$o{'email_smtp_port'},
6424
		      "--cc",$o{'email_cc'},
6425
		      "--bcc",$o{'email_bcc'},
6426
		     );
6427
6428
    for(@{$projet{'options'}->{'email_attachment'}}) {
6429
      push @mailing_args,"--attach",absolu($_);
6430
    }
6431
6432
    commande('commande'=>["auto-multiple-choice","mailing",
6433
			  pack_args(@mailing_args,
6434
				    "--debug",debug_file(),
6435
				    "--progression-id",'mailing',
6436
				    "--progression",1,
6437
				   ),
6438
			 ],
6439
	     'progres.id'=>'mailing',
6440
	     'texte'=>__"Sending emails...",
6441
	     'o'=>{'fh'=>$fh},
6442
	     'fin'=>sub {
6443
	       my $c=shift;
6444
	       close($c->{'o'}->{'fh'});
6445
6446
	       my $ok=$c->variable('OK') || 0;
6447
	       my $failed=$c->variable('FAILED') || 0;
6448
	       my @message;
6449
	       push @message,sprintf(__"%d message(s) has been sent.",$ok);
6450
	       if($failed>0) {
6451
		 push @message,"<b>".sprintf("%d message(s) could not be sent.",$failed)."</b>";
6452
	       }
6453
	       my $dialog = Gtk2::MessageDialog
6454
		 ->new_with_markup($w{'main_window'},
6455
				   'destroy-with-parent',
6456
				   ($failed>0 ? 'warning' : 'info'),'ok',
6457
				   join("\n",@message));
6458
	       $dialog->run;
6459
	       $dialog->destroy;
6460
	     },
6461
	    );
6462
  }
6463
}
6464
6465
sub email_change_col {
6466
  my %oo=('email_col'=>'');
6467
  reprend_pref('email',\%oo);
6468
6469
  my $i=$emails_store->get_iter_first;
6470
  while(defined($i)) {
6471
    my ($s)=$projet{_students_list}->data($email_key,$emails_store->get($i,EMAILS_ID));
6472
    $emails_store->set($i,EMAILS_EMAIL,$s->{$oo{'email_col'}});
6473
    $i=$emails_store->iter_next($i);
6474
  }
6475
}
6476
6477
sub mailing_set_project_name {
6478
  my $dialog=Gtk2::Dialog
6479
    ->new(__("Set exam name"),$w{''},
6480
	  ['destroy-with-parent'],'gtk-cancel'=>'cancel','gtk-ok'=>'ok',
6481
	  );
6482
  $dialog->set_default_response ('ok');
6483
6484
  my $t=Gtk2::Table->new(2,2);
6485
  my $widget;
6486
  $t->attach(Gtk2::Label->new(__"Examination name"),0,1,0,1,
6487
	     [],[],2,2);
6488
  $widget=Gtk2::Entry->new();
6489
  $w{'set_name_x_nom_examen'}=$widget;
6490
  $t->attach($widget,1,2,0,1,['expand','fill'],[],2,2);
6491
  $t->attach(Gtk2::Label->new(__"Code (short name) for examination"),0,1,1,2,
6492
	     [],[],2,2);
6493
  $widget=Gtk2::Entry->new();
6494
  $w{'set_name_x_code_examen'}=$widget;
6495
  $t->attach($widget,1,2,1,2,['expand','fill'],[],2,2);
6496
6497
  $t->show_all;
6498
  $dialog->get_content_area()->add ($t);
6499
6500
  transmet_pref('','set_name',$projet{'options'},{},'',1);
6501
6502
  my $response = $dialog->run;
6503
6504
  if($response eq 'ok') {
6505
    reprend_pref('set_name',$projet{'options'},'',
6506
		 {'nom_examen'=>1,'code_examen'=>1});
6507
    $w{'label_name'}->set_text(project_email_name());
6508
  }
6509
6510
  $dialog->destroy;
6511
}
6512
6513
#######################################
6514
6515
sub choose_columns {
6516
  my ($type)=@_;
6517
6518
  my $l=$projet{'options'}->{'export_'.$type.'_columns'};
6519
6520
  my $i=1;
6521
  my %selected=map { $_=>$i++ } (split(/,+/,$l));
6522
  my %order=();
6523
  @available=('student.copy','student.key','student.name',
6524
	      $projet{_students_list}->heads());
6525
  $i=0;
6526
  for(@available) {
6527
     if($selected{$_}) {
6528
       $i=$selected{$_};
6529
     } else {
6530
       $i.='1';
6531
     }
6532
     $order{$_}=$i;
6533
   }
6534
  @available=sort { $order{$a} cmp $order{$b} } @available;
6535
6536
  my $gcol=read_glade('choose_columns',
6537
		      qw/columns_list columns_instructions/);
6538
6539
  $col=$w{'choose_columns'}->style()->bg('prelight');
6540
  for my $s (qw/normal insensitive/) {
6541
    for my $k (qw/columns_instructions/) {
6542
      $w{$k}->modify_base($s,$col);
6543
    }
6544
  }
6545
  my $columns_store=Gtk2::ListStore->new('Glib::String','Glib::String');
6546
  $w{'columns_list'}->set_model($columns_store);
6547
  my $renderer=Gtk2::CellRendererText->new;
6548
# 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.
6549
  my $column = Gtk2::TreeViewColumn->new_with_attributes (__"column",
6550
						       $renderer,
6551
						       text=> 0);
6552
  $w{'columns_list'}->append_column ($column);
6553
6554
  my @selected_iters=();
6555
  for my $c (@available) {
6556
    my $name=$c;
6557
    $name=__("<full name>") if($c eq 'student.name');
6558
    $name=__("<student identifier>") if($c eq 'student.key');
6559
    $name=__("<student copy>") if($c eq 'student.copy');
6560
    my $iter=$columns_store->append;
6561
    $columns_store->set($iter,
6562
			0,$name,
6563
			1,$c);
6564
    push @selected_iters,$iter if($selected{$c});
6565
  }
6566
  $w{'columns_list'}->set_reorderable(1);
6567
  $w{'columns_list'}->get_selection->set_mode(GTK_SELECTION_MULTIPLE);
6568
  for(@selected_iters) { $w{'columns_list'}->get_selection->select_iter($_); }
6569
6570
  my $resp=$w{'choose_columns'}->run;
6571
  if($resp==1) {
6572
    my @k=();
6573
    my @s=$w{'columns_list'}->get_selection->get_selected_rows;
6574
    for my $i (@s) {
6575
      push @k,$columns_store->get($columns_store->get_iter($i),1);
6576
    }
6577
    $projet{'options'}->{'export_'.$type.'_columns'}=join(',',@k);
6578
  }
6579
6580
  $w{'choose_columns'}->destroy;
6581
}
6582
6583
sub choose_columns_current {
6584
  choose_columns(lc($projet{'options'}->{'format_export'}));
6585
}
6586
6587
#######################################
6588
6589
# PLUGINS
6590
6591
sub plugins_add {
6592
  my $d=Gtk2::FileChooserDialog
6593
    ->new(__("Install an AMC plugin"),
6594
	  $w{'main_window'},'open',
6595
	  'gtk-cancel'=>'cancel',
6596
	  'gtk-ok'=>'ok');
6597
  my $filter=Gtk2::FileFilter->new();
6598
  $filter->set_name(__"Plugins (zip, tgz)");
6599
  for my $ext (qw/ZIP zip TGZ tgz tar.gz TAR.GZ/) {
6600
    $filter->add_pattern("*.$ext");
6601
  }
6602
  $d->add_filter($filter);
6603
6604
  my $r=$d->run;
6605
  if($r eq 'ok') {
6606
    my $plugin=$d->get_filename;
6607
    $d->destroy;
6608
6609
    # unzip in a temporary directory
6610
6611
    my ($temp_dir,$error)=unzip_to_temp($plugin);
6612
6613
    if($error) {
6614
      my $dialog = Gtk2::MessageDialog
6615
	->new_with_markup($w{'main_window'},
6616
			  'destroy-with-parent',
6617
			  'error','ok',
6618
			  sprintf(__("An error occured while trying to extract files from the plugin archive: %s."),$error));
6619
      $dialog->run;
6620
      $dialog->destroy;
6621
      return();
6622
    }
6623
6624
    # checks validity
6625
6626
    my ($nf,$main)=n_fich($temp_dir);
6627
    if($nf<1) {
6628
      my $dialog = Gtk2::MessageDialog
6629
	->new_with_markup($w{'main_window'},
6630
			  'destroy-with-parent',
6631
			  'error','ok',
6632
			  __"Nothing extracted from the plugin archive. Check it.");
6633
      $dialog->run;
6634
      $dialog->destroy;
6635
      return();
6636
    }
6637
    if($nf>1 || !-d $main) {
6638
      my $dialog = Gtk2::MessageDialog
6639
	->new_with_markup($w{'main_window'},
6640
			  'destroy-with-parent',
6641
			  'error','ok',
6642
			  __"This is not a valid plugin, as it contains more than one directory at the first level.");
6643
      $dialog->run;
6644
      $dialog->destroy;
6645
      return();
6646
    }
6647
6648
    if(!-d "$main/perl/AMC") {
6649
      my $dialog = Gtk2::MessageDialog
6650
	->new_with_markup($w{'main_window'},
6651
			  'destroy-with-parent',
6652
			  'error','ok',
6653
			  __"This is not a valid plugin, as it does not contain a perl/AMC subdirectory.");
6654
      $dialog->run;
6655
      $dialog->destroy;
6656
      return();
6657
    }
6658
6659
    my $name=$main;
6660
    $name =~ s/.*\///;
6661
6662
    # already installed?
6663
6664
    if($name=~/[^.]/ && -e "$o_dir/plugins/$name") {
6665
      my $dialog = Gtk2::MessageDialog
6666
	->new_with_markup($w{'main_window'},
6667
			  'destroy-with-parent',
6668
			  'question','yes-no',
6669
			  sprintf(__("A plugin is already installed with the same name (%s). Do you want to delete the old one and overwrite?"),
6670
				  "<b>$name</b>"));
6671
      my $r=$dialog->run;
6672
      $dialog->destroy;
6673
      return if($r ne 'yes');
6674
6675
      remove_tree("$o_dir/plugins/$name",{'verbose'=>0,'safe'=>1,'keep_root'=>0});
6676
    }
6677
6678
    # go!
6679
6680
    debug "Installing plugin $name to $o_dir/plugins";
6681
6682
    if(system('mv',$main,"$o_dir/plugins")!=0) {
6683
      my $dialog = Gtk2::MessageDialog
6684
	->new_with_markup($w{'main_window'},
6685
			  'destroy-with-parent',
6686
			  'error','ok',
6687
			  sprintf(__("Error while moving the plugin to the user plugin directory: %s"),$!));
6688
      my $r=$dialog->run;
6689
      $dialog->destroy;
6690
      return();
6691
    }
6692
6693
    my $dialog = Gtk2::MessageDialog
6694
	->new_with_markup($w{'main_window'},
6695
			  'destroy-with-parent',
6696
			  'info','ok',
6697
			  __"Please restart AMC before using the new plugin...");
6698
      my $r=$dialog->run;
6699
      $dialog->destroy;
6700
6701
  } else {
6702
    $d->destroy;
6703
  }
6704
}
6705
6706
#######################################
6707
6708
sub file_size {
6709
  my ($oo,@files)=@_;
6710
  my $s=0;
6711
  $oo->{recursive}--;
6712
 FILE: for my $f (@files) {
6713
    if(-f $f) {
6714
      $s += -s $f;
6715
    } elsif(-d $f) {
6716
      if($oo->{recursive}>=0) {
6717
	if(opendir(SDIR,$f)) {
6718
	  my @dir_files=map { "$f/$_"; } 
6719
	    grep { !$oo->{pattern} || /$oo->{pattern}/ }
6720
	    grep { ! /^\.{1,2}$/ } readdir(SDIR);
6721
	  closedir(SDIR);
6722
	  $s += file_size({%$oo},@dir_files);
6723
	}
6724
      }
6725
    }
6726
  }
6727
  return($s);
6728
}
6729
6730
my @size_units=('k','M','G','T','P');
6731
6732
sub human_readable_size {
6733
  my ($s)=@_;
6734
  my $i=0;
6735
  while($s>=1024) {
6736
    $s /= 1024;
6737
    $i++;
6738
  }
6739
  if($i==0) {
6740
    return($s);
6741
  } else {
6742
    return(sprintf('%.3g%s',$s,$size_units[$i-1]));
6743
  }
6744
}
6745
6746
my @cleanup_components
6747
  =(
6748
    {id=>'zooms',
6749
     short=>__("zooms"),
6750
     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."),
6751
     size=>sub {
6752
       return(file_size({recursive=>5},absolu('%PROJET/cr/zooms'))
6753
	     + $projet{'_capture'}->zooms_total_size_transaction());
6754
     },
6755
     action=>sub {
6756
       return(remove_tree(absolu('%PROJET/cr/zooms'),
6757
			  {'verbose'=>0,'safe'=>1,'keep_root'=>1})
6758
	     + $projet{'_capture'}->zooms_cleanup_transaction());
6759
     },
6760
    },
6761
    {id=>'matching_reports',
6762
     short=>__("layout reports"),
6763
     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."),
6764
     size=>sub {
6765
       return(file_size({pattern=>'^page-',recursive=>1},
6766
			absolu('%PROJET/cr/')));
6767
     },
6768
     action=>sub {
6769
       my $dir=absolu('%PROJET/cr/');
6770
       if(opendir(CRDIR,$dir)) {
6771
	 my @files=map { "$dir/$_" } grep { /^page-/ } readdir(CRDIR);
6772
	 closedir(CRDIR);
6773
	 return(unlink(@files));
6774
       } else { return(0); }
6775
     },
6776
    },
6777
    {id=>'annotated_pages',
6778
     short=>__("annotated pages"),
6779
     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."),
6780
     size=>sub {
6781
       return(file_size({recursive=>5},absolu('%PROJET/cr/corrections/jpg')));
6782
     },
6783
     action=>sub {
6784
       return(remove_tree(absolu('%PROJET/cr/corrections/jpg'),
6785
			  {'verbose'=>0,'safe'=>1,'keep_root'=>1}));
6786
     },
6787
    },
6788
   );
6789
6790
sub table_sep {
6791
  my ($t,$y,$x)=@_;
6792
  my $sep=Gtk2::HSeparator->new();
6793
  $t->attach($sep,0,$x,$y,$y+1,["expand","fill"],[],0,0);
6794
}
6795
6796
sub cleanup_dialog {
6797
  my %files;
6798
  my %cb;
6799
6800
  my $gap=read_glade('cleanup');
6801
6802
  my $dialog=$gap->get_object('cleanup');
6803
  my $notebook=$gap->get_object('components');
6804
  for my $c (@cleanup_components) {
6805
    my $t=$c->{text};
6806
    my $s=undef;
6807
    if($c->{size}) {
6808
      $s=&{$c->{size}}();
6809
      $t.= "\n".__("Total size of concerned files:")." ".
6810
	human_readable_size($s);
6811
    }
6812
    my $label=Gtk2::Label->new($t);
6813
    $label->set_justify('left');
6814
    $label->set_line_wrap(1);
6815
    $label->set_line_wrap_mode('word');
6816
6817
    my $check=Gtk2::CheckButton->new;
6818
    $c->{check}=$check;
6819
    my $short_label=Gtk2::Label->new($c->{short});
6820
    $short_label->set_justify('center');
6821
    $short_label->set_sensitive(!( defined($s) && $s==0));
6822
    my $hb=Gtk2::HBox->new();
6823
    $hb->pack_start($check,FALSE,FALSE,0);
6824
    $hb->pack_start($short_label,TRUE,TRUE,0);
6825
    $hb->show_all;
6826
6827
    $notebook->append_page_menu($label,$hb,undef);
6828
  }
6829
  $notebook->show_all;
6830
6831
  my $reponse=$dialog->run();
6832
6833
  for my $c (@cleanup_components) {
6834
    $c->{active}=$c->{check}->get_active();
6835
  }
6836
6837
  $dialog->destroy();
6838
  Gtk2->main_iteration while ( Gtk2->events_pending );
6839
6840
  debug "RESPONSE=$reponse";
6841
6842
  return() if($reponse!=10);
6843
6844
  my $n=0;
6845
6846
  for my $c (@cleanup_components) {
6847
    if($c->{active}) {
6848
      debug "Removing ".$c->{id}." ...";
6849
      $n+=&{$c->{action}};
6850
    }
6851
  }
6852
6853
  $dialog = Gtk2::MessageDialog
6854
    ->new($w{'main_window'},
6855
	  'destroy-with-parent',
6856
	  'info','ok',
6857
	  __("%s files were removed."),$n);
6858
  $dialog->run;
6859
  $dialog->destroy;
6860
}
6861
6862
#######################################
6863
6864
if($o{'conserve_taille'} && $o{'taille_x_main'} && $o{'taille_y_main'}) {
6865
    $w{'main_window'}->resize($o{'taille_x_main'},$o{'taille_y_main'});
6866
}
6867
6868
projet_ouvre($ARGV[0]);
6869
6870
#######################################
6871
# For MacPorts with latexfree variant, for example
6872
6873
if("0" =~ /(1|true|yes)/i) {
6874
    my $message='';
6875
    if(!commande_accessible("kpsewhich")) {
6876
	$message=sprintf(__("I don't find the command %s."),"kpsewhich")
6877
	    .__("Perhaps LaTeX is not installed?");
6878
    } else {
6879
	if(!get_sty()) {
6880
# TRANSLATORS: Do not translate 'auto-multiple-choice latex-link', which is a command to be typed on MacOsX
6881
	    $message=__("The style file automultiplechoice.sty seems to be unreachable. Try to use command 'auto-multiple-choice latex-link' as root to fix this.");
6882
	}
6883
    }
6884
    if($message) {
6885
	my $dialog = Gtk2::MessageDialog
6886
	    ->new($w{'main_window'},
6887
		  'destroy-with-parent',
6888
		  'error','ok',$message);
6889
	$dialog->run;
6890
	$dialog->destroy;
6891
    }
6892
}
6893
6894
#######################################
6895
6896
test_commandes();
6897
6898
Gtk2->main();
6899
6900
1;
6901
6902
__END__
6903