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

Revision 41, 109.2 kB (checked in by ug, 5 years ago)

Updated copyright notice

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