PyScore

001: # -*- python -*-
002: # classescon.py # python package PyScore.timesheet module classes.controller #
003: 
004: # PyScore
005: # a race scoring programme
006: # written by Matt Draisey
007: # 2004 April 6
008: 
009: reloadable=True
010: reloadables=[]
011: 
012: # classescon.py # python package PyScore.timesheet module classes.controller #
013: 
014: import itertools,random
015: import gtk,pango
016: from treemodel import filttab,filtmaster
017: from utility import utils,guiutils
018: from timesheet import Entries,entriesmod as modeller
019: from timesheet import classesview as view
020: 
021: # the initial model and model constants
022: 
023: def initialize_model():
024:     pass
025: 
026: classes_defaults=[("","")]+Entries.boatlist.classes_defaults_tab
027: 
028: mastermodel=filtmaster.FiltMaster(
029:     rows=[
030:         (division,fleet,{
031:             "":0,"PHRF A":1,"PHRF B":2,"JOG A":3,"JOG B":4,"Shark":5,
032:         }[division])
033:         for (division,fleet) in classes_defaults
034:     ],
035:     wcols=[True,False],xcols=[(0,int)]
036: )
037: mastermodel.add_strikethrough_attribute()
038: view.listview[0].set_model(mastermodel.FiltTable())
039: 
040: affirmmaster=filtmaster.FiltMaster(
041:     rows=[
042:         ("",       pango.STYLE_NORMAL,),
043:         ("Deny",   pango.STYLE_ITALIC,),
044:         ("Race",   pango.STYLE_NORMAL,),
045:         ("Series", pango.STYLE_NORMAL,),
046:         ("Year",   pango.STYLE_NORMAL,),
047:     ],
048:     ncols=1,xcols=[(pango.STYLE_NORMAL,int)]
049: )
050: affirmmaster.add_strikethrough_attribute()
051: view.listview[1].set_model(affirmmaster.FiltTable())
052: 
053: mastercascade=[
054:     filtmaster.FiltMaster(
055:         rows=[(str(p),pango.STYLE_NORMAL,) for p in ratingsranges],
056:         ncols=1,xcols=[(pango.STYLE_ITALIC,int)],
057:     )
058:     for ratingsranges in [
059:         [],
060:         [126,145,176],
061:         [177,192,225],
062:         [126,145,200],
063:         [201,213,225],
064:         [228],
065:     ]
066: ]
067: view.listview[2].set_model(mastercascade[0].FiltCombo())
068: 
069: # create working model
070: 
071: def working_model(zipnum):
072:     classrows=[c[:2] for c in Entries.boatlist.sample_boats_class[zipnum]]
073:     classratings=dict([
074:         (c[:2],c[2])
075:         for c in Entries.boatlist.sample_boats_class[zipnum]
076:     ])
077:     workingmodel=mastermodel.clone_master()
078:     workingcascade=mastercascade[:]
079:     def workingappend(c):
080:         l=len(workingcascade)
081:         workingcascade.append(filtmaster.FiltMaster(
082:             rows=[(str(p),pango.STYLE_NORMAL,) for p in [classratings[c]]],
083:             ncols=1,
084:             xcols=[(pango.STYLE_ITALIC,int)],
085:         ))
086:         return l
087:     for (i,c) in enumerate(classes_defaults):
088:         if c in classrows:
089:             classrows.remove(c)
090:             workingmodel.set_field(i,2,workingappend(c))
091:         else:
092:             workingmodel.set_strikethrough(i)
093:     for c in classrows:
094:         workingmodel.append_to_master(c)
095:         workingappend(c)
096:     return (workingmodel,workingcascade)
097: 
098: # the entry interface
099: 
100: def entry_open(zipnum,insertionfield=None,*c):
101:     s=":".join(utils.whittle(*c))
102:     if zipnum>=0:
103:         working=working_model(zipnum)
104:         model=working[0].FiltTable()
105:         workingcascade=working[1]
106:     else:
107:         model=mastermodel.FiltTable()
108:         workingcascade=mastercascade
109:     if insertionfield==0:
110:         insertionpoint=len(c[0])
111:     elif insertionfield==1:
112:         insertionpoint=len(c[0])+1+len(c[1])
113:     else:
114:         insertionpoint=None
115:     reset_entry(model,workingcascade,s,insertionfield,insertionpoint)
116: 
117: def entry_is_open():
118:     return is_entry()
119: 
120: def entry_tweak(zipnum):
121:     if zipnum>=0:
122:         working=working_model(zipnum)
123:         tweakmodel=working[0].FiltTable()
124:         tweakmastercascade=working[1]
125:     else:
126:         tweakmodel=mastermodel.FiltTable()
127:         tweakmastercascade=mastercascade
128:     tweak_entry(tweakmodel,tweakmastercascade)
129: 
130: def entry_close():
131:     clear_entry()
132: 
133: # constants and classes globals # (re)initialization interface
134: 
135: basecolour=view.listview[0].get_style().base[gtk.STATE_NORMAL]
136: colcolour=gtk.gdk.color_parse("#ffcc99")
137: 
138: class Classes(object):
139:     model=[view.listview[i].get_model() for i in [0,1,2]]
140:     cascademaster=mastercascade
141:     currentcascade=-1
142:     parse=filttab.SplitColumns(2,1,1)
143:     activemod=0
144:     currentf=["","",""]
145:     activecol=0
146: 
147: view.renderer[0].set_property("cell-background-gdk",colcolour)
148: 
149: if Classes.model[0].filter_nonempty():
150:     view.listview[0].set_cursor((0,),view.column[0])
151: if Classes.model[1].filter_nonempty():
152:     view.listview[1].set_cursor((0,))
153: if Classes.model[2].filter_nonempty():
154:     view.listview[2].set_cursor((0,))
155: 
156: def reset_entry(model,cascademaster,text,insertionfield,insertionpoint):
157:     view.box.set_text(text)
158:     Classes.model[0]=model
159:     Classes.cascademaster=cascademaster
160:     Classes.currentcascade=-1
161:     insertionpoint=None #box.set_position(insertionpoint)
162:     # split into submodels
163:     (newmod,newcol)=Classes.parse.split_comprehend(text,insertionpoint)
164:     chain=Classes.parse.get_split_padded()
165:     Classes.model[0].filter_comprehend(chain[0])
166:     # pretty background colour
167:     newcolumn=(view.column[newcol] if 0<=newcol<4 else None)
168:     if newcolumn<>None:
169:         view.renderer[newcol].set_property("cell-background-gdk",colcolour)
170:     Classes.activecol=newcol
171:     # model 0 set cursor
172:     view.listview[0].set_model(Classes.model[0])
173:     bestfit=Classes.model[0].exact_fit(2)
174:     if bestfit==None:
175:         Classes.model[0].filter_comprehend(chain[0])
176:         bestfit=Classes.model[0].best_fit()
177:     if bestfit<>None:
178:         if 0<=newcol<2:
179:             view.listview[0].set_cursor(bestfit,newcolumn)
180:         else:
181:             view.listview[0].set_cursor(bestfit)
182:         zipdiv=Classes.model[0].get_row(bestfit)[2]
183:     else:
184:         modeller.set_class_subentry(0,"","")
185:         zipdiv=-1
186:     Classes.currentf[0]=chain[0]
187:     # cascade model
188:     if zipdiv>=0:
189:         tweakmaster=Classes.cascademaster[zipdiv]
190:     else:
191:         tweakmaster=mastercascade[0]
192:     Classes.currentcascade=zipdiv
193:     Classes.model[1]=affirmmaster.FiltTable()
194:     Classes.model[1].filter_comprehend(chain[1])
195:     Classes.model[2]=tweakmaster.FiltCombo()
196:     Classes.model[2].filter_comprehend(chain[2])
197:     # model 1 set cursor
198:     view.listview[1].set_model(Classes.model[1])
199:     assert Classes.model[1].filter_nonempty()
200:     view.listview[1].set_cursor((0,))
201:     Classes.currentf[1]=chain[1]
202:     # model 2 set cursor
203:     view.listview[2].set_model(Classes.model[2])
204:     assert Classes.model[2].filter_nonempty()
205:     view.listview[2].set_cursor((0,))
206:     Classes.currentf[2]=chain[2]
207:     Classes.activemod=newmod
208: 
209: def is_entry():
210:     return Classes.model[0]<>None
211: 
212: def tweak_entry(tweakmodel,tweakmaster):
213:     if not Classes.model[0]: return
214:     s=Classes.currentf[0]
215:     tweakmodel.filter_comprehend(s)
216:     Classes.cascademaster=tweakmaster
217:     Classes.currentcascade=-1
218:     bestfit=tweakmodel.best_fit()
219:     view.listview[0].set_model(tweakmodel)
220:     Classes.model[0]=tweakmodel
221:     if bestfit<>None:
222:         view.listview[0].set_cursor(bestfit)
223:     else:
224:         modeller.set_class_subentry(0,"","")
225:         tweak_cascade_model(-1)
226: 
227: def tweak_cascade_model(zipdiv):
228:     if not Classes.model[1]: return
229:     if Classes.currentcascade==zipdiv:
230:         return
231:     if zipdiv>=0:
232:         tweakmaster=Classes.cascademaster[zipdiv]
233:     else:
234:         tweakmaster=mastercascade[0]
235:     tweakmodel=tweakmaster.FiltCombo(Classes.currentf[2])
236:     #(r,c)=view.listview[1].get_cursor()
237:     #row=Classes.model[1].get_row(r)
238:     #hysteresisfit=tweakmodel.find_match_row(lambda r: r[0]==row[0])
239:     Classes.currentcascade=zipdiv
240:     Classes.model[1]=affirmmaster.FiltTable()
241:     Classes.model[2]=tweakmodel
242:     view.listview[2].set_model(tweakmodel)
243:     if tweakmodel.filtered_size()==2:
244:         view.listview[2].set_cursor((1,))
245:     #elif hysteresisfit:
246:     #   view.listview[2].set_cursor(hysteresisfit)
247:     else:
248:         view.listview[2].set_cursor((0,))
249: 
250: def clear_entry():
251:     for i in [0,1,2]:
252:         view.listview[i].set_model(None)
253:         Classes.model[i]=None
254:     Classes.cascademaster=[]
255:     if Classes.activecol<>None and 0<=Classes.activecol<4:
256:         oldrenderer=view.renderer[Classes.activecol]
257:         oldrenderer.set_property("cell-background-gdk",basecolour)
258:     Classes.activecol=None
259:     Classes.currentf=["","",""]
260:     Classes.currentcascade=-1
261:     view.box.set_text("")
262: 
263: # gui callbacks
264: 
265: def on_box_changed(box):
266:     assert box==view.box
267:     if not Classes.model[0]: return
268:     insertionpoint=None #insertionpoint=box.get_position()
269:     # split into submodels
270:     (newmod,newcol)=\
271:         Classes.parse.split_comprehend(box.get_text(),insertionpoint)
272:     chain=Classes.parse.get_split_padded()
273:     # submodel 0
274:     Classes.model[0].filter_comprehend(chain[0])
275:     # pretty background colour
276:     newcolumn=(view.column[newcol] if 0<=newcol<4 else None)
277:     if newcol<>Classes.activecol:
278:         if 0<=Classes.activecol<4:
279:             oldrenderer=view.renderer[Classes.activecol]
280:             oldrenderer.set_property("cell-background-gdk",basecolour)
281:         if newcolumn<>None:
282:             view.renderer[newcol].set_property("cell-background-gdk",colcolour)
283:         for i in [0,1,2]:
284:             view.listview[i].queue_draw()
285:         Classes.activecol=newcol
286:     # model 0 set cursor
287:     # avoid unnecessarily touching fields as we want user
288:     # selected row to remain stably selected
289:     if Classes.currentf[0]<>chain[0]:
290:         bestfit=Classes.model[0].best_fit()
291:         if bestfit<>None:
292:             if 0<=newcol<2:
293:                 view.listview[0].set_cursor(bestfit,newcolumn)
294:             else:
295:                 view.listview[0].set_cursor(bestfit)
296:             zipdiv=Classes.model[0].get_row(bestfit)[2]
297:         else:
298:             modeller.set_class_subentry(0,"","")
299:             zipdiv=-1
300:         Classes.currentf[0]=chain[0]
301:         # cascade model
302:         tweak_cascade_model(zipdiv)
303:     # submodel 1
304:     Classes.model[1].filter_comprehend(chain[1])
305:     # model 1 set cursor
306:     if Classes.currentf[1]<>chain[1]:
307:         bestfit=Classes.model[1].best_fit()
308:         if bestfit<>None:
309:             view.listview[1].set_cursor(bestfit)
310:         else:
311:             modeller.set_class_subentry(1,"")
312:         Classes.currentf[1]=chain[1]
313:     # submodel 2
314:     Classes.model[2].filter_comprehend(chain[2])
315:     # model 2 set cursor
316:     if Classes.currentf[2]<>chain[2]:
317:         bestfit=Classes.model[2].best_fit()
318:         if bestfit<>None:
319:             view.listview[2].set_cursor(bestfit)
320:         else:
321:             modeller.set_class_subentry(2,"")
322:         Classes.currentf[2]=chain[2]
323:     Classes.activemod=newmod
324: 
325: def on_box_key_press(widget,event):
326:     assert widget==view.box
327:     if not Classes.model[Classes.activemod]: return
328:     k=event.keyval
329:     if k in guiutils.cursor_mover:
330:         view_move_cursor(
331:             view.listview[Classes.activemod],*guiutils.cursor_mover[k])
332:         return 1
333: 
334: def view_move_cursor(tree,*mover):
335:     assert tree==view.listview[Classes.activemod]
336:     if Classes.model[Classes.activemod].filter_nonempty():
337:         rsize=Classes.model[Classes.activemod].filtered_size()
338:         (r,c)=view.listview[Classes.activemod].get_cursor()
339:         r=guiutils.page_cursor_move(r,rsize,*mover)
340:         view.listview[Classes.activemod].set_cursor(r,c)
341:     return 1
342: 
343: def on_view_cursor_changed(tree,i):
344:     assert tree==view.listview[i]
345:     (r,c)=view.listview[i].get_cursor()
346:     if i==0:
347:         row=Classes.model[0].get_row(r)
348:         modeller.set_class_subentry(0,*row[:2])
349:         tweak_cascade_model(row[2])
350:     elif i==1:
351:         modeller.set_class_subentry(1,*Classes.model[1].get_row(r)[:1])
352:     elif i==2:
353:         modeller.set_class_subentry(2,*Classes.model[2].get_row(r)[:1])
354: 
355: ######## register reloadables ########
356: 
357: reloadables+=[guiutils]
358: reloadables+=[filttab,filtmaster,utils]
359: 
360: # classescon.py # python package PyScore.timesheet module classes.controller #