class Categories(wx.Panel): CategoryTypeChoices = [ _('Start Wave'), u' ' + _('Component'), _('Custom') ] DistanceTypeChoices = [_('Lap'), _('Race')] def __init__(self, parent, id=wx.ID_ANY): wx.Panel.__init__(self, parent, id) self.state = RaceInputState() vs = wx.BoxSizer(wx.VERTICAL) self.ignoreColour = wx.Colour(80, 80, 80) self.inactiveColour = wx.Colour(200, 200, 200) border = 4 flag = wx.ALL hs = wx.BoxSizer(wx.HORIZONTAL) self.activateAllButton = wx.Button(self, label=_('Activate All'), style=wx.BU_EXACTFIT) self.Bind(wx.EVT_BUTTON, self.onActivateAll, self.activateAllButton) hs.Add(self.activateAllButton, 0, border=border, flag=flag) hs.AddSpacer(6) self.newCategoryButton = wx.Button(self, label=_('New'), style=wx.BU_EXACTFIT) self.Bind(wx.EVT_BUTTON, self.onNewCategory, self.newCategoryButton) hs.Add(self.newCategoryButton, 0, border=border, flag=flag) self.delCategoryButton = wx.Button(self, label=_('Delete'), style=wx.BU_EXACTFIT) self.Bind(wx.EVT_BUTTON, self.onDelCategory, self.delCategoryButton) hs.Add(self.delCategoryButton, 0, border=border, flag=(flag & ~wx.LEFT)) hs.AddSpacer(6) self.upCategoryButton = wx.Button(self, label=u'\u2191', style=wx.BU_EXACTFIT) self.Bind(wx.EVT_BUTTON, self.onUpCategory, self.upCategoryButton) hs.Add(self.upCategoryButton, 0, border=border, flag=flag) self.downCategoryButton = wx.Button(self, label=u'\u2193', style=wx.BU_EXACTFIT) self.Bind(wx.EVT_BUTTON, self.onDownCategory, self.downCategoryButton) hs.Add(self.downCategoryButton, 0, border=border, flag=(flag & ~wx.LEFT)) hs.AddSpacer(6) self.setGpxDistanceButton = wx.Button(self, label=_('Set Gpx Distance'), style=wx.BU_EXACTFIT) self.Bind(wx.EVT_BUTTON, self.onSetGpxDistance, self.setGpxDistanceButton) hs.Add(self.setGpxDistanceButton, 0, border=border, flag=flag) hs.AddSpacer(6) self.addExceptionsButton = wx.Button(self, label=_('Bib Exceptions'), style=wx.BU_EXACTFIT) self.Bind(wx.EVT_BUTTON, self.onAddExceptions, self.addExceptionsButton) hs.Add(self.addExceptionsButton, 0, border=border, flag=flag) hs.AddSpacer(6) ''' self.updateStartWaveNumbersButton = wx.Button(self, label=_('Update Start Wave Bibs'), style=wx.BU_EXACTFIT) self.Bind( wx.EVT_BUTTON, self.onUpdateStartWaveNumbers, self.updateStartWaveNumbersButton ) hs.Add( self.updateStartWaveNumbersButton, 0, border = border, flag = flag ) ''' self.normalizeButton = wx.Button(self, label=_('Normalize'), style=wx.BU_EXACTFIT) self.Bind(wx.EVT_BUTTON, self.onNormalize, self.normalizeButton) hs.Add(self.normalizeButton, 0, border=border, flag=flag) hs.AddStretchSpacer() self.printButton = wx.Button(self, label=u'{}...'.format(_('Print')), style=wx.BU_EXACTFIT) self.Bind(wx.EVT_BUTTON, self.onPrint, self.printButton) hs.Add(self.printButton, 0, border=border, flag=flag) self.excelButton = wx.Button(self, label=u'{}...'.format(_('Excel')), style=wx.BU_EXACTFIT) self.Bind(wx.EVT_BUTTON, self.onExcel, self.excelButton) hs.Add(self.excelButton, 0, border=border, flag=flag) self.grid = ReorderableGrid(self) self.colNameFields = [ (u'', None), (_('Category Type'), 'catType'), (_('Active'), 'active'), (_('Name'), 'name'), (_('Gender'), 'gender'), (_('Numbers'), 'catStr'), (_('Start\nOffset'), 'startOffset'), (_('Race\nLaps'), 'numLaps'), (_('Race\nMinutes'), 'raceMinutes'), (_('Lapped\nRiders\nContinue'), 'lappedRidersMustContinue'), (_('Distance'), 'distance'), (_('Dist.\nBy'), 'distanceType'), (_('First\nLap\nDist.'), 'firstLapDistance'), (_('80%\nLap\nTime'), 'rule80Time'), (_('CrossMgr\nEstimated\nLaps'), 'suggestedLaps'), (_('Publish'), 'publishFlag'), (_('Upload'), 'uploadFlag'), (_('Series'), 'seriesFlag'), ] self.computedFields = {'rule80Time', 'suggestedLaps'} self.colnames = [ colName if not colName.startswith('_') else _('Name Copy') for colName, fieldName in self.colNameFields ] self.iCol = { fieldName: i for i, (colName, fieldName) in enumerate(self.colNameFields) if fieldName and not colName.startswith('_') } self.activeColumn = self.iCol['active'] self.genderColumn = self.iCol['gender'] self.numbersColumn = self.iCol['catStr'] self.grid.CreateGrid(0, len(self.colnames)) self.grid.SetRowLabelSize(32) self.grid.SetMargins(0, 0) for col, name in enumerate(self.colnames): self.grid.SetColLabelValue(col, name) self.cb = None self.boolCols = set() self.choiceCols = set() self.readOnlyCols = set() self.dependentCols = set() # Set column attributes for the table. for col, (colName, fieldName) in enumerate(self.colNameFields): attr = gridlib.GridCellAttr() if fieldName is None: attr.SetRenderer(CategoryIconRenderer()) attr.SetAlignment(wx.ALIGN_LEFT, wx.ALIGN_CENTRE) attr.SetReadOnly(True) self.readOnlyCols.add(col) elif fieldName == 'catType': self.catTypeWidth = 64 attr.SetEditor( gridlib.GridCellChoiceEditor(self.CategoryTypeChoices, False)) attr.SetAlignment(wx.ALIGN_LEFT, wx.ALIGN_CENTRE) self.choiceCols.add(col) elif fieldName in { 'active', 'lappedRidersMustContinue', 'publishFlag', 'uploadFlag', 'seriesFlag' }: boolEditor = gridlib.GridCellBoolEditor() boolEditor.UseStringValues('1', '0') attr.SetEditor(boolEditor) attr.SetRenderer(gridlib.GridCellBoolRenderer()) attr.SetAlignment(wx.ALIGN_CENTRE, wx.ALIGN_CENTRE) self.boolCols.add(col) if fieldName == 'lappedRidersMustContinue': self.dependentCols.add(col) elif fieldName == 'gender': attr.SetEditor( gridlib.GridCellChoiceEditor( [_('Open'), _('Men'), _('Women')], False)) self.choiceCols.add(col) elif fieldName == 'startOffset': attr.SetEditor(TimeEditor()) attr.SetAlignment(wx.ALIGN_CENTRE, wx.ALIGN_CENTRE) self.dependentCols.add(col) elif fieldName == 'numLaps': attr.SetEditor(wx.grid.GridCellNumberEditor()) attr.SetAlignment(wx.ALIGN_CENTRE, wx.ALIGN_CENTRE) self.dependentCols.add(col) elif fieldName == 'raceMinutes': attr.SetEditor(wx.grid.GridCellNumberEditor()) attr.SetAlignment(wx.ALIGN_CENTRE, wx.ALIGN_CENTRE) self.dependentCols.add(col) elif fieldName in ['rule80Time', 'suggestedLaps']: attr.SetReadOnly(True) attr.SetAlignment(wx.ALIGN_CENTRE, wx.ALIGN_CENTRE) self.readOnlyCols.add(col) self.dependentCols.add(col) elif fieldName in ['distance', 'firstLapDistance']: attr.SetEditor(gridlib.GridCellFloatEditor(7, 3)) attr.SetRenderer(gridlib.GridCellFloatRenderer(7, 3)) attr.SetAlignment(wx.ALIGN_CENTRE, wx.ALIGN_CENTRE) self.dependentCols.add(col) elif fieldName == 'distanceType': attr.SetEditor( gridlib.GridCellChoiceEditor(self.DistanceTypeChoices, False)) attr.SetAlignment(wx.ALIGN_RIGHT, wx.ALIGN_CENTRE) self.choiceCols.add(col) self.dependentCols.add(col) elif colName == '_name2': attr.SetAlignment(wx.ALIGN_LEFT, wx.ALIGN_CENTRE) attr.SetBackgroundColour(wx.Colour(240, 240, 240)) attr.SetReadOnly(True) self.grid.SetColAttr(col, attr) self.Bind(gridlib.EVT_GRID_CELL_LEFT_CLICK, self.onGridLeftClick) self.Bind(gridlib.EVT_GRID_SELECT_CELL, self.onCellSelected) self.Bind(gridlib.EVT_GRID_CELL_CHANGED, self.onCellChanged) self.Bind(gridlib.EVT_GRID_EDITOR_CREATED, self.onEditorCreated) vs.Add(hs, 0, flag=wx.EXPAND | wx.ALL, border=4) vs.Add(self.grid, 1, flag=wx.GROW | wx.ALL | wx.EXPAND) self.rowCur = 0 self.colCur = 0 self.SetSizer(vs) def onPrint(self, event): self.commit() PrintCategories() def onExcel(self, event): self.commit() export = getExportGrid() xlFName = Utils.getMainWin().getFormatFilename('excel') xlFName = os.path.splitext( xlFName)[0] + '-Categories' + os.path.splitext(xlFName)[1] wb = xlwt.Workbook() sheetCur = wb.add_sheet(_('Categories')) export.toExcelSheet(sheetCur) try: wb.save(xlFName) if Utils.getMainWin().launchExcelAfterPublishingResults: Utils.LaunchApplication(xlFName) Utils.MessageOK( self, u'{}:\n\n {}'.format(_('Excel file written to'), xlFName), _('Excel Write')) except IOError: Utils.MessageOK( self, u'{} "{}"\n\n{}\n{}'.format( _('Cannot write'), xlFName, _('Check if this spreadsheet is already open.'), _('If so, close it, and try again.')), _('Excel File Error'), iconMask=wx.ICON_ERROR) def onSetGpxDistance(self, event): race = Model.race geoTrack = getattr(race, 'geoTrack', None) if not geoTrack: return if not Utils.MessageOK(self, _('Set the GPX distance for all Categories?'), _('Set GPX Distance'), wx.ICON_QUESTION): return distance = geoTrack.lengthKm if race.distanceUnit == Model.Race.UnitKm else geoTrack.lengthMiles for category in race.getCategories(): category.distance = distance race.setChanged() self.refresh(forceRefresh=True) def onNormalize(self, event): self.commit() if Model.race: Model.race.normalizeCategories() self.state.reset() self.refresh() #------------------------------------------ def onGridLeftClick(self, event): if event.GetCol() in self.boolCols: r, c = event.GetRow(), event.GetCol() if c == self.iCol['active']: active = (self.grid.GetCellValue(r, self.iCol['active']) == u'1') wx.CallAfter( self.fixRow, r, self.CategoryTypeChoices.index( self.grid.GetCellValue(r, self.iCol['catType'])), not active) self.grid.SetCellValue( r, c, '1' if self.grid.GetCellValue(r, c)[:1] != '1' else '0') event.Skip() def onCellSelected(self, event): self.rowCur = event.GetRow() self.colCur = event.GetCol() if self.colCur in self.choiceCols or self.colCur in self.boolCols: wx.CallAfter(self.grid.EnableCellEditControl) event.Skip() def onCellChanged(self, event): self.rowCur = event.GetRow() self.colCur = event.GetCol() if self.colCur in [1, 2]: wx.CallAfter(self.fixCells) event.Skip() def onEditorCreated(self, event): if event.GetCol() == self.numbersColumn: ctrl = event.GetControl() ctrl.Bind(wx.EVT_KEY_DOWN, self.onNumbersKeyEvent) ctrl.Bind(wx.EVT_TEXT_PASTE, self.onPaste) event.Skip() def getCleanClipboardText(self): if wx.TheClipboard.Open(): data = wx.TextDataObject() if wx.TheClipboard.GetData(data): txt = data.GetText() txt = re.sub('[^0-9,-]+', ',', txt) wx.TheClipboard.Close() return txt return None def isExternalChange(self): if not Model.race: return False categories = Model.race.getAllCategories() for cat in categories: try: cat.distance = float(cat.distance) except: cat.distance = None try: cat.firstLapDistance = float(cat.firstLapDistance) except: cat.firstLapDistance = None if self.grid.GetNumberRows() != len(categories): return True def distanceMatches(distance, cellValue): try: value = float(cellValue) except ValueError: value = None if not distance and not value: return True return u'{:.3f}'.format(distance or 0.0) == cellValue def numLapsMatches(numLaps, cellValue): v = u'{}'.format(numLaps if numLaps is not None else '') return v == cellValue return any(( cat.name != self.grid.GetCellValue(r, self.iCol['name']) or cat.catStr != self.grid.GetCellValue(r, self.iCol['catStr']) or not distanceMatches( cat.distance, self.grid.GetCellValue(r, self.iCol['distance'])) or not distanceMatches( cat.firstLapDistance, self.grid.GetCellValue(r, self.iCol['firstLapDistance'])) or not numLapsMatches( cat.numLaps, self.grid.GetCellValue(r, self.iCol['numLaps']))) for r, cat in enumerate(categories)) def pasteFromClipboard(self, event): txt = self.getCleanClipboardText() if txt: event.GetEventObject().WriteText(txt) return True return False def onNumbersKeyEvent(self, event): # Handle column pastes from Excel when there are newlines. if event.GetModifiers() == wx.MOD_CONTROL and event.GetKeyCode() == 86: if self.pasteFromClipboard(event): return event.Skip() def onPaste(self, event): self.pasteFromClipboard(event) event.Skip() #------------------------------------------ def onUpdateStartWaveNumbers(self, event): self.commit() undo.pushState() with Model.LockRace() as race: race.adjustAllCategoryWaveNumbers() self.state.reset() wx.CallAfter(self.refresh) wx.CallAfter(Utils.refreshForecastHistory) def onAddExceptions(self, event): with Model.LockRace() as race: if not race or not race.getAllCategories(): return r = self.grid.GetGridCursorRow() if r is None or r < 0: Utils.MessageOK(self, _('You must select a Category first'), _('Select a Category')) return with Model.LockRace() as race: categories = race.getAllCategories() category = categories[r] dlg = wx.TextEntryDialog( self, u'{}: {}'.format( category.name, _('''Add Bib Exceptions (comma separated). This will add the given list of Bibs to this category, and remove them from other categories.'''), ), _('Add Bib Exceptions')) good = (dlg.ShowModal() == wx.ID_OK) if good: response = dlg.GetValue() dlg.Destroy() if not good: return undo.pushState() response = re.sub('[^0-9,]', '', response.replace(' ', ',')) with Model.LockRace() as race: for numException in response.split(','): race.addCategoryException(category, numException) self.state.reset() self.refresh() def _setRow( self, r, active, name, catStr, startOffset='00:00:00', numLaps=None, raceMinutes=None, lappedRidersMustContinue=False, distance=None, distanceType=None, firstLapDistance=None, gender=None, catType=Model.Category.CatWave, publishFlag=True, uploadFlag=True, seriesFlag=True, ): if len(startOffset) < len('00:00:00'): startOffset = '00:' + startOffset GetTranslation = _ gender = gender if gender in ['Men', 'Women'] else 'Open' self.grid.SetRowLabelValue(r, u'') self.grid.SetCellValue(r, self.iCol['active'], u'1' if active else u'0') self.grid.SetCellValue(r, self.iCol['catType'], self.CategoryTypeChoices[catType]) self.grid.SetCellValue(r, self.iCol['name'], name) self.grid.SetCellValue(r, self.iCol['gender'], GetTranslation(gender)) self.grid.SetCellValue(r, self.iCol['catStr'], catStr) self.grid.SetCellValue(r, self.iCol['startOffset'], startOffset) self.grid.SetCellValue(r, self.iCol['numLaps'], u'{}'.format(numLaps) if numLaps else u'') self.grid.SetCellValue( r, self.iCol['raceMinutes'], u'{}'.format(raceMinutes) if raceMinutes else u'') self.grid.SetCellValue(r, self.iCol['lappedRidersMustContinue'], u'1' if lappedRidersMustContinue else u'0') self.grid.SetCellValue(r, self.iCol['rule80Time'], u'') self.grid.SetCellValue(r, self.iCol['suggestedLaps'], u'') self.grid.SetCellValue(r, self.iCol['distance'], ('%.3f' % distance) if distance else u'') self.grid.SetCellValue( r, self.iCol['distanceType'], self.DistanceTypeChoices[distanceType if distanceType else 0]) self.grid.SetCellValue(r, self.iCol['firstLapDistance'], ('%.3f' % firstLapDistance) if firstLapDistance else '') self.grid.SetCellValue(r, self.iCol['publishFlag'], u'1' if publishFlag else u'0') self.grid.SetCellValue(r, self.iCol['uploadFlag'], u'1' if uploadFlag else u'0') self.grid.SetCellValue(r, self.iCol['seriesFlag'], u'1' if seriesFlag else u'0') race = Model.race category = race.categories.get(u'{} ({})'.format(name.strip(), gender), None) if race else None if not category or category.catType != Model.Category.CatWave: return # Get the 80% time cutoff. if not active or not Model.race: return rule80Time = race.getRule80CountdownTime(category) if race else None if rule80Time: self.grid.SetCellValue(r, self.iCol['rule80Time'], Utils.formatTime(rule80Time)) laps = race.getCategoryRaceLaps().get(category, 0) if race else None if laps: self.grid.SetCellValue(r, self.iCol['suggestedLaps'], u'{}'.format(laps)) def fixRow(self, row, catType, active): activeColour = wx.WHITE if active else self.inactiveColour colour = activeColour if catType == Model.Category.CatWave else self.ignoreColour for colName, fieldName in self.colNameFields: if not fieldName: continue col = self.iCol[fieldName] self.grid.SetCellBackgroundColour( row, col, colour if col in self.dependentCols else activeColour) def fixCells(self, event=None): for row in xrange(self.grid.GetNumberRows()): active = self.grid.GetCellValue(row, self.iCol['active'])[:1] in 'TtYy1' catType = self.CategoryTypeChoices.index( self.grid.GetCellValue(row, self.iCol['catType'])) self.fixRow(row, catType, active) def onActivateAll(self, event): self.commit() if Model.race: for c in Model.race.getAllCategories(): if not c.active: c.active = True Model.race.setChanged() self.state.reset() wx.CallAfter(self.refresh) def onDeactivateAll(self, event): self.commit() if Model.race: for c in Model.race.getAllCategories(): if c.active: c.active = False Model.race.setChanged() self.state.reset() wx.CallAfter(self.refresh) def doAutosize(self): self.grid.AutoSizeColumns(False) colWidth = self.grid.GetColSize(self.iCol['catStr']) maxWidth = wx.GetDisplaySize().width / 3 if colWidth > maxWidth: self.grid.SetColSize(self.iCol['catStr'], maxWidth) self.grid.ForceRefresh() def onNewCategory(self, event): self.grid.AppendRows(1) self._setRow(r=self.grid.GetNumberRows() - 1, active=True, name=u'<{}> '.format(_('CategoryName')), catStr='100-199,504,-128') self.doAutosize() def onDelCategory(self, event): r = self.grid.GetGridCursorRow() if r is None or r < 0: return if Utils.MessageOKCancel( self, u'{} "{} ({})"?'.format( _('Delete Category'), self.grid.GetCellValue(r, 3).strip(), self.grid.GetCellValue(r, 4).strip(), ), _('Delete Category')): self.grid.DeleteRows(r, 1, True) def onUpCategory(self, event): self.grid.SaveEditControlValue() self.grid.DisableCellEditControl() r = self.grid.GetGridCursorRow() Utils.SwapGridRows(self.grid, r, r - 1) if r - 1 >= 0: self.grid.MoveCursorUp(False) self.grid.ClearSelection() self.grid.SelectRow(max(r - 1, 0), True) def onDownCategory(self, event): self.grid.SaveEditControlValue() self.grid.DisableCellEditControl() r = self.grid.GetGridCursorRow() Utils.SwapGridRows(self.grid, r, r + 1) if r + 1 < self.grid.GetNumberRows(): self.grid.MoveCursorDown(False) self.grid.ClearSelection() self.grid.SelectRow(min(r + 1, self.grid.GetNumberRows() - 1), True) def refresh(self, forceRefresh=False): self.setGpxDistanceButton.Enable(hasattr(Model.race, 'geoTrack')) if not (forceRefresh or self.isExternalChange() or self.state.changed()): return # Fix the height of the column labels. dc = wx.WindowDC(self.grid) dc.SetFont(self.grid.GetLabelFont()) textHeight = dc.GetTextExtent('Label')[1] self.colLabelHeight = textHeight * max( name.count('\n') + 1 for name in self.colnames) + textHeight // 4 self.grid.SetColLabelSize(self.colLabelHeight) with Model.LockRace() as race: self.grid.ClearGrid() if race is None: return for c in xrange(self.grid.GetNumberCols()): if self.grid.GetColLabelValue(c).startswith(_('Distance')): self.grid.SetColLabelValue( c, u'{}\n({})'.format(_('Distance'), ['km', 'miles' ][getattr(race, 'distanceUnit', 0)])) break categories = race.getAllCategories() if self.grid.GetNumberRows() > 0: self.grid.DeleteRows(0, self.grid.GetNumberRows()) self.grid.AppendRows(len(categories)) for r, cat in enumerate(categories): self._setRow( r=r, active=cat.active, name=cat.name, gender=getattr(cat, 'gender', None), catStr=cat.catStr, catType=cat.catType, startOffset=cat.startOffset, numLaps=cat._numLaps, raceMinutes=cat.raceMinutes, lappedRidersMustContinue=getattr( cat, 'lappedRidersMustContinue', False), distance=getattr(cat, 'distance', None), distanceType=getattr(cat, 'distanceType', Model.Category.DistanceByLap), firstLapDistance=getattr(cat, 'firstLapDistance', None), publishFlag=cat.publishFlag, uploadFlag=cat.uploadFlag, seriesFlag=cat.seriesFlag, ) self.doAutosize() self.fixCells() # Force the grid to the correct size. self.grid.FitInside() self.GetSizer().Layout() def commit(self): undo.pushState() with Model.LockRace() as race: self.grid.SaveEditControlValue() self.grid.DisableCellEditControl( ) # Make sure the current edit is committed. if race is None: return numStrTuples = [] for r in xrange(self.grid.GetNumberRows()): values = { name: self.grid.GetCellValue(r, c) for name, c in self.iCol.iteritems() if name not in self.computedFields } values['catType'] = self.CategoryTypeChoices.index( values['catType']) values['distanceType'] = self.DistanceTypeChoices.index( values['distanceType']) numStrTuples.append(values) race.setCategories(numStrTuples) race.adjustAllCategoryWaveNumbers() wx.CallAfter(Utils.refreshForecastHistory)
class Races(wx.Panel): #---------------------------------------------------------------------- headerNames = ['Race', 'Grade', 'Points', 'Team Pts', 'Race File'] RaceCol = 0 GradeCol = 1 PointsCol = 2 TeamPointsCol = 3 RaceFileCol = 4 RaceStatusCol = 5 def __init__(self, parent): wx.Panel.__init__(self, parent) self.seriesNameLabel = wx.StaticText(self, label='Series Name:') self.seriesName = wx.TextCtrl(self) self.organizerNameLabel = wx.StaticText(self, label='Organizer:') self.organizerName = wx.TextCtrl(self) self.explanation = wx.StaticText( self, label=u'\n'.join([ _("Add all the races in your Series."), _("Make sure the races are in chronological order."), _("You can change the order by dragging-and-dropping the first grey column in the table." ), u'', _("Configure the Points Structures or Time Scoring parameters on the Scoring Criteria page." ), _("Each race can have its own Points Structure. For example, you could create 'Double Points' for one race." ), u'', _("Race results are shown Last-to-First in the output by default." ), _("You can change this on the Options page."), ])) self.grid = ReorderableGrid(self, style=wx.BORDER_SUNKEN) self.grid.DisableDragRowSize() self.grid.SetRowLabelSize(64) self.grid.CreateGrid(0, len(self.headerNames)) for col in range(self.grid.GetNumberCols()): self.grid.SetColLabelValue(col, self.headerNames[col]) self.pointsChoiceEditor = gridlib.GridCellChoiceEditor( [], allowOthers=False) attr = gridlib.GridCellAttr() attr.SetEditor(self.pointsChoiceEditor) self.grid.SetColAttr(self.PointsCol, attr) self.teamPointsChoiceEditor = gridlib.GridCellChoiceEditor( [], allowOthers=False) attr = gridlib.GridCellAttr() attr.SetEditor(self.teamPointsChoiceEditor) self.grid.SetColAttr(self.TeamPointsCol, attr) attr = gridlib.GridCellAttr() attr.SetReadOnly(True) self.grid.SetColAttr(self.RaceCol, attr) attr = gridlib.GridCellAttr() attr.SetAlignment(wx.ALIGN_CENTRE, wx.ALIGN_CENTRE) self.grid.SetColAttr(self.GradeCol, attr) ''' attr = gridlib.GridCellAttr() attr.SetReadOnly( True ) self.grid.SetColAttr( self.RaceStatusCol, attr ) ''' attr = gridlib.GridCellAttr() attr.SetReadOnly(True) self.grid.SetColAttr(self.RaceFileCol, attr) self.grid.Bind(gridlib.EVT_GRID_CELL_CHANGED, self.onGridChange) self.gridAutoSize() self.grid.Bind(wx.grid.EVT_GRID_EDITOR_CREATED, self.onGridEditorCreated) self.grid.Bind(wx.grid.EVT_GRID_CELL_LEFT_CLICK, self.onEditRaceFileName) self.addButton = wx.Button(self, wx.ID_ANY, 'Add Races') self.addButton.Bind(wx.EVT_BUTTON, self.doAddRace) self.removeButton = wx.Button(self, wx.ID_ANY, 'Remove Race') self.removeButton.Bind(wx.EVT_BUTTON, self.doRemoveRace) fgs = wx.FlexGridSizer(rows=2, cols=2, vgap=2, hgap=2) fgs.AddGrowableCol(1, proportion=1) fgs.Add(self.seriesNameLabel, flag=wx.ALIGN_CENTRE_VERTICAL | wx.ALIGN_RIGHT) fgs.Add(self.seriesName, flag=wx.EXPAND) fgs.Add(self.organizerNameLabel, flag=wx.ALIGN_CENTRE_VERTICAL | wx.ALIGN_RIGHT) fgs.Add(self.organizerName, flag=wx.EXPAND) hs = wx.BoxSizer(wx.HORIZONTAL) hs.Add(self.addButton, 0, flag=wx.ALL, border=4) hs.Add(self.removeButton, 0, flag=wx.ALL, border=4) sizer = wx.BoxSizer(wx.VERTICAL) sizer.Add(fgs, 0, flag=wx.EXPAND | wx.ALL, border=4) sizer.Add(self.explanation, 0, flag=wx.EXPAND | wx.ALL, border=4) sizer.Add(hs, 0, flag=wx.EXPAND) sizer.Add(self.grid, 1, flag=wx.EXPAND | wx.ALL, border=6) self.SetSizer(sizer) def getGrid(self): return self.grid wildcard = 'CrossMgr or Excel files (*.cmn, *.xlsx, *.xlsm, *.xls)|*.cmn;*.xlsx;*.xlsm;*.xls;' def onEditRaceFileName(self, event): col = event.GetCol() if col != self.RaceFileCol: event.Skip() return row = event.GetRow() dlg = wx.FileDialog(self, message="Choose a CrossMgr or Excel file", defaultFile='', wildcard=self.wildcard, style=wx.FD_OPEN | wx.FD_CHANGE_DIR) ret = dlg.ShowModal() fileName = '' if ret == wx.ID_OK: fileName = dlg.GetPath() self.grid.SetCellValue(row, self.RaceCol, SeriesModel.RaceNameFromPath(fileName)) self.grid.SetCellValue(row, self.RaceFileCol, fileName) dlg.Destroy() self.commit() def doAddRace(self, event): dlg = wx.FileDialog(self, message="Choose a CrossMgr or Excel file", defaultFile='', wildcard=self.wildcard, style=wx.FD_OPEN | wx.FD_CHANGE_DIR | wx.FD_MULTIPLE) ret = dlg.ShowModal() if ret == wx.ID_OK: for fileName in dlg.GetPaths(): SeriesModel.model.addRace(fileName) dlg.Destroy() self.refresh() def doRemoveRace(self, event): row = self.grid.GetGridCursorRow() if row < 0: Utils.MessageOK( self, 'No Selected Race.\nPlease Select a Race to Remove.', 'No Selected Race') return if Utils.MessageOKCancel( self, 'Confirm Remove Race:\n\n {}'.format( self.grid.GetCellValue(row, 0)), 'Remove Race'): self.grid.DeleteRows(row) self.commit() def updatePointsChoices(self): try: comboBox = self.comboBox except AttributeError: return comboBox.SetItems([p.name for p in SeriesModel.model.pointStructures]) def updateTeamPointsChoices(self): try: teamComboBox = self.teamComboBox except AttributeError: return teamComboBox.SetItems( [''] + [p.name for p in SeriesModel.model.pointStructures]) def onGridEditorCreated(self, event): if event.GetCol() == self.PointsCol: self.comboBox = event.GetControl() self.updatePointsChoices() elif event.GetCol() == self.TeamPointsCol: self.teamComboBox = event.GetControl() self.updateTeamPointsChoices() event.Skip() def gridAutoSize(self): self.grid.AutoSize() self.grid.EnableDragGridSize(False) self.grid.EnableDragColSize(False) self.Layout() self.Refresh() def onGridChange(self, event): wx.CallAfter(self.gridAutoSize) def refresh(self): model = SeriesModel.model Utils.AdjustGridSize(self.grid, len(model.races)) for row, race in enumerate(model.races): self.grid.SetCellValue(row, self.RaceCol, race.getRaceName()) self.grid.SetCellValue(row, self.GradeCol, race.grade) self.grid.SetCellValue(row, self.PointsCol, race.pointStructure.name) self.grid.SetCellValue( row, self.TeamPointsCol, race.teamPointStructure.name if race.teamPointStructure else u'') self.grid.SetCellValue(row, self.RaceFileCol, race.fileName) wx.CallAfter(self.gridAutoSize) self.seriesName.SetValue(SeriesModel.model.name) self.organizerName.SetValue(SeriesModel.model.organizer) def commit(self): self.grid.SaveEditControlValue() self.grid.DisableCellEditControl( ) # Make sure the current edit is committed. raceList = [] for row in range(self.grid.GetNumberRows()): race = SeriesModel.model.races[row] fileName = self.grid.GetCellValue(row, self.RaceFileCol).strip() pname = self.grid.GetCellValue(row, self.PointsCol) pteamname = self.grid.GetCellValue(row, self.TeamPointsCol) or None grade = self.grid.GetCellValue(row, self.GradeCol).strip().upper()[:1] if not (grade and ord(u'A') <= ord(grade) <= ord(u'Z')): grade = u'A' if not fileName or not pname: continue raceList.append((fileName, pname, pteamname, grade)) model = SeriesModel.model model.setRaces(raceList) if self.seriesName.GetValue() != model.name: model.name = self.seriesName.GetValue() model.changed = True if self.organizerName.GetValue() != model.organizer: model.organizer = self.organizerName.GetValue() model.changed = True wx.CallAfter(self.refresh)