| 1 | #! /usr/bin/env python |
|---|
| 2 | # File: kombilo.py |
|---|
| 3 | |
|---|
| 4 | ## Copyright (C) 2001-4 Ulrich Goertz (u@g0ertz.de) |
|---|
| 5 | |
|---|
| 6 | ## Kombilo 0.5h 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.5h\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) |
|---|