Scid  4.7.0
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros
misc.tcl
Go to the documentation of this file.
1 ###
2 ### misc.tcl: part of Scid.
3 ### Copyright (C) 2001 Shane Hudson.
4 ### Copyright (C) 2007 Pascal Georges
5 ### Copyright (C) 2015 Fulvio Benini
6 ###
7 ### Miscellaneous routines called by other Tcl functions
8 
9 ################################################################################
10 # vwait but will timeout after a delay. Var must be fully qualified (::)
11 ################################################################################
12 proc vwaitTimed { var {delay 0} {warn "warnuser"} } {
13 
14  proc trigger {var warn} {
15  if {$warn == "warnuser"} {
16  tk_messageBox -type ok -icon error -parent . -title "Protocol error" -message "vwait timeout for $var"
17  }
18  set $var 1
19  }
20 
21  if { $delay != 0 } {
22  set timerId [after $delay "trigger $var $warn"]
23  }
24 
25  vwait $var
26 
27  if [info exists timerId] { after cancel $timerId}
28 
29 }
30 
31 ################################################################################
32 # bindFocusColors:
33 # Configures a text or entry widget so it turns lightYellow when it
34 # gets the focus, and turns white again when it loses focus.
35 #
36 # THIS IS CURRENTLY DISABLED since it works fine with regular entry widgets
37 # but causes problems with our combobox widgets, not sure why!
38 #
39 proc bindFocusColors {w {inColor lightYellow} {outColor white}} {
40  $w configure -background $outColor
41  #bind $w <FocusIn> "+$w configure -background $inColor"
42  #bind $w <FocusOut> "+$w configure -background $outColor"
43 }
44 
45 
46 ## FROM TK 8.5.9
47 ## ttk::bindMouseWheel $bindtag $command...
48 # Adds basic mousewheel support to $bindtag.
49 # $command will be passed one additional argument
50 # specifying the mousewheel direction (-1: up, +1: down).
51 #
52 
53 proc bindMouseWheel {bindtag callback} {
54  switch -- [tk windowingsystem] {
55  x11 {
56  bind $bindtag <ButtonPress-4> "$callback -1; break"
57  bind $bindtag <ButtonPress-5> "$callback +1; break"
58  }
59  win32 {
60  bind $bindtag <<MWheel>> "[append callback { [expr {-(%d/120)}]}]; break"
61  }
62  aqua {
63  bind $bindtag <MouseWheel> "[append callback { [expr {-(%D)}]}]; break"
64  }
65  }
66 }
67 
68 # dialogbuttonframe:
69 # Creates a frame that will be shown at the bottom of a
70 # dialog window. It takes two parameters: the frame widget
71 # name to create, and a list of button args. Each element
72 # should contain a widget name, and button arguments.
73 #
74 proc dialogbuttonframe {frame buttonlist} {
75  ttk::frame $frame
76  set bnames {}
77  set maxlength 0
78  foreach buttonargs $buttonlist {
79  set bname $frame.[lindex $buttonargs 0]
80  set bargs [lrange $buttonargs 1 end]
81  eval ttk::button $bname $bargs
82  set bnames [linsert $bnames 0 $bname]
83  set length [string length [$bname cget -text]]
84  if {$length > $maxlength} { set length $maxlength}
85  }
86  if {$maxlength < 7} { set maxlength 7}
87  foreach b $bnames {
88  $b configure -width $maxlength -padx 4
89  pack $b -side right -padx 4 -pady 4
90  }
91 }
92 
93 # packbuttons
94 # Packs a row of dialog buttons to the left/right of their frame
95 # with a standard amount of padding.
96 #
97 proc packbuttons {side args} {
98  eval pack $args -side $side -padx 5 -pady 3
99 }
100 proc packdlgbuttons {args} {
101  pack {*}$args -side right -padx 5 -pady "15 5"
102 }
103 # dialogbutton:
104 # Creates a button that will be shown in a dialog box, so it
105 # is given a minimum width.
106 #
107 proc dialogbutton {w args} {
108  set retval [eval ttk::button $w $args] ;# -style TButton
109  set length [string length [$w cget -text]]
110  if {$length < 7} { set length 7}
111  $w configure -width $length
112  return retval
113 }
114 
115 proc dialogbuttonsmall {w args {style "Small.TButton"} } {
116  set retval [eval ttk::button $w -style $style $args]
117  set length [string length [$w cget -text]]
118  if {$length < 7} { set length 7}
119  $w configure -width $length
120  return retval
121 }
122 
123 # autoscrollframe
124 # Creates and returns a frame containing a widget which is gridded
125 # with scrollbars that automatically hide themselves when they are
126 # not needed.
127 # The frame and widget may already exist; they are created if needed.
128 # FBF 2011.03.05:
129 # $frame and $w aspects are not changed if they already exists
130 # scrollbars are created on time 0, otherwise they are not hidden
131 #
132 # Usage:
133 # autoscrollframe [-bars none|x|y|both] frame type w args
134 #
135 proc autoscrollframe {args} {
136  set bars both
137  if {[lindex $args 0] == "-bars"} {
138  set bars [lindex $args 1]
139  if {$bars != "x" && $bars != "y" && $bars != "none" && $bars != "both"} {
140  return -code error "Invalid parameter: -bars $bars"
141  }
142  set args [lrange $args 2 end]
143  }
144  if {[llength $args] < 3} {
145  return -code error "Insufficient number of parameters"
146  }
147  set frame [lindex $args 0]
148  set type [lindex $args 1]
149  set w [lindex $args 2]
150  set args [lrange $args 3 end]
151 
152  set retval $frame
153  if {! [winfo exists $frame]} {
154  ttk::frame $frame
155  $frame configure -relief sunken -borderwidth 2
156  }
157  if {! [winfo exists $w]} {
158  $type $w
159  if {[llength $args] > 0} {
160  eval $w configure $args
161  }
162  $w configure -relief flat -borderwidth 0
163  }
164 
165  autoscrollBars $bars $frame $w
166  return $retval
167 }
168 
169 proc autoscrollText {bars frame widget style} {
170  ttk::frame $frame
171  text $widget -cursor arrow -state disabled -highlightthickness 0
172  applyThemeStyle $style $widget
173  autoscrollBars $bars $frame $widget
174 }
175 
176 proc autoscrollBars {bars frame w} {
177  global _autoscroll
178 
179  grid $w -in $frame -row 0 -column 0 -sticky news
180 
181  if {$bars == "y" || $bars == "both"} {
182  ttk::scrollbar $frame.ybar -command [list $w yview] -takefocus 0
183  $w configure -yscrollcommand [list _autoscroll $frame.ybar]
184  grid $frame.ybar -row 0 -column 1 -sticky ns
185  set _autoscroll($frame.ybar) 1
186  set _autoscroll(time:$frame.ybar) 0
187  bindMouseWheel $w "_autoscrollMouseWheel $w $frame.ybar"
188  }
189  if {$bars == "x" || $bars == "both"} {
190  ttk::scrollbar $frame.xbar -command [list $w xview] -takefocus 0 -orient horizontal
191  $w configure -xscrollcommand [list _autoscroll $frame.xbar]
192  grid $frame.xbar -row 1 -column 0 -sticky we
193  set _autoscroll($frame.xbar) 1
194  set _autoscroll(time:$frame.xbar) 0
195  }
196  grid rowconfigure $frame 0 -weight 1
197  grid columnconfigure $frame 0 -weight 1
198  grid rowconfigure $frame 1 -weight 0
199  grid columnconfigure $frame 1 -weight 0
200 }
201 
202 proc _autoscrollMouseWheel {{w} {bar} {direction}} {
203  if {$::_autoscroll($bar) == 0} return
204  $w yview scroll $direction units
205 }
206 
207 array set _autoscroll {}
208 
209 # _autoscroll
210 # This is the "set" command called for auto-scrollbars.
211 # If the bar is shown but should not be, it is hidden.
212 # If the bar is hidden but should be shown, it is redrawn.
213 # Note that once a bar is shown, it will not be removed again for
214 # at least a few milliseconds; this is to overcome problematic
215 # interactions between the x and y scrollbars where hiding one
216 # causes the other to be shown etc. This usually happens because
217 # the stupid Tcl/Tk text widget doesn't handle scrollbars well.
218 #
219 proc _autoscroll {bar args} {
220  global _autoscroll
221  if {[llength $args] == 2} {
222  set min [lindex $args 0]
223  set max [lindex $args 1]
224  if {$min > 0.0 || $max < 1.0} {
225  if {! $_autoscroll($bar)} {
226  grid configure $bar
227  set _autoscroll($bar) 1
228  set _autoscroll(time:$bar) [clock clicks -milli]
229  }
230  } else {
231  if {[clock clicks -milli] > [expr {$_autoscroll(time:$bar) + 100}]} {
232  grid remove $bar
233  set _autoscroll($bar) 0
234  }
235  }
236  }
237  eval $bar set $args
238 }
239 
240 proc _autoscrollMap {frame} {
241  # wm geometry [winfo toplevel $frame] [wm geometry [winfo toplevel $frame]]
242 }
243 
244 
245 # busyCursor, unbusyCursor:
246 # Sets all cursors to watch (indicating busy) or back to their normal
247 # setting again.
248 
249 array set scid_busycursor {}
250 set scid_busycursorState 0
251 
252 proc doBusyCursor {w flag} {
253  global scid_busycursor
254  if {! [winfo exists $w]} { return}
255  if {[winfo class $w] == "Menu"} { return}
256 
257  if {$flag} {
258  if { [ catch { set scid_busycursor($w) [$w cget -cursor]}] } {
259  return
260  }
261  catch {$w configure -cursor watch}
262  } else {
263  catch {$w configure -cursor $scid_busycursor($w)} err
264  }
265  foreach i [winfo children $w] { doBusyCursor $i $flag}
266 }
267 
268 proc busyCursor {w {flag 1}} {
269  global scid_busycursor scid_busycursorState
270  if {$scid_busycursorState == $flag} { return}
271  set scid_busycursorState $flag
272  doBusyCursor $w $flag
273 }
274 
275 proc unbusyCursor {w} {busyCursor $w 0}
276 
277 
278 # addHorizontalRule, addVerticalRule
279 # Add a horizontal/vertical rule frame to a window.
280 # The optional parameters [x/y]padding and sunken allow the spacing and
281 # appearance of the rule to be specified.
282 #
283 set horizRuleCounter 0
284 set vertRuleCounter 0
285 
286 proc addHorizontalRule {w {ypadding 5} {relief sunken} {height 2} } {
287  global horizRuleCounter
288 
289  ttk::separator $w.line$horizRuleCounter -orient horizontal
290  pack $w.line$horizRuleCounter -fill x ;# -pady $ypadding
291 
292  # set f [ ttk::frame $w.line$horizRuleCounter -height $height -borderwidth 2 -relief $relief ]
293  # pack $f -fill x -pady $ypadding
294  incr horizRuleCounter
295 }
296 
297 proc addVerticalRule {w {xpadding 5} {relief sunken}} {
298  global vertRuleCounter
299 
300  ttk::separator $w.line$vertRuleCounter -orient vertical
301  pack $w.line$vertRuleCounter -fill y -side left ;# -padx $xpadding
302 
303  # set f [ ttk::frame $w.line$vertRuleCounter -width 2 -borderwidth 2 -relief $relief ]
304  # pack $f -fill y -padx $xpadding -side left
305  incr vertRuleCounter
306 }
307 
308 # progressWindow:
309 # Creates a window with a label, progress bar, and (if specified),
310 # a cancel button and cancellation command.
311 #
312 proc progressWindow { title text {button ""} {command "progressBarCancel"} } {
313  set w .progressWin
314  if {[winfo exists $w]} { return}
315 
316  set ::progressWin_focus [focus]
317 
318  win::createDialog $w 6
319  wm resizable $w 0 0
320  wm title $w $title
321 
322  ttk::frame $w.f
323  ttk::label $w.f.t -text $text
324  autoscrollframe -bars y $w.f.cmsg text $w.f.cmsg.text -width 70 -height 14 -wrap word -font font_Regular
325  canvas $w.f.c -width 400 -height 20 -bg white -relief solid -border 1 -highlightthickness 0
326  $w.f.c create rectangle 0 0 0 0 -fill blue -outline blue -tags bar
327  $w.f.c create text 395 10 -anchor e -font font_Regular -tags time -fill black -text "0:00 / 0:00"
328  ttk::button $w.f.cancel -text $button -command "$command"
329 
330  grid $w.f.t -row 0 -columnspan 2 -pady 4 -sticky news
331  grid $w.f.cmsg -row 1 -columnspan 2 -pady 4 -sticky news
332  grid $w.f.c -row 2 -column 0 -pady 4 -stick w
333  grid $w.f.cancel -row 2 -column 1 -padx "10 0"
334  grid $w.f -sticky news
335  grid rowconfigure $w.f 1 -weight 1
336  grid columnconfigure $w.f 0 -weight 1
337  grid remove $w.f.cmsg
338  if {$button == ""} { grid remove $w.f.cancel}
339 
340  # Set up geometry for middle of screen:
341  set x [expr ([winfo screenwidth $w] - 400) / 2]
342  set y [expr ([winfo screenheight $w] - 40) / 2]
343  wm geometry $w +$x+$y
344  grab $w
345  wm withdraw $w
346 
347  set ::progressWin_time [clock seconds]
348  progressBarSet $w.f.c 401 21
349 
350  set ::progressCanvas(show) "catch {wm deiconify $w}"
351 }
352 
353 proc progressBarSet { canvasname width height } {
354  update idletasks
355  set ::progressCanvas(name) $canvasname
356  set ::progressCanvas(w) $width
357  set ::progressCanvas(h) $height
358  set ::progressCanvas(cancel) 0
359  set ::progressCanvas(init) 1
360  set ::progressCanvas(show) {}
361  after idle { unset ::progressCanvas(init) }
362 }
363 
364 proc progressBarCancel { } {
365  set ::progressCanvas(cancel) 1
366 }
367 
368 
369 proc progressCallBack {done {total 1} {elapsed 0} {estimated 0} {msg ""}} {
370  if {$done == "init"} {
371  if {[info exists ::progressCanvas(init)]} {
372  return $::progressCanvas(init)
373  }
374  # No progress bar
375  return -code break
376  }
377 
378  if {! [winfo exists $::progressCanvas(name)] || $::progressCanvas(cancel)} {
379  #Interrupted
380  return -code break
381  }
382 
383  if {$::progressCanvas(show) != ""} {
384  if {$elapsed == 0 && $estimated < 2 && $msg == ""} { return}
385  eval $::progressCanvas(show)
386  set ::progressCanvas(show) {}
387  }
388 
389  set width $::progressCanvas(w)
390  if {$total > 0} {
391  set width [expr {int(double($width) * double($done) / double($total))}]
392  }
393  $::progressCanvas(name) coords bar 0 0 $width $::progressCanvas(h)
394 
395  set t [format "%d:%02d / %d:%02d" \
396  [expr {$elapsed / 60}] [expr {$elapsed % 60}] \
397  [expr {$estimated / 60}] [expr {$estimated % 60}]]
398  $::progressCanvas(name) itemconfigure time -text $t
399 
400  if {$msg != ""} {
401  catch {
402  set widget "$::progressCanvas(name)msg"
403  grid $widget
404  append widget ".text"
405  $widget insert end "$msg\n"
406  }
407  }
408 
409  update
410 }
411 
412 proc changeProgressWindow {newtext} {
413  set w .progressWin
414  if {[winfo exists $w]} {
415  $w.f.t configure -text $newtext
416  update idletasks
417  }
418 }
419 
420 proc updateProgressWindow {done total} {
421  set w .progressWin
422  if {! [winfo exists $w]} { return}
423  set elapsed [expr {[clock seconds] - $::progressWin_time}]
424  set estimated $elapsed
425  if {$done != 0} {
426  set estimated [expr {int(double($elapsed) * double($total) / double($done))}]
427  }
428  ::progressCallBack "$done" "$total" "$elapsed" "$estimated"
429 }
430 
431 proc closeProgressWindow {{force false}} {
432  set w .progressWin
433  if {! [winfo exists $w]} { return}
434 
435  if {!$force && [$w.f.cmsg.text index end] != "2.0" } {
436  $w.f.cancel configure -text "$::tr(Close)"
437  $w.f.cancel configure -command "closeProgressWindow true"
438  grid forget $w.f.c
439  grid $w.f.cancel
440  $w.f.cmsg.text configure -state disabled
441  return
442  }
443  grab release $w
444  destroy $w
445  update idletasks
446  catch {focus $::progressWin_focus}
447 }
448 
449 proc CreateSelectDBWidget {{w} {varname} {ref_base ""} {readOnly 1}} {
450  set listbases {}
451  if {$ref_base == ""} { set ref_base [sc_base current]}
452  set tr_database [tr Database]
453  set tr_prefix_len [expr {[string length $tr_database] + 1}]
454  set selected 0
455  foreach i [sc_base list] {
456  if {$readOnly || ![sc_base isReadOnly $i]} {
457  set fname [file tail [sc_base filename $i]]
458  if {$i == $ref_base} { set selected [llength $listbases]}
459  lappend listbases "$tr_database $i: $fname"
460  }
461  }
462 
463  ttk::combobox $w.lb -values $listbases -state readonly
464  grid $w.lb -sticky news
465  grid columnconfigure $w 0 -weight 1
466 
467  bind $w.lb <<ComboboxSelected>> "
468  set $varname \[ string index \[$w.lb get\] $tr_prefix_len \]
469  "
470  $w.lb current $selected
471  event generate $w.lb <<ComboboxSelected>>
472 }
473 proc storeEmtComment { h m s } {
474  set time "[format "%d" $h]:[format "%02d" $m]:[format "%02d" $s]"
475 
476  #Replace %emt if present, otherwise prepend it
477  if {[regsub {\[%emt\s*.*?\]} [sc_pos getComment] "\[%emt $time\]" comment]} {
478  sc_pos setComment "$comment"
479  } else {
480  sc_pos setComment "\[%emt $time\]$comment"
481  }
482  }
483 proc storeEvalComment { value } {
484  #Replace %eval if present, otherwise prepend it
485  if {[regsub {\[%eval\s*.*?\]} [sc_pos getComment] "\[%eval $value\]" comment]} {
486  sc_pos setComment "$comment"
487  } else {
488  sc_pos setComment "\[%eval $value\]$comment"
489  }
490  }
491 
492 ################################################################################
493 # clock widget
494 ################################################################################
495 namespace eval gameclock {
496  array set data {}
497  ################################################################################
498  proc new { parent n { size 100 } {showfall 0} } {
499  global ::gameclock::data
500  set data(showfallen$n) $showfall
501  set data(id$n) ""
502  if {$parent != ""} {
503  set data(id$n) $parent.clock$n
504  canvas $data(id$n) -height $size -width $size
505  pack $data(id$n) -side top -anchor center
506  for {set i 1} {$i<13} {incr i} {
507  set a [expr {$i/6.*acos(-1)}]
508  set x [expr { ($size/2 + (($size-15)/2)*sin($a) ) }]
509  set y [expr { ($size/2 - (($size-15)/2)*cos($a) ) }]
510  $data(id$n) create text $x $y -text $i -tag clock$n
511  }
512  bind $data(id$n) <Button-1> "::gameclock::toggleClock $n"
513  }
514  set data(fg$n) "black"
515  set data(running$n) 0
516  set data(digital$n) 1
519  }
520  ################################################################################
521  proc draw { n } {
522  global ::gameclock::data
523 
524  #TODO: Hack. For the moment we assume that:
525  # -clock 1 is the white clock on the main board
526  # -clock 2 is the black clock on the main board
527  set sec $data(counter$n)
528  set h [format "%d" [expr abs($sec) / 60 / 60]]
529  set m [format "%02d" [expr (abs($sec) / 60) % 60]]
530  set s [format "%02d" [expr abs($sec) % 60]]
531  if {$n == 1} { set ::gamePlayers(clockW) "$h:$m:$s"}
532  if {$n == 2} { set ::gamePlayers(clockB) "$h:$m:$s"}
533 
534  if {! [winfo exists $data(id$n)]} { return}
535  $data(id$n) delete aig$n
536 
537  set w [$data(id$n) cget -width]
538  set h [$data(id$n) cget -height]
539  set cx [ expr $w / 2]
540  set cy [ expr $h / 2]
541  if {$w < $h} {
542  set size [ expr $w - 15]
543  } else {
544  set size [ expr $h - 15]
545  }
546 
547  if { $sec > 0 && $data(showfallen$n) } {
548  set color "red"
549  } else {
550  set color $::gameclock::data(fg$n)
551  }
552 
553  if {$color == "white"} {set fg "black"} else {set fg "white"}
554 
555  foreach divisor {30 1800 21600} length "[expr $size/2 * 0.8] [expr $size/2 * 0.7] [expr $size/2 * 0.4]" \
556  width {1 2 3} {
557  set angle [expr {$sec * acos(-1) / $divisor}]
558  set x [expr {$cx + $length * sin($angle)}]
559  set y [expr {$cy - $length * cos($angle)}]
560  $data(id$n) create line $cx $cy $x $y -width $width -tags aig$n -fill $color
561  }
562  # draw a digital clock
563  if {$data(digital$n)} {
564  set m [format "%02d" [expr abs($sec) / 60]]
565  set s [format "%02d" [expr abs($sec) % 60]]
566  $data(id$n) create text $cx [expr $cy + $size/4] -text "$m:$s" -anchor center -fill $color -tag aig$n
567  }
568  }
569  ################################################################################
570  proc every {ms body n} {
571  incr ::gameclock::data(counter$n)
572  eval $body
573  if {$::gameclock::data(id$n) == "" ||
574  [winfo exists $::gameclock::data(id$n)]} {
575  after $ms [info level 0]
576  }
577  }
578  ################################################################################
579  proc getSec { n } {
580  return [expr 0 - $::gameclock::data(counter$n)]
581  }
582  ################################################################################
583  proc setSec { n value } {
584  set ::gameclock::data(counter$n) $value
586  }
587  ################################################################################
588  proc add { n value } {
589  set ::gameclock::data(counter$n) [ expr $::gameclock::data(counter$n) - $value]
591  }
592 
593  ################################################################################
594  proc reset { n } {
596  set ::gameclock::data(counter$n) 0
597  }
598  ################################################################################
599  proc start { n } {
600  if {$::gameclock::data(running$n)} { return}
601  set ::gameclock::data(running$n) 1
602  ::gameclock::every 1000 "draw $n" $n
603  }
604  ################################################################################
605  proc stop { n } {
606  if {! $::gameclock::data(running$n)} { return 0}
607  set ::gameclock::data(running$n) 0
608  after cancel "::gameclock::every 1000 \{draw $n\} $n"
609  return 1
610  }
611  ################################################################################
612  proc storeTimeComment { color } {
613  set sec [::gameclock::getSec $color]
614  set h [format "%d" [expr abs($sec) / 60 / 60]]
615  set m [format "%02d" [expr (abs($sec) / 60) % 60]]
616  set s [format "%02d" [expr abs($sec) % 60]]
617  set time "$h:$m:$s"
618 
619  #Replace %clk if present, otherwise prepend it
620  if {[regsub {\[%clk\s*.*?\]} [sc_pos getComment] "\[%clk $time\]" comment]} {
621  sc_pos setComment "$comment"
622  } else {
623  sc_pos setComment "\[%clk $time\]$comment"
624  }
625  }
626  ################################################################################
627  proc toggleClock { n } {
628  if { $::gameclock::data(running$n) } {
629  stop $n
630  } else {
631  start $n
632  }
633  }
634  ################################################################################
635  proc setColor { n color } {
636  if {$color == "white"} {
637  set fg "black"
638  set bg "white"
639  } else {
640  set fg "white"
641  set bg "black"
642  }
643  set ::gameclock::data(fg$n) $fg
644  $::gameclock::data(id$n) configure -background $bg
645  $::gameclock::data(id$n) itemconfigure clock$n -fill $fg
646  $::gameclock::data(id$n) itemconfigure aig$n -fill $fg
647  }
648  ################################################################################
649  proc isRunning { } {
650  global ::gameclock::data
651  catch {
652  if {$data(running1) || $data(running2)} { return 1}
653  }
654  return 0
655  }
656 }
657 ################################################################################
658 # html generation
659 ################################################################################
660 namespace eval html {
661  set data {}
662  set idx 0
663 
664  ################################################################################
665  proc exportCurrentFilter {} {
666  # Check that we have some games to export:
667  if {[sc_filter count] == 0} {
668  tk_messageBox -title "Scid: Filter empty" -type ok -icon info \
669  -message "The filter contains no games."
670  return
671  }
672  set ftype {
673  { "HTML files" {".html" ".htm"} }
674  { "All files" {"*"} }
675  }
676  set idir $::initialDir(html)
677  set fName [tk_getSaveFile -initialdir $idir -filetypes $ftype -defaultextension ".html" -title "Create an HTML file"]
678  if {$fName == ""} { return}
679  if {[file extension $fName] != ".html" } {
680  append fName ".html"
681  }
682  set prefix [file rootname [file tail $fName]]
683  set dirtarget [file dirname $fName]
684  set sourcedir [file join $::scidExeDir html]
685  catch {file copy -force [file join $sourcedir bitmaps] $dirtarget}
686  catch {file copy -force [file join $sourcedir scid.js] $dirtarget}
687  catch {file copy -force [file join $sourcedir scid.css] $dirtarget}
688  # writeIndex "[file join $dirtarget $prefix].html" $prefix
689  progressWindow "Scid" "Exporting games..."
690  set savedGameNum [sc_game number]
691  set gn [sc_filter first]
692  set players {}
693  set ::html::cancelHTML 0
694  set total [sc_filter count]
695 
696  # build the list of matches
697  set idx 1
698  while {$gn != 0 && ! $::html::cancelHTML} {
699  updateProgressWindow $idx $total
700  sc_game load $gn
701  set pl "[sc_game tags get White] - [sc_game tags get Black]"
702  lappend players $pl
703  set gn [sc_filter next]
704  incr idx
705  }
706 
707  set idx 1
708  set gn [sc_filter first]
709  while {$gn != 0 && ! $::html::cancelHTML} {
710  updateProgressWindow $idx $total
711  sc_game load $gn
712  fillData
713  set pl "[sc_game tags get White] - [sc_game tags get Black]"
714  toHtml $::html::data $idx $dirtarget $prefix $players $pl [sc_game tags get "Event"] [sc_game tags get "ECO"] [sc_game info result] [sc_game tags get "Date"]
715  set gn [sc_filter next]
716  incr idx
717  }
718 
720  exportPGN "[file join $dirtarget $prefix].pgn" "filter"
721  sc_game load $savedGameNum
722  }
723  ################################################################################
724  proc sc_progressBar {} {
725  set ::html::cancelHTML 1
726  }
727  ################################################################################
728  proc exportCurrentGame {} {
729 
730  set ftype {
731  { "HTML files" {".html" ".htm"} }
732  { "All files" {"*"} }
733  }
734  set idir $::initialDir(html)
735  set fName [tk_getSaveFile -initialdir $idir -filetypes $ftype -defaultextension ".html" -title "Create an HTML file"]
736  if {[file extension $fName] != ".html" && [file extension $fName] != ".htm" } {
737  append fName ".html"
738  }
739  if {$fName == ""} { return}
740  set prefix [file rootname [file tail $fName]]
741  set dirtarget [file dirname $fName]
742  set sourcedir [file join $::scidExeDir html]
743  catch { file copy -force [file join $sourcedir bitmaps] $dirtarget}
744  catch { file copy -force [file join $sourcedir scid.js] $dirtarget}
745  catch { file copy -force [file join $sourcedir scid.css] $dirtarget}
746 
747  fillData
748  set players [list "[sc_game tags get White] - [sc_game tags get Black]"]
749  toHtml $::html::data -1 $dirtarget $prefix $players [lindex $players 0] \
750  [sc_game tags get "Event"] [sc_game tags get "ECO"] \
751  [sc_game info result] [sc_game tags get "Date"]
752  exportPGN "[file join $dirtarget $prefix].pgn" "current"
753  }
754  ################################################################################
755  # Dictionary mapping from special characters to their entities. (from tcllib)
756  variable entities {
757  \xa0 &nbsp; \xa1 &iexcl; \xa2 &cent; \xa3 &pound; \xa4 &curren;
758  \xa5 &yen; \xa6 &brvbar; \xa7 &sect; \xa8 &uml; \xa9 &copy;
759  \xaa &ordf; \xab &laquo; \xac &not; \xad &shy; \xae &reg;
760  \xaf &macr; \xb0 &deg; \xb1 &plusmn; \xb2 &sup2; \xb3 &sup3;
761  \xb4 &acute; \xb5 &micro; \xb6 &para; \xb7 &middot; \xb8 &cedil;
762  \xb9 &sup1; \xba &ordm; \xbb &raquo; \xbc &frac14; \xbd &frac12;
763  \xbe &frac34; \xbf &iquest; \xc0 &Agrave; \xc1 &Aacute; \xc2 &Acirc;
764  \xc3 &Atilde; \xc4 &Auml; \xc5 &Aring; \xc6 &AElig; \xc7 &Ccedil;
765  \xc8 &Egrave; \xc9 &Eacute; \xca &Ecirc; \xcb &Euml; \xcc &Igrave;
766  \xcd &Iacute; \xce &Icirc; \xcf &Iuml; \xd0 &ETH; \xd1 &Ntilde;
767  \xd2 &Ograve; \xd3 &Oacute; \xd4 &Ocirc; \xd5 &Otilde; \xd6 &Ouml;
768  \xd7 &times; \xd8 &Oslash; \xd9 &Ugrave; \xda &Uacute; \xdb &Ucirc;
769  \xdc &Uuml; \xdd &Yacute; \xde &THORN; \xdf &szlig; \xe0 &agrave;
770  \xe1 &aacute; \xe2 &acirc; \xe3 &atilde; \xe4 &auml; \xe5 &aring;
771  \xe6 &aelig; \xe7 &ccedil; \xe8 &egrave; \xe9 &eacute; \xea &ecirc;
772  \xeb &euml; \xec &igrave; \xed &iacute; \xee &icirc; \xef &iuml;
773  \xf0 &eth; \xf1 &ntilde; \xf2 &ograve; \xf3 &oacute; \xf4 &ocirc;
774  \xf5 &otilde; \xf6 &ouml; \xf7 &divide; \xf8 &oslash; \xf9 &ugrave;
775  \xfa &uacute; \xfb &ucirc; \xfc &uuml; \xfd &yacute; \xfe &thorn;
776  \xff &yuml; \u192 &fnof; \u391 &Alpha; \u392 &Beta; \u393 &Gamma;
777  \u394 &Delta; \u395 &Epsilon; \u396 &Zeta; \u397 &Eta; \u398 &Theta;
778  \u399 &Iota; \u39A &Kappa; \u39B &Lambda; \u39C &Mu; \u39D &Nu;
779  \u39E &Xi; \u39F &Omicron; \u3A0 &Pi; \u3A1 &Rho; \u3A3 &Sigma;
780  \u3A4 &Tau; \u3A5 &Upsilon; \u3A6 &Phi; \u3A7 &Chi; \u3A8 &Psi;
781  \u3A9 &Omega; \u3B1 &alpha; \u3B2 &beta; \u3B3 &gamma; \u3B4 &delta;
782  \u3B5 &epsilon; \u3B6 &zeta; \u3B7 &eta; \u3B8 &theta; \u3B9 &iota;
783  \u3BA &kappa; \u3BB &lambda; \u3BC &mu; \u3BD &nu; \u3BE &xi;
784  \u3BF &omicron; \u3C0 &pi; \u3C1 &rho; \u3C2 &sigmaf; \u3C3 &sigma;
785  \u3C4 &tau; \u3C5 &upsilon; \u3C6 &phi; \u3C7 &chi; \u3C8 &psi;
786  \u3C9 &omega; \u3D1 &thetasym; \u3D2 &upsih; \u3D6 &piv;
787  \u2022 &bull; \u2026 &hellip; \u2032 &prime; \u2033 &Prime;
788  \u203E &oline; \u2044 &frasl; \u2118 &weierp; \u2111 &image;
789  \u211C &real; \u2122 &trade; \u2135 &alefsym; \u2190 &larr;
790  \u2191 &uarr; \u2192 &rarr; \u2193 &darr; \u2194 &harr; \u21B5 &crarr;
791  \u21D0 &lArr; \u21D1 &uArr; \u21D2 &rArr; \u21D3 &dArr; \u21D4 &hArr;
792  \u2200 &forall; \u2202 &part; \u2203 &exist; \u2205 &empty;
793  \u2207 &nabla; \u2208 &isin; \u2209 &notin; \u220B &ni; \u220F &prod;
794  \u2211 &sum; \u2212 &minus; \u2217 &lowast; \u221A &radic;
795  \u221D &prop; \u221E &infin; \u2220 &ang; \u2227 &and; \u2228 &or;
796  \u2229 &cap; \u222A &cup; \u222B &int; \u2234 &there4; \u223C &sim;
797  \u2245 &cong; \u2248 &asymp; \u2260 &ne; \u2261 &equiv; \u2264 &le;
798  \u2265 &ge; \u2282 &sub; \u2283 &sup; \u2284 &nsub; \u2286 &sube;
799  \u2287 &supe; \u2295 &oplus; \u2297 &otimes; \u22A5 &perp;
800  \u22C5 &sdot; \u2308 &lceil; \u2309 &rceil; \u230A &lfloor;
801  \u230B &rfloor; \u2329 &lang; \u232A &rang; \u25CA &loz;
802  \u2660 &spades; \u2663 &clubs; \u2665 &hearts; \u2666 &diams;
803  \x22 &quot; \x26 &amp; \x3C &lt; \x3E &gt; \u152 &OElig;
804  \u153 &oelig; \u160 &Scaron; \u161 &scaron; \u178 &Yuml;
805  \u2C6 &circ; \u2DC &tilde; \u2002 &ensp; \u2003 &emsp; \u2009 &thinsp;
806  \u200C &zwnj; \u200D &zwj; \u200E &lrm; \u200F &rlm; \u2013 &ndash;
807  \u2014 &mdash; \u2018 &lsquo; \u2019 &rsquo; \u201A &sbquo;
808  \u201C &ldquo; \u201D &rdquo; \u201E &bdquo; \u2020 &dagger;
809  \u2021 &Dagger; \u2030 &permil; \u2039 &lsaquo; \u203A &rsaquo;
810  \u20AC &euro;
811  }
812  proc html_entities {s} {
813  variable entities
814  return [string map $entities $s]
815  }
816  ################################################################################
817  proc toHtml { dt game dirtarget prefix {players ""} {this_players ""} {event ""} {eco "ECO"} {result "*"} {date ""} } {
818 
819  if { $game != -1 } {
820  set f [open "[file join $dirtarget $prefix]_${game}.html" w]
821  } else {
822  set f [open "[file join $dirtarget $prefix].html" w]
823  }
824  # header
825  puts $f "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">"
826  puts $f "<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en\" lang=\"en\">"
827  puts $f "<head>"
828  puts $f "<meta http-equiv=\"content-type\" content=\"text/html; charset=utf-8\" />"
829  puts $f "<link rel=\"stylesheet\" type=\"text/css\" href=\"scid.css\" />"
830  puts $f "<script src=\"scid.js\" type=\"text/javascript\"></script>"
831  puts $f "<script type=\"text/javascript\">"
832  puts $f "// <!\[CDATA\["
833  puts $f "movesArray = new Array("
834  for {set i 0} {$i<[llength $dt]} {incr i} {
835  array set elt [lindex $dt $i]
836  puts -nonewline $f "\"$elt(fen) $elt(prev) $elt(next)\""
837  if {$i < [expr [llength $dt] -1]} { puts $f ","}
838  }
839  puts $f ");"
840  puts $f "var current = 0;"
841  puts $f "var prefix = \"$prefix\";"
842  puts $f "// \]\]>"
843  puts $f "</script>"
844  puts $f "<title>Scid</title>"
845  puts $f "<meta content=\"Scid\" name=\"author\" />"
846  puts $f "</head>"
847  puts $f "<body onload=\"doinit()\" onkeydown=\"handlekey(event)\">"
848  puts $f "<div id=\"framecontent\">"
849  puts $f "<div class=\"innertube\">"
850  # diagram
851  puts $f "<div id=\"diagram\"><!-- diagram goes here --></div>"
852  # navigation
853  puts $f "<div id=\"nav\" style=\"text-align: center\"><!-- navigation goes here -->"
854  puts $f "<form action=\"#\">"
855  puts $f "<p>"
856  puts $f "<input type='button' value=' &darr;&uarr; ' onclick='rotate()' /> <input type='button' value=' |&lt; ' onclick='jump(0)' /> <input type='button' value=' &lt; ' onclick='moveForward(0)' /> <input type='button' value=' &gt; ' onclick='moveForward(1)' /> <input type='button' value=' &gt;| ' onclick='jump(1)' /> "
857  puts $f "</p><p>"
858  # other games navigation
859  puts $f "<select name=\"gameselect\" id=\"gameselect\" size=\"1\" onchange=\"gotogame()\">"
860  set i 1
861  foreach l $players {
862  if { $game == $i } {
863  puts $f "<option selected=\"selected\">$i. [html_entities $l]</option>"
864  } else {
865  puts $f "<option>$i. [html_entities $l]</option>"
866  }
867  incr i
868  }
869  puts $f "</select>"
870  puts $f "</p><p>"
871  puts $f "<input type=\"button\" value=\"&lt;--\" onclick=\"gotoprevgame()\" /> &nbsp; <input type=\"button\" value=\"--&gt;\" onclick=\"gotonextgame()\" />"
872  puts $f "</p><p>"
873  puts $f "<a href=\"${prefix}.pgn\">${prefix}.pgn</a>"
874  puts $f "</p>"
875  puts $f "</form>"
876  puts $f "</div>"
877  puts $f "</div>"
878  puts $f "</div>"
879  puts $f "<div id=\"maincontent\">"
880  puts $f "<div class=\"innertube\">"
881  puts $f "<div id=\"moves\"><!-- moves go here -->"
882  # game header
883  puts $f "<span class=\"hPlayers\"> [html_entities $this_players]</span>"
884  puts $f "<span class=\"hEvent\"><br /> [html_entities $event]</span>"
885  puts $f "<span class=\"hEvent\"><br />\[$date\]</span>"
886  puts $f "<span class=\"hAnnot\"><br />\[$eco\]</span>"
887  puts $f "<p>"
888  # link moves
889  set prevdepth 0
890  set prevvarnumber 0
891  for {set i 0} {$i<[llength $dt]} {incr i} {
892  array set elt [lindex $dt $i]
893  if {$elt(depth) == 0} {
894  set class "V0"
895  } elseif {$elt(depth) == 1} {
896  set class "V1"
897  } else {
898  set class "V2"
899  }
900  if { $prevdepth == $elt(depth) && $prevvarnumber != $elt(var) } {
901  puts $f "<span class=\"VC\">\]</span></div>"
902  puts $f "<div class=\"var\"><span class=\"VC\">\[</span>"
903  } else {
904  while { $prevdepth > $elt(depth) } {
905  puts $f "<span class=\"VC\">\]</span></div>"
906  set prevdepth [expr $prevdepth - 1]
907  }
908  while { $prevdepth < $elt(depth) } {
909  puts $f "<div class=\"var\"><span class=\"VC\">\[</span>"
910  set prevdepth [expr $prevdepth + 1]
911  }
912  }
913  set prevvarnumber $elt(var)
914  # id = "mv1" not "id=1" now
915  set nag [html_entities $elt(nag)]
916  set comment [html_entities $elt(comment)]
917  puts $f "<a href=\"javascript:gotoMove($elt(idx))\" id=\"mv$elt(idx)\" class=\"$class\">$elt(move)$nag</a>"
918  if {$elt(diag)} {
919  insertMiniDiag $elt(fen) $f
920  }
921  if {$comment != ""} {
922  puts $f "<span class=\"VC\">$comment</span>"
923  }
924  }
925  while { $prevdepth > 0 } {
926  puts $f "<span class=\"VC\">\]</span></div>"
927  set prevdepth [expr $prevdepth - 1]
928  }
929 
930  puts $f "<br /><span class=\"VH\">$result</span>"
931  puts $f "<p>"
932  puts $f "<a href=\"http://scid.sourceforge.net/\" style=\"font-size: 0.8em\">Created with Scid</a>"
933  puts $f "</div>"
934  puts $f "</div>"
935  puts $f "</div>"
936  puts $f "</body>"
937  puts $f "</html>"
938  close $f
939  }
940  ################################################################################
941  proc colorSq {sq} {
942  if { [expr $sq % 2] == 1 && [expr int($sq / 8) %2] == 0 || [expr $sq % 2] == 0 && [expr int($sq / 8) %2] == 1 } {
943  return "bs"
944  } else {
945  return "ws"
946  }
947  }
948  ################################################################################
949  proc piece2gif {piece} {
950  if {$piece == "K"} { return "wk"}
951  if {$piece == "k"} { return "bk"}
952  if {$piece == "Q"} { return "wq"}
953  if {$piece == "q"} { return "bq"}
954  if {$piece == "R"} { return "wr"}
955  if {$piece == "r"} { return "br"}
956  if {$piece == "B"} { return "wb"}
957  if {$piece == "b"} { return "bb"}
958  if {$piece == "N"} { return "wn"}
959  if {$piece == "n"} { return "bn"}
960  if {$piece == "P"} { return "wp"}
961  if {$piece == "p"} { return "bp"}
962  if {$piece == " "} { return "sq"}
963  }
964  ################################################################################
965  proc insertMiniDiag {fen f} {
966 
967  set square 0
968  set space " "
969  puts $f "<table Border=0 CellSpacing=0 CellPadding=0><tr>"
970 
971  for {set i 0} {$i < [string length $fen]} {incr i} {
972  set l [string range $fen $i $i]
973  set res [scan $l "%d" c]
974  if {$res == 1} {
975  if { $c >= 1 && $c <= 8 } {
976  for { set j 0} {$j < $c} {incr j} {
977  puts $f "<td class=\"[colorSq $square]\"><img border=0 align=\"left\" src=\"bitmaps/mini/[piece2gif $space].gif\"></td>"
978  incr square
979  }
980  }
981  } elseif {$l == "/"} {
982  puts $f "</tr><tr>"
983  } else {
984  puts $f "<td class=\"[colorSq $square]\"><img border=0 align=\"left\" src=\"bitmaps/mini/[piece2gif $l].gif\"></td>"
985  incr square
986  }
987  }
988 
989  puts $f "</tr></table>"
990  }
991 
992  ################################################################################
993  # fill data with { idx FEN prev next move nag comment depth }
994  proc fillData {} {
995  set ::html::data {}
996  set ::html::idx -1
997  sc_move start
998  parseGame
999  }
1000 
1001  ################################################################################
1002  proc parseGame { {prev -2} } {
1003  global ::html::data ::html::idx
1004 
1005  set already_written 0
1006 
1007  set dots 0
1008 
1009  while {1} {
1010  if { ! $already_written } {
1011  recordElt $dots $prev
1012  set dots 0
1013  set prev -2
1014  } else {
1015  set dots 1
1016  }
1017  set already_written 0
1018 
1019  # handle variants
1020  if {[sc_var count]>0} {
1021  # First write the move in the current line for which variations exist
1022  #
1023  if { ![sc_pos isAt vend]} {
1024  sc_move forward
1025  recordElt $dots $prev
1026  sc_move back
1027  set lastIdx $idx
1028  set already_written 1
1029  }
1030  for {set v 0} {$v<[sc_var count]} {incr v} {
1031  sc_var enter $v
1032  # in order to get the comment before first move
1033  sc_move back
1034  parseGame -1
1035  sc_var exit
1036  }
1037  #update the "next" token
1038  array set elt [lindex $data $lastIdx]
1039  set elt(next) [expr $idx + 1]
1040  lset data $lastIdx [array get elt]
1041  #update the "previous" token
1042  set prev $lastIdx
1043  }
1044 
1045  if {[sc_pos isAt vend]} { break}
1046  sc_move forward
1047  }
1048  }
1049  ################################################################################
1050  proc recordElt { dots {prev -2} } {
1051  global ::html::data ::html::idx
1052 
1053  array set elt {}
1054 
1055  incr idx
1056  set elt(idx) $idx
1057  set elt(fen) [lindex [split [sc_pos fen]] 0]
1058  if {$prev != -2} {
1059  set elt(prev) $prev
1060  } else {
1061  set elt(prev) [expr $idx-1]
1062  }
1063 
1064  set nag [sc_pos getNags]
1065  if {$nag == "0"} { set nag ""}
1066  if {[string match "*D *" $nag] || [string match "*# *" $nag]} {
1067  set elt(diag) 1
1068  } else {
1069  set elt(diag) 0
1070  }
1071  set nag [regsub -all "D " $nag ""]
1072  set nag [regsub -all "# " $nag ""]
1073  set elt(nag) $nag
1074  set comment [sc_pos getComment]
1075  set comment [regsub -all "\[\x5B\]%draw (.)+\[\x5D\]" $comment ""]
1076  set elt(comment) $comment
1077  set elt(depth) [sc_var level]
1078  set elt(var) [sc_var number]
1079  if {![sc_pos isAt vend]} {
1080  set elt(next) [expr $idx +1]
1081  } else {
1082  set elt(next) -1
1083  }
1084 
1085  set m [sc_game info previousMove]
1086  set mn [sc_pos moveNumber]
1087 
1088  set elt(move) ""
1089  if {[sc_pos side] == "black" && $m != ""} {
1090  set elt(move) "$mn.$m"
1091  } else {
1092 
1093  if {! [sc_pos isAt vstart] } {
1094  sc_move back
1095  set pnag [sc_pos getNags]
1096  if {$pnag == "0"} { set pnag ""}
1097  if {[string match "*D *" $pnag] || [string match "*# *" $pnag]} {
1098  set pdiag 1
1099  } else {
1100  set pdiag 0
1101  }
1102  if { [sc_pos isAt vstart] || [sc_pos getComment] != "" || $pdiag > 0 } {
1103  set dots 1
1104  }
1105  sc_move forward
1106  }
1107 
1108  if {$dots && $m != ""} {
1109  set elt(move) "[expr $mn -1]. ... $m"
1110  } else {
1111  set elt(move) $m
1112  }
1113 
1114  }
1115 
1116  lappend ::html::data [array get elt]
1117 
1118  }
1119 
1120  ################################################################################
1121  # proc writeIndex {fn prefix} {
1122  # set f [open $fn w]
1123  # puts $f "<!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">"
1124  # puts $f "<html>"
1125  # puts $f "<head>"
1126  # puts $f "<meta content=\"text/html; charset=ISO-8859-1\" http-equiv=\"content-type\">"
1127  # puts $f "<title>Scid</title>"
1128  # puts $f "<meta content=\"Scid\" name=\"author\">"
1129  # puts $f "</head>"
1130  # puts $f "<frameset BORDER=\"0\" FRAMEBORDER=\"0\" FRAMESPACING=\"0\" COLS=\"380,*\">"
1131  # puts $f "<frameset BORDER=\"0\" FRAMEBORDER=\"0\" FRAMESPACING=\"0\" ROWS=\"380,*\">"
1132  # puts $f "<frame NAME=\"diagram\" SCROLLING=\"Auto\">"
1133  # puts $f "<frame NAME=\"nav\" SRC=\"${prefix}_nav.html\" SCROLLING=\"Auto\">"
1134  # puts $f "</frameset>"
1135  # puts $f "<frame NAME=\"moves\" SRC=\"${prefix}_1.html\" SCROLLING=\"Auto\">"
1136  # puts $f "</frameset>"
1137  # puts $f "</html>"
1138  # close $f
1139  # }
1140  ################################################################################
1141  proc exportPGN { fName selection } {
1142  if {$selection == "filter"} {
1143  progressWindow "Scid" "Exporting games..." $::tr(Cancel)
1144  }
1145  sc_base export $selection "PGN" $fName -append 0 -starttext "" -endtext "" -comments 1 -variations 1 \
1146  -space 1 -symbols 1 -indentC 0 -indentV 0 -column 0 -noMarkCodes 1 -convertNullMoves 1
1147  if {$selection == "filter"} {
1149  }
1150  }
1151 
1152 }
1153 ################################################################################
1154 #
1155 ################################################################################
1156 
1157 # end of misc.tcl