From Code to Community: Sponsoring The Perl and Raku Conference 2025 Learn more

#
# This file is part of Config-Model-TkUI
#
# This software is Copyright (c) 2008-2021 by Dominique Dumont.
#
# This is free software, licensed under:
#
# The GNU Lesser General Public License, Version 2.1, February 1999
#
use strict;
use Carp;
use vars qw/$icon_path/;
Construct Tk::Widget 'ConfigModelWizard';
my $logger = Log::Log4perl::get_logger('Tk::Wizard');
my @fbe1 = qw/-fill both -expand 1/;
my @fxe1 = qw/-fill x -expand 1/;
my @fx = qw/-fill x /;
sub ClassInit {
my ( $class, $mw ) = @_;
# ClassInit is often used to define bindings and/or other
# resources shared by all instances, e.g., images.
# cw->Advertise(name=>$widget);
}
sub Populate {
my ( $cw, $args ) = @_;
foreach my $parm (qw/-root/) {
my $attr = $parm;
$attr =~ s/^-//;
$cw->{$attr} = delete $args->{$parm}
or croak "Missing $parm arg\n";
}
foreach my $parm (qw/-from_widget -stop_on_important -store_cb -show_cb -end_cb -font/) {
my $attr = $parm;
$attr =~ s/^-//;
$cw->{$attr} = delete $args->{$parm};
}
$logger->info("Creating wizard widget");
$cw->{show_cb} ||= sub { };
$cw->{store_cb} ||= sub { };
$cw->{has_stopped} = 0;
my $title = delete $args->{'-title'}
|| "config wizard " . $cw->{root}->config_class_name;
$cw->Label(
-text => "Configuration of " . $cw->{root}->config_class_name,
)->pack;
my $ed = $cw->{ed_frame} = $cw->Frame->pack(qw/-pady 0 -fill both -expand 1 -anchor n/);
$cw->{ed_frame}->packPropagate(0);
$args->{-title} = $title;
$cw->Advertise( ed_frame => $ed, );
$cw->ConfigSpecs(
-font => [['SELF','DESCENDANTS'], 'font','Font', $cw->{font} ],
#-background => ['DESCENDANTS', 'background', 'Background', $background],
#-selectbackground => [$hlist, 'selectBackground', 'SelectBackground',
# $selectbackground],
-width => [ $ed, undef, undef, 600 ],
-height => [ $ed, undef, undef, 400 ],
DEFAULT => [$ed]
);
$cw->{font} //= $cw->cget('-font');
$cw->SUPER::Populate($args);
}
sub save {
my $cw = shift;
$cw->check();
$logger->info("Saving data in default directory with instance write_back");
$cw->{root}->instance->write_back();
}
sub leaf_cb {
my ( $cw, $scanner, $data_ref, $node, $element_name, $index, $leaf_object ) = @_;
$cw->{has_stopped} = 1;
# cleanup existing widget contained in this frame
$cw->{show_cb}->($leaf_object);
$cw->{ed_w} = $cw->{ed_frame}->ConfigModelLeafEditor(
-item => $leaf_object,
-store_cb => $cw->{store_cb},
-font => $cw->{font},
)->pack(@fbe1);
}
sub list_element_cb {
my ( $cw, $scanner, $data_ref, $node, $element_name, @indexes ) = @_;
$cw->{has_stopped} = 1;
# cleanup existing widget contained in this frame
my $obj = $node->fetch_element($element_name);
$cw->{show_cb}->($obj);
$cw->{ed_w} = $cw->{ed_frame}->ConfigModelListEditor(
-item => $obj,
-store_cb => $cw->{store_cb},
-font => $cw->{font},
)->pack(@fbe1);
}
sub hash_element_cb {
my ( $cw, $scanner, $data_ref, $node, $element_name, @keys ) = @_;
$cw->{has_stopped} = 1;
# cleanup existing widget contained in this frame
my $obj = $node->fetch_element($element_name);
$cw->{show_cb}->($obj);
$cw->{ed_w} = $cw->{ed_frame}->ConfigModelHashEditor(
-item => $obj,
-store_cb => $cw->{store_cb},
-font => $cw->{font},
)->pack(@fbe1);
}
sub check_list_element_cb {
my ( $cw, $scanner, $data_ref, $node, $element_name, @items ) = @_;
$cw->{has_stopped} = 1;
# cleanup existing widget contained in this frame
my $obj = $node->fetch_element($element_name);
$cw->{show_cb}->($obj);
$cw->{ed_w} = $cw->{ed_frame}->ConfigModelCheckListEditor(
-item => $obj,
-store_cb => $cw->{store_cb},
-font => $cw->{font},
)->pack(@fbe1);
}
sub prepare_wizard {
my ( $cw, %args ) = @_;
my $text =
'The wizard will scan all configuration items and stop on '
. '"important" items or on error (like missing mandatory values). If no '
. '"important" item and no error are found, the wizard will exit immediately';
my $edf = $cw->{ed_frame};
my $textw =
$edf->ROText( qw/-relief flat -wrap word -height 8/, -font => $cw->{font});
$textw->insert( end => $text );
$textw->pack( qw/-side top -anchor n/, @fxe1 );
my $stop_on_warn = 0;
$edf->Checkbutton( -text => 'stop on warning', -variable => \$stop_on_warn , -font => $cw->{font})
->pack(qw/-side top -anchor w/);
$edf->Button(
-text => 'OK',
-font => $cw->{font},
-command => sub { $cw->start_wizard(stop_on_warning => $stop_on_warn) }
)->pack(qw/-side right -anchor e/);
$edf->Button(
-text => 'cancel',
-font => $cw->{font},
-command => sub { $cw->destroy_wizard() }
)->pack(qw/-side left -anchor w/);
}
sub start_wizard {
my ( $cw, %args ) = @_;
my $button_f = $cw->Frame->pack(qw/-pady 0 -fill x -expand 1/);
$cw->{has_stopped} = 0;
my $back = $button_f->Button(
-text => 'Back',
-font => $cw->{font},
-command => sub {
$cw->{keep_wiz_editor} = 0;
$cw->{wizard}->go_backward;
} );
$back->pack(qw/-side left -fill x -expand 1/);
my $stop = $button_f->Button(
-text => 'Store and stop',
-font => $cw->{font},
-command => sub {
$cw->{ed_w}->store if $cw->{ed_w}->can('store');
$cw->{keep_wiz_editor} = 0;
$cw->{wizard}->bail_out;
} );
$stop->pack(qw/-side left -fill x -expand 1/);
my $quit = $button_f->Button(
-text => 'quit wizard',
-font => $cw->{font},
-command => sub {
$cw->{keep_wiz_editor} = 0;
$cw->{wizard}->bail_out;
} );
$quit->pack(qw/-side left -fill x -expand 1/);
my $forw = $button_f->Button(
-text => 'Next',
-font => $cw->{font},
-command => sub {
$cw->{keep_wiz_editor} = 0;
$cw->{wizard}->go_forward;
} );
$forw->pack(qw/-side right -fill x -expand 1/);
$cw->{keep_wiz_editor} = 1;
my %cb_table;
# a local event loop is run within the call-back
foreach my $cb_key (
qw/leaf_cb check_list_element_cb
list_element_cb hash_element_cb/
) {
$cb_table{$cb_key} = sub {
my ( $scanner, $data_ref, $node, $element_name ) = @_;
my @all_args = @_; # @_ does not work in try blocks
$logger->info( "$cb_key (element $element_name) called on '",
$node->name, "'->'$element_name'" );
map { $_->destroy if Tk::Exists($_) } $cw->{ed_frame}->children;
$cw->{keep_wiz_editor} = 1;
try {
$cw->$cb_key(@all_args);
}
catch {
$cw->{keep_wiz_editor} = 0; # destroy wizard in case of error
};
my $loop_c = 0;
$logger->debug( "$cb_key wizard entered local loop ", ++$loop_c );
$cw->DoOneEvent() while $cw->{keep_wiz_editor};
$logger->debug( "$cb_key wizard exited local loop ", $loop_c );
};
}
my @wiz_args = ( %cb_table );
foreach (qw/warning important/) {
push @wiz_args, "call_back_on_$_" => $args{"stop_on_$_"}
if defined $args{"stop_on_$_"};
}
#Tk::ObjScanner::scan_object(\@wiz_args) ;
$cw->{wizard} = $cw->{root}->instance->iterator(@wiz_args);
# exits when wizard is done
$cw->{wizard}->start;
$cw->destroy_wizard;
}
sub destroy_wizard {
my $cw = shift;
delete $cw->{ed_w};
delete $cw->{wizard};
# print "Destroying wizard\n" ;
$logger->debug("Destroying wizard");
$cw->destroy;
if ( defined $cw->{end_cb} ) {
$logger->debug("Calling end_cb");
$cw->{end_cb}->( $cw->{has_stopped} );
}
}
1;