1 # vim: ft=tcl foldmethod=marker foldmarker=<<<,>>> ts=4 shiftwidth=4
4 # dirty signal handling
7 inherit tlc::Mywidget tlc::Signalsource tlc::Handlers tlc::Baselog
12 itk_option define -image image Image {}
21 variable helpwidth 200
24 variable linkhandler {}
26 method set_data {args}
27 method get_data {args}
30 method mark_dirty {newstate}
36 variable routing_tables
38 variable current_page ""
41 method load_page {page}
42 method load_page_form {details}
43 method load_page_html {details}
45 method apply_action {op}
47 method try_load_page {op page}
49 method html_escape {text}
50 method helpshown_changed {newstate}
51 method htmlclick {widget x y}
52 method busy_changed {newstate}
57 configbody tlc::Wizard::showhelp { #<<<
58 $signals(helpshown) set_state $showhelp
62 configbody tlc::Wizard::helpstyles { #<<<
63 $w.help.html style $helpstyles
67 configbody tlc::Wizard::state { #<<<
68 $signals(enabled) set_state [expr {$state eq "normal"}]
72 configbody tlc::Wizard::helpwidth { #<<<
73 $w.help.html configure -width $helpwidth
77 body tlc::Wizard::constructor {args} { #<<<
78 package require Tkhtml 3.0
81 array set page_details {}
82 array set routing_tables {}
83 array set pagesignals {}
85 tlc::Signal #auto signals(enabled) -name "$w enabled"
86 tlc::Signal #auto signals(helpshown) -name "$w helpshown"
87 tlc::Signal #auto signals(busy) -name "$w busy"
89 tlc::Gate #auto signals(back) -mode "and" -name "$w back"
90 tlc::Gate #auto signals(next) -mode "and" -name "$w next"
91 tlc::Gate #auto signals(finish) -mode "and" -name "$w finish"
92 tlc::Gate #auto signals(valid) -mode "and" -name "$w valid"
93 tlc::Gate #auto signals(dirty) -mode "and" -name "$w valid"
95 tlc::Signal #auto signals(back_defined) -name "$w back_defined"
96 tlc::Signal #auto signals(next_defined) -name "$w next_defined"
97 tlc::Signal #auto signals(finish_defined) -name "$w finish_defined"
98 tlc::Signal #auto signals(finished) -name "$w finished"
100 $signals(back) attach_input $signals(back_defined)
101 $signals(next) attach_input $signals(next_defined)
102 $signals(finish) attach_input $signals(finish_defined)
103 $signals(finish) attach_input $signals(valid)
105 $signals(back) attach_input $signals(finished) inverted
106 $signals(next) attach_input $signals(finished) inverted
107 $signals(finish) attach_input $signals(finished) inverted
109 $signals(back) attach_input $signals(busy) inverted
110 $signals(next) attach_input $signals(busy) inverted
111 $signals(finish) attach_input $signals(busy) inverted
113 ttk::label $w.title -font [$tlc::theme setting hugefont]
119 html $w.help.html -yscrollcommand [list $w.help.vsb set]
120 myscrollbar_win32 $w.help.vsb -orient v -command [list $w.help.html yview]
122 bind $w.help.html <Button-1> [code $this htmlclick $w.help.html %x %y]
124 $w.help.html 1,1 -fill both \
125 $w.help.vsb 1,2 -fill y
126 blt::table configure $w.help c2 -resize none
129 Tools $w.ops -buttonwidth 9
130 $w.ops add "< Back" [code $this apply_action back] right
131 $w.ops add "Next >" [code $this apply_action next] right
132 $w.ops add "Finish" [code $this finish] right
133 $w.ops add "Cancel" [code $this cancel] right
135 $w.ops attach_signal "< Back" $signals(back)
136 $w.ops attach_signal "Next >" $signals(next)
137 $w.ops attach_signal "Finish" $signals(finish)
140 $w.title 1,2 -anchor w -pady {0 5} \
141 $w.page 2,2 -fill both \
142 $w.ops 3,2 -fill x -pady 5 -padx 5
143 blt::table configure $w c1 r1 r3 -resize none
144 blt::table configure $w r1 r3 -resize none
146 $signals(helpshown) attach_output [code $this helpshown_changed]
148 itk_initialize {*}$args
150 foreach {page details} [tlc::decomment $pages] {
151 set page_details($page) [tlc::decomment $details]
154 foreach {page info} [tlc::decomment $routing] {
155 foreach {op expressions} [tlc::decomment $info] {
156 set op [string tolower $op]
157 set routing_tables($op,$page) [tlc::decomment $expressions]
161 $signals(busy) attach_output [code $this busy_changed]
167 body tlc::Wizard::destructor {} { #<<<
171 body tlc::Wizard::set_data {args} { #<<<
172 set argcount [llength $args]
173 if {$argcount == 1} {
174 set data [lindex $args 0]
175 } elseif {$argcount % 2 == 0} {
182 # TODO: update whatever page is loaded
186 body tlc::Wizard::get_data {args} { #<<<
187 if {[llength $args] == 0} {
188 return [array get dat]
193 body tlc::Wizard::load_page {page} { #<<<
195 if {![info exists page_details($page)]} {
196 error "No such page: $page" "" [list invalid_page $page]
198 $signals(busy) set_state 1
200 $signals(back_defined) set_state [info exists routing_tables(back,$page)]
201 $signals(next_defined) set_state [info exists routing_tables(next,$page)]
202 $signals(finish_defined) set_state [info exists routing_tables(finish,$page)]
204 set details $page_details($page)
207 if {[info exists unload_page]} {
211 if {[winfo exists $w.page.child]} {
212 destroy $w.page.child
216 if {[dict exists $details help]} {
217 $w.help.html style $helpstyles
218 $w.help.html parse -final [dict get $details help]
221 if {[dict exists $details title]} {
222 $w.title configure -text [dict get $details title]
224 $w.title configure -text ""
228 form {load_page_form $details}
229 html {load_page_html $details}
231 error "Invalid type \"$d(type)\" for page \"$page\"" "" \
232 [list invalid_type $d(type)]
236 set current_page $page
237 $signals(busy) set_state 0
242 body tlc::Wizard::load_page_form {details} { #<<<
249 Form $w.page.child {*}$d(options) -schema $d(schema)
251 $w.page.child set_data [array get dat]
253 $signals(next) attach_input [$w.page.child signal_ref form_valid]
254 $signals(valid) attach_input [$w.page.child signal_ref form_valid]
255 $signals(dirty) attach_input [$w.page.child signal_ref form_dirty]
258 $w.page.child 1,1 -fill both
260 $w.page.child takefocus
265 body tlc::Wizard::load_page_html {details} { #<<<
267 package require Tkhtml 3
268 ttk::frame $w.page.child
269 html $w.page.child.html \
270 -xscrollcommand [code $w.page.child.hsb set] \
271 -yscrollcommand [code $w.page.child.vsb set]
272 myscrollbar_win32 $w.page.child.hsb -orient h \
273 -command [code $w.page.child.html xview]
274 myscrollbar_win32 $w.page.child.vsb -orient v \
275 -command [code $w.page.child.html yview]
276 bind $w.page.child.html <Button-1> [code $this htmlclick $w.page.child.html %x %y]
278 if {[dict exists $details styles]} {
279 $w.page.child.html style [dict get $details styles]
282 set map [dict create]
283 foreach {k v} [array get dat] {
284 dict set map %$k% [html_escape $v]
286 set html [string map $map [dict get $details html]]
287 $w.page.child.html parse -final $html
289 blt::table $w.page.child \
290 $w.page.child.html 1,1 -fill both \
291 $w.page.child.hsb 2,1 -fill x \
292 $w.page.child.vsb 1,2 -fill y
293 blt::table configure $w.page.child r2 c2 -resize none
295 tlc::Signal #auto pagesignals(form_valid) -name "fake valid"
296 tlc::Signal #auto pagesignals(form_dirty) -name "fake dirty"
297 $pagesignals(form_valid) set_state 1
298 $pagesignals(form_dirty) set_state 1
300 $signals(next) attach_input $pagesignals(form_valid)
301 $signals(valid) attach_input $pagesignals(form_valid)
302 $signals(dirty) attach_input $pagesignals(form_dirty)
305 array unset pagesignals
306 array set pagesignals {}
310 $w.page.child 1,1 -fill both
315 body tlc::Wizard::apply_action {op} { #<<<
317 if {![$signals($op) state]} return
318 $signals(busy) set_state 1
319 set expressions $routing_tables($op,$current_page)
321 # TODO: Handle non-forms
323 [winfo exists $w.page.child] &&
324 [itcl::is object $w.page.child] &&
325 [$w.page.child isa tlc::Form]
327 array set dat [$w.page.child get_data]
330 if {[llength $expressions] == 1} {
331 # This is a convenience syntax where there is no logic to the next step
332 try_load_page $op $expressions
336 set default_target_page ""
337 foreach {expression target_page} $expressions {
338 if {$expression eq "default"} {
339 set default_target_page $target_page
343 try_load_page $op $target_page
347 if {$default_target_page ne ""} {
348 try_load_page $op $default_target_page
352 $signals(busy) set_state 0
353 error "No routing actions specified that are applicable" "" \
358 body tlc::Wizard::finish {} { #<<<
359 $signals(finished) set_state 1
363 body tlc::Wizard::try_load_page {op page} { #<<<
365 if {[info exists routing_tables(skip_if,$page)]} {
366 if $routing_tables(skip_if,$page) {
367 # TODO: handle finish
368 log debug "skipping because ($routing_tables(skip_if,$page)) is false"
369 set current_page $page
370 $signals(busy) set_state 0 ;# This raises a slight risk of a race
380 body tlc::Wizard::cancel {} { #<<<
381 if {$oncancel ne {}} {
389 body tlc::Wizard::reset {} { #<<<
396 body tlc::Wizard::html_escape {text} { #<<<
398 \xa0 \xa1 ¡ \xa2 ¢ \xa3 £ \xa4 ¤
399 \xa5 ¥ \xa6 ¦ \xa7 § \xa8 ¨ \xa9 ©
400 \xaa ª \xab « \xac ¬ \xad ­ \xae ®
401 \xaf ¯ \xb0 ° \xb1 ± \xb2 ² \xb3 ³
402 \xb4 ´ \xb5 µ \xb6 ¶ \xb7 · \xb8 ¸
403 \xb9 ¹ \xba º \xbb » \xbc ¼ \xbd ½
404 \xbe ¾ \xbf ¿ \xc0 À \xc1 Á \xc2 Â
405 \xc3 Ã \xc4 Ä \xc5 Å \xc6 Æ \xc7 Ç
406 \xc8 È \xc9 É \xca Ê \xcb Ë \xcc Ì
407 \xcd Í \xce Î \xcf Ï \xd0 Ð \xd1 Ñ
408 \xd2 Ò \xd3 Ó \xd4 Ô \xd5 Õ \xd6 Ö
409 \xd7 × \xd8 Ø \xd9 Ù \xda Ú \xdb Û
410 \xdc Ü \xdd Ý \xde Þ \xdf ß \xe0 à
411 \xe1 á \xe2 â \xe3 ã \xe4 ä \xe5 å
412 \xe6 æ \xe7 ç \xe8 è \xe9 é \xea ê
413 \xeb ë \xec ì \xed í \xee î \xef ï
414 \xf0 ð \xf1 ñ \xf2 ò \xf3 ó \xf4 ô
415 \xf5 õ \xf6 ö \xf7 ÷ \xf8 ø \xf9 ù
416 \xfa ú \xfb û \xfc ü \xfd ý \xfe þ
417 \xff ÿ \u192 ƒ \u391 Α \u392 Β \u393 Γ
418 \u394 Δ \u395 Ε \u396 Ζ \u397 Η \u398 Θ
419 \u399 Ι \u39A Κ \u39B Λ \u39C Μ \u39D Ν
420 \u39E Ξ \u39F Ο \u3A0 Π \u3A1 Ρ \u3A3 Σ
421 \u3A4 Τ \u3A5 Υ \u3A6 Φ \u3A7 Χ \u3A8 Ψ
422 \u3A9 Ω \u3B1 α \u3B2 β \u3B3 γ \u3B4 δ
423 \u3B5 ε \u3B6 ζ \u3B7 η \u3B8 θ \u3B9 ι
424 \u3BA κ \u3BB λ \u3BC μ \u3BD ν \u3BE ξ
425 \u3BF ο \u3C0 π \u3C1 ρ \u3C2 ς \u3C3 σ
426 \u3C4 τ \u3C5 υ \u3C6 φ \u3C7 χ \u3C8 ψ
427 \u3C9 ω \u3D1 ϑ \u3D2 ϒ \u3D6 ϖ
428 \u2022 • \u2026 … \u2032 ′ \u2033 ″
429 \u203E ‾ \u2044 ⁄ \u2118 ℘ \u2111 ℑ
430 \u211C ℜ \u2122 ™ \u2135 ℵ \u2190 ←
431 \u2191 ↑ \u2192 → \u2193 ↓ \u2194 ↔ \u21B5 ↵
432 \u21D0 ⇐ \u21D1 ⇑ \u21D2 ⇒ \u21D3 ⇓ \u21D4 ⇔
433 \u2200 ∀ \u2202 ∂ \u2203 ∃ \u2205 ∅
434 \u2207 ∇ \u2208 ∈ \u2209 ∉ \u220B ∋ \u220F ∏
435 \u2211 ∑ \u2212 − \u2217 ∗ \u221A √
436 \u221D ∝ \u221E ∞ \u2220 ∠ \u2227 ∧ \u2228 ∨
437 \u2229 ∩ \u222A ∪ \u222B ∫ \u2234 ∴ \u223C ∼
438 \u2245 ≅ \u2248 ≈ \u2260 ≠ \u2261 ≡ \u2264 ≤
439 \u2265 ≥ \u2282 ⊂ \u2283 ⊃ \u2284 ⊄ \u2286 ⊆
440 \u2287 ⊇ \u2295 ⊕ \u2297 ⊗ \u22A5 ⊥
441 \u22C5 ⋅ \u2308 ⌈ \u2309 ⌉ \u230A ⌊
442 \u230B ⌋ \u2329 ⟨ \u232A ⟩ \u25CA ◊
443 \u2660 ♠ \u2663 ♣ \u2665 ♥ \u2666 ♦
444 \x22 " \x26 & \x3C < \x3E > \u152 Œ
445 \u153 œ \u160 Š \u161 š \u178 Ÿ
446 \u2C6 ˆ \u2DC ˜ \u2002   \u2003   \u2009  
447 \u200C ‌ \u200D ‍ \u200E ‎ \u200F ‏ \u2013 –
448 \u2014 — \u2018 ‘ \u2019 ’ \u201A ‚
449 \u201C “ \u201D ” \u201E „ \u2020 †
450 \u2021 ‡ \u2030 ‰ \u2039 ‹ \u203A ›
456 body tlc::Wizard::helpshown_changed {newstate} { #<<<
459 $w.help 1,1 -rspan 3 -fill both
461 if {"$w.help" in [blt::table search $w]} {
462 blt::table forget $w.help
468 body tlc::Wizard::htmlclick {widget x y} { #<<<
469 set raw [$widget node -index $x $y]
470 if {$raw eq ""} return
472 lassign $raw node byteoffset
473 set parent [$node parent]
474 if {[string tolower [$parent tag]] ne "a"} return
475 set href [$parent attribute -default "" href]
476 if {$href eq ""} return
478 invoke_handlers html_link_clicked $href
482 body tlc::Wizard::unfinish {} { #<<<
483 $signals(finished) set_state 0
487 body tlc::Wizard::busy_changed {newstate} { #<<<
492 body tlc::Wizard::mark_dirty {newstate} { #<<<
493 log warning "Not clear what to do here"