use
5.10.1;
use
vars
qw/$icon_path $error_img $warn_img/
;
use
subs
qw/menu_struct/
;
Construct Tk::Widget
'ConfigModelUI'
;
my
$cust_img
;
my
$tool_img
;
my
%gnome_img
;
my
$mod_file
=
'Config/Model/TkUI.pm'
;
$icon_path
=
$INC
{
$mod_file
};
$icon_path
=~ s/TkUI.pm//;
$icon_path
.=
'Tk/icons/'
;
my
$logger
= Log::Log4perl::get_logger(
'TkUI'
);
no
warnings
"redefine"
;
sub
Tk::Error {
my
(
$cw
,
$error
,
@locations
) =
@_
;
my
$msg
= (
ref
(
$error
) &&
$error
->can(
'as_string'
) ) ?
$error
->as_string :
$error
;
warn
$msg
;
$msg
.=
"Tk stack: \n@locations\n"
;
$cw
->CmeDialog(
-title
=>
'Config::Model error'
,
-text
=>
$msg
,
)->Show;
}
my
$default_config
= {
font
=> {
-family
=>
'DejaVu Sans'
,
qw/-size -13 -weight normal/
}
};
my
$main_window
;
my
$home_str
= File::HomeDir->my_home ||
'/tmp/'
;
my
$config_path
= path(
$home_str
)->child(
'.cme/config/'
);
my
$config_file
=
$config_path
->child(
'tkui.yml'
);
my
$ypp
= YAML::PP->new;
$config_path
-> mkpath;
my
$config
=
$config_file
->is_file ?
$ypp
->load_file(
$config_file
) :
$default_config
;
sub
ClassInit {
my
(
$class
,
$mw
) =
@_
;
$main_window
=
$mw
;
}
sub
set_font {
my
$cw
=
shift
;
my
$tk_font
=
$main_window
->FontDialog->Show;
if
(
defined
$tk_font
) {
$main_window
->RefontTree(
-font
=>
$tk_font
);
$config
->{font} = {
$tk_font
->actual} ;
$cw
->ConfigSpecs(
-font
=> [
'DESCENDANTS'
,
'font'
,
'Font'
,
$tk_font
]);
$ypp
->dump_file(
$config_file
->stringify,
$config
);
}
}
sub
Populate {
my
(
$cw
,
$args
) =
@_
;
unless
(
defined
$error_img
) {
$error_img
=
$cw
->Photo(
-file
=>
$icon_path
.
'stop.png'
);
$cust_img
=
$cw
->Photo(
-file
=>
$icon_path
.
'next.png'
);
$warn_img
=
$cw
->Photo(
-file
=>
$icon_path
.
'dialog-warning.png'
);
$tool_img
=
$cw
->Photo(
-file
=>
$icon_path
.
'tools_nicu_buculei_01.png'
);
foreach
my
$img_name
(
qw/next previous window-close gtk-execute/
) {
$gnome_img
{
$img_name
} =
$cw
->Photo(
-file
=>
$icon_path
.
"gnome-$img_name.png"
);
}
}
if
(
$args
->{-root}) {
carp
"TkUI: -root parameter is deprecated in favor of -instance"
;
my
$root
=
delete
$args
->{-root};
$cw
->{instance} =
$root
->instance;
}
$cw
->{instance} //=
delete
$args
->{-instance};
foreach
my
$parm
(
qw/-store_sub -quit/
) {
my
$attr
=
$parm
;
$attr
=~ s/^-//;
$cw
->{
$attr
} =
delete
$args
->{
$parm
};
}
my
$extra_menu
=
delete
$args
->{
'-extra-menu'
} || [];
my
$title
=
delete
$args
->{
'-title'
}
|| $0 .
" "
.
$cw
->{instance}->config_root->config_class_name;
croak
"Unknown parameter "
,
join
(
' '
,
keys
%$args
)
if
%$args
;
$cw
->{location} =
''
;
$cw
->{current_mode} =
'view'
;
$cw
->setup_scanner();
my
$menubar
=
$cw
->Menu;
$cw
->configure(
-menu
=>
$menubar
);
$cw
->{my_menu} =
$menubar
;
my
$file_items
= [
[
qw/command wizard -command/
,
sub
{
$cw
->wizard } ],
[
command
=>
'redraw tree'
,
-command
=>
sub
{
$cw
->reload } ],
[
command
=>
'reload from file'
,
-command
=>
sub
{
$cw
->ask_reset; } ],
[
command
=>
'check for errors'
,
-command
=>
sub
{
$cw
->check(1) } ],
[
command
=>
'check for warnings'
,
-command
=>
sub
{
$cw
->check( 1, 1 ) } ],
[
command
=>
'show unsaved changes'
,
-command
=>
sub
{
$cw
->show_changes; } ],
[
command
=>
'save (Ctrl-s)'
,
-command
=>
sub
{
$cw
->save } ],
@$extra_menu
,
[
command
=>
'debug ...'
,
-command
=>
sub
{
Tk::ObjScanner::scan_object(
$cw
->{instance}->config_root );
}
],
[
command
=>
'quit (Ctrl-q)'
,
-command
=>
sub
{
$cw
->quit } ],
];
$menubar
->cascade(
-label
=>
'File'
,
-menuitems
=>
$file_items
);
$cw
->add_help_menu(
$menubar
);
$cw
->
bind
(
'<Control-s>'
,
sub
{
$cw
->save } );
$cw
->
bind
(
'<Control-q>'
,
sub
{
$cw
->quit } );
$cw
->
bind
(
'<Control-c>'
,
sub
{
$cw
->edit_copy } );
$cw
->
bind
(
'<Control-v>'
,
sub
{
$cw
->edit_paste } );
$cw
->
bind
(
'<Control-f>'
,
sub
{
$cw
->pack_find_widget } );
my
$edit_items
= [
[
command
=>
'copy (Ctrl-c)'
,
'-command'
,
sub
{
$cw
->edit_copy } ],
[
command
=>
'paste (Ctrl-v)'
,
'-command'
,
sub
{
$cw
->edit_paste } ],
[
command
=>
'find (Ctrl-f)'
,
'-command'
,
sub
{
$cw
->pack_find_widget; } ],
];
$menubar
->cascade(
-label
=>
'Edit'
,
-menuitems
=>
$edit_items
);
my
$option_menu
=
$menubar
->cascade(
-label
=>
'Options'
);
$option_menu
->command(
-label
=>
'Font'
,
-command
=>
sub
{
$cw
->set_font(); });
$cw
->{hide_empty_values} = 0;
$option_menu
->checkbutton(
-label
=>
"Hide empty values"
,
-variable
=> \
$cw
->{hide_empty_values},
-command
=>
sub
{
$cw
->reload(
$cw
->{location}) },
);
$cw
->{show_only_custom} = 0;
$option_menu
->checkbutton(
-label
=>
'Show only custom values'
,
-variable
=> \
$cw
->{show_only_custom},
-command
=>
sub
{
$cw
->reload(
$cw
->{location}) },
);
$cw
->{auto_save_mode} = 0;
$option_menu
->checkbutton(
-label
=>
'Auto save'
,
-variable
=> \
$cw
->{auto_save_mode},
);
my
$weak_cw
=
$cw
;
weaken(
$weak_cw
);
$cw
->{instance}->on_change_cb(
sub
{
$weak_cw
->save
if
$weak_cw
->{auto_save_mode};;
});
my
$loc_frame
=
$cw
->Frame(
-relief
=>
'sunken'
,
-borderwidth
=> 1 )->
pack
(
-pady
=> 0,
-fill
=>
'x'
);
$loc_frame
->Label(
-text
=>
'location :'
)->
pack
(
-side
=>
'left'
);
$loc_frame
->Label(
-textvariable
=> \
$cw
->{location} )->
pack
(
-side
=>
'left'
);
my
$bottom_frame
=
$cw
->Frame->
pack
(
qw/-pady 0 -fill both -expand 1/
);
my
$tree_frame
=
$bottom_frame
->Frame->
pack
(
qw/-fill both -expand 1 -side left/
);
my
$filter_frame
=
$tree_frame
->Frame->
pack
(
qw/-fill x -side top/
);
my
$tree
=
$tree_frame
->Scrolled(
qw/Tree/
,
-columns
=> 4,
-header
=> 1,
-opencmd
=>
sub
{
$cw
->open_item(
@_
); },
-closecmd
=>
sub
{
$cw
->close_item(
@_
); },
)->
pack
(
qw/-fill both -expand 1 -side bottom/
);
$cw
->{tktree} =
$tree
;
my
$sub_filter
=
sub
{
$cw
->reload;
};
my
$clear_filter
=
sub
{
$cw
->{elt_filter_value} =
''
;
$cw
->reload;
};
my
$reload_on_key
=
sub
{
$cw
->reload;
};
my
$filter_clear
=
$filter_frame
->Button (
-image
=>
$gnome_img
{
'window-close'
},
-command
=>
$clear_filter
);
$cw
->Balloon(
-state
=>
'balloon'
)->attach(
$filter_clear
,
-msg
=>
'clear filter'
);
$filter_clear
->
pack
(
-side
=>
'right'
);
$filter_frame
->Label(
-text
=>
'filter elements'
,)->
pack
(
-side
=>
'left'
);
$cw
->{elt_filter_value} =
''
;
my
$element_filter_w
=
$filter_frame
->Entry(
-textvariable
=> \
$cw
->{elt_filter_value},
);
$cw
->Balloon(
-state
=>
'balloon'
)->attach(
$element_filter_w
,
-msg
=>
'define a filter applied to element name. At least 3 character long.'
.
' This can be a Perl regexp.'
);
$element_filter_w
->
pack
(
qw/-side right -fill x -expand 1/
);
$element_filter_w
->
bind
(
'<KeyRelease>'
,
$reload_on_key
);
$bottom_frame
->Adjuster()->packAfter(
$tree_frame
,
-side
=>
'left'
);
$tree
->headerCreate( 0,
-text
=>
"element"
);
$tree
->headerCreate( 1,
-text
=>
"status"
);
$tree
->headerCreate( 2,
-text
=>
"value"
);
$tree
->headerCreate( 3,
-text
=>
"standard value"
);
$cw
->reload;
my
$eh_frame
=
$bottom_frame
->Frame->
pack
(
qw/-fill both -expand 1 -side right/
);
my
$e_frame
=
$eh_frame
->Frame->
pack
(
qw/-side top -fill both -expand 1/
);
$e_frame
->Label(
-image
=>
$tool_img
,
-width
=> 400,
)->
pack
(
-side
=>
'top'
);
$e_frame
->Button(
-text
=>
"Run Wizard !"
,
-command
=>
sub
{
$cw
->wizard } )->
pack
(
-side
=>
'bottom'
);
my
$b1_sub
=
sub
{
my
$item
=
$tree
->nearest(
$tree
->pointery -
$tree
->rooty );
$cw
->on_browse(
$item
);
};
my
$b3_sub
=
sub
{
my
$item
=
$tree
->nearest(
$tree
->pointery -
$tree
->rooty );
$cw
->on_select(
$item
);
};
$tree
->
bind
(
'<Return>'
,
$b3_sub
);
$tree
->
bind
(
'<ButtonRelease-3>'
,
$b3_sub
);
bind_clicks(
$tree
,
$b1_sub
,
$b3_sub
);
my
$b2_sub
=
sub
{
my
$item
=
$tree
->nearest(
$tree
->pointery -
$tree
->rooty );
$cw
->on_cut_buffer_dump(
$item
);
};
$tree
->
bind
(
'<ButtonRelease-2>'
,
$b2_sub
);
$tree
->
bind
(
'<Control-c>'
,
sub
{
$cw
->edit_copy } );
$tree
->
bind
(
'<Control-v>'
,
sub
{
$cw
->edit_paste } );
$tree
->
bind
(
'<Control-f>'
,
sub
{
$cw
->pack_find_widget } );
my
$find_frame
=
$cw
->create_find_widget;
my
$msg_label
=
$cw
->Label(
-textvariable
=> \
$cw
->{message},
-relief
=>
'sunken'
,
-borderwidth
=> 1,
-anchor
=>
'w'
,
);
$msg_label
->
pack
(
-pady
=> 0,
-fill
=>
'x'
);
$args
->{-title} =
$title
;
$cw
->SUPER::Populate(
$args
);
my
$tk_font
=
$cw
->Font(%{
$config
->{font}});
$cw
->ConfigSpecs(
-font
=> [
'DESCENDANTS'
,
'font'
,
'Font'
,
$tk_font
],
-tree_width
=> [
'METHOD'
,
undef
,
undef
, 80 ],
-tree_height
=> [
'METHOD'
,
undef
,
undef
, 30 ],
-width
=> [
$eh_frame
,
qw/width Width 1280/
],
-height
=> [
$eh_frame
,
qw/height Height 1024/
],
-selectmode
=> [
$tree
,
'selectMode'
,
'SelectMode'
,
'single'
],
DEFAULT
=> [
$tree
] );
$cw
->Advertise(
tree
=>
$tree
);
$cw
->Advertise(
menubar
=>
$menubar
);
$cw
->Advertise(
right_frame
=>
$eh_frame
);
$cw
->Advertise(
ed_frame
=>
$e_frame
);
$cw
->Advertise(
find_frame
=>
$find_frame
);
$cw
->Advertise(
msg_label
=>
$msg_label
);
$cw
->OnDestroy(
sub
{
$cw
->Parent->destroy
if
ref
(
$cw
->Parent) eq
'MainWindow'
} );
$cw
->Delegates;
}
sub
show_message {
my
(
$cw
,
$msg
) =
@_
;
$cw
->{message} =
$msg
;
if
(
my
$id
=
$cw
->{id}) {
$cw
->afterCancel(
$id
) ;
} ;
my
$unshow
=
sub
{
delete
$cw
->{id};
$cw
->{message} =
''
;
} ;
$cw
->{id} =
$cw
->
after
(5000,
$unshow
) ;
}
sub
tree_width {
my
(
$cw
,
$value
) =
@_
;
$cw
->Subwidget(
'tree'
)->configure(
-width
=>
$value
);
}
sub
tree_height {
my
(
$cw
,
$value
) =
@_
;
$cw
->Subwidget(
'tree'
)->configure(
-height
=>
$value
);
}
my
$parser
= Pod::POM->new();
my
$pom
=
$parser
->parse_file(__FILE__)
||
die
$parser
->error();
my
$help_text
;
my
$info_text
;
foreach
my
$head1
(
$pom
->head1() ) {
$help_text
= Pod::POM::View::Text->view_head1(
$head1
)
if
$head1
->title eq
'USAGE'
;
$info_text
= Pod::POM::View::Text->view_head1(
$head1
)
if
$head1
->title =~ /more information/i;
}
sub
add_help_menu {
my
(
$cw
,
$menubar
) =
@_
;
my
$about_sub
=
sub
{
$cw
->Dialog(
-title
=>
'About'
,
-text
=>
"Config::Model::TkUI \n"
.
"(c) 2008-2021 Dominique Dumont \n"
.
"Licensed under LGPLv2\n"
)->Show;
};
my
$info_sub
=
sub
{
$cw
->CmeDialog(
-title
=>
'TODO'
,
-text
=>
$info_text
)->Show;
};
my
$help_sub
=
sub
{
$cw
->CmeDialog(
-title
=>
'help'
,
-text
=>
$help_text
)->Show;
};
my
$class
=
$cw
->{instance}->config_root->config_class_name;
my
$man_sub
=
sub
{
$cw
->Pod(
-tree
=> 0,
-file
=>
"Config::Model::models::"
.
$class
,
-title
=>
$class
,
-exitbutton
=> 0,
);
};
my
$help_items
= [
[
qw/command About -command/
,
$about_sub
],
[
qw/command Usage -command/
,
$help_sub
],
[
command
=>
'More info'
,
-command
=>
$info_sub
],
[
command
=>
"$class help"
,
-command
=>
$man_sub
],
];
$menubar
->cascade(
-label
=>
'Help'
,
-menuitems
=>
$help_items
);
}
sub
open_item {
my
(
$cw
,
$path
) =
@_
;
my
$tktree
=
$cw
->{tktree};
$logger
->trace(
"open_item on $path"
);
my
$data
=
$tktree
->infoData(
$path
);
$data
->[0]->(1);
$cw
->show_single_list_value (
$tktree
,
$data
->[1],
$path
, 0);
my
@children
=
$tktree
->infoChildren(
$path
);
$logger
->trace(
"open_item show @children"
);
map
{
$tktree
->show(
-entry
=>
$_
); }
@children
;
}
sub
close_item {
my
(
$cw
,
$path
) =
@_
;
my
$tktree
=
$cw
->{tktree};
$logger
->trace(
"close_item on $path"
);
my
$data
=
$tktree
->infoData(
$path
);
$cw
->show_single_list_value (
$tktree
,
$data
->[1],
$path
, 1);
my
@children
=
$tktree
->infoChildren(
$path
);
$logger
->trace(
"close_item hide @children"
);
map
{
$tktree
->hide(
-entry
=>
$_
); }
@children
;
}
sub
check {
my
$cw
=
shift
;
my
$show
=
shift
|| 0;
my
$check_warnings
=
shift
|| 0;
my
$wiz
=
$cw
->setup_wizard(
sub
{
$cw
->check_end(
$show
,
@_
); } );
$wiz
->start_wizard(
stop_on_warning
=>
$check_warnings
);
}
sub
check_end {
my
$cw
=
shift
;
my
$show
=
shift
;
my
$has_stopped
=
shift
;
$cw
->reload
if
$has_stopped
;
if
(
$show
and not
$has_stopped
) {
$cw
->Dialog(
-title
=>
'Check'
,
-text
=>
"No issue found"
)->Show;
}
}
sub
save {
my
$cw
=
shift
;
my
$cb
=
shift
||
sub
{};
my
$dir
=
$cw
->{save_dir};
my
$trace_dir
=
defined
$dir
?
$dir
:
'default'
;
my
@wb_args
=
defined
$dir
? (
config_dir
=>
$dir
) : ();
my
$save_job
=
sub
{
$cw
->check();
if
(
defined
$cw
->{store_sub} ) {
$logger
->info(
"Saving data in $trace_dir directory with store call-back"
);
eval
{
$cw
->{store_sub}->(
$dir
) };
}
else
{
$logger
->info(
"Saving data in $trace_dir directory with instance write_back"
);
eval
{
$cw
->{instance}->write_back(
@wb_args
); };
}
if
($@) {
my
$err
= $@ ;
my
$answer
=
$cw
->CmeDialog(
-title
=>
'Save error'
,
-text
=>
"Cannot save: $err"
,
-buttons
=> [
qw/quit cancel/
],
-default_button
=>
'cancel'
,
)->Show;
$cb
->(
$err
)
if
$answer
eq
'quit'
;
}
else
{
$cw
->show_message(
"Save done ..."
);
$cb
->();
}
};
$cw
->show_message(
"Saving... please wait ..."
);
$cw
->
after
(100,
$save_job
) ;
}
sub
ask_reset {
my
$text
=
"Discard changes and reload from file ?"
;
my
$cw
=
shift
;
if
(
$cw
->{instance}->needs_save ) {
my
$answer
=
$cw
->Dialog(
-title
=>
"reload from file"
,
-text
=>
$text
,
-buttons
=> [
qw/yes cancel/
,
'show changes'
],
-default_button
=>
'yes'
,
)->Show;
if
(
$answer
eq
'yes'
) {
$cw
->do_reset;
}
elsif
(
$answer
=~ /show/ ) {
$cw
->show_changes(
sub
{
$cw
->ask_reset } );
}
}
else
{
$cw
->do_reset;
}
}
sub
do_reset {
my
$cw
=
shift
;
$cw
->{instance}->reset_config;
$cw
->{instance}->clear_changes;
$cw
->reload;
}
sub
quit {
my
$cw
=
shift
;
my
$text
=
shift
||
"Save data ?"
;
if
(
$cw
->{instance}->needs_save ) {
my
$answer
=
$cw
->Dialog(
-title
=>
"quit"
,
-text
=>
$text
,
-buttons
=> [
qw/yes no cancel/
,
'show changes'
],
-default_button
=>
'yes'
,
)->Show;
if
(
$answer
eq
'yes'
) {
$cw
->save(
sub
{
$cw
->self_destroy;});
}
elsif
(
$answer
eq
'no'
) {
$cw
->self_destroy;
}
elsif
(
$answer
=~ /show/ ) {
$cw
->show_changes(
sub
{
$cw
->quit } );
}
}
else
{
$cw
->self_destroy;
}
}
sub
self_destroy {
my
$cw
=
shift
;
if
(
defined
$cw
->{quit} and
$cw
->{quit} eq
'soft'
) {
$cw
->destroy;
}
else
{
$cw
->parent->destroy;
}
}
sub
show_changes {
my
$cw
=
shift
;
my
$cb
=
shift
;
my
$changes
=
$cw
->{instance}->list_changes;
my
$change_widget
=
$cw
->Toplevel;
$change_widget
->Scrolled(
'ROText'
)->
pack
(
-expand
=> 1,
-fill
=>
'both'
)
->insert(
'1.0'
,
$changes
);
$change_widget
->Button(
-command
=>
sub
{
$change_widget
->destroy;
$cb
->()
if
defined
$cb
; },
-text
=>
'ok'
,
)->
pack
;
}
sub
reload {
my
$cw
=
shift
;
carp
"reload: too many parameters"
if
@_
> 1;
my
$force_display_path
=
shift
//
''
;
$logger
->trace(
"reloading tk tree"
. (
defined
$force_display_path
?
" (force display $force_display_path)"
:
''
) );
my
$actions
=
$cw
->{cm_actions} //= {};
my
%filter_args
=
map
{ (
$_
=>
$cw
->{
$_
} //
''
) }
qw/elt_filter_value show_only_custom hide_empty_values instance/
;
eval
{
apply_filter(
actions
=>
$actions
,
fd_path
=>
$force_display_path
,
%filter_args
);
};
if
($@) {
my
$msg
= $@;
say
"filter error: $msg"
;
$msg
=~ s/at lib.*//s;
$cw
->show_message(
"filter error: $msg"
);
}
my
$tree
=
$cw
->{tktree};
my
$instance_name
=
$cw
->{instance}->name;
my
$root
=
$cw
->{instance}->config_root;
my
$new_drawing
= not
$tree
->infoExists(
$instance_name
);
my
$scan_root
=
sub
{
my
$opening
=
shift
;
$tree
->itemConfigure(
$instance_name
, 2,
-text
=>
$root
->fetch_gist);
$cw
->{scanner}->scan_node( [
$instance_name
,
$cw
,
$opening
,
$actions
,
$force_display_path
],
$root
);
};
if
(
$new_drawing
) {
$tree
->add(
$instance_name
,
-data
=> [
$scan_root
,
$root
] );
$tree
->itemCreate(
$instance_name
, 0,
-text
=>
$instance_name
, );
$tree
->itemCreate(
$instance_name
, 2,
-text
=>
''
);
$tree
->setmode(
$instance_name
,
'close'
);
$tree
->
open
(
$instance_name
);
}
$scan_root
->( 1 );
$cw
->{editor}->reload
if
defined
$cw
->{editor};
}
sub
on_browse {
my
(
$cw
,
$path
) =
@_
;
$cw
->update_loc_bar(
$path
);
$cw
->create_element_widget(
'view'
);
}
sub
update_loc_bar {
my
(
$cw
,
$path
) =
@_
;
my
$datar
=
$cw
->{tktree}->infoData(
$path
);
my
$obj
=
$datar
->[1];
$cw
->{location} =
$obj
->location_short;
}
sub
on_select {
my
(
$cw
,
$path
) =
@_
;
$cw
->update_loc_bar(
$path
);
$cw
->create_element_widget(
'edit'
);
}
sub
on_cut_buffer_dump {
my
(
$cw
,
$tree_path
,
$selection_for_test
) =
@_
;
$cw
->update_loc_bar(
$tree_path
);
my
$sel
=
$selection_for_test
//
eval
{
$cw
->SelectionGet; };
return
if
$@;
my
$obj
=
$cw
->{tktree}->infoData(
$tree_path
)->[1];
my
$type
=
$obj
->get_type;
if
(
$type
eq
"leaf"
) {
$obj
->store(
value
=>
$sel
,
callback
=>
sub
{
$cw
->reload; } );
}
elsif
(
$type
eq
'hash'
) {
my
@keys
= (
$sel
=~ /\n/m ) ?
split
( /\n/,
$sel
) : (
$sel
);
foreach
my
$key
(
@keys
) {
$obj
->fetch_with_id(
$key
);
};
}
elsif
(
$type
eq
'list'
) {
if
(
$obj
->get_cargo_type =~ /node/ ) {
$cw
->show_message(
"cannot paste on list of node"
);
}
else
{
my
@v
=
(
$sel
=~ /\n/m ) ?
split
( /\n/,
$sel
)
: (
$sel
=~ /,/ ) ?
split
( /,/,
$sel
)
: (
$sel
);
$obj
->
push
(
@v
);
}
}
else
{
$cw
->show_message(
"cannot paste on $type parameter"
);
}
$cw
->reload;
$cw
->create_element_widget(
$cw
->{current_mode},
$tree_path
);
$cw
->open_item(
$tree_path
);
$cw
->{tktree}->setmode(
$tree_path
=>
'close'
)
if
$type
eq
'list'
or
$type
eq
'hash'
;
}
sub
to_path {
my
$str
=
shift
;
$str
=~ s/\./_|_/g;
return
$str
; }
sub
force_element_display {
my
$cw
=
shift
;
my
$elt_obj
=
shift
;
$logger
->trace(
"force display of "
.
$elt_obj
->location );
$cw
->reload(
$elt_obj
->location );
}
sub
prune {
my
$cw
=
shift
;
my
$path
=
shift
;
$logger
->trace(
"prune $path"
);
my
%list
=
map
{
"$path."
. to_path(
$_
) => 1 }
@_
;
my
$tkt
=
$cw
->{tktree};
map
{
$tkt
->deleteEntry(
$_
)
if
$_
and not
defined
$list
{
$_
}; }
$tkt
->infoChildren(
$path
);
$logger
->trace(
"prune $path done"
);
}
my
%elt_mode
= (
leaf
=>
'none'
,
hash
=>
'open'
,
list
=>
'open'
,
node
=>
'open'
,
check_list
=>
'none'
,
warped_node
=>
'open'
,
);
sub
disp_obj_elt {
my
(
$scanner
,
$data_ref
,
$node
,
@orig_element_list
) =
@_
;
my
(
$path
,
$cw
,
$opening
,
$actions
,
$force_display_path
) =
@$data_ref
;
my
$tkt
=
$cw
->{tktree};
my
$mode
=
$tkt
->getmode(
$path
);
my
@element_list
;
foreach
my
$elt
(
@orig_element_list
) {
my
$obj
=
$node
->fetch_element(
$elt
);
my
$loc
=
$obj
->location;
my
$action
=
$actions
->{
$loc
} //
''
;
if
(
$action
ne
'hide'
) {
push
@element_list
,
$elt
;
}
}
$logger
->trace(
"disp_obj_elt path $path mode $mode opening $opening "
.
"(@element_list)"
);
$cw
->prune(
$path
,
@element_list
);
my
$node_loc
=
$node
->location;
my
$prevpath
=
''
;
foreach
my
$elt
(
@element_list
) {
my
$newpath
=
"$path."
. to_path(
$elt
);
my
$scan_sub
=
sub
{
$scanner
->scan_element( [
$newpath
,
$cw
,
$opening
,
$actions
,
$force_display_path
],
$node
,
$elt
);
};
my
@data
= (
$scan_sub
,
$node
->fetch_element(
$elt
) );
weaken(
$data
[1] );
my
$elt_type
=
$node
->element_type(
$elt
);
my
$eltmode
=
$elt_mode
{
$elt_type
};
if
(
$tkt
->infoExists(
$newpath
) ) {
$eltmode
=
$tkt
->getmode(
$newpath
);
}
else
{
my
@opt
=
$prevpath
? (
-after
=>
$prevpath
) : (
-at
=> 0 );
$logger
->trace(
"disp_obj_elt add $newpath mode $eltmode type $elt_type"
);
$tkt
->add(
$newpath
,
-data
=> \
@data
,
@opt
);
$tkt
->itemCreate(
$newpath
, 0,
-text
=>
$elt
);
$tkt
->setmode(
$newpath
=>
$eltmode
);
}
my
$elt_loc
=
$node_loc
?
$node_loc
.
' '
.
$elt
:
$elt
;
$cw
->setmode(
'node'
,
$newpath
,
$eltmode
,
$elt_loc
,
$opening
,
$actions
,
$scan_sub
);
my
$obj
=
$node
->fetch_element(
$elt
);
if
(
$elt_type
=~
'node'
) {
$tkt
->itemCreate(
$newpath
, 2,
-text
=>
$obj
->fetch_gist );
}
if
(
$elt_type
eq
'hash'
) {
$cw
->update_hash_image(
$obj
,
$newpath
);
}
if
(
$elt_type
eq
'hash'
or
$elt_type
eq
'list'
) {
my
$size
=
$obj
->fetch_size;
$tkt
->entryconfigure(
$newpath
,
-text
=>
"$elt [$size]"
);
}
$cw
->show_single_list_value (
$tkt
,
$obj
,
$newpath
,
$tkt
->getmode(
$newpath
) eq
'open'
? 1 : 0);
if
(
$force_display_path
and
$force_display_path
eq
$elt_loc
) {
$cw
->force_display(
$newpath
,
$elt_loc
);
}
if
(not
$force_display_path
and
$cw
->{location} eq
$elt_loc
) {
$cw
->force_display(
$newpath
,
$elt_loc
);
}
$prevpath
=
$newpath
;
}
}
sub
force_display {
my
(
$cw
,
$path
,
$loc
) =
@_
;
$logger
->debug(
"force_display called on $path, location $loc"
);
my
$tree
=
$cw
->{tktree};
$tree
->see(
$path
);
$tree
->selectionClear();
$tree
->selectionSet(
$path
,
$path
);
$cw
->{location} =
$loc
;
}
sub
show_single_list_value {
my
(
$cw
,
$tkt
,
$obj
,
$path
,
$show
) =
@_
;
my
$elt_type
=
$obj
->get_type;
return
unless
$elt_type
eq
'list'
and
$obj
->get_cargo_type eq
'leaf'
;
my
$size
=
$obj
->fetch_size;
$logger
->trace(
"show_single_list_value called on "
,
$obj
->location,
" show is $show, size is $size"
);
if
(
$size
== 1 and
$show
) {
disp_leaf(
undef
,[
$path
,
$cw
],
$obj
->parent,
$obj
->element_name, 0,
$obj
->fetch_with_id(0));
}
else
{
foreach
my
$column
(
qw/1 2 3/
) {
$tkt
->itemDelete(
$path
,
$column
)
if
$tkt
->itemExists(
$path
,
$column
);
};
}
}
sub
disp_hash {
my
(
$scanner
,
$data_ref
,
$node
,
$element_name
,
@all_idx
) =
@_
;
my
(
$path
,
$cw
,
$opening
,
$actions
,
$force_display_path
) =
@$data_ref
;
my
$tkt
=
$cw
->{tktree};
my
$mode
=
$tkt
->getmode(
$path
);
my
@idx
;
my
$hash
=
$node
->fetch_element(
$element_name
);
foreach
my
$id
(
@all_idx
) {
my
$loc
=
$hash
->fetch_with_id(
$id
)->location;
my
$action
=
$actions
->{
$loc
} //
''
;
if
(
$action
ne
'hide'
) {
push
@idx
,
$id
;
}
}
$logger
->trace(
"disp_hash path is $path mode $mode (@idx)"
);
$cw
->prune(
$path
,
@idx
);
my
$elt
=
$node
->fetch_element(
$element_name
);
my
$elt_type
=
$elt
->get_cargo_type();
my
$prev_sibling
=
''
;
my
%tk_previous_path
;
foreach
(
$tkt
->info(
'children'
,
$path
) ) {
$tk_previous_path
{
$_
} =
$prev_sibling
;
$prev_sibling
=
$_
;
}
my
$prevpath
=
''
;
foreach
my
$idx
(
@idx
) {
my
$newpath
=
$path
.
'.'
. to_path(
$idx
);
my
$scan_sub
=
sub
{
$scanner
->scan_hash(
[
$newpath
,
$cw
,
$opening
,
$actions
,
$force_display_path
],
$node
,
$element_name
,
$idx
);
};
my
$eltmode
=
$elt_mode
{
$elt_type
};
my
$sub_elt
=
$elt
->fetch_with_id(
$idx
);
if
(
$tkt
->infoExists(
$newpath
) ) {
if
(
$prevpath
ne
$tk_previous_path
{
$newpath
} ) {
$logger
->trace(
"disp_hash deleting mismatching $newpath mode $eltmode cargo_type $elt_type"
);
$tkt
->
delete
(
entry
=>
$newpath
);
}
}
if
(
$tkt
->infoExists(
$newpath
) ) {
my
$previous_data
=
$tkt
->info(
data
=>
$newpath
);
my
$previous_elt
=
$previous_data
->[1] ||
''
;
$eltmode
=
$tkt
->getmode(
$newpath
);
$logger
->trace(
"disp_hash reuse $newpath mode $eltmode cargo_type $elt_type"
.
" obj $previous_elt (expect $sub_elt)"
);
if
(
$sub_elt
ne
$previous_elt
) {
$logger
->trace(
"disp_hash delete $newpath mode $eltmode (got "
.
"$previous_elt expected $sub_elt)"
);
$tkt
->
delete
(
entry
=>
$newpath
);
}
}
if
( not
$tkt
->infoExists(
$newpath
) ) {
my
@opt
=
$prevpath
? (
-after
=>
$prevpath
) : (
-at
=> 0 );
$logger
->trace(
"disp_hash add $newpath mode $eltmode cargo_type $elt_type"
.
" elt $sub_elt"
);
my
@data
= (
$scan_sub
,
$sub_elt
);
weaken(
$data
[1] );
$tkt
->add(
$newpath
,
-data
=> \
@data
,
@opt
);
$tkt
->itemCreate(
$newpath
, 0,
-text
=>
$node
->shorten_idx(
$idx
) );
$tkt
->setmode(
$newpath
=>
$eltmode
);
}
my
$gist
=
$elt_type
=~ /node/ ?
$elt
->fetch_with_id(
$idx
)->fetch_gist :
''
;
$tkt
->itemCreate(
$newpath
, 2,
-text
=>
$gist
);
my
$elt_loc
=
$sub_elt
->location;
$cw
->setmode(
'hash'
,
$newpath
,
$eltmode
,
$elt_loc
,
$opening
,
$actions
,
$scan_sub
);
if
(
$force_display_path
and
$force_display_path
eq
$elt_loc
) {
$cw
->force_display(
$newpath
,
$elt_loc
)
}
$prevpath
=
$newpath
;
}
}
sub
update_hash_image {
my
(
$cw
,
$elt
,
$path
) =
@_
;
my
$tkt
=
$cw
->{tktree};
my
$img
;
{
no
warnings
qw/uninitialized/
;
$img
=
$warn_img
if
$elt
->warning_msg;
}
if
(
defined
$img
) {
$tkt
->itemCreate(
$path
, 1,
-itemtype
=>
'image'
,
-image
=>
$img
);
}
else
{
$tkt
->itemDelete(
$path
, 1 )
if
$tkt
->itemExists(
$path
, 1 );
}
}
sub
setmode {
my
(
$cw
,
$type
,
$newpath
,
$eltmode
,
$elt_loc
,
$opening
,
$actions
,
$scan_sub
) =
@_
;
my
$tkt
=
$cw
->{tktree};
$actions
->{
$elt_loc
} //=
''
;
my
$force_open
=
$actions
->{
$elt_loc
} eq
'show'
? 1 : 0;
my
$force_close
=
$actions
->{
$elt_loc
} eq
'hide'
? 1 : 0;
$logger
->trace(
"$type: elt_loc '$elt_loc', opening $opening "
.
"eltmode $eltmode force_open $force_open"
);
if
( not
$force_close
and (
$eltmode
ne
'open'
or
$force_open
or
$opening
)) {
$tkt
->show(
-entry
=>
$newpath
);
$tkt
->setmode(
$newpath
=>
'close'
)
if
(
$force_open
and
$eltmode
ne
'none'
);
}
elsif
(
$force_close
and
$eltmode
eq
'open'
) {
$tkt
->hide(
-entry
=>
$newpath
);
}
else
{
$tkt
->
close
(
$newpath
);
}
$scan_sub
->(
$force_open
)
if
( (
$eltmode
ne
'open'
) or
$force_open
);
}
sub
trim_value {
my
$cw
=
shift
;
my
$value
=
shift
;
return
undef
unless
defined
$value
;
$value
=~ s/\n/ /g;
$value
=
substr
(
$value
, 0, 15 ) .
'...'
if
length
(
$value
) > 15;
return
$value
;
}
sub
disp_check_list {
my
(
$scanner
,
$data_ref
,
$node
,
$element_name
,
$index
,
$leaf_object
) =
@_
;
my
(
$path
,
$cw
,
$opening
,
$actions
) =
@$data_ref
;
$logger
->trace(
"disp_check_list path is $path"
);
my
$value
=
$leaf_object
->fetch;
my
$tkt
=
$cw
->{tktree};
$tkt
->itemCreate(
$path
, 2,
-text
=>
$cw
->trim_value(
$value
) );
my
$std_v
=
$leaf_object
->fetch(
'standard'
);
$tkt
->itemCreate(
$path
, 3,
-text
=>
$cw
->trim_value(
$std_v
) );
if
(
$leaf_object
->has_data ) {
$tkt
->itemCreate(
$path
, 1,
-itemtype
=>
'image'
,
-image
=>
$cust_img
);
}
else
{
$tkt
->itemDelete(
$path
, 1 )
if
$tkt
->itemExists(
$path
, 1 );
}
}
sub
disp_leaf {
my
(
$scanner
,
$data_ref
,
$node
,
$element_name
,
$index
,
$leaf_object
) =
@_
;
my
(
$path
,
$cw
,
$opening
,
$actions
) =
@$data_ref
;
$logger
->trace(
"disp_leaf path is $path"
);
my
$std_v
=
$leaf_object
->fetch(
qw/mode standard check no silent 1/
);
my
$value
=
$leaf_object
->fetch(
check
=>
'no'
,
silent
=> 1 );
my
$tkt
=
$cw
->{tktree};
my
$img
;
if
(
$leaf_object
->has_error) {
$img
=
$error_img
;
}
elsif
(
$leaf_object
->has_warning) {
$img
=
$warn_img
;
}
elsif
(
$leaf_object
->has_data) {
$img
=
$cust_img
;
}
if
(
defined
$img
) {
$tkt
->itemCreate(
$path
, 1,
-itemtype
=>
'image'
,
-image
=>
$img
);
}
elsif
(
$tkt
->itemExists(
$path
, 1 )) {
$tkt
->itemDelete(
$path
, 1 ) ;
}
$tkt
->itemCreate(
$path
, 2,
-text
=>
$cw
->trim_value(
$value
) );
$tkt
->itemCreate(
$path
, 3,
-text
=>
$cw
->trim_value(
$std_v
) );
}
sub
disp_node {
my
(
$scanner
,
$data_ref
,
$node
,
$element_name
,
$key
,
$contained_node
) =
@_
;
my
(
$path
,
$cw
,
$opening
,
$actions
) =
@$data_ref
;
$logger
->trace(
"disp_node path is $path"
);
my
$curmode
=
$cw
->{tktree}->getmode(
$path
);
$cw
->{tktree}->setmode(
$path
,
'open'
)
if
$curmode
eq
'none'
;
$scanner
->scan_node(
$data_ref
,
$contained_node
);
}
sub
setup_scanner {
my
(
$cw
) =
@_
;
my
$scanner
= Config::Model::ObjTreeScanner->new(
fallback
=>
'node'
,
check
=>
'no'
,
node_content_cb
=> \
&disp_obj_elt
,
list_element_cb
=> \
&disp_hash
,
check_list_element_cb
=> \
&disp_check_list
,
hash_element_cb
=> \
&disp_hash
,
node_element_cb
=> \
&disp_node
,
leaf_cb
=> \
&disp_leaf
,
enum_value_cb
=> \
&disp_leaf
,
integer_value_cb
=> \
&disp_leaf
,
number_value_cb
=> \
&disp_leaf
,
boolean_value_cb
=> \
&disp_leaf
,
string_value_cb
=> \
&disp_leaf
,
uniline_value_cb
=> \
&disp_leaf
,
reference_value_cb
=> \
&disp_leaf
,
up_cb
=>
sub
{ },
);
$cw
->{scanner} =
$scanner
;
}
my
%widget_table
= (
edit
=> {
leaf
=>
'ConfigModelLeafEditor'
,
check_list
=>
'ConfigModelCheckListEditor'
,
list
=>
'ConfigModelListEditor'
,
hash
=>
'ConfigModelHashEditor'
,
node
=>
'ConfigModelNodeEditor'
,
},
view
=> {
leaf
=>
'ConfigModelLeafViewer'
,
check_list
=>
'ConfigModelCheckListViewer'
,
list
=>
'ConfigModelListViewer'
,
hash
=>
'ConfigModelHashViewer'
,
node
=>
'ConfigModelNodeViewer'
,
},
);
sub
create_element_widget {
my
$cw
=
shift
;
my
$mode
=
shift
;
my
$tree_path
=
shift
;
my
$obj
=
shift
;
my
$tree
=
$cw
->{tktree};
unless
(
defined
$tree_path
) {
$tree_path
=
$tree
->nearest(
$tree
->pointery -
$tree
->rooty );
}
if
(
$tree
->info(
exists
=>
$tree_path
) ) {
$tree
->selectionClear();
$tree
->selectionSet(
$tree_path
);
my
$data_ref
=
$tree
->infoData(
$tree_path
);
unless
(
defined
$data_ref
->[1] ) {
$cw
->reload;
return
;
}
$obj
=
$data_ref
->[1];
weaken(
$obj
);
}
my
$loc
=
$obj
->location;
my
$type
=
$obj
->get_type;
$logger
->trace(
"item $loc to $mode (type $type)"
);
my
$e_frame
=
$cw
->Subwidget(
'ed_frame'
);
delete
$cw
->{editor};
map
{
$_
->destroy
if
Tk::Exists(
$_
) }
$e_frame
->children;
my
$widget
=
$widget_table
{
$mode
}{
$type
}
||
die
"Cannot find $mode widget for type $type"
;
my
$weak_cw
=
$cw
;
weaken(
$weak_cw
);
my
@store
=
$mode
eq
'edit'
? (
-store_cb
=>
sub
{
$weak_cw
->reload(
@_
) } ) : ();
$cw
->{current_mode} =
$mode
;
my
$tk_font
=
$cw
->cget(
'-font'
);
$cw
->{editor} =
$e_frame
->
$widget
(
-item
=>
$obj
,
-path
=>
$tree_path
,
-font
=>
$tk_font
,
@store
,
);
$cw
->{editor}->ConfigSpecs(
-font
=> [
'DESCENDANTS'
,
'font'
,
'Font'
,
$tk_font
]);
$cw
->{editor}->
pack
(
-expand
=> 1,
-fill
=>
'both'
);
return
$cw
->{editor};
}
sub
edit_copy {
my
$cw
=
shift
;
my
$tkt
=
$cw
->{tktree};
my
@selected
=
@_
?
@_
:
$tkt
->info(
'selection'
);
my
@res
;
foreach
my
$selection
(
@selected
) {
my
$data_ref
=
$tkt
->infoData(
$selection
);
my
$cfg_elt
=
$data_ref
->[1];
my
$type
=
$cfg_elt
->get_type;
my
$cfg_class
=
$type
eq
'node'
?
$cfg_elt
->config_class_name :
''
;
push
@res
,
[
$cfg_elt
->element_name,
$cfg_elt
->index_value,
$cfg_elt
->composite_name,
$type
,
$cfg_class
,
$cfg_elt
->dump_as_data() ];
}
$cw
->{cut_buffer} = \
@res
;
return
\
@res
;
}
sub
edit_paste {
my
$cw
=
shift
;
my
$tkt
=
$cw
->{tktree};
my
@selected
=
@_
?
@_
:
$tkt
->info(
'selection'
);
return
unless
@selected
;
my
@res
;
my
$selection
=
$selected
[0];
my
$data_ref
=
$tkt
->infoData(
$selection
);
my
$cfg_elt
=
$data_ref
->[1];
my
$t_type
=
$cfg_elt
->get_type;
my
$t_class
=
$t_type
eq
'node'
?
$cfg_elt
->config_class_name :
''
;
my
$t_name
=
$cfg_elt
->element_name;
my
$cut_buf
=
$cw
->{cut_buffer} || [];
foreach
my
$data
(
@$cut_buf
) {
my
(
$name
,
$index
,
$composite
,
$type
,
$cfg_class
,
$dump
) =
@$data
;
if
( (
$name
eq
$t_name
and
$type
eq
$t_type
)
or
$t_class
eq
$cfg_class
) {
$cfg_elt
->load_data(
$dump
);
}
elsif
( (
$t_type
eq
'hash'
or
$t_type
eq
'list'
) and
defined
$index
) {
$cfg_elt
->fetch_with_id(
$index
)->load_data(
$dump
);
}
elsif
(
$t_type
eq
'hash'
or
$t_type
eq
'list'
or
$t_type
eq
'leaf'
) {
$cfg_elt
->load_data(
$dump
);
}
else
{
$cfg_elt
->grab(
$composite
)->load_data(
$dump
);
}
}
$cw
->reload()
if
@$cut_buf
;
$cw
->create_element_widget(
$cw
->{current_mode},
$selection
);
}
sub
wizard {
my
$cw
=
shift
;
my
$wiz
=
$cw
->setup_wizard(
sub
{
$cw
->deiconify;
$cw
->raise;
$cw
->reload; } );
$cw
->withdraw;
$wiz
->prepare_wizard();
}
sub
setup_wizard {
my
$cw
=
shift
;
my
$end_sub
=
shift
;
my
$tk_font
=
$cw
->cget(
'-font'
);
return
$cw
->ConfigModelWizard(
-root
=>
$cw
->{instance}->config_root,
-end_cb
=>
$end_sub
,
-font
=>
$tk_font
,
);
}
sub
create_find_widget {
my
$cw
=
shift
;
my
$f
=
$cw
->Frame(
-relief
=>
'ridge'
,
-borderwidth
=> 1, );
my
$remove_img
=
$cw
->Photo(
-file
=>
$icon_path
.
'remove.png'
);
$f
->Button(
-image
=>
$remove_img
,
-command
=>
sub
{
$f
->packForget(); },
-relief
=>
'flat'
,
)->
pack
(
-side
=>
'left'
);
my
$searcher
=
$cw
->{instance}->config_root->tree_searcher(
type
=>
'all'
);
my
$search
=
''
;
my
@result
;
$f
->Label(
-text
=>
'Find: '
)->
pack
(
-side
=>
'left'
);
my
$e
=
$f
->Entry(
-textvariable
=> \
$search
,
-validate
=>
'key'
,
-validatecommand
=>
sub
{
@result
= ();
return
1; },
)->
pack
(
-side
=>
'left'
);
$cw
->Advertise(
find_entry
=>
$e
);
foreach
my
$direction
(
qw/previous next/
) {
my
$s
=
sub
{
$cw
->find_item(
$direction
,
$searcher
, \
$search
, \
@result
); };
$f
->Button(
-compound
=>
'left'
,
-image
=>
$gnome_img
{
$direction
},
-text
=>
ucfirst
(
$direction
),
-command
=>
$s
,
-relief
=>
'flat'
,
)->
pack
(
-side
=>
'left'
);
}
$e
->
bind
(
'<Key-Return>'
,
sub
{
$cw
->find_item(
'next'
,
$searcher
, \
$search
, \
@result
); } );
return
$f
;
}
sub
pack_find_widget {
my
$cw
=
shift
;
$cw
->Subwidget(
'find_frame'
)->
pack
(
-anchor
=>
'w'
,
-fill
=>
'x'
);
$cw
->Subwidget(
'find_entry'
)->focus;
}
sub
find_item {
my
(
$cw
,
$direction
,
$searcher
,
$search_ref
,
$result
) =
@_
;
my
$find_frame
=
$cw
->Subwidget(
'find_frame'
);
if
(not
@$result
) {
$logger
->debug(
"Running search on $$search_ref"
);
@$result
=
$searcher
->search(
$$search_ref
);
$logger
->trace(
"Search on $$search_ref result: @$result"
);
}
if
(
@$result
) {
if
(
defined
$cw
->{old_path} and
$direction
eq
'next'
) {
push
@$result
,
shift
@$result
;
}
elsif
(
defined
$cw
->{old_path} ) {
unshift
@$result
,
pop
@$result
;
}
my
$path
=
$result
->[0];
$cw
->{old_path} =
$path
;
$cw
->force_element_display(
$cw
->{instance}->config_root->grab(
$path
) );
}
}
1;