| 1 | #!/usr/bin/python |
|---|
| 2 | # File: v.py |
|---|
| 3 | |
|---|
| 4 | ## Copyright (C) 2001-2 Ulrich Goertz (u@g0ertz.de) |
|---|
| 5 | |
|---|
| 6 | ## This is a simple SGF viewer; it comes with the go database program |
|---|
| 7 | ## Kombilo. |
|---|
| 8 | |
|---|
| 9 | ## This program is free software; you can redistribute it and/or modify |
|---|
| 10 | ## it under the terms of the GNU General Public License as published by |
|---|
| 11 | ## the Free Software Foundation; either version 2 of the License, or |
|---|
| 12 | ## (at your option) any later version. |
|---|
| 13 | |
|---|
| 14 | ## This program is distributed in the hope that it will be useful, |
|---|
| 15 | ## but WITHOUT ANY WARRANTY; without even the implied warranty of |
|---|
| 16 | ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|---|
| 17 | ## GNU General Public License for more details. |
|---|
| 18 | |
|---|
| 19 | ## You should have received a copy of the GNU General Public License |
|---|
| 20 | ## along with this program (gpl.txt); if not, write to the Free Software |
|---|
| 21 | ## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
|---|
| 22 | ## The GNU GPL is also currently available at |
|---|
| 23 | ## http://www.gnu.org/copyleft/gpl.html |
|---|
| 24 | |
|---|
| 25 | |
|---|
| 26 | from Tkinter import * |
|---|
| 27 | from tkMessageBox import * |
|---|
| 28 | from ScrolledText import ScrolledText |
|---|
| 29 | import tkFileDialog |
|---|
| 30 | import cPickle |
|---|
| 31 | import os |
|---|
| 32 | import sys |
|---|
| 33 | from string import split, find, replace, join, strip |
|---|
| 34 | from copy import copy, deepcopy |
|---|
| 35 | |
|---|
| 36 | from sgfparser import * |
|---|
| 37 | from board1 import * |
|---|
| 38 | |
|---|
| 39 | # --------------------------------------------------------------------------------------- |
|---|
| 40 | |
|---|
| 41 | class BunchTkVar: |
|---|
| 42 | """ This class is used to collect the Tk variables where the options |
|---|
| 43 | are stored. """ |
|---|
| 44 | |
|---|
| 45 | def saveToDisk(self, filename, onFailure = lambda:None): |
|---|
| 46 | d = {} |
|---|
| 47 | for x in self.__dict__.keys(): |
|---|
| 48 | d[x] = self.__dict__[x].get() |
|---|
| 49 | try: |
|---|
| 50 | f = open(filename, 'w') |
|---|
| 51 | cPickle.dump(d,f) |
|---|
| 52 | f.close() |
|---|
| 53 | except IOError: |
|---|
| 54 | onFailure() |
|---|
| 55 | |
|---|
| 56 | |
|---|
| 57 | def loadFromDisk(self, filename, onFailure = lambda:None): |
|---|
| 58 | try: |
|---|
| 59 | f = open(filename) |
|---|
| 60 | d = cPickle.load(f) |
|---|
| 61 | f.close() |
|---|
| 62 | except IOError: |
|---|
| 63 | onFailure() |
|---|
| 64 | else: |
|---|
| 65 | for x in self.__dict__.keys(): |
|---|
| 66 | if d.has_key(x): self.__dict__[x].set(d[x]) |
|---|
| 67 | |
|---|
| 68 | |
|---|
| 69 | |
|---|
| 70 | # --------------------------------------------------------------------------------------- |
|---|
| 71 | |
|---|
| 72 | class EnhancedCursor(Cursor): |
|---|
| 73 | """ Adds a snapshot/restore feature to Cursor. """ |
|---|
| 74 | |
|---|
| 75 | def __init__(self, sgf, comments): |
|---|
| 76 | self.comments = comments |
|---|
| 77 | Cursor.__init__(self, sgf) |
|---|
| 78 | |
|---|
| 79 | def snapshot(self): |
|---|
| 80 | """ Returns the current position in the sgf file (as some tuple); |
|---|
| 81 | with this tuple, the position can be restored via 'restore'. |
|---|
| 82 | This is used for the 'back' button.""" |
|---|
| 83 | |
|---|
| 84 | comment = self.comments.get('1.0', END) |
|---|
| 85 | |
|---|
| 86 | return (copy(self.currentPosition), |
|---|
| 87 | copy(self.oldIndices), comment) |
|---|
| 88 | |
|---|
| 89 | def restore(self, d): |
|---|
| 90 | """ Restore the position given by d (cf. snapshot). """ |
|---|
| 91 | |
|---|
| 92 | self.currentPosition = d[0] |
|---|
| 93 | self.oldIndices = d[1] |
|---|
| 94 | self.currentNode = self.parseNode(self.currentPosition) |
|---|
| 95 | self.setFlags() |
|---|
| 96 | |
|---|
| 97 | self.comments.delete('1.0', END) |
|---|
| 98 | self.comments.insert('1.0', d[2]) |
|---|
| 99 | |
|---|
| 100 | # --------------------------------------------------------------------------------------- |
|---|
| 101 | |
|---|
| 102 | class ScrolledText_HSB(ScrolledText): |
|---|
| 103 | """ A ScrolledText which hides the scroll bar when it is not needed. """ |
|---|
| 104 | |
|---|
| 105 | def __init__(self, parent, **args): |
|---|
| 106 | if not args: |
|---|
| 107 | args = {} |
|---|
| 108 | apply(ScrolledText.__init__, (self, parent,) , args) |
|---|
| 109 | self.vbarPackinfo = self.vbar.pack_info() |
|---|
| 110 | self.vbar.pack_forget() |
|---|
| 111 | |
|---|
| 112 | def insert(self, pos, text): |
|---|
| 113 | ScrolledText.insert(self, pos, text) |
|---|
| 114 | if self.yview() != (0.0, 1.0): |
|---|
| 115 | apply(self.vbar.pack, (), self.vbarPackinfo) |
|---|
| 116 | |
|---|
| 117 | def delete(self, pos1, pos2): |
|---|
| 118 | ScrolledText.delete(self, pos1, pos2) |
|---|
| 119 | if self.yview() == (0.0, 1.0): |
|---|
| 120 | self.vbar.pack_forget() |
|---|
| 121 | |
|---|
| 122 | # --------------------------------------------------------------------------------------- |
|---|
| 123 | |
|---|
| 124 | class Viewer: |
|---|
| 125 | """ This is the main class of v.py. """ |
|---|
| 126 | |
|---|
| 127 | def convCoord(self, x): |
|---|
| 128 | """ This takes coordinates in SGF style (aa - ss), |
|---|
| 129 | and returns the corresponding |
|---|
| 130 | integer coordinates (between 1 and 19). """ |
|---|
| 131 | |
|---|
| 132 | try: |
|---|
| 133 | p, q = ord(x[0])-ord('a')+1, ord(x[1])-ord('a')+1 |
|---|
| 134 | if 1 <= p <= 19 and 1 <= q <= 19: return (p,q) |
|---|
| 135 | else: return 0 |
|---|
| 136 | except: |
|---|
| 137 | return 0 |
|---|
| 138 | |
|---|
| 139 | |
|---|
| 140 | def setup(self): |
|---|
| 141 | """ Set up initial position (clear board, put handi stones etc.). """ |
|---|
| 142 | |
|---|
| 143 | self.board.clear() |
|---|
| 144 | self.cursor.game(0) |
|---|
| 145 | |
|---|
| 146 | # display game name |
|---|
| 147 | gameName = self.currentFile[:15] |
|---|
| 148 | self.gameName.set(gameName) |
|---|
| 149 | |
|---|
| 150 | self.moveno.set('0') |
|---|
| 151 | self.capW = 0 |
|---|
| 152 | self.capB = 0 |
|---|
| 153 | self.capVar.set('Cap - B: ' + str(self.capB) + ', W: ' + str(self.capW)) |
|---|
| 154 | |
|---|
| 155 | if self.cursor.currentNode.has_key('PW'): pw = self.cursor.currentNode['PW'][0] |
|---|
| 156 | else: pw = '' |
|---|
| 157 | if self.cursor.currentNode.has_key('PB'): pb = self.cursor.currentNode['PB'][0] |
|---|
| 158 | else: pb = '' |
|---|
| 159 | self.master.title(pw + ' - ' + pb) |
|---|
| 160 | |
|---|
| 161 | try: |
|---|
| 162 | |
|---|
| 163 | # look for first relevant node |
|---|
| 164 | while not (self.cursor.currentNode.has_key('AB') or self.cursor.currentNode.has_key('AW') \ |
|---|
| 165 | or self.cursor.currentNode.has_key('B') or self.cursor.currentNode.has_key('W')): |
|---|
| 166 | self.cursor.next() |
|---|
| 167 | |
|---|
| 168 | # and put stones on the board |
|---|
| 169 | while not (self.cursor.currentNode.has_key('B') or self.cursor.currentNode.has_key('W')): |
|---|
| 170 | if self.cursor.currentNode.has_key('AB'): |
|---|
| 171 | for x in self.cursor.currentNode['AB']: |
|---|
| 172 | self.board.play(self.convCoord(x), 'black') |
|---|
| 173 | if self.cursor.currentNode.has_key('AW'): |
|---|
| 174 | for x in self.cursor.currentNode['AW']: |
|---|
| 175 | self.board.play(self.convCoord(x), 'white') |
|---|
| 176 | self.cursor.next() |
|---|
| 177 | |
|---|
| 178 | if not self.cursor.atStart: |
|---|
| 179 | if self.cursor.currentNode.has_key('B'): self.board.currentColor = 'black' |
|---|
| 180 | elif self.cursor.currentNode.has_key('W'): self.board.currentColor = 'white' |
|---|
| 181 | self.cursor.previous() |
|---|
| 182 | else: |
|---|
| 183 | if self.cursor.currentNode.has_key('B'): |
|---|
| 184 | self.board.play(self.convCoord(self.cursor.currentNode['B'][0]), 'black') |
|---|
| 185 | elif self.cursor.currentNode.has_key('W'): |
|---|
| 186 | self.board.play(self.convCoord(self.cursor.currentNode['B'][0]), 'white') |
|---|
| 187 | |
|---|
| 188 | except SGFError: |
|---|
| 189 | showwarning('SGF Error', 'SGF Error') |
|---|
| 190 | self.gameName.set('') |
|---|
| 191 | self.currentFile = '' |
|---|
| 192 | self.cursor = None |
|---|
| 193 | return 0 |
|---|
| 194 | |
|---|
| 195 | self.displayLabels(self.cursor.currentNode) |
|---|
| 196 | self.markAll() |
|---|
| 197 | return 1 |
|---|
| 198 | |
|---|
| 199 | |
|---|
| 200 | def markAll(self): |
|---|
| 201 | """ Mark all variations for the next move. """ |
|---|
| 202 | |
|---|
| 203 | if not self.cursor or self.cursor.atEnd: return |
|---|
| 204 | |
|---|
| 205 | passV = 0 |
|---|
| 206 | |
|---|
| 207 | try: |
|---|
| 208 | |
|---|
| 209 | for i in range(self.cursor.noChildren()): |
|---|
| 210 | c = self.cursor.next(i) |
|---|
| 211 | |
|---|
| 212 | for color in ['B', 'W']: |
|---|
| 213 | if c.has_key(color): |
|---|
| 214 | if c[color][0] and self.convCoord(c[color][0]): |
|---|
| 215 | if self.options.showNextMoveVar.get(): |
|---|
| 216 | self.board.placeMark(self.convCoord(c[color][0]),'') |
|---|
| 217 | else: |
|---|
| 218 | passV = 1 |
|---|
| 219 | if color == 'B': self.board.currentColor = 'black' |
|---|
| 220 | else: self.board.currentColor = 'white' |
|---|
| 221 | |
|---|
| 222 | self.cursor.previous() |
|---|
| 223 | |
|---|
| 224 | if passV: self.passButton.config(state=NORMAL) |
|---|
| 225 | else: self.passButton.config(state=DISABLED) |
|---|
| 226 | |
|---|
| 227 | except SGFError: |
|---|
| 228 | showwarning('SGF Error', 'SGF Error') |
|---|
| 229 | self.board.delMarks() |
|---|
| 230 | |
|---|
| 231 | |
|---|
| 232 | def showNextMove(self): |
|---|
| 233 | """ Toggle 'show next move' option. """ |
|---|
| 234 | if self.options.showNextMoveVar.get(): |
|---|
| 235 | self.markAll() |
|---|
| 236 | else: |
|---|
| 237 | self.board.delMarks() |
|---|
| 238 | |
|---|
| 239 | def passFct(self): |
|---|
| 240 | """ React to pass button: choose the 'pass variation' in the SGF file.""" |
|---|
| 241 | |
|---|
| 242 | if not self.currentFile: return 0 |
|---|
| 243 | |
|---|
| 244 | self.leaveNode() |
|---|
| 245 | |
|---|
| 246 | if self.board.currentColor == 'black': nM = 'B' |
|---|
| 247 | else: nM = 'W' |
|---|
| 248 | |
|---|
| 249 | for i in range(self.cursor.noChildren()): |
|---|
| 250 | try: |
|---|
| 251 | c = self.cursor.next(i) |
|---|
| 252 | except SGFError: |
|---|
| 253 | continue |
|---|
| 254 | if c.has_key(nM) and not self.convCoord(c[nM][0]): # found |
|---|
| 255 | self.board.delMarks() |
|---|
| 256 | self.board.delLabels() |
|---|
| 257 | self.moveno.set(str(int(self.moveno.get())+1)) |
|---|
| 258 | |
|---|
| 259 | self.markAll() |
|---|
| 260 | self.displayNode(c) |
|---|
| 261 | return 1 |
|---|
| 262 | else: |
|---|
| 263 | self.cursor.previous() |
|---|
| 264 | return 0 |
|---|
| 265 | |
|---|
| 266 | |
|---|
| 267 | def nextMove(self, p): |
|---|
| 268 | """ React to mouse-click on the board""" |
|---|
| 269 | |
|---|
| 270 | if not self.currentFile: return 0 |
|---|
| 271 | |
|---|
| 272 | self.leaveNode() |
|---|
| 273 | |
|---|
| 274 | x, y = p |
|---|
| 275 | |
|---|
| 276 | if self.board.currentColor == 'black': |
|---|
| 277 | nM = 'B' |
|---|
| 278 | else: |
|---|
| 279 | nM = 'W' |
|---|
| 280 | |
|---|
| 281 | done = 0 |
|---|
| 282 | for i in range(self.cursor.noChildren()): # look for the move in the SGF file |
|---|
| 283 | if (not done): |
|---|
| 284 | try: |
|---|
| 285 | c = self.cursor.next(i) |
|---|
| 286 | except SGFError: |
|---|
| 287 | continue |
|---|
| 288 | if c.has_key(nM) and self.convCoord(c[nM][0])==p: # found |
|---|
| 289 | self.board.delMarks() |
|---|
| 290 | self.board.delLabels() |
|---|
| 291 | done = 1 |
|---|
| 292 | |
|---|
| 293 | self.moveno.set(str(int(self.moveno.get())+1)) |
|---|
| 294 | |
|---|
| 295 | self.markAll() |
|---|
| 296 | self.displayNode(c) |
|---|
| 297 | if c.has_key('B'): |
|---|
| 298 | self.capB = self.capB + len(self.board.undostack[len(self.board.undostack)-1][2]) |
|---|
| 299 | if c.has_key('W'): |
|---|
| 300 | self.capW = self.capW + len(self.board.undostack[len(self.board.undostack)-1][2]) |
|---|
| 301 | self.capVar.set('Cap - B: ' + str(self.capB) + ', W: ' + str(self.capW)) |
|---|
| 302 | |
|---|
| 303 | else: |
|---|
| 304 | self.cursor.previous() |
|---|
| 305 | |
|---|
| 306 | return done |
|---|
| 307 | |
|---|
| 308 | |
|---|
| 309 | def prev(self): |
|---|
| 310 | """ Go back one move. """ |
|---|
| 311 | |
|---|
| 312 | if not self.currentFile: return |
|---|
| 313 | |
|---|
| 314 | if not self.cursor.atStart: |
|---|
| 315 | self.leaveNode() |
|---|
| 316 | c = self.cursor.currentNode |
|---|
| 317 | if (c.has_key('B') and c['B'][0]) or (c.has_key('W') and c['W'][0]): |
|---|
| 318 | if c.has_key('B'): |
|---|
| 319 | self.capB = self.capB - len(self.board.undostack[len(self.board.undostack)-1][2]) |
|---|
| 320 | if c.has_key('W'): |
|---|
| 321 | self.capW = self.capW - len(self.board.undostack[len(self.board.undostack)-1][2]) |
|---|
| 322 | self.capVar.set('Cap - B: ' + str(self.capB) + ', W: ' + str(self.capW)) |
|---|
| 323 | |
|---|
| 324 | self.board.undo() |
|---|
| 325 | |
|---|
| 326 | if c.has_key('AE') and c['AE'][0]: |
|---|
| 327 | for p in c['AE']: |
|---|
| 328 | self.board.undo(1, 0) |
|---|
| 329 | if c.has_key('AW') and c['AW'][0]: |
|---|
| 330 | for p in c['AW']: |
|---|
| 331 | self.board.undo(1, 0) |
|---|
| 332 | if c.has_key('AB') and c['AB'][0]: |
|---|
| 333 | for p in c['AB']: |
|---|
| 334 | self.board.undo(1, 0) |
|---|
| 335 | |
|---|
| 336 | c = self.cursor.previous() |
|---|
| 337 | self.moveno.set(str(int(self.moveno.get())-1)) |
|---|
| 338 | |
|---|
| 339 | self.board.delLabels() |
|---|
| 340 | self.board.delMarks() |
|---|
| 341 | |
|---|
| 342 | self.markAll() |
|---|
| 343 | self.displayLabels(c) |
|---|
| 344 | |
|---|
| 345 | def next(self): |
|---|
| 346 | """Go to the next move.""" |
|---|
| 347 | |
|---|
| 348 | if not self.currentFile: return |
|---|
| 349 | |
|---|
| 350 | if not self.cursor.atEnd: |
|---|
| 351 | self.leaveNode() |
|---|
| 352 | |
|---|
| 353 | try: |
|---|
| 354 | c = self.cursor.next() |
|---|
| 355 | except SGFError: |
|---|
| 356 | return 0 # failure |
|---|
| 357 | |
|---|
| 358 | self.moveno.set(str(int(self.moveno.get())+1)) |
|---|
| 359 | |
|---|
| 360 | self.board.delMarks() |
|---|
| 361 | self.board.delLabels() |
|---|
| 362 | |
|---|
| 363 | self.displayNode(c) |
|---|
| 364 | if c.has_key('B') and c['B'][0]: |
|---|
| 365 | self.capB = self.capB + len(self.board.undostack[len(self.board.undostack)-1][2]) |
|---|
| 366 | if c.has_key('W') and c['W'][0]: |
|---|
| 367 | self.capW = self.capW + len(self.board.undostack[len(self.board.undostack)-1][2]) |
|---|
| 368 | self.capVar.set('Cap - B: ' + str(self.capB) + ', W: ' + str(self.capW)) |
|---|
| 369 | |
|---|
| 370 | if not self.cursor.atEnd: self.markAll() |
|---|
| 371 | else: self.passButton.config(state=DISABLED) |
|---|
| 372 | |
|---|
| 373 | return 1 # success |
|---|
| 374 | |
|---|
| 375 | |
|---|
| 376 | def displayNode(self, c): |
|---|
| 377 | """Display the stones played in the current node, |
|---|
| 378 | and call displayLabels(). """ |
|---|
| 379 | |
|---|
| 380 | if c.has_key('AB') and c['AB'][0]: |
|---|
| 381 | for p in c['AB']: |
|---|
| 382 | if not self.board.play(self.convCoord(p), 'black'): |
|---|
| 383 | self.board.undostack.append(((20,20), '' ,[])) |
|---|
| 384 | else: self.board.currentColor = self.board.invert(self.board.currentColor) |
|---|
| 385 | if c.has_key('AW') and c['AW'][0]: |
|---|
| 386 | for p in c['AW']: |
|---|
| 387 | if not self.board.play(self.convCoord(p), 'white'): |
|---|
| 388 | self.board.undostack.append(((20,20), '' ,[])) |
|---|
| 389 | else: self.board.currentColor = self.board.invert(self.board.currentColor) |
|---|
| 390 | if c.has_key('AE') and c['AE'][0]: |
|---|
| 391 | for p in c['AE']: |
|---|
| 392 | if not self.board.remove(self.convCoord(p)): |
|---|
| 393 | self.board.undostack.append(((20,20), '' ,[])) |
|---|
| 394 | |
|---|
| 395 | if c.has_key('B') and c['B'][0]: |
|---|
| 396 | p = self.convCoord(c['B'][0]) |
|---|
| 397 | if not p or not self.board.play(p, 'black'): |
|---|
| 398 | self.board.undostack.append(((20,20), '' ,[])) |
|---|
| 399 | elif c.has_key('W') and c['W'][0]: |
|---|
| 400 | p = self.convCoord(c['W'][0]) |
|---|
| 401 | if not p or not self.board.play(p, 'white'): |
|---|
| 402 | self.board.undostack.append(((20,20), '' ,[])) |
|---|
| 403 | |
|---|
| 404 | self.displayLabels(c) |
|---|
| 405 | |
|---|
| 406 | |
|---|
| 407 | def displayLabels(self, c): |
|---|
| 408 | """ Display the labels in the current node.""" |
|---|
| 409 | |
|---|
| 410 | self.comments.delete('1.0', END) |
|---|
| 411 | if c.has_key('C'): |
|---|
| 412 | self.comments.insert('1.0', replace(c['C'][0], '\r', '')) |
|---|
| 413 | |
|---|
| 414 | for type in ['CR', 'MA', 'SQ', 'TR']: |
|---|
| 415 | if c.has_key(type) and c[type][0]: |
|---|
| 416 | for p in c[type]: |
|---|
| 417 | self.board.placeLabel(self.convCoord(p), type) |
|---|
| 418 | |
|---|
| 419 | if c.has_key('LB') and c['LB'][0]: |
|---|
| 420 | for p1 in c['LB']: |
|---|
| 421 | p, text = split(p1, ':') |
|---|
| 422 | self.board.placeLabel(self.convCoord(p), 'LB', text) |
|---|
| 423 | |
|---|
| 424 | def next10(self): |
|---|
| 425 | for i in range(10): self.next() |
|---|
| 426 | |
|---|
| 427 | def prev10(self): |
|---|
| 428 | for i in range(10): self.prev() |
|---|
| 429 | |
|---|
| 430 | def end(self): |
|---|
| 431 | """ Go to end of game. """ |
|---|
| 432 | if not self.currentFile: return |
|---|
| 433 | while not self.cursor.atEnd and self.next(): pass |
|---|
| 434 | |
|---|
| 435 | def start(self, update=1): |
|---|
| 436 | """ Go to beginning of game.""" |
|---|
| 437 | if not self.currentFile: return |
|---|
| 438 | if update: self.leaveNode() |
|---|
| 439 | self.board.delMarks() |
|---|
| 440 | self.board.delLabels() |
|---|
| 441 | self.setup() |
|---|
| 442 | |
|---|
| 443 | |
|---|
| 444 | def leaveNode(self): |
|---|
| 445 | """This method should be called before leaving the current node in the |
|---|
| 446 | SGF file. It will take care of saving the changes (currently that |
|---|
| 447 | means: changes to the comments) to the SGF file.""" |
|---|
| 448 | |
|---|
| 449 | if not self.currentFile: return |
|---|
| 450 | |
|---|
| 451 | s = strip(self.comments.get('1.0', END)) |
|---|
| 452 | changed = 0 |
|---|
| 453 | |
|---|
| 454 | if self.cursor.currentNode.has_key('C'): |
|---|
| 455 | if strip(self.cursor.currentNode['C'][0]) != strip(s): |
|---|
| 456 | self.cursor.currentNode['C'] = [s] |
|---|
| 457 | changed = 1 |
|---|
| 458 | else: |
|---|
| 459 | if strip(s): |
|---|
| 460 | self.cursor.currentNode['C'] = [s] |
|---|
| 461 | changed = 1 |
|---|
| 462 | |
|---|
| 463 | if changed: self.cursor.updateCurrentNode() |
|---|
| 464 | |
|---|
| 465 | |
|---|
| 466 | |
|---|
| 467 | def readSGFfile(self, path = None, filename = None): |
|---|
| 468 | """ Read an SGF file given by filename (if None, ask |
|---|
| 469 | for a filename). """ |
|---|
| 470 | |
|---|
| 471 | self.leaveNode() |
|---|
| 472 | |
|---|
| 473 | self.board.clear() |
|---|
| 474 | self.board.delMarks() |
|---|
| 475 | |
|---|
| 476 | self.gameName.set('') |
|---|
| 477 | |
|---|
| 478 | self.board.state('normal', self.nextMove) |
|---|
| 479 | self.master.update() |
|---|
| 480 | |
|---|
| 481 | if not path: |
|---|
| 482 | path = '.' |
|---|
| 483 | |
|---|
| 484 | if not filename: |
|---|
| 485 | path, filename = os.path.split(tkFileDialog.askopenfilename(filetypes=[('SGF files', '*.sgf'), |
|---|
| 486 | ('All files', '*')], |
|---|
| 487 | initialdir=self.sgfpath)) |
|---|
| 488 | if filename: |
|---|
| 489 | try: |
|---|
| 490 | f = open(os.path.join(path,filename)) |
|---|
| 491 | s = f.read() |
|---|
| 492 | f.close() |
|---|
| 493 | except IOError: |
|---|
| 494 | showwarning('Open file', 'Cannot open this file\n') |
|---|
| 495 | return 0 |
|---|
| 496 | else: |
|---|
| 497 | if not s: return 0 |
|---|
| 498 | self.sgfpath = path |
|---|
| 499 | try: |
|---|
| 500 | self.currentSGF = s |
|---|
| 501 | self.cursor = EnhancedCursor(self.currentSGF, self.comments) |
|---|
| 502 | except SGFError: |
|---|
| 503 | showwarning('Parsing Error', 'Error in SGF file!') |
|---|
| 504 | return 0 |
|---|
| 505 | self.currentFile = filename |
|---|
| 506 | return self.setup() |
|---|
| 507 | |
|---|
| 508 | |
|---|
| 509 | def saveSGFfile(self): |
|---|
| 510 | if not self.currentFile or not self.cursor: return |
|---|
| 511 | self.leaveNode() |
|---|
| 512 | |
|---|
| 513 | file = open(os.path.join(self.sgfpath, self.currentFile), 'w') |
|---|
| 514 | file.write(self.cursor.output()) |
|---|
| 515 | file.close() |
|---|
| 516 | |
|---|
| 517 | def saveasSGFfile(self): |
|---|
| 518 | if not self.currentFile or not self.cursor: return |
|---|
| 519 | self.leaveNode() |
|---|
| 520 | |
|---|
| 521 | f = tkFileDialog.asksaveasfilename(filetypes=[('SGF files', '*.sgf'), ('All files', '*')], |
|---|
| 522 | initialdir = self.sgfpath) |
|---|
| 523 | |
|---|
| 524 | if not f: return |
|---|
| 525 | try: |
|---|
| 526 | file = open(f, 'w') |
|---|
| 527 | file.write(self.cursor.output()) |
|---|
| 528 | file.close() |
|---|
| 529 | except IOError: |
|---|
| 530 | showwarning('I/O Error', 'Could not write to file ' + f) |
|---|
| 531 | |
|---|
| 532 | self.sgfpath, self.currentFile = os.path.split(f) |
|---|
| 533 | self.gameName.set(self.currentFile[:15]) |
|---|
| 534 | |
|---|
| 535 | |
|---|
| 536 | def quit(self): |
|---|
| 537 | """ Exit the program. """ |
|---|
| 538 | self.master.destroy() |
|---|
| 539 | |
|---|
| 540 | |
|---|
| 541 | def saveOptions(self): |
|---|
| 542 | """ Save options to disk (to file 'v.opt'). """ |
|---|
| 543 | self.options.windowGeom = StringVar() |
|---|
| 544 | self.options.windowGeom.set(self.master.geometry()) |
|---|
| 545 | self.options.saveToDisk(os.path.join(self.optionspath,'v.opt'), |
|---|
| 546 | lambda: showwarning('Save options', 'IO Error')) |
|---|
| 547 | |
|---|
| 548 | def loadOptions(self): |
|---|
| 549 | """ Load options from disk. """ |
|---|
| 550 | self.options.windowGeom = StringVar() |
|---|
| 551 | self.options.loadFromDisk(os.path.join(self.optionspath,'v.opt')) |
|---|
| 552 | if self.options.windowGeom.get(): |
|---|
| 553 | self.master.geometry(self.options.windowGeom.get()) |
|---|
| 554 | |
|---|
| 555 | |
|---|
| 556 | def helpAbout(self): |
|---|
| 557 | """ Display the 'About ...' window with some basic information. """ |
|---|
| 558 | |
|---|
| 559 | t = 'v.py - written by Ulrich Goertz (u@g0ertz.de)\n\n' |
|---|
| 560 | t = t + 'v.py is a program to display go game records in SGF format.\n' |
|---|
| 561 | t = t + 'It comes together with the go database program Kombilo.\n' |
|---|
| 562 | t = t + 'You can find more information on v.py and Kombilo and the newest ' |
|---|
| 563 | t = t + 'version at http://www.g0ertz.de/kombilo/\n\n' |
|---|
| 564 | |
|---|
| 565 | t = t + 'v.py is free software; for more information ' |
|---|
| 566 | t = t + 'see the file license.txt.\n\n' |
|---|
| 567 | t = t + 'It is written in Python (see http://www.python.org/). ' |
|---|
| 568 | |
|---|
| 569 | window = Toplevel() |
|---|
| 570 | window.title('About v.py ...') |
|---|
| 571 | |
|---|
| 572 | text = Text(window, height=15, width=60, relief=FLAT, wrap=WORD) |
|---|
| 573 | text.insert(1.0, t) |
|---|
| 574 | |
|---|
| 575 | text.config(state=DISABLED) |
|---|
| 576 | text.pack() |
|---|
| 577 | |
|---|
| 578 | b = Button(window, text="OK", command = window.destroy) |
|---|
| 579 | b.pack(side=RIGHT) |
|---|
| 580 | |
|---|
| 581 | window.update_idletasks() |
|---|
| 582 | |
|---|
| 583 | window.focus() |
|---|
| 584 | window.grab_set() |
|---|
| 585 | window.wait_window() |
|---|
| 586 | |
|---|
| 587 | |
|---|
| 588 | def helpLicense(self): |
|---|
| 589 | """ Display the GNU General Public License. """ |
|---|
| 590 | try: |
|---|
| 591 | t = 'v.py\n (C) Ulrich Goertz (u@g0ertz.de), 2001-2002.\n' |
|---|
| 592 | t = t + '------------------------------------------------------------------------\n\n' |
|---|
| 593 | file = open(os.path.join(self.basepath,'doc/license.txt')) |
|---|
| 594 | t = t + file.read() |
|---|
| 595 | file.close() |
|---|
| 596 | except IOError: |
|---|
| 597 | t = 'v.py was written by Ulrich Goertz (u@g0ertz.de).\n' |
|---|
| 598 | t = t + 'It is published under the GNU General Public License. ' |
|---|
| 599 | t = t + 'See the file doc/license.txt for more information. ' |
|---|
| 600 | t = t + 'The GPL is also available at http://www.gnu.org/copyleft/gpl.html\n' |
|---|
| 601 | t = t + 'This program is distributed WITHOUT ANY WARRANTY!\n\n' |
|---|
| 602 | self.textWindow(t,'v.py license') |
|---|
| 603 | |
|---|
| 604 | def textWindow(self, t, title='', grab=1): |
|---|
| 605 | """ Open a window and display the text in the string t. |
|---|
| 606 | The window has the title title, and grabs the focus if grab==1. """ |
|---|
| 607 | |
|---|
| 608 | window = Toplevel() |
|---|
| 609 | window.title(title) |
|---|
| 610 | text = ScrolledText(window, height=25, width=80, relief=FLAT, wrap=WORD) |
|---|
| 611 | text.insert(1.0, t) |
|---|
| 612 | |
|---|
| 613 | text.config(state=DISABLED) |
|---|
| 614 | text.pack() |
|---|
| 615 | |
|---|
| 616 | b = Button(window, text="OK", command = window.destroy) |
|---|
| 617 | b.pack(side=RIGHT) |
|---|
| 618 | |
|---|
| 619 | window.update_idletasks() |
|---|
| 620 | if grab: |
|---|
| 621 | window.focus() |
|---|
| 622 | window.grab_set() |
|---|
| 623 | window.wait_window() |
|---|
| 624 | |
|---|
| 625 | |
|---|
| 626 | |
|---|
| 627 | def gameinfoOK(self): |
|---|
| 628 | keylist = ['PB', 'BR', 'PW', 'WR', 'EV', 'RE', 'DT', 'KM'] |
|---|
| 629 | for key in keylist: |
|---|
| 630 | self |
|---|