| Config-Model-CursesUI documentation | Contained in the Config-Model-CursesUI distribution. |
Config::Model::CursesUI - Curses interface to edit config data
use Config::Model ;
use Config::Model::CursesUI ;
my $model = Config::Model -> new ;
my $inst = $model->instance (root_class_name => 'XXX',
instance_name => 'yyy');
# create dialog
my $dialog = Config::Model::CursesUI-> new
(
experience => 'beginner', # or 'advanced'
) ;
# start never returns
$dialog->start($model) ;
This class provides a Curses::UI interface to configuration data managed by Config::Model.
IMPORTANT: Once the CursesUI object is created, STDOUT and STDERR are managed by the Curses interface, so all print and warn will not work as expected.
The constructor accepts the following parameters:
Specifies the experience level of the user (default:
beginner). The experience can be master advanced
beginner.
Subroutine ref containing the code to load the configuration data from the configuration files. This may overrides loading mechanism specified in the model with Config::Model::AutoRead. This sub is called without any arguments.
Subroutine ref containing the code to store the configuration data in the configuration files. This may overrides writing mechanism specified in the model with Config::Model::AutoRead. This sub is called without any arguments.
Dominique Dumont, (ddumont at cpan dot org)
Copyright (c) 2007-2009,2011 Dominique Dumont.
This file is part of Config-Model.
Config-Model is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser Public License as
published by the Free Software Foundation; either version 2.1 of
the License, or (at your option) any later version.
Config-Model 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
Lesser Public License for more details.
You should have received a copy of the GNU Lesser Public License
along with Config-Model; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
02110-1301 USA
| Config-Model-CursesUI documentation | Contained in the Config-Model-CursesUI distribution. |
my $verb_wiz = 1 ; package Config::Model::CursesUI ; require Exporter; use strict ; use Config::Model::Exception ; use Carp; use warnings FATAL => "all"; use Config::Model::ObjTreeScanner ; use Curses::UI ; use Config::Model::Exception ; use Exception::Class ( 'Config::Model::CursesUI::AbortWizard' => { isa => 'Config::Model::Exception::Any', description => 'wizard found a highlighted item' , fields => [qw/object slot index info/] }, ) ; our $VERSION = '1.104'; my @help_settings = qw/-bg green -fg black -border 1 -titlereverse 0 -padbottom 1 -wrapping 1/ ; sub new { my $class = shift ; my %args = @_ ; my $self = { init_done => 0 , stack => [], debug => 0 } ; $self->{debug} = $args{debug} if defined $args{debug} ; foreach my $param (qw/store load/) { $self->{tree}{$param} = $args{$param} if defined $args{$param} ; } $self->{cui} = new Curses::UI ( -color_support => 1, #-default_colors=> 0, #-clear_on_exit => 1, #-compat => 1, #-debug => 1 ); $self->{experience} = $args{experience} || 'beginner' ; my %cb_set = ( # scanner self list_element_cb => sub {shift; shift->display_hash_element (@_); }, check_list_element_cb => sub {shift; shift->display_check_list_element(@_); }, hash_element_cb => sub {shift; shift->display_hash_element (@_); }, node_element_cb => sub {shift; shift->display_node_element (@_); }, node_content_cb => sub {shift; shift->display_node_content (@_); }, leaf_cb => sub {shift; shift->display_leaf (@_); }, string_value_cb => sub {shift; shift->display_string (@_); }, reference_value_cb => sub {shift; shift->display_enum (@_); }, enum_value_cb => sub {shift; shift->display_enum (@_); }, integer_value_cb => sub {shift; shift->display_string (@_); }, number_value_cb => sub {shift; shift->display_string (@_); }, boolean_value_cb => sub {shift; shift->display_boolean (@_); }, ) ; eval { $self->{scan} = Config::Model::ObjTreeScanner -> new ( fallback => 'all', experience => $self->{experience}, %cb_set , ) ; }; $self->{cui}->fatalerror("Could not create ObjTreeScanner:\n$@") if $@ ; bless $self,$class ; } # create dialog windows sub init { my $self = shift ; my $cui = $self->{cui}; $self->create_explanation ; # Bind <CTRL+Q> to quit. my $quit_sub = sub { $self->store_config ; exit; } ; $cui->set_binding( $quit_sub, "\cQ" ); # Bind <CTRL+C> to quit. $cui->set_binding( sub {exit;}, "\cC" ); # Bind <CTRL+R> to reset. $cui->set_binding( sub {$self->reset_screen} , "\cR" ); # Bind <CTRL+B> to back. $cui->set_binding( sub {$self->back}, "\cB" ); # Bind <CTRL+X> to menubar. $cui->set_binding( sub{ $self->{cui}->root->focus('menu') }, "\cX" ); $self->{init_done} = 1; } sub back { my $self=shift ; return unless @{$self->{stack}} > 1; pop @{$self->{stack}}; $self->reset_screen ; } sub reset_screen { my $self=shift ; return unless @{$self->{stack}}; &{ pop @{$self->{stack}} }; } sub create_explanation { my $self = shift ; my $cui = $self->{cui} ; my $w_bottom = $cui->add( 'bottom', 'Window', -border => 1, '-y' => -1, -height => 3 ); $w_bottom->add( 'explain', 'Label', -text => "CTRL-Q: save & quit CTRL+C: exit CTRL+X: menu CTRL+B: " . "back CTRL-R: reset screen" ); my $w_url = $cui->add( 'undef', 'Window', -border => 1, '-y' => 1, -height => 3 ); $self->{conf_label} = $w_url->add ('conf_label', 'Label', -x => 1, -text => "", -width => 15 ); $self->{loc_label} = $w_url->add ( 'location', 'Label', -bg => 'blue', -fg => 'white', -bold => 1, '-padleft' => 16, -text => "." x 60 ); } sub set_center_window { $_[0]->{cui}->delete('center') ; $_[0]->{cui}->add ( 'center', 'Window', -border => 1, -titlereverse => 0, -padtop => 4, -padbottom => 3, -ipad => 1, -title => $_[1] ) ; } sub add_debug_label { my ($self,$win) = @_ ; my @a = caller (1) ; my $f = $a[3] ; $f =~ s/.*::// ; $win->add(undef, 'Label', -x => 40, -text => "debug: '$f()',l $a[2]") if $self->{debug} ; } sub start { my $self = shift ; my $model_obj = shift ; $self->{model_obj} = $model_obj ; $self->init unless $self->{init_done} ; $self->create_menu ; $self->{conf_label} -> text('') ; my $start = sub {$self->start($model_obj)} ; push @{$self->{stack}} , $start ; $self->{start_all} = $start ; $self->{loc_label}->text('') ; my @inst_names = $model_obj -> instance_names ; warn "found @inst_names\n"; my $win = $self->set_center_window("XXX configuration"); # create an instance screen if more than one instance was passed if (@inst_names > 1) { # TBD scan the tree to get a name $self->add_debug_label($win) ; $win->add(undef, 'Label', -text => "Choose your configuration instance"); my $y = 2 ; foreach my $i_name (@inst_names) { $win->add ( undef, 'Buttonbox', '-x' => 2, '-y'=> $y , -width => 15, '-buttons' => [ { -label => "< $i_name >", -onpress => sub {$self->start_config($i_name) ;}, }, ] ) ; $y++ ; } $win->focus ; } else { $self->start_config(@inst_names) ; } $self->{cui}->mainloop; } sub reset_config { my ($self,$inst_name) = @_ ; $self->{cui}->status("Reseting $inst_name ...") ; $self->{tree}{root} = $self->{model_obj}->instance(name => $inst_name) -> reset_config ; $self->{tree}{load}->() if defined $self->{tree}{load} ; $self->{cui}->nostatus ; return $self->{tree}{root} ; } sub load_config { my ($self,$inst_name) = @_ ; $self->{cui}->status("Loading $inst_name ...") ; warn "Loading config $inst_name ...\n" ; my $root = $self->{tree}{root} = $self->{model_obj}->instance(name => $inst_name)->config_root ; $self->{tree}{load}->() if defined $self->{tree}{load} ; $self->{cui}->nostatus ; return $root; } sub store_config { my ($self) = @_ ; my $label = $self->{tree}{root}->instance->name ; if (defined $self->{tree}{store}) { warn "Storing config $label with provided store call-back...\n" ; $self->{tree}{store}->() ; } else { warn "Storing config $label with model call-back...\n" ; $self->{tree}{root}->instance->write_back; } $self->{cui}->nostatus ; } sub start_config { my $self = shift ; my $inst_name = shift ; $self->{start_config} = sub {$self->start_config($inst_name) } ; my $inst = $self->{model_obj}->instance(name => $inst_name) ; $self->{conf_label} -> text($inst_name.':') ; $self->create_config_menu($inst_name) ; # reset location label $self->{loc_label}->text('') ; $self->{tree}{root} ||= $self->load_config($inst_name); my $root = $self->{tree}{root} ; $self->init unless $self->{init_done} ; my $win = $self->set_center_window($inst->name." configuration"); $self->add_debug_label($win) ; $win->add(undef, 'Label', -text => $root->name." configuration"); $win ->add ( undef, 'Buttonbox', '-y' => 2, -vertical => 1, '-buttons' => [ { -label => "< config wizard >", -onpress => sub{$self->wizard($root,1) ;}, }, { -label => "< open >", -onpress => sub{$self->scan('node',$root) ;}, }, { -label => "< search >", -onpress => sub{$self->display_all_elements($root) ;}, }, { -label => "< overall tabular view >", '-onpress' => sub{$self->display_view_list($root, 'std', 'tabular') ;}, }, { -label => "< overall tabular audit >", -onpress => sub{$self->display_view_list($root, 'audit', 'tabular') ;}, }, { -label => "< overall view >", -onpress => sub{$self->display_view_list($root, 'std', 'tree') ;}, }, { -label => "< overall audit >", -onpress => sub{$self->display_view_list($root, 'audit', 'tree') ;}, }, { -label => "< look for errors >", -onpress => sub{$self->wizard($root,0) ;}, }, ] ); $self->{displayed_object} = $_[0] ; push @{$self->{stack}} , $self->{start_config}; $self->{cui}->getobj('center')->focus ; # must add: # button to access a view style list } # update the location label with config element path # add the current screen on user's call stack sub wrap_screen { my ($self,$node,$element,$idx) = @_ ; $self->{displayed_object} = $node ; $self->update_location($node,$element,$idx) ; my $scan_type = defined $idx ? 'hash' : defined $element ? 'element' : 'node' ; push @{$self->{stack}} , sub{$self->scan($scan_type,$node,$element,$idx)}; $self->{cui}->getobj('center')->focus ; } sub update_location { my ($self,$node,$element,$idx) = @_ ; my $loc = $node->location ; $loc .= ' ' if $loc ; $loc .= $element if defined $element ; $loc .= ":$idx" if defined $idx ; $self->{loc_label}->text($loc) ; } sub scan { my ($self,$what,@args) = @_ ; my $meth = 'scan_'.$what ; eval {$self->{scan}->$meth($self,@args) ; }; # we may want to handle differently the exception $self->{cui}->fatalerror("Error in $meth:\n$@") if $@ ; } sub display_node_content { my ($self,$node,@element) = @_ ; my $win = $self->set_center_window("Node ".$node->name) ; $self->add_debug_label($win) ; $win->add(undef, 'Label', '-y' => 0, -text => "Choose one of the elements:"); my $valuew = $win->add(undef, 'Label', -bg => 'yellow', '-y' => 2, '-x' => 40, -width => 38 ); my $permw = $win->add(undef, 'Label', '-y' => 3, '-x' => 40, -width => 38 ); my $selw = $win->add(undef, 'Label', '-y' => 4, '-x' => 40, -width => 38 ); my $helpw = $win->add(undef, 'TextViewer', '-y' => 5, '-x' => 40, -width => 38, '-title' => 'Help on element', @help_settings); my $listbox ; my $buttons ; my $lb_change = sub { my $sel = ($listbox->get)[0]; $selw->text("selected $sel "); $buttons -> focus ; } ; my $lb_sel_change = sub { my $sel = ($listbox->get_active_value)[0]; return unless defined $sel ; # may happen with empty node my $help = $node->get_help($sel) ; $help = "no help for $sel" unless $help ; $helpw->text($help) ; if ($self->{experience} ne 'beginner') { my $p = $node -> get_element_property(property => 'experience', element => $sel) ; $permw->text("experience: $p"); } my $type = $node->element_type($sel) ; my $elt = $node->fetch_element($sel) ; my $v_str = '' ; if ($type eq 'leaf') { my $v = $elt->fetch_no_check ; $v_str = 'value: '; $v_str .= defined $v ? "'$v'" : '<undef>'; } elsif ($type =~ 'node') { $v_str = 'node: '.$elt->config_class_name ; } else { $v_str = 'type: '.$type ; } $valuew -> text ( $v_str ) ; }; $listbox = $win->add ( 'mylistbox', 'Listbox', -border => 1, '-y' => 2, -width => 38 , -padbottom => 1, -title => 'element', -vscrollbar => 1, -onchange => $lb_change , -onselchange => $lb_sel_change , -values => \@element, -selected => 0, # automatically select first item ); $listbox->focus ; my $go = { -label => '< GO >', -onpress => sub { my @sel = $listbox->get; if (@sel) { $self->scan('element',$node,$sel[0]); } else { $self->{cui}->dialog(-message => "Please select an element"); } } } ; my $help = { -label => '< Help on node >', -onpress => sub { my $help= $node->get_help ; $help = "Sorry, no help available" unless defined $help; $self->{cui}->dialog($help) ; } } ; my $parent = $node->parent ; # closure: don't remove the $buttons assignment $buttons = $self->add_std_button($win,$parent,undef,$help,$go) ; $self->wrap_screen($node) ; # display value and help of selected element (i.e. -selected 0) my $sel = ($listbox->get)[0]; $selw->text("selected $sel "); &$lb_sel_change() ; } # node_element_cb sub display_node_element { my ($self,$node,$element,$key, $contained_node) = @_ ; # here, there's no need to define a screen, just fetch the # node and scan it if (not $node->is_accessible($element)) { my $str = "Node ".$node->name." element: $element"; $str .= " key $key" if defined $key; my $win = $self->set_center_window($str); $win->add ( undef, 'Label', -text => "Node is currently unavailable.\n" . "To make it available, change one " . "of the following items" ); my $y = 3 ; foreach my $master ($contained_node->get_all_warper_object) { my $s = $master->element_name ; my $cb = sub { my $p = $master->parent ; $self->scan('element',$p,$s) ; }; $win->add ( undef, 'Buttonbox', '-y' => $y++ , -buttons => [{ -label => "< ".$master->name." >", -value => $master, -width => 20, -onpress => $cb }] ); no warnings "uninitialized" ; $win->add ( undef, 'Label', '-y' => $y++ , '-x' => 3 , #-width => 20, -text => "* $s value '".$master->fetch."'" ); } $self->wrap_screen($node,$element,$key) ; } else { $self->{scan}->scan_node($self,$contained_node); } } sub display_hash_element { my ($self,$node,$element,@keys) = @_ ; my $win = $self->set_center_window(ucfirst($node->element_type($element))); $self->add_debug_label($win) ; my $listbox = $self->layout_hash($win, $node,$element,@keys) ; my @but = ( { -label => '< GO >', -onpress => sub { my @sel = $listbox->get; return unless @sel; $self->scan('hash',$node,$element,$sel[0]); } } ) ; $self->add_std_button_with_help($win,$node,$element,@but) ; $self->wrap_screen($node,$element) ; return $win ; } sub layout_hash { my ($self,$win,$node,$element,@keys) = @_ ; $win->add(undef, 'Label', -text => "Select or add one element:"); my $lb_sel_change ; my $listbox = $win->add ( 'mylistbox', 'Listbox', -border => 1, '-y' => 2, -padbottom => 1, -width => 40 , -title => $element.' elements', -onselchange => $lb_sel_change , -vscrollbar => 1, -values => \@keys, -selected => 0, # automatically select first item ); my $hash_obj = $node->fetch_element($element) ; my $redraw = sub { my @rkeys = $self->{scan}->get_keys($node,$element) ; warn "redraw: keys are @rkeys\n" ; $listbox->values( \@rkeys ) ; #$listbox->layout ; $listbox->draw ; #intellidraw ; #$win->intellidraw ; } ; $listbox->focus ; $win->add(undef, 'Label', '-x' => 41, '-y' => 2, -text => "Id to add, rm, cp, mv:"); my $editor = $win -> add ( undef, 'TextEntry', -sbborder => 1, '-x' => 41, '-y' => 3, -width => 15, -text => '' ); # $node and $element are closure my $add_sub = sub { my $add = $editor->get; if ($add) { my $res = $self->try_it(sub {$hash_obj->fetch_with_id($add);}) ; &$redraw; } else { $self->{cui}->dialog(-message => "Please type in an id to add"); } }; my $del_sub = sub { my $del = $listbox->get; if ($del) { $self->try_it(sub {$hash_obj->delete($del);}) or return ; &$redraw; } else { $self->{cui}->error(-message => "Please type in an id to remove"); } }; my $copy_sub = sub { my @sel = $listbox->get; my $to = $editor->get; unless (@sel) { $self->{cui}->error(-message => "Please select an id to copy from"); return ; } unless ($to) { $self->{cui}->error(-message => "Please type in an id to copy to") ; return ; } $self->try_it(sub { $hash_obj -> copy ($sel[0],$to) ;} ) ; # redraw the screen &$redraw; }; my $move_sub = sub { my @sel = $listbox->get; my $to = $editor->get; unless (@sel) { $self->{cui}->error(-message => "Please select an id to move from"); return ; } unless ($to) { $self->{cui}->error(-message => "Please type in an id to move to"); return ; } $self->try_it(sub { $hash_obj -> move($sel[0], $to) ;} ); # redraw the screen &$redraw; } ; $win->add(undef, 'Label', '-x' => 41, '-y' => 4, -text => "do: " ); $win->add ( undef, 'Buttonbox', '-y' => 4, '-x' => 45, #-buttonalignment => 'left', -width => 20, -vertical => 0, -buttons => [ { -label => '<add>' , -onpress => $add_sub }, { -label => '<rm>' , -onpress => $del_sub }, { -label => '<cp>' , -onpress => $copy_sub }, { -label => '<mv>', -onpress => $move_sub } ] ); $win->add(undef, 'Label', '-x' => 41, '-y' => 5, -bg => 'yellow', -text => "Cargo type: ".$hash_obj->cargo_type ); my $value_w = $win-> add(undef, 'Label', '-x' => 41, '-y' => 6,-width => 38, -bg => 'yellow', -text => "content: " ); $lb_sel_change = sub { my $sel = ($listbox->get_active_value)[0]; return unless defined $sel ; # may happen with empty hash my $ct = $hash_obj -> cargo_type ; my $value = $ct eq 'leaf' ? $hash_obj->fetch_with_id($sel) -> fetch : $ct =~ /node/ ? "node " . $hash_obj->config_class_name : "type $ct" ; $value_w->text("content: ".$value) ; }; &$lb_sel_change ; # to display selected value ; my $helpw = $win->add(undef, 'TextViewer', '-y' => 7, '-x' => 41, -width => 38, '-title' => 'Help on element', @help_settings); my $help = $node->get_help($element) || "no help for $element" ; $helpw->text($help) ; return $listbox ; } sub display_check_list_element { my ($self,$node,$element,@check_items) = @_ ; my $win = $self->set_center_window("Check list"); $self->layout_checklist($win, $node,$element) ; $self->wrap_screen($node,$element) ; return $win ; } sub layout_checklist { my ($self,$win,$node,$element) = @_ ; my $check_list_obj = $node->fetch_element($element) ; my $notebook = $win->add(undef, 'Notebook', -intellidraw => 1); my $content_page = $notebook->add_page('edit content'); $self->layout_checklist_editor($content_page,$node,$element) ; if ($check_list_obj -> ordered ) { my $lb ; my $c_sub = sub { my @values = $check_list_obj->get_checked_list ; $lb->values(\@values) ; }; my $order_page = $notebook->add_page('change order', -on_activate => $c_sub ) ; $lb = $self->layout_checklist_order($order_page,$node,$element) ; } } sub layout_checklist_info { my ($self,$win,$node,$element, $yr,$text) = @_ ; my $check_list_obj = $node->fetch_element($element) ; $win->add(undef, 'Label', '-y' => $$yr , -text => "Current value :"); my $cur_val_w = $win->add(undef, 'Label', '-y' => $$yr++ , '-x' => 16 ); $win->add(undef, 'Label', '-y' => $$yr++ , -text => $text); my @values = $check_list_obj->get_choice ; my $help_w = $win -> add ( undef, 'TextViewer', '-x' => 42 , '-y' => $$yr , -width => 35, -text => $node->get_help($element) , '-title' => 'Help on value', @help_settings ) ; my $help_update = sub { my $widget = shift ; my $choice = $values[$widget->get_active_id] ; $help_w->text($check_list_obj->get_help($choice)) ; } ; return ($cur_val_w,$help_update) ; } sub layout_checklist_editor { my ($self,$win,$node,$element) = @_ ; my $y = 1 ; my ($cur_val_w,$help_update) = $self->layout_checklist_info($win,$node,$element,\$y, "Check one or more:" ) ; my $check_list_obj = $node->fetch_element($element) ; my @values = $check_list_obj->get_choice ; my $listbox = $win->add ( 'mylistbox', 'Listbox', -border => 1, '-y' => $y, -multi => 1 , -padbottom => 1, -width => 40 , -title => $element.' elements', -vscrollbar => 1, -onselchange => $help_update , -selected => { 0 => 1, 1 => 1 } , -values => \@values , ); my $update_value = sub { $cur_val_w->text(join(",",$check_list_obj->get_checked_list)) ; my %new_hash = $check_list_obj->get_checked_list_as_hash ; my $idx = 0; $listbox->clear_selection ; foreach my $v (sort keys %new_hash) { warn "set $v ($idx) to $new_hash{$v} for @{$listbox->{-values}}\n"; $listbox->set_selection($idx) if $new_hash{$v} ; $idx ++ ; } # Tk::ObjScanner::scan_object($listbox) ; $listbox->draw ; } ; $update_value->() ; my $ok_sub = sub { my (@set) = $listbox->get ; $check_list_obj->set_checked_list(@set) ; $update_value->() ; } ; my @buttons = ( { -label => '< Store >', -onpress => $ok_sub } ) ; $self->add_std_button_with_help($win,$node,$element, @buttons ) ; $listbox->focus ; return $listbox ; } sub layout_checklist_order { my ($self,$win,$node,$element) = @_ ; my $y = 1; my ($cur_val_w,$help_update) = $self->layout_checklist_info($win,$node,$element,\$y, "Current value :"); my $check_list_obj = $node->fetch_element($element) ; my @values = $check_list_obj->get_checked_list ; my $listbox = $win->add ( 'mylistbox', 'Listbox', -border => 1, '-y' => $y, -padbottom => 1, -width => 40 , -title => $element.' elements', -vscrollbar => 1, -onselchange => $help_update , -values => \@values , ); my $update_value = sub { my $set = shift ; my @new_list = $check_list_obj->get_checked_list ; $cur_val_w->text(join(",",$check_list_obj->get_checked_list)) ; $listbox->values(\@new_list) ; # Tk::ObjScanner::scan_object($listbox) ; $listbox->set_selection($set) if defined $set ; $listbox->draw ; } ; $win->onFocus(sub {$update_value->()} ) ; ; my $up_sub = sub { my ($item) = $listbox->get || return ; # no selection my ($idx) = $listbox->id || return ; # first item selected $check_list_obj->move_up($item) ; $update_value->($idx - 1) ; } ; my $down_sub = sub { my ($item) = $listbox->get || return ; my ($idx) = $listbox->id ; my @new_list = $check_list_obj->get_checked_list ; return if $idx >= $#new_list ; # last item selected $check_list_obj->move_down($item) ; $update_value->($idx + 1) ; } ; my @buttons = ( { -label => '< up >', -onpress => $up_sub } , { -label => '< down >', -onpress => $down_sub } , ) ; $self->add_std_button_with_help($win,$node,$element, @buttons ) ; $listbox->focus ; return $listbox ; } ## end check_list sub display_leaf { my ($self,$node,$element,$index,$leaf) = @_ ; my $win = $self->set_center_window($element); my $editor = $self->layout_leaf_value($win,$node,$element,$index,$leaf ) ; $editor -> focus; $self->add_std_button_with_help($win,$node,$element) ; $self->wrap_screen($node,$element,$index); } sub layout_leaf_value { goto &layout_string_value ; } sub set_leaf_value { my ($self,$leaf,$new) = @_ ; my $sub = sub { no warnings "uninitialized" ; warn "set_leaf_value: ", $leaf->name,"-> store( $new )\n"; my $v = $leaf->store($new); } ; $self->try_it($sub) ; } sub try_it { my ($self,$sub) = @_ ; eval { &$sub ; warn "try_it: call to sub succeeded\n" if $verb_wiz ; } ; my $e ; if ($e = Config::Model::Exception::User->caught()) { my $oops = $e->error ; $oops =~ s/\t//g; chomp($oops) ; $self->{cui}->error(-message => $oops ) ; return undef; } elsif ($@) { warn $@ ; $self->{cui}->fatalerror("try_it: $@") ; # does not return ... } ; } sub display_enum { my ($self,$node,$element,$index, $leaf) = @_ ; my $win = $self->set_center_window("display_enum $element"); my $lb = $self->layout_enum_value($win,$node,$element,$index, $leaf) ; my $but = { -label => '< OK >', -onpress => sub {$self->back} } ; $lb->focus ; $self->add_std_button_with_help($win,$node,$element,$but) ; $self->wrap_screen($node,$element,$index); } sub layout_enum_value { my ($self,$win,$node,$element,$index, $leaf) = @_ ; $self->add_debug_label($win) ; my ($orig_value,$current_value_widget,$help) = $self->value_info($win,$leaf, 40, 1) ; $help -> text ($leaf->get_help($orig_value) ) ; my $y = 0; if ($leaf->value_type eq 'reference') { $win -> add ( undef, 'Label', '-y' => $y++, -text => "Enum values are taken from:" ) ; foreach my $c_obj ($leaf->reference_object->compute_obj) { my $button ; my $path = $c_obj -> user_formula ; if (defined $path) { my $target = $leaf->grab($path) ; my $p_target = $target->parent ; my $n_target = $target->element_name ; my $go = sub { $self->scan('element',$p_target, $n_target) ; } ; $button = { -label => "< go to '$path' >", -onpress => $go } ; } else { my $go = sub {$self->{cui}->fatalerror( $c_obj->compute_info )} ; $button = { -label => "< info on undef '$path' >", -onpress => $go } ; } $win -> add ( undef, 'Buttonbox', '-y' => $y++, '-x' => 0 , -buttons => [ $button ] , ) ; } $y ++ ; } $win -> add ( undef, 'Label', '-y' => $y, -text => "Select new value.\nPress </> for a" . "'less'-like\nsearch through the choice list." ) ; $y += 3 ; my $listbox ; my $value = $orig_value ; my $lb_change = sub { my ($new) = $listbox->get; if (not defined $orig_value or $new ne $value) { $self->set_leaf_value($leaf,$new); $value = $new ; $current_value_widget->text($new) ; } } ; my $lb_sel_change = sub { my ($new) = $listbox->get_active_value; $help ->text($leaf->get_help($new)) ; } ; $listbox = $win -> add ( undef, 'Listbox', '-y' => $y , -padbottom => 1, -values => $leaf->choice, -width => 35, -border => 1, -title => 'Enum choice', -vscrollbar => 1, -onchange => $lb_change , -onselchange => $lb_sel_change , ) ; return $listbox ; } sub display_boolean { my ($self,$node,$element,$index, $leaf) = @_ ; my $win = $self->set_center_window("display_boolean $element"); my $listbox = $self->layout_boolean_value($win,$node,$element,$index, $leaf) ; $listbox->focus; my $but = { -label => '< OK >', -onpress => sub {$self->back} } ; $self->add_std_button_with_help($win,$node,$element,$but) ; $self->wrap_screen($node,$element,$index); } sub layout_boolean_value { my ($self,$win,$node,$element,$index, $leaf) = @_ ; my ($orig_value,$current_value_widget, $help) = $self->value_info($win,$leaf, 0, 4, 75) ; $orig_value ||= 0 ; # avoid undef boolean values my $value = $orig_value ; my $check_box ; my $set = sub { my ($new) = $check_box->get; if (not defined $orig_value or $new ne $value) { $self->set_leaf_value($leaf , 0+$new ) ; $value = $new ; $current_value_widget->text( 0+$new ) ; $help ->text($leaf->get_help($new ? '1' : '0')) ; } } ; $check_box = $win -> add ( undef, 'Checkbox', -label => "Toggle checkbox for new value", '-y' => 1, -checked => $orig_value , -onchange => $set ) ; my $reset = sub { my $meth = $orig_value == 1 ? 'check' : 'uncheck' ; $check_box->$meth() ; $check_box ->draw ; $set->() ; } ; $win->add(undef, 'Buttonbox', '-y' => 2 , '-x' => 0 , '-width' => 40 , -buttons => [ { -label => '< Reset value >', -onpress => $reset} ] ) ; return $check_box ; } sub display_string { my ($self,$node,$element,$index, $leaf) = @_ ; my $win = $self->set_center_window("display_string_v $element"); my $editor = $self->layout_string_value($win,$node,$element,$index, $leaf ) ; $editor -> focus; my $but = { -label => '< OK >', -onpress => sub {$self->back} } ; $self->add_std_button_with_help($win,$node,$element, $but) ; $self->wrap_screen($node,$element,$index); } sub layout_string_value { my ($self,$win,$node,$element,$index, $leaf) = @_ ; $self->add_debug_label($win) ; my $v_type = $leaf->value_type; my $height = $v_type eq 'uniline' ? 1 : 4 ; my ($orig_value,$current_value_widget, $help) = $self->value_info($win,$leaf, 0, $height + 2 , 75) ; $win -> add ( undef, 'Label', '-y' => 0, -bold => 1, -text => "Enter new value:") ; my $editor = $win -> add ( undef, $v_type eq 'string' ? 'TextEditor' : 'TextEntry', -sbborder => 1, '-y' => 1, '-height' => $height, -width => 70, -wrapping => 1, -showhardreturns => 1, -text => $orig_value ); my $value = $orig_value ; my $store = sub { my ($new) = $editor->get; if (not defined $orig_value or $new ne $value) { $self->set_leaf_value($leaf,$new) ; $value = $new ; $current_value_widget->text($new) ; } else { $editor -> focus; } } ; my $reset = sub { my $reset_value = defined $orig_value ? $orig_value : '<undef>'; $self->set_leaf_value($leaf , $orig_value ); $editor->text($orig_value || '') ; $current_value_widget->text($reset_value) ; } ; $win->add(undef, 'Buttonbox', '-y' => $height + 1 , '-x' => 0 , '-width' => 40 , -buttons => [ { -label => '< Reset value >', -onpress => $reset}, { -label => '< store >', -onpress => $store } ] ) ; return $editor ; } sub value_info { my ($self,$win,$leaf, $x,$y, $width) = @_ ; my $inst = $leaf->instance ; no warnings "uninitialized"; my $value = $leaf->fetch(check => 'no') ; $win -> add ( undef, 'Label', -text => "current value: ", '-x' => $x, '-y' => $y ) ; my $display_value = defined $value ? $value : '<undef>' ; my $cur_win = $win -> add ( undef, 'Label', -text => $display_value , -bg => 'yellow', -width => $width || 35 , '-x' => $x + 15, '-y' => $y++ ) ; my @items = (); if (defined $leaf->upstream_default) { push @items, "upstream_default value: " . $leaf->upstream_default ; } elsif (defined $leaf->fetch_standard) { push @items, "default value: " . $leaf->fetch_standard ; } my $m = $leaf->mandatory ; push @items, "is mandatory: ".($m ? 'yes':'no') if defined $m; my @minmax ; foreach my $what (qw/min max/) { my $v = $leaf->$what() ; push @minmax, "$what: $v" if defined $v; } push @items, join(', ',@minmax) if @minmax ; $win -> add ( undef, 'Label', '-x' => $x, '-y' => $y, '-text' => join("\n",@items), ) ; my $help = $win -> add ( undef, 'TextViewer', '-x' => $x , '-y' => $y + scalar @items , -width => $width || 35, '-title' => 'Help on value', @help_settings ) ; return ($value, $cur_win, $help) ; } sub create_menu { my $self = shift ; $self->{cui}->delete('menu') ; my $file_menu = [ { -label => 'Quit', -value => sub { exit(0) ;} }, ]; my $menu = [ { -label => 'File', -submenu => $file_menu }, ]; $self->{cui}->add('menu', 'Menubar', -menu => $menu); } sub create_config_menu { my ($self,$label) = @_ ; $self->{cui}->delete('menu') ; my $file_menu = [ { -label => 'Commit config' , -value => sub {$self->store_config($label)} }, { -label => 'Go back to config root', -value => $self->{start_config}}, { -label => 'Reset config' , -value => sub {$self->reset_config($label)} }, { -label => 'Abort config', -value => $self->{start_all} }, ]; my @menu_data = ( ['View', 'std',' tree' ], ['View Audit', 'audit','tree' ], ['Tabular View', 'std', 'tabular'], ['Tabular View Audit', 'audit','tabular'], ) ; my @nav_menu ; foreach my $i (@menu_data) { my $sub = sub { $self->display_view_list( $self->{displayed_object} || $self->{root}, $i->[1],$i->[2] ) ; }; push @nav_menu , {-label => $i->[0], -value => $sub } ; } my $menu = [ { -label => 'File', -submenu => $file_menu }, { -label => 'Navigate', -submenu => \@nav_menu } ]; $self->{cui}->add('menu', 'Menubar', -menu => $menu); } sub add_std_button_with_help { my ($self,$win,$node,$element,@buttons) = @_ ; my $help = $self->show_node_element_help($node,$element) ; unshift @buttons, { -label => '< More help >', -onpress => sub{$self->{cui}->dialog($help);} } if $help ; $self->add_std_button($win,$node,$element,@buttons) ; } sub add_std_button { my ($self,$win,$node,$element,@buttons) = @_ ; my $up = defined $node ? sub {$self->scan('node',$node);} : $self->{start_config} ; unshift @buttons, { -label => '< Back >', -onpress => sub {$self->back} }, { -label => '< Up >', -onpress => $up }, { -label => '< Reset >', -onpress => sub {$self->reset_screen ;} }, { -label => '< Top >', -onpress => $self->{start_config} } ; $win->add (undef, 'Buttonbox', '-y' => $win->canvasheight-1 , -buttonalignment => 'middle', -buttons => \@buttons, -selected => $#buttons, # select last button ) ; } ##### explore with Searcher sub display_all_elements { my ($self,$root) = @_; unless (defined $self->{searcher}) { $self->{searcher} = $root->searcher ; } my $searcher = $self->{searcher} ; my $win = $self->set_center_window("Search for an element"); $win -> add ( undef, 'Label', -text => "Select the element you're looking for. \n" . "Press </> for a" . "'less'-like search through the list." ) ; my @searchable_elements = $self->{searcher}->get_searchable_elements ; # The searcher must be set in manual mode my $listbox ; my $sub = sub { my ($searched) = $listbox->get; $searcher->prepare(element => $searched) ; my $choices = $searcher->next_choice ; if (@$choices ) { $self->display_possible_element ($root,@$choices) ; } else { # go fetch the searched object my $target = $searcher->current_object ; warn "Search found ",$target->name,"\n"; } } ; $listbox = $win -> add ( undef, 'Listbox', '-y' => 3, -values => \@searchable_elements, -width => 30, -border => 1, -title => 'Search element', -vscrollbar => 1, -onchange => $sub , ) ; $listbox->focus ; #$self->add_std_button($win,$node,$but) ; push @{$self->{stack}} , sub{$self->display_all_elements($root)}; } sub search_dispatch { my ($self, $object) = @_ ; my $obj_type = $object->get_type ; my $elt_name = $object->element_name ; my $idx_value = $object->index_value ; my $scan_type = $obj_type eq 'leaf' ? 'element' : $obj_type ; my $scan_object = $obj_type eq 'leaf' ? $object->parent : $object ; $self->scan($scan_type, $scan_object, $elt_name, $idx_value ) ; } sub add_id_elt_in_search { my ($self,$node,$element,@keys) = @_ ; my $win = $self->set_center_window(ucfirst($node->element_type($element))); my $listbox = $self->layout_hash($win, $node,$element,@keys) ; my @but = ( { -label => '< Done >', -onpress => sub { my @sel = $listbox->get; if (scalar @sel) { $self->search_choose_jump($sel[0]) ; } else { $self->{cui}->error(-message => "Please select an id"); } } } ) ; $self->add_std_button_with_help($win,$node,$element,@but) ; $self->wrap_screen($node,$element) ; return $win ; } sub search_choose_jump { my $self = shift ; my $id = shift ; $self->{searcher}->choose($id) ; warn "choose $id\n"; my $next_choices = $self->{searcher}->next_choice ; my $next_object = $self->{searcher}->current_object ; warn "jump: to ",$next_object->name," with @$next_choices\n"; if ($next_object->get_type =~ /list|hash/ or scalar @$next_choices ) { $self->display_possible_element ($next_object,@$next_choices) ; } else { # go fetch the searched object warn "Search found ",$next_object->name,"\n"; $self->search_dispatch($next_object) ; } } sub display_possible_element { my ($self,$object, @choices) = @_; $self->update_location($object) ; my $obj_type = $object->get_type ; my $elt_name = $object->element_name ; my $idx_value = $object->index_value ; my $searched = $self->{searcher}->searched ; my $win = $self->set_center_window("Select a path for $searched"); $win -> add ( undef, 'Label', -text => "'$searched' can be found in all these\n" . "configuration elements. Please select one."); $self->add_debug_label($win) ; if ($obj_type eq 'list' or $obj_type eq 'hash') { $win->add (undef, 'Buttonbox', '-y'=> 3 , -buttons => [ { -label => "< jump to '$elt_name' to add an id >", -onpress => sub{$self->add_id_elt_in_search($object->parent,$elt_name,@choices) ;}, }, ] ) ; } my $jump = sub { my $id = shift->get; $self -> search_choose_jump($id) ; } ; my $listbox = $win -> add ( undef, 'Listbox', '-y' => 5, -values => \@choices, -width => 30, -border => 1, -title => 'Select path', -vscrollbar => 1, -onchange => $jump , ) ; $listbox->focus ; #$self->add_std_button($win,$node,$but) ; push @{$self->{stack}} , sub{$self->display_possible_element($object,@choices)}; } ##### explore through view like list sub display_view_list { my ($self,$root,$select,$view_type,$pre_select) = @_; # reset location label $self->{loc_label}->text('') ; my $audit_cb = sub { my ($scanner, $data_ref,$node,$element_name,$index, $leaf_object) = @_ ; my $custom = $leaf_object->fetch_custom; push @$data_ref, [ $node,$element_name,$index , $custom ] if defined $custom; } ; my $std_cb = sub { my ($scanner, $data_ref,$node,$element_name,$index, $leaf_object) = @_ ; my $value = $leaf_object->fetch(check => 'no') ; my $value_str = length($value) ? $value : $leaf_object->mandatory ? '*MISSING*' : undef ; $value_str = '"'.$value_str.'"' if defined $value_str && $value_str =~ /\s/ ; push @$data_ref, [ $node, $element_name, $index , $value_str ] ; } ; my $hash_cb = sub { my ($scanner, $data_ref,$node,$element_name,@keys) = @_ ; foreach my $k (@keys) { push @$data_ref, [ $node, $element_name, undef, $k ] ; $scanner->scan_hash($data_ref,$node,$element_name,$k) ; } } ; my $node_cb = sub { my ($scanner, $data_ref,$node,$element_name,$key, $contained_node) = @_ ; push @$data_ref, [ $node, $element_name, $key ] ; $scanner->scan_node($data_ref,$contained_node); } ; my $leaf_cb = ($select eq 'audit') ? $audit_cb : $std_cb ; my @scan_args = ( experience => $self->{experience}, fallback => 'all', hash_element_cb => $hash_cb , leaf_cb => $leaf_cb , node_element_cb => $node_cb , check => 'no', ); my $view_scanner = Config::Model::ObjTreeScanner->new (@scan_args); my @leaves ; eval { # perform the scan that fills @leaves $view_scanner-> scan_node(\@leaves, $root) ; } ; if ($@) { warn "$@" ; $self->{cui}->fatalerror("display_view_list: $@") ; }; my $idx = 0; my @good_leaves = $view_type eq 'tree' ? @leaves : grep { @$_ == 4 } @leaves ; my %labels = map { my ($node,$element,$index,$value) = @$_ ; my $name = defined $index ? "$element:$index" : $element ; my $loc = $node->location ; no warnings "uninitialized" ; my $str ; if ($view_type eq 'tabular') { $str =sprintf("%-28s | %-10s | %-30s", $name,$value,$node->name) ; } else { my @level = split m/ +/ ,$loc ; $str = ('. ' x scalar @level) . $name ; $str .= " = '$value'" if @$_ == 4; } ($idx++,$str) ; } @good_leaves ; my $win = $self->set_center_window("View ".$root->name); $win -> add ( undef, 'Label', -text => "Select the item you're looking for. \n" . "Press </> for a " . "'less'-like search through the list." ) ; my $listbox ; my $sub = sub { my ($searched) = $listbox->get; my ($node,$element,$index,$value) = @{$good_leaves[$searched]} ; # replace call with a call with a selected value pop @{$self->{stack}} ; push @{$self->{stack}} , sub{$self->display_view_list($root,$select,$view_type,$searched)}; if (defined $index) { $self->scan('hash',$node,$element,$index) ; } else { $self->scan('element',$node,$element) ; } } ; $listbox = $win -> add ( undef, 'Listbox', '-y' => 3, -values => [0 .. $#good_leaves], -labels => \%labels , -border => 1, -title => 'Search element', -vscrollbar => 1, -onchange => $sub , -selected => $pre_select ) ; $listbox->focus ; #$self->add_std_button($win,$node,$but) ; push @{$self->{stack}} , sub{$self->display_view_list($root,$select,$view_type,$pre_select)}; } ##### wizard: explore depth first and stop on "important" or undefined ##### mandatory elements (or on erroneous elements ?) sub wizard { my ($self,$root, $stop_on_important) = @_; # reset location label $self->{loc_label}->text('') ; eval { $self->wiz_walk( $stop_on_important , $root) ; } ; if (Config::Model::CursesUI::AbortWizard->caught()) { # ignored } elsif ($@) { warn "$@" ; $self->{cui}->fatalerror("search: $@") ; } ; $self->{start_config}->() ; } # do not delete sub display_hash_wizard { my ($self, $node, $element) = @_ ; my $win = $self->set_center_window('wizard') ; $self->layout_hash($win,$node,$element)->focus ; $self->update_location($node, $element) ; $self->wrap_wizard($win, $node, $element) ; } sub show_node_element_help { my ($self,$node, $element) = @_ ; my $text = '' ; return $text unless defined $node ; my $node_help = $node->get_help(); my $element_name = $node->element_name() ; # may be undef for root class if ($node_help) { $text .= "$element_name:\n " if defined $element_name; $text .= "$node_help\n" ; } if (defined $element) { my $element_help = $node->get_help($element); $text .= "$element:\n $element_help\n" if $element_help ; } return $text ; } my $loop_c = 0 ; sub wrap_wizard { my ($self,$win, $node, $element) = @_ ; my $keep_wiz = 1 ; my $abort_wiz = 0 ; my @buttons = ( { -label => '< Exit wizard >', -onpress => sub {$keep_wiz=0 ; $abort_wiz = 1 ;} } ); my $help = $self->show_node_element_help($node, $element) ; push @buttons, { -label => '< More help >', -onpress => sub { $self->{cui}->dialog($help) ;} } if $help ; push @buttons, { -label => '< Back >', -onpress => sub {$self->{wizard}->go_backward ; $keep_wiz = 0 ;} }, { -label => "< Next >", -onpress => sub {$self->{wizard}->go_forward ; $keep_wiz = 0 ;} } ; my $buttons = $win->add ( undef, 'Buttonbox', '-y' => $win->canvasheight-1 , -buttonalignment => 'middle', -selected => $#buttons , # select < Next > at startup -buttons => \@buttons ) ; $buttons -> focus ; $self->{cui}->draw ; warn "entered local loop ",++$loop_c,"\n"; while ($keep_wiz) { $self->{cui}->do_one_event ; } warn "exited local loop ",$loop_c,"\n"; $self->{cui}->delete('wizard'); if ($abort_wiz) { Config::Model::CursesUI::AbortWizard->throw ; } } # callback is used for tests only sub wiz_walk { my ($self, $stop_on_important , $root) = @_ ; # mode can be wizard or error check warn "wiz_walk called on '", $root->name, "'\n" if $verb_wiz; my ($sort_element, $sort_idx) ; my $hash_element_cb = sub { my ($scanner, $data_ref,$node,$element_name,@keys) = @_ ; warn "wiz_walk, hash_cb (element $element_name) called on '", $node->name, "' keys: '@keys' \n" if $verb_wiz; $self->display_hash_wizard($node, $element_name) ; } ; my %cb_hash ; my %override_meth = ( integer_value => 'layout_string_value', number_value => 'layout_string_value', leaf => 'layout_leaf_value', check_list_element => 'layout_checklist' , ) ; foreach my $leaf_item (qw/leaf enum_value integer_value number_value boolean_value string_value/) { my $layout_meth = $override_meth{$leaf_item} || 'layout_'.$leaf_item ; $cb_hash{$leaf_item.'_cb'} = sub { my @cb_args = @_ ; splice @cb_args,0,2; # remove scanner and data_ref from cb args warn "called $layout_meth for $leaf_item"; my $win = $self->set_center_window('wizard') ; $self->$layout_meth($win,@cb_args) ; $self->update_location(@cb_args) ; $self->wrap_wizard($win,@cb_args) ; } ; } my @wiz_args = (experience => $self->{experience}, hash_element_cb => $hash_element_cb , %cb_hash ); #Tk::ObjScanner::scan_object(\@wiz_args) ; $self->{wizard} = $root->instance->iterator (@wiz_args); my $result; eval {$self->{wizard}->start ;} ; if (my $e = Config::Model::CursesUI::AbortWizard->caught()) { $e -> throw ; # propagate up } elsif ($@) { # really die warn "$@" ; $self->{cui}->fatalerror("display_view_list: $@") ; } return $result ; } 1; __END__