# Copyright (C) 2008-2025 Alexis Bienvenüe <paamc@passoire.fr>
#
# This file is part of Auto-Multiple-Choice
#
# Auto-Multiple-Choice is free software: you can redistribute it
# and/or modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation, either version 2 of
# the License, or (at your option) any later version.
#
# Auto-Multiple-Choice is distributed in the hope that it will be
# useful, but WITHOUT ANY WARRANTY; without even the implied warranty
# of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Auto-Multiple-Choice.  If not, see
# <http://www.gnu.org/licenses/>.

use warnings;
use 5.012;

package AMC::Gui::Manuel;

use Getopt::Long;
use Gtk3 -init;
use Glib qw/TRUE FALSE/;

use XML::Simple;
use File::Spec::Functions
  qw/splitpath catpath splitdir catdir catfile rel2abs tmpdir/;
use File::Temp qw/ tempfile tempdir /;

use AMC::Path;
use AMC::Basic;
use AMC::Gui::PageArea;
use AMC::Gui::WindowSize;
use AMC::Data;
use AMC::DataModule::capture qw/:zone/;

use constant {
    MDIAG_ID         => 0,
    MDIAG_ID_BACK    => 1,
    MDIAG_EQM        => 2,
    MDIAG_DELTA      => 3,
    MDIAG_EQM_BACK   => 4,
    MDIAG_DELTA_BACK => 5,
    MDIAG_I          => 6,
    MDIAG_STUDENT    => 7,
    MDIAG_PAGE       => 8,
    MDIAG_COPY       => 9,
    MDIAG_WHY        => 10,
};

use_gettext;

sub new {
    my %o    = (@_);
    my $self = {
        'data-dir'         => '',
        'project-dir'      => '',
        sujet              => '',
        etud               => '',
        dpi                => 75,
        seuil              => 0.1,
        seuil_up           => 1.0,
        seuil_sens         => 8.0,
        seuil_eqm          => 3.0,
        fact               => 1 / 4,
        iid                => 0,
        displayed_iid      => -1,
        global             => 0,
        en_quittant        => '',
        encodage_interne   => 'UTF-8',
        image_type         => 'xpm',
        editable           => 1,
        multiple           => 0,
        onscan             => '',
        size_monitor       => '',
        invalid_color_name => "#FFEF3B",
        empty_color_name   => "#78FFED",
        navigate_q_id      => -1,
        navigate_q_margin  => 10,
    };

    for ( keys %o ) {
        $self->{$_} = $o{$_} if ( defined( $self->{$_} ) );
    }

    bless $self;

    # recupere la liste des fichiers MEP des pages qui correspondent

    $self->{data}    = AMC::Data->new( $self->{'data-dir'} );
    $self->{layout}  = $self->{data}->module('layout');
    $self->{capture} = $self->{data}->module('capture');
    $self->{scoring} = $self->{data}->module('scoring');
    $self->{scoring} = $self->{data}->module('scoring');

    die "No PDF subject file" if ( !$self->{sujet} );
    die "Subject file " . $self->{sujet} . " not found"
      if ( !-f $self->{sujet} );

    my $temp_loc = tmpdir();
    $self->{'temp-dir'} = tempdir(
        DIR     => $temp_loc,
        CLEANUP => ( !get_debug() )
    );

    $self->{'tmp-image'} = $self->{'temp-dir'} . "/page";

    ## GUI

    my $glade_xml = __FILE__;
    $glade_xml =~ s/\.p[ml]$/.glade/i;

    $self->{gui} = Gtk3::Builder->new();
    $self->{gui}->set_translation_domain('auto-multiple-choice');
    $self->{gui}->add_from_file($glade_xml);

    for my $k (
        qw/general area navigation_h navigation_v goto goto_v
           diag_tree button_photocopy scan_view navigate
           navigate_question scan_yadjust
           button_erase button_cancel/
      )
    {
        $self->{$k} = $self->{gui}->get_object($k);
    }

    $self->{general}->set_title( __ "Page layout" )
      if ( !$self->{editable} );

    if ( !$self->{editable} ) {
        $self->{navigation_v}->show();
    } else {
        $self->{scan_view_model} = cb_model( 0, __("Original"), 1, __("Scan") );
        $self->{scan_view}->set_model( $self->{scan_view_model} );

        $self->{navigate_model} = cb_model(
            0,
            __(
          # TRANSLATORS: This is one of the choices for pages navigation in
          # manual data capture window. Here, navigation goes through all
          # pages. Please keep this text very short (say less than 5 letters) so
          # that the window is not too large
                "all"
            ),

            1,
            __(
          # TRANSLATORS: This is one of the choices for pages navigation in
          # manual data capture window. Here, navigation goes through pages with
          # some invalid answers. Please keep this text very short (say less
          # than 5 letters) so that the window is not too large
                "inv"
            ),

            2,
            __(
          # TRANSLATORS: This is one of the choices for pages navigation in
          # manual data capture window. Here, navigation goes through pages with
          # empty or invalid answers. Please keep this text very short (say less
          # than 5 letters) so that the window is not too large
                "i&e"
            )
        );
        $self->{navigate}->set_model( $self->{navigate_model} );
        $self->{navigate}
          ->set_active_iter( $self->{navigate_model}->get_iter_first );

        # get list of questions IDs

        $self->{capture}->begin_read_transaction;
        my @questions =
          sort { $a->{name} cmp $b->{name} } $self->{layout}->questions_list();
        $self->{capture}->end_transaction;

        $self->{navigate_question_model} =
          cb_model( -1, __("all"),
            map { $_->{question}, $_->{name} } @questions );
        $self->{navigate_question}
          ->set_model( $self->{navigate_question_model} );
        $self->{navigate_question}
          ->set_active_iter( $self->{navigate_question_model}->get_iter_first );

        $self->{navigate_question}
          ->signal_connect( "changed", \&update_nav_question, $self );

        # show navigation tab

        $self->{button_erase}->show();
        $self->{button_cancel}->show();
        $self->{navigation_h}->show();
        $self->{button_photocopy}->show() if ( $self->{multiple} );
    }

    $self->{cursor_watch} = Gtk3::Gdk::Cursor->new('GDK_WATCH');

    Gtk3::main_iteration while (Gtk3::events_pending);

    AMC::Gui::PageArea::add_feuille(
        $self->{area},
        yfactor            => 2,
        invalid_color_name => $self->{invalid_color_name},
        empty_color_name   => $self->{empty_color_name},
        editable           => $self->{editable},
        marks              => ( $self->{editable} ? '' : 'blue' ),
        zones              => ( $self->{editable} ? '' : 'lightblue' ),
    );

    AMC::Gui::WindowSize::size_monitor( $self->window, $self->{size_monitor} )
      if ( $self->{size_monitor} );

    Gtk3::main_iteration while (Gtk3::events_pending);

    ### modele DIAGNOSTIQUE SAISIE

    if ( $self->{editable} ) {
        my ( $renderer, $column );

        $renderer = Gtk3::CellRendererText->new;
        $column   = Gtk3::TreeViewColumn->new_with_attributes(
            __ "page",
            $renderer,
            text       => MDIAG_ID,
            background => MDIAG_ID_BACK
        );
        $column->set_sort_column_id(MDIAG_ID);
        $self->{diag_tree}->append_column($column);

        $renderer = Gtk3::CellRendererText->new;
        $column   = Gtk3::TreeViewColumn->new_with_attributes(
            __ "MSE",
            $renderer,
            text       => MDIAG_EQM,
            background => MDIAG_EQM_BACK
        );
        $column->set_sort_column_id(MDIAG_EQM);
        $self->{diag_tree}->append_column($column);

        $renderer = Gtk3::CellRendererText->new;
        $column   = Gtk3::TreeViewColumn->new_with_attributes(
            __ "sensitivity",
            $renderer,
            text       => MDIAG_DELTA,
            background => MDIAG_DELTA_BACK
        );
        $column->set_sort_column_id(MDIAG_DELTA);
        $self->{diag_tree}->append_column($column);
    }

    $self->{general}->get_window()->set_cursor( $self->{cursor_watch} );
    Gtk3::main_iteration while (Gtk3::events_pending);

    $self->maj_list_all;

    $self->{general}->get_window()->set_cursor(undef);

    $self->{gui}->connect_signals( undef, $self );

    $self->select_page(0);

    return ($self);
}

sub new_diagstore {
    my ($self) = @_;
    my $diag_store = Gtk3::ListStore->new(
        'Glib::String', 'Glib::String', 'Glib::String', 'Glib::String',
        'Glib::String', 'Glib::String', 'Glib::String', 'Glib::String',
        'Glib::String', 'Glib::String', 'Glib::String',
    );
    $diag_store->set_sort_func( MDIAG_EQM,   \&sort_num, MDIAG_EQM );
    $diag_store->set_sort_func( MDIAG_DELTA, \&sort_num, MDIAG_DELTA );
    $diag_store->set_sort_func( MDIAG_ID,
        \&sort_from_columns,
        [
            { type => 'n', col => MDIAG_STUDENT },
            { type => 'n', col => MDIAG_COPY },
            { type => 'n', col => MDIAG_PAGE },
        ]
    );
    $self->{diag_store} = $diag_store;
    return ($diag_store);
}

sub sort_diagstore {
    my ($self) = @_;
    $self->{diag_store}->set_sort_column_id( MDIAG_ID, 'ascending' );
}

sub show_diagstore {
    my ($self) = @_;
    $self->{diag_tree}->set_model( $self->{diag_store} );
}

sub window {
    my ($self) = @_;
    return ( $self->{general} );
}

###

sub scan_view_change {
    my ($self) = @_;
    $self->{onscan} = $self->{scan_view}->get_active();
    $self->ecrit();
    $self->{area}->{onscan} = $self->{onscan};
    $self->charge_i();
}

sub current_iter {
    my ($self)         = @_;
    my @sel            = $self->{diag_tree}->get_selection->get_selected_rows();
    my $first_selected = $sel[0]->[0];
    if ( defined($first_selected) ) {
        return ( $self->{diag_store}->get_iter($first_selected) );
    } else {
        return ();
    }
}

sub iter_to_spc {
    my ( $self, $iter ) = @_;
    if ($iter) {
        return ( $self->{diag_store}
              ->get( $iter, MDIAG_STUDENT, MDIAG_PAGE, MDIAG_COPY ) );
    } else {
        return ();
    }
}

sub current_spc {
    my ($self) = @_;
    return ( $self->iter_to_spc( $self->current_iter ) );
}

sub displayed_iter {
    my ($self) = @_;
    if ( defined( $self->{displayed_iid} )
        && $self->{displayed_iid} >= 0 )
    {
        return (
            model_id_to_iter(
                $self->{diag_store}, MDIAG_I, $self->{displayed_iid}
            )
        );
    }
}

sub displayed_spc {
    my ($self) = @_;
    return ( $self->iter_to_spc( $self->displayed_iter ) );
}

sub page_selected {
    my ($self) = @_;
    my $current = $self->current_iter;
    if ($current) {
        $self->ecrit();
        $self->{iid} = $self->{diag_store}->get( $current, MDIAG_I );
        $self->charge_i();
    }
    return TRUE;
}

sub select_page_from_iter {
    my ( $self, $iter ) = @_;
    $self->{diag_tree}
      ->set_cursor( $self->{diag_store}->get_path($iter), undef, FALSE )
      if ($iter);
}

sub select_page {
    my ( $self, $iid ) = @_;
    $self->select_page_from_iter(
        model_id_to_iter( $self->{diag_store}, MDIAG_I, $iid ) );
}

sub maj_list_all {
    my ( $self, $select_spc ) = @_;

    $self->{capture}->begin_read_transaction;
    my $summary = $self->{capture}->summaries(
        darkness_threshold    => $self->{seuil},
        darkness_threshold_up => $self->{seuil_up},
        sensitivity_threshold => $self->{seuil_sens},
        mse_threshold         => $self->{seuil_eqm},
        why                   => 1
    );
    my $capture_free = [];
    push @$capture_free, @{ $self->{capture}->question_only_pages }
      if ( !$self->{editable} );
    push @$capture_free, @{ $self->{capture}->no_capture_pages };
    $self->{capture}->end_transaction;

    my $diag_store = $self->new_diagstore;

    debug "Adding " . ( 1 + $#$summary ) . " summaries to list...";
    my $select_iid = -1;
    my $i          = 0;
    for my $p (@$summary) {
        $diag_store->insert_with_values(
            $i,
            MDIAG_I, $i,
            MDIAG_ID, pageids_string( $p->{student}, $p->{page}, $p->{copy} ),
            MDIAG_STUDENT,    $p->{student},
            MDIAG_PAGE,       $p->{page},
            MDIAG_COPY,       $p->{copy},
            MDIAG_ID_BACK,    $p->{color},
            MDIAG_EQM,        $p->{mse_string},
            MDIAG_EQM_BACK,   $p->{mse_color},
            MDIAG_DELTA,      $p->{sensitivity_string},
            MDIAG_DELTA_BACK, $p->{sensitivity_color},
            MDIAG_WHY,        $p->{why},
        );
        $select_iid = $i
          if ( $select_spc
            && $select_spc->[0] == $p->{student}
            && $select_spc->[1] == $p->{page}
            && $select_spc->[2] == $p->{copy} );
        $i++;
    }
    debug "Adding " . ( 1 + $#$capture_free ) . " free captures...";
    for my $p (@$capture_free) {
        $diag_store->insert_with_values(
            $i,                  MDIAG_I,
            $i,                  MDIAG_ID,
            pageids_string(@$p), MDIAG_STUDENT,
            $p->[0],             MDIAG_PAGE,
            $p->[1],             MDIAG_COPY,
            $p->[2],             MDIAG_ID_BACK,
            undef,               MDIAG_EQM,
            '',                  MDIAG_EQM_BACK,
            undef,               MDIAG_DELTA,
            '',                  MDIAG_DELTA_BACK,
            undef,               MDIAG_WHY,
            '',
        );
        $select_iid = $i
          if ( $select_spc
            && $select_spc->[0] == $p->[0]
            && $select_spc->[1] == $p->[1]
            && $select_spc->[2] == $p->[2] );
        $i++;
    }
    debug "Sorting...";
    $self->sort_diagstore;
    debug "List complete.";
    $self->show_diagstore;

    $self->select_page($select_iid) if ($select_iid);
}

sub maj_list_i {
    my ($self) = @_;
    return if ( !$self->{editable} );

    my $iter = $self->displayed_iter;
    return if ( !$iter );
    my @spc = $self->iter_to_spc($iter);
    return if ( !@spc );

    debug "List update for SPC=" . join( ',', @spc );

    $self->{capture}->begin_read_transaction('lUPD');
    my %ps = $self->{capture}->page_summary(
        @spc,
        mse_threshold         => $self->{seuil_eqm},
        darkness_threshold    => $self->{seuil},
        darkness_threshold_up => $self->{seuil_up},
        sensitivity_threshold => $self->{seuil_sens},
    );
    $self->{capture}->end_transaction('lUPD');

    for my $k ( keys %ps ) {
        debug( " - $k = " . ( defined( $ps{$k} ) ? $ps{$k} : '<undef>' ) );
    }

    $self->{diag_store}->set(
        $iter,                   MDIAG_ID_BACK,
        $ps{color},              MDIAG_EQM,
        $ps{mse_string},         MDIAG_EQM_BACK,
        $ps{mse_color},          MDIAG_DELTA,
        $ps{sensitivity_string}, MDIAG_DELTA_BACK,
        $ps{sensitivity_color},
    );
}

sub choix {
    my ( $self, $widget, $event ) = (@_);
    $widget->choix($event);
}

sub une_modif {
    my ($self) = @_;
    $self->{area}->modif();
}

sub page_id {
    my $i = shift;
    return (
        "+" . join( '/', map { $i->{$_} } (qw/student page checksum/) ) . "+" );
}

sub charge_i {
    my ($self) = @_;

    $self->{layinfo} = {};

    my $current_iter = $self->current_iter;
    my @spc          = $self->iter_to_spc($current_iter);

    if ( !@spc ) {
        $self->{area}->set_content();
        $self->{displayed_iid} = -1;
        return ();
    }

    $self->{displayed_iid} = $self->{diag_store}->get( $current_iter, MDIAG_I );

    debug "ID " . pageids_string(@spc);

    $self->{layout}->begin_read_transaction;

    debug "page_info";

    my @ep = @spc[ 0, 1 ];

    $self->{info} = $self->{layout}->page_info(@ep);
    my $page = $self->{info}->{subjectpage};

    my $scan_file = proj2abs(
        { '%PROJET' => $self->{'project-dir'} },
        $self->{capture}->get_scan_page(@spc)
    );

    debug "PAGE $page";

    ################################
    # fabrication du xpm
    ################################

    my $display_image = '';
    my $tmp_image     = '';
    my $tmp_ppm       = '';

    debug "Making XPM";

    if ( $self->{onscan} && -f $scan_file ) {

        $display_image = $scan_file;

    } else {

        $self->{general}->get_window()->set_cursor( $self->{cursor_watch} );
        Gtk3::main_iteration while (Gtk3::events_pending);

        system_debug(
            cmd => [
                "pdftoppm", "-f", $page, "-l", $page, "-r", $self->{dpi},
                $self->{sujet}, $self->{'temp-dir'} . "/page"
            ]
        );

        # recherche de ce qui a ete fabrique...
        opendir( TDIR, $self->{'temp-dir'} )
          || die "can't opendir $self->{'temp-dir'} : $!";
        my @candidats =
          grep { /^page-.*\.ppm$/ && -f $self->{'temp-dir'} . "/$_" }
          readdir(TDIR);
        closedir TDIR;
        debug "Candidates : " . join( ' ', @candidats );
        $tmp_ppm   = $self->{'temp-dir'} . "/" . $candidats[0];
        $tmp_image = $tmp_ppm;

        if ( $self->{image_type} && $self->{image_type} ne 'ppm' ) {
            $tmp_image = $self->{'tmp-image'} . "." . $self->{image_type};
            debug "ppmto" . $self->{image_type} . " : $tmp_ppm -> $tmp_image";
            system( "ppmto"
                  . $self->{image_type}
                  . " \"$tmp_ppm\" > \"$tmp_image\"" );
        }

        $display_image = $tmp_image;

    }

    ################################
    # synchro variables
    ################################

    if ( $spc[2] == 0 && $self->{multiple} && $self->{editable} ) {
        $self->{layinfo}->{block_message} = sprintf(
            __
"This is a template exam that you cannot edit. To create a new exam from this one to be edited, use the '%s' button.",
            __ "Add photocopy"
        );
    } else {

        debug "Getting layout info";

        for (qw/box questionbox namefield idzone digit scorezone/) {
            $self->{layinfo}->{$_} = [];
        }

        if ( $self->{onscan} ) {
            my %ci = ();
            for my $c ( @{ $self->{capture}->get_zones_corners(@spc) } ) {
                %ci = (
                    %$c,
                    xy   => [],
                    xmin => $c->{x},
                    xmax => $c->{x},
                    ymin => $c->{y},
                    ymax => $c->{y},
                ) if ( $c->{corner} == 1 );
                push @{ $ci{xy} }, $c->{x}, $c->{y};
                $ci{xmax} = $c->{x} if ( $c->{x} > $ci{xmax} );
                $ci{ymax} = $c->{y} if ( $c->{y} > $ci{ymax} );
                $ci{xmin} = $c->{x} if ( $c->{x} < $ci{xmin} );
                $ci{ymin} = $c->{y} if ( $c->{y} < $ci{ymin} );
                push @{ $self->{layinfo}->{box} }, {%ci}
                  if ( $c->{corner} == 4 );
            }
        } else {
            my $c;
            my $sth;

            for my $type (qw/box questionbox scorezone digit namefield idzone/) {
                for my $c ( $self->{layout}->type_info( $type, @ep ) ) {
                    push @{ $self->{layinfo}->{$type} }, {%$c};
                }
            }

            $self->{layinfo}->{page} = $self->{layout}->page_info(@ep);
        }

        if ( $self->{editable} ) {

            # gets scoring results to highlight empty and invalid answers

            for my $a ( @{ $self->{layinfo}->{box} } ) {
                $a->{scoring} = $self->{scoring}
                  ->question_result( @spc[ 0, 2 ], $a->{question} );
            }

            # mise a jour des cases suivant saisies deja presentes

            for my $i ( @{ $self->{layinfo}->{box} } ) {
                my $id = $i->{question} . "." . $i->{answer};
                my $t  = $self->{capture}->ticked(
                    @spc[ 0, 2 ], $i->{question}, $i->{answer},
                    $self->{seuil}, $self->{seuil_up}
                );
                $t = '' if ( !defined($t) );
                debug "Q=$id R=$t";
                $i->{id}     = [@spc];
                $i->{ticked} = $t;
            }
        }
    }

    $self->{layout}->end_transaction;

    # utilisation

    $self->{area}->set_content(
        image       => $display_image,
        layout_info => $self->{layinfo}
    );

    unlink($tmp_ppm) if ($tmp_ppm);
    unlink($tmp_image)
      if ( $tmp_image && ( $tmp_ppm ne $tmp_image ) && !get_debug() );

    # fin du traitement...

    $self->{general}->get_window()->set_cursor(undef);
}

sub ecrit {
    my ($self) = (@_);

    return if ( !$self->{editable} );

    my @spc = $self->displayed_spc;
    return if ( !@spc );

    if ( $self->{area}->modifs() ) {
        debug "Saving " . pageids_string(@spc);

        $self->{capture}->begin_transaction('manw');
        $self->{capture}->outdate_annotated_page(@spc);

        $self->{capture}->set_page_manual( @spc, time() );

        for my $i ( @{ $self->{layinfo}->{box} } ) {
            $self->{capture}->set_manual( @{ $i->{id} },
                ZONE_BOX, $i->{question}, $i->{answer},
                ( $i->{ticked} ? 1 : 0 ) );
        }

        $self->{capture}->end_transaction('manw');

        $self->synchronise();
    }
}

sub synchronise {
    my ($self) = (@_);

    $self->{area}->sync();

    $self->maj_list_i;
}

sub update_nav_question {
    my ( $w, $self ) = @_;
    $self->{navigate_q_id} = get_active_id($w);
    if ( $self->{navigate_q_id} >= 0 ) {
        $self->{layout}->begin_read_transaction;
        my @pages =
          $self->{layout}->pages_for_question( $self->{navigate_q_id} );
        $self->{pages_ok} =
          { map { pageids_string( $_->{student}, $_->{page} ) => $_->{miny} }
              @pages };
        $self->{layout}->end_transaction;
        debug "NAVQ_PAGES=" . join( ', ', keys %{ $self->{pages_ok} } );
        $self->move_if_not_ok();
    }
}

sub navigate_ok {
    my ( $self, $nav_mode, $path ) = @_;
    my $iter = $self->{diag_store}->get_iter($path);
    if ( $self->{navigate_q_id} >= 0 ) {
        my $student = $self->{diag_store}->get( $iter, MDIAG_STUDENT );
        my $page    = $self->{diag_store}->get( $iter, MDIAG_PAGE );
        return (0)
          if (
            !exists( $self->{pages_ok}->{ pageids_string( $student, $page ) } )
          );
    }
    return (1) if ( $nav_mode == 0 );
    my $why = $self->{diag_store}->get( $iter, MDIAG_WHY );
    return (0) if ( $nav_mode == 1 && $why !~ /E/ );
    return (0) if ( $nav_mode == 2 && $why !~ /[EV]/ );
    return (1);
}

sub passe_precedent {
    my ($self) = @_;
    my $nav    = $self->{navigate}->get_active();
    my ($path) = $self->{diag_tree}->get_cursor();
    if ($path) {
        my $ok;
        do {
            $ok = $path->prev();
        } while ( $ok && !$self->navigate_ok( $nav, $path ) );
        $self->{diag_tree}->set_cursor( $path, undef, FALSE ) if ($ok);
        $self->focus();
    } else {
        debug "Previous: no path";
    }
}

sub passe_suivant {
    my ($self) = @_;
    my $nav    = $self->{navigate}->get_active();
    my ($path) = $self->{diag_tree}->get_cursor();
    my $n      = $self->{diag_store}->iter_n_children(undef);
    if ($path) {
        do {
            $path->next();
          } while ( $path->to_string < $n
            && !$self->navigate_ok( $nav, $path ) );
        $self->{diag_tree}->set_cursor( $path, undef, FALSE )
          if ( $path->to_string < $n );
        $self->focus();
    } else {
        debug "Next: no path";
    }
}

sub focus {
    my ($self) = @_;
    if ( $self->{navigate_q_id} >= 0 ) {
        my $miny = $self->{area}->question_miny( $self->{navigate_q_id} ) -
          $self->{navigate_q_margin};
        $miny = 0 if ( $miny < 0 );
        my $adj_upper = $miny + $self->{scan_yadjust}->get_page_size();
        $adj_upper = $self->{area}->{ty}
          if ( $adj_upper < $self->{area}->{ty} );
        $self->{scan_yadjust}->set_upper($adj_upper);
        $self->{scan_yadjust}->set_value($miny);
    } else {
        $self->{scan_yadjust}->set_upper( $self->{area}->{ty} );
    }
}

sub move_if_not_ok {
    my ($self) = @_;
    my $nav    = $self->{navigate}->get_active();
    my ($path) = $self->{diag_tree}->get_cursor();
    if ( !$self->navigate_ok( $nav, $path ) ) {
        $self->passe_suivant();
    }
    $self->focus();
}

sub annule {
    my ($self) = (@_);

    $self->charge_i();
}

sub efface_saisie {
    my ($self) = (@_);

    my @spc = $self->displayed_spc;
    return if ( !@spc );

    debug "Ereasing manual data for SPC=" . join( ',', @spc );

    $self->{capture}->begin_transaction('manx');
    $self->{capture}->outdate_annotated_page(@spc);
    $self->{capture}->remove_manual(@spc);
    $self->{capture}->end_transaction('manx');

    $self->synchronise();
    $self->charge_i();
}

sub duplique_saisie {
    my ($self) = @_;

    my ( $student, $page, $copy ) = $self->displayed_spc;
    return if ( !defined($student) );

    $self->{capture}->begin_transaction;
    $copy = $self->{capture}->new_page_copy( $student, $page );
    annotate_source_change( $self->{capture} );
    $self->{capture}->set_page_manual( $student, $page, $copy, -1 );
    $self->{capture}->end_transaction;

    $self->maj_list_all( [ $student, $page, $copy ] );
}

sub ok_quitter {
    my ($self) = (@_);

    $self->ecrit();
    $self->quitter();
}

sub quitter {
    my ($self) = (@_);
    if ( $self->{global} ) {
        Gtk3->main_quit;
    } else {
        $self->{general}->destroy;
        if ( $self->{en_quittant} ) {
            &{ $self->{en_quittant} }();
        }
    }
}

sub goto_activate_cb {
    my ($self) = (@_);

    my $dest = $self->{ ( $self->{editable} ? 'goto' : 'goto_v' ) }->get_text();
    my $iid  = -1;

    $self->ecrit();

    debug "Go to $dest";

    # recherche d'un ID correspondant
    $dest .= '/' if ( $dest !~ m:/: );

    $self->select_page_from_iter(
        model_id_to_iter( $self->{diag_store}, 're:' . MDIAG_ID, $dest ) );
}

1;

__END__
