| 1 |
|
|---|
| 2 |
|
|---|
| 3 |
|
|---|
| 4 |
|
|---|
| 5 |
|
|---|
| 6 |
|
|---|
| 7 |
|
|---|
| 8 |
|
|---|
| 9 |
|
|---|
| 10 |
|
|---|
| 11 |
|
|---|
| 12 |
|
|---|
| 13 |
|
|---|
| 14 |
|
|---|
| 15 |
|
|---|
| 16 |
|
|---|
| 17 |
|
|---|
| 18 |
|
|---|
| 19 |
|
|---|
| 20 |
|
|---|
| 21 |
|
|---|
| 22 |
|
|---|
| 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 |
if sys.platform.startswith('darwin'): |
|---|
| 69 |
self.bind('<M2-Button-1>', self.selStart) |
|---|
| 70 |
self.bind('<M2-B1-Motion>', self.selDrag) |
|---|
| 71 |
else: |
|---|
| 72 |
self.bind('<Button-3>', self.selStart) |
|---|
| 73 |
self.bind('<B3-Motion>', self.selDrag) |
|---|
| 74 |
self.bind('<Shift-1>', self.wildcard) |
|---|
| 75 |
|
|---|
| 76 |
def resize(self, event): |
|---|
| 77 |
Board.resize(self, event) |
|---|
| 78 |
for x,y in self.wildcards.keys(): |
|---|
| 79 |
x1, x2, y1, y2 = self.getPixelCoord((x,y),1) |
|---|
| 80 |
self.wildcards[(x,y)] = self.create_oval(x1+4, x2+4, y1-4, y2-4, fill = 'green', |
|---|
| 81 |
tags=('wildcard','non-bg')) |
|---|
| 82 |
|
|---|
| 83 |
self.delete('selection') |
|---|
| 84 |
if self.selection != ((1,1),(19,19)) and self.selection[1] != (0,0): |
|---|
| 85 |
p0 = self.getPixelCoord(self.selection[0],1) |
|---|
| 86 |
p1 = self.getPixelCoord((self.selection[1][0]+1, self.selection[1][1]+1),1) |
|---|
| 87 |
|
|---|
| 88 |
self.create_rectangle(p0[0], p0[1], p1[0], p1[1], fill='brown', stipple='gray50', |
|---|
| 89 |
tags='selection') |
|---|
| 90 |
self.tkraise('non-bg') |
|---|
| 91 |
|
|---|
| 92 |
self.update_idletasks() |
|---|
| 93 |
|
|---|
| 94 |
def wildcard(self, event): |
|---|
| 95 |
""" Place a wildcard at position of click. """ |
|---|
| 96 |
x, y = self.getBoardCoord((event.x, event.y), 1) |
|---|
| 97 |
if not x*y or self.status.has_key((x,y)) \ |
|---|
| 98 |
or self.wildcards.has_key((x,y)): return |
|---|
| 99 |
|
|---|
| 100 |
x1, x2, y1, y2 = self.getPixelCoord((x,y),1) |
|---|
| 101 |
self.wildcards[(x,y)] = self.create_oval(x1+4, x2+4, y1-4, y2-4, fill = 'green', |
|---|
| 102 |
tags=('wildcard','non-bg')) |
|---|
| 103 |
|
|---|
| 104 |
self.changed.set(1) |
|---|
| 105 |
|
|---|
| 106 |
|
|---|
| 107 |
def delWildcards(self): |
|---|
| 108 |
if self.wildcards: self.changed.set(1) |
|---|
| 109 |
self.delete('wildcard') |
|---|
| 110 |
self.wildcards = {} |
|---|
| 111 |
|
|---|
| 112 |
|
|---|
| 113 |
|
|---|
| 114 |
def selStart(self, event): |
|---|
| 115 |
""" React to right-click. """ |
|---|
| 116 |
self.delete('selection') |
|---|
| 117 |
x, y = self.getBoardCoord((event.x, event.y), 1) |
|---|
| 118 |
x = max(x, 1) |
|---|
| 119 |
y = max(y, 1) |
|---|
| 120 |
self.selection = ((x,y), (0,0)) |
|---|
| 121 |
if self.smartFixedColor.get(): |
|---|
| 122 |
self.fixedColor.set(1) |
|---|
| 123 |
self.changed.set(1) |
|---|
| 124 |
|
|---|
| 125 |
def selDrag(self, event): |
|---|
| 126 |
""" React to right-mouse-key-drag. """ |
|---|
| 127 |
pos = self.getBoardCoord((event.x, event.y), 1) |
|---|
| 128 |
if pos[0] >= self.selection[0][0] and pos[1] >= self.selection[0][1]: |
|---|
| 129 |
self.setSelection(self.selection[0], pos) |
|---|
| 130 |
|
|---|
| 131 |
|
|---|
| 132 |
def setSelection(self, pos0, pos1): |
|---|
| 133 |
self.selection = (pos0, pos1) |
|---|
| 134 |
self.delete('selection') |
|---|
| 135 |
p0 = self.getPixelCoord(pos0,1) |
|---|
| 136 |
p1 = self.getPixelCoord((pos1[0]+1, pos1[1]+1), 1) |
|---|
| 137 |
|
|---|
| 138 |
self.create_rectangle(p0[0], p0[1], p1[0], p1[1], fill='brown', stipple='gray50', |
|---|
| 139 |
tags='selection') |
|---|
| 140 |
self.tkraise('non-bg') |
|---|
| 141 |
|
|---|
| 142 |
if self.smartFixedColor.get(): |
|---|
| 143 |
if self.selection == ((1,1), (19,19)): |
|---|
| 144 |
self.fixedColor.set(1) |
|---|
| 145 |
else: |
|---|
| 146 |
self.fixedColor.set(0) |
|---|
| 147 |
|
|---|
| 148 |
|
|---|
| 149 |
def newPosition(self): |
|---|
| 150 |
""" Clear board, selection. """ |
|---|
| 151 |
self.delete('selection') |
|---|
| 152 |
self.clear() |
|---|
| 153 |
self.delLabels() |
|---|
| 154 |
self.delMarks() |
|---|
| 155 |
self.delWildcards() |
|---|
| 156 |
self.selection = ((1,1),(19,19)) |
|---|
| 157 |
|
|---|
| 158 |
if self.smartFixedColor.get(): |
|---|
| 159 |
self.fixedColor.set(1) |
|---|
| 160 |
|
|---|
| 161 |
|
|---|
| 162 |
|
|---|
| 163 |
def snapshot(self): |
|---|
| 164 |
""" Return a dictionary which contains the data of all the objects |
|---|
| 165 |
currently displayed on the board. """ |
|---|
| 166 |
|
|---|
| 167 |
data = {} |
|---|
| 168 |
data['status'] = copy(self.status) |
|---|
| 169 |
data['marks'] = copy(self.marks) |
|---|
| 170 |
data['labels'] = copy(self.labels) |
|---|
| 171 |
data['wildcards'] = copy(self.wildcards) |
|---|
| 172 |
data['selection'] = self.selection |
|---|
| 173 |
data['currentcolor'] = self.currentColor |
|---|
| 174 |
data['undostack'] = deepcopy(self.undostack) |
|---|
| 175 |
|
|---|
| 176 |
return data |
|---|
| 177 |
|
|---|
| 178 |
def restore(self, d): |
|---|
| 179 |
""" Restore a board position from a 'snapshot' dictionary. """ |
|---|
| 180 |
|
|---|
| 181 |
self.newPosition() |
|---|
| 182 |
for x in d['status'].keys(): self.play(x, d['status'][x]) |
|---|
| 183 |
for x in d['marks'].keys(): self.placeMark(x, d['marks'][x]) |
|---|
| 184 |
for x in d['labels'].keys(): self.placeLabel(x, d['labels'][x][0], d['labels'][x][1]) |
|---|
| 185 |
for x,y in d['wildcards'].keys(): |
|---|
| 186 |
x1, x2, y1, y2 = self.getPixelCoord((x,y),1) |
|---|
| 187 |
self.wildcards[(x,y)] = self.create_oval(x1+4, x2+4, y1-4, y2-4, fill = 'green', |
|---|
| 188 |
tags=('wildcard','non-bg')) |
|---|
| 189 |
|
|---|
| 190 |
if d['selection'] != ((1,1),(19,19)) and d['selection'][1] != (0,0): |
|---|
| 191 |
p0 = self.getPixelCoord(d['selection'][0],1) |
|---|
| 192 |
p1 = self.getPixelCoord((d['selection'][1][0]+1, d['selection'][1][1]+1),1) |
|---|
| 193 |
|
|---|
| 194 |
self.create_rectangle(p0[0], p0[1], p1[0], p1[1], fill='brown', stipple='gray50', |
|---|
| 195 |
tags='selection') |
|---|
| 196 |
self.tkraise('non-bg') |
|---|
| 197 |
self.selection = d['selection'] |
|---|
| 198 |
|
|---|
| 199 |
self.currentColor = d['currentcolor'] |
|---|
| 200 |
self.undostack = deepcopy(d['undostack']) |
|---|
| 201 |
|
|---|
| 202 |
|
|---|
| 203 |
|
|---|
| 204 |
class chooseDirectory(Dialog): |
|---|
| 205 |
""" A wrapper tor the Tk chooseDirectory widget. """ |
|---|
| 206 |
|
|---|
| 207 |
command = "tk_chooseDirectory" |
|---|
| 208 |
|
|---|
| 209 |
def _fixresult(self, widget, result): |
|---|
| 210 |
if result: |
|---|
| 211 |
self.options["initialdir"] = result |
|---|
| 212 |
self.directory = result |
|---|
| 213 |
return result |
|---|
| 214 |
|
|---|
| 215 |
def askdirectory(**options): |
|---|
| 216 |
return apply(chooseDirectory, (), options).show() |
|---|
| 217 |
|
|---|
| 218 |
|
|---|
| 219 |
|
|---|
| 220 |
class TextEditor: |
|---|
| 221 |
""" A very simple text editor, based on the Tkinter ScrolledText widget. |
|---|
| 222 |
You can perform very limited editing, and save the result to a file. """ |
|---|
| 223 |
|
|---|
| 224 |
def __init__(self, t = '', defpath='', font = None): |
|---|
| 225 |
|
|---|
| 226 |
if font is None: |
|---|
| 227 |
font = (StringVar(), IntVar(), StringVar()) |
|---|
| 228 |
font[0].set('Courier') |
|---|
| 229 |
font[1].set(10) |
|---|
| 230 |
font[2].set('') |
|---|
| 231 |
|
|---|
| 232 |
self.window = Toplevel() |
|---|
| 233 |
|
|---|
| 234 |
self.window.protocol('WM_DELETE_WINDOW', self.quit) |
|---|
| 235 |
|
|---|
| 236 |
self.text = ScrolledText(self.window, width=70, height=30, |
|---|
| 237 |
font=(font[0].get(), font[1].get(), font[2].get())) |
|---|
| 238 |
self.text.pack(fill=BOTH, expand=YES) |
|---|
| 239 |
self.text.insert(END, t) |
|---|
| 240 |
|
|---|
| 241 |
self.buttonFrame = Frame(self.window) |
|---|
| 242 |
self.buttonFrame.pack(side=RIGHT) |
|---|
| 243 |
|
|---|
| 244 |
Button(self.buttonFrame, text='Quit', command=self.quit).pack(side=RIGHT) |
|---|
| 245 |
Button(self.buttonFrame, text='Save as', command=self.saveas).pack(side=RIGHT) |
|---|
| 246 |
|
|---|
| 247 |
|
|---|
| 248 |
self.window.focus_force() |
|---|
| 249 |
|
|---|
| 250 |
if defpath: |
|---|
| 251 |
self.defpath = defpath |
|---|
| 252 |
else: |
|---|
| 253 |
self.defpath = os.curdir |
|---|
| 254 |
|
|---|
| 255 |
def saveas(self): |
|---|
| 256 |
f = tkFileDialog.asksaveasfilename(initialdir = self.defpath) |
|---|
| 257 |
try: |
|---|
| 258 |
file = open(f, 'w') |
|---|
| 259 |
file.write(self.text.get('1.0', END)) |
|---|
| 260 |
file.close() |
|---|
| 261 |
except IOError: |
|---|
| 262 |
showwarning('IO Error', 'Cannot write to ' + f) |
|---|
| 263 |
|
|---|
| 264 |
def quit(self): |
|---|
| 265 |
self.window.destroy() |
|---|
| 266 |
|
|---|
| 267 |
|
|---|
| 268 |
|
|---|
| 269 |
class ESR_TextEditor(TextEditor): |
|---|
| 270 |
"""The text editor which is used by the exportSearchResults function. |
|---|
| 271 |
It adds a button to include the complete game list to the TextEditor.""" |
|---|
| 272 |
|
|---|
| 273 |
def __init__(self, master, style, t='', defpath='', font=None): |
|---|
| 274 |
TextEditor.__init__(self, t, defpath, font) |
|---|
| 275 |
self.master = master |
|---|
| 276 |
self.style = style |
|---|
| 277 |
|
|---|
| 278 |
Button(self.buttonFrame, text='Include game list', command=self.includeGameList).pack(side=RIGHT) |
|---|
| 279 |
|
|---|
| 280 |
def includeGameList(self): |
|---|
| 281 |
if self.style == 'wiki': |
|---|
| 282 |
self.text.insert(END, '\n\n!Game list\n\n' + join(self.master.gamelist.list.get(0, END), ' %%%\n')) |
|---|
| 283 |
else: |
|---|
| 284 |
self.text.insert(END, '\n\nGame list\n\n' + join(self.master.gamelist.list.get(0, END), '\n')) |
|---|
| 285 |
|
|---|
| 286 |
|
|---|
| 287 |
|
|---|
| 288 |
|
|---|
| 289 |
class ScrolledList(Frame): |
|---|
| 290 |
""" A listbox with vertical and horizontal scrollbars. """ |
|---|
| 291 |
|
|---|
| 292 |
def __init__(self, parent, **kw): |
|---|
| 293 |
Frame.__init__(self, parent) |
|---|
| 294 |
|
|---|
| 295 |
self.sbar = Scrollbar(self) |
|---|
| 296 |
self.sbar1 = Scrollbar(self) |
|---|
| 297 |
|
|---|
| 298 |
if not kw: kw = {} |
|---|
| 299 |
|
|---|
| 300 |
for var, value in [('height', 12), ('width', 40), ('relief', SUNKEN), |
|---|
| 301 |
('selectmode', SINGLE), ('takefocus', 1)]: |
|---|
| 302 |
if not kw.has_key(var): kw[var] = value |
|---|
| 303 |
|
|---|
| 304 |
self.list = Listbox(self, kw) |
|---|
| 305 |
self.sbar.config(command = self.list.yview) |
|---|
| 306 |
self.sbar1.config(command = self.list.xview, orient='horizontal') |
|---|
| 307 |
self.list.config(xscrollcommand = self.sbar1.set, yscrollcommand = self.sbar.set) |
|---|
| 308 |
self.list.grid(row=0, column=0, sticky=NSEW) |
|---|
| 309 |
self.sbar.grid(row=0, column=1, sticky=NSEW) |
|---|
| 310 |
self.sbar1.grid(row=1, column=0, sticky=NSEW) |
|---|
| 311 |
|
|---|
| 312 |
self.rowconfigure(0, weight=1) |
|---|
| 313 |
self.columnconfigure(0, weight=1) |
|---|
| 314 |
|
|---|
| 315 |
self.focus_force() |
|---|
| 316 |
|
|---|
| 317 |
|
|---|
| 318 |
|
|---|
| 319 |
|
|---|
| 320 |
class GameList(ScrolledList): |
|---|
| 321 |
""" This is a scrolled list which shows the game list. All the underlying data |
|---|
| 322 |
is contained in self.DBlist, which is a list of dictionaries containing the |
|---|
| 323 |
information for the single databases. self.DBlist[i] will contain the keys |
|---|
| 324 |
'name': name (=path) of the database |
|---|
| 325 |
'data': list of all games in the database. |
|---|
| 326 |
This is a list of tuples of the form |
|---|
| 327 |
(filename, namelistIndex, PB, PW, result, signature) |
|---|
| 328 |
'current': a list of the indices of the games in data which are in the current |
|---|
| 329 |
game list |
|---|
| 330 |
'results': for each game in current, the list of matches found in the previous |
|---|
| 331 |
search (If there are lots of matches, this list will consume a lot |
|---|
| 332 |
of memory. Maybe at some time I will get around to fix this ...) |
|---|
| 333 |
'stringlist': A list of strings from entries in 'data' which are not yet |
|---|
| 334 |
displayed in the ScrolledList (to speed things up by inserting |
|---|
| 335 |
many entries at once into the ScrolledList) |
|---|
| 336 |
""" |
|---|
| 337 |
|
|---|
| 338 |
def __init__(self, parent, master, noGamesLabel, winPercLabel, gameinfo): |
|---|
| 339 |
ScrolledList.__init__(self, parent) |
|---|
| 340 |
self.list.config(width=52, height=10) |
|---|
| 341 |
self.list.bind('<Button-1>', self.printGameInfo) |
|---|
| 342 |
|
|---|
| 343 |
self.bind('<Up>', self.up) |
|---|
| 344 |
self.bind('<Down>', self.down) |
|---|
| 345 |
self.bind('<Prior>', self.pgup) |
|---|
| 346 |
self.bind('<Next>', self.pgdown) |
|---|
| 347 |
self.bind('<Return>', self.handleDoubleClick) |
|---|
| 348 |
self.list.bind('<Double-1>', self.handleDoubleClick) |
|---|
| 349 |
self.list.bind('<Button-3>', self.rightMouseButton) |
|---|
| 350 |
|
|---|
| 351 |
self.master = master |
|---|
| 352 |
|
|---|
| 353 |
self.DBlist = [] |
|---|
| 354 |
|
|---|
| 355 |
self.noGamesLabel = noGamesLabel |
|---|
| 356 |
self.winPercLabel = winPercLabel |
|---|
| 357 |
self.gameinfo = gameinfo |
|---|
| 358 |
|
|---|
| 359 |
self.Bwins = 0 |
|---|
| 360 |
self.Wwins = 0 |
|---|
| 361 |
self.Owins = 0 |
|---|
| 362 |
|
|---|
| 363 |
self.appendClist = [] |
|---|
| 364 |
|
|---|
| 365 |
|
|---|
| 366 |
def rightMouseButton(self, event): |
|---|
| 367 |
|
|---|
| 368 |
index = self.list.nearest(event.y) |
|---|
| 369 |
if index == -1: return |
|---|
| 370 |
DBindex = 0 |
|---|
| 371 |
while index >= len(self.DBlist[DBindex]['current']): |
|---|
| 372 |
index -= len(self.DBlist[DBindex]['current']) |
|---|
| 373 |
DBindex += 1 |
|---|
| 374 |
if DBindex >= len(self.DBlist) or index > len(self.DBlist[DBindex]['current']): return |
|---|
| 375 |
|
|---|
| 376 |
filename = strip(os.path.join(self.DBlist[DBindex]['name'], |
|---|
| 377 |
self.DBlist[DBindex]['data'][self.DBlist[DBindex]['current'][index]][0] + '.sgf')) |
|---|
| 378 |
|
|---|
| 379 |
try: |
|---|
| 380 |
file = open(filename) |
|---|
| 381 |
sgf = file.read() |
|---|
| 382 |
file.close() |
|---|
| 383 |
c = Cursor(sgf) |
|---|
| 384 |
rootNode = c.getRootNode() |
|---|
| 385 |
except: |
|---|
| 386 |
return |
|---|
| 387 |
|
|---|
| 388 |
backup = copy(rootNode) |
|---|
| 389 |
|
|---|
| 390 |
newRootNode = self.master.gameinfo(rootNode) |
|---|
| 391 |
if backup != newRootNode: |
|---|
| 392 |
c.updateRootNode(newRootNode) |
|---|
| 393 |
try: |
|---|
| 394 |
file = open(filename, 'w') |
|---|
| 395 |
file.write(c.output()) |
|---|
| 396 |
file.close() |
|---|
| 397 |
except IOError: |
|---|
| 398 |
showwarning('I/O Error', 'Could not write to file ' + filename) |
|---|
| 399 |
|
|---|
| 400 |
|
|---|
| 401 |
def handleDoubleClick(self, event): |
|---|
| 402 |
""" This is called upon double-clicks.""" |
|---|
| 403 |
|
|---|
| 404 |
index = self.list.curselection() |
|---|
| 405 |
if index: |
|---|
| 406 |
label = self.list.get(index) |
|---|
| 407 |
self.master.openViewer(index, label) |
|---|
| 408 |
|
|---|
| 409 |
|
|---|
| 410 |
def up(self, event): |
|---|
| 411 |
if not self.list.curselection(): return |
|---|
| 412 |
index = int(self.list.curselection()[0]) |
|---|
| 413 |
if index != 0: |
|---|
| 414 |
self.list.select_clear(index) |
|---|
| 415 |
self.list.select_set(index-1) |
|---|
| 416 |
self.list.see(index-1) |
|---|
| 417 |
self.printGameInfo(None, index-1) |
|---|
| 418 |
|
|---|
| 419 |
def down(self, event): |
|---|
| 420 |
if not self.list.curselection(): return |
|---|
| 421 |
index = int(self.list.curselection()[0]) |
|---|
| 422 |
if index != self.list.size()-1: |
|---|
| 423 |
self.list.select_clear(index) |
|---|
| 424 |
self.list.select_set(index+1) |
|---|
| 425 |
self.list.see(index+1) |
|---|
| 426 |
self.printGameInfo(None, index+1) |
|---|
| 427 |
|
|---|
| 428 |
def pgup(self, event): |
|---|
| 429 |
if not self.list.curselection(): return |
|---|
| 430 |
index = int(self.list.curselection()[0]) |
|---|
| 431 |
if index >= 10: |
|---|
| 432 |
self.list.select_clear(index) |
|---|
| 433 |
self.list.select_set(index-10) |
|---|
| 434 |
self.list.see(index-10) |
|---|
| 435 |
self.printGameInfo(None, index-10) |
|---|
| 436 |
elif self.list.size(): |
|---|
| 437 |
self.list.select_clear(index) |
|---|
| 438 |
self.list.select_set(0) |
|---|
| 439 |
self.list.see(0) |
|---|
| 440 |
self.printGameInfo(None, 0) |
|---|
| 441 |
|
|---|
| 442 |
def pgdown(self, event): |
|---|
| 443 |
if not self.list.curselection(): return |
|---|
| 444 |
index = int(self.list.curselection()[0]) |
|---|
| 445 |
if index <= self.list.size()-10: |
|---|
| 446 |
self.list.select_clear(index) |
|---|
| 447 |
self.list.select_set(index+10) |
|---|
| 448 |
self.list.see(index+10) |
|---|
| 449 |
self.printGameInfo(None, index+10) |
|---|
| 450 |
elif self.list.size(): |
|---|
| 451 |
self.list.select_clear(index) |
|---|
| 452 |
self.list.select_set(self.list.size()-1) |
|---|
| 453 |
self.list.see(END) |
|---|
| 454 |
self.printGameInfo(None, self.list.size()-1) |
|---|
| 455 |
|
|---|
| 456 |
def delete(self): |
|---|
| 457 |
""" Clear the list. """ |
|---|
| 458 |
|
|---|
| 459 |
self.update() |
|---|
| 460 |
|
|---|
| 461 |
for db in self.DBlist: |
|---|
| 462 |
if not db['disabled']: |
|---|
| 463 |
db['current'] = array('L') |
|---|
| 464 |
db['results'] = [] |
|---|
| 465 |
|
|---|
| 466 |
self.Bwins = 0 |
|---|
| 467 |
self.Wwins = 0 |
|---|
| 468 |
self.Owins = 0 |
|---|
| 469 |
self.update() |
|---|
| 470 |
|
|---|
| 471 |
self.list.delete(0,END) |
|---|
| 472 |
self.list.update_idletasks() |
|---|
| 473 |
|
|---|
| 474 |
def append(self, i, d): |
|---|
| 475 |
""" Append an entry to the gamelist. """ |
|---|
| 476 |
self.DBlist[i]['data'].append(d) |
|---|
| 477 |
|
|---|
| 478 |
if not self.DBlist[i]['disabled']: |
|---|
| 479 |
|
|---|
| 480 |
if d[4] == 'B': self.Bwins = self.Bwins + 1 |
|---|
| 481 |
elif d[4] == 'W': self.Wwins = self.Wwins + 1 |
|---|
| 482 |
else: self.Owins = self.Owins + 1 |
|---|
| 483 |
|
|---|
| 484 |
s = d[0] + ': ' + d[3] + ' - ' + d[2] + ' (' + d[4] + ') ' |
|---|
| 485 |
self.DBlist[i]['stringlist'].append(s) |
|---|
| 486 |
|
|---|
| 487 |
|
|---|
| 488 |
def appendC(self, db, i, res = ''): |
|---|
| 489 |
""" Append an entry to self.current """ |
|---|
| 490 |
|
|---|
| 491 |
db['current'].append(i) |
|---|
| 492 |
db['results'].append(res) |
|---|
| 493 |
self.appendClist.append((db['data'][i], res)) |
|---|
| 494 |
|
|---|
| 495 |
|
|---|
| 496 |
def reset(self): |
|---|
| 497 |
""" Reset the list, s.t. it includes all the games from self.data. """ |
|---|
| 498 |
for db in self.DBlist: |
|---|
| 499 |
if not db['disabled']: |
|---|
| 500 |
db['current'] = array('L', range(len(db['data']))) |
|---|
| 501 |
db['results'] = [''] * len(db['data']) |
|---|
| 502 |
self.appendClist += map(None, db['data'], db['results']) |
|---|
| 503 |
else: |
|---|
| 504 |
db['current'] = array('L') |
|---|
| 505 |
db['results'] = [] |
|---|
| 506 |
self.list.delete(0, END) |
|---|
| 507 |
self.Bwins, self.Wwins, self.Owins = 0, 0, 0 |
|---|
| 508 |
self.update() |
|---|
| 509 |
self.clearGameInfo() |
|---|
| 510 |
|
|---|
| 511 |
def update(self): |
|---|
| 512 |
""" Put the changes in data, current in effect in self.list. """ |
|---|
| 513 |
|
|---|
| 514 |
if self.appendClist: |
|---|
| 515 |
|
|---|
| 516 |
strl = [] |
|---|
| 517 |
|
|---|
| 518 |
for d, res in self.appendClist: |
|---|
| 519 |
if d[4] == 'B': self.Bwins = self.Bwins + 1 |
|---|
| 520 |
elif d[4] == 'W': self.Wwins = self.Wwins + 1 |
|---|
| 521 |
else: self.Owins = self.Owins + 1 |
|---|
| 522 |
|
|---|
| 523 |
s = d[0] + ': ' + d[3] + ' - ' + d[2] + ' (' + d[4] + ') ' + res |
|---|
| 524 |
strl.append(s) |
|---|
| 525 |
apply(self.list.insert, [END] + strl) |
|---|
| 526 |
|
|---|
| 527 |
self.appendClist = [] |
|---|
| 528 |
|
|---|
| 529 |
for db in self.DBlist: |
|---|
| 530 |
if db['stringlist']: |
|---|
| 531 |
apply(self.list.insert, [END] + db['stringlist']) |
|---|
| 532 |
|
|---|
| 533 |
db['current'].extend(array('L', range(len(db['data'])-len(db['stringlist']), len(db['data'])))) |
|---|
| 534 |
db['results'] = db['results'] + [''] * len(db['stringlist']) |
|---|
| 535 |
db['stringlist'] = [] |
|---|
| 536 |
|
|---|
| 537 |
noOfG = self.noOfGames() |
|---|
| 538 |
self.noGamesLabel.config(text = `noOfG` + ' games') |
|---|
| 539 |
|
|---|
| 540 |
if noOfG: |
|---|
| 541 |
Bperc = self.Bwins * 100.0 / noOfG |
|---|
| 542 |
Wperc = self.Wwins * 100.0 / noOfG |
|---|
| 543 |
self.winPercLabel.config(text='B: %1.1f%%, W: %1.1f%%' % (Bperc, Wperc)) |
|---|
| 544 |
else: self.winPercLabel.config(text='') |
|---|
| 545 |
|
|---|
| 546 |
def noOfGames(self): |
|---|
| 547 |
return reduce(lambda x,y:x+y, [ len(db['current']) for db in self.DBlist if not db['disabled'] ], 0) |
|---|
| 548 |
|
|---|
| 549 |
def printGameInfo(self, event, index = -1): |
|---|
| 550 |
""" Print game info of selected game. """ |
|---|
| 551 |
|
|---|
| 552 |
if index == -1: |
|---|
| 553 |
index = self.list.nearest(event.y) |
|---|
| 554 |
|
|---|
| 555 |
if index == -1: |
|---|
| 556 |
return |
|---|
| 557 |
|
|---|
| 558 |
DBindex = 0 |
|---|
| 559 |
|
|---|
| 560 |
while index >= len(self.DBlist[DBindex]['current']): |
|---|
| 561 |
index -= len(self.DBlist[DBindex]['current']) |
|---|
| 562 |
DBindex += 1 |
|---|
| 563 |
|
|---|
| 564 |
if DBindex >= len(self.DBlist) or index > len(self.DBlist[DBindex]['current']): return |
|---|
| 565 |
|
|---|
| 566 |
filename = strip(os.path.join(self.DBlist[DBindex]['name'], |
|---|
| 567 |
self.DBlist[DBindex]['data'][self.DBlist[DBindex]['current'][index]][0] + '.sgf')) |
|---|
| 568 |
|
|---|
| 569 |
try: |
|---|
| 570 |
f = open(filename) |
|---|
| 571 |
sgf = f.read() |
|---|
| 572 |
f.close() |
|---|
| 573 |
|
|---|
| 574 |
node = Cursor(sgf).getRootNode() |
|---|
| 575 |
|
|---|
| 576 |
except: |
|---|
| 577 |
return |
|---|
| 578 |
|
|---|
| 579 |
t = '' |
|---|
| 580 |
|
|---|
| 581 |
if node.has_key('PW'): t = t + node['PW'][0] |
|---|
| 582 |
else: t = t + ' ?' |
|---|
| 583 |
if node.has_key('WR'): t = t + ', ' + node['WR'][0] |
|---|
| 584 |
|
|---|
| 585 |
t = t + ' - ' |
|---|
| 586 |
|
|---|
| 587 |
if node.has_key('PB'): t = t + node['PB'][0] |
|---|
| 588 |
else: t = t + ' ?' |
|---|
| 589 |
if node.has_key('BR'): t = t + ', ' + node['BR'][0] |
|---|
| 590 |
|
|---|
| 591 |
if node.has_key('RE'): t = t + ', ' + node['RE'][0] |
|---|
| 592 |
if node.has_key('KM'): t = t + ' (Komi ' + node['KM'][0] + ')' |
|---|
| 593 |
if node.has_key('HA'): t = t + ' (Hcp ' + node['HA'][0] + ')' |
|---|
| 594 |
|
|---|
| 595 |
t = t + '\n' |
|---|
| 596 |
|
|---|
| 597 |
if node.has_key('EV'): t = t + node['EV'][0] + ', ' |
|---|
| 598 |
if node.has_key('RO'): t = t + node['RO'][0] + ', ' |
|---|
| 599 |
if node.has_key('DT'): t = t + node['DT'][0] + '\n' |
|---|
| 600 |
|
|---|
| 601 |
if node.has_key('GC'): |
|---|
| 602 |
gc = node['GC'][0] |
|---|
| 603 |
gc = replace(gc, '\n\r', ' ') |
|---|
| 604 |
gc = replace(gc, '\r\n', ' ') |
|---|
| 605 |
gc = replace(gc, '\r', ' ') |
|---|
| 606 |
gc = replace(gc, '\n', ' ') |
|---|
| 607 |
|
|---|
| 608 |
t = t + gc |
|---|
| 609 |
|
|---|
| 610 |
self.gameinfo.config(state=NORMAL) |
|---|
| 611 |
self.gameinfo.delete('1.0', END) |
|---|
| 612 |
self.gameinfo.insert('1.0', t) |
|---|
| 613 |
self.gameinfo.config(state=DISABLED) |
|---|
| 614 |
|
|---|
| 615 |
def clearGameInfo(self): |
|---|
| 616 |
self.gameinfo.config(state=NORMAL) |
|---|
| 617 |
self.gameinfo.delete('1.0', END) |
|---|
| 618 |
self.gameinfo.config(state=DISABLED) |
|---|
| 619 |
|
|---|
| 620 |
def readDB(self, dbpath): |
|---|
| 621 |
""" Read the database files containing the information for a pattern search. """ |
|---|
| 622 |
|
|---|
| 623 |
try: |
|---|
| 624 |
posTable = array('L') |
|---|
| 625 |
mainlistArr = array('B') |
|---|
| 626 |
finalpos = array('B') |
|---|
| 627 |
hashT = array('l') |
|---|
| 628 |
hash = array('B') |
|---|
| 629 |
|
|---|
| 630 |
filelist = [(mainlistArr, 'lists'), (posTable, 'posTable'), |
|---|
| 631 |
(finalpos, 'finalpos'), |
|---|
| 632 |
(hashT, 'hashT'), (hash, 'hash')] |
|---|
| 633 |
|
|---|
| 634 |
for var, filename in filelist: |
|---|
| 635 |
file = open(os.path.join(dbpath, filename+'.db'), 'rb') |
|---|
| 636 |
while 1: |
|---|
| 637 |
try: var.fromfile(file, 1000000) |
|---|
| 638 |
except EOFError: break |
|---|
| 639 |
file.close() |
|---|
| 640 |
|
|---|
| 641 |
return tuple([x[0] for x in filelist]) |
|---|
| 642 |
|
|---|
| 643 |
except IOError: |
|---|
| 644 |
showwarning('Error', 'Database files not found: '+dbpath) |
|---|
| 645 |
return None, None, None, None, None |
|---|
| 646 |
|
|---|
| 647 |
def checkDuplicate(self, sig, pos, strict): |
|---|
| 648 |
""" Check if a game with Dyer signature sig and final position pos |
|---|
| 649 |
occurs among the games in self.data. """ |
|---|
| 650 |
|
|---|
| 651 |
duplist = [] |
|---|
| 652 |
for db in self.DBlist: |
|---|
| 653 |
if db['disabled']: continue |
|---|
| 654 |
for g in db['data']: |
|---|
| 655 |
if sig == g[5]: |
|---|
| 656 |
if not strict: |
|---|
| 657 |
duplist.append(os.path.join(db['name'], g[0])) |
|---|
| 658 |
else: |
|---|
| 659 |
mainlistArr, posTable, finalpos, hashT, hash = self.readDB(db['name']) |
|---|
| 660 |
if not finalpos: continue |
|---|
| 661 |
if pos == finalpos[150*g[1]:150*g[1]+100]: |
|---|
| 662 |
duplist.append(os.path.join(db['name'], g[0])) |
|---|
| 663 |
return duplist |
|---|
| 664 |
|
|---|
| 665 |
|
|---|
| 666 |
|
|---|
| 667 |
|
|---|
| 668 |
class PrevSearchesStack: |
|---|
| 669 |
|
|---|
| 670 |
""" This class is a stack which contains the data of the previous searches, |
|---|
| 671 |
s.t. one can return to the previous search with the back button. This is a |
|---|
| 672 |
little bit tricky since we have to distinguish if we want to return |
|---|
| 673 |
to the very last search (if after it something changed on the board) |
|---|
| 674 |
or to the search before that. Thus the very last search is stored in |
|---|
| 675 |
self.previous, and the rest in a list self.data. """ |
|---|
| 676 |
|
|---|
| 677 |
def __init__(self, maxLength, boardChanged): |
|---|
| 678 |
self.data = [] |
|---|
| 679 |
self.previous = () |
|---|
| 680 |
self.maxLength = maxLength |
|---|
| 681 |
self.boardChanged = boardChanged |
|---|
| 682 |
|
|---|
| 683 |
def append(self, s): |
|---|
| 684 |
if self.previous: |
|---|
| 685 |
if len(self.data) >= self.maxLength.get() > 0 : |
|---|
| 686 |
del self.data[0] |
|---|
| 687 |
self.data.append(self.previous) |
|---|
| 688 |
self.boardChanged.set(0) |
|---|
| 689 |
|
|---|
| 690 |
self.previous = s |
|---|
| 691 |
|
|---|
| 692 |
def pop(self): |
|---|
| 693 |
if not self.previous: return |
|---|
| 694 |
|
|---|
| 695 |
if not self.boardChanged.get() or not self.previous[0]: |
|---|
| 696 |
if not self.data: |
|---|
| 697 |
self.previous = () |
|---|
| 698 |
else: |
|---|
| 699 |
self.previous = self.data.pop() |
|---|
| 700 |
|
|---|
| 701 |
return self.previous |
|---|
| 702 |
|
|---|
| 703 |
def clear(self): |
|---|
| 704 |
self.data = [] |
|---|
| 705 |
self.previous = () |
|---|
| 706 |
|
|---|
| 707 |
|
|---|
| 708 |
|
|---|
| 709 |
class Message(ScrolledText): |
|---|
| 710 |
""" A ScrolledText widget which is usually DISABLED (i.e. the user cannot |
|---|
| 711 |
enter any text), and which automatically scrolls down upon insertion. """ |
|---|
| 712 |
|
|---|
| 713 |
def __init__(self, window): |
|---|
| 714 |
ScrolledText.__init__(self, window, height=8, width=75, relief=SUNKEN, wrap=WORD) |
|---|
| 715 |
self.config(state=DISABLED) |
|---|
| 716 |
|
|---|
| 717 |
def insert(self, pos, text): |
|---|
| 718 |
self.config(state=NORMAL) |
|---|
| 719 |
ScrolledText.insert(self, pos, text) |
|---|
| 720 |
self.see(END) |
|---|
| 721 |
self.config(state=DISABLED) |
|---|
| 722 |
|
|---|
| 723 |
def delete(self, pos1, pos2): |
|---|
| 724 |
self.config(state=NORMAL) |
|---|
| 725 |
ScrolledText.delete(self, pos1, pos2) |
|---|
| 726 |
self.config(state=DISABLED) |
|---|
| 727 |
|
|---|
| 728 |
|
|---|
| 729 |
|
|---|
| 730 |
class ProgressBar(Canvas): |
|---|
| 731 |
""" A simple progress bar. """ |
|---|
| 732 |
|
|---|
| 733 |
def __init__(self, parent, width=390, font = None, colors = None): |
|---|
| 734 |
if not font: |
|---|
| 735 |
self.font = (StringVar(), IntVar(), StringVar()) |
|---|
| 736 |
self.font[0].set('Helvetica') |
|---|
| 737 |
self.font[1].set(8) |
|---|
| 738 |
self.font[2].set('') |
|---|
| 739 |
else: self.font = font |
|---|
| 740 |
if not colors: |
|---|
| 741 |
self.colors = (StringVar(), StringVar()) |
|---|
| 742 |
self.colors[0].set('black') |
|---|
| 743 |
self.colors[1].set('white') |
|---|
| 744 |
else: self.colors = colors |
|---|
| 745 |
self.width = width |
|---|
| 746 |
Canvas.__init__(self, parent, height=15, width=width, highlightthickness=0, |
|---|
| 747 |
borderwidth=2, relief=RIDGE) |
|---|
| 748 |
|
|---|
| 749 |
self.current, self.previous = -1, -1 |
|---|
| 750 |
|
|---|
| 751 |
def redraw(self, percent): |
|---|
| 752 |
self.delete(ALL) |
|---|
| 753 |
x = int(self.width*percent+3) |
|---|
| 754 |
self.create_rectangle(0,0,x,18, fill=self.colors[0].get(), outline='') |
|---|
| 755 |
self.update() |
|---|
| 756 |
|
|---|
| 757 |
|
|---|
| 758 |
def clear(self): |
|---|
| 759 |
self.delete(ALL) |
|---|
| 760 |
|
|---|
| 761 |
def write(self, text): |
|---|
| 762 |
self.create_text(self.width-10,9, text=text, fill=self.colors[1].get(), |
|---|
| 763 |
font=(self.font[0].get(), self.font[1].get(), self.font[2].get()), anchor=E) |
|---|
| 764 |
|
|---|
| 765 |
|
|---|
| 766 |
|
|---|
| 767 |
|
|---|
| 768 |
|
|---|
| 769 |
class customMenus: |
|---|
| 770 |
|
|---|
| 771 |
def __init__(self, master): |
|---|
| 772 |
self.mainMenu = master.mainMenu |
|---|
| 773 |
self.master = master |
|---|
| 774 |
self.htmlpath = os.curdir |
|---|
| 775 |
self.windowOpen = 0 |
|---|
| 776 |
self.path = os.curdir |
|---|
| 777 |
|
|---|
| 778 |
def compare(self, entry1, entry2): |
|---|
| 779 |
if entry1['name'] < entry2['name']: return -1 |
|---|
| 780 |
elif entry1['name'] == entry2['name']: return 0 |
|---|
| 781 |
elif entry1['name'] > entry2['name']: return 1 |
|---|
| 782 |
|
|---|
| 783 |
def buildMenus(self, reload=1, alternativePath = ''): |
|---|
| 784 |
""" |
|---|
| 785 |
format of menuList entries: |
|---|
| 786 |
{ 'name' : name (which will be displayed in the menu, |
|---|
| 787 |
'entries': [list of entries] |
|---|
| 788 |
'subm' : [list of submenus] } |
|---|
| 789 |
|
|---|
| 790 |
format of entry: |
|---|
| 791 |
{ 'name' : name which will be displayed in the menu, |
|---|
| 792 |
'file' : name of a html file which will be displayed upon clicking |
|---|
| 793 |
this entry (or empty), |
|---|
| 794 |
'gisearch' : parameters of a game info search to be done when |
|---|
| 795 |
this is chosen, |
|---|
| 796 |
'psearch' : pattern and options for a pattern search } |
|---|
| 797 |
|
|---|
| 798 |
format of submenu: {} as before |
|---|
| 799 |
""" |
|---|
| 800 |
|
|---|
| 801 |
if reload: |
|---|
| 802 |
|
|---|
| 803 |
try: |
|---|
| 804 |
file = open(os.path.join(self.path, 'menus.def')) |
|---|
| 805 |
self.customMenuList = cPickle.load(file) |
|---|
| 806 |
file.close() |
|---|
| 807 |
except IOError: |
|---|
| 808 |
if alternativePath: |
|---|
| 809 |
try: |
|---|
| 810 |
file = open(os.path.join(alternativePath, 'menus.def')) |
|---|
| 811 |
self.customMenuList = cPickle.load(file) |
|---|
| 812 |
file.close() |
|---|
| 813 |
except IOError: |
|---|
| 814 |
self.customMenuList = [] |
|---|
| 815 |
else: |
|---|
| 816 |
self.customMenuList = [] |
|---|
| 817 |
|
|---|
| 818 |
self.noOfMenus = len(self.customMenuList) |
|---|
| 819 |
|
|---|
| 820 |
|
|---|
| 821 |
|
|---|
| 822 |
self.customMenuCommands = [] |
|---|
| 823 |
|
|---|
| 824 |
self.mmIndex = 3 |
|---|
| 825 |
|
|---|
| 826 |
self.customMenuList.sort(self.compare) |
|---|
| 827 |
|
|---|
| 828 |
for item in self.customMenuList: |
|---|
| 829 |
m = Menu(self.mainMenu) |
|---|
| 830 |
self.mainMenu.insert_cascade(self.mmIndex, menu=m, label = item['name']) |
|---|
| 831 |
self.mmIndex += 1 |
|---|
| 832 |
|
|---|
| 833 |
self.addMenu(item['subm'], m) |
|---|
| 834 |
|
|---|
| 835 |
item['entries'].sort(self.compare) |
|---|
| 836 |
|
|---|
| 837 |
for e in item['entries']: |
|---|
| 838 |
|
|---|
| 839 |
m.add_command(label=e[ |
|---|