Scid  4.7.0
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros
gamelist.tcl
Go to the documentation of this file.
1 ########################################################################
2 ### Games list window
3 # Copyright (C) 2011-2014 Fulvio Benini
4 #
5 # This file is part of Scid (Shane's Chess Information Database).
6 # Scid is free software: you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation.
9 
10 proc ::windows::gamelist::Open { {base ""} {filter ""} } {
11  if {$base == ""} { set base [sc_base current]}
12  if { $filter == "" } {
13  set filter "dbfilter"
14  foreach glwin $::windows::gamelist::wins {
15  set b [::windows::gamelist::GetBase $glwin]
16  if {$b == $base && $::gamelistFilter($glwin) == "dbfilter"} {
17  set filter [sc_filter new $base]
18  }
19  }
20  }
21  if { $base != $::clipbase_db } {
22  foreach glwin $::windows::gamelist::wins {
23  set b [::windows::gamelist::GetBase $glwin]
24  if {$b == $::clipbase_db && [sc_base numGames $b] == 0 } {
25  if {[info exists ::recentSort]} {
26  set idx [lsearch -exact $::recentSort "[sc_base filename $base]"]
27  if {$idx != -1} {
28  set ::glist_Sort(ly$glwin) [lindex $::recentSort [expr $idx +1]]
30  }
31  }
32  ::windows::gamelist::SetBase $glwin $base $filter
33  ##TODO: this is a hack to raise the gamelist window
34  createToplevel $glwin
35  focus $glwin.games.glist
36  return
37  }
38  }
39  }
40 
41  set i 1
42  set closeto ""
43  while {[winfo exists .glistWin$i]} {
44  set closeto .glistWin$i
45  incr i
46  }
47  set w .glistWin$i
48  ::createToplevel $w $closeto
49 
50  set ::gamelistTitle($w) "[tr WindowsGList]:"
51  ::windows::gamelist::createWin_ $w $base $filter
52  focus $w
53 }
54 
55 proc ::windows::gamelist::OpenTreeBest { {base} {w} } {
56  if {[::createToplevel $w] == "already_exists"} {
57  focus .
58  destroy $w
59  return
60  }
61  set ::gamelistTitle($w) "[tr TreeBestGames]:"
62  ::windows::gamelist::createWin_ $w $base "tree"
63 
64  grid forget $w.buttons
65  set ::gamelistPosMask($w) 1
66 }
67 
68 proc ::windows::gamelist::Refresh {{moveup 1} {wlist ""}} {
69  if {$wlist == ""} { set wlist $::windows::gamelist::wins}
70  foreach w $wlist {
71  set err [catch {sc_base inUse $::gamelistBase($w)} inUse]
72  if {$err !=0 || $inUse == 0} {
74  continue
75  }
77  }
78 }
79 
80 proc ::windows::gamelist::DatabaseModified {{dbase} {filter -1}} {
81  set wlist $::windows::gamelist::wins
82  foreach w $wlist {
83  if {$::gamelistBase($w) == $dbase} {
84  if {$filter == -1} {
86  } elseif {$filter == $::gamelistFilter($w) || \
87  $filter == [sc_filter compose $::gamelistBase($w) $::gamelistFilter($w) ""]} {
89  }
91  }
92  }
93 }
94 
95 proc ::windows::gamelist::PosMaskProgress {} {
96  update
97  if { $::gamelistUpdating != 1 } { break}
98 }
99 
100 proc ::windows::gamelist::PosChanged {{wlist ""}} {
101  if { [info exists ::gamelistUpdating] } {
102  incr ::gamelistUpdating
103  return
104  }
105  set ::gamelistUpdating 1
106 
107  set bases {}
108  if {$wlist == ""} { set wlist $::windows::gamelist::wins}
109  foreach w $wlist {
110  if { $::gamelistPosMask($w) != 0 } {
111  $w.games.glist tag configure fsmall -foreground #bababa
112  $w.buttons.boardFilter configure -image tb_BoardMaskBusy
113  if { [lsearch -exact $bases $::gamelistBase($w)] == -1 } {
114  lappend bases $::gamelistBase($w)
115  }
116  }
117  }
118 
119  foreach base $bases {
120  update idletasks
121  #TODO: [sc_filter release $base $f]
122  set f [sc_filter new $base FEN]
123  if { $::gamelistUpdating != 1 } {
124  after idle {
125  unset ::gamelistUpdating
126  ::windows::gamelist::PosChanged
127  }
128  return
129  }
130  if {$f != ""} {
131  foreach w $::windows::gamelist::wins {
132  if { $::gamelistBase($w) == $base && $::gamelistPosMask($w) != 0 } {
133  $w.games.glist tag configure fsmall -foreground ""
134  $w.buttons.boardFilter configure -image tb_BoardMask
135  set ::gamelistFilter($w) [sc_filter compose $::gamelistBase($w) $::gamelistFilter($w) $f]
136  ::notify::DatabaseModified $base $::gamelistFilter($w)
137  }
138  }
139  }
140  }
141  unset ::gamelistUpdating
142 }
143 
144 proc ::windows::gamelist::FilterReset {{w} {base}} {
145  set f "dbfilter"
146  if {$w != "" && $base == $::gamelistBase($w)} { set f $::gamelistFilter($w)}
147  sc_filter reset $base $f full
149 }
150 
151 proc ::windows::gamelist::FilterNegate {{w} {base}} {
152  set f "dbfilter"
153  if {$w != "" && $base == $::gamelistBase($w)} { set f $::gamelistFilter($w)}
154  sc_filter negate $base $f
156 }
157 
158 proc ::windows::gamelist::FilterExport {{w}} {
159  set ftype {
160  { {PGN} {.pgn} }
161  { {LaTeX} {.tex .ltx} }
162  }
163  set fName [tk_getSaveFile -initialdir $::initialDir(base) \
164  -filetypes $ftype \
165  -typevariable ::gamelistExport \
166  -title [tr ToolsExpFilter]]
167  if {$fName == ""} { return}
168  progressWindow "Scid" "Exporting games..." $::tr(Cancel)
169  if {$::gamelistExport == "LaTeX"} {
170  if {[file extension $fName] == ""} { append fName ".tex"}
171  set err [catch {sc_filter export $::gamelistBase($w) $::gamelistFilter($w) \
172  $::glistSortStr($w.games.glist) $fName $::gamelistExport \
173  $::exportStartFile(LaTeX) $::exportEndFile(LaTeX)}]
174  } else {
175  if {[file extension $fName] == ""} { append fName ".pgn"}
176  set err [catch {sc_filter export $::gamelistBase($w) $::gamelistFilter($w) \
177  $::glistSortStr($w.games.glist) $fName $::gamelistExport}]
178  }
180  if {$err && $::errorCode != $::ERROR::UserCancel} { ERROR::MessageBox}
181 }
182 
183 # Returns text describing state of filter for specified
184 # database, e.g. "no games" or "all / 400" or "1,043 / 2,057"
185 proc ::windows::gamelist::filterText {{w ""} {base -1}} {
186  if {$base == -1} { set base [sc_base current]}
187  set f "dbfilter"
188  if {$w != "" && $base == $::gamelistBase($w)} {
189  set f $::gamelistFilter($w)
190  }
191 
192  foreach {filterSz gameSz mainSz} [sc_filter sizes $base $f] {}
193  return [formatFilterText $filterSz $gameSz]
194 }
195 
196 # Returns text describing state of filter for specified
197 # database, e.g. "no games" or "all / 400" or "1,043 / 2,057"
198 proc ::windows::gamelist::formatFilterText {filterSz gameSz} {
199  if {$gameSz == 0} { return $::tr(noGames)}
200  if {$gameSz == $filterSz} {
201  return "$::tr(all) / [::utils::thousands $gameSz 100000]"
202  }
203  return "[::utils::thousands $filterSz 100000] / [::utils::thousands $gameSz 100000]"
204 }
205 
206 proc ::windows::gamelist::GetBase {{w}} {
207  if {[info exists ::gamelistBase($w)]} { return $::gamelistBase($w)}
208  return ""
209 }
210 
211 proc ::windows::gamelist::SetBase {{w} {base} {filter "dbfilter"}} {
212  if {[lsearch -exact $::windows::gamelist::wins $w] == -1} { return}
213  after idle "::windows::gamelist::filterRelease_ $::gamelistBase($w) $::gamelistFilter($w)"
214  set ::gamelistBase($w) $base
215  set ::gamelistFilter($w) $filter
216  busyCursor $w
217  update idletasks
218  if { $::gamelistPosMask($w) != 0 } {
220  } else {
221  ::windows::gamelist::DatabaseModified $::gamelistBase($w) $::gamelistFilter($w)
222  }
223  unbusyCursor $w
224 }
225 
226 #Examples
227 # Search the games played by Carlsen: carlsen
228 # Search the games played by Magnus Carlsen: carlsen,magnus
229 # Search the games _not_ played by Carlsen: !carlsen
230 # Search only the games played as white: white carlsen
231 # Search only the games played as black: black carlsen
232 # Search games with players with elo above 2500: >2500
233 # Search games with a white player with elo above 2500: welo >2500
234 # Search games with a white player with elo under 2500: welo <2500
235 # Search games with a black player with elo between 2100-2500: belo 2100-2500
236 # Search games with a black player with elo between 2100-2500 or 0: belo 2100-2500 + belo 0
237 # Search games with a specific ECO: A00-A99
238 # Search games with ECO A00-B99 or D00-D99: A00-B99 + D00-D99
239 # Search games with ECO A00-B99 or D00-D99: A00-D99 !C00-C99
240 # Search games played after a specific year: >2013
241 # Search games played in a specific period: 2012.09.01-2013.05.31
242 # Search game number 2000: gnum 2000
243 # Search games where Carlsen played as white against Kramnik: white carlsen kramnik
244 # Search a specific game: carlsen kramnik 2013.06.13
245 #
246 # An empty search will reset the filter
247 #
248 proc ::windows::gamelist::Awesome {{w} {txt}} {
249  if {[lsearch -exact $::windows::gamelist::wins $w] == -1} { return}
250 
251  set filter [sc_filter compose $::gamelistBase($w) $::gamelistFilter($w) ""]
252  if {$txt == ""} {
253  # Quick way to reset the filter: search an empty string
254  sc_filter reset "$::gamelistBase($w)" $filter full
255  } else {
256  sc_filter reset "$::gamelistBase($w)" $filter empty
257  #Split the string using " + "
258  foreach {dummy sub} [regexp -all -inline {(.+?)(?:\s\+\s|$)} $txt] {
259  set cmd "sc_filter search $::gamelistBase($w) $filter header -filter OR"
260  progressWindow "Scid" "$::tr(HeaderSearch)..." $::tr(Cancel)
261  set res [eval "$cmd [AweParse $sub]"]
263  }
264  }
265  ::notify::DatabaseModified $::gamelistBase($w) $::gamelistFilter($w)
266 }
267 
268 proc ::windows::gamelist::AweInit {} {
269  global awe_guess awe_min awe_max
270  set awe_guess {}
271  set awe_min(-gnum) {0}
272  set awe_max(-gnum) {999999999}
273  set awe_min(-welo) {0}
274  set awe_max(-welo) {3999}
275  set awe_min(-belo) {0}
276  set awe_max(-belo) {3999}
277  set awe_min(-elo) {0}
278  set awe_max(-elo) {3999}
279  set awe_min(-eco) {A00}
280  set awe_max(-eco) {E99z4}
281  set awe_min(-date) {0000.00.00}
282  set awe_max(-date) {2047.12.31}
283 
284  set ranged {}
285  # date: YYYY.MM.GG
286  lappend ranged [list "-date" \
287  {(?:\d\d\d\d\.(?:0[0-9]|1[0-2])\.(?:[012][0-9]|3[01]))}]
288  # date: 4digits between 1801 2099, excluding 1900 and 2000
289  lappend ranged [list "-date" \
290  {(?:1[89](?!00)\d\d|20(?!00)\d\d)}]
291  # elo: 4digits between 1000 3999 or 0
292  lappend ranged [list "-elo" \
293  {(?:(?:[123]\d\d\d)|0)}]
294  # game number: all digits
295  lappend ranged [list "-gnum" \
296  {(?:\d+)}]
297  # eco: a letter [A-E] plus 2digits and optional scid subcode
298  lappend ranged [list "-eco" \
299  {(?:[A-E]\d\d(?:[a-z](?:[1-4])?)?)}]
300 
301  foreach guess $ranged {
302  set prefix {^(?:(.*?)\s+)??(}
303  set suffix {)(?:\s+(.*))?$}
304 
305  set r {[<>!]?}
306  append r [lindex $guess 1]
307  lappend awe_guess [list [lindex $guess 0] "$prefix$r$suffix"]
308 
309  set r {[!]?}
310  append r [lindex $guess 1]
311  append r {(?:-}
312  append r [lindex $guess 1]
313  append r {)?}
314  lappend awe_guess [list [lindex $guess 0] "$prefix$r$suffix"]
315  }
316 
317  #default
318  lappend awe_guess [list "-player" \
319  {^(?:(.*?)\s+)??([!]?[_,%"[:alnum:]]+)(?:\s+(.*))?$}]
320 }
321 
322 proc ::windows::gamelist::AweGuess {{txt}} {
323  global awe_guess
324  if {![info exists awe_guess]} { AweInit}
325 
326  # Remove extra spaces around ><!
327  regsub -all {(^|\s)>\s} $txt { >} txt
328  regsub -all {(^|\s)<\s} $txt { <} txt
329  regsub -all {(^|\s)!\s} $txt { !} txt
330 
331  # Replace all spaces inside "" with %%
332  regsub -all {(".*?) (.*?")} $txt {\1%%\2} txt
333 
334  # Search for explicit params
335  set param(0) ""
336  set val(0) ""
337  set extra(0) "$txt"
338  for {set np 1} {
339  [regexp \
340  {^(?:(.*?)\s+)??(gnum|white|black|welo|belo|elo|eco|date|event|site)\s+(.+?)(?:\s+(.*))?$} \
341  $extra([expr $np -1]) -> extra([expr $np -1]) param($np) val($np) extra($np)]
342  } {incr np} {}
343 
344  #Guess extras
345  set res {}
346  for {set i 0} {$i < $np} { incr i} {
347  if {$param($i) != ""} {
348  lappend res [list "-$param($i)" $val($i)]
349  }
350  if {$extra($i) == ""} { continue}
351  foreach guess $awe_guess {
352  if {[regexp [lindex $guess 1] $extra($i) -> prefix value suffix]} {
353  lappend res [list [lindex $guess 0] $value]
354  set param($np) ""
355  set extra($np) $prefix
356  incr np
357  set param($np) ""
358  set extra($np) $suffix
359  incr np
360  break
361  }
362  }
363  }
364 
365  return $res
366 }
367 
368 proc ::windows::gamelist::AweParse {{txt}} {
369  global awe_min awe_max
370  set res {}
371  foreach op [AweGuess $txt] {
372  set param [lindex $op 0]
373  set value [lindex $op 1]
374  #Restore spaces inside ""
375  regsub -all {%%} $value { } value
376  catch {
377  regsub {^<} $value "$awe_min($param) " value
378  regsub {^>} $value "$awe_max($param) " value
379  regsub {(\w*?\d+)-(\w*?\d+)} $value {\1 \2} value
380  }
381  if {[regsub {^!} $value {} value]} {
382  append param "!"
383  }
384  lappend res [list $param $value]
385  }
386 
387  return [join $res]
388 }
389 
390 proc ::windows::gamelist::CopyGames {{w} {srcBase} {dstBase}} {
391  set filter "dbfilter"
392  if {$w != "" && $srcBase == $::gamelistBase($w)} { set filter $::gamelistFilter($w)}
393 
394  set fromName [file tail [sc_base filename $srcBase]]
395  set targetName [file tail [sc_base filename $dstBase]]
396  set nGamesToCopy [sc_filter count $srcBase $filter]
397  set targetReadOnly [sc_base isReadOnly $dstBase]
398  set err ""
399  if {$nGamesToCopy == 0} {
400  set err "$::tr(CopyErrSource) $::tr(CopyErrNoGames)."
401  } elseif {$targetReadOnly} {
402  set err "$::tr(CopyErrTarget) ($targetName) $::tr(CopyErrReadOnly)."
403  }
404  if {$err != ""} {
405  tk_messageBox -type ok -icon info -title "Scid" \
406  -message "$::tr(CopyErr) \n\"$fromName\" -> \"$targetName\": \n$err"
407  return
408  }
409  # If copying to the clipbase, do not bother asking for confirmation:
410  if {$dstBase != $::clipbase_db} {
411  set confirm [tk_messageBox -type "okcancel" -icon question -title "Scid: $::tr(CopyGames)" \
412  -message [subst $::tr(CopyConfirm)]]
413  if {$confirm != "ok"} { return}
414  }
415 
416  progressWindow "Scid" "$::tr(CopyGames)..." $::tr(Cancel)
417  set copyErr [catch {sc_base copygames $srcBase $filter $dstBase} result]
419  if {$copyErr} { ERROR::MessageBox "$result"}
421 }
422 
423 proc ::windows::gamelist::ClearClipbase {} {
424  foreach w $::windows::gamelist::wins {
425  if {$::gamelistBase($w) == $::clipbase_db} {
426  ::windows::gamelist::SetBase $w $::gamelistBase($w)
427  }
428  }
429  sc_clipbase clear
430  ::notify::DatabaseModified $::clipbase_db
431  if {[sc_base current] == $::clipbase_db} { ::notify::GameChanged}
432 }
433 
434 #Private:
435 set ::windows::gamelist::wins {}
436 
437 proc ::windows::gamelist::createWin_ { {w} {base} {filter} } {
438  set ::gamelistBase($w) $base
439  set ::gamelistFilter($w) $filter
440  set ::gamelistPosMask($w) 0
441  set ::gamelistMenu($w) ""
443  if {[info exists ::recentSort]} {
444  set idx [lsearch -exact $::recentSort "[sc_base filename $base]"]
445  if {$idx != -1} { set ::glist_Sort(ly$w) [lindex $::recentSort [expr $idx +1]]}
446  }
448  grid rowconfigure $w 0 -weight 1
449  grid columnconfigure $w 0 -weight 0
450  grid columnconfigure $w 1 -weight 0
451  grid columnconfigure $w 2 -weight 1
452  bind $w <Destroy> {
453  if { [winfo class %W] == "Toplevel" } {
454  set idx [lsearch -exact $::windows::gamelist::wins %W]
455  set ::windows::gamelist::wins [lreplace $::windows::gamelist::wins $idx $idx]
456  ::windows::gamelist::filterRelease_ $::gamelistBase(%W) $::gamelistFilter(%W)
457  }
458  }
459  bind $w <Control-l> "::windows::gamelist::Open \$::gamelistBase($w)"
461  lappend ::windows::gamelist::wins $w
463 }
464 
465 proc ::windows::gamelist::createMenu_ {w} {
466  ttk::frame $w.buttons -padding {5 5 2 5}
467  ttk::button $w.buttons.database -image tb_CC_book -command "::windows::gamelist::menu_ $w database"
468  ttk::button $w.buttons.filter -image tb_search_on -command "::windows::gamelist::menu_ $w filter"
469  ttk::button $w.buttons.layout -image tb_Layout -command "::windows::gamelist::menu_ $w layout"
470  ttk::button $w.buttons.stats -image tb_Stats -command "::windows::gamelist::menu_ $w stats; ::windows::gamelist::updateStats_ $w"
471  ttk::button $w.buttons.boardFilter -image tb_BoardMask -command "::windows::gamelist::searchpos_ $w"
472  #TODO:
473  #ttk::button $w.buttons.stats -image b_bargraph
474  ::utils::tooltip::Set $w.buttons.database $::tr(ShowHideDB)
475  ::utils::tooltip::Set $w.buttons.filter $::tr(ChangeFilter)
476  ::utils::tooltip::Set $w.buttons.layout $::tr(ChangeLayout)
477  ::utils::tooltip::Set $w.buttons.stats $::tr(ShowHideStatistic)
478  ::utils::tooltip::Set $w.buttons.boardFilter $::tr(BoardFilter)
479  grid $w.buttons.database -row 0
480  grid $w.buttons.filter -row 1
481  grid $w.buttons.layout -row 2
482  grid $w.buttons.stats -row 3
483  grid $w.buttons.boardFilter -row 4
484  grid $w.buttons -row 0 -column 0 -sticky news
485 
486  ttk::frame $w.database -padding {0 5 6 2}
487  ::windows::switcher::Create $w.database $w
488  $w.database.border configure -borderwidth 2 -relief groove
489 
490  ttk::frame $w.filter -padding {4 5 6 0}
491  ttk::frame $w.filter.b
492  grid $w.filter.b -sticky news
493  grid rowconfigure $w.filter 0 -weight 1
494  grid columnconfigure $w.filter 0 -weight 1
495  ttk::button $w.filter.b.rfilter -image tb_rfilter \
496  -command "::windows::gamelist::filter_ $w r"
497  ttk::button $w.filter.b.bsearch -image tb_bsearch \
498  -command "::windows::gamelist::filter_ $w b"
499  ttk::button $w.filter.b.hsearch -image tb_hsearch \
500  -command "::windows::gamelist::filter_ $w h"
501  ttk::button $w.filter.b.msearch -image tb_msearch \
502  -command "::windows::gamelist::filter_ $w m"
503  ttk::button $w.filter.b.tmt -image tb_tmt \
504  -command ::tourney::toggle
505  ttk::button $w.filter.b.crosst -image tb_crosst \
506  -command ::crosstab::Open
507  #TODO: rewrite the tooltip system (most tooltip are not translated when you change language)
508  ::utils::tooltip::Set "$w.filter.b.rfilter" "$::helpMessage($::language,SearchReset)"
509  ::utils::tooltip::Set "$w.filter.b.bsearch" "$::helpMessage($::language,SearchCurrent)"
510  ::utils::tooltip::Set "$w.filter.b.hsearch" "$::helpMessage($::language,SearchHeader)"
511  ::utils::tooltip::Set "$w.filter.b.msearch" "$::helpMessage($::language,SearchMaterial)"
512  ::utils::tooltip::Set "$w.filter.b.tmt" "$::helpMessage($::language,WindowsTmt)"
513  ::utils::tooltip::Set "$w.filter.b.crosst" "$::helpMessage($::language,ToolsCross)"
514  grid $w.filter.b.rfilter
515  grid $w.filter.b.hsearch
516  grid $w.filter.b.bsearch
517  grid $w.filter.b.msearch
518  grid $w.filter.b.crosst
519 
520  ttk::frame $w.layout -padding {0 5 6 2}
521  ttk::frame $w.layout.b -borderwidth 2 -relief groove
522  grid $w.layout.b -sticky news
523  grid rowconfigure $w.layout 0 -weight 1
524  grid columnconfigure $w.layout 0 -weight 1
526 
527  ttk::frame $w.stats -padding {0 5 6 2}
528  ttk::frame $w.stats.b -borderwidth 2 -relief groove
529  grid $w.stats.b -sticky news
530  grid rowconfigure $w.stats 0 -weight 1
531  grid columnconfigure $w.stats 0 -weight 1
532  autoscrollframe -bars y $w.stats.b canvas $w.stats.b.c -highlightthickness 0 -background white
533 }
534 
535 proc ::windows::gamelist::createGList_ {{w}} {
536  if {[winfo exists $w.games]} { destroy $w.games}
537  ttk::frame $w.games -borderwidth 0 -padding {8 5 5 2}
538  glist.create $w.games "ly$w"
539  grid $w.games -row 0 -column 2 -sticky news
540 }
541 
542 proc ::windows::gamelist::menu_ {{w} {button}} {
543  if {$::gamelistMenu($w) != ""} {
544  $w.buttons.$::gamelistMenu($w) state !pressed
545  grid forget $w.$::gamelistMenu($w)
546  if {$button == "filter"} { event generate $w.games.find.hide <ButtonPress-1>}
547  }
548  if {$::gamelistMenu($w) != $button} {
549  $w.buttons.$button state pressed
550  set ::gamelistMenu($w) $button
551  grid $w.$button -row 0 -column 1 -sticky news
552  if {$button == "filter"} { event generate $w <Control-f>}
553  } else {
554  set ::gamelistMenu($w) ""
555  }
556 }
557 
558 proc ::windows::gamelist::filter_ {{w} {type}} {
559  if {$type == "r"} {
560  ::windows::gamelist::FilterReset $w $::gamelistBase($w)
561  } elseif {$type == "b"} {
562  ::search::board $::gamelistBase($w) $::gamelistFilter($w)
563  } elseif {$type == "h"} {
564  ::search::header $::gamelistBase($w) $::gamelistFilter($w)
565  } elseif {$type == "m"} {
566  ::search::material $::gamelistBase($w)
567  }
568 }
569 
570 proc ::windows::gamelist::update_ {{w} {moveUp}} {
571  set f $::gamelistFilter($w)
572  foreach {filterSz gameSz mainSz} [sc_filter sizes $::gamelistBase($w) $f] {}
573 
574  if {$gameSz == $mainSz} {
575  $w.buttons.filter configure -image tb_search_on
576  } else {
577  $w.buttons.filter configure -image tb_search_off
578  }
579 
580  set fr [::windows::gamelist::formatFilterText $filterSz $gameSz]
581  set fn [file tail [sc_base filename $::gamelistBase($w)]]
582  ::setTitle $w "$::gamelistTitle($w) $fn ($fr)"
583  if {$moveUp} {
584  #Reset double-click behavior
585  set ::glistClickOp($w.games.glist) 0
586  }
587  glist.update $w.games $::gamelistBase($w) $::gamelistFilter($w) $moveUp
588 }
589 
590 proc ::windows::gamelist::searchpos_ {{w}} {
591  if {$::gamelistPosMask($w) == 0} {
592  set ::gamelistPosMask($w) 1
593  $w.buttons.boardFilter state pressed
595  } else {
596  set ::gamelistPosMask($w) 0
597  $w.buttons.boardFilter state !pressed
598  set ::gamelistFilter($w) [sc_filter compose $::gamelistBase($w) $::gamelistFilter($w) ""]
599  $w.games.glist tag configure fsmall -foreground ""
600  $w.buttons.boardFilter configure -image tb_BoardMask
601  ::notify::DatabaseModified $::gamelistBase($w) $::gamelistFilter($w)
602  }
603 }
604 
605 proc ::windows::gamelist::filterRelease_ {{base} {filter}} {
606  set used 0
607  foreach win $::windows::gamelist::wins {
608  if { $::gamelistBase($win) == $base && $::gamelistFilter($win) == $filter } {
609  incr used
610  }
611  }
612  if {! $used} {
613  catch {sc_filter release $base $filter}
614  ::notify::DatabaseModified $base $filter
615  }
616 }
617 
618 proc ::windows::gamelist::updateStats_ { {w} } {
619  if {$::gamelistMenu($w) != "stats"} { return}
620  set stats {}
621  set stats [sc_filter treestats $::gamelistBase($w) $::gamelistFilter($w)]
622  set lineH [expr { round(1.8 * [font metrics font_Regular -linespace]) }]
623  set rectW [expr { round([font metrics font_Regular -ascent] *0.5) }]
624  set rectB [expr { [font metrics font_Regular -descent] + int($rectW*0.25) }]
625  set rectH [expr { $rectW + $rectB }]
626  incr rectW 4
627  set moveW 0
628  foreach move $stats {
629  set m [font measure font_Regular "..[lindex $move 0]"]
630  set n [font measure font_Italic [lindex $move 1]]
631  set s [expr { $m + $n + int($rectW*2) }]
632  if {$s > $moveW} { set moveW $s}
633  }
634  set barW [expr { $moveW + 6 }]
635  set percW [expr { [font measure font_Small 99%] / 2 }]
636  set winW [expr { $barW + 10 * $percW + 4 }]
637  if {[info exists ::gamelistLastTreeW($w)]} {
638  set diff [expr { $::gamelistLastTreeW($w) - $winW }]
639  if {$diff > -5 && $diff < [expr 4 * $rectW]} {
640  set winW $::gamelistLastTreeW($w)
641  incr barW $diff
642  incr moveW $diff
643  }
644  }
645  set ::gamelistLastTreeW($w) $winW
646  set coeff [expr $percW / 10.0]
647  set line $lineH
648  $w.stats.b.c delete all
649  set i_add 0
650  foreach move $stats {
651  set performance [lindex $move 5]
652  set n_ratedgames [lindex $move 6]
653  set toMove [lindex $move 7]
654  set pColor "#707070"
655  set perfCmd ""
656  if { $n_ratedgames > 5 } {
657  if { $toMove == "B" } { set performance [expr { $performance * -1 }]}
658  set rate [expr { $performance / $n_ratedgames }]
659  if { $rate > 0.1 } { set pColor "#47a148"}
660  if { $rate < -0.1 } { set pColor "#f40000"}
661  #TODO:
662  set perfCmd "tk_messageBox -message \"$rate ($performance / $n_ratedgames)\" "
663  }
664  $w.stats.b.c create rectangle 4 [expr { $line - $rectH }] $rectW [expr { $line -$rectB }] \
665  -fill $pColor -outline "" -tag perf$i_add
666  $w.stats.b.c bind perf$i_add <ButtonPress-1> "$perfCmd"
667 
668  set moveSAN [lindex $move 0]
669  $w.stats.b.c bind add$i_add <ButtonPress-1> "
670  if {\[addSanMove \{$moveSAN\}\] && \$::gamelistPosMask($w) == 0} {
671  $w.buttons.boardFilter invoke
672  }
673  "
674  if { $toMove == "B" } { set moveSAN "..$moveSAN"}
675  $w.stats.b.c create text [expr int($rectW*1.5)] $line -anchor sw \
676  -text $moveSAN -fill black -font font_Regular -tag add$i_add
677 
678  incr i_add
679  $w.stats.b.c create text $moveW $line -anchor se \
680  -text [lindex $move 1] -fill #707070 -font font_Italic
681  set barh1 [expr { $line - 2*$rectB }]
682  set barh2 [expr { $line - $rectB }]
683  set n_white [lindex $move 2]
684  set n_draw [lindex $move 3]
685  set n_black [lindex $move 4]
686  set n_tot [expr { $n_white + $n_draw + $n_black }]
687  if {$n_tot != 0} {
688  set p_white [expr { 100.0 * $n_white / $n_tot }]
689  set p_draw [expr { 100.0 * $n_draw / $n_tot }]
690  set p_black [expr { 100.0 - $p_white - $p_draw }]
691  if {$n_tot > 99} {
692  set t_white "[expr { round($p_white) }]%"
693  set t_draw "[expr { round($p_draw) }]%"
694  set t_black "[expr { round($p_black) }]%"
695  } else {
696  set t_white "$n_white "
697  set t_draw "$n_draw "
698  set t_black "$n_black "
699  }
700 
701  set win [expr { int($barW + $coeff * $p_white) }]
702  $w.stats.b.c create rectangle $barW $barh1 $win $barh2 -fill white -outline ""
703  set draw [expr { int($win + $coeff * $p_draw) }]
704  $w.stats.b.c create rectangle $win $barh1 $draw $barh2 -fill #707070 -outline ""
705  set loss [expr { $barW + $percW * 10 }]
706  $w.stats.b.c create rectangle $draw $barh1 $loss $barh2 -fill black -outline ""
707 
708  $w.stats.b.c create rectangle $barW $barh1 $loss $barh2
709 
710  $w.stats.b.c create text [expr { $barW + $percW * 3 }] $barh1 \
711  -font font_Small -anchor se -fill black -text "$t_white"
712  $w.stats.b.c create text [expr { $barW + $percW * 6 }] $barh1 \
713  -font font_Small -anchor se -fill black -text "$t_draw"
714  $w.stats.b.c create text [expr { $barW + $percW * 9 }] $barh1 \
715  -font font_Small -anchor se -fill black -text "$t_black"
716  }
717 
718  incr line $lineH
719  }
720  incr line -$lineH
721  $w.stats.b.c configure -scrollregion [list 0 0 $winW $line] -width $winW
722 }
723 
724 namespace eval ::glist_Ly {
725  proc Create {w} {
726  if {! [info exists ::glist_Layouts] } { set ::glist_Layouts {}}
727  options.save ::glist_Layouts
728  set ::gamelistNewLayout [::glist_Ly::createName_]
729  autoscrollframe -bars y $w.layout.b canvas $w.layout.b.c -highlightthickness 0
730  ::applyThemeColor_background $w.layout.b.c
731  bind $w.layout.b.c <Configure> { ::glist_Ly::AdjScrollbar_ %W }
733  }
734  proc UpdateAll_ {} {
735  foreach w $::windows::gamelist::wins {
736  if {[winfo exists $w]} { Update_ $w}
737  }
738  }
739  proc Update_ {w} {
740  if {[winfo exists $w.layout.b.c.f]} { destroy $w.layout.b.c.f}
741  ttk::frame $w.layout.b.c.f -padding 5
742  $w.layout.b.c create window 0 0 -window $w.layout.b.c.f -anchor nw
743  ttk::entry $w.layout.b.c.f.text_new -textvariable ::gamelistNewLayout -width 12
744  ttk::button $w.layout.b.c.f.new -image tb_new -command "::glist_Ly::New_ $w"
745  grid $w.layout.b.c.f.text_new $w.layout.b.c.f.new
746  ttk::frame $w.layout.b.c.f.sep -padding { 0 4 0 4 }
747  ttk::separator $w.layout.b.c.f.sep.line
748  grid rowconfigure $w.layout.b.c.f.sep 0 -weight 1
749  grid columnconfigure $w.layout.b.c.f.sep 0 -weight 1
750  grid $w.layout.b.c.f.sep.line -sticky news
751  grid $w.layout.b.c.f.sep -columnspan 2 -sticky we
752  for {set i 0} {$i < [llength $::glist_Layouts]} {incr i} {
753  set name [lindex $::glist_Layouts $i]
754  ttk::button $w.layout.b.c.f.layout$i -text $name -width 12 -command "::glist_Ly::Change_ $w $i"
755  ttk::button $w.layout.b.c.f.layoutDel$i -image tb_CC_delete -command "::glist_Ly::Del_ $w $i"
756  grid $w.layout.b.c.f.layout$i $w.layout.b.c.f.layoutDel$i -sticky we
757  }
758  after idle "::glist_Ly::AdjScrollbar_ $w.layout.b.c"
759  }
760  proc New_ {w} {
761  set newLy $::gamelistNewLayout
762  Copy_ $newLy ly$w
763  options.save ::glist_ColOrder($newLy)
764  options.save ::glist_ColWidth($newLy)
765  options.save ::glist_ColAnchor($newLy)
766  options.save ::glist_Sort($newLy)
767  options.save ::glist_FindBar($newLy)
768  set replaced [lsearch -exact $::glist_Layouts $newLy]
769  if {$replaced == -1 } { lappend ::glist_Layouts $newLy}
770  set ::gamelistNewLayout [::glist_Ly::createName_]
772  }
773  proc Del_ {w idx} {
774  set old_layout [lindex $::glist_Layouts $idx]
775  options.save_cancel ::glist_ColOrder($old_layout)
776  options.save_cancel ::glist_ColWidth($old_layout)
777  options.save_cancel ::glist_ColAnchor($old_layout)
778  options.save_cancel ::glist_Sort($old_layout)
779  options.save_cancel ::glist_FindBar($old_layout)
780  set ::glist_Layouts [lreplace $::glist_Layouts $idx $idx]
782  }
783  proc Change_ {w idx} {
784  Copy_ ly$w [lindex $::glist_Layouts $idx]
787  }
788  proc Copy_ {{oldLy} {newLy}} {
789  set ::glist_ColOrder($oldLy) $::glist_ColOrder($newLy)
790  set ::glist_ColWidth($oldLy) $::glist_ColWidth($newLy)
791  set ::glist_ColAnchor($oldLy) $::glist_ColAnchor($newLy)
792  set ::glist_Sort($oldLy) $::glist_Sort($newLy)
793  set ::glist_FindBar($oldLy) $::glist_FindBar($newLy)
794  }
795  proc createName_ {} {
796  set i 1
797  set prefix "NewLayout"
798  while {[lsearch -exact $::glist_Layouts "$prefix$i"] != -1} { incr i}
799  return "$prefix$i"
800  }
801  proc AdjScrollbar_ {w} {
802  set l [winfo reqwidth $w.f]
803  set h [winfo reqheight $w.f]
804  $w configure -scrollregion [list 0 0 $l $h] -width $l
805  }
806 }
807 
808 
809 
810 
811 
812 ##########################################################################
813 # June 2011: A new reusable and simplified gamelist widget
814 # glist.create
815 # Create a gamelist widget
816 # w: parent window of the gamelist widget
817 # layout: a string name that will be assigned to columns layout.
818 # layout will be saved and restored in successive glist.create calls.
819 proc glist.create {{w} {layout}} {
820  # Default values
821  if {! [info exists ::glist_ColOrder($layout)] } {
822  set ::glist_ColOrder($layout) {7 3 4 5 6 1 2 23 8 10 9 16 17 22 21 18 11 12 13 14 15 0}
823  }
824  if {! [info exists ::glist_ColWidth($layout)] } {
825  set ::glist_ColWidth($layout) {50 50 39 120 40 120 40 70 200 30 200 30 20 20 20 20 35 119 30 78 40 40 50 155}
826  }
827  if {! [info exists ::glist_ColAnchor($layout)] } {
828  set ::glist_ColAnchor($layout) {e c c w c w c w w e w c c c c c c c c c c c c w}
829  }
830  if {! [info exists ::glist_Sort($layout)] } {
831  set ::glist_Sort($layout) {0 +}
832  }
833  if {! [info exists ::glist_FindBar($layout)] } {
834  set ::glist_FindBar($layout) 0
835  }
836 
837  ttk::treeview $w.glist -style Gamelist.Treeview -columns $::glist_Headers -show headings -selectmode browse
838  $w.glist tag configure current -background lightSteelBlue
839  $w.glist tag configure fsmall -font font_Small
840  $w.glist tag configure deleted -foreground #a5a2ac
841  menu $w.glist.header_menu
842  menu $w.glist.header_menu.addcol
843  menu $w.glist.game_menu
844  bind $w.glist <Configure> {
845  set hWin [winfo height %W]
846  set hHeading 18
847  set space [expr double($hWin - $hHeading)]
848  set ::glistVisibleLn(%W) [expr int(ceil($space / $::glistRowHeight)) ]
849  after 100 "glist.loadvalues_ %W"
850  }
851  if {$::windowsOS} {
852  bind $w.glist <App> "glist.popupmenu_ %W %x %y %X %Y $layout"
853  } else {
854  bind $w.glist <Menu> "glist.popupmenu_ %W %x %y %X %Y $layout"
855  }
856  bind $w.glist <2> "glist.popupmenu_ %W %x %y %X %Y $layout"
857  bind $w.glist <3> "glist.popupmenu_ %W %x %y %X %Y $layout"
858  bind $w.glist <ButtonRelease-1> "glist.release_ %W %x %y $layout"
859  bind $w.glist <Double-ButtonRelease-1> "glist.doubleclick_ %W %x %y $layout"
860  bind $w.glist <KeyPress-Up> {glist.movesel_ %W prev -1 0; break}
861  bind $w.glist <KeyPress-Down> {glist.movesel_ %W next +1 end; break}
862  bind $w.glist <KeyPress-Right> {continue}
863  bind $w.glist <KeyPress-Left> {continue}
864  bind $w.glist <KeyPress-Prior> {glist.ybar_ %W scroll -1 pages; break}
865  bind $w.glist <KeyPress-Next> {glist.ybar_ %W scroll 1 pages; break}
866  bind $w.glist <KeyPress-Return> {
867  foreach {idx ply} [split [%W selection] "_"] {}
868  if {[info exists idx]} {
869  ::file::SwitchToBase $::glistBase(%W) 0
870  ::game::Load $idx $ply
871  }
872  break
873  }
874  bind $w.glist <Delete> {
875  foreach {idx ply} [split [%W selection] "_"] {}
876  if {[info exists idx]} {
877  glist.delflag_ %W $idx
878  glist.movesel_ %W next +1 end
879  }
880  break
881  }
882  bind $w.glist <Control-Delete> {
883  foreach {idx ply} [split [%W selection] "_"] {}
884  if {[info exists idx]} {
885  glist.movesel_ %W next +1 end;
886  sc_filter remove $::glistBase(%W) $::glistFilter(%W) $idx
887  ::notify::DatabaseModified $::glistBase(%W)
888  }
889  break
890  }
891  bind $w.glist <Destroy> "glist.destroy_ $w.glist"
892 
893  set i 0
894  foreach col $::glist_Headers {
895  $w.glist heading $col -text $::tr($col)
896  $w.glist column $col -stretch 0 \
897  -width [lindex $::glist_ColWidth($layout) $i]\
898  -anchor [lindex $::glist_ColAnchor($layout) $i]
899  incr i
900  }
901  $w.glist configure -displaycolumns $::glist_ColOrder($layout)
902 
903  autoscrollframe -bars both $w "" $w.glist
904  set ::glistYScroll($w.glist) [$w.glist cget -yscrollcommand]
905  $w.glist configure -yscrollcommand "glist.yscroll_ $w.glist"
906  $w.ybar configure -command "glist.ybar_ $w.glist"
907  bind $w.ybar <ButtonRelease-1> "+glist.ybar_ $w.glist buttonrelease"
908  bindMouseWheel $w.glist "glist.ybar_ $w.glist"
909 
910  # Find widget
911  ttk::frame $w.find
912  ttk::label $w.find.hide -image "tb_close hover tb_close_hover"
913  bind $w.find.hide <ButtonPress-1> "set ::glist_FindBar($layout) 0; glist.showfindbar_ $w.glist $layout"
914  ttk::frame $w.find.t
915  ttk::label $w.find.t_text -text $::tr(Search)
916  entry $w.find.text -width 20 -bg white
917  ttk::button $w.find.filter -image tb_search16 -command "glist.findgame_ $w awe"
918  ttk::button $w.find.b1_text -image tb_down -command \
919  "after cancel glist.findgame_ $w 1; after idle glist.findgame_ $w 1"
920  ttk::button $w.find.b2_text -image tb_up -command \
921  "after cancel glist.findgame_ $w 0; after idle glist.findgame_ $w 0"
922  bind $w.find.text <Escape> "set ::glist_FindBar($layout) 0; glist.showfindbar_ $w.glist $layout"
923  bind $w.find.text <Return> "$w.find.filter invoke"
924  bind $w.find.text <KeyPress-Down> "$w.find.b1_text invoke; break"
925  bind $w.find.text <KeyPress-Up> "$w.find.b2_text invoke; break"
926  #TODO: -from 0 -to 100
927  #TODO: set scale position when normal ybar is used
928  ttk::scale $w.find.scale -command "glist.ybar_ $w.glist moveto"
929  grid $w.find.t_text $w.find.text $w.find.filter $w.find.b2_text $w.find.b1_text -in $w.find.t -padx 2
930  grid $w.find.hide
931  grid $w.find.t -row 0 -column 1 -padx 6
932  grid $w.find.scale -row 0 -column 3 -sticky ew
933  grid columnconfigure $w.find 3 -weight 1
934  set ::glistFindBar($w.glist) $w.find
935  glist.showfindbar_ $w.glist $layout
936  bind [winfo toplevel $w] <Control-f> "set ::glist_FindBar($layout) 1; glist.showfindbar_ $w.glist $layout"
937 
938  # On exit save layout in options.dat
939  options.save ::glist_ColOrder($layout)
940  options.save ::glist_ColWidth($layout)
941  options.save ::glist_ColAnchor($layout)
942  options.save ::glist_Sort($layout)
943  options.save ::glist_FindBar($layout)
944 
945  set ::glistLoaded($w.glist) 0
946  set ::glistTotal($w.glist) 0
947  set ::glistVisibleLn($w.glist) 0
948  glist.sortInit_ $w.glist $layout
949 }
950 
951 # glist.update
952 # Retrieve values from database and update the widget
953 # w: the parent windows of the widget that was passed to glist.create
954 # base: the database from which retrieve values
955 # filter: returns only values in the specified filter
956 # moveUp: reset glist to show the first results
957 proc glist.update {{w} {base} {filter} {moveUp 1}} {
958  set w $w.glist
959  if {! [winfo exists $w]} { return}
960 
961  set ::glistFilter($w) $filter
962  set ::glistTotal($w) [sc_filter count $base $filter]
963  if {$moveUp == 1} { set ::glistFirst($w) 0}
964 
965  glist.update_ $w $base
966 }
967 
968 
969 ##########################################################################
970 #private:
971 
972 set glist_Headers {"GlistNumber" "GlistResult" "GlistLength" "GlistWhite" "GlistWElo"
973  "GlistBlack" "GlistBElo" "GlistDate" "GlistEvent" "GlistRound"
974  "GlistSite" "GlistAnnos" "GlistComments" "GlistVars" "GlistDeleted"
975  "GlistFlags" "GlistECO" "GlistEndMaterial" "GlistStart" "GlistEDate"
976  "GlistYear" "GlistAverageElo" "GlistRating" "GlistMoveField" }
977 
978 set glist_DefaultOrder {+ + - + - + - - + + + - - - - - + + - - - - - +}
979 
980 set glist_SortShortcuts { "N" "r" "m" "w" "W"
981  "b" "B" "d" "e" "n"
982  "s" "A" "C" "V" "D"
983  "???" "o" "???" "???" "E"
984  "y" "R" "i" "???" }
985 
986 proc glist.destroy_ {{w}} {
987  if {[info exists ::glistSortCache($w)]} {
988  catch { sc_base sortcache $::glistBase($w) release $::glistSortCache($w)}
989  unset ::glistSortCache($w)
990  }
991  unset ::glistSortStr($w)
992  catch { unset ::glistBase($w)}
993  catch { unset ::glistFilter($w)}
994  catch { unset ::glistFirst($w)}
995  catch { unset ::glistClickOp($w)}
996  catch { unset ::glistVisibleLn($w)}
997  unset ::glistLoaded($w)
998  unset ::glistTotal($w)
999  unset ::glistYScroll($w)
1000  unset ::glistFindBar($w)
1001 }
1002 
1003 proc glist.update_ {{w} {base}} {
1004  if {! [info exists ::glistBase($w)] } {
1005  #Create a sortcache to speed up sorting
1006  sc_base sortcache $base create $::glistSortStr($w)
1007  set ::glistFirst($w) 0
1008  } elseif {$::glistBase($w) != $base || $::glistSortCache($w) != $::glistSortStr($w)} {
1009  #Create a new sortcache
1010  catch { sc_base sortcache $::glistBase($w) release $::glistSortCache($w)}
1011  sc_base sortcache $base create $::glistSortStr($w)
1012  set ::glistFirst($w) 0
1013  }
1014  set ::glistSortCache($w) $::glistSortStr($w)
1015  set ::glistBase($w) $base
1017 }
1018 
1019 proc glist.loadvalues_ {{w}} {
1020  set sel [$w selection]
1021  $w delete [$w children {}]
1022  set base $::glistBase($w)
1023  if {$base == [sc_base current]} {
1024  set current_game [sc_game number]
1025  } else {
1026  set current_game -1
1027  }
1028  set i 0
1029  foreach {idx line deleted} [sc_base gameslist $base $::glistFirst($w) $::glistVisibleLn($w)\
1030  $::glistFilter($w) $::glistSortStr($w)] {
1031  if {[lindex $line 1] == "=-="} { set line [lreplace $line 1 1 "\u00BD-\u00BD"]}
1032  $w insert {} end -id $idx -values $line -tag fsmall
1033  if {$deleted == "D"} { $w item $idx -tag {fsmall deleted}}
1034  foreach {n ply} [split $idx "_"] {
1035  if {$n == $current_game} { $w item $idx -tag "[$w item $idx -tag] current"}
1036  }
1037  incr i
1038  }
1039  set ::glistLoaded($w) $i
1040  catch {$w selection set $sel}
1041 
1043 }
1044 
1045 proc glist.showfindbar_ {{w} {layout}} {
1046  if {$::glist_FindBar($layout) == 0} {
1047  grid forget $::glistFindBar($w)
1048  focus $w
1049  } else {
1050  grid $::glistFindBar($w) -row 2 -columnspan 2 -sticky news
1051  focus $::glistFindBar($w).text
1052  $::glistFindBar($w).text selection range 0 end
1053  }
1054 }
1055 
1056 proc glist.findcurrentgame_ {{w} {gnum}} {
1057  set r [sc_base gamelocation $::glistBase($w) $::glistFilter($w) $::glistSortStr($w) $gnum]
1058  if {$r != "none"} {
1059  set ::glistFirst($w) $r
1060  glist.ybar_ $w scroll
1061  }
1062 }
1063 
1064 proc glist.findgame_ {{w_parent} {dir}} {
1065  set w $w_parent.glist
1066  set w_entryT $w_parent.find.text
1067  set txt [$w_entryT get]
1068  $w_entryT configure -bg white
1069  if { $dir == "awe" } {
1070  ::windows::gamelist::Awesome [winfo toplevel $w_parent] "$txt"
1071  $w_entryT selection range 0 end
1072  return
1073  }
1074  if { $txt == "" } { return}
1075  busyCursor $w_parent
1076  update idletasks
1077 
1078  if { [string is integer $txt] } {
1079  set r [sc_base gamelocation $::glistBase($w) $::glistFilter($w) $::glistSortStr($w) $txt]
1080  } else {
1081  set gstart [expr int($::glistFirst($w))]
1082  foreach {n ply} [split [$w selection] "_"] {
1083  if {$n != ""} {
1084  set gstart [sc_base gamelocation $::glistBase($w) $::glistFilter($w) $::glistSortStr($w) $n]
1085  }
1086  }
1087  if {$dir == "1"} { incr gstart}
1088  set r [sc_base gamelocation $::glistBase($w) $::glistFilter($w) $::glistSortStr($w) 0\
1089  $txt $gstart $dir]
1090  }
1091  if {$r == "none"} {
1092  $w_entryT configure -bg red
1093  } else {
1094  if {$r >= [expr $::glistFirst($w) + $::glistVisibleLn($w)] || $r < $::glistFirst($w)} {
1095  set ::glistFirst($w) $r
1096  glist.ybar_ $w scroll
1097  }
1098  after idle glist.select_ $w [expr $r +1]
1099  }
1100  unbusyCursor $w_parent
1101 }
1102 
1103 proc glist.select_ {w {idx 0}} {
1104  if {$idx != "end" && $idx > 0} {
1105  set idx [expr int($idx - $::glistFirst($w) -1)]
1106  }
1107  $w selection set [lindex [$w children {}] $idx]
1108 }
1109 
1110 proc glist.movesel_ {{w} {cmd} {scroll} {select}} {
1111  set sel [$w selection]
1112  if {$sel == ""} { glist.select_ $w; return}
1113  set newsel [$w $cmd $sel]
1114  if {$newsel == "" || [$w bbox $newsel] == ""} {
1115  glist.ybar_ $w scroll $scroll
1116  }
1117  if {$newsel == ""} {
1118  after idle glist.select_ $w $select
1119  } else {
1120  $w selection set $newsel
1121  }
1122 }
1123 
1124 proc glist.delflag_ {{w} {idx}} {
1125  sc_base gameflag $::glistBase($w) $idx invert del
1126  ::notify::DatabaseModified $::glistBase($w)
1127 }
1128 
1129 proc glist.doubleclick_ {{w} {x} {y} {layout}} {
1130  lassign [$w identify $x $y] what
1131  if {$what == "heading"} {
1132  glist.sortClickHandle_ $w $x $y $layout 1
1133  } else {
1134  foreach {idx ply} [split [$w identify item $x $y] "_"] {}
1135  if {[info exists idx]} {
1136  if {[info exists ::glistClickOp($w)]} {
1137  if {$::glistClickOp($w) == 1} {
1138  glist.delflag_ $w $idx;
1139  $w selection set {};
1140  return
1141  }
1142  if {$::glistClickOp($w) == 2} {
1143  glist.removeFromFilter_ $w $idx
1144  return
1145  }
1146  }
1147  ::file::SwitchToBase $::glistBase($w) 0
1148  ::game::Load $idx $ply
1149  }
1150  }
1151 }
1152 
1153 proc glist.removeFromFilter_ {{w} {idx} {dir ""}} {
1154  if {$dir == ""} {
1155  sc_filter remove $::glistBase($w) $::glistFilter($w) $idx
1156  } else {
1157  sc_filter remove $::glistBase($w) $::glistFilter($w) $idx $dir $::glistSortStr($w)
1158  }
1159  ::notify::DatabaseModified $::glistBase($w) $::glistFilter($w)
1160  if {$dir == "+"} { glist.ybar_ $w moveto 1}
1161 }
1162 
1163 proc glist.popupmenu_ {{w} {x} {y} {abs_x} {abs_y} {layout}} {
1164 # identify region requires at least tk 8.5.9
1165 # identify row have scrollbar problems
1166  if { 0 != [catch {set region [$w identify region $x $y]}] } {
1167  if {[$w identify row $x $y] == "" } {
1168  set region "heading"
1169  } else {
1170  set region ""
1171  }
1172  }
1173  if { $region != "heading" } {
1174 # if {[$w identify region $x $y] != "heading" }
1175  event generate $w <ButtonPress-1> -x $x -y $y
1176  foreach {idx ply} [split [$w selection] "_"] {}
1177  if {[info exists idx]} {
1178  if { [winfo exists $w.game_menu.merge] } { destroy $w.game_menu.merge}
1179  if { [winfo exists $w.game_menu.copy] } { destroy $w.game_menu.copy}
1180  if { [winfo exists $w.game_menu.filter] } { destroy $w.game_menu.filter}
1181  $w.game_menu delete 0 end
1182  #LOAD/BROWSE/MERGE GAME
1183  $w.game_menu add command -label $::tr(LoadGame) \
1184  -command "::file::SwitchToBase $::glistBase($w) 0; ::game::Load $idx $ply"
1185  $w.game_menu add command -label $::tr(BrowseGame) \
1186  -command "::gbrowser::new $::glistBase($w) $idx $ply"
1187  $w.game_menu add command -label $::tr(MergeGame) \
1188  -command "mergeGame $::glistBase($w) $idx"
1189  menu $w.game_menu.merge
1190  menu $w.game_menu.copy
1191  foreach i [sc_base list] {
1192  if { $i == $::glistBase($w) || [sc_base isReadOnly $i] } { continue}
1193  set fname [file tail [sc_base filename $i]]
1194  $w.game_menu.merge add command -label "$i $fname" -command "::game::mergeInBase $::glistBase($w) $i $idx"
1195  $w.game_menu.copy add command -label "$i $fname" \
1196  -command "sc_base copygames $::glistBase($w) $idx $i; ::notify::DatabaseModified $i"
1197  }
1198  $w.game_menu add cascade -label $::tr(GlistMergeGameInBase) -menu $w.game_menu.merge
1199  $w.game_menu add cascade -label $::tr(CopyGameTo) -menu $w.game_menu.copy
1200 
1201  #GOTO GAME
1202  $w.game_menu add separator
1203  $w.game_menu add checkbutton -variable ::glist_FindBar($layout) \
1204  -label $::tr(FindBar) -command "glist.showfindbar_ $w $layout"
1205  if {$::glistBase($w) == [sc_base current] && [sc_game number] != 0} {
1206  $w.game_menu add command -label $::tr(FindCurrentGame) -command "glist.findcurrentgame_ $w [sc_game number]"
1207  } else {
1208  $w.game_menu add command -label $::tr(FindCurrentGame) -state disabled
1209  }
1210  $w.game_menu add separator
1211  menu $w.game_menu.filter
1212  $w.game_menu.filter add command -label $::tr(Export) -command "::windows::gamelist::FilterExport [winfo toplevel $w]"
1213  $w.game_menu.filter add separator
1214  $w.game_menu.filter add command -label [tr SearchReset] \
1215  -command "::windows::gamelist::FilterReset [winfo toplevel $w] $::glistBase($w)"
1216  $w.game_menu.filter add command -label [tr SearchNegate] \
1217  -command "::windows::gamelist::FilterNegate [winfo toplevel $w] $::glistBase($w)"
1218  $w.game_menu.filter add separator
1219  $w.game_menu.filter add command -label $::tr(GlistRemoveGameAndAboveFromFilter) \
1220  -command "glist.removeFromFilter_ $w $idx -"
1221  $w.game_menu.filter add command -label $::tr(GlistRemoveThisGameFromFilter) \
1222  -command "glist.removeFromFilter_ $w $idx"
1223  $w.game_menu.filter add command -label $::tr(GlistRemoveGameAndBelowFromFilter) \
1224  -command "glist.removeFromFilter_ $w $idx +"
1225  $w.game_menu.filter add separator
1226  $w.game_menu.filter add command -label $::tr(GlistDeleteAllGames) \
1227  -command "sc_base gameflag $::glistBase($w) $::glistFilter($w) set del; ::notify::DatabaseModified $::glistBase($w)"
1228  $w.game_menu.filter add command -label $::tr(GlistUndeleteAllGames) \
1229  -command "sc_base gameflag $::glistBase($w) $::glistFilter($w) unset del; ::notify::DatabaseModified $::glistBase($w)"
1230  $w.game_menu add cascade -label $::tr(Filter) -menu $w.game_menu.filter
1231  $w.game_menu add separator
1232  set dellabel $::tr(DeleteGame)
1233  if {[sc_base gameflag $::glistBase($w) $idx get del]} { set dellabel $::tr(UndeleteGame)}
1234  $w.game_menu add command -label $dellabel -command "glist.delflag_ $w $idx; $w selection set {};"
1235  tk_popup $w.game_menu $abs_x $abs_y
1236  }
1237  } else {
1238  set col [$w identify column $x $y]
1239  set col_idx [lsearch -exact $::glist_Headers [$w column $col -id]]
1240  $w.header_menu delete 0 end
1241 
1242  #CHANGE ALIGNMENT
1243  set cur_a [lindex $::glist_ColAnchor($layout) $col_idx]
1244  if {$cur_a != "w"} {
1245  $w.header_menu add command -label $::tr(GlistAlignL) \
1246  -command "$w column $col -anchor w; lset ::glist_ColAnchor($layout) $col_idx w"
1247  }
1248  if {$cur_a != "e"} {
1249  $w.header_menu add command -label $::tr(GlistAlignR) \
1250  -command "$w column $col -anchor e; lset ::glist_ColAnchor($layout) $col_idx e"
1251  }
1252  if {$cur_a != "c"} {
1253  $w.header_menu add command -label $::tr(GlistAlignC) \
1254  -command "$w column $col -anchor c; lset ::glist_ColAnchor($layout) $col_idx c"
1255  }
1256 
1257  #ADD/REMOVE COLUMN
1258  $w.header_menu add separator
1259  $w.header_menu.addcol delete 0 end
1260  set empty disabled
1261  set i 0
1262  foreach h $::glist_Headers {
1263  if {[lsearch -exact $::glist_ColOrder($layout) $i] == -1} {
1264  set empty normal
1265  $w.header_menu.addcol add command -label $::tr($h) -command "glist.insertcol_ $w $layout $i $col"
1266  }
1267  incr i
1268  }
1269  $w.header_menu add cascade -label $::tr(GlistAddField) -menu $w.header_menu.addcol -state $empty
1270  $w.header_menu add command -label $::tr(GlistDeleteField) -command "glist.removecol_ $w $layout $col"
1271 
1272  #RESET SORT
1273  $w.header_menu add separator
1274  $w.header_menu add command -label $::tr(ResetSort) -command "glist.sort_ $w 0 $layout 1"
1275 
1276  #BARS
1277  $w.header_menu add separator
1278  $w.header_menu add checkbutton -variable ::glist_FindBar($layout) \
1279  -label $::tr(FindBar) -command "glist.showfindbar_ $w $layout"
1280 
1281  tk_popup $w.header_menu $abs_x $abs_y
1282  }
1283 }
1284 
1285 # Sorting
1286 proc glist.sortInit_ {w {layout}} {
1287  set ::glistSortStr($w) ""
1288  set i 0
1289  foreach {c dir} $::glist_Sort($layout) {
1290  set arrow_idx [expr $i *2]
1291  if {$dir == "-"} { incr arrow_idx}
1292  $w heading $c -image ::glist_Arrows($arrow_idx)
1293  append ::glistSortStr($w) [lindex $::glist_SortShortcuts $c] $dir
1294  incr i
1295  }
1296 }
1297 
1298 proc glist.sortClickHandle_ {{w} {x} {y} {layout} {clear 0}} {
1299  set col [$w identify column $x $y]
1300  set col_idx [lsearch -exact $::glist_Headers [$w column $col -id]]
1301  if {"???" == [lindex $::glist_SortShortcuts $col_idx]} {
1302  # TODO: notify the user that the column cannot be used for sorting
1303  return
1304  }
1305  glist.sort_ $w $col_idx $layout $clear
1306 }
1307 
1308 proc glist.sort_ {{w} {col_idx} {layout} {clear 0}} {
1309  if {[lindex $::glist_Sort($layout) 0] == 0 && $col_idx != 0} { set clear 1; }
1310  if {$clear} {
1311  foreach {c dir} $::glist_Sort($layout) { $w heading $c -image ""}
1312  set ::glist_Sort($layout) {}
1313  }
1314 
1315  set exists [lsearch -exact $::glist_Sort($layout) $col_idx]
1316  if {$exists == -1} {
1317  set order [lindex $::glist_DefaultOrder $col_idx]
1318  lappend ::glist_Sort($layout) $col_idx $order
1319  } else {
1320  incr exists
1321  if {[lindex $::glist_Sort($layout) $exists] == "+"} {
1322  lset ::glist_Sort($layout) $exists {-}
1323  } else {
1324  lset ::glist_Sort($layout) $exists {+}
1325  }
1326  }
1327  busyCursor $w
1328  update idletasks
1329  glist.sortInit_ $w $layout
1330  set file [sc_base filename $::glistBase($w)]
1331  if {[info exists ::recentSort]} {
1332  set idx [lsearch -exact $::recentSort "$file"]
1333  if {$idx != -1} {
1334  set ::recentSort [lreplace $::recentSort $idx [expr $idx +1]]
1335  }
1336  while {[llength $::recentSort] > 20} {
1337  set ::recentSort [lreplace $::recentSort 0 1]
1338  }
1339  }
1340  lappend ::recentSort "$file" "$::glist_Sort($layout)"
1341  glist.update_ $w $::glistBase($w)
1342  unbusyCursor $w
1343 }
1344 
1345 # Scrollbar
1346 proc glist.ybar_ {w cmd {n 0} {units ""}} {
1347  if { $cmd == "-1" || $cmd == "+1" } {
1348  #MouseWheel
1349  set n $cmd
1350  set units "units"
1351  set cmd scroll
1352  }
1353  if { $cmd == "scroll" || $cmd == "moveto"} {
1354  if {$cmd == "moveto"} {
1355  set ::glistFirst($w) [expr int(ceil($n * $::glistTotal($w)))]
1356  } else {
1357  if {$units == "pages"} {
1358  set ::glistFirst($w) [expr $::glistFirst($w) + $n * ($::glistVisibleLn($w) -1)]
1359  } else {
1360  set ::glistFirst($w) [expr $::glistFirst($w) + $n]
1361  }
1362  }
1363 
1364  set d [expr $::glistTotal($w) - $::glistVisibleLn($w) +1]
1365  if {$::glistFirst($w) > $d } { set ::glistFirst($w) $d}
1366  if { $::glistFirst($w) < 0 } { set ::glistFirst($w) 0}
1367 
1368  after cancel glist.loadvalues_ $w
1369  after idle glist.loadvalues_ $w
1370  }
1371 }
1372 
1373 proc glist.ybarupdate_ {w} {
1374  if { $::glistLoaded($w) != $::glistTotal($w) } {
1375  set first [expr double($::glistFirst($w)) / $::glistTotal($w)]
1376  set last [expr double($::glistFirst($w) + $::glistVisibleLn($w)) / $::glistTotal($w)]
1377  eval $::glistYScroll($w) $first $last
1378  }
1379 }
1380 
1381 proc glist.yscroll_ {w first last} {
1382  if { $::glistLoaded($w) == $::glistTotal($w) } {
1383  eval $::glistYScroll($w) $first $last
1384  }
1385 }
1386 
1387 #Drag and drop and changes in column's layout
1388 proc glist.insertcol_ {{w} {layout} {col} {after}} {
1389  set b [expr [string trimleft $after {#}]]
1390  set ::glist_ColOrder($layout) [linsert $::glist_ColOrder($layout) $b $col]
1391  $w configure -displaycolumns $::glist_ColOrder($layout)
1392 }
1393 
1394 proc glist.removecol_ {{w} {layout} {col}} {
1395  set d [expr [string trimleft $col {#}] -1]
1396  set ::glist_ColOrder($layout) [lreplace $::glist_ColOrder($layout) $d $d]
1397  $w configure -displaycolumns $::glist_ColOrder($layout)
1398 }
1399 
1400 proc glist.release_ {{w} {x} {y} {layout}} {
1401  switch $::ttk::treeview::State(pressMode) {
1402  resize {
1403  set col_id [$w column $::ttk::treeview::State(resizeColumn) -id]
1404  set i [lsearch -exact $::glist_Headers $col_id]
1405  if {$i != -1} {
1406  lset ::glist_ColWidth($layout) $i [$w column $::ttk::treeview::State(resizeColumn) -width]
1407  }
1408  }
1409  heading {
1410  lassign [$w identify $x $y] what
1411  if {$what == "heading"} {
1412  set new_col [$w identify column $x $y]
1413  set from [expr [string trimleft $::ttk::treeview::State(heading) {#}] -1]
1414  set to [expr [string trimleft $new_col {#}] -1]
1415  set val [lindex $::glist_ColOrder($layout) $from]
1416  if {$from != $to} {
1417  set ::glist_ColOrder($layout) [lreplace $::glist_ColOrder($layout) $from $from]
1418  set ::glist_ColOrder($layout) [linsert $::glist_ColOrder($layout) $to $val]
1419  $w configure -displaycolumns $::glist_ColOrder($layout)
1420  } else {
1421  glist.sortClickHandle_ $w $x $y $layout
1422  }
1423  }
1424  }
1425  }
1426  ttk::treeview::Release $w $x $y
1427 }
1428 
1429 image create bitmap ::glist_Arrows(0) -foreground blue -data {
1430  #define arrows_width 12
1431  #define arrows_height 10
1432  static unsigned char arrows_bits[] = {
1433  0x00, 0x00, 0x00, 0x03, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x10, 0x02,
1434  0x38, 0x07, 0x7c, 0x00, 0xfe, 0x00, 0x00, 0x00 };
1435 }
1436 image create bitmap ::glist_Arrows(1) -foreground blue -data {
1437  #define arrows_width 12
1438  #define arrows_height 10
1439  static unsigned char arrows_bits[] = {
1440  0x00, 0x00, 0x00, 0x03, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0xfe, 0x02,
1441  0x7c, 0x07, 0x38, 0x00, 0x10, 0x00, 0x00, 0x00 };
1442 }
1443 image create bitmap ::glist_Arrows(2) -foreground blue -data {
1444  #define arrows_width 12
1445  #define arrows_height 10
1446  static unsigned char arrows_bits[] = {
1447  0x00, 0x00, 0x80, 0x03, 0x00, 0x04, 0x00, 0x04, 0x00, 0x02, 0x08, 0x01,
1448  0x9c, 0x07, 0x3e, 0x00, 0x7f, 0x00, 0x00, 0x00 };
1449 }
1450 image create bitmap ::glist_Arrows(3) -foreground blue -data {
1451  #define arrows_width 12
1452  #define arrows_height 10
1453  static unsigned char arrows_bits[] = {
1454  0x00, 0x00, 0x80, 0x03, 0x00, 0x04, 0x00, 0x04, 0x00, 0x02, 0x7f, 0x01,
1455  0xbe, 0x07, 0x1c, 0x00, 0x08, 0x00, 0x00, 0x00 };
1456 }
1457 image create bitmap ::glist_Arrows(4) -foreground blue -data {
1458  #define arrows_width 12
1459  #define arrows_height 10
1460  static unsigned char arrows_bits[] = {
1461  0x00, 0x00, 0x80, 0x03, 0x00, 0x04, 0x00, 0x04, 0x00, 0x03, 0x08, 0x04,
1462  0x9c, 0x07, 0x3e, 0x00, 0x7f, 0x00, 0x00, 0x00 };
1463 }
1464 image create bitmap ::glist_Arrows(5) -foreground blue -data {
1465  #define arrows_width 12
1466  #define arrows_height 10
1467  static unsigned char arrows_bits[] = {
1468  0x00, 0x00, 0x80, 0x03, 0x00, 0x04, 0x00, 0x04, 0x00, 0x03, 0x7f, 0x04,
1469  0xbe, 0x03, 0x1c, 0x00, 0x08, 0x00, 0x00, 0x00 };
1470 }
1471 image create bitmap ::glist_Arrows(6) -foreground blue -data {
1472  #define arrows_width 12
1473  #define arrows_height 10
1474  static unsigned char arrows_bits[] = {
1475  0x00, 0x00, 0x00, 0x02, 0x00, 0x03, 0x00, 0x03, 0x80, 0x02, 0x88, 0x07,
1476  0x1c, 0x02, 0x3e, 0x00, 0x7f, 0x00, 0x00, 0x00 };
1477 }
1478 image create bitmap ::glist_Arrows(7) -foreground blue -data {
1479  #define arrows_width 12
1480  #define arrows_height 10
1481  static unsigned char arrows_bits[] = {
1482  0x00, 0x00, 0x00, 0x02, 0x00, 0x03, 0x00, 0x03, 0x80, 0x02, 0xff, 0x07,
1483  0x3e, 0x02, 0x1c, 0x00, 0x08, 0x00, 0x00, 0x00 };
1484 }
1485 image create bitmap ::glist_Arrows(8) -foreground blue -data {
1486  #define arrows_width 12
1487  #define arrows_height 10
1488  static unsigned char arrows_bits[] = {
1489  0x00, 0x00, 0x80, 0x07, 0x80, 0x00, 0x80, 0x03, 0x00, 0x04, 0x08, 0x04,
1490  0x9c, 0x03, 0x3e, 0x00, 0x7f, 0x00, 0x00, 0x00 };
1491 }
1492 image create bitmap ::glist_Arrows(9) -foreground blue -data {
1493  #define arrows_width 12
1494  #define arrows_height 10
1495  static unsigned char arrows_bits[] = {
1496  0x00, 0x00, 0x80, 0x07, 0x80, 0x00, 0x80, 0x03, 0x00, 0x04, 0x7f, 0x04,
1497  0xbe, 0x03, 0x1c, 0x00, 0x08, 0x00, 0x00, 0x00 };
1498 }
1499 image create bitmap ::glist_Arrows(10) -foreground blue -data {
1500  #define arrows_width 12
1501  #define arrows_height 10
1502  static unsigned char arrows_bits[] = {
1503  0x00, 0x00, 0x00, 0x07, 0x80, 0x00, 0x80, 0x00, 0x80, 0x03, 0x88, 0x04,
1504  0x1c, 0x07, 0x3e, 0x00, 0x7f, 0x00, 0x00, 0x00 };
1505 }
1506 image create bitmap ::glist_Arrows(11) -foreground blue -data {
1507  #define arrows_width 12
1508  #define arrows_height 10
1509  static unsigned char arrows_bits[] = {
1510  0x00, 0x00, 0x00, 0x07, 0x80, 0x01, 0x80, 0x00, 0x80, 0x07, 0xff, 0x04,
1511  0x3e, 0x03, 0x1c, 0x00, 0x08, 0x00, 0x00, 0x00 };
1512 }
1513 image create bitmap ::glist_Arrows(12) -foreground blue -data {
1514  #define arrows_width 12
1515  #define arrows_height 10
1516  static unsigned char arrows_bits[] = {
1517  0x00, 0x00, 0x80, 0x07, 0x00, 0x04, 0x00, 0x02, 0x00, 0x02, 0x08, 0x02,
1518  0x1c, 0x01, 0x3e, 0x00, 0x7f, 0x00, 0x00, 0x00 };
1519 }
1520 image create bitmap ::glist_Arrows(13) -foreground blue -data {
1521  #define arrows_width 12
1522  #define arrows_height 10
1523  static unsigned char arrows_bits[] = {
1524  0x00, 0x00, 0x80, 0x07, 0x00, 0x04, 0x00, 0x02, 0x00, 0x02, 0x7f, 0x02,
1525  0x3e, 0x01, 0x1c, 0x00, 0x08, 0x00, 0x00, 0x00 };
1526 }
1527 image create bitmap ::glist_Arrows(14) -foreground blue -data {
1528  #define arrows_width 12
1529  #define arrows_height 10
1530  static unsigned char arrows_bits[] = {
1531  0x00, 0x00, 0x00, 0x03, 0x80, 0x04, 0x80, 0x04, 0x00, 0x03, 0x88, 0x04,
1532  0x9c, 0x07, 0x3e, 0x00, 0x7f, 0x00, 0x00, 0x00 };
1533 }
1534 image create bitmap ::glist_Arrows(15) -foreground blue -data {
1535  #define arrows_width 12
1536  #define arrows_height 10
1537  static unsigned char arrows_bits[] = {
1538  0x00, 0x00, 0x00, 0x03, 0x80, 0x04, 0x80, 0x04, 0x00, 0x03, 0xff, 0x04,
1539  0xbe, 0x07, 0x1c, 0x00, 0x08, 0x00, 0x00, 0x00 };
1540 }
1541 image create bitmap ::glist_Arrows(16) -foreground blue -data {
1542  #define arrows_width 12
1543  #define arrows_height 10
1544  static unsigned char arrows_bits[] = {
1545  0x00, 0x00, 0x00, 0x03, 0x80, 0x04, 0x80, 0x07, 0x00, 0x04, 0x08, 0x06,
1546  0x9c, 0x03, 0x3e, 0x00, 0x7f, 0x00, 0x00, 0x00 };
1547 }
1548 image create bitmap ::glist_Arrows(17) -foreground blue -data {
1549  #define arrows_width 12
1550  #define arrows_height 10
1551  static unsigned char arrows_bits[] = {
1552  0x00, 0x00, 0x00, 0x03, 0x80, 0x04, 0x80, 0x07, 0x00, 0x04, 0x7f, 0x06,
1553  0xbe, 0x03, 0x1c, 0x00, 0x08, 0x00, 0x00, 0x00 };
1554 }
1555 
1556 ##########################################################################