root/06/devel-old/kombilo.py

Revision 174, 154.6 kB (checked in by ug, 2 years ago)

Worked some more on pattern.cc ... not done yet, though.

  • Property svn:executable set to *
Line 
1 #! /usr/bin/env python
2 # File: kombilo.py
3
4 ##   Copyright (C) 2001-6 Ulrich Goertz (u@g0ertz.de)
5
6 ##   Kombilo 0.6 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 tkCommonDialog import Dialog
29 from tkSimpleDialog import askstring
30
31 try:
32     import Pmw   # FIXME: also check Pmw version!
33 except:
34     root=Tk()
35     t = Text(root, wrap=WORD, width=70, height=12)
36     t.pack()
37     t.insert(END, 'Kombilo 0.6\n\n The Python Megawidgets (Pmw) library was not found. It is needed by Kombilo, ' + \
38              'so you have to install it before you can use Kombilo. See the Kombilo tutorial at ' + \
39              'http://www.u-go.net/kombilo/tutorial#installation or the Pmw web site at ' + \
40              'http://pmw.sourceforge.net/ for more information.')
41     root.mainloop()
42     sys.exit()
43    
44 import time
45 import os
46 import sys
47 import cPickle
48 from copy import copy, deepcopy
49 from string import split, find, join, strip, replace, digits, maketrans, translate, lower, upper
50 import glob
51 import re
52 from array import *
53 import webbrowser
54
55 try:
56     from abstractBoard import *
57 except ImportError:
58     print 'ouch' # FIXME
59     from abstractBoardPY import *
60 from board import *
61 import v
62 try:
63     from patternPY import *
64 except ImportError:
65     print 'ouch pattern' # FIXME
66     from patternPY import *
67 from searchPY import *
68 from aglPY import *
69 from algosPY import *
70
71 try:
72     from sgfpars import Node, Cursor, SGFError, SGFescape
73 except:
74     from sgfparser import Node, Cursor, SGFError, SGFescape
75    
76 try:
77     import matchC
78     CimportSucceeded = 1
79 except:
80     CimportSucceeded = 0
81
82 # ---------------------------------------------------------------------------------------
83
84 def wildcolor_to_char(color):
85     return {'green': '*', 'black': 'x', 'white': 'o'}[color[1]]
86
87 def char_to_wildcolor(c):
88     try:
89         return {'*': 'green', 'x': 'black', 'o': 'white'}[c]
90     except:
91         print "DEBUG: char_to_wildcolor called with", c
92         # FIXME
93
94
95 class BoardWC(Board):
96     """
97     Board with support for wildcards and selection
98     of search-relevant region.
99     """
100    
101     def __init__(self, master, boardsize, canvasSize, fuzzy, labelFontsize, fixedColor, smartFixedColor,
102                  boardImg, blackImg, whiteImg):
103         Board.__init__(self, master, boardsize, canvasSize, fuzzy, labelFontsize, 1, None,
104                        boardImg, blackImg, whiteImg)
105
106         self.wildcards = {}
107         self.MSlabels = {}
108         self.selection = ((0,0),(boardsize-1, boardsize-1))
109
110         self.fixedColor = fixedColor
111         self.smartFixedColor = smartFixedColor
112        
113         self.bound3 = self.bind('<Button-3>', self.selStart)
114         self.bound3m = self.bind('<B3-Motion>', self.selDrag)
115         self.bounds1 = self.bind('<Shift-1>', self.wildcard)
116
117         self.invertSelection = IntVar()
118
119
120     def resize(self, event = None, force=0):
121         """ Resize the board. Take care of wildcards and selection here. """
122        
123         Board.resize(self, event, force)
124         for x,y in self.wildcards.keys():
125             x1, x2, y1, y2 = self.getPixelCoord((x,y),1)
126             if self.canvasSize[1]<=7: margin = 5
127             else: margin = 4
128             self.wildcards[(x,y)] = self.create_oval(x1+margin, x2+margin, y1-margin, y2-margin, fill = 'green',
129                                                      tags=('wildcard','non-bg'))
130
131         self.delete('selection')
132         if self.selection != ((0,0),(self.boardsize-1,self.boardsize-1)) and self.selection[1] != (-1,-1):
133             p0 = self.getPixelCoord(self.selection[0],1)
134             p1 = self.getPixelCoord((self.selection[1][0]+1, self.selection[1][1]+1), 1)
135             min = self.getPixelCoord((0,0), 1)[0]+1
136             max = self.getPixelCoord((self.boardsize,self.boardsize),1)[1]-1
137             if self.canvasSize[1] <= 7:
138                 self.create_rectangle(p0[0], p0[1], p1[0], p1[1],
139                                       tags=('selection', 'non-bg'))
140             elif self.invertSelection.get():
141                 self.create_rectangle(p0[0], p0[1], p1[0], p1[1], fill='brown', stipple='gray50', outline='',
142                                       tags='selection')
143             else:
144                 if p0[1] > min:
145                     self.create_rectangle(min, min, max, p0[1], fill='brown', stipple='gray50', outline='',
146                                           tags='selection')
147                 if p0[0] > min and p0[1] < max:
148                     self.create_rectangle(min, p0[1], p0[0], max, fill='brown', stipple='gray50', outline='',
149                                           tags='selection')
150                 if p1[1] < max:
151                     self.create_rectangle(p0[0], p1[1], p1[0], max, fill='brown', stipple='gray50', outline='',
152                                           tags='selection')
153                 if p1[0] < max and p0[1] < max:
154                     self.create_rectangle(p1[0], p0[1], max, max, fill='brown', stipple='gray50', outline='',
155                                           tags='selection')
156             self.tkraise('non-bg')
157            
158         self.update_idletasks()
159
160
161     def wildcard(self, event):
162         """ Place/delete a wildcard at position of click. """
163        
164         x, y = self.getBoardCoord((event.x, event.y), 1)
165         if x==-1 or y==-1 or self.status.has_key((x,y)): return
166
167         if self.wildcards.has_key((x,y)):
168             if self.wildcards[(x,y)][1] == self.currentColor:
169                 self.delete(self.wildcards[(x,y)][0])
170                 color = self.invert(self.currentColor)
171             elif self.wildcards[(x,y)][1] == 'green':
172                 self.delete(self.wildcards[(x,y)][0])
173                 color = self.currentColor
174             else:
175                 self.delete(self.wildcards[(x,y)][0])
176                 del self.wildcards[(x,y)]
177                 self.changed.set(1)
178                 return
179         else:
180             color = 'green'
181            
182         x1, x2, y1, y2 = self.getPixelCoord((x,y),1)
183         if self.canvasSize[1]<=7: margin = 5
184         else: margin = 4
185         self.wildcards[(x,y)] = (self.create_oval(x1+margin, x2+margin, y1-margin, y2-margin, fill = color,
186                                                   tags=('wildcard','non-bg')),
187                                  color)
188         self.tkraise('label')
189         self.changed.set(1)       
190
191
192     def delWildcards(self):
193         """ Delete all wildcards. """
194        
195         if self.wildcards: self.changed.set(1)
196         self.delete('wildcard')
197         self.wildcards = {}
198
199
200     def placeLabel(self, pos, type, text=None, color=None):
201         """ Place a label; take care of wildcards at same position. """
202        
203         if self.wildcards.has_key(pos):
204             if self.widlcards[pos][1] in ['green', 'white']:
205                 override = ('black', '')
206             else:
207                 override = ('white', '')
208         else: override = None
209
210         Board.placeLabel(self, pos, type, text, color, override)
211
212        
213     def placeMSLabel(self, pos, type, text):
214         x1, x2, y1, y2 = self.getPixelCoord(pos, 1)
215         self.create_text((x1+y1)/2,(x2+y2)/2, text=text, fill='red',
216                          font = (self.labelFont[0].get(),
217                                  self.labelFont[1].get() + self.canvasSize[1]/5,
218                                  self.labelFont[2].get()),
219                          tags=('non-bg', 'MSLabel'))
220
221
222     def delMSLabels(self):
223         self.delete('MSLabel')
224         self.MSLabels = {}
225                      
226     # ---- selection of search-relevant section -----------------------------------
227
228     def selStart(self, event):
229         """ React to right-click. """
230         self.delete('selection')
231         x, y = self.getBoardCoord((event.x, event.y), 1)
232         x = max(x, 0)
233         y = max(y, 0)
234         self.selection = ((x,y), (-1,-1))
235         if self.smartFixedColor.get(): self.fixedColor.set(1)
236         self.changed.set(1)
237
238
239     def selDrag(self, event):
240         """ React to right-mouse-key-drag. """
241         pos = self.getBoardCoord((event.x, event.y), 1)
242         if pos[0] >= self.selection[0][0] and pos[1] >= self.selection[0][1]:
243             self.setSelection(self.selection[0], pos)
244            
245
246     def setSelection(self, pos0, pos1):
247         self.selection = (pos0, pos1)
248         self.delete('selection')
249         p0 = self.getPixelCoord(pos0,1)
250         p1 = self.getPixelCoord((pos1[0]+1, pos1[1]+1), 1)
251         min = self.getPixelCoord((0,0), 1)[0]+1
252         max = self.getPixelCoord((self.boardsize,self.boardsize),1)[1]-1
253         if self.canvasSize[1] <= 7:
254             self.create_rectangle(p0[0], p0[1], p1[0], p1[1],
255                                   tags=('selection', 'non-bg'))
256         elif self.invertSelection.get():
257             self.create_rectangle(p0[0], p0[1], p1[0], p1[1], fill='brown', stipple='gray50', outline='',
258                                   tags='selection')
259         else:
260             if p0[1] > min:
261                 self.create_rectangle(min, min, max, p0[1], fill='brown', stipple='gray50', outline='',
262                                       tags='selection')
263             if p0[0] > min and p0[1] < max:
264                 self.create_rectangle(min, p0[1], p0[0], max, fill='brown', stipple='gray50', outline='',
265                                       tags='selection')
266             if p1[1] < max:
267                 self.create_rectangle(p0[0], p1[1], p1[0], max, fill='brown', stipple='gray50', outline='',
268                                       tags='selection')
269             if p1[0] < max and p0[1] < max:
270                 self.create_rectangle(p1[0], p0[1], max, max, fill='brown', stipple='gray50', outline='',
271                                       tags='selection')
272            
273         self.tkraise('non-bg')
274        
275         if self.smartFixedColor.get():
276             if self.selection == ((0,0), (self.boardsize, self.boardsize-1)):
277                 self.fixedColor.set(1)
278             else:
279                 self.fixedColor.set(0)
280                
281        
282     def newPosition(self):
283         """ Clear board, selection. """
284         self.delete('selection')
285         self.clear()
286         self.delLabels()
287         self.delMarks()
288         self.delWildcards()
289         self.selection = ((0,0),(self.boardsize-1,self.boardsize-1))
290
291         if self.smartFixedColor.get(): self.fixedColor.set(1)
292
293     # ---- snapshot & restore (for 'back' button)
294
295     def snapshot(self):
296         data = Board.snapshot(self)
297         data.extend([copy(self.wildcards), self.selection])
298         return data
299
300     def restore(self, data):
301         self.wildcards = data[5]
302         self.selection = data[6]
303         Board.restore(self, data) # This does a self.resize(), which takes care of wildcards and selection in our case
304
305 # ---------------------------------------------------------------------------------------
306
307 class chooseDirectory(Dialog):
308     """ A wrapper tor the Tk chooseDirectory widget. """
309    
310     command = "tk_chooseDirectory"
311
312     def _fixresult(self, widget, result):
313         if result:
314             self.options["initialdir"] = result
315         self.directory = result
316         return result
317
318
319 def askdirectory(**options):
320     return apply(chooseDirectory, (), options).show()
321
322 # ---------------------------------------------------------------------------------------
323
324 class TextEditor:
325     """ A very simple text editor, based on the Tkinter ScrolledText widget.
326     You can perform very limited editing, and save the result to a file. """
327
328     def __init__(self, t = '', defpath='', font = None):
329
330         if font is None:
331             font = (StringVar(), IntVar(), StringVar())
332             font[0].set('Courier')
333             font[1].set(10)
334             font[2].set('')
335            
336         self.window = Toplevel()
337         self.window.protocol('WM_DELETE_WINDOW', self.quit)
338         self.text = ScrolledText(self.window, width=70, height=30,
339                                  font=(font[0].get(), font[1].get(), font[2].get()))
340         self.text.pack(side=BOTTOM, fill=BOTH, expand=YES)
341         self.text.insert(END, t)
342
343         self.buttonFrame = Frame(self.window)
344         self.buttonFrame.pack(side=TOP, expand=NO, fill=X)
345
346         Button(self.buttonFrame, text='Quit', command=self.quit).pack(side=RIGHT)
347         Button(self.buttonFrame, text='Save as', command=self.saveas).pack(side=RIGHT)
348
349         # self.window.tkraise()
350         self.window.focus_force()
351
352         if defpath:
353             self.defpath = defpath
354         else:
355             self.defpath = os.curdir
356
357            
358     def saveas(self):
359         f = tkFileDialog.asksaveasfilename(initialdir = self.defpath)
360         if not f: return
361         try:
362             file = open(f, 'w')
363             file.write(self.text.get('1.0', END).encode('utf-8','ignore'))
364             file.close()
365         except IOError:
366             showwarning('IO Error', 'Cannot write to ' + f)
367
368
369     def quit(self):
370         self.window.destroy()
371
372
373 class ESR_TextEditor(TextEditor):
374     """
375     The text editor which is used by the exportSearchResults function.
376     It adds a button to include the complete game list to the TextEditor.
377     """
378
379     def __init__(self, master, style, t='', defpath='', font=None):
380         TextEditor.__init__(self, t, defpath, font)
381         self.mster = master
382         self.style = style
383        
384         Button(self.buttonFrame, text='Include game list', command=self.includeGameList).pack(side=LEFT)
385
386
387     def includeGameList(self):
388         if self.style: # wiki
389             self.text.insert(END, '\n\n!Game list\n\n' + join(self.mster.gamelist.getAllStrings(), ' %%%\n'))
390         else:
391             self.text.insert(END, '\n\nGame list\n\n' + join(self.mster.gamelist.getAllStrings(), '\n'))   
392
393 # -------------------------------------------------------------------------------------
394
395 class DataWindow(v.DataWindow):
396
397     def __init__(self, master):
398
399         v.DataWindow.__init__(self, master)
400
401         self.prevSF = Pmw.ScrolledFrame(self.prevSearchF, usehullsize=1, hull_width=300, hull_height=135,
402                                         hscrollmode='static', vscrollmode='none', vertflex='elastic')
403         self.prevSF.pack(expand=YES, fill=X)
404         self.prevSV = IntVar()
405         self.prevSV.set(1)
406
407         b1 = Checkbutton(self.toolbarF, text = 'History', variable = self.prevSV,
408                          command = self.togglePrevSearches, indicatoron=0)
409         b1.grid(row=0, column=6)
410
411         self.win.setnaturalsize()
412
413        
414     def initPanes(self):
415         """ Create the panes in the data window. """
416
417         self.toolbarF = self.win.add(name='toolb', min=28, max=28)
418         self.filelistF = self.win.add(name='filel', min=0.01, max=1.0, size=65)
419         self.gamelistF = self.win.add(name='gamel', min=0.01, max=1.0, size=65)
420         self.gameinfoF = self.win.add(name='gamei', min=0.01, max=1.0, size=75)
421         self.editToolsF = self.win.add(name='editt', min=1, max=30, size=30)
422         self.gametreeF = self.win.add(name='gamet', min=0.01, max=1.0, size=90)
423         self.commentsF = self.win.add(name='comm', min=0.01, max=1.0, size=80)
424         self.prevSearchF = self.win.add('prse', min=1, max=1.0, size=135)
425
426
427     def get_geometry(self):
428         """ Return a list of current sizes of the panes. """
429        
430         l = [ self.filelistV.get(), self.win._size['filel'],
431               self.gamelistV.get(), self.win._size['gamel'],
432               self.gameinfoV.get(), self.win._size['gamei'],
433               self.editToolsV.get(), self.win._size['editt'],
434               self.gametreeV.get(), self.win._size['gamet'],
435               self.commentsV.get(), self.win._size['comm'],
436               self.prevSV.get(), self.win._size['prse']]
437         l1 = [ `x` for x in l ]
438         l1.append(self.window.geometry())
439
440         return join(l1, '|%')
441
442        
443     def set_geometry(self, s):
444         """ Reset the sizes of the panes to the given ones. """
445        
446         l = split(s, '|%')
447        
448         l1 = [ self.filelistV,
449                self.gamelistV,
450                self.gameinfoV,
451                self.editToolsV,
452                self.gametreeV,
453                self.commentsV,
454                self.prevSV ]
455                
456         for i in range(len(l)/2):
457             l1[i].set(int(l[2*i]))
458             if int(l[2*i]):
459                 self.win.configurepane(i+1, min=10, max=1.0, size = int(l[2*i+1]))
460             else:
461                 self.win.configurepane(i+1, min=0.0, max=0.0, size = 0.0)
462         self.win.updatelayout()
463         self.window.geometry(l[-1])
464
465
466     def gamelistRelease(self, event):
467         index1, index2 = v.DataWindow.gamelistRelease(self, event)
468         if index1:
469             self.mster.prevSearches.exchangeGames(self.mster.cursor, index1, index2)
470        
471
472     def togglePrevSearches(self):
473         if self.prevSV.get():
474             s = self.window.geometry()
475             x1, x2, x3 = split(s, '+')
476             x, y = split(x1, 'x')
477             y = `int(y)+135`
478             self.window.geometry('%sx%s+%s+%s' % (x, y, x2, x3))
479             self.win.configurepane(7, min=10, max=1.0, size=135)
480         else:
481             s = self.window.geometry()
482             x1, x2, x3 = split(s, '+')
483             x, y = split(x1, 'x')
484             y = `int(y)-self.win._size['prse']`
485             self.window.geometry('%sx%s+%s+%s' % (x, y, x2, x3))
486             self.win.configurepane(7, min=0.0, max=0.0, size=0.0)
487         self.win.updatelayout()
488        
489 # -------------------------------------------------------------------------------------
490
491 class GameList(abstractGameList, v.ScrolledList):
492     """
493     This is a scrolled list which shows the game list. All the underlying data
494     is contained in self.DBlist['9'], self.DBlist['13'], self.DBlist['19'], which are
495     lists of dictionaries containing the information for the single databases.
496     self.DBlist[`boardsize`][i] will contain the attributes
497     name: name of the database, i.e. path to the *.db files
498     sgfpath: path to the SGF files
499     namelist: list of all games in the database.
500               This is a list of tuples of the form
501               (filename, PB, PW, result, signature, date)
502     current: a list of the indices of the games in data which are in the current
503              game list
504     results: for each game in current, the list of matches found in the previous
505              search (If there are lots of matches, this list will consume a lot
506              of memory. Maybe at some time I will get around to fix this ...)
507     """
508    
509     def __init__(self, parent, master, noGamesLabel, winPercLabel, gameinfo):
510         abstractGameList.__init__(self)
511         v.ScrolledList.__init__(self, parent)
512         self.list.config(width=52, height=6)
513
514         self.onSelectionChange = self.printGameInfo
515         self.list.bind('<Button-1>', self.onSelectionChange)
516
517         self.bind('<Up>', self.up)
518         self.bind('<Down>', self.down)
519         self.bind('<Prior>', self.pgup)
520         self.bind('<Next>', self.pgdown)
521         self.bind('<Return>', self.handleDoubleClick)
522         self.list.bind('<Double-1>', self.handleDoubleClick)
523         self.list.bind('<Shift-1>', self.handleShiftClick)
524         self.list.bind('<Button-3>', self.rightMouseButton)
525
526         self.mster = master
527         self.options = self.mster.options
528         self.datapath = self.mster.datapath # FIXME !?
529         
530         self.noGamesLabel = noGamesLabel
531         self.winPercLabel = winPercLabel
532         self.gameinfo = gameinfo
533
534        
535     def rightMouseButton(self, event):
536        
537         index = self.list.nearest(event.y)
538         if index == -1: return
539         filename, gameNumber = self.getFilename(index)
540
541         try:
542             file = open(filename)
543             sgf = file.read()
544             file.close()
545             c = Cursor(sgf, 1)
546             rootNode = c.getRootNode(gameNumber)
547         except IOError:
548             showwarning('Error', 'I/O Error')
549             return
550         except SGFError:
551             showwarning('Error', 'SGF error')
552             return
553        
554         backup = copy(rootNode)
555
556         newRootNode = self.mster.gameinfo(rootNode)
557         if backup != newRootNode:
558             c.updateRootNode(newRootNode, gameNumber)
559             try:
560                 s = c.output()
561                 file = open(filename, 'w')
562                 file.write(s)
563                 file.close()
564             except IOError:
565                 showwarning('I/O Error', 'Could not write to file ' + filename)
566
567
568     def handleDoubleClick(self, event):
569         """ This is called upon double-clicks."""
570        
571         index = self.list.curselection()
572         if index: self.mster.openViewer(index)
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)
580         self.onSelectionChange(event)
581         try:
582             label = self.list.get(index)
583         except:
584             return
585         self.mster.altOpenViewer(index)
586
587
588     def reset(self):
589
590         abstractGameList.reset(self)
591         self.sync()
592
593
594     def changeBoardSize(self, boardsize):
595         abstractGameList.changeBoardSize(self, boardsize)
596         self.sync()
597
598
599     def sync(self):
600         """ Put the changes in abstractGamelist in effect in self.list. """
601         numOfG = self.numOfGames()
602         self.noGamesLabel.config(text = `numOfG` + ' games')
603
604         if self.numOfGames():
605             Bperc = self.Bwins * 100.0 / numOfG
606             Wperc = self.Wwins * 100.0 / numOfG
607             self.winPercLabel.config(text='B: %1.1f%%, W: %1.1f%%' % (Bperc, Wperc))
608         else: self.winPercLabel.config(text='')
609
610         self.delete(0, END)
611         self.insert(END, self.getAllStrings())
612
613
614     def printGameInfo(self, event, index = -1):
615         """ Print game info of selected game. """
616
617         if index == -1:
618             index = self.list.nearest(event.y)
619             self.focus()
620         if index == -1:
621             return
622
623         t, t2 = abstractGameList.getGameInfo(self, index)
624        
625         self.gameinfo.config(state=NORMAL)
626         self.gameinfo.delete('1.0', END)
627         self.gameinfo.insert('1.0', t)
628         if t2:
629             if t[-1]!='\n': self.gameinfo.insert(END, '\n')
630             self.gameinfo.insert(END, t2, 'blue')
631         self.gameinfo.config(state=DISABLED)
632
633
634     def clearGameInfo(self):
635         self.gameinfo.config(state=NORMAL)
636         self.gameinfo.delete('1.0', END)
637         self.gameinfo.config(state=DISABLED)
638
639     # ---- administration of DBlist ----------------------------------------------------
640
641     def addDB(self, window):
642         self.editDB_OK.config(state=DISABLED)
643         self.saveProcMess.config(state=DISABLED)
644         self.stopAddDBButton.config(state=NORMAL)
645
646         if self.options.oldTkVar.get():
647             dbp = tkFileDialog.askopenfilename(initialdir = self.datapath)
648
649             if dbp and os.path.isfile(dbp):
650                 dbp = os.path.split(dbp)[0]
651                 dbp = os.path.normpath(dbp)
652             else:
653                 self.editDB_OK.config(state=NORMAL)
654                 self.saveProcMess.config(state=NORMAL)
655                 self.stopAddDBButton.config(state=DISABLED)
656                 return
657         else:
658             try:
659                 dbp = str(askdirectory(initialdir = self.datapath))
660             except TclError:
661                 self.options.oldTkVar.set(1)
662                 dbp = tkFileDialog.askopenfilename(initialdir = self.datapath)
663
664                 if dbp and os.path.isfile(dbp): dbp = os.path.split(dbp)[0]
665                 else:
666                     self.editDB_OK.config(state=NORMAL)
667                     self.saveProcMess.config(state=NORMAL)
668                     self.stopAddDBButton.config(state=DISABLED)
669                     return
670                
671             if not dbp:
672                 self.editDB_OK.config(state=NORMAL)
673                 self.saveProcMess.config(state=NORMAL)
674                 self.stopAddDBButton.config(state=DISABLED)
675                 return
676             else: dbp = os.path.normpath(dbp)
677            
678         self.datapath = os.path.split(dbp)[0]
679
680         if self.options.storeDatabasesSeparately.get() and self.options.whereToStoreDatabases.get():
681             datap = (self.options.whereToStoreDatabases.get(), '')
682             if os.path.exists(datap[0]) and not os.path.isdir(datap[0]):
683                 showwarning('Error', datap[0] + ' is not a directory.')
684                 self.editDB_OK.config(state=NORMAL)
685                 self.saveProcMess.config(state=NORMAL)
686                 self.stopAddDBButton.config(state=DISABLED)
687                 return
688             elif not os.path.exists(datap[0]):
689                 if askyesno('Error', 'Directory ' + datap[0] + ' does not exist. Create it?'):
690                     try:
691                         os.makedirs(datap[0])
692                     except:
693                         showwarning('Error', datap[0] + ' could not be created.')
694                         self.editDB_OK.config(state=NORMAL)
695                         self.saveProcMess.config(state=NORMAL)
696                         self.stopAddDBButton.config(state=DISABLED)
697                         return
698                 else:
699                    self.editDB_OK.config(state=NORMAL)
700                    self.saveProcMess.config(state=NORMAL)
701                    self.stopAddDBButton.config(state=DISABLED)
702                    return         
703         else:
704             datap = ('', '#') # this means: same as dbpath
705
706         abstractGameList.addDB(self, dbp, datap, self.processMessages, 0, 15,
707                                '*.sgf', 1, 1, 0)
708         self.sync()
709         self.editDB_OK.config(state=NORMAL)
710         self.saveProcMess.config(state=NORMAL)
711         self.stopAddDBButton.config(state=DISABLED)
712
713
714     def addOneDB(self, arguments, dbpath, dummy):
715         abstractGameList.addOneDB(self, arguments, dbpath, dummy)
716         self.sync()
717         self.db_list.insert(END, dbpath)
718         self.db_list.list.see(END)       
719
720      
721     def removeDB(self):
722         toBeRemoved = []
723         while self.db_list.list.curselection():
724             index = self.db_list.list.curselection()[0]
725             self.db_list.delete(index)
726             i = int(index)
727             toBeRemoved.append(i)
728
729         abstractGameList.removeDB(self, toBeRemoved, self.processMessages)
730         self.sync()
731         self.mster.prevSearches.clear()
732         self.mster.currentSearchPattern = None
733                                  
734
735     def reprocessDB(self):
736         toBeReprocessed = []
737         self.editDB_OK.config(state=DISABLED)
738         self.mster.prevSearches.clear()
739         self.mster.currentSearchPattern = None
740        
741         for index in self.db_list.list.curselection():
742             i = int(index)
743             toBeReprocessed.append(i)
744
745         algos = None # FIXME
746         failed = abstractGameList.reprocessDB(self, toBeReprocessed, self.processMessages, algos)
747
748         for i in failed:
749             self.db_list.delete(i)
750
751         self.sync()
752         self.editDB_OK.config(state=NORMAL)
753
754
755     def toggleDisabled(self):
756         for index in self.db_list.list.curselection():
757             i = int(index)
758        
759             db = self.DBlist[`self.boardsize`][i]
760             if db.disabled:
761                 db.disabled = 0
762                 self.db_list.delete(i)
763                 self.db_list.insert(i, db.sgfpath)
764                 self.db_list.list.select_set(i)
765                 self.db_list.list.see(i)
766             else:
767                 db.disabled = 1
768                 self.db_list.delete(i)
769                 self.db_list.insert(i, 'DISABLED - ' + db.sgfpath)
770                 self.db_list.list.select_set(i)
771                 self.db_list.list.see(i)
772         self.reset()
773         self.mster.prevSearches.clear()
774         self.mster.currentSearchPattern = None
775        
776
777     def saveMessagesEditDBlist(self):
778         filename = tkFileDialog.asksaveasfilename(initialdir = os.curdir)
779         try:
780             file = open(filename, 'w')
781             file.write(self.processMessages.get('1.0', END))
782             file.close()
783         except IOError:
784             showwarning('Error', 'Could not write to ' + filename)
785
786     # ---- FIXME: move click, drag, (release) to DnDScrolledList (?!)
787
788     def DBlistClick(self, event):
789         self.db_list.clickedLast = self.db_list.list.nearest(event.y)
790         self.db_list.dragLast = -1
791
792
793     def DBlistDrag(self, event):
794         i = self.db_list.list.nearest(event.y)
795         if self.db_list.dragLast == -1:
796             if self.db_list.clickedLast == i: return
797             else: self.db_list.dragLast = self.db_list.clickedLast       
798         if self.db_list.dragLast != i:
799             s = self.db_list.list.get(self.db_list.dragLast)
800             self.db_list.delete(self.db_list.dragLast)
801             self.db_list.insert(i, s)
802             self.db_list.list.select_set(i)
803             self.db_list.dragLast = i
804         return 'break'
805
806
807     def DBlistRelease(self, event):
808         if self.db_list.dragLast == -1:
809             return
810        
811         i = self.db_list.list.nearest(event.y)
812
813         if self.db_list.dragLast != i:
814             s = self.db_list.list.get(self.db_list.dragLast)
815             self.db_list.delete(self.db_list.dragLast)
816             self.db_list.insert(i, s)
817             self.db_list.list.select_set(i)
818             self.db_list.dragLast = i
819
820         if self.db_list.clickedLast != i:
821             db = self.gamelist.DBlist[`self.boardsize`][self.db_list.clickedLast]
822             del self.gamelist.DBlist[`self.boardsize`][self.db_list.clickedLast]
823             self.gamelist.DBlist[`self.boardsize`].insert(i, db)
824             self.gamelist.sortlist(self.options.sortPerDatabase.get(), self.options.sortCriterion.get())
825             self.gamelist.reset()
826             self.prevSearches.clear()
827             self.currentSearchPattern = None
828
829
830     def browseDatabases(self):
831         initdir = self.options.whereToStoreDatabases.get() or os.curdir
832         filename = str(askdirectory(initialdir = initdir))
833         self.options.whereToStoreDatabases.set(filename)
834        
835
836     def toggleWhereDatabases(self):
837         if self.options.storeDatabasesSeparately.get():
838             self.whereDatabasesEntry.config(state=NORMAL)
839             if not self.options.whereToStoreDatabases.get(): self.browseDatabases()
840         else:
841             self.whereDatabasesEntry.config(state=DISABLED)
842
843        
844     def editDBlistWindow(self):
845
846         self.clearGameInfo()
847         self.stopAddDBVar = IntVar()
848         window = Toplevel()
849         window.transient(self.master)
850         window.title('Edit database list')
851
852         f1 = Frame(window)
853         f1.grid(row=0, sticky=NSEW)
854         f2 = Frame(wind