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