root/05/release-0.5j/v.py

Revision 106, 113.1 kB (checked in by ug, 5 years ago)

py2exe fix; board.currentColor fix; quit fix

Line 
1#!/usr/bin/python
2# File: v.py
3
4##   Copyright (C) 2001-4 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
26from Tkinter import *
27from tkMessageBox import *
28from ScrolledText import ScrolledText
29import tkFileDialog
30import Pmw
31
32import cPickle
33import os
34import sys
35
36try:
37    import cjkcodecs.aliases
38    cjkcodecs_available = 1
39except:
40    cjkcodecs_available = 0
41   
42from string import split, find, replace, join, strip
43from copy import copy, deepcopy
44from math import sqrt
45from whrandom import randint
46
47try:
48    from sgfpars import Node, Cursor, SGFError, SGFescape
49except:
50    from sgfparser import Node, Cursor, SGFError, SGFescape
51from board1 import *
52
53# ---------------------------------------------------------------------------------------
54
55class BunchTkVar:
56    """ This class is used to collect the Tk variables where the options
57        are stored. """
58   
59    def saveToDisk(self, filename, onFailure = lambda:None):
60        d = {}
61        for x in self.__dict__.keys():
62            d[x] = self.__dict__[x].get()
63        try:
64            f = open(filename, 'w')
65            cPickle.dump(d,f)
66            f.close()
67        except IOError:
68            onFailure()
69           
70
71    def loadFromDisk(self, filename, onFailure = lambda:None):
72        try:
73            f = open(filename)
74            d = cPickle.load(f)
75            f.close()
76        except IOError:
77            onFailure()
78        else:
79            for x in self.__dict__.keys():
80                if d.has_key(x): self.__dict__[x].set(d[x])
81
82
83# ---------------------------------------------------------------------------------------
84
85
86class ScrolledText_HSB(ScrolledText):
87    """ A ScrolledText which hides the scroll bar when it is not needed. """
88
89    def __init__(self, parent, **args):
90        if not args:
91            args = {}
92        apply(ScrolledText.__init__, (self, parent,) , args)
93        self.bind('<Configure>', self.checkScrollbars)
94        self.bind('<Key>', self.checkScrollbars)
95        self.vbarPackinfo = self.vbar.pack_info()
96        self.vbar.pack_forget()
97
98    def insert(self, pos, text, tags = None):
99        ScrolledText.insert(self, pos, text, tags)
100        if self.yview() != (0.0, 1.0):
101            apply(self.vbar.pack, (), self.vbarPackinfo)
102
103    def delete(self, pos1, pos2):
104        ScrolledText.delete(self, pos1, pos2)
105        if self.yview() == (0.0, 1.0):
106            self.vbar.pack_forget()
107
108    def checkScrollbars(self, event):
109        if self.yview() == (0.0, 1.0):
110            self.vbar.pack_forget()
111        else:
112            apply(self.vbar.pack, (), self.vbarPackinfo)
113
114
115# ---------------------------------------------------------------------------------------
116
117class ScrolledList(Frame):
118    """ A listbox with dynamic vertical and horizontal scrollbars. """
119   
120    def __init__(self, parent, **kw):
121        Frame.__init__(self, parent)
122
123        self.sbar = Scrollbar(self)
124        self.sbar1 = Scrollbar(self)
125        self.checking = 0
126       
127        if not kw: kw = {}
128
129        for var, value in [('height', 12), ('width', 40), ('relief', SUNKEN),
130                           ('selectmode', SINGLE), ('takefocus', 1), ('exportselection', 0)]:
131            if not kw.has_key(var): kw[var] = value
132       
133        self.list = Listbox(self, kw)
134        # self.rowconfigure(0, weight=1)
135        # self.columnconfigure(0, weight=1)
136        self.sbar.config(command = self.list.yview)
137        self.sbar1.config(command = self.list.xview, orient='horizontal')
138        self.list.config(xscrollcommand = self.sbar1.set, yscrollcommand = self.sbar.set)
139        self.list.grid(row=0, column=0, sticky=NSEW)
140        self.sbar.grid(row=0, column=1, sticky=NSEW)
141        self.sbar1.grid(row=1, column=0, sticky=NSEW)
142
143        self.rowconfigure(0, weight=1)
144        self.columnconfigure(0, weight=1)
145
146        self.focus_force()
147
148        self.onSelectionChange = None
149
150        self.bind('<Up>', self.up)
151        self.bind('<Down>', self.down)
152        self.bind('<Prior>', self.pgup)
153        self.bind('<Next>', self.pgdown)
154
155        # self.unbind('<Configure>')
156        self.bind('<Configure>', self.checkScrollbars)
157       
158        self.sbar.grid_forget()
159        self.sbar1.grid_forget()
160
161    def insert(self, index, data):
162        if type(data) == type([]):
163            apply(self.list.insert, [index] + data)
164        else:
165            self.list.insert(index, data)
166       
167        if self.list.yview() != (0.0, 1.0):
168            self.sbar.grid(row=0, column=1, sticky=NSEW)
169        if self.list.xview() != (0.0, 1.0):
170            self.sbar1.grid(row=1, column=0, sticky=NSEW)
171
172    def delete(self, index, data=None):
173        if data:
174            self.list.delete(index, data)
175        else:
176            self.list.delete(index)
177
178        if self.list.yview() == (0.0, 1.0):
179            self.sbar.grid_forget()
180
181        if self.list.xview() == (0.0, 1.0):
182            self.sbar1.grid_forget()
183
184
185    def checkScrollbars(self, event=None):
186        if self.checking:
187            if self.list.yview() != (0.0, 1.0):
188                self.sbar.grid(row=0, column=1, sticky=NSEW)
189            else:
190                self.sbar.grid_forget()
191            if self.list.xview() != (0.0, 1.0):
192                self.sbar1.grid(row=1, column=0, sticky=NSEW)
193            else:
194                self.sbar1.grid_forget()
195        else:
196            self.after(100, self.checkScrollbars)
197        self.checking = 1-self.checking
198
199           
200    def up(self, event):
201        if not self.list.curselection() or len(self.list.curselection())>1: return
202        index = int(self.list.curselection()[0])
203        if index != 0:
204            self.list.select_clear(index)
205            self.list.select_set(index-1)
206            self.list.see(index-1)
207            if self.onSelectionChange:
208                self.onSelectionChange(None, index-1)
209
210    def down(self, event):
211        if not self.list.curselection() or len(self.list.curselection())>1: return
212        index = int(self.list.curselection()[0])
213        if index != self.list.size()-1:
214            self.list.select_clear(index)
215            self.list.select_set(index+1)
216            self.list.see(index+1)
217            if self.onSelectionChange:
218                self.onSelectionChange(None, index+1)
219
220    def pgup(self, event):
221        if not self.list.curselection() or len(self.list.curselection())>1: return
222        index = int(self.list.curselection()[0])
223        if index >= 10:
224            self.list.select_clear(index)
225            self.list.select_set(index-10)
226            self.list.see(index-10)
227            if self.onSelectionChange:
228                self.onSelectionChange(None, index-10)
229        elif self.list.size():
230            self.list.select_clear(index)
231            self.list.select_set(0)
232            self.list.see(0)
233            if self.onSelectionChange:
234                self.onSelectionChange(None, 0)
235
236    def pgdown(self, event):
237        if not self.list.curselection() or len(self.list.curselection())>1: return
238        index = int(self.list.curselection()[0])
239        if index <= self.list.size()-10:
240            self.list.select_clear(index)
241            self.list.select_set(index+10)
242            self.list.see(index+10)
243            if self.onSelectionChange:
244                self.onSelectionChange(None, index+10)
245        elif self.list.size():
246            self.list.select_clear(index)
247            self.list.select_set(self.list.size()-1)
248            self.list.see(END)
249            if self.onSelectionChange:
250                self.onSelectionChange(None, self.list.size()-1)
251
252
253
254# ---------------------------------------------------------------------------------------
255
256class SGFtreeCanvas(Frame):
257    """ The canvas (in the data window) displaying the tree structure of the current game."""
258
259    def __init__(self, parent, **args):
260
261        Frame.__init__(self, parent)
262       
263        if not args: args = {}
264
265        for var, value in [('height', 100), ('width', 150), ('relief', SUNKEN)]:
266            if not args.has_key(var): args[var] = value
267       
268        self.canvas = Canvas(self, background='#FFBA59')
269        apply(self.canvas.config, (), args)
270        self.canvas.config(scrollregion=(0,0,1000,30))
271
272        self.sbar_vert = Scrollbar(self)
273        self.sbar_hor = Scrollbar(self)
274
275        self.sbar_vert.config(command = self.yview)
276        self.sbar_hor.config(command = self.xview, orient='horizontal')
277        self.canvas.config(xscrollcommand = self.sbar_hor.set, yscrollcommand = self.sbar_vert.set)
278       
279        self.movenoCanvas = Canvas(self, width=150, height=15, background='white')
280        self.movenoCanvas.config(scrollregion=(0,0,1000,15))
281        # self.movenoCanvas.config(xscrollcommand = self.sbar_hor.set)
282
283        self.updateMovenoCanvas()
284     
285        self.movenoCanvas.grid(row=0, column=0, sticky=NSEW)
286        self.sbar_vert.grid(row=1, column=1, sticky=NSEW)
287        self.sbar_hor.grid(row=2, column=0, sticky=NSEW)
288        self.canvas.grid(row=1, column=0, sticky=NSEW)
289
290        self.rowconfigure(1, weight=1)
291        self.columnconfigure(0, weight=1)
292
293        # self.config(height=100, width=200)
294        # self.canvSize = 200,100
295        self.drawn = []
296
297        self.lastbind = None
298
299    def updateMovenoCanvas(self):
300        """ Put numbers on the small white canvas above the SGF tree canvas, to indicate move numbers."""
301       
302        self.movenoCanvas.delete(ALL)
303        for i in range(90):
304            self.movenoCanvas.create_text(int(SGFtreeCanvas.UNIT*.75)+i*5*SGFtreeCanvas.UNIT,7,text=`5*i`,
305                                          font=('Helvetica', 8))
306   
307
308    def xview(self, a1, a2=None, a3=None):
309        if a1 == MOVETO:
310            self.movenoCanvas.xview(a1, a2)
311            self.canvas.xview(a1, a2)
312        elif a1 == SCROLL:
313            self.movenoCanvas.xview(a1, a2, a3)
314            self.canvas.xview(a1, a2, a3)
315        else:
316            return
317            # raise Exception()
318
319
320    def yview(self, a1, a2=None, a3=None):
321
322        if a1 == MOVETO: self.canvas.yview(a1, a2)
323        elif a1 == SCROLL: self.canvas.yview(a1, a2, a3)
324        elif a1 == 'refresh': pass
325        else: return # raise Exception()
326
327        self.canvas.update_idletasks()
328
329        vert = self.sbar_vert.get()
330        y0 = int(vert[0] * self.canvSize[1])
331        y1 = int(vert[1] * self.canvSize[1])
332
333        u = max(0, (y0-300)/SGFtreeCanvas.UNIT)
334        l = (y1+500)/SGFtreeCanvas.UNIT
335
336        # print 'yview', u,l
337
338        nodelist = [(self.rootnode, 0,0)]
339
340        while nodelist:
341            c, posx, posy = nodelist[-1]
342            del nodelist[-1]
343
344            # print posx, posy
345
346            if u <= posy <= l and not posy in self.drawn:
347                self.mark(c, posx, posy)
348               
349                if c.previous:
350                    if c.up and c.up.up:
351                        self.link(posx, posy, c.posyD+1)
352                    else:
353                        self.link(posx, posy, c.posyD)
354
355                    if c.down and posy+c.down.posyD>l:
356                        if c.up:
357                            self.link(posx, posy+c.down.posyD, c.down.posyD+1)
358                        else:
359                            self.link(posx, posy+c.down.posyD, c.down.posyD)
360
361            if c != self.rootnode and c.down and posy + c.down.posyD <= l:
362                d = c.down
363                py = posy + d.posyD
364                while d.down and py + d.down.posyD < u:
365                    d = d.down
366                    py += d.posyD
367                nodelist.append((d, posx, py))
368
369            if c.down and posy + c.down.posyD < u: continue
370
371            while c.next:
372                c = c.next
373                posx += 1
374                if u <= posy <= l and not posy in self.drawn:
375
376                    self.mark(c, posx, posy)
377
378                    if c.previous:
379                        if c.up and c.up.up:
380                            self.link(posx, posy, c.posyD+1)
381                        else:
382                            self.link(posx, posy, c.posyD)
383
384                        if c.down and posy+c.down.posyD>l:
385                            if c.up: delta2 = 1
386                            else: delta2 = 0
387                            self.link(posx, posy + c.down.posyD, c.down.posyD + delta2)
388
389                if c.down and posy + c.down.posyD <= l:
390                    d = c.down
391                    py = posy + d.posyD
392                    while d.down and py + d.down.posyD < u:
393                        d = d.down
394                        py += d.posyD
395                    nodelist.append((d, posx, py))
396
397        self.canvas.lower('lines')
398           
399        for i in range(u, l):
400             if not i in self.drawn: self.drawn.append(i)
401
402       
403    def mark(self, c, posx, posy):
404        try:
405            n = c.getData()
406            if n.has_key('W'): color = 'white'
407            elif n.has_key('B'): color = 'black'
408            else:
409                color = 'red'
410        except: color = 'yellow'
411
412        self.canvas.create_oval(SGFtreeCanvas.UNIT*posx+SGFtreeCanvas.UNIT/2,
413                                SGFtreeCanvas.UNIT*posy+SGFtreeCanvas.UNIT/2,
414                                SGFtreeCanvas.UNIT*posx+SGFtreeCanvas.UNIT,
415                                SGFtreeCanvas.UNIT*posy+SGFtreeCanvas.UNIT, fill=color)
416
417        try:
418            if n.has_key('C') or n.has_key('TR') or n.has_key('SQ') or n.has_key('CR') or n.has_key('LB') or n.has_key('MA'):
419                self.canvas.create_oval(SGFtreeCanvas.UNIT*posx+SGFtreeCanvas.UNIT*2/3,
420                                        SGFtreeCanvas.UNIT*posy+SGFtreeCanvas.UNIT*2/3,
421                                        SGFtreeCanvas.UNIT*posx+SGFtreeCanvas.UNIT*5/6,
422                                        SGFtreeCanvas.UNIT*posy+SGFtreeCanvas.UNIT*5/6, fill='blue')
423        except: pass
424       
425
426    def link(self, posx, posy, delta):
427
428        s4 = SGFtreeCanvas.UNIT/4
429        s34 = 3*SGFtreeCanvas.UNIT/4
430       
431        if delta == 0:
432            self.canvas.create_line(SGFtreeCanvas.UNIT*posx-s4,
433                                    SGFtreeCanvas.UNIT*posy+s34,
434                                    SGFtreeCanvas.UNIT*posx+s34,
435                                    SGFtreeCanvas.UNIT*posy+s34,
436                                    fill='blue', tags='lines', width=2)       
437        else:
438            self.canvas.create_line(SGFtreeCanvas.UNIT*posx-s4,
439                                    SGFtreeCanvas.UNIT*(posy-1)+s34,
440                                    SGFtreeCanvas.UNIT*posx+s34,
441                                    SGFtreeCanvas.UNIT*posy+s34,
442                                    fill='blue', tags='lines', width=2)       
443            if delta > 1:
444                self.canvas.create_line(SGFtreeCanvas.UNIT*posx-s4,
445                                        SGFtreeCanvas.UNIT*(posy-delta)+s34,
446                                        SGFtreeCanvas.UNIT*posx-s4,
447                                        SGFtreeCanvas.UNIT*(posy-1)+s34,
448                                        fill='blue', tags='lines', width=2)       
449
450
451SGFtreeCanvas.UNIT = 25
452
453
454
455# ---------------------------------------------------------------------------------------
456
457class DataWindow:
458
459    def __init__(self, master):
460        self.mster = master
461       
462        window = Toplevel()
463        window.title('Data window')
464        window.protocol('WM_DELETE_WINDOW', self.quit)
465        # window.withdraw()
466       
467        win = Pmw.PanedWidget(window, orient='vertical')
468
469        self.win = win
470        win.pack(expand=YES, fill=BOTH)
471       
472        self.initPanes()
473       
474        self.SGFtreeC = SGFtreeCanvas(self.gametreeF)
475       
476        self.SGFtreeC.pack(side=LEFT, expand=YES, fill=BOTH)
477
478        self.guessModeCanvas = Canvas(self.gametreeF, width=150, height=80, background='#FFBA59')
479
480       
481       
482        self.filelistV = IntVar()
483        self.filelistV.set(1)
484        b1 = Checkbutton(self.toolbarF, text = 'Files', variable = self.filelistV,
485                         command = self.toggleFilelist, indicatoron=0)
486        b1.grid(row=0, column=0)
487       
488        self.gamelistV = IntVar()
489        self.gamelistV.set(1)
490        b1 = Checkbutton(self.toolbarF, text = 'Games', variable = self.gamelistV,
491                         command = self.toggleGamelist, indicatoron=0)
492        b1.grid(row=0, column=1)
493
494        self.gameinfoV = IntVar()
495        self.gameinfoV.set(1)
496        b1 = Checkbutton(self.toolbarF, text = 'Info', variable = self.gameinfoV,
497                         command = self.toggleGameinfo, indicatoron=0)
498        b1.grid(row=0, column=2)
499
500        self.editToolsV = IntVar()
501        self.editToolsV.set(1)
502        b1 = Checkbutton(self.toolbarF, text = 'Edit tools', variable = self.editToolsV,
503                         command = self.toggleEditTools, indicatoron=0)
504        b1.grid(row=0, column=3)
505
506        self.gametreeV = IntVar()
507        self.gametreeV.set(1)
508        b1 = Checkbutton(self.toolbarF, text = 'Game tree', variable = self.gametreeV,
509                         command = self.toggleGametree, indicatoron=0)
510        b1.grid(row=0, column=4)
511
512        self.commentsV = IntVar()
513        self.commentsV.set(1)
514        b1 = Checkbutton(self.toolbarF, text = 'Comments', variable = self.commentsV,
515                         command = self.toggleComments, indicatoron=0)
516        b1.grid(row=0, column=5)
517
518        self.filelistF.rowconfigure(0, weight=1)
519        self.filelistF.columnconfigure(0, weight=1)
520
521        self.filelist = ScrolledList(self.filelistF, height = 4)
522        self.filelist.grid(row=0, column=0, rowspan=4, sticky=NSEW)
523
524        self.filelistB1 = Button(self.filelistF, text = 'NEW', command = self.mster.newFile)
525        self.filelistB1.grid(row=0, column=1, sticky=S)
526        self.filelistB2 = Button(self.filelistF, text= 'OPEN', command = self.mster.openFile)
527        self.filelistB2.grid(row=1, column=1, sticky=S)
528        self.filelistB3 = Button(self.filelistF, text = 'DEL', command = self.mster.delFile)
529        self.filelistB3.grid(row=0, column=2, sticky=S)
530        self.filelistB4 = Button(self.filelistF, text = 'split', command=self.mster.splitCollection)
531        self.filelistB4.grid(row=1, column=2, sticky=S)
532
533        self.tkImages = []
534        for button, filename in [(self.filelistB1, 'new'), (self.filelistB2, 'open'), (self.filelistB3, 'trash'),
535                                 (self.filelistB4, 'split')]:
536            try:
537                im = PhotoImage(file=os.path.join(self.mster.basepath, 'gifs/', filename+'.gif'))
538                button.config(image=im)
539                self.tkImages.append(im)
540            except:
541                pass
542
543        self.filelist.onSelectionChange = self.mster.changeCurrentFile
544        self.filelist.list.bind('<1>', self.mster.changeCurrentFile)
545
546        self.gamelistF.rowconfigure(0, weight=1)
547        self.gamelistF.columnconfigure(0, weight=1)
548
549        self.gamelist = ScrolledList(self.gamelistF, height=4)
550        self.gamelist.grid(row=0, column=0, rowspan=4, sticky=NSEW)
551
552        self.gamelistB1 = Button(self.gamelistF, text = 'NEW', command = self.mster.newGame)
553        self.gamelistB1.grid(row=0, column=1, sticky=S)
554        self.gamelistB2 = Button(self.gamelistF, text = 'DEL', command = self.mster.delGame)
555        self.gamelistB2.grid(row=1, column=1, sticky=S)
556
557        for button, filename in [(self.gamelistB1, 'new'), (self.gamelistB2, 'trash')]:
558            try:
559                im = PhotoImage(file=os.path.join(self.mster.basepath, 'gifs/', filename+'.gif'))
560                button.config(image=im)
561                self.tkImages.append(im)
562            except:
563                pass
564       
565        self.gamelist.onSelectionChange = self.mster.changeCurrentGame
566        self.gamelist.list.bind('<1>', self.gamelistClick)
567        self.gamelist.list.bind('<B1-Motion>', self.gamelistDrag)
568        self.gamelist.list.bind('<ButtonRelease-1>', self.gamelistRelease)
569
570        self.gamelist.clickedLast = -1
571        self.gamelist.dragLast = -1
572
573        self.gameinfo = ScrolledText_HSB(self.gameinfoF, height = 4, width = 20, wrap=WORD, 
574                                         relief=SUNKEN)
575        self.gameinfo.config(state=DISABLED)
576        self.gameinfo.pack(fill=BOTH, expand=NO)
577
578        lab = Label(self.editToolsF, text='Ctrl-Click:')
579       
580        self.labelType = StringVar()
581        self.labelType.set('DEL ST')
582
583