root/05/release-0.5i/kombilo.py

Revision 59, 255.1 kB (checked in by ug, 5 years ago)

Removed M2-B1 bindings. Set new version number (0.5i)

  • Property svn:executable set to
<
Line 
1 #! /usr/bin/env python
2 # File: kombilo.py
3
4 ##   Copyright (C) 2001-4 Ulrich Goertz (u@g0ertz.de)
5
6 ##   Kombilo 0.5i is a go database program.
7
8 ##   This program 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; either version 2 of the License, or
11 ##   (at your option) any later version.
12
13 ##   This program is distributed in the hope that it will be useful,
14 ##   but WITHOUT ANY WARRANTY; without even the implied warranty of
15 ##   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 ##   GNU General Public License for more details.
17
18 ##   You should have received a copy of the GNU General Public License
19 ##   along with this program (see doc/license.txt); if not, write to the Free Software
20 ##   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
21 ##   The GNU GPL is also currently available at
22 ##   http://www.gnu.org/copyleft/gpl.html
23
24 from Tkinter import *
25 from tkMessageBox import *
26 from ScrolledText import ScrolledText
27 import tkFileDialog
28 # from tkFileDialog import askdirectory
29 from tkCommonDialog import Dialog
30 from tkSimpleDialog import askstring
31
32 try:
33     import Pmw
34 except:
35     root=Tk()
36     t = Text(root, wrap=WORD, width=70, height=12)
37     t.pack()
38     t.insert(END, 'Kombilo 0.5i\n\n The Python Megawidgets (Pmw) library was not found. It is needed by Kombilo, ' + \
39              'so you have to install it before you can use Kombilo. See the Kombilo tutorial at ' + \
40              'http://www.u-go.net/kombilo/tutorial#installation or the Pmw web site at ' + \
41              'http://pmw.sourceforge.net/ for more information.')
42     root.mainloop()
43     sys.exit()
44
45 try: import Image
46 except: pass
47
48 import encodings.utf_8
49 import time
50 import os
51 import sys
52 import cPickle
53 from copy import copy, deepcopy
54 from string import split, find, join, strip, replace, digits, maketrans, translate, lower
55 import glob
56 import re
57 from array import *
58 import webbrowser
59
60 from board1 import *
61 import v
62
63 try:
64     from sgfpars import Node, Cursor, SGFError, SGFescape
65 except:
66     from sgfparser import Node, Cursor, SGFError, SGFescape
67    
68 try:
69     import matchC
70     CimportSucceeded = 1
71 except:
72     CimportSucceeded = 0
73
74 #---------------------------------------------------------------------------------------
75
76 class BoardWC(Board):
77     """ Board with support for wildcards and selection
78         of search-relevant region. Furthermore, snapshot returns a dictionary which
79         describes the current board position. It can then be restored with restore."""
80
81    
82     def __init__(self, master, boardSize, canvasSize, fuzzy, labelFontsize, fixedColor, smartFixedColor,
83                  boardImg, blackImg, whiteImg):
84         Board.__init__(self, master, boardSize, canvasSize, fuzzy, labelFontsize, 1, None,
85                        boardImg, blackImg, whiteImg)
86
87         self.wildcards = {}
88
89         self.selection = ((1,1),(19,19))
90
91         self.fixedColor = fixedColor
92         self.smartFixedColor = smartFixedColor
93
94 #        self.boundM2_1 = self.bind('<M2-Button-1>', self.selStart)
95 #        self.boundM2_1m = self.bind('<M2-B1-Motion>', self.selDrag)
96         self.bound3 = self.bind('<Button-3>', self.selStart)
97         self.bound3m = self.bind('<B3-Motion>', self.selDrag)
98         self.bounds1 = self.bind('<Shift-1>', self.wildcard)
99
100         self.invertSelection = IntVar()
101
102
103     def resize(self, event = None):
104         """ Resize the board. Take care of wildcards and selection here. """
105        
106         Board.resize(self, event)
107         for x,y in self.wildcards.keys():
108             x1, x2, y1, y2 = self.getPixelCoord((x,y),1)
109             if self.canvasSize[1]<=7: margin = 5
110             else: margin = 4
111             self.wildcards[(x,y)] = self.create_oval(x1+margin, x2+margin, y1-margin, y2-margin, fill = 'green',
112                                                      tags=('wildcard','non-bg'))
113
114         self.delete('selection')
115         if self.selection != ((1,1),(19,19)) and self.selection[1] != (0,0):
116             p0 = self.getPixelCoord(self.selection[0],1)
117             p1 = self.getPixelCoord((self.selection[1][0]+1, self.selection[1][1]+1), 1)
118             min = self.getPixelCoord((1,1), 1)[0]+1
119             max = self.getPixelCoord((self.boardSize+1,self.boardSize+1),1)[1]-1
120             if self.canvasSize[1] <= 7:
121                 self.create_rectangle(p0[0], p0[1], p1[0], p1[1],
122                                       tags=('selection', 'non-bg'))
123             elif self.invertSelection.get():
124                 self.create_rectangle(p0[0], p0[1], p1[0], p1[1], fill='brown', stipple='gray50', outline='',
125                                       tags='selection')
126             else:
127                 if p0[1] > min:
128                     self.create_rectangle(min, min, max, p0[1], fill='brown', stipple='gray50', outline='',
129                                           tags='selection')
130                 if p0[0] > min and p0[1] < max:
131                     self.create_rectangle(min, p0[1], p0[0], max, fill='brown', stipple='gray50', outline='',
132                                           tags='selection')
133                 if p1[1] < max:
134                     self.create_rectangle(p0[0], p1[1], p1[0], max, fill='brown', stipple='gray50', outline='',
135                                           tags='selection')
136                 if p1[0] < max and p0[1] < max:
137                     self.create_rectangle(p1[0], p0[1], max, max, fill='brown', stipple='gray50', outline='',
138                                           tags='selection')
139             self.tkraise('non-bg')
140            
141         self.update_idletasks()
142
143
144     def wildcard(self, event):
145         """ Place/delete a wildcard at position of click. """
146        
147         x, y = self.getBoardCoord((event.x, event.y), 1)
148         if not x*y or self.status.has_key((x,y)): return
149
150         if self.wildcards.has_key((x,y)):
151             self.delete(self.wildcards[(x,y)])
152             del self.wildcards[(x,y)]
153         else:
154             x1, x2, y1, y2 = self.getPixelCoord((x,y),1)
155             if self.canvasSize[1]<=7: margin = 5
156             else: margin = 4
157             self.wildcards[(x,y)] = self.create_oval(x1+margin, x2+margin, y1-margin, y2-margin, fill = 'green',
158                                                      tags=('wildcard','non-bg'))
159             self.tkraise('label')
160         self.changed.set(1)       
161
162
163     def delWildcards(self):
164         """ Delete all wildcards. """
165        
166         if self.wildcards: self.changed.set(1)
167         self.delete('wildcard')
168         self.wildcards = {}
169
170
171     def placeLabel(self, pos, type, text=None, color=None):
172         """ Place a label; take care of wildcards at same position. """
173        
174         if self.wildcards.has_key(pos): override = ('black', '')
175         else: override = None
176
177         Board.placeLabel(self, pos, type, text, color, override)
178
179                              
180     # ---- selection of search-relevant section -----------------------------------
181
182     def selStart(self, event):
183         """ React to right-click. """
184         self.delete('selection')
185         x, y = self.getBoardCoord((event.x, event.y), 1)
186         x = max(x, 1)
187         y = max(y, 1)
188         self.selection = ((x,y), (0,0))
189         if self.smartFixedColor.get():
190             self.fixedColor.set(1)
191         self.changed.set(1)
192
193
194     def selDrag(self, event):
195         """ React to right-mouse-key-drag. """
196         pos = self.getBoardCoord((event.x, event.y), 1)
197         if pos[0] >= self.selection[0][0] and pos[1] >= self.selection[0][1]:
198             self.setSelection(self.selection[0], pos)
199            
200
201     def setSelection(self, pos0, pos1):
202         self.selection = (pos0, pos1)
203         self.delete('selection')
204         p0 = self.getPixelCoord(pos0,1)
205         p1 = self.getPixelCoord((pos1[0]+1, pos1[1]+1), 1)
206         min = self.getPixelCoord((1,1), 1)[0]+1
207         max = self.getPixelCoord((self.boardSize+1,self.boardSize+1),1)[1]-1
208         if self.canvasSize[1] <= 7:
209             self.create_rectangle(p0[0], p0[1], p1[0], p1[1],
210                                   tags=('selection', 'non-bg'))
211         elif self.invertSelection.get():
212             self.create_rectangle(p0[0], p0[1], p1[0], p1[1], fill='brown', stipple='gray50', outline='',
213                                   tags='selection')
214         else:
215             if p0[1] > min:
216                 self.create_rectangle(min, min, max, p0[1], fill='brown', stipple='gray50', outline='',
217                                       tags='selection')
218             if p0[0] > min and p0[1] < max:
219                 self.create_rectangle(min, p0[1], p0[0], max, fill='brown', stipple='gray50', outline='',
220                                       tags='selection')
221             if p1[1] < max:
222                 self.create_rectangle(p0[0], p1[1], p1[0], max, fill='brown', stipple='gray50', outline='',
223                                       tags='selection')
224             if p1[0] < max and p0[1] < max:
225                 self.create_rectangle(p1[0], p0[1], max, max, fill='brown', stipple='gray50', outline='',
226                                       tags='selection')
227            
228         self.tkraise('non-bg')
229        
230         if self.smartFixedColor.get():
231             if self.selection == ((1,1), (19,19)):
232                 self.fixedColor.set(1)
233             else:
234                 self.fixedColor.set(0)
235                
236        
237     def newPosition(self):
238         """ Clear board, selection. """
239         self.delete('selection')
240         self.clear()
241         self.delLabels()
242         self.delMarks()
243         self.delWildcards()
244         self.selection = ((1,1),(19,19))
245
246         if self.smartFixedColor.get():
247             self.fixedColor.set(1)
248
249
250     # ---- snapshot & restore (for 'back' button)
251
252     def snapshot(self):
253         """ Return a dictionary which contains the data of all the objects
254             currently displayed on the board, which are not stored in the SGF file.
255             This means, at the moment: wildcards, and selection. """
256        
257         data = {}
258         data['wildcards'] = copy(self.wildcards)
259         data['selection'] = self.selection
260         return data
261
262    
263     def restore(self, d):
264         """ Restore the data from a 'snapshot' dictionary. """
265        
266         for x,y in d['wildcards'].keys():
267             x1, x2, y1, y2 = self.getPixelCoord((x,y),1)
268             self.wildcards[(x,y)] = self.create_oval(x1+4, x2+4, y1-4, y2-4, fill = 'green',
269                                                      tags=('wildcard','non-bg'))
270
271         if d['selection'] != ((1,1),(19,19)) and d['selection'][1] != (0,0):
272             self.setSelection(d['selection'][0], d['selection'][1])
273
274
275 # ---------------------------------------------------------------------------------------
276
277 class chooseDirectory(Dialog):
278     """ A wrapper tor the Tk chooseDirectory widget. """
279    
280     command = "tk_chooseDirectory"
281
282     def _fixresult(self, widget, result):
283         if result:
284             self.options["initialdir"] = result
285         self.directory = result
286         return result
287
288 def askdirectory(**options):
289     return apply(chooseDirectory, (), options).show()
290
291 # ---------------------------------------------------------------------------------------
292
293 class TextEditor:
294     """ A very simple text editor, based on the Tkinter ScrolledText widget.
295     You can perform very limited editing, and save the result to a file. """
296
297     def __init__(self, t = '', defpath='', font = None):
298
299         if font is None:
300             font = (StringVar(), IntVar(), StringVar())
301             font[0].set('Courier')
302             font[1].set(10)
303             font[2].set('')
304            
305         self.window = Toplevel()
306
307         self.window.protocol('WM_DELETE_WINDOW', self.quit)
308
309         self.text = ScrolledText(self.window, width=70, height=30,
310                                  font=(font[0].get(), font[1].get(), font[2].get()))
311         self.text.pack(side=BOTTOM, fill=BOTH, expand=YES)
312         self.text.insert(END, t)
313
314         self.buttonFrame = Frame(self.window)
315         self.buttonFrame.pack(side=TOP, expand=NO, fill=X)
316
317         Button(self.buttonFrame, text='Quit', command=self.quit).pack(side=RIGHT)
318         Button(self.buttonFrame, text='Save as', command=self.saveas).pack(side=RIGHT)
319
320         # self.window.tkraise()
321         self.window.focus_force()
322
323         if defpath:
324             self.defpath = defpath
325         else:
326             self.defpath = os.curdir
327            
328     def saveas(self):
329         f = tkFileDialog.asksaveasfilename(initialdir = self.defpath)
330         if not f: return
331         try:
332             file = open(f, 'w')
333             file.write(self.text.get('1.0', END).encode('utf-8', 'ignore'))
334             file.close()
335         except IOError:
336             showwarning('IO Error', 'Cannot write to ' + f)
337
338     def quit(self):
339         self.window.destroy()
340
341
342 class ESR_TextEditor(TextEditor):
343     """The text editor which is used by the exportSearchResults function.
344     It adds a button to include the complete game list to the TextEditor."""
345
346
347     def __init__(self, master, style, t='', defpath='', font=None):
348         TextEditor.__init__(self, t, defpath, font)
349         self.mster = master
350         self.style = style
351        
352         Button(self.buttonFrame, text='Include game list', command=self.includeGameList).pack(side=LEFT)
353
354
355     def includeGameList(self):
356         if self.style: # wiki
357             self.text.insert(END, '\n\n!Game list\n\n' + join(self.mster.gamelist.list.get(0, END), ' %%%\n'))
358         else:
359             self.text.insert(END, '\n\nGame list\n\n' + join(self.mster.gamelist.list.get(0, END), '\n'))
360        
361
362 # -------------------------------------------------------------------------------------
363
364 class DataWindow(v.DataWindow):
365
366     def __init__(self, master):
367
368         v.DataWindow.__init__(self, master)
369
370         self.prevSF = Pmw.ScrolledFrame(self.prevSearchF, usehullsize=1, hull_width=300, hull_height=135,
371                                         hscrollmode='static', vscrollmode='none', vertflex='elastic')
372         self.prevSF.pack(expand=YES, fill=X)
373         self.prevSV = IntVar()
374         self.prevSV.set(1)
375
376         b1 = Checkbutton(self.toolbarF, text = 'History', variable = self.prevSV,
377                          command = self.togglePrevSearches, indicatoron=0)
378         b1.grid(row=0, column=6)
379
380         self.win.setnaturalsize()
381
382        
383     def initPanes(self):
384         """ Create the panes in the data window. """
385
386         self.toolbarF = self.win.add(name='toolb', min=28, max=28)
387         self.filelistF = self.win.add(name='filel', min=0.01, max=1.0, size=65)
388         self.gamelistF = self.win.add(name='gamel', min=0.01, max=1.0, size=65)
389         self.gameinfoF = self.win.add(name='gamei', min=0.01, max=1.0, size=75)
390         self.editToolsF = self.win.add(name='editt', min=1, max=30, size=30)
391         self.gametreeF = self.win.add(name='gamet', min=0.01, max=1.0, size=90)
392         self.commentsF = self.win.add(name='comm', min=0.01, max=1.0, size=80)
393         self.prevSearchF = self.win.add('prse', min=1, max=1.0, size=135)
394
395
396     def get_geometry(self):
397         """ Return a list of current sizes of the panes. """
398        
399         l = [ self.filelistV.get(), self.win._size['filel'],
400               self.gamelistV.get(), self.win._size['gamel'],
401               self.gameinfoV.get(), self.win._size['gamei'],
402               self.editToolsV.get(), self.win._size['editt'],
403               self.gametreeV.get(), self.win._size['gamet'],
404               self.commentsV.get(), self.win._size['comm'],
405               self.prevSV.get(), self.win._size['prse']]
406         l1 = [ `x` for x in l ]
407         l1.append(self.window.geometry())
408
409         return join(l1, '|%')
410        
411     def set_geometry(self, s):
412         """ Reset the sizes of the panes to the given ones. """
413        
414         l = split(s, '|%')
415        
416         l1 = [ self.filelistV,
417                self.gamelistV,
418                self.gameinfoV,
419                self.editToolsV,
420                self.gametreeV,
421                self.commentsV,
422                self.prevSV ]
423                
424         for i in range(len(l)/2):
425             l1[i].set(int(l[2*i]))
426             if int(l[2*i]):
427                 self.win.configurepane(i+1, min=10, max=1.0, size = int(l[2*i+1]))
428             else:
429                 self.win.configurepane(i+1, min=0.0, max=0.0, size = 0.0)
430         self.win.updatelayout()
431         self.window.geometry(l[-1])
432
433
434     def gamelistRelease(self, event):
435         index1, index2 = v.DataWindow.gamelistRelease(self, event)
436         if index1:
437             self.mster.prevSearches.exchangeGames(self.mster.cursor, index1, index2)
438        
439
440     def togglePrevSearches(self):
441         if self.prevSV.get():
442             s = self.window.geometry()
443             x1, x2, x3 = split(s, '+')
444             x, y = split(x1, 'x')
445             y = `int(y)+135`
446             self.window.geometry('%sx%s+%s+%s' % (x, y, x2, x3))
447             self.win.configurepane(7, min=10, max=1.0, size=135)
448         else:
449             s = self.window.geometry()
450             x1, x2, x3 = split(s, '+')
451             x, y = split(x1, 'x')
452             y = `int(y)-self.win._size['prse']`
453             self.window.geometry('%sx%s+%s+%s' % (x, y, x2, x3))
454             self.win.configurepane(7, min=0.0, max=0.0, size=0.0)
455         self.win.updatelayout()
456        
457
458 # -------------------------------------------------------------------------------------
459
460 class GameList(v.ScrolledList):
461     """ This is a scrolled list which shows the game list. All the underlying data
462         is contained in self.DBlist, which is a list of dictionaries containing the
463         information for the single databases. self.DBlist[i] will contain the keys
464         'name': name of the database, i.e. path to the *.db files
465         'sgfpath': path to the SGF files
466         'data': list of all games in the database.
467                 This is a list of tuples of the form
468                 (filename, namelistIndex, PB, PW, result, signature)
469         'current': a list of the indices of the games in data which are in the current
470                    game list
471         'results': for each game in current, the list of matches found in the previous
472                    search (If there are lots of matches, this list will consume a lot
473                    of memory. Maybe at some time I will get around to fix this ...)
474         'numNewGames': number of games which were "append"ed, but not yet inserted into
475                        the list (by addAppendedGames)
476         """
477    
478     def __init__(self, parent, master, noGamesLabel, winPercLabel, gameinfo):
479         v.ScrolledList.__init__(self, parent)
480         self.list.config(width=52, height=6)
481
482         self.onSelectionChange = self.printGameInfo
483         self.list.bind('<Button-1>', self.onSelectionChange)
484
485         self.bind('<Up>', self.up)
486         self.bind('<Down>', self.down)
487         self.bind('<Prior>', self.pgup)
488         self.bind('<Next>', self.pgdown)
489         self.bind('<Return>', self.handleDoubleClick)
490         self.list.bind('<Double-1>', self.handleDoubleClick)
491         self.list.bind('<Shift-1>', self.handleShiftClick)
492         self.list.bind('<Button-3>', self.rightMouseButton)
493
494         self.mster = master
495        
496         self.DBlist = []      # list of dicts
497
498         self.noGamesLabel = noGamesLabel
499         self.winPercLabel = winPercLabel
500         self.gameinfo = gameinfo
501        
502         self.Bwins = 0
503         self.Wwins = 0
504         self.Owins = 0 # others: Jigo, Void, Left unfinished, ? (Unknown)
505
506         self.appendClist = []
507
508         self.references = {}
509
510         self.sort = None
511         self.gameIndex = []
512
513         self.showFilename = 1
514         self.showDate = 0
515
516
517     def rightMouseButton(self, event):
518        
519         index = self.list.nearest(event.y)
520         if index == -1: return
521
522         DBindex, index = self.getIndex(index)
523         if DBindex == -1: return
524
525         # print self.DBlist[DBindex]['data'][self.DBlist[DBindex]['current'][index]][5]
526         # return
527
528         f1 = strip(os.path.join(self.DBlist[DBindex]['sgfpath'],
529                                 self.DBlist[DBindex]['data'][self.DBlist[DBindex]['current'][index]][0]))
530        
531         if find(f1, '[') != -1:
532             f1, f2 = split(f1, '[')
533             gameNumber = int(strip(f2)[:-1])
534         else:
535             gameNumber = 0
536
537         filename = getFilename(f1)
538
539         try:
540             file = open(filename)
541             sgf = file.read()
542             file.close()
543             c = Cursor(sgf, 1)
544             rootNode = c.getRootNode(gameNumber)
545         except IOError:
546             showwarning('Error', 'I/O Error')
547             return
548         except SGFError:
549             showwarning('Error', 'SGF error')
550             return
551        
552         backup = copy(rootNode)
553
554         newRootNode = self.mster.gameinfo(rootNode)
555         if backup != newRootNode:
556             c.updateRootNode(newRootNode, gameNumber)
557             try:
558                 s = c.output()
559                 file = open(filename, 'w')
560                 file.write(s)
561                 file.close()
562             except IOError:
563                 showwarning('I/O Error', 'Could not write to file ' + filename)
564
565
566     def handleDoubleClick(self, event):
567         """ This is called upon double-clicks."""
568        
569         index = self.list.curselection()
570         if index:
571             label = self.list.get(index)
572             self.mster.openViewer(index, label)
573
574
575     def handleShiftClick(self, event):
576         index = self.list.nearest(event.y)
577         index1 = self.list.curselection()
578         if index1: self.list.select_clear(index1[0])
579         self.list.select_set(index)
580         self.onSelectionChange(event)
581         try:
582             label = self.list.get(index)
583         except:
584             return
585         self.mster.altOpenViewer([index], label)
586
587
588     def sortCrit(self, index, c):
589         dbIndex, j = self.getIndex(index)
590         return self.DBlist[dbIndex]['data'][self.DBlist[dbIndex]['current'][j]][c]
591
592
593     def sortlist(self, perDB, criterion):
594         """ Sort the game list wrt the given criterion, and update it. """
595
596         self.sort = []
597
598         if criterion == 'PW': c = 3
599         elif criterion == 'PB': c = 2
600         elif criterion == 'DT': c = 6
601         else: c = 0          # criterion == 'filename'
602
603         self.appendClist = []
604
605         if perDB and c == 0:
606             for i in range(len(self.DBlist)):
607                 db = self.DBlist[i]
608                 if db['disabled']: continue
609                 if db.has_key('sort'): del db['sort']
610                 self.appendClist.extend([(i, db['current'][j], j) for j in xrange(len(db['current']))])
611         elif perDB:
612             for i in range(len(self.DBlist)):
613                 db = self.DBlist[i]
614                 if db['disabled']: continue
615                 if db.has_key('sort'): del db['sort']
616                
617                 s = []
618                 s.extend([(db['data'][j][c], j) for j in range(len(db['data']))])
619
620                 s.sort()
621
622                 self.sort.extend([(i, j) for dummy, j in s])
623         else:
624            
625             for i in range(len(self.DBlist)):
626                 db = self.DBlist[i]
627                 if db['disabled']: continue
628                 self.sort.extend([(db['data'][j][c], i, j) for j in range(len(db['data']))])
629
630             self.sort.sort()
631            
632             self.sort = [(i, j) for dummy, i, j in self.sort]
633
634         if not (perDB and c==0):
635
636             for i in range(len(self.DBlist)):
637                 db = self.DBlist[i]
638                 db['sort'] = array('L', [0] * len(db['data']))
639
640                 for ii in range(len(self.sort)):
641                     if self.sort[ii][0] == i: db['sort'][self.sort[ii][1]] = ii
642                
643                 self.appendClist.extend([(i, db['current'][j], j) for j in xrange(len(db['current']))])
644            
645         self.list.delete(0, END)
646         self.Bwins, self.Wwins, self.Owins = 0,0,0
647         self.update()
648
649
650     def getIndex(self, i):
651         """ Returns dbIndex, j, such that self.DBlist[dbIndex]['current'][j] corresponds to the
652         i-th line of the ScrolledList. """
653
654         if i < len(self.gameIndex):
655             return self.gameIndex[i]
656         else:
657             return -1, -1
658
659
660     def clear(self):
661         """ Clear the list. """
662
663         self.update()
664
665         for db in self.DBlist:
666             if not db['disabled']:
667                 db['current'] = array('L')
668                 db['results'] = []
669            
670         self.Bwins = 0
671         self.Wwins = 0
672         self.Owins = 0
673         self.update()
674
675         self.delete(0,END)
676         self.list.update_idletasks()
677
678
679     def append(self, i, d):
680         """ Append an entry to the gamelist. """
681         self.DBlist[i]['data'].append(d)
682
683         if not self.DBlist[i]['disabled']:
684             self.DBlist[i]['numNewGames'] += 1
685
686     def addAppendedGames(self):
687         """ Display the games which were "append"ed in the list. """
688        
689         for i in range(len(self.DBlist)):
690             db = self.DBlist[i]
691             if db['numNewGames']:                              # display append'ed entries
692                 db['current'].extend(array('L', range(len(db['data'])-db['numNewGames'], len(db['data']))))
693                 self.gameIndex.extend([(i, j) for j in range(db['numNewGames'])])
694             db['results'] = db['results'] + [''] * db['numNewGames']
695             db['numNewGames'] = 0
696
697         noOfG = self.noOfGames()
698         self.noGamesLabel.config(text = `noOfG` + ' games')
699
700         if noOfG:
701             Bperc = self.Bwins * 100.0 / noOfG
702             Wperc = self.Wwins * 100.0 / noOfG
703             self.winPercLabel.config(text='B: %1.1f%%, W: %1.1f%%' % (Bperc, Wperc))
704         else: self.winPercLabel.config(text='')
705
706         self.delete(0,END)
707         self.sortlist(self.mster.options.sortPerDatabase.get(), self.mster.options.sortCriterion.get())
708        
709
710     def appendC(self, dbIndex, i, res = ''):
711         """ Append an entry to self.current """
712
713         db = self.DBlist[dbIndex]
714        
715         db['current'].append(i)
716         db['results'].append(res)
717         self.appendClist.append((dbIndex, i, len(db['current'])-1))
718
719
720     def reset(self):
721         """ Reset the list, s.t. it includes all the games from self.data. """
722        
723         for i in range(len(self.DBlist)):
724             db = self.DBlist[i]
725             if not db['disabled']:
726                 db['current'] = array('L', range(len(db['data'])))
727                 db['results'] = [''] * len(db['data'])
728                 self.appendClist += [ (i, j, j) for j in range(len(db['data']))]
729             else:
730                 db['current'] = array('L')
731                 db['results'] = []
732         self.delete(0, END)
733         self.Bwins, self.Wwins, self.Owins = 0, 0, 0
734         self.update()
735         self.clearGameInfo()
736
737        
738     def update(self):
739         """ Put the changes in data, current in effect in self.list. """
740        
741         if self.appendClist:         # display appendC'ed entries
742             
743             strl = []
744             self.gameIndex = []
745
746             if self.sort:
747                 self.appendClist = [ (self.DBlist[x[0]]['sort'][x[1]], x) for x in self.appendClist ]
748                 self.appendClist.sort()
749                 self.appendClist = map(lambda x: x[1], self.appendClist)
750
751             if self.mster.options.sortReverse.get():
752                 self.appendClist.reverse()
753
754             for db, index, index1 in self.appendClist:
755                 d = self.DBlist[db]['data'][index]
756                 res = self.DBlist[db]['results'][index1]
757                 if d[4] == 'B': self.Bwins += 1
758                 elif d[4] == 'W': self.Wwins += 1
759                 else: self.Owins += 1
760
761                 li = [d[7]]
762                 if self.showFilename:
763                     endFilename = find(d[0], '[')
764                     if endFilename == -1: endFilename = len(d[0])
765                
766                     if d[0][endFilename-1] == '.':
767                         filename = d[0][:endFilename-1] + d[0][endFilename:]
768                     elif d[0][endFilename-2:endFilename] == '.m':
769                         filename = d[0][:endFilename-2] + d[0][endFilename:]
770                     else: filename = d[0]
771
772                     li.append(filename + ': ')
773
774                 li.append(d[3] + ' - ' + d[2] + ' (' + d[4] + '), ')
775                 if self.showDate: li.append(d[6]+', ')
776                 li.append(res)
777                 strl.append(join(li, ''))
778                 self.gameIndex.append((db, index1))
779
780             ti = time.time()
781             self.insert(END, strl)
782            
783             self.appendClist = []
784
785         noOfG = self.noOfGames()
786         self.noGamesLabel.config(text = `noOfG` + ' games')
787
788         if noOfG:
789             Bperc = self.Bwins * 100.0 / noOfG
790             Wperc = self.Wwins * 100.0 / noOfG
791             self.winPercLabel.config(text='B: %1.1f%%, W: %1.1f%%' % (Bperc, Wperc))
792         else: self.winPercLabel.config(text='')
793        
794
795     def noOfGames(self):
796         return reduce(lambda x,y:x+y, [ len(db['current']) for db in self.DBlist if not db['disabled'] ], 0)
797
798
799     def printGameInfo(self, event, index = -1):
800         """ Print game info of selected game. """
801
802         if index == -1:
803             index = self.list.nearest(event.y)
804             self.focus()
805         if index == -1:
806             return
807
808         DBindex, index = self.getIndex(index)
809         if DBindex == -1: return
810            
811         f1 = strip(os.path.join(self.DBlist[DBindex]['sgfpath'],
812                                 self.DBlist[DBindex]['data'][self.DBlist[DBindex]['current'][index]][0]))
813
814         if find(f1, '[') != -1:
815             f1, f2 = split(f1, '[')
816             gameNumber = int(strip(f2)[:-1])
817         else:
818             gameNumber = 0
819
820         filename = getFilename(f1)
821
822         try:
823             f = open(filename)
824             sgf = f.read()
825             f.close()
826             node = Cursor(sgf, 1).getRootNode(gameNumber)
827         except:
828             return
829
830         t = ''
831        
832         if node.has_key('PW'): t = t + node['PW'][0]
833         else: t = t + ' ?'
834         if node.has_key('WR'): t = t + ', ' + node['WR'][0]
835