Scid  4.7.0
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros
main.tcl
Go to the documentation of this file.
1 # Copyright (C) 1999-2004 Shane Hudson
2 # Copyright (C) 2006-2009 Pascal Georges
3 # Copyright (C) 2008-2011 Alexander Wagner
4 # Copyright (C) 2013-2016 Fulvio Benini
5 #
6 # This file is part of Scid (Shane's Chess Information Database).
7 #
8 # Scid is free software: you can redistribute it and/or modify
9 # it under the terms of the GNU General Public License as published by
10 # the Free Software Foundation.
11 #
12 # Scid is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU General Public License for more details.
16 #
17 # You should have received a copy of the GNU General Public License
18 # along with Scid. If not, see <http://www.gnu.org/licenses/>.
19 
20 ###
21 ### main.tcl: Routines for creating and updating the main window.
22 ###
23 
24 ############################################################
25 # Keyboard move entry:
26 # Handles letters, digits and BackSpace/Delete keys.
27 # Note that king- and queen-side castling moves are denoted
28 # "OK" and "OQ" respectively.
29 # The letters n, r, q, k, o and l are promoted to uppercase
30 # automatically. A "b" can match to a b-pawn or Bishop move,
31 # so in some rare cases, a capital B may be needed for the
32 # Bishop move to distinguish it from the pawn move.
33 
34 set moveEntry(Text) ""
35 set moveEntry(List) {}
36 
37 proc moveEntry_Clear {} {
38  global moveEntry
39  set moveEntry(Text) ""
40  set moveEntry(List) {}
41 }
42 
43 proc moveEntry_Complete {} {
44  global moveEntry
45  set len [llength $moveEntry(List)]
46  if {$len > 0} {
47  set move [lindex $moveEntry(List) 0]
48  if {$move == "OK"} { set move "O-O"}
49  if {$move == "OQ"} { set move "O-O-O"}
51  addSanMove $move
52  }
53 }
54 
55 proc moveEntry_Backspace {} {
56  global moveEntry
57  set moveEntry(Text) [string range $moveEntry(Text) 0 \
58  [expr {[string length $moveEntry(Text)] - 2}]]
59  set moveEntry(List) [sc_pos matchMoves $moveEntry(Text) $moveEntry(Coord)]
61 }
62 
63 proc moveEntry_Char {ch} {
64  global moveEntry
65  set oldMoveText $moveEntry(Text)
66  set oldMoveList $moveEntry(List)
67  append moveEntry(Text) $ch
68  set moveEntry(List) [sc_pos matchMoves $moveEntry(Text) $moveEntry(Coord)]
69  set len [llength $moveEntry(List)]
70  if {$len == 0} {
71  # No matching moves, so do not accept this character as input:
72  set moveEntry(Text) $oldMoveText
73  set moveEntry(List) $oldMoveList
74  } elseif {$len == 1} {
75  # Exactly one matching move, so make it if AutoExpand is on,
76  # or if it equals the move entered. Note the comparison is
77  # case insensitive to allow for 'b' to match both pawn and
78  # Bishop moves.
79  set move [string tolower [lindex $moveEntry(List) 0]]
80 
81  if {$moveEntry(AutoExpand) > 0 ||
82  ![string compare [string tolower $moveEntry(Text)] $move]} {
83  return [moveEntry_Complete]
84  }
85  } elseif {$len == 2} {
86  # Check for the special case where the user has entered a b-pawn
87  # capture that clashes with a Bishop move (e.g. bxc4 and Bxc4):
88  set first [string tolower [lindex $moveEntry(List) 0]]
89  set second [string tolower [lindex $moveEntry(List) 1]]
90  if {[string equal $first $second]} {
91  set moveEntry(List) [list $moveEntry(Text)]
92  return [moveEntry_Complete]
93  }
94  }
96 }
97 
98 # updateMainGame:
99 # Updates the main board with games's info
100 #
101 proc updateMainGame {} {
102  global gamePlayers
103  set gamePlayers(nameW) [sc_game info white]
104  set gamePlayers(nameB) [sc_game info black]
105  set eloW [sc_game info welo]
106  set gamePlayers(eloW) [expr {$eloW == 0 ? "" : "($eloW)"}]
107  set eloB [sc_game info belo]
108  set gamePlayers(eloB) [expr {$eloB == 0 ? "" : "($eloB)"}]
109  set gamePlayers(clockW) ""
110  set gamePlayers(clockB) ""
111 }
112 
113 # updateTitle:
114 # Updates the main Scid window title.
115 #
116 proc updateTitle {} {
117  set title "Scid - "
118  set fname [sc_base filename $::curr_db]
119  set fname [file tail $fname]
120  append title "$fname ($::tr(game) "
121  append title "[::utils::thousands [sc_game number]] / "
122  append title "[::utils::thousands [sc_base numGames $::curr_db]])"
123  ::setTitle . $title
124  set white [sc_game info white]
125  set black [sc_game info black]
126  if {[string length $white] > 2 && [string length $black] > 2} {
127  if {$fname == {[clipbase]} } { set fname clipbase}
128  set altered ""
129  if {[sc_game altered]} { set altered "*"}
130  ::setTitle .main "($fname$altered): $white -- $black"
131  } else {
132  ::setTitle .main $title
133  }
134 }
135 
136 # updateStatusBar:
137 # Updates the main Scid window status bar.
138 #
139 proc updateStatusBar {} {
140  if {! [winfo exists .main]} { return}
141 
142  if {$::menuHelpMessage != ""} {
143  ::board::setInfoAlert .main.board "[tr Help]:" "$::menuHelpMessage" "black" ""
144  return
145  }
146 
147  if {$::autoplayMode == 1} {
148  ::board::setInfoAlert .main.board "Autoplay:" [tr Stop] "red" "cancelAutoplay"
149  return
150  }
151 
152  if {[info exists ::playMode]} {
153  set pInfo [eval "$::playMode info"]
154  if {[llength $pInfo] != 4} {
155  ::board::setInfoAlert .main.board "Playing..." [tr Stop] "red" {eval "$::playMode stop"}
156  } else {
157  ::board::setInfoAlert .main.board {*}pInfo
158  }
159  return
160  }
161 
162  # show [%clk] command (if we are not playing)
163  set toMove [sc_pos side]
164  set comment [sc_pos getComment]
165  if { ![gameclock::isRunning] } {
166  set ::gamePlayers(clockW) ""
167  set ::gamePlayers(clockB) ""
168  set clkExp {.*?\[%clk\s*(.*?)\s*\].*}
169  set prevCom [sc_pos getPrevComment]
170  if {$toMove == "white"} {
171  regexp $clkExp $comment -> ::gamePlayers(clockB)
172  regexp $clkExp $prevCom -> ::gamePlayers(clockW)
173  } else {
174  regexp $clkExp $comment -> ::gamePlayers(clockW)
175  regexp $clkExp $prevCom -> ::gamePlayers(clockB)
176  }
177  }
178 
179  if {[info exists ::guessedAddMove]} {
180  set ::gameLastMove [lindex $::guessedAddMove 1]
181  ::board::setInfoAlert .main.board [lindex $::guessedAddMove 0] "\[click to change\]" "blue" ".main.menuaddchoice"
182  unset ::guessedAddMove
183  return
184  }
185 
186  global moveEntry
187  if {$moveEntry(Text) != ""} {
188  set msg "\[ $moveEntry(Text) \] "
189  foreach thisMove $moveEntry(List) {
190  append msg "$thisMove "
191  }
192  ::board::setInfoAlert .main.board "Enter Move:" "$msg" "blue" ""
193  return
194  }
195 
196  # remove technical comments, notify only human readable ones
197  regsub -all {\[%.*\]} $comment {} comment
198 
199  if {$comment != ""} {
200  ::board::setInfoAlert .main.board "Comment:" "$comment" "green" "::makeCommentWin"
201  ::board::addInfo .main.board [sc_game info ECO]
202  return
203  }
204 
205 
206  set statusBar ""
207  set move [sc_game info previousMoveNT]
208  if {$move != ""} {
209  regsub {K} $move "\u2654" move
210  regsub {Q} $move "\u2655" move
211  regsub {R} $move "\u2656" move
212  regsub {B} $move "\u2657" move
213  regsub {N} $move "\u2658" move
214  set number "[sc_pos moveNumber]"
215  if {$toMove == "white"} {
216  incr number -1
217  append number ".."
218  }
219  append statusBar "Last move"
220  if {[sc_var level] != 0} { append statusBar " (var)"}
221  append statusBar ": $number.$move"
222  ::board::setInfo .main.board "$statusBar"
223  } else {
224  set msg "[sc_game info date] - [sc_game info event]"
225  ::board::setInfoAlert .main.board "[tr Event]:" $msg "blue" "::crosstab::Open"
226  }
227  ::board::addInfo .main.board [sc_game info ECO]
228 }
229 
230 proc updateMainToolbar {} {
231  if {[sc_pos isAt start]} {
232  ::board::setButtonCmd .main.board back ""
233  catch { unset ::gameInfoBar(tb_BD_Start)}
234  } else {
235  ::board::setButtonCmd .main.board back "::move::Back"
236  set ::gameInfoBar(tb_BD_Start) "::move::Start"
237  }
238  if {[sc_pos isAt end] || [sc_pos isAt vend]} {
239  ::board::setButtonCmd .main.board forward ""
240  catch { unset ::gameInfoBar(tb_BD_End)}
241  catch { unset ::gameInfoBar(tb_BD_Autoplay)}
242  } else {
243  ::board::setButtonCmd .main.board forward "::move::Forward"
244  set ::gameInfoBar(tb_BD_End) "::move::End"
245  set ::gameInfoBar(tb_BD_Autoplay) "startAutoplay"
246  }
247 
248  if {[sc_var level] == 0} {
249  catch { unset ::gameInfoBar(tb_BD_VarDelete)}
250  catch { unset ::gameInfoBar(tb_BD_VarPromote)}
251  catch { unset ::gameInfoBar(tb_BD_VarLeave)}
252  catch { unset ::gameInfoBar(tb_BD_BackToMainline)}
253  } else {
254  set ::gameInfoBar(tb_BD_VarDelete) { ::pgn::deleteVar }
255  set ::gameInfoBar(tb_BD_VarPromote) { ::pgn::mainVar }
256  set ::gameInfoBar(tb_BD_VarLeave) { ::move::ExitVar }
257  set ::gameInfoBar(tb_BD_BackToMainline) { while {[sc_var level] != 0} {::move::ExitVar} }
258  }
259 
260  set ::gameInfoBar(tb_BD_SetupBoard) "setupBoard"
261 }
262 
263 
264 
265 proc toggleRotateBoard {} {
266  ::board::flip .main.board
267 }
268 
269 
270 
271 
272 ############################################################
273 ### The board:
274 
275 proc toggleShowMaterial {} {
276  board::toggleMaterial .main.board
277 }
278 
279 # MouseWheel in main window:
280 proc main_mousewheelHandler {direction} {
281  if {$direction < 0} {
283  } else {
285  }
286 }
287 
288 ################################################################################
289 # added by Pascal Georges
290 # returns a list of num moves from main line following current position
291 ################################################################################
292 proc getNextMoves { {num 4} } {
293  set tmp ""
294  set count 0
295  while { [sc_game info nextMove] != "" && $count < $num} {
296  append tmp " [sc_game info nextMove]"
297  sc_move forward
298  incr count
299  }
300  sc_move back $count
301  return $tmp
302 }
303 ################################################################################
304 # displays a box with main line and variations for easy selection with keyboard
305 ################################################################################
306 proc showVars {} {
307  if {$::autoplayMode == 1} { return}
308 
309  # No need to display an empty menu
310  if {[sc_var count] == 0} {
311  return
312  }
313 
314  if {[sc_var count] == 1 && [sc_game info nextMove] == ""} {
315  # There is only one variation and no main line, so enter it
316  sc_var moveInto 0
318  return
319  }
320 
321  set w .variations
322  if {[winfo exists $w]} { return}
323 
324  set varList [sc_var list]
325  set numVars [sc_var count]
326 
327  # Present a menu of the possible variations
328  toplevel $w
329  ::setTitle $w $::tr(Variations)
330  setWinLocation $w
331  set h [expr $numVars + 1]
332  if { $h> 19} { set h 19}
333  listbox $w.lbVar -selectmode browse -height $h -width 20
334  pack $w.lbVar -expand yes -fill both -side top
335 
336  #insert main line
337  set move [sc_game info nextMove]
338  if {$move == ""} {
339  set move "($::tr(empty))"
340  } else {
341  $w.lbVar insert end "0: [getNextMoves 5]"
342  }
343 
344  # insert variations
345  for {set i 0} {$i < $numVars} {incr i} {
346  set move [::trans [lindex $varList $i]]
347  if {$move == ""} {
348  set move "($::tr(empty))"
349  } else {
350  sc_var moveInto $i
351  append move [getNextMoves 5]
352  sc_var exit
353  }
354  set str "[expr {$i + 1}]: $move"
355  $w.lbVar insert end $str
356  }
357  $w.lbVar selection set 0
358  # bindings
359  bind $w <Configure> "recordWinSize $w"
360  bind .variations <Return> {catch { event generate .variations <Right> } }
361  bind .variations <ButtonRelease-1> {catch { event generate .variations <Right> } }
362  bind .variations <Right> {
363  set cur [.variations.lbVar curselection]
364  destroy .variations
365  if {$cur == 0} {
366  sc_move forward; updateBoard -animate
367  } else {
368  sc_var moveInto [expr $cur - 1]; updateBoard -animate
369  }
370  }
371  bind .variations <Up> { set cur [.variations.lbVar curselection] ; .variations.lbVar selection clear $cur
372  set sel [expr $cur - 1]
373  if {$sel < 0} { set sel 0 }
374  .variations.lbVar selection set $sel ; .variations.lbVar see $sel}
375  bind .variations <Down> { set cur [.variations.lbVar curselection] ; .variations.lbVar selection clear $cur
376  set sel [expr $cur + 1]
377  if {$sel >= [.variations.lbVar index end]} { set sel end }
378  .variations.lbVar selection set $sel ; .variations.lbVar see $sel}
379  bind .variations <Left> { destroy .variations }
380  bind .variations <Escape> { catch { event generate .variations <Destroy> } }
381  # in order to have the window always on top : this does not really work ...
382  bind .variations <Visibility> {
383  if { "%s" != "VisibilityUnobscured" } {
384  focus .variations
385  raise .variations
386  }
387  }
388  bind .variations <FocusOut> {
389  focus .variations
390  raise .variations
391  }
392 
393  # Needed or the main window loses the focus
394  bind .variations <Destroy> { focus -force .main }
395 
396  catch { focus .variations}
397  catch { grab $w}
398  update
399 }
400 ################################################################################
401 #
402 ################################################################################
403 
404 # updateBoard:
405 # Updates the main board.
406 # If a parameter "-pgn" is specified, the PGN text is also regenerated.
407 # If a parameter "-animate" is specified, board changes are animated.
408 #
409 proc updateBoard {args} {
410  ::notify::PosChanged {*}$args
411 }
412 
413 
414 # updateGameInfo:
415 # Update the game status window .main.gameInfo
416 #
417 proc updateGameInfo {} {
418  global gameInfo
419 
420  .main.gameInfo.text configure -state normal
421  .main.gameInfo.text delete 0.0 end
422  ::htext::display .main.gameInfo.text [sc_game info -hide $gameInfo(hideNextMove) \
423  -material $gameInfo(showMaterial) \
424  -cfull $gameInfo(fullComment) \
425  -fen $gameInfo(showFEN) -tb $gameInfo(showTB)]
426  if {$gameInfo(wrap)} {
427  .main.gameInfo.text configure -wrap word
428  .main.gameInfo.text tag configure wrap -lmargin2 10
429  .main.gameInfo.text tag add wrap 1.0 end
430  } else {
431  .main.gameInfo.text configure -wrap none
432  }
433  .main.gameInfo.text configure -state disabled
435 }
436 
437 set photosMinimized 0
438 proc togglePhotosSize {{toggle 1}} {
439  place forget .main.photoW
440  place forget .main.photoB
441  if {! $::gameInfo(photos)} { return}
442 
444  if {$toggle} { set ::photosMinimized [expr !$::photosMinimized]}
445 
446  set distance [expr {[image width photoB] + 2}]
447  if { $distance < 10 } { set distance 82}
448 
449  if {$::photosMinimized} {
450  place .main.photoW -in .main.gameInfo.text -x -17 -relx 1.0 -relheight 0.15 -width 15 -anchor ne
451  place .main.photoB -in .main.gameInfo.text -x -1 -relx 1.0 -relheight 0.15 -width 15 -anchor ne
452  } else {
453  place .main.photoW -in .main.gameInfo.text -x -$distance -relx 1.0 -relheight 1 -width [image width photoW] -anchor ne
454  place .main.photoB -in .main.gameInfo.text -x -1 -relx 1.0 -relheight 1 -width [image width photoB] -anchor ne
455  }
456 }
457 
458 
459 # readPhotoFile executed once at startup for each SPF file. Loads SPI file if it exists.
460 # Otherwise it generates index information and tries to write SPI file to disk (if it can be done)
461 proc readPhotoFile {fname} {
462  set count 0
463  set writespi 0
464 
465  if {! [regsub {\.spf$} $fname {.spi} spi]} {
466  # How does it happend?
467  return
468  }
469 
470  # If SPI file was found then just source it and exit
471  if { [file readable $spi]} {
472  set count [array size ::unsafe::spffile]
473  safeSource $spi fname $fname
474  set newcount [array size ::unsafe::spffile]
475  if {[expr $newcount - $count] > 0} {
476  ::splash::add "Found [expr $newcount - $count] player photos in [file tail $fname]"
477  ::splash::add "Loading information from index file [file tail $spi]"
478  return [expr $newcount - $count]
479  } else {
480  set count 0
481  }
482  }
483 
484  # Check for the absence of the SPI file and check for the write permissions
485  if { ![file exists $spi] && ![catch {open $spi w} fd_spi]} {
486  # SPI file will be written to disk by scid
487  set writespi 1
488  }
489 
490  if {! [file readable $fname]} { return}
491 
492  set fd [open $fname]
493  while {[gets $fd line] >= 0} {
494  # search for the string photo "Player Name"
495  if { [regexp {^photo \"(.*)\" \{$} $line -> name] } {
496  set count [expr $count + 1]
497  set begin [tell $fd]
498  # skip data block
499  while {1} {
500  set end [tell $fd]
501  gets $fd line
502  if {[regexp {.*\}.*} $line]} {break}
503  }
504  set trimname [trimString $name]
505  set size [expr $end - $begin]
506  set ::unsafe::photobegin($trimname) $begin
507  set ::unsafe::photosize($trimname) $size
508  set ::unsafe::spffile($trimname) $fname
509  if { $writespi } {
510  # writing SPI file to disk
511  puts $fd_spi "set \"photobegin($trimname)\" $begin"
512  puts $fd_spi "set \"photosize($trimname)\" $size"
513  puts $fd_spi "set \"spffile($trimname)\" \"\$fname\""
514  }
515  }
516  }
517  if {$count > 0 && $writespi} {
518  ::splash::add "Found $count player photos in [file tail $fname]"
519  ::splash::add "Index file [file tail $spi] was generated succesfully"
520  }
521  if {$count > 0 && !$writespi} {
522  ::splash::add "Found $count player photos in [file tail $fname]"
523  ::splash::add "Could not generate index file [file tail $spi]"
524  ::splash::add "Use spf2spi script to generate [file tail $spi] file "
525  }
526 
527  if { $writespi } { close $fd_spi}
528  close $fd
529  return $count
530 }
531 
532 
533 #convert $data string tolower case and strip the first two blanks.
534 proc trimString {data} {
535  set data [string tolower $data]
536  set strindex [string first "\ " $data]
537  set data [string replace $data $strindex $strindex]
538  set strindex [string first "\ " $data]
539  set data [string replace $data $strindex $strindex]
540  return $data
541 }
542 
543 
544 # retrieve photo from the SPF file using index information
545 proc getphoto {name} {
546  set data ""
547  if {[info exists ::unsafe::spffile($name)]} {
548  set fd [open $::unsafe::spffile($name)]
549  seek $fd $::unsafe::photobegin($name) start
550  set data [read $fd $::unsafe::photosize($name)]
551  close $fd
552  }
553  return $data
554 }
555 
556 
557 proc loadPlayersPhoto {} {
558  set ::gamePlayers(photoW) {}
559  set ::gamePlayers(photoB) {}
560  image create photo photoW
561  image create photo photoB
562 
563  # Directories where Scid searches for the photo files
564  set photodirs [list $::scidDataDir $::scidUserDir $::scidConfigDir [file join $::scidShareDir "photos"]]
565  if {[info exists ::scidPhotoDir]} { lappend photodirs $::scidPhotoDir}
566 
567  # Read all Scid photo (*.spf) files in the Scid data/user/config directories:
568  set nImg 0
569  set nFiles 0
570  foreach dir $photodirs {
571  foreach photofile [glob -nocomplain -directory $dir "*.spf"] {
572  set n [readPhotoFile $photofile]
573  if {$n > 0} {
574  incr nFiles
575  incr nImg $n
576  }
577  }
578  }
579 
580  return [list $nImg $nFiles]
581 }
583 
584 # Try to change the engine name: ignore version number, try to ignore blanks
585 # TODO: rename this function (spellcheck playernames, converts to lower case and remove spaces)
586 proc trimEngineName { engine } {
587  catch {
588  set spell_name [sc_name retrievename $engine]
589  if {$spell_name != ""} { set engine $spell_name}
590  }
591  set engine [string tolower $engine]
592 
593  if { [string first "deep " $engine] == 0 } {
594  # strip "deep "
595  set engine [string range $engine 5 end]
596  }
597  # delete two first blank to make "The King" same as "TheKing"
598  # or "Green Light Chess" as "Greenlightchess"
599  set strindex [string first "\ " $engine]
600  set engine [string replace $engine $strindex $strindex]
601  set strindex [string first "\ " $engine]
602  set engine [string replace $engine $strindex $strindex]
603  set strindex [string first "," $engine]
604  set slen [string len $engine]
605  if { $strindex == -1 && $slen > 2 } {
606  #seems to be a engine name:
607  # search until longest name matches an engine name
608  set slen [string len $engine]
609  for { set strindex $slen} {![info exists ::unsafe::spffile([string range $engine 0 $strindex])]\
610  && $strindex > 2 } {set strindex [expr {$strindex - 1}]} { }
611  set engine [string range $engine 0 $strindex]
612  }
613  return $engine
614 }
615 
616 
617 # updatePlayerPhotos
618 # Updates the images photoW and photoB for the two players of the current game.
619 #
620 proc updatePlayerPhotos {{force ""}} {
621  foreach {name img} {nameW photoW nameB photoB} {
622  set spellname $::gamePlayers($name)
623  if {$::gamePlayers($img) != $spellname} {
624  set ::gamePlayers($img) $spellname
625  catch { set spellname [trimEngineName $spellname]}
626  image create photo $img -data [getphoto $spellname]
627  }
628  }
629 }
630 
631 #########################################################
632 ### Chess move input
633 
634 # Globals for mouse-based move input:
635 
636 set selectedSq -1
637 set bestSq -1
638 
639 set EMPTY 0
640 set KING 1
641 set QUEEN 2
642 set ROOK 3
643 set BISHOP 4
644 set KNIGHT 5
645 set PAWN 6
646 
647 ################################################################################
648 #
649 ################################################################################
650 proc getPromoPiece {} {
651  set w .promoWin
652  set ::result 2
653  toplevel $w
654  # wm transient $w .main
655  ::setTitle $w "Scid"
656  wm resizable $w 0 0
657  set col "w"
658  if { [sc_pos side] == "black" } { set col "b"}
659  ttk::button $w.bq -image ${col}q45 -command "set ::result 2 ; destroy $w"
660  ttk::button $w.br -image ${col}r45 -command "set ::result 3 ; destroy $w"
661  ttk::button $w.bb -image ${col}b45 -command "set ::result 4 ; destroy $w"
662  ttk::button $w.bn -image ${col}n45 -command "set ::result 5 ; destroy $w"
663  pack $w.bq $w.br $w.bb $w.bn -side left
664  bind $w <Escape> "set ::result 2 ; destroy $w"
665  bind $w <Return> "set ::result 2 ; destroy $w"
666  update
667  catch { grab $w}
668  tkwait window $w
669  return $::result
670 }
671 
672 # TODO: remove this
673 # confirmReplaceMove:
674 # Asks the user what to do when adding a move when a move already
675 # exists.
676 # Returns a string value:
677 # "replace" to replace the move, truncating the game.
678 # "var" to add the move as a new variation.
679 # "cancel" to do nothing.
680 #
681 
682 proc confirmReplaceMove {} {
683  if {[winfo exists $::reviewgame::window]} {
684  return "var"
685  }
686  if {! $::askToReplaceMoves} {
687  return "replace"
688  }
689 
690  option add *Dialog.msg.wrapLength 4i interactive
691  catch {tk_dialog .dialog "Scid: $::tr(ReplaceMove)?" \
692  $::tr(ReplaceMoveMessage) "" 0 \
693  $::tr(ReplaceMove) $::tr(NewMainLine) \
694  $::tr(AddNewVar) $::tr(Cancel)} answer
695  option add *Dialog.msg.wrapLength 3i interactive
696  if {$answer == 0} { return "replace"}
697  if {$answer == 1} { return "mainline"}
698  if {$answer == 2} { return "var"}
699  return "cancel"
700 }
701 
702 proc addNullMove {} {
703  addMove null null
704 }
705 
706 proc addMove { sq1 sq2 {animate "-animate"}} {
707  global EMPTY
708  set nullmove 0
709  if {$sq1 == "null" && $sq2 == "null"} { set nullmove 1}
710  if {!$nullmove && [sc_pos isLegal $sq1 $sq2] == 0} {
711  # Illegal move, but if it is King takes king then treat it as
712  # entering a null move:
713  set board [sc_pos board]
714  set k1 [string tolower [string index $board $sq1]]
715  set k2 [string tolower [string index $board $sq2]]
716  if {$k1 == "k" && $k2 == "k"} { set nullmove 1} else { return}
717  }
718  if {$nullmove} {
719  if {[sc_pos isCheck]} { return}
720  set moveUCI "0000"
721  } else {
722  set moveUCI [::board::san $sq2][::board::san $sq1]
723  }
724  addMoveUCI $moveUCI "" $animate
725 }
726 
727 proc addSanMove { {san} } {
728  set err [catch { sc_game SANtoUCI $san} moveUCI]
729  if {! $err} { addMoveUCI $moveUCI}
730  return $err
731 }
732 
733 # addMoveUCI:
734 # Adds the move indicated if it is legal.
735 # If the move is a promotion, getPromoPiece will be called
736 # to get the promotion piece from the user.
737 #
738 proc addMoveUCI {{moveUCI} {action ""} {animate "-animate"}} {
739  set sq1 [::board::sq [string range $moveUCI 0 1]]
740  set sq2 [::board::sq [string range $moveUCI 2 3]]
741  if { [::fics::setPremove $sq1 $sq2] || ! [::fics::playerCanMove] || ! [::reviewgame::playerCanMove]} { return} ;# not player's turn
742 
743  if { [string length $moveUCI] == 4 && $sq1 != $sq2 && [sc_pos isPromotion $sq1 $sq2] } {
744  switch -- [getPromoPiece] {
745  2 { set promoLetter "q"}
746  3 { set promoLetter "r"}
747  4 { set promoLetter "b"}
748  5 { set promoLetter "n"}
749  default {set promoLetter ""}
750  }
751  append moveUCI $promoLetter
752  }
753 
754  if {! $::annotateMode} {
755  if {[::move::Follow $moveUCI]} { return [updateBoard $animate]}
756  }
757 
758 
759  if {![sc_pos isAt vend]} {
760  if {$action == ""} {
761  set replacedmove ""
762  set n [sc_var count]
763  if {$n == 0} {
764  sc_move forward
765  if {[sc_pos isAt vend]} {
766  set replacedmove [sc_game info previousMoveNT]
767  }
768  sc_move back
769  }
770  if {$replacedmove != ""} {
771  set ::guessedAddMove [list "Replaced Move $replacedmove"]
772  } else {
773  set action "var"
774  }
775  }
776 
777  switch -- $action {
778  mainline { set ::guessedAddMove [list "New Main Line"]}
779  var { set ::guessedAddMove [list "New Variation"]}
780  replace { set ::guessedAddMove [list "Replaced Main Line"]}
781  }
782  lappend ::guessedAddMove $moveUCI
783  }
784 
785  undoFeature save
786  if {($action == "mainline" || $action == "var") && ![sc_pos isAt vend]} {
787  sc_var create
788  }
789 
790  if {$moveUCI == "0000"} {
791  sc_move addSan null
792  } else {
793  sc_move addUCI $moveUCI
794  }
795  if {$action == "mainline"} {
796  sc_var promote
797  sc_move forward 1
798  }
799 
800  set ::sergame::lastPlayerMoveUci "$moveUCI"
801 
802  if {[winfo exists .fics]} {
803  if { [::fics::playerCanMove] } {
804  if { [string length $moveUCI] == 5 } {
805  set promoletter [ string tolower [ string index $moveUCI end]]
806  ::fics::writechan "promote $promoletter"
807  }
808  ::fics::writechan [ string range [sc_game info previousMoveUCI] 0 3]
810  }
811  }
812 
813  if {$::novag::connected} {
814  ::novag::addMove "$moveUCI"
815  }
816 
817  set san [sc_game info previous]
818  after idle [list ::utils::sound::AnnounceNewMove $san]
819 
820  ::notify::PosChanged -pgn $animate
821 }
822 
823 proc suggestMove {} {
824  if {! $::suggestMoves} { return 0}
825  if {[info exists ::playMode]} {
826  return [eval "$::playMode suggestMove"]
827  }
828  if {$::fics::playing != 0} { return 0}
829  return 1
830 }
831 
832 # enterSquare:
833 # Called when the mouse pointer enters a board square.
834 # Finds the best matching square for a move (if there is a
835 # legal move to or from this square), and colors the squares
836 # to indicate the suggested move.
837 #
838 proc enterSquare { square } {
839  global bestSq bestcolor selectedSq
840  if {$selectedSq == -1} {
841  set bestSq -1
842  if {[::suggestMove]} {
843  set bestSq [sc_pos bestSquare $square]
844  if {$bestSq != -1} {
845  ::board::colorSquare .main.board $square $bestcolor
846  ::board::colorSquare .main.board $bestSq $bestcolor
847  }
848  }
849  }
850 }
851 
852 # leaveSquare:
853 # Called when the mouse pointer leaves a board square.
854 # Recolors squares to normal (lite/dark) color.
855 #
856 proc leaveSquare { square } {
857  global selectedSq bestSq
858  if {$selectedSq == -1} {
859  ::board::colorSquare .main.board $bestSq
860  ::board::colorSquare .main.board $square
861  }
862 }
863 
864 # pressSquare:
865 # Called when the left mouse button is pressed on a square. Sets
866 # that square to be the selected square.
867 #
868 proc pressSquare { square } {
869  global selectedSq highcolor
870 
871  if { ![::fics::playerCanMove] || ![::reviewgame::playerCanMove] } { return} ;# not player's turn
872 
873  # if training with calculations of var is on, just log the event
874  if { [winfo exists .calvarWin] } {
875  ::calvar::pressSquare $square
876  return
877  }
878 
879  if {$selectedSq == -1} {
880  set selectedSq $square
881  ::board::colorSquare .main.board $square $highcolor
882  # Drag this piece if it is the same color as the side to move:
883  set c [string index [sc_pos side] 0] ;# will be "w" or "b"
884  set p [string index [::board::piece .main.board $square] 0] ;# "w", "b" or "e"
885  if {$c == $p} {
886  ::board::setDragSquare .main.board $square
887  }
888  } else {
889  ::board::setDragSquare .main.board -1
890  ::board::colorSquare .main.board $selectedSq
891  ::board::colorSquare .main.board $square
892  set tmp $selectedSq
893  set selectedSq -1
894  if {$square != $tmp} {
895  addMove $square $tmp
896  }
897  enterSquare $square
898  }
899 }
900 
901 # releaseSquare:
902 # Called when the left mouse button is released over a square.
903 # If the square is different to that the button was pressed on, it
904 # is a dragged move; otherwise it is just selecting this square as
905 # part of a move.
906 #
907 proc releaseSquare { w x y } {
908  if { [winfo exists .calvarWin] } { return}
909 
910  global selectedSq bestSq
911 
913  set square [::board::getSquare $w $x $y]
914  if {$square < 0} {
915  set selectedSq -1
916  return
917  }
918 
919  if {$square == $selectedSq} {
920  if {[::suggestMove]} {
921  # User pressed and released on same square, so make the
922  # suggested move if there is one:
923  set selectedSq -1
924  ::board::colorSquare $w $bestSq
925  ::board::colorSquare $w $square
926  addMove $square $bestSq
927  enterSquare $square
928  } else {
929  # Current square is the square user pressed the button on,
930  # so we do nothing.
931  }
932  } elseif {$selectedSq != -1} {
933  # User has dragged to another square, so try to add this as a move:
934  set tmp $selectedSq
935  set selectedSq -1
936  addMove $square $tmp ""
937  ::board::colorSquare $w $square
938  ::board::colorSquare $w $tmp
939  }
940 }
941 
942 
943 # backSquare:
944 # Handles the retracting of a move (when the right mouse button is
945 # clicked on a square). Recolors squares to normal color also.
946 # If the move is the last in the game or variation, is is removed
947 # by truncating the game after retracting the move.
948 #
949 proc backSquare {} {
950  global selectedSq bestSq
951  set lastMoveInLine 0
952  if {[sc_pos isAt vend]} {
953  set lastMoveInLine 1
954  }
955  sc_move back
956 
957  # RMB used to delete the move if it was the last in a line. Removed it as there is no undo.
958  # if {[sc_pos isAt vstart] && [sc_var level] != 0} {
959  # ::pgn::deleteVar [sc_var number]
960  # } elseif {$lastMoveInLine} {
961  # sc_game truncate
962  # }
963 
964  set selectedSq -1
965  set bestSq -1
966  # update the board without -pgn option because of poor performance with long games
967  updateBoard -animate
969 }
970 
971 # addMarker:
972 # add/delete square markers and arrows to the current position
973 #
974 proc addMarker {w x y} {
975  set sq [::board::getSquare $w $x $y]
976  if {! [info exists ::markStartSq]} {
977  set ::markStartSq [::board::san $sq]
978  return
979  }
980 
981  set from $::markStartSq
982  unset ::markStartSq
983  set to [::board::san $sq]
984  if {$from == "" || $to == ""} { return}
985 
986  if {$from == $to } {
987  set cmd "$::markType,$to,$::markColor"
988  set cmd_erase "\[a-z\]*,$to,\[a-z\]*"
989  } else {
990  set cmd "arrow,$from,$to,$::markColor"
991  set cmd_erase "arrow,$from,$to,\[a-z\]*"
992  }
993  set oldComment [sc_pos getComment]
994  regsub -all " *\\\[%draw $cmd\\\]" $oldComment "" newComment
995  if {$newComment == $oldComment} {
996  regsub -all " *\\\[%draw $cmd_erase\\\]" $oldComment "" newComment
997  append newComment " \[%draw $cmd\]"
998  }
999 
1000  sc_pos setComment $newComment
1002 }
1003 
1004 # addNag:
1005 # add a Nag to the current position
1006 #
1007 proc addNag {nag} {
1008  undoFeature save
1009  sc_pos addNag "$nag"
1011 }
1012 
1013 ################################################################################
1014 #
1015 ################################################################################
1016 proc undoFeature {action} {
1017  if {$action == "save"} {
1018  sc_game undoPoint
1019  } elseif {$action == "undo"} {
1020  sc_game undo
1022  } elseif {$action == "redo"} {
1023  sc_game redo
1025  } elseif {$action == "undoAll"} {
1026  sc_game undoAll
1028  }
1029 }
1030 
1031 proc setPlayMode { callback } {
1032  set ::playMode "$callback"
1033  if {$::playMode == ""} { unset ::playMode}
1035 }
1036 
1037 ################################################################################
1038 # In docked mode, resize board automatically
1039 ################################################################################
1040 proc resizeMainBoard {} {
1041  if { $::autoResizeBoard } {
1042  update idletasks
1043  set availw [winfo width .fdockmain]
1044  set availh [winfo height .fdockmain]
1045  if {$::showGameInfo} {
1046  set availh [expr $availh - [winfo height .main.gameInfo]]
1047  }
1048  if { [llength [pack slaves .main.tb]] != 0 } {
1049  set availh [expr $availh - [winfo height .main.tb]]
1050  }
1051  set ::boardSize [::board::resizeAuto .main.board "0 0 $availw $availh"]
1052  }
1053 }
1054 ################################################################################
1055 # sets visibility of gameInfo panel at the bottom of main board
1056 proc toggleGameInfo {} {
1057  if {$::showGameInfo} {
1058  grid .main.gameInfo -row 3 -column 0 -sticky news
1059  } else {
1060  grid forget .main.gameInfo
1061  }
1063 }
1064 ################################################################################
1065 
1066 proc CreateMainBoard { {w} } {
1067  createToplevel $w
1068  setTitle $w [ ::tr "Board"]
1069 
1071 
1072  ::board::new $w.board $::boardSize
1073  ::board::showMarks $w.board 1
1074  for {set i 0} {$i < $::boardCoords} {incr i} { ::board::coords $w.board}
1075  if {$::gameInfo(showMaterial)} { ::board::toggleMaterial $w.board}
1076 
1077  ::board::addNamesBar $w.board gamePlayers
1078  ::board::addInfoBar $w.board gameInfoBar
1079 
1080  set ::gameInfoBar(tb_BD_Coords) "set ::boardCoords \[::board::coords $w.board\]"
1081  set ::gameInfoBar(tb_BD_Material) "set ::gameInfo(showMaterial) \[::board::toggleMaterial $w.board\]"
1082 
1083  menu .main.menuaddchoice -bg white -font font_Regular
1084  .main.menuaddchoice add command -label " Undo" -image tb_BD_Undo -compound left \
1085  -command {undoFeature undo}
1086  .main.menuaddchoice add command -label " $::tr(ReplaceMove)" -image tb_BD_Replace -compound left \
1087  -command {sc_game undo; addMoveUCI $::gameLastMove replace}
1088  .main.menuaddchoice add command -label " $::tr(NewMainLine)" -image tb_BD_NewMainline -compound left \
1089  -command {sc_game undo; addMoveUCI $::gameLastMove mainline}
1090  .main.menuaddchoice add command -label " $::tr(AddNewVar)" -image tb_BD_NewVar -compound left \
1091  -command {sc_game undo; addMoveUCI $::gameLastMove var}
1092 
1093  InitToolbar .main.tb
1094 
1095  for {set i 0} { $i < 64 } { incr i} {
1096  ::board::bind $w.board $i <Enter> "enterSquare $i"
1097  ::board::bind $w.board $i <Leave> "leaveSquare $i"
1098  ::board::bind $w.board $i <ButtonPress-1> "pressSquare $i"
1099  ::board::bind $w.board $i <Control-ButtonPress-1> "addMarker $w.board %X %Y"
1100  ::board::bind $w.board $i <Control-ButtonRelease-1> "addMarker $w.board %X %Y"
1101  ::board::bind $w.board $i <B1-Motion> "::board::dragPiece $w.board %X %Y"
1102  ::board::bind $w.board $i <ButtonRelease-1> "releaseSquare $w.board %X %Y"
1103  ::board::bind $w.board $i <ButtonPress-$::MB3> backSquare
1104  }
1105 
1106  foreach i {o q r n k O Q R B N K} {
1107  bind $w <$i> "moveEntry_Char [string toupper $i]"
1108  bind $w <Alt-$i> { continue }
1109 
1110  }
1111  foreach i {a b c d e f g h 1 2 3 4 5 6 7 8} {
1112  bind $w <Key-$i> "moveEntry_Char $i"
1113  bind $w <Alt-$i> { continue }
1114  }
1115 
1116  bind $w <Control-BackSpace> backSquare
1117  bind $w <Control-Delete> backSquare
1118  bind $w <BackSpace> moveEntry_Backspace
1119  bind $w <Delete> moveEntry_Backspace
1120  bind $w <space> moveEntry_Complete
1121  bind $w <ButtonRelease> "focus $w"
1122  bind $w <Configure> {+::resizeMainBoard }
1123  bind $w <Return> { #TODO: improve this
1124  if {[winfo exists .analysisWin1] && $analysis(analyzeMode1)} {
1125  .analysisWin1.b1.move invoke
1126  }
1127  }
1128 
1129  bindMouseWheel $w "main_mousewheelHandler"
1130 
1131  ttk::frame $w.space
1132  grid $w.space -row 4 -column 0 -columnspan 3 -sticky nsew
1133  grid rowconfigure $w 3 -weight 0
1134  grid rowconfigure $w 4 -weight 1
1135 
1136  grid columnconfigure $w 0 -weight 1
1137  grid $w.board -row 2 -column 0 -sticky we ;# -padx 5 -pady 5
1138 
1142  updateTitle
1143 }
1144 
1145 proc CreateGameInfo {} {
1146  # .gameInfo is the game information widget:
1147  #
1148  autoscrollframe .main.gameInfo text .main.gameInfo.text
1149  .main.gameInfo.text configure -width 20 -height 6 -fg black -bg white -wrap none -state disabled -cursor top_left_arrow -setgrid 1
1150  ::htext::init .main.gameInfo.text
1151 
1152  # Set up player photos:
1153  label .main.photoW -background white -image photoW -anchor ne
1154  label .main.photoB -background white -image photoB -anchor ne
1155  bind .main.photoW <ButtonPress-1> "togglePhotosSize"
1156  bind .main.photoB <ButtonPress-1> "togglePhotosSize"
1157 
1158  # Right-mouse button menu for gameInfo frame:
1159  menu .main.gameInfo.menu -tearoff 0
1160 
1161  .main.gameInfo.menu add checkbutton -label GInfoHideNext \
1162  -variable gameInfo(hideNextMove) -offvalue 0 -onvalue 1 -command updateBoard
1163 
1164  .main.gameInfo.menu add checkbutton -label GInfoMaterial -variable gameInfo(showMaterial) -offvalue 0 -onvalue 1 \
1165  -command { toggleShowMaterial }
1166 
1167  .main.gameInfo.menu add checkbutton -label GInfoFEN \
1168  -variable gameInfo(showFEN) -offvalue 0 -onvalue 1 -command updateBoard
1169 
1170  .main.gameInfo.menu add checkbutton -label GInfoMarks \
1171  -variable gameInfo(showMarks) -offvalue 0 -onvalue 1 -command updateBoard
1172 
1173  .main.gameInfo.menu add checkbutton -label GInfoWrap \
1174  -variable gameInfo(wrap) -offvalue 0 -onvalue 1 -command updateBoard
1175 
1176  .main.gameInfo.menu add checkbutton -label GInfoFullComment \
1177  -variable gameInfo(fullComment) -offvalue 0 -onvalue 1 -command updateBoard
1178 
1179  .main.gameInfo.menu add checkbutton -label GInfoPhotos \
1180  -variable gameInfo(photos) -offvalue 0 -onvalue 1 \
1181  -command {togglePhotosSize 0}
1182 
1183  .main.gameInfo.menu add separator
1184 
1185  .main.gameInfo.menu add radiobutton -label GInfoTBNothing \
1186  -variable gameInfo(showTB) -value 0 -command updateBoard
1187 
1188  .main.gameInfo.menu add radiobutton -label GInfoTBResult \
1189  -variable gameInfo(showTB) -value 1 -command updateBoard
1190 
1191  .main.gameInfo.menu add radiobutton -label GInfoTBAll \
1192  -variable gameInfo(showTB) -value 2 -command updateBoard
1193 
1194  .main.gameInfo.menu add separator
1195 
1196  .main.gameInfo.menu add command -label GInfoDelete -command {
1197  sc_base gameflag [sc_base current] [sc_game number] invert del
1198  ::notify::DatabaseModified [sc_base current]
1199  }
1200 
1201  bind .main.gameInfo.text <ButtonPress-$::MB3> {
1202  tk_popup .main.gameInfo.menu %X %Y
1203  }
1204 
1205  storeMenuLabels .main.gameInfo.menu
1206 }
1207 
1208 # Set toolbar help status messages:
1209 proc setToolbarTooltips { tb } {
1210  foreach {b m} {
1211  newdb FileNew open FileOpen finder FileFinder
1212  save GameReplace closedb FileClose bkm FileBookmarks
1213  gprev GamePrev gnext GameNext
1214  newgame GameNew copy EditCopy paste EditPaste
1215  boardsearch SearchCurrent
1216  headersearch SearchHeader materialsearch SearchMaterial
1217  switcher WindowsSwitcher glist WindowsGList pgn WindowsPGN tmt WindowsTmt
1218  maint WindowsMaint eco WindowsECO tree WindowsTree crosstab ToolsCross
1219  engine ToolsAnalysis } {
1220  ::utils::tooltip::Set $tb.$b $::helpMessage($::language,$m)
1221  }
1222 }
1223 
1224 proc InitToolbar {{tb}} {
1225  ttk::frame $tb -relief raised -border 1
1226  ttk::button $tb.newdb -image tb_newdb -command ::file::New -padding {2 0}
1227  ttk::button .main.tb.open -image tb_open -command ::file::Open -padding {2 0}
1228  ttk::button .main.tb.save -image tb_save -padding {2 0} -command {
1229  if {[sc_game number] != 0} {
1230  #busyCursor .
1231  gameReplace
1232  # catch {.save.buttons.save invoke}
1233  #unbusyCursor .
1234  } else {
1235  gameAdd
1236  }
1237  }
1238  ttk::button .main.tb.closedb -image tb_closedb -command ::file::Close -padding {2 0}
1239  ttk::button .main.tb.finder -image tb_finder -command ::file::finder::Open -padding {2 0}
1240  ttk::menubutton .main.tb.bkm -image tb_bkm -menu .main.tb.bkm.menu -padding {2 0}
1241  menu .main.tb.bkm.menu
1242  ::bookmarks::RefreshMenu .main.tb.bkm.menu
1243 
1244  ttk::frame .main.tb.space1 -width 4
1245  ttk::button .main.tb.newgame -image tb_newgame -command ::game::Clear -padding {2 0}
1246  ttk::button .main.tb.copy -image tb_copy -command ::gameAddToClipbase -padding {2 0}
1247  ttk::button .main.tb.paste -image tb_paste \
1248  -command {catch {sc_clipbase paste}; updateBoard -pgn} -padding {2 0}
1249  ttk::frame .main.tb.space2 -width 4
1250  ttk::button .main.tb.gprev -image tb_gprev -command {::game::LoadNextPrev previous} -padding {2 0}
1251  ttk::button .main.tb.gnext -image tb_gnext -command {::game::LoadNextPrev next} -padding {2 0}
1252  ttk::frame .main.tb.space3 -width 4
1253  ttk::button .main.tb.boardsearch -image tb_boardsearch -command ::search::board -padding {2 0}
1254  ttk::button .main.tb.headersearch -image tb_headersearch -command ::search::header -padding {2 0}
1255  ttk::button .main.tb.materialsearch -image tb_materialsearch -command ::search::material -padding {2 0}
1256  ttk::frame .main.tb.space4 -width 4
1257  ttk::button .main.tb.switcher -image tb_switcher -command ::windows::switcher::Open -padding {2 0}
1258  ttk::button .main.tb.glist -image tb_glist -command ::windows::gamelist::Open -padding {2 0}
1259  ttk::button .main.tb.pgn -image tb_pgn -command ::pgn::OpenClose -padding {2 0}
1260  ttk::button .main.tb.tmt -image tb_tmt -command ::tourney::toggle -padding {2 0}
1261  ttk::button .main.tb.maint -image tb_maint -command ::maint::OpenClose -padding {2 0}
1262  ttk::button .main.tb.eco -image tb_eco -command ::windows::eco::OpenClose -padding {2 0}
1263  ttk::button .main.tb.tree -image tb_tree -command ::tree::make -padding {2 0}
1264  ttk::button .main.tb.crosstab -image tb_crosstab -command ::crosstab::OpenClose -padding {2 0}
1265  ttk::button .main.tb.engine -image tb_engine -command makeAnalysisWin -padding {2 0}
1266  ttk::button .main.tb.help -image tb_help -command {helpWindow Index} -padding {2 0}
1267 
1268  foreach i {newdb open save closedb finder bkm newgame copy paste gprev gnext \
1269  boardsearch headersearch materialsearch \
1270  switcher glist pgn tmt maint eco tree crosstab engine help} {
1271  .main.tb.$i configure -takefocus 0
1272  }
1273 
1274  setToolbarTooltips $tb
1276 }
1277 
1278 proc toggleToolbarButton { b i } {
1279  if { $::toolbar_temp($i) } {
1280  set ::toolbar_temp($i) 0
1281  $b.$i state !pressed
1282  } else {
1283  set ::toolbar_temp($i) 1
1284  $b.$i state pressed
1285  }
1286 }
1287 
1288 proc configToolbar {} {
1289  set w .tbconfig
1291  wm title $w "Scid: [tr OptionsToolbar]"
1292 
1293  array set ::toolbar_temp [array get ::toolbar_state]
1294  pack [ttk::frame $w.f] -side top -fill x
1295  set col 0
1296  set row 0
1297  foreach i {newdb open closedb finder save bkm row gprev gnext row newgame copy paste row boardsearch headersearch \
1298  materialsearch row switcher glist pgn tmt maint eco tree crosstab engine } {
1299  if { $i eq "row" } { incr row; set col 0} else {
1300  ttk::button $w.f.$i -image tb_$i -command "toggleToolbarButton $w.f $i"
1301  if { $::toolbar_temp($i) } { $w.f.$i state pressed}
1302  grid $w.f.$i -row $row -column $col -sticky news -padx 4 -pady "0 8"
1303  incr col
1304  }
1305  }
1306  setToolbarTooltips $w.f
1308  pack [ttk::frame $w.b] -side bottom -fill x
1309  ttk::button $w.on -text "+ [::utils::string::Capital $::tr(all)]" -command {
1310  foreach i [array names toolbar_temp] { set toolbar_temp($i) 1; .tbconfig.f.$i state pressed }
1311  }
1312  ttk::button $w.off -text "- [::utils::string::Capital $::tr(all)]" -command {
1313  foreach i [array names toolbar_temp] { set toolbar_temp($i) 0 ; .tbconfig.f.$i state !pressed }
1314  }
1315  ttk::button $w.ok -text "OK" -command {
1316  array set ::toolbar_state [array get toolbar_temp]
1317  catch {grab release .tbconfig}
1318  destroy .tbconfig
1319  redrawToolbar
1320  }
1321  ttk::button $w.cancel -text $::tr(Cancel) \
1322  -command "catch {grab release $w}; destroy $w"
1323  pack $w.cancel $w.ok -side right -padx 2 -pady "5 0"
1324  pack $w.on $w.off -side left -padx 2 -pady "5 0"
1325  catch {grab $w}
1326 }
1327 
1328 proc redrawToolbar {} {
1329  foreach i [winfo children .main.tb] { pack forget $i}
1330  set seenAny 0
1331  set seen 0
1332  foreach i {newdb open closedb finder save bkm} {
1333  if {$::toolbar_state($i)} {
1334  set seen 1; set seenAny 1
1335  pack .main.tb.$i -side left -pady 1 -padx 0 -ipadx 0 -pady 0 -ipady 0
1336  }
1337  }
1338  if {$seen} { pack .main.tb.space1 -side left}
1339  set seen 0
1340  foreach i {gprev gnext} {
1341  if {$::toolbar_state($i)} {
1342  set seen 1; set seenAny 1
1343  pack .main.tb.$i -side left -pady 1 -padx 0 -ipadx 0 -pady 0 -ipady 0
1344  }
1345  }
1346  if {$seen} { pack .main.tb.space2 -side left}
1347  set seen 0
1348  foreach i {newgame copy paste} {
1349  if {$::toolbar_state($i)} {
1350  set seen 1; set seenAny 1
1351  pack .main.tb.$i -side left -pady 1 -padx 0 -ipadx 0 -pady 0 -ipady 0
1352  }
1353  }
1354  if {$seen} { pack .main.tb.space3 -side left}
1355  set seen 0
1356  foreach i {boardsearch headersearch materialsearch} {
1357  if {$::toolbar_state($i)} {
1358  set seen 1; set seenAny 1
1359  pack .main.tb.$i -side left -pady 1 -padx 0 -ipadx 0 -pady 0 -ipady 0
1360  }
1361  }
1362  if {$seen} { pack .main.tb.space4 -side left}
1363  set seen 0
1364  foreach i {switcher glist pgn tmt maint eco tree crosstab engine} {
1365  if {$::toolbar_state($i)} {
1366  set seen 1; set seenAny 1
1367  pack .main.tb.$i -side left -pady 1 -padx 0 -ipadx 0 -pady 0 -ipady 0
1368  }
1369  }
1370  if {$seenAny} {
1371  grid .main.tb -row 0 -column 0 -columnspan 3 -sticky we
1372  } else {
1373  grid forget .main.tb
1374  }
1375 }
1376 
1377 ##############################