1 # vim: ft=tcl foldmethod=marker foldmarker=<<<,>>> ts=4 shiftwidth=4
4 # valid_status_changed(isvalid?, {reasons}) Fired when the form valid status
6 # onchange(datarray) Fired when some form data is changed
7 # onchange,field(newvalue) Fired when specific form element is changed
9 proc tlc::form {pathName args} {
10 return [uplevel [list tlc::Form $pathName] $args]
13 ::itk::usual tlc::Form {
14 # This is a placeholder. Flesh out with appropriate options
19 inherit tlc::Border tlc::Handlers tlc::Textvariable tlc::Signalsource
20 #inherit tlc::Formbase tlc::Border
25 itk_option define -padding padding Padding 0 {need_rerender}
26 itk_option define -tooltipdelay toolTipDelay ToolTipDelay 1500 {}
27 itk_option define -tooltiptimeout toolTipTimeout ToolTipTimeout 3500 {}
28 itk_option define -winding winding Winding v {need_rerender}
32 variable state "normal"
35 method set_data {args}
37 method get_data {args}
39 method itemconfig {name args}
41 method path_by_var {varname}
42 method set_tips {args}
44 method dirty_gate_ref {} ;# Obsolete, use signal_ref form_dirty
45 method valid_gate_ref {} ;# Obsolete, use signal_ref form_valid
46 method changed_dom_ref {}
48 method mark_dirty {state}
49 method item_attach_signal {name signal {sense normal}}
50 method item_attach_expression {name expression}
51 method varname_from_path {path}
52 method arm_dirty {state}
53 method force_form_valid_update {}
54 method valid_condition {new_condition desc fields}
55 method valid_signal {signal desc fields}
56 method default_options {type args}
60 method textvariable_changed {newvalue}
61 method handlers_debug {lvl msg} {log $lvl $msg}
62 method widget_destroyed {}
73 variable valid_toggles
75 variable expression_lists
76 variable valid_conditions {}
77 variable valid_signals
81 variable default_options
82 variable onchange_handlers {}
86 method need_rerender {}
87 method dat_changed {n1 n2 op}
88 method clear_valid_conditions {}
90 method valid_changed {condition desc fields newstate}
91 method update_valid_desc {}
92 method enabled_changed {newstate}
93 method form_valid_reasons_changed {}
94 method canonize_type {type}
96 method run_onchange_handlers {}
101 configbody tlc::Form::schema { #<<<1
103 set schema [tlc::decomment $schema]
108 configbody tlc::Form::state { #<<<1
109 log debug "state: ($state)"
110 [stategate_ref] configure -default [expr {$state eq "normal"}]
114 configbody tlc::Form::tooltipdelay { #<<<1
115 $w.tips configure -delay $itk_option(-tooltipdelay)
119 configbody tlc::Form::tooltiptimeout { #<<<1
120 $w.tips configure -showtime $itk_option(-tooltiptimeout)
124 body tlc::Form::constructor {args} { #<<<1
126 array set paths_by_var {}
128 array set valid_toggles {}
129 array set expressions {}
130 array set expression_lists {}
131 array set valid_signals {}
132 array set field_valid {}
134 array set default_options {}
136 tlc::Domino #auto dominos(need_rerender) -name "$w need_rerender"
137 tlc::Domino #auto dominos(run_onchange_handlers) -name "$w run_onchange_handlers"
138 tlc::Domino #auto dominos(need_valid_desc_update) \
139 -name "$w need_valid_desc_update"
140 tlc::Varwatch #auto signals(form_dirty) -name "$w form_dirty"
141 tlc::Vardomino #auto dominos(changed) -name "$w changed" \
142 -textvariable [scope dat]
143 $signals(form_dirty) attach_dirtyvar [scope dat]
144 $signals(form_dirty) register_handler onchange_info [code $this dat_changed]
145 tlc::Hoverbox $w.tips
146 tlc::Hoverbox $w.valid_tips
147 tlc::Gate #auto signals(enabled) -name "$w enabled" \
148 -mode "and" -default 1
149 tlc::Gate #auto signals(form_valid) -name "$w form_valid" \
150 -mode "and" -default 1
151 tlc::Domino #auto dominos(reasons_changed) -name "$w reasons_changed"
153 $signals(enabled) attach_input [stategate_ref]
154 $signals(enabled) attach_output [code $this enabled_changed]
156 $dominos(reasons_changed) attach_output \
157 [code $this form_valid_reasons_changed]
158 $signals(enabled) attach_output [list $dominos(reasons_changed) tip]
160 $dominos(need_rerender) attach_output [code $this rerender]
161 $dominos(need_valid_desc_update) attach_output \
162 [code $this update_valid_desc]
164 $dominos(run_onchange_handlers) attach_output \
165 [code $this run_onchange_handlers]
167 itk_initialize {*}$args
172 log debug "Setting name ($name) for $w"
174 set baselog_instancename $name
179 body tlc::Form::destructor {} { #<<<1
182 # Thse reference widgets that may not exist anymore (if we are here
183 # because of destroy rather than delete object, for instance), and
184 # the standard cleanup will have them change state, and hence try to
185 # configure their widgets. Doing this causes them to die before their
186 # inputs, which stops them trying to update their outputs
188 #foreach child [winfo children $w] {
189 # if {[itcl::is object -class tlc::Form $child]} {
190 # log debug "Pre-emptively deleting subform $child"
191 # delete object $child
197 body tlc::Form::set_data {args} { #<<<1
198 $dominos(need_rerender) force_if_pending
200 if {[llength $args] == 1} {
201 set data [lindex $args 0]
205 if {[llength $data] % 2 != 0} {
206 log error "Badly formatted data, must be a list of key value pairs"
207 error "Badly formatted data, must be a list of key value pairs" "" \
208 [list data_format_error]
211 $signals(form_dirty) disarm
213 log debug "Setting dat:"
216 $dominos(run_onchange_handlers) tip
219 foreach {varname exprs} [array get expression_lists] {
220 foreach exprname $exprs {
221 $expressions($exprname) reassess
225 $signals(form_dirty) arm
226 if {[$signals(form_dirty) is_armed]} {
227 $signals(form_dirty) set_state 0
232 body tlc::Form::set_key {args} { #<<<1
234 switch -- [llength $args] {
235 0 {error "No key specified" "" [list syntax_error no_key_specified]}
236 1 {error "No value specified" "" [list syntax_error no_value_specified]}
238 set key [lindex $args 0]
239 set val [lindex $args 1]
240 return [set_data $key $val]
244 set key [lindex $args 0]
245 set keys [lrange $args 1 end-1]
246 set val [lindex $args end]
247 set handler [path_by_var $key]
248 if {[itcl::is object $handler] && [$handler isa tlc::Form]} {
249 $handler set_key {*}$keys $val
250 set dat($key) [$handler get_data]
252 # TODO: recursive setter that composes the nested array list on unwind
253 error "Only subforms are currently supported for deep key sets" "" \
254 [list not_implemented]
259 body tlc::Form::get_data {args} { #<<<1
260 $dominos(need_rerender) force_if_pending
263 if {[llength $args] == 0} {
264 return [array get dat]
265 } elseif {[llength $args] == 1} {
266 set key [lindex $args 0]
267 if {![info exists dat($key)]} {
268 error "Bad key: \"$key\", should be one of [join [array names tmp] {, }]" "" \
269 [list bad_key [list $key]]
273 set last [array get dat]
281 error "Cannot build array from key level [llength $so_far] \"[join $so_far ->]\": $errmsg" "" \
282 [list format_error $so_far]
286 if {![info exists tmp($key)]} {
287 error "Bad key: \"[join $so_far ->]\", should be one of [join [array names tmp] {, }]" "" \
288 [list bad_key $so_far]
297 body tlc::Form::dirty_gate_ref {} { #<<<1
298 log warning "dirty_gate_ref deprecated, use \"signal_ref form_dirty\" instead"
299 return $signals(form_dirty)
303 body tlc::Form::rerender {} { #<<<1
304 if {$schema eq $oldschema} return
308 $signals(form_dirty) disarm
310 destroy {*}[lindex [tlc::intersect3 [winfo children $w] [list $w.tips $w.valid_tips]] 0]
313 # Layout context init
315 label_col_args_sticky {}
316 label_cell_args_sticky {-anchor ne}
317 label_widget_args_sticky {}
318 cell_args_sticky {-anchor nw}
321 set layout(row_args_sticky) [list -pady $itk_option(-padding)]
323 catch {unset paths}; array set paths {}
324 catch {unset paths_by_var}; array set paths_by_var {}
325 catch {unset toggles}; array set toggles {}
326 catch {unset valid_toggles}; array set valid_toggles {}
327 catch {unset field_valid}; array set field_valid {}
328 catch {unset valid_signals}; array set valid_signals {}
329 catch {unset expressions}
330 catch {unset expression_lists}
331 clear_valid_conditions
336 foreach {label info} $schema {
342 set layout(label_cell_args) $layout(label_cell_args_sticky)
343 set layout(label_col_args) $layout(label_col_args_sticky)
344 set layout(label_widget_args) $layout(label_widget_args_sticky)
345 set layout(cell_args) $layout(cell_args_sticky)
346 set layout(row_args) $layout(row_args_sticky)
347 set layout(col_args) $layout(col_args_sticky)
350 if {[string index $label 0] eq "_"} {
353 if {$label eq "_layout"} {
354 switch -- [lindex $info 0] {
358 label_cell_args_sticky -
359 label_widget_args_sticky -
360 label_col_args_sticky {
361 set layout([lindex $info 0]) [lrange $info 1 end]
362 set layout([string range [lindex $info 0] 0 end-7]) \
366 log warning "_layout label_args_sticky is deprecated - use label_cell_args_sticky"
367 set layout(label_cell_args_sticky) [lrange $info 1 end]
368 set layout(label_cell_args) \
378 set layout([lindex $info 0]) [lrange $info 1 end]
382 log warning "_layout label_args is deprecated - use label_cell_args"
383 set layout(label_cell_args) [lrange $info 1 end]
390 switch -- [string index $itk_option(-winding) 0] {
394 set layout(row_args) $layout(row_args_sticky)
401 set layout(col_args) $layout(col_args_sticky)
412 switch -- [string index $itk_option(-winding) 0] {
414 set amnt [lindex $info 1]
415 if {$amnt eq ""} {set amnt 1}
422 set amnt [lindex $info 1]
423 if {$amnt eq ""} {set amnt 1}
430 log warning "default_options mode of _layout is deprecated. Use top level directive _default_options instead"
431 set type [canonize_type [lindex $info 1]]
432 set default_options($type) [lrange $info 2 end]
436 error "Unrecognised layout parameter: ([lindex $info 0])"
442 _validation - _validation_not_blank { #<<<
443 if {$label eq "_validation"} {
444 valid_condition [lindex $info 0] [lindex $info 1] [lindex $info 2]
446 if {$label eq "_validation_not_blank"} {
447 set template [lindex $info 0]
448 foreach {labelname varname} [lrange $info 1 end] {
449 set condition "\[string trim \$dat([string map {{ } {\ } {)} {\)}} $varname])\] ne {}"
452 [string map [list %1 $labelname] $template] \
458 _tips - _tooltips { #<<<
463 log debug "Setting defaults: ($info)"
468 _default_options { #<<<
469 set type [canonize_type [lindex $info 0]]
470 set default_options($type) [lrange $info 1 end]
474 lappend onchange_handlers $info
478 error "Invalid meta directive \"$label\", must be one of _layout, _validation, _validation_not_blank, _tips or _tooltips" "" [list invalid_meta_directive $label]
487 lassign $info varname type
488 lappend formvars $varname
489 set type [canonize_type $type]
491 if {[info exists default_options($type)]} {
492 set arglist [concat $default_options($type) [lrange $info 2 end]]
494 set arglist [lrange $info 2 end]
497 if {[string index $label 0] eq " "} {
498 ttk::label $w.$row,$col,l -text "" -width 0
500 ttk::label $w.$row,$col,l -text $label
502 if {[llength $layout(label_widget_args)] > 0} {
503 $w.$row,$col,l configure {*}$layout(label_widget_args)
506 if {![info exists dat($varname)]} {
509 if {[string first " " $varname] != -1} {
510 # Itcl bug (still as of 3.3) prevents scope / resolver working
511 # with arrays whose keys contain spaces. We craft our own here
512 # that works with the resolver
515 #set scoped_varname "[list @itcl $this] [namespace current]::dat($varname)"
516 set tmp [scope dat($varname)]
517 set scoped_varname [concat [lrange $tmp 0 1] [lindex $tmp 2]]
519 set scoped_varname [scope dat($varname)]
522 tlc::Gate #auto field_valid($varname) -name "$w field_valid $varname" \
523 -mode "and" -default 1
524 tlc::StateToggle #auto valid_toggles($varname) \
525 -mode "or" -default 1 \
527 -foreground {red black}
528 #tlc::StateToggle #auto valid_toggles($varname) \
529 # -mode "or" -default 1 \
531 ## -font [list [$tlc::theme setting boldfont] [$tlc::theme setting font]]
532 $valid_toggles($varname) attach_signal $signals(enabled) inverted
533 $valid_toggles($varname) attach_signal $field_valid($varname)
534 $valid_toggles($varname) attach_output [list apply {
536 if {![winfo exists $widget]} return
538 $widget state !invalid
540 $widget state invalid
545 $valid_toggles($varname) attach_output \
546 [list $dominos(reasons_changed) tip]
548 set toggle {-state {disabled normal}}
551 ttk::entry $w.$row,$col,v -textvariable $scoped_varname \
556 ttk::checkbutton $w.$row,$col,v -variable $scoped_varname \
561 ttk::label $w.$row,$col,v -textvariable $scoped_varname \
563 -font [$tlc::theme setting boldfont] \
568 tlc::vartextbox $w.$row,$col,v -textvariable $scoped_varname \
573 ttk::button $w.$row,$col,v {*}$arglist
577 tlc::mycombobox $w.$row,$col,v -textvariable $scoped_varname \
582 tlc::Combobox $w.$row,$col,v -textvariable $scoped_varname \
587 tlc::Dateentry $w.$row,$col,v -textvariable $scoped_varname \
592 tlc::Calendar $w.$row,$col,v -command [code set dat($varname)] \
597 tlc::Fileselectbox $w.$row,$col,v \
598 -textvariable $scoped_varname \
603 tlc::Browse_tktreectrl_list $w.$row,$col,v \
604 -textvariable $scoped_varname \
609 tlc::Lookup $w.$row,$col,v \
610 -textvariable $scoped_varname \
615 tlc::Spinint $w.$row,$col,v \
616 -textvariable $scoped_varname \
621 tlc::Spinner $w.$row,$col,v \
622 -textvariable $scoped_varname \
624 # TODO: wire valid_signal?
628 message $w.$row,$col,v \
629 -textvariable $scoped_varname \
630 -font [$tlc::theme setting boldfont] \
636 ttk::entry $w.$row,$col,v -textvariable $scoped_varname \
638 -validatecommand {string is integer {%P}} \
643 tlc::Tagentry $w.$row,$col,v \
644 -textvariable $scoped_varname \
649 tlc::Radiogroup $w.$row,$col,v \
650 -textvariable $scoped_varname \
655 tlc::Checkgroup $w.$row,$col,v \
656 -textvariable $scoped_varname \
661 tlc::Form $w.$row,$col,v -textvariable $scoped_varname \
662 -name "$name -> $varname" \
664 valid_signal [$w.$row,$col,v signal_ref form_valid] "" $varname
668 tlc::Tablelist $w.$row,$col,v \
669 -textvariable $scoped_varname \
671 # TODO: wire valid_signal?
675 if {[info exists tlc::Form::custom_types($type)]} {
676 $tlc::Form::custom_types($type) $w.$row,$col,v \
677 -textvariable $scoped_varname \
680 log error "Unknown type: ($type)"
686 # Automatically wire in the element's valid signal, if it has one <<<
688 [itcl::is object $w.$row,$col,v] &&
689 [$w.$row,$col,v isa tlc::Signalsource] &&
690 "valid" in [$w.$row,$col,v signals_available]
693 [$w.$row,$col,v signal_ref valid] \
694 "$label is invalid" \
697 # Automatically wire in the element's valid signal, if it has one >>>
699 set paths($label) $w.$row,$col,v
700 set paths_by_var($varname) $w.$row,$col,v
701 tlc::StateToggle #auto toggles($varname) $w.$row,$col,v \
703 $toggles($varname) attach_signal $signals(enabled)
705 set dc [expr {$col+1}]
706 blt::table $w $w.$row,$col,l $row,$col {*}$layout(label_cell_args)
707 blt::table configure $w c$col -resize none
709 blt::table $w $w.$row,$col,v $row,$dc {*}$layout(cell_args)
710 blt::table configure $w c$col {*}$layout(label_col_args)
711 blt::table configure $w c$dc {*}$layout(col_args)
712 blt::table configure $w r$row {*}$layout(row_args)
714 switch -- [string index $itk_option(-winding) 0] {
729 if {![info exists signals(form_dirty)]} {
730 log error "Freak out - signals(form_dirty) missing\n$w\n[tlc::stackdump]"
733 run_onchange_handlers
735 $signals(form_dirty) arm
736 $signals(form_dirty) set_state 0
740 body tlc::Form::takefocus {} { #<<<1
741 $dominos(need_rerender) force_if_pending
742 if {[winfo exists $w.0,0,v]} {
748 body tlc::Form::need_rerender {} { #<<<1
749 $dominos(need_rerender) tip
753 body tlc::Form::itemconfig {name args} { #<<<1
754 set path [path $name]
755 $path configure {*}$args
759 body tlc::Form::path {name} { #<<<1
760 $dominos(need_rerender) force_if_pending
762 if {[string is integer -strict $name]} {
763 if {[winfo exists $w.$name,0,v]} {
766 error "No such item index: ($name)"
768 } elseif {[regexp {[0-9]+,[0-9]+} $name]} {
769 lassign [split $name ,] r c
770 if {[winfo exists $w.$r,$c,v]} {
773 error "No such item index: ($name)"
776 if {![info exists paths($name)]} {
777 error "No such form item: ($name), choose from (\"[join [array names paths] {", "}]\")"
784 body tlc::Form::changed_dom_ref {} { #<<<1
785 return $dominos(changed)
789 body tlc::Form::set_tips {args} { #<<<1
790 if {[llength $args] == 1} {
791 # Support the variant syntax of one item packed with all the tips
793 set args [lindex $args 0]
795 foreach {label tip} $args {
796 set widget [path $label]
797 $w.tips attach $widget $tip
803 body tlc::Form::mark_dirty {state} { #<<<1
805 $signals(form_dirty) set_state $state
809 body tlc::Form::path_by_var {varname} { #<<<1
810 $dominos(need_rerender) force_if_pending
812 if {![info exists paths_by_var($varname)]} {
813 error "No such form item: ($varname)" "" \
814 [list invalid_form_item $varname]
816 return $paths_by_var($varname)
819 body tlc::Form::dat_changed {n1 n2 op} { #<<<1
821 log debug "dat($n2) changed: ($dat($n2))"
822 } elseif {$op eq "u"} {
823 log debug "dat($n2) unset"
828 if {[info exists dat($n2)]} {
829 if {[info exists expression_lists($n2)]} {
830 foreach exprname $expression_lists($n2) {
831 $expressions($exprname) reassess
836 $dominos(run_onchange_handlers) tip
838 set_textvariable [get_data]
840 if {[$signals(form_dirty) is_armed]} {
841 if {[info exists dat($n2)]} {
842 invoke_handlers onchange,$n2 $dat($n2)
844 invoke_handlers onchange [array get dat]
846 $signals(form_dirty) set_state 1
851 body tlc::Form::clear_data {} { #<<<1
853 $signals(form_dirty) disarm
856 # TODO: figure out why this is necessary
857 foreach key [array names dat] {
862 #array set toggles {}
863 #array unset expressions
864 $signals(form_dirty) set_state 0
865 $signals(form_dirty) arm
869 body tlc::Form::item_attach_signal {name signal {sense normal}} { #<<<1
870 set path [path $name]
871 set varname [varname_from_path $path]
872 $toggles($varname) attach_signal $signal $sense
876 body tlc::Form::item_attach_expression {name expression} { #<<<1
877 set path [path $name]
878 set varname [varname_from_path $path]
879 #array unset expressions $varname
880 set exprname "$varname[incr expr_seq]"
881 tlc::Expression #auto expressions($exprname) -name "Form expression on $varname ($exprname)"
882 lappend expression_lists($varname) $exprname
883 set expression [string map [list \$value %<dat($varname)%>] $expression]
884 $expressions($exprname) set_expression $expression
886 return $expressions($exprname)
890 body tlc::Form::varname_from_path {path} { #<<<1
892 foreach {v p} [array get paths_by_var] {
903 error "No varname known for path: ($path)"
908 body tlc::Form::arm_dirty {state} { #<<<1
911 $signals(form_dirty) arm
913 $signals(form_dirty) disarm
918 body tlc::Form::valid_gate_ref {} { #<<<1
919 log warning "valid_gate_ref deprecated, use \"signal_ref form_valid\" instead"
920 return $signals(form_valid)
924 body tlc::Form::valid_condition {new_condition desc fields} { #<<<1
925 tlc::Signal #auto valid_signals($new_condition) -name "valid_signals($new_condition)"
926 lappend valid_conditions [list $new_condition $desc $fields $valid_signals($new_condition)]
928 foreach field $fields {
929 $field_valid($field) attach_input $valid_signals($new_condition)
930 $valid_toggles($field) attach_output \
931 [code $this valid_changed $new_condition $desc $fields]
936 $signals(form_valid) attach_input $valid_signals($new_condition)
940 body tlc::Form::valid_signal {signal desc fields} { #<<<1
941 $dominos(need_rerender) force_if_pending
943 lappend valid_conditions [list {} $desc $fields $signal]
945 foreach field $fields {
946 $field_valid($field) attach_input $signal
947 $valid_toggles($field) attach_output \
948 [code $this valid_changed {} $desc $fields]
951 $signals(form_valid) attach_input $signal
955 body tlc::Form::check_valid {} { #<<<1
957 foreach condition_desc $valid_conditions {
958 lassign $condition_desc condition desc fields signal
959 if {$condition ne {}} {
961 $signal set_state [expr $condition]
964 log error "Error in valid check \"$condition\", desc: \"$desc\": $errmsg"
972 body tlc::Form::clear_valid_conditions {} { #<<<1
973 set valid_conditions {}
979 body tlc::Form::valid_changed {condition desc fields newstate} { #<<<1
980 $dominos(need_valid_desc_update) tip
984 body tlc::Form::update_valid_desc {} { #<<<1
985 array set new_desc {}
987 foreach condition_desc $valid_conditions {
988 lassign $condition_desc condition desc fields signal
989 foreach field $fields {
990 if {![$valid_toggles($field) state]} {
991 lappend new_desc($field) $desc
996 set obsolete [lindex [tlc::intersect3 [array names paths_by_var] [array names new_desc]] 0]
997 foreach field $obsolete {
998 $w.valid_tips detach [$valid_toggles($field) target]
1000 foreach {field desclist} [array get new_desc] {
1001 $w.valid_tips attach [$valid_toggles($field) target] [join $desclist \n]
1006 body tlc::Form::force_form_valid_update {} { #<<<1
1007 form_valid_reasons_changed
1011 body tlc::Form::enabled_changed {newstate} { #<<<1
1013 $w.valid_tips popdown
1018 body tlc::Form::form_valid_reasons_changed {} { #<<<1
1019 set newstate [$signals(form_valid) state]
1021 invoke_handlers valid_status_changed $newstate {}
1023 if {[$signals(enabled) state]} {
1025 foreach condition_desc $valid_conditions {
1026 lassign $condition_desc condition desc fields signal
1027 if {![$signal state]} {
1028 lappend reasons $desc
1031 invoke_handlers valid_status_changed $newstate $reasons
1033 invoke_handlers valid_status_changed $newstate "Form disabled"
1039 body tlc::Form::textvariable_changed {newvalue} { #<<<1
1040 log debug "$name newvalue: ($newvalue)"
1041 array set tmp $newvalue
1042 set existing [array names dat]
1043 set new [array names tmp]
1046 set removed_keys [lindex [tlc::intersect3 $existing $new] 2]
1047 foreach key $removed_keys {
1055 body tlc::Form::default_options {type args} { #<<<1
1056 set type [canonize_type $type]
1058 set default_options($type) $args
1062 body tlc::Form::canonize_type {type} { #<<<1
1065 entry {return "entry"}
1067 checkbutton {return "checkbutton"}
1068 label {return "label"}
1071 text {return "text"}
1072 button {return "button"}
1073 combobox {return "combobox"}
1074 mycombobox {return "mycombobox"}
1076 dateentry {return "dateentry"}
1077 calendar {return "calendar"}
1078 fileselect {return "fileselect"}
1079 list {return "list"}
1080 lookup {return "lookup"}
1081 spinint {return "spinint"}
1082 spinner {return "spinner"}
1083 message {return "message"}
1084 intentry {return "intentry"}
1085 tagentry {return "tagentry"}
1086 radiogroup {return "radiogroup"}
1087 checkgroup {return "checkgroup"}
1089 form {return "subform"}
1090 tablelist {return "tablelist"}
1093 if {[info exists tlc::Form::custom_types($type)]} {
1096 error "Unknown type: ($type)"
1103 body tlc::Form::cleanup {} { #<<<1
1104 #log warning "$w:[tlc::stackdump]"
1106 array unset valid_toggles
1112 body tlc::Form::widget_destroyed {} { #<<<1
1115 #log warning "$w done"
1119 body tlc::Form::run_onchange_handlers {} { #<<<1
1121 set before [array get dat]
1123 set changed_fields {}
1124 set tmp [tlc::intersect3 [dict keys $old] [dict keys $before]]
1125 foreach key [lindex $tmp 0] {
1126 dict set changed_fields $key "removed"
1128 foreach key [lindex $tmp 1] {
1129 if {[dict get $old $key] ne [dict get $before $key]} {
1130 dict set changed_fields $key "changed"
1133 foreach key [lindex $tmp 2] {
1134 dict set changed_fields $key "added"
1138 set slave [interp create -safe]
1140 $slave alias element apply {
1141 {form label op args} {
1142 return [[$form path $label] $op {*}$args]
1146 #$slave alias log [code $this log] ;# is this safe?
1147 $slave alias log apply {
1153 $slave eval [list array set dat [array get dat]]
1154 $slave eval [list set old $old]
1155 $slave eval [list set new $before]
1156 $slave eval [list set changed_fields $changed_fields]
1158 foreach handler $onchange_handlers {
1159 $slave eval [list apply {
1161 if {[catch {uplevel #0 $code} errmsg options]} {
1162 log error "Error in _onchange handler: $errmsg\n$::errorInfo"
1168 set after [$slave eval {array get dat}]
1173 if {[info exists slave]} {
1174 interp delete $slave
1177 if {[info exists after]} {
1179 dict for {k v} $after {
1180 log debug "Checking ($k) ($v), existed before? ([dict exists $before $k])"
1182 ![dict exists $before $k] ||
1183 [dict get $before $k] ne [dict get $after $k]
1185 dict set changeset $k $v
1189 if {[llength $changeset] > 0} {
1190 log debug "Applying changes to dat():"
1191 array set chgset $changeset
1193 $dominos(run_onchange_handlers) lock
1195 array set dat $changeset
1197 $dominos(run_onchange_handlers) unlock
1202 set old [array get dat]