AMC-gui.pl

Sylvain L., 11/03/2015 12:05 am

Download (207.4 kB)

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