| 1 | #!/usr/bin/python |
|---|
| 2 | # File: kombilo.py |
|---|
| 3 | |
|---|
| 4 | ## Copyright (C) 2001-2 Ulrich Goertz (u@g0ertz.de) |
|---|
| 5 | |
|---|
| 6 | ## Kombilo 0.4d 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 tkCommonDialog import Dialog |
|---|
| 29 | from tkSimpleDialog import askstring |
|---|
| 30 | import time |
|---|
| 31 | import os |
|---|
| 32 | import sys |
|---|
| 33 | import cPickle |
|---|
| 34 | from copy import copy, deepcopy |
|---|
| 35 | from string import split, find, join, strip, replace, digits |
|---|
| 36 | import glob |
|---|
| 37 | import re |
|---|
| 38 | from array import * |
|---|
| 39 | import webbrowser |
|---|
| 40 | |
|---|
| 41 | from sgfparser import * |
|---|
| 42 | from board1 import * |
|---|
| 43 | import v |
|---|
| 44 | |
|---|
| 45 | try: |
|---|
| 46 | import matchC |
|---|
| 47 | CimportSucceeded = 1 |
|---|
| 48 | except: |
|---|
| 49 | CimportSucceeded = 0 |
|---|
| 50 | |
|---|
| 51 | # --------------------------------------------------------------------------------------- |
|---|
| 52 | |
|---|
| 53 | class BoardWC(Board): |
|---|
| 54 | """ Board with support for wildcards and selection |
|---|
| 55 | of search-relevant region. Furthermore, snapshot returns a dictionary which |
|---|
| 56 | describes the current board position. It can then be restored with restore.""" |
|---|
| 57 | |
|---|
| 58 | def __init__(self, master, boardSize, canvasSize, fuzzy, labelFontsize, fixedColor, smartFixedColor): |
|---|
| 59 | Board.__init__(self, master, boardSize, canvasSize, fuzzy, labelFontsize) |
|---|
| 60 | |
|---|
| 61 | self.wildcards = {} |
|---|
| 62 | |
|---|
| 63 | self.selection = ((1,1),(19,19)) |
|---|
| 64 | |
|---|
| 65 | self.fixedColor = fixedColor |
|---|
| 66 | self.smartFixedColor = smartFixedColor |
|---|
| 67 | |
|---|
| 68 | self.bind('<Button-3>', self.selStart) |
|---|
| 69 | self.bind('<B3-Motion>', self.selDrag) |
|---|
| 70 | self.bind('<Shift-1>', self.wildcard) |
|---|
| 71 | |
|---|
| 72 | def resize(self, event): |
|---|
| 73 | Board.resize(self, event) |
|---|
| 74 | for x,y in self.wildcards.keys(): |
|---|
| 75 | x1, x2, y1, y2 = self.getPixelCoord((x,y),1) |
|---|
| 76 | self.wildcards[(x,y)] = self.create_oval(x1+4, x2+4, y1-4, y2-4, fill = 'green', |
|---|
| 77 | tags=('wildcard','non-bg')) |
|---|
| 78 | |
|---|
| 79 | self.delete('selection') |
|---|
| 80 | if self.selection != ((1,1),(19,19)) and self.selection[1] != (0,0): |
|---|
| 81 | p0 = self.getPixelCoord(self.selection[0],1) |
|---|
| 82 | p1 = self.getPixelCoord((self.selection[1][0]+1, self.selection[1][1]+1),1) |
|---|
| 83 | |
|---|
| 84 | self.create_rectangle(p0[0], p0[1], p1[0], p1[1], fill='brown', stipple='gray50', |
|---|
| 85 | tags='selection') |
|---|
| 86 | self.tkraise('non-bg') |
|---|
| 87 | |
|---|
| 88 | self.update_idletasks() |
|---|
| 89 | |
|---|
| 90 | def wildcard(self, event): |
|---|
| 91 | """ Place a wildcard at position of click. """ |
|---|
| 92 | x, y = self.getBoardCoord((event.x, event.y), 1) |
|---|
| 93 | if not x*y or self.status.has_key((x,y)) \ |
|---|
| 94 | or self.wildcards.has_key((x,y)): return |
|---|
| 95 | |
|---|
| 96 | x1, x2, y1, y2 = self.getPixelCoord((x,y),1) |
|---|
| 97 | self.wildcards[(x,y)] = self.create_oval(x1+4, x2+4, y1-4, y2-4, fill = 'green', |
|---|
| 98 | tags=('wildcard','non-bg')) |
|---|
| 99 | |
|---|
| 100 | self.changed.set(1) |
|---|
| 101 | |
|---|
| 102 | |
|---|
| 103 | def delWildcards(self): |
|---|
| 104 | if self.wildcards: self.changed.set(1) |
|---|
| 105 | self.delete('wildcard') |
|---|
| 106 | self.wildcards = {} |
|---|
| 107 | |
|---|
| 108 | # ---- selection of search-relevant section ----------------------------------- |
|---|
| 109 | |
|---|
| 110 | def selStart(self, event): |
|---|
| 111 | """ React to right-click. """ |
|---|
| 112 | self.delete('selection') |
|---|
| 113 | x, y = self.getBoardCoord((event.x, event.y), 1) |
|---|
| 114 | x = max(x, 1) |
|---|
| 115 | y = max(y, 1) |
|---|
| 116 | self.selection = ((x,y), (0,0)) |
|---|
| 117 | if self.smartFixedColor.get(): |
|---|
| 118 | self.fixedColor.set(1) |
|---|
| 119 | self.changed.set(1) |
|---|
| 120 | |
|---|
| 121 | def selDrag(self, event): |
|---|
| 122 | """ React to right-mouse-key-drag. """ |
|---|
| 123 | pos = self.getBoardCoord((event.x, event.y), 1) |
|---|
| 124 | if pos[0] >= self.selection[0][0] and pos[1] >= self.selection[0][1]: |
|---|
| 125 | self.setSelection(self.selection[0], pos) |
|---|
| 126 | |
|---|
| 127 | |
|---|
| 128 | def setSelection(self, pos0, pos1): |
|---|
| 129 | self.selection = (pos0, pos1) |
|---|
| 130 | self.delete('selection') |
|---|
| 131 | p0 = self.getPixelCoord(pos0,1) |
|---|
| 132 | p1 = self.getPixelCoord((pos1[0]+1, pos1[1]+1), 1) |
|---|
| 133 | |
|---|
| 134 | self.create_rectangle(p0[0], p0[1], p1[0], p1[1], fill='brown', stipple='gray50', |
|---|
| 135 | tags='selection') |
|---|
| 136 | self.tkraise('non-bg') |
|---|
| 137 | |
|---|
| 138 | if self.smartFixedColor.get(): |
|---|
| 139 | if self.selection == ((1,1), (19,19)): |
|---|
| 140 | self.fixedColor.set(1) |
|---|
| 141 | else: |
|---|
| 142 | self.fixedColor.set(0) |
|---|
| 143 | |
|---|
| 144 | |
|---|
| 145 | def newPosition(self): |
|---|
| 146 | """ Clear board, selection. """ |
|---|
| 147 | self.delete('selection') |
|---|
| 148 | self.clear() |
|---|
| 149 | self.delLabels() |
|---|
| 150 | self.delMarks() |
|---|
| 151 | self.delWildcards() |
|---|
| 152 | self.selection = ((1,1),(19,19)) |
|---|
| 153 | |
|---|
| 154 | if self.smartFixedColor.get(): |
|---|
| 155 | self.fixedColor.set(1) |
|---|
| 156 | |
|---|
| 157 | # ---- snapshot & restore (for 'back' button) |
|---|
| 158 | |
|---|
| 159 | def snapshot(self): |
|---|
| 160 | """ Return a dictionary which contains the data of all the objects |
|---|
| 161 | currently displayed on the board. """ |
|---|
| 162 | |
|---|
| 163 | data = {} |
|---|
| 164 | data['status'] = copy(self.status) |
|---|
| 165 | data['marks'] = copy(self.marks) |
|---|
| 166 | data['labels'] = copy(self.labels) |
|---|
| 167 | data['wildcards'] = copy(self.wildcards) |
|---|
| 168 | data['selection'] = self.selection |
|---|
| 169 | data['currentcolor'] = self.currentColor |
|---|
| 170 | data['undostack'] = deepcopy(self.undostack) |
|---|
| 171 | |
|---|
| 172 | return data |
|---|
| 173 | |
|---|
| 174 | def restore(self, d): |
|---|
| 175 | """ Restore a board position from a 'snapshot' dictionary. """ |
|---|
| 176 | |
|---|
| 177 | self.newPosition() |
|---|
| 178 | for x in d['status'].keys(): self.play(x, d['status'][x]) |
|---|
| 179 | for x in d['marks'].keys(): self.placeMark(x, d['marks'][x]) |
|---|
| 180 | for x in d['labels'].keys(): self.placeLabel(x, d['labels'][x][0], d['labels'][x][1]) |
|---|
| 181 | for x,y in d['wildcards'].keys(): |
|---|
| 182 | x1, x2, y1, y2 = self.getPixelCoord((x,y),1) |
|---|
| 183 | self.wildcards[(x,y)] = self.create_oval(x1+4, x2+4, y1-4, y2-4, fill = 'green', |
|---|
| 184 | tags=('wildcard','non-bg')) |
|---|
| 185 | |
|---|
| 186 | if d['selection'] != ((1,1),(19,19)) and d['selection'][1] != (0,0): |
|---|
| 187 | p0 = self.getPixelCoord(d['selection'][0],1) |
|---|
| 188 | p1 = self.getPixelCoord((d['selection'][1][0]+1, d['selection'][1][1]+1),1) |
|---|
| 189 | |
|---|
| 190 | self.create_rectangle(p0[0], p0[1], p1[0], p1[1], fill='brown', stipple='gray50', |
|---|
| 191 | tags='selection') |
|---|
| 192 | self.tkraise('non-bg') |
|---|
| 193 | self.selection = d['selection'] |
|---|
| 194 | |
|---|
| 195 | self.currentColor = d['currentcolor'] |
|---|
| 196 | self.undostack = deepcopy(d['undostack']) |
|---|
| 197 | |
|---|
| 198 | # --------------------------------------------------------------------------------------- |
|---|
| 199 | |
|---|
| 200 | class chooseDirectory(Dialog): |
|---|
| 201 | """ A wrapper tor the Tk chooseDirectory widget. """ |
|---|
| 202 | |
|---|
| 203 | command = "tk_chooseDirectory" |
|---|
| 204 | |
|---|
| 205 | def _fixresult(self, widget, result): |
|---|
| 206 | if result: |
|---|
| 207 | self.options["initialdir"] = result |
|---|
| 208 | self.directory = result |
|---|
| 209 | return result |
|---|
| 210 | |
|---|
| 211 | def askdirectory(**options): |
|---|
| 212 | return apply(chooseDirectory, (), options).show() |
|---|
| 213 | |
|---|
| 214 | # --------------------------------------------------------------------------------------- |
|---|
| 215 | |
|---|
| 216 | class TextEditor: |
|---|
| 217 | """ A very simple text editor, based on the Tkinter ScrolledText widget. |
|---|
| 218 | You can perform very limited editing, and save the result to a file. """ |
|---|
| 219 | |
|---|
| 220 | def __init__(self, t = '', defpath='', font = None): |
|---|
| 221 | |
|---|
| 222 | if font is None: |
|---|
| 223 | font = (StringVar(), IntVar(), StringVar()) |
|---|
| 224 | font[0].set('Courier') |
|---|
| 225 | font[1].set(10) |
|---|
| 226 | font[2].set('') |
|---|
| 227 | |
|---|
| 228 | self.window = Toplevel() |
|---|
| 229 | |
|---|
| 230 | self.window.protocol('WM_DELETE_WINDOW', self.quit) |
|---|
| 231 | |
|---|
| 232 | self.text = ScrolledText(self.window, width=70, height=30, |
|---|
| 233 | font=(font[0].get(), font[1].get(), font[2].get())) |
|---|
| 234 | self.text.pack(fill=BOTH, expand=YES) |
|---|
| 235 | self.text.insert(END, t) |
|---|
| 236 | |
|---|
| 237 | self.buttonFrame = Frame(self.window) |
|---|
| 238 | self.buttonFrame.pack(side=RIGHT) |
|---|
| 239 | |
|---|
| 240 | Button(self.buttonFrame, text='Quit', command=self.quit).pack(side=RIGHT) |
|---|
| 241 | Button(self.buttonFrame, text='Save as', command=self.saveas).pack(side=RIGHT) |
|---|
| 242 | |
|---|
| 243 | # self.window.tkraise() |
|---|
| 244 | self.window.focus_force() |
|---|
| 245 | |
|---|
| 246 | if defpath: |
|---|
| 247 | self.defpath = defpath |
|---|
| 248 | else: |
|---|
| 249 | self.defpath = os.curdir |
|---|
| 250 | |
|---|
| 251 | def saveas(self): |
|---|
| 252 | f = tkFileDialog.asksaveasfilename(initialdir = self.defpath) |
|---|
| 253 | try: |
|---|
| 254 | file = open(f, 'w') |
|---|
| 255 | file.write(self.text.get('1.0', END)) |
|---|
| 256 | file.close() |
|---|
| 257 | except IOError: |
|---|
| 258 | showwarning('IO Error', 'Cannot write to ' + f) |
|---|
| 259 | |
|---|
| 260 | def quit(self): |
|---|
| 261 | self.window.destroy() |
|---|
| 262 | |
|---|
| 263 | |
|---|
| 264 | |
|---|
| 265 | class ESR_TextEditor(TextEditor): |
|---|
| 266 | """The text editor which is used by the exportSearchResults function. |
|---|
| 267 | It adds a button to include the complete game list to the TextEditor.""" |
|---|
| 268 | |
|---|
| 269 | def __init__(self, master, style, t='', defpath='', font=None): |
|---|
| 270 | TextEditor.__init__(self, t, defpath, font) |
|---|
| 271 | self.master = master |
|---|
| 272 | self.style = style |
|---|
| 273 | |
|---|
| 274 | Button(self.buttonFrame, text='Include game list', command=self.includeGameList).pack(side=RIGHT) |
|---|
| 275 | |
|---|
| 276 | def includeGameList(self): |
|---|
| 277 | if self.style == 'wiki': |
|---|
| 278 | self.text.insert(END, '\n\n!Game list\n\n' + join(self.master.gamelist.list.get(0, END), ' %%%\n')) |
|---|
| 279 | else: |
|---|
| 280 | self.text.insert(END, '\n\nGame list\n\n' + join(self.master.gamelist.list.get(0, END), '\n')) |
|---|
| 281 | |
|---|
| 282 | |
|---|
| 283 | # --------------------------------------------------------------------------------------- |
|---|
| 284 | |
|---|
| 285 | class ScrolledList(Frame): |
|---|
| 286 | """ A listbox with vertical and horizontal scrollbars. """ |
|---|
| 287 | |
|---|
| 288 | def __init__(self, parent, **kw): |
|---|
| 289 | Frame.__init__(self, parent) |
|---|
| 290 | |
|---|
| 291 | self.sbar = Scrollbar(self) |
|---|
| 292 | self.sbar1 = Scrollbar(self) |
|---|
| 293 | |
|---|
| 294 | if not kw: kw = {} |
|---|
| 295 | |
|---|
| 296 | for var, value in [('height', 12), ('width', 40), ('relief', SUNKEN), |
|---|
| 297 | ('selectmode', SINGLE), ('takefocus', 1)]: |
|---|
| 298 | if not kw.has_key(var): kw[var] = value |
|---|
| 299 | |
|---|
| 300 | self.list = Listbox(self, kw) |
|---|
| 301 | self.sbar.config(command = self.list.yview) |
|---|
| 302 | self.sbar1.config(command = self.list.xview, orient='horizontal') |
|---|
| 303 | self.list.config(xscrollcommand = self.sbar1.set, yscrollcommand = self.sbar.set) |
|---|
| 304 | self.list.grid(row=0, column=0, sticky=NSEW) |
|---|
| 305 | self.sbar.grid(row=0, column=1, sticky=NSEW) |
|---|
| 306 | self.sbar1.grid(row=1, column=0, sticky=NSEW) |
|---|
| 307 | |
|---|
| 308 | self.rowconfigure(0, weight=1) |
|---|
| 309 | self.columnconfigure(0, weight=1) |
|---|
| 310 | |
|---|
| 311 | self.focus_force() |
|---|
| 312 | |
|---|
| 313 | |
|---|
| 314 | # ------------------------------------------------------------------------------------- |
|---|
| 315 | |
|---|
| 316 | class GameList(ScrolledList): |
|---|
| 317 | """ This is a scrolled list which shows the game list. All the underlying data |
|---|
| 318 | is contained in self.DBlist, which is a list of dictionaries containing the |
|---|
| 319 | information for the single databases. self.DBlist[i] will contain the keys |
|---|
| 320 | 'name': name (=path) of the database |
|---|
| 321 | 'data': list of all games in the database. |
|---|
| 322 | This is a list of tuples of the form |
|---|
| 323 | (filename, namelistIndex, PB, PW, result, signature) |
|---|
| 324 | 'current': a list of the indices of the games in data which are in the current |
|---|
| 325 | game list |
|---|
| 326 | 'results': for each game in current, the list of matches found in the previous |
|---|
| 327 | search (If there are lots of matches, this list will consume a lot |
|---|
| 328 | of memory. Maybe at some time I will get around to fix this ...) |
|---|
| 329 | 'stringlist': A list of strings from entries in 'data' which are not yet |
|---|
| 330 | displayed in the ScrolledList (to speed things up by inserting |
|---|
| 331 | many entries at once into the ScrolledList) |
|---|
| 332 | """ |
|---|
| 333 | |
|---|
| 334 | def __init__(self, parent, master, noGamesLabel, winPercLabel, gameinfo): |
|---|
| 335 | ScrolledList.__init__(self, parent) |
|---|
| 336 | self.list.config(width=52, height=10) |
|---|
| 337 | self.list.bind('<Button-1>', self.printGameInfo) |
|---|
| 338 | |
|---|
| 339 | self.bind('<Up>', self.up) |
|---|
| 340 | self.bind('<Down>', self.down) |
|---|
| 341 | self.bind('<Prior>', self.pgup) |
|---|
| 342 | self.bind('<Next>', self.pgdown) |
|---|
| 343 | self.bind('<Return>', self.handleDoubleClick) |
|---|
| 344 | self.list.bind('<Double-1>', self.handleDoubleClick) |
|---|
| 345 | self.list.bind('<Button-3>', self.rightMouseButton) |
|---|
| 346 | |
|---|
| 347 | self.master = master |
|---|
| 348 | |
|---|
| 349 | self.DBlist = [] # list of dicts |
|---|
| 350 | |
|---|
| 351 | self.noGamesLabel = noGamesLabel |
|---|
| 352 | self.winPercLabel = winPercLabel |
|---|
| 353 | self.gameinfo = gameinfo |
|---|
| 354 | |
|---|
| 355 | self.Bwins = 0 |
|---|
| 356 | self.Wwins = 0 |
|---|
| 357 | self.Owins = 0 # others: Jigo, Void, Left unfinished, ? (Unknown) |
|---|
| 358 | |
|---|
| 359 | self.appendClist = [] |
|---|
| 360 | |
|---|
| 361 | |
|---|
| 362 | def rightMouseButton(self, event): |
|---|
| 363 | |
|---|
| 364 | index = self.list.nearest(event.y) |
|---|
| 365 | if index == -1: return |
|---|
| 366 | DBindex = 0 |
|---|
| 367 | while index >= len(self.DBlist[DBindex]['current']): |
|---|
| 368 | index -= len(self.DBlist[DBindex]['current']) |
|---|
| 369 | DBindex += 1 |
|---|
| 370 | if DBindex >= len(self.DBlist) or index > len(self.DBlist[DBindex]['current']): return |
|---|
| 371 | |
|---|
| 372 | filename = strip(os.path.join(self.DBlist[DBindex]['name'], |
|---|
| 373 | self.DBlist[DBindex]['data'][self.DBlist[DBindex]['current'][index]][0] + '.sgf')) |
|---|
| 374 | |
|---|
| 375 | try: |
|---|
| 376 | file = open(filename) |
|---|
| 377 | sgf = file.read() |
|---|
| 378 | file.close() |
|---|
| 379 | c = Cursor(sgf) |
|---|
| 380 | rootNode = c.getRootNode() |
|---|
| 381 | except: |
|---|
| 382 | return |
|---|
| 383 | |
|---|
| 384 | backup = copy(rootNode) |
|---|
| 385 | |
|---|
| 386 | newRootNode = self.master.gameinfo(rootNode) |
|---|
| 387 | if backup != newRootNode: |
|---|
| 388 | c.updateRootNode(newRootNode) |
|---|
| 389 | try: |
|---|
| 390 | file = open(filename, 'w') |
|---|
| 391 | file.write(c.output()) |
|---|
| 392 | file.close() |
|---|
| 393 | except IOError: |
|---|
| 394 | showwarning('I/O Error', 'Could not write to file ' + filename) |
|---|
| 395 | |
|---|
| 396 | |
|---|
| 397 | def handleDoubleClick(self, event): |
|---|
| 398 | """ This is called upon double-clicks.""" |
|---|
| 399 | |
|---|
| 400 | index = self.list.curselection() |
|---|
| 401 | if index: |
|---|
| 402 | label = self.list.get(index) |
|---|
| 403 | self.master.openViewer(index, label) |
|---|
| 404 | |
|---|
| 405 | |
|---|
| 406 | def up(self, event): |
|---|
| 407 | if not self.list.curselection(): return |
|---|
| 408 | index = int(self.list.curselection()[0]) |
|---|
| 409 | if index != 0: |
|---|
| 410 | self.list.select_clear(index) |
|---|
| 411 | self.list.select_set(index-1) |
|---|
| 412 | self.list.see(index-1) |
|---|
| 413 | self.printGameInfo(None, index-1) |
|---|
| 414 | |
|---|
| 415 | def down(self, event): |
|---|
| 416 | if not self.list.curselection(): return |
|---|
| 417 | index = int(self.list.curselection()[0]) |
|---|
| 418 | if index != self.list.size()-1: |
|---|
| 419 | self.list.select_clear(index) |
|---|
| 420 | self.list.select_set(index+1) |
|---|
| 421 | self.list.see(index+1) |
|---|
| 422 | self.printGameInfo(None, index+1) |
|---|
| 423 | |
|---|
| 424 | def pgup(self, event): |
|---|
| 425 | if not self.list.curselection(): return |
|---|
| 426 | index = int(self.list.curselection()[0]) |
|---|
| 427 | if index >= 10: |
|---|
| 428 | self.list.select_clear(index) |
|---|
| 429 | self.list.select_set(index-10) |
|---|
| 430 | self.list.see(index-10) |
|---|
| 431 | self.printGameInfo(None, index-10) |
|---|
| 432 | elif self.list.size(): |
|---|
| 433 | self.list.select_clear(index) |
|---|
| 434 | self.list.select_set(0) |
|---|
| 435 | self.list.see(0) |
|---|
| 436 | self.printGameInfo(None, 0) |
|---|
| 437 | |
|---|
| 438 | def pgdown(self, event): |
|---|
| 439 | if not self.list.curselection(): return |
|---|
| 440 | index = int(self.list.curselection()[0]) |
|---|
| 441 | if index <= self.list.size()-10: |
|---|
| 442 | self.list.select_clear(index) |
|---|
| 443 | self.list.select_set(index+10) |
|---|
| 444 | self.list.see(index+10) |
|---|
| 445 | self.printGameInfo(None, index+10) |
|---|
| 446 | elif self.list.size(): |
|---|
| 447 | self.list.select_clear(index) |
|---|
| 448 | self.list.select_set(self.list.size()-1) |
|---|
| 449 | self.list.see(END) |
|---|
| 450 | self.printGameInfo(None, self.list.size()-1) |
|---|
| 451 | |
|---|
| 452 | def delete(self): |
|---|
| 453 | """ Clear the list. """ |
|---|
| 454 | |
|---|
| 455 | self.update() |
|---|
| 456 | |
|---|
| 457 | for db in self.DBlist: |
|---|
| 458 | if not db['disabled']: |
|---|
| 459 | db['current'] = array('L') |
|---|
| 460 | db['results'] = [] |
|---|
| 461 | |
|---|
| 462 | self.Bwins = 0 |
|---|
| 463 | self.Wwins = 0 |
|---|
| 464 | self.Owins = 0 |
|---|
| 465 | self.update() |
|---|
| 466 | |
|---|
| 467 | self.list.delete(0,END) |
|---|
| 468 | self.list.update_idletasks() |
|---|
| 469 | |
|---|
| 470 | def append(self, i, d): |
|---|
| 471 | """ Append an entry to the gamelist. """ |
|---|
| 472 | self.DBlist[i]['data'].append(d) |
|---|
| 473 | |
|---|
| 474 | if not self.DBlist[i]['disabled']: |
|---|
| 475 | |
|---|
| 476 | if d[4] == 'B': self.Bwins = self.Bwins + 1 |
|---|
| 477 | elif d[4] == 'W': self.Wwins = self.Wwins + 1 |
|---|
| 478 | else: self.Owins = self.Owins + 1 |
|---|
| 479 | |
|---|
| 480 | s = d[0] + ': ' + d[3] + ' - ' + d[2] + ' (' + d[4] + ') ' # filename: PW-PB (RES) |
|---|
| 481 | self.DBlist[i]['stringlist'].append(s) |
|---|
| 482 | |
|---|
| 483 | |
|---|
| 484 | def appendC(self, db, i, res = ''): |
|---|
| 485 | """ Append an entry to self.current """ |
|---|
| 486 | |
|---|
| 487 | db['current'].append(i) |
|---|
| 488 | db['results'].append(res) |
|---|
| 489 | self.appendClist.append((db['data'][i], res)) |
|---|
| 490 | |
|---|
| 491 | |
|---|
| 492 | def reset(self): |
|---|
| 493 | """ Reset the list, s.t. it includes all the games from self.data. """ |
|---|
| 494 | for db in self.DBlist: |
|---|
| 495 | if not db['disabled']: |
|---|
| 496 | db['current'] = array('L', range(len(db['data']))) |
|---|
| 497 | db['results'] = [''] * len(db['data']) |
|---|
| 498 | self.appendClist += map(None, db['data'], db['results']) |
|---|
| 499 | else: |
|---|
| 500 | db['current'] = array('L') |
|---|
| 501 | db['results'] = [] |
|---|
| 502 | self.list.delete(0, END) |
|---|
| 503 | self.Bwins, self.Wwins, self.Owins = 0, 0, 0 |
|---|
| 504 | self.update() |
|---|
| 505 | self.clearGameInfo() |
|---|
| 506 | |
|---|
| 507 | def update(self): |
|---|
| 508 | """ Put the changes in data, current in effect in self.list. """ |
|---|
| 509 | |
|---|
| 510 | if self.appendClist: # display appendC'ed entries |
|---|
| 511 | |
|---|
| 512 | strl = [] |
|---|
| 513 | |
|---|
| 514 | for d, res in self.appendClist: |
|---|
| 515 | if d[4] == 'B': self.Bwins = self.Bwins + 1 |
|---|
| 516 | elif d[4] == 'W': self.Wwins = self.Wwins + 1 |
|---|
| 517 | else: self.Owins = self.Owins + 1 |
|---|
| 518 | |
|---|
| 519 | s = d[0] + ': ' + d[3] + ' - ' + d[2] + ' (' + d[4] + ') ' + res |
|---|
| 520 | strl.append(s) |
|---|
| 521 | apply(self.list.insert, [END] + strl) |
|---|
| 522 | |
|---|
| 523 | self.appendClist = [] |
|---|
| 524 | |
|---|
| 525 | for db in self.DBlist: |
|---|
| 526 | if db['stringlist']: # display append'ed entries |
|---|
| 527 | apply(self.list.insert, [END] + db['stringlist']) |
|---|
| 528 | |
|---|
| 529 | db['current'].extend(array('L', range(len(db['data'])-len(db['stringlist']), len(db['data'])))) |
|---|
| 530 | db['results'] = db['results'] + [''] * len(db['stringlist']) |
|---|
| 531 | db['stringlist'] = [] |
|---|
| 532 | |
|---|
| 533 | noOfG = self.noOfGames() |
|---|
| 534 | self.noGamesLabel.config(text = `noOfG` + ' games') |
|---|
| 535 | |
|---|
| 536 | if noOfG: |
|---|
| 537 | Bperc = self.Bwins * 100.0 / noOfG |
|---|
| 538 | Wperc = self.Wwins * 100.0 / noOfG |
|---|
| 539 | self.winPercLabel.config(text='B: %1.1f%%, W: %1.1f%%' % (Bperc, Wperc)) |
|---|
| 540 | else: self.winPercLabel.config(text='') |
|---|
| 541 | |
|---|
| 542 | def noOfGames(self): |
|---|
| 543 | return reduce(lambda x,y:x+y, [ len(db['current']) for db in self.DBlist if not db['disabled'] ], 0) |
|---|
| 544 | |
|---|
| 545 | def printGameInfo(self, event, index = -1): |
|---|
| 546 | """ Print game info of selected game. """ |
|---|
| 547 | |
|---|
| 548 | if index == -1: |
|---|
| 549 | index = self.list.nearest(event.y) |
|---|
| 550 | |
|---|
| 551 | if index == -1: |
|---|
| 552 | return |
|---|
| 553 | |
|---|
| 554 | DBindex = 0 |
|---|
| 555 | |
|---|
| 556 | while index >= len(self.DBlist[DBindex]['current']): |
|---|
| 557 | index -= len(self.DBlist[DBindex]['current']) |
|---|
| 558 | DBindex += 1 |
|---|
| 559 | |
|---|
| 560 | if DBindex >= len(self.DBlist) or index > len(self.DBlist[DBindex]['current']): return |
|---|
| 561 | |
|---|
| 562 | filename = strip(os.path.join(self.DBlist[DBindex]['name'], |
|---|
| 563 | self.DBlist[DBindex]['data'][self.DBlist[DBindex]['current'][index]][0] + '.sgf')) |
|---|
| 564 | |
|---|
| 565 | try: |
|---|
| 566 | f = open(filename) |
|---|
| 567 | sgf = f.read() |
|---|
| 568 | f.close() |
|---|
| 569 | |
|---|
| 570 | node = Cursor(sgf).getRootNode() |
|---|
| 571 | |
|---|
| 572 | except: |
|---|
| 573 | return |
|---|
| 574 | |
|---|
| 575 | t = '' |
|---|
| 576 | |
|---|
| 577 | if node.has_key('PW'): t = t + node['PW'][0] |
|---|
| 578 | else: t = t + ' ?' |
|---|
| 579 | if node.has_key('WR'): t = t + ', ' + node['WR'][0] |
|---|
| 580 | |
|---|
| 581 | t = t + ' - ' |
|---|
| 582 | |
|---|
| 583 | if node.has_key('PB'): t = t + node['PB'][0] |
|---|
| 584 | else: t = t + ' ?' |
|---|
| 585 | if node.has_key('BR'): t = t + ', ' + node['BR'][0] |
|---|
| 586 | |
|---|
| 587 | if node.has_key('RE'): t = t + ', ' + node['RE'][0] |
|---|
| 588 | if node.has_key('KM'): t = t + ' (Komi ' + node['KM'][0] + ')' |
|---|
| 589 | if node.has_key('HA' |
|---|