def __init__( self, parent, id=wx.ID_ANY, addEditButton=True ): super(Properties, self).__init__(parent, id) self.state = RaceInputState() self.SetBackgroundColour( wx.WHITE ) mainSizer = wx.BoxSizer( wx.VERTICAL ) self.SetSizer( mainSizer ) bookStyle = ( flatnotebook.FNB_NO_NAV_BUTTONS | flatnotebook.FNB_NO_X_BUTTON | flatnotebook.FNB_VC8 | flatnotebook.FNB_NODRAG ) self.notebook = flatnotebook.FlatNotebook( self, agwStyle=bookStyle ) self.notebook.SetBackgroundColour( wx.WHITE ) self.notebook.SetTabAreaColour( wx.WHITE ) self.notebook.Bind( wx.EVT_NOTEBOOK_PAGE_CHANGED, self.onPageChanging ) self.propClassName = [ ('generalInfoProperties', GeneralInfoProperties, _('General Info') ), ('raceOptionsProperties', RaceOptionsProperties, _('Race Options') ), ('notesProperties', NotesProperties, _('Notes') ), ('rfidProperties', RfidProperties, _('RFID') ), ('cameraProperties', CameraProperties, _('Camera') ), ('animationProperties', AnimationProperties, _('Animation') ), ('filesProperties', FilesProperties, _('Files') ), ] for prop, PropClass, name in self.propClassName: setattr( self, prop, PropClass(self.notebook) ) self.notebook.AddPage( getattr(self, prop), name ) self.updateFileName() mainSizer.Add( self.notebook, 1, flag=wx.ALL|wx.EXPAND, border=4 ) if addEditButton: hs = wx.BoxSizer( wx.HORIZONTAL ) self.commitButton = wx.Button(self, label=_('Commit')) self.commitButton.Bind( wx.EVT_BUTTON, self.commitButtonCallback ) hs.Add( self.commitButton, flag=wx.TOP|wx.BOTTOM, border=8 ) self.excelButton = wx.Button(self, label=_('Link External Excel Sheet...')) self.excelButton.Bind( wx.EVT_BUTTON, self.excelButtonCallback ) hs.Add( self.excelButton, flag=wx.LEFT|wx.TOP|wx.BOTTOM, border=8 ) mainSizer.Add( hs, flag=wx.ALL, border=4 ) self.setEditable() mainSizer.Fit(self) self.Layout()
def __init__( self, parent, id=wx.ID_ANY, size=wx.DefaultSize ): super(TeamResults, self).__init__( parent, id, size=size ) self.state = RaceInputState() vsOverall = wx.BoxSizer( wx.VERTICAL ) #--------------------------------------------------------------- self.colnames = ( _('Pos'), _('Team'), _('Time'), _('Gap'), ) self.grid = ReorderableGrid( self ) self.grid.CreateGrid( 0, len(self.colnames) ) self.grid.SetRowLabelSize( 0 ) self.grid.SetMargins( 0, 0 ) self.grid.AutoSizeColumns( True ) self.grid.DisableDragColSize() self.grid.DisableDragRowSize() self.grid.AutoSizeColumns( False ) self.grid.AutoSizeRows( False ) #--------------------------------------------------------------- self.hbs = wx.BoxSizer(wx.HORIZONTAL) self.categoryLabel = wx.StaticText( self, label = _('Category:') ) self.categoryChoice = wx.Choice( self ) self.Bind(wx.EVT_CHOICE, self.doChooseCategory, self.categoryChoice) self.exportButton = wx.Button( self, label='{} {}/{}'.format(_('Export'), _('Excel'), _('PDF')) ) self.exportButton.Bind( wx.EVT_BUTTON, self.doExport ) self.hbs.Add( self.categoryLabel, flag=wx.TOP|wx.BOTTOM|wx.LEFT|wx.ALIGN_CENTRE_VERTICAL, border=4 ) self.hbs.Add( self.categoryChoice, flag=wx.ALL|wx.ALIGN_CENTRE_VERTICAL, border=4 ) self.hbs.Add( self.exportButton, flag=wx.ALL|wx.ALIGN_CENTRE_VERTICAL, border=4 ) #--------------------------------------------------------------- vsOverall.Add( self.hbs, flag=wx.ALL, border=4 ) vsOverall.Add( self.grid, 1, flag=wx.EXPAND|wx.ALL, border=4 ) self.SetSizer( vsOverall )
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)
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 Properties( wx.Panel ): badFileCharsRE = re.compile( '[^a-zA-Z0-9_ ]+' ) dateFormat = '%Y-%m-%d' def __init__( self, parent, id=wx.ID_ANY, addEditButton=True ): super(Properties, self).__init__(parent, id) self.state = RaceInputState() self.SetBackgroundColour( wx.WHITE ) mainSizer = wx.BoxSizer( wx.VERTICAL ) self.SetSizer( mainSizer ) bookStyle = ( flatnotebook.FNB_NO_NAV_BUTTONS | flatnotebook.FNB_NO_X_BUTTON | flatnotebook.FNB_VC8 | flatnotebook.FNB_NODRAG ) self.notebook = flatnotebook.FlatNotebook( self, agwStyle=bookStyle ) self.notebook.SetBackgroundColour( wx.WHITE ) self.notebook.SetTabAreaColour( wx.WHITE ) self.notebook.Bind( wx.EVT_NOTEBOOK_PAGE_CHANGED, self.onPageChanging ) self.propClassName = [ ('generalInfoProperties', GeneralInfoProperties, _('General Info') ), ('raceOptionsProperties', RaceOptionsProperties, _('Race Options') ), ('notesProperties', NotesProperties, _('Notes') ), ('rfidProperties', RfidProperties, _('RFID') ), ('cameraProperties', CameraProperties, _('Camera') ), ('animationProperties', AnimationProperties, _('Animation') ), ('filesProperties', FilesProperties, _('Files') ), ] for prop, PropClass, name in self.propClassName: setattr( self, prop, PropClass(self.notebook) ) self.notebook.AddPage( getattr(self, prop), name ) self.updateFileName() mainSizer.Add( self.notebook, 1, flag=wx.ALL|wx.EXPAND, border=4 ) if addEditButton: hs = wx.BoxSizer( wx.HORIZONTAL ) self.commitButton = wx.Button(self, label=_('Commit')) self.commitButton.Bind( wx.EVT_BUTTON, self.commitButtonCallback ) hs.Add( self.commitButton, flag=wx.TOP|wx.BOTTOM, border=8 ) self.excelButton = wx.Button(self, label=_('Link External Excel Sheet...')) self.excelButton.Bind( wx.EVT_BUTTON, self.excelButtonCallback ) hs.Add( self.excelButton, flag=wx.LEFT|wx.TOP|wx.BOTTOM, border=8 ) mainSizer.Add( hs, flag=wx.ALL, border=4 ) self.setEditable() mainSizer.Fit(self) self.Layout() def onJChipIntegration( self, event ): self.rfidProperties.autocorrectLapsDefault.SetValue( not self.rfidProperties.jchip.GetValue() ) def onPageChanging( self, event ): ''' if Model.race: notebook = event.GetEventObject() notebook.GetPage( event.GetOldSelection() ).commit() notebook.GetPage( event.GetSelection() ).refresh() self.updateFileName() ''' if hasattr(self, 'cameraProperties'): notebook = event.GetEventObject() if notebook.GetPage(event.GetOldSelection()) == self.cameraProperties: self.cameraProperties.commit() if notebook.GetPage(event.GetSelection()) == self.cameraProperties: self.cameraProperties.refresh() event.Skip() # Required to properly repaint the screen. def excelButtonCallback( self, event ): mainWin = Utils.getMainWin() if mainWin: mainWin.menuLinkExcel() def commitButtonCallback( self, event ): mainWin = Utils.getMainWin() if Model.race: wx.CallAfter( self.commit ) else: Utils.MessageOK( self, _('You must have a valid race File|Open...') + u'\n' + _('Or create one with File|New....'), _('Valid Race Required'), wx.ICON_WARNING ) def setEditable( self, editable = True ): pass def incNext( self ): if not hasattr(self,'generalInfoProperties'): return '' gi = self.generalInfoProperties gi.raceNum.SetValue( gi.raceNum.GetValue() + 1 ) gi.memo.SetValue( '' ) if gi.scheduledStart.GetValue() == '10:00' and gi.minutes.GetValue() == 40 and gi.raceNum.GetValue() == 2: gi.scheduledStart.SetValue( '11:30' ) gi.minutes.SetValue( 50 ) elif gi.scheduledStart.GetValue() == '11:30' and gi.minutes.GetValue() == 50 and gi.raceNum.GetValue() == 3: gi.scheduledStart.SetValue( '13:00' ) gi.minutes.SetValue( 60 ) else: sStr = '{}'.format(gi.scheduledStart.GetValue()) fields = sStr.split(':') if len(fields) == 2: mins = int(fields[0],10) * 60 + int(fields[1],10) mins += gi.minutes.GetValue() mins += 15 # Add time for a break. if (mins/60) >= 24: mins = 0 sNew = '%02d:%02d:00' % (int(mins/60), mins%60) gi.scheduledStart.SetValue( sNew ) def onChanged( self, event ): self.updateFileName() def updateFileName( self ): if not hasattr(self,'filesProperties') or not hasattr(self,'generalInfoProperties'): return '' gi = self.generalInfoProperties rDate = gi.date.GetValue().Format(Properties.dateFormat) rName = Properties.badFileCharsRE.sub( ' ', gi.raceName.GetValue() ).strip() rNum = gi.raceNum.GetValue() rMemo = Properties.badFileCharsRE.sub( ' ', gi.memo.GetValue() ).strip() fname = u'%s-%s-r%d-%s.cmn' % (rDate, rName, rNum, rMemo ) self.filesProperties.fileName.SetLabel( fname ) return fname def saveFileNameFields( self ): if not hasattr(self,'generalInfoProperties'): return '' gi = self.generalInfoProperties for f in ['date', 'raceName', 'raceNum', 'memo']: setattr(self, f + 'Original', getattr(gi, f).GetValue()) def restoreFileNameFields( self ): if not hasattr(self,'generalInfoProperties'): return '' gi = self.generalInfoProperties for f in ['date', 'raceName', 'raceNum', 'memo']: getattr(gi, f).SetValue( getattr(self, f + 'Original') ) def getFileName( self ): return self.updateFileName() def refresh( self ): if not self.state.changed(): return with Model.LockRace() as race: self.setEditable( False ) if race is None: return for prop, PropClass, name in self.propClassName: getattr(self, prop).refresh() self.saveFileNameFields() self.GetSizer().Layout() def doCommit( self ): undo.pushState() with Model.LockRace() as race: if race is None: return for prop, PropClass, name in self.propClassName: getattr(self, prop).commit() race.setChanged() if Utils.getMainWin(): Utils.getMainWin().record.setTimeTrialInput( race.isTimeTrial ) def commit( self ): success = SetNewFilename( self, self ) self.doCommit() Model.resetCache() mainWin = Utils.getMainWin() if mainWin: wx.CallAfter( mainWin.writeRace, False ) wx.CallAfter( Utils.refreshForecastHistory ) if not success and mainWin: wx.CallAfter( mainWin.showPageName, _("Properties") )
def __init__( self, parent, id=wx.ID_ANY, size=wx.DefaultSize ): super(Primes, self).__init__( parent, id, size=size ) self.state = RaceInputState() vsOverall = wx.BoxSizer( wx.VERTICAL ) #--------------------------------------------------------------- self.colNameFields = ( (_('Prime For'), 'effortType', 's'), (_('or Custom'), 'effortCustom', 's'), (_('Position'), 'position', 'i'), (_('Laps\nTo Go'), 'lapsToGo', 'i'), (_('Sponsor'), 'sponsor', 's'), (_('Cash'), 'cash', 'f'), (_('Points'), 'points', 'i'), (_('Merchandise'), 'merchandise', 's'), (_('Winner\nBib'), 'winnerBib', 'i'), (u'', 'winnerInfo', 's'), ) self.colnames = [colName for colName, fieldName, dataType in self.colNameFields] self.iCol = dict( (fieldName, i) for i, (colName, fieldName, dataType) in enumerate(self.colNameFields) if fieldName ) self.grid = ReorderableGrid( self ) self.grid.CreateGrid( 0, len(self.colNameFields) ) GetTranslation = _ for col, (colName, fieldName, dataType) in enumerate(self.colNameFields): self.grid.SetColLabelValue( col, colName ) attr = wx.grid.GridCellAttr() if fieldName == 'effortType': attr.SetEditor( wx.grid.GridCellChoiceEditor(choices=[GetTranslation(name) for code, name in EffortChoices]) ) attr.SetAlignment( wx.ALIGN_CENTRE, wx.ALIGN_TOP ) elif fieldName == 'position': attr.SetAlignment( wx.ALIGN_CENTRE, wx.ALIGN_TOP ) elif fieldName == 'winnerInfo': attr.SetReadOnly( True ) elif dataType == 'i': attr.SetAlignment( wx.ALIGN_RIGHT, wx.ALIGN_TOP ) attr.SetEditor( wx.grid.GridCellFloatEditor(precision=0) ) attr.SetRenderer( wx.grid.GridCellFloatRenderer(precision=0) ) elif dataType == 'f': attr.SetAlignment( wx.ALIGN_RIGHT, wx.ALIGN_TOP ) attr.SetEditor( wx.grid.GridCellFloatEditor(precision=2) ) attr.SetRenderer( wx.grid.GridCellFloatRenderer(precision=2) ) attr.SetAlignment( wx.ALIGN_RIGHT, wx.ALIGN_TOP ) self.grid.SetColAttr( col, attr ) if fieldName == 'lapsToGo': self.lapsToGoCol = col self.grid.Bind( wx.grid.EVT_GRID_CELL_CHANGE, self.onCellChange ) self.grid.AutoSizeColumns( False ) self.grid.AutoSizeRows( False ) #--------------------------------------------------------------- self.photosButton = wx.Button( self, label=u'{}...'.format(_('Photos')) ) self.photosButton.Bind( wx.EVT_BUTTON, self.onPhotos ) self.finishStrip = wx.Button( self, label=u'{}...'.format(_('Finish Strip')) ) self.finishStrip.Bind( wx.EVT_BUTTON, self.onFinishStrip ) self.history = wx.Button( self, label=u'{}...'.format(_('History')) ) self.history.Bind( wx.EVT_BUTTON, self.onHistory ) self.newButton = wx.Button( self, id=wx.ID_NEW ) self.newButton.SetToolTip( wx.ToolTip(_('Create a new Prime')) ) self.newButton.Bind( wx.EVT_BUTTON, self.onNew ) self.nextPositionButton = wx.Button( self, label=('Next Position') ) self.nextPositionButton.SetToolTip( wx.ToolTip(_('Create a Prime from an Existing Prime for the Next Position')) ) self.nextPositionButton.Bind( wx.EVT_BUTTON, self.onNextPosition ) self.nextPrimeButton = wx.Button( self, label=('Next Prime') ) self.nextPrimeButton.SetToolTip( wx.ToolTip(_('Create a Prime from an Existing Prime')) ) self.nextPrimeButton.Bind( wx.EVT_BUTTON, self.onNextPrime ) self.deleteButton = wx.Button( self, id=wx.ID_DELETE ) self.deleteButton.SetToolTip( wx.ToolTip(_('Delete a Prime')) ) self.deleteButton.Bind( wx.EVT_BUTTON, self.onDelete ) hsButtons = wx.BoxSizer( wx.HORIZONTAL ) hsButtons.Add( self.photosButton, flag=wx.ALL, border=4 ) hsButtons.Add( self.finishStrip, flag=wx.ALL, border=4 ) hsButtons.Add( self.history, flag=wx.ALL, border=4 ) hsButtons.AddStretchSpacer() hsButtons.Add( self.newButton, flag=wx.ALL, border=4 ) hsButtons.Add( self.nextPositionButton, flag=wx.ALL, border=4 ) hsButtons.Add( self.nextPrimeButton, flag=wx.ALL, border=4 ) hsButtons.Add( self.deleteButton, flag=wx.ALL, border=4 ) #--------------------------------------------------------------- vsOverall.Add( self.grid, 1, flag=wx.EXPAND|wx.ALL, border=4 ) vsOverall.Add( hsButtons, flag=wx.EXPAND|wx.LEFT|wx.RIGHT|wx.BOTTOM, border=4 ) self.SetSizer( vsOverall )
class Primes( wx.Panel ): def __init__( self, parent, id=wx.ID_ANY, size=wx.DefaultSize ): super(Primes, self).__init__( parent, id, size=size ) self.state = RaceInputState() vsOverall = wx.BoxSizer( wx.VERTICAL ) #--------------------------------------------------------------- self.colNameFields = ( (_('Prime For'), 'effortType', 's'), (_('or Custom'), 'effortCustom', 's'), (_('Position'), 'position', 'i'), (_('Laps\nTo Go'), 'lapsToGo', 'i'), (_('Sponsor'), 'sponsor', 's'), (_('Cash'), 'cash', 'f'), (_('Points'), 'points', 'i'), (_('Merchandise'), 'merchandise', 's'), (_('Winner\nBib'), 'winnerBib', 'i'), (u'', 'winnerInfo', 's'), ) self.colnames = [colName for colName, fieldName, dataType in self.colNameFields] self.iCol = dict( (fieldName, i) for i, (colName, fieldName, dataType) in enumerate(self.colNameFields) if fieldName ) self.grid = ReorderableGrid( self ) self.grid.CreateGrid( 0, len(self.colNameFields) ) GetTranslation = _ for col, (colName, fieldName, dataType) in enumerate(self.colNameFields): self.grid.SetColLabelValue( col, colName ) attr = wx.grid.GridCellAttr() if fieldName == 'effortType': attr.SetEditor( wx.grid.GridCellChoiceEditor(choices=[GetTranslation(name) for code, name in EffortChoices]) ) attr.SetAlignment( wx.ALIGN_CENTRE, wx.ALIGN_TOP ) elif fieldName == 'position': attr.SetAlignment( wx.ALIGN_CENTRE, wx.ALIGN_TOP ) elif fieldName == 'winnerInfo': attr.SetReadOnly( True ) elif dataType == 'i': attr.SetAlignment( wx.ALIGN_RIGHT, wx.ALIGN_TOP ) attr.SetEditor( wx.grid.GridCellFloatEditor(precision=0) ) attr.SetRenderer( wx.grid.GridCellFloatRenderer(precision=0) ) elif dataType == 'f': attr.SetAlignment( wx.ALIGN_RIGHT, wx.ALIGN_TOP ) attr.SetEditor( wx.grid.GridCellFloatEditor(precision=2) ) attr.SetRenderer( wx.grid.GridCellFloatRenderer(precision=2) ) attr.SetAlignment( wx.ALIGN_RIGHT, wx.ALIGN_TOP ) self.grid.SetColAttr( col, attr ) if fieldName == 'lapsToGo': self.lapsToGoCol = col self.grid.Bind( wx.grid.EVT_GRID_CELL_CHANGE, self.onCellChange ) self.grid.AutoSizeColumns( False ) self.grid.AutoSizeRows( False ) #--------------------------------------------------------------- self.photosButton = wx.Button( self, label=u'{}...'.format(_('Photos')) ) self.photosButton.Bind( wx.EVT_BUTTON, self.onPhotos ) self.finishStrip = wx.Button( self, label=u'{}...'.format(_('Finish Strip')) ) self.finishStrip.Bind( wx.EVT_BUTTON, self.onFinishStrip ) self.history = wx.Button( self, label=u'{}...'.format(_('History')) ) self.history.Bind( wx.EVT_BUTTON, self.onHistory ) self.newButton = wx.Button( self, id=wx.ID_NEW ) self.newButton.SetToolTip( wx.ToolTip(_('Create a new Prime')) ) self.newButton.Bind( wx.EVT_BUTTON, self.onNew ) self.nextPositionButton = wx.Button( self, label=('Next Position') ) self.nextPositionButton.SetToolTip( wx.ToolTip(_('Create a Prime from an Existing Prime for the Next Position')) ) self.nextPositionButton.Bind( wx.EVT_BUTTON, self.onNextPosition ) self.nextPrimeButton = wx.Button( self, label=('Next Prime') ) self.nextPrimeButton.SetToolTip( wx.ToolTip(_('Create a Prime from an Existing Prime')) ) self.nextPrimeButton.Bind( wx.EVT_BUTTON, self.onNextPrime ) self.deleteButton = wx.Button( self, id=wx.ID_DELETE ) self.deleteButton.SetToolTip( wx.ToolTip(_('Delete a Prime')) ) self.deleteButton.Bind( wx.EVT_BUTTON, self.onDelete ) hsButtons = wx.BoxSizer( wx.HORIZONTAL ) hsButtons.Add( self.photosButton, flag=wx.ALL, border=4 ) hsButtons.Add( self.finishStrip, flag=wx.ALL, border=4 ) hsButtons.Add( self.history, flag=wx.ALL, border=4 ) hsButtons.AddStretchSpacer() hsButtons.Add( self.newButton, flag=wx.ALL, border=4 ) hsButtons.Add( self.nextPositionButton, flag=wx.ALL, border=4 ) hsButtons.Add( self.nextPrimeButton, flag=wx.ALL, border=4 ) hsButtons.Add( self.deleteButton, flag=wx.ALL, border=4 ) #--------------------------------------------------------------- vsOverall.Add( self.grid, 1, flag=wx.EXPAND|wx.ALL, border=4 ) vsOverall.Add( hsButtons, flag=wx.EXPAND|wx.LEFT|wx.RIGHT|wx.BOTTOM, border=4 ) self.SetSizer( vsOverall ) def onCellChange( self, event ): row, col = event.GetRow(), event.GetCol() colName = self.colNameFields[col][1] GetTranslation = _ if colName == 'effortCustom': if self.grid.GetCellValue(row, col).strip(): self.grid.SetCellValue( row, col-1, GetTranslation('Custom') ) elif colName == 'effortType': if self.grid.GetCellValue(row, col) != 'Custom': self.grid.SetCellValue( row, col+1, u'' ) elif colName == 'winnerBib': bib = int( u''.join(c for c in self.grid.GetCellValue(row, col) if c.isdigit()) ) self.grid.SetCellValue( row, col+1, getWinnerInfo(bib) ) wx.CallAfter( self.grid.AutoSizeColumns, False ) def getT( self ): race = Model.race if not race: return 0 row = self.grid.GetGridCursorRow() if row is None or row < 0: return lapsToGo = int( u''.join(c for c in self.grid.GetCellValue(row, self.lapsToGoCol) if c.isdigit()) ) tMax = 0.0 for rr in GetResults(None): try: tMax = min( tMax, rr.raceTimes[-1-lapsToGo] ) except IndexError: pass return tMax def onPhotos( self, event ): mainWin = Utils.getMainWin() if not mainWin: return mainWin.photoDialog.SetT( self.getT() ) mainWin.photoDialog.Show() def onFinishStrip( self, event ): ShowFinishStrip( self, self.getT() ) def onHistory( self, event ): mainWin = Utils.getMainWin() if not mainWin: return mainWin.openMenuWindow( 'history' ) def selectGridRow( self, row ): self.grid.SelectRow( row ) self.grid.SetGridCursor( row, 0 ) self.grid.ShowCellEditControl() def onNew( self, event ): race = Model.race if not race: Utils.MessageOK( self, _('You must have a Race to create a Prime.'), _('Missing Race') ) return self.commit() race.primes = getattr(race, 'primes', []) rowNew = len( race.primes ) race.primes.append( {} ) self.updateGrid() self.selectGridRow( rowNew ) def onNextPosition( self, event ): rowNext = self.grid.GetGridCursorRow() if rowNext is None or rowNext < 0: return self.commit() race = Model.race if not race: Utils.MessageOK( self, _('You must have a Race to create a next Prime.'), _('Missing Race') ) return nextPrime = race.primes[rowNext].copy() nextPoints = { (1, 5): 3, (2, 3): 2, (3, 2): 1, }.get( (nextPrime['position'], nextPrime['points']), None ) if nextPoints is not None: nextPrime['points'] = nextPoints try: nextPrime['position'] += 1 except: pass nextPrime['winnerBib'] = None race.primes = race.primes[:rowNext+1] + [nextPrime] + race.primes[rowNext+1:] self.updateGrid() self.selectGridRow( rowNext + 1 ) def onNextPrime( self, event ): rowNext = self.grid.GetGridCursorRow() if rowNext is None or rowNext < 0: return self.commit() race = Model.race if not race: Utils.MessageOK( self, _('You must have a Race to create a next Prime.'), _('Missing Race') ) return nextPrime = race.primes[rowNext].copy() nextPrime['position'] = 1 if nextPrime['points']: nextPrime['points'] = 5 if nextPrime['lapsToGo'] > 0: nextPrime['lapsToGo'] -= 1 nextPrime['winnerBib'] = None race.primes = race.primes[:rowNext+1] + [nextPrime] + race.primes[rowNext+1:] self.updateGrid() self.selectGridRow( rowNext + 1 ) def onDelete( self, event ): rowDelete = self.grid.GetGridCursorRow() if rowDelete is None or rowDelete < 0: return self.commit() race = Model.race if race and Utils.MessageOKCancel( self, u'{}: {} ?'.format(_('Delete Prime'), rowDelete+1), _('Confirm Delete Primes') ): race.primes = getattr(race, 'primes', []) try: del race.primes[rowDelete] except Exception as e: return self.updateGrid() if race.primes: self.grid.SetGridCursor( rowDelete, 0 ) def getSponsors( self ): race = Model.race if not race: return [] sponsors = [prime.get('sponsor', u'') for prime in getattr(race, 'primes', [])] + [race.organizer] sponsors = [s for s in sponsors if s] return sponsors def getMerchandise( self ): race = Model.race if not race: return [] merchandise = [prime.get('merchandise', u'') for prime in getattr(race, 'primes', [])] merchandise = [m for m in merchandise if m] return merchandise def setRow( self, prime, row, updateGrid=True ): GetTranslation = _ data = [] for col, (name, attr, dataType) in enumerate(self.colNameFields): if attr == 'effortType': effortType = prime.get('effortType', 'Pack') v = GetTranslation(effortType) elif attr == 'position': position = prime.get('position', 1) v = u'' if position == 0 else unicode(position) elif attr == 'winnerBib': winnerBib = prime.get('winnerBib', None) v = u'' if not winnerBib else unicode(winnerBib) elif attr == 'winnerInfo': v = getWinnerInfo(winnerBib) elif attr == 'cash': cash = prime.get('cash', 0.0) v = u'{:.2f}'.format( prime.get('cash', 0.0) ) if cash else u'' else: v = unicode(prime.get(attr, u'')) if updateGrid: self.grid.SetCellValue( row, col, v ) data.append( v ) return data def getRow( self, row ): values = {} for col, (name, attr, dataType) in enumerate(self.colNameFields): v = self.grid.GetCellValue( row, col ).strip() if dataType == 'i': v = u''.join( c for c in v if c.isdigit() ) v = int( v or 0 ) elif dataType == 'f': v = u''.join( c for c in v if c.isdigit() or c == '.') v = float( v or 0.0 ) values[attr] = v GetTranslation = _ for code, name in EffortChoices: if values['effortType'] == GetTranslation(name): values['effortType'] = name break if values['effortCustom']: values['effortType'] = 'Custom' return values def updateGrid( self ): race = Model.race if not race or not getattr(race, 'primes', None): self.grid.ClearGrid() return Utils.AdjustGridSize( self.grid, len(race.primes) ) for row, prime in enumerate(race.primes): self.setRow( prime, row ) self.grid.AutoSizeColumns( False ) # Resize to fit the column name. self.grid.AutoSizeRows( False ) def refresh( self ): if self.state.changed(): self.updateGrid() def commit( self ): self.grid.SaveEditControlValue() # Make sure the current edit is committed. self.grid.DisableCellEditControl() race = Model.race if not race: return race.primes = [self.getRow(row) for row in xrange(self.grid.GetNumberRows())]
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( 220, 220, 220 ) 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 ) self.deactivateAllButton = wx.Button(self, label=_('&Deactivate All'), style=wx.BU_EXACTFIT) self.Bind( wx.EVT_BUTTON, self.onDeactivateAll, self.deactivateAllButton ) hs.Add( self.deactivateAllButton, 0, border = border, flag = (flag & ~wx.LEFT) ) hs.AddSpacer( 8 ) self.newCategoryButton = wx.Button(self, label=_('&New Category'), 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 Category'), style=wx.BU_EXACTFIT) self.Bind( wx.EVT_BUTTON, self.onDelCategory, self.delCategoryButton ) hs.Add( self.delCategoryButton, 0, border = border, flag = flag ) hs.AddSpacer( 8 ) self.upCategoryButton = wx.Button(self, label=_('Move &Up'), 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=_('Move D&own'), 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( 8 ) self.addExceptionsButton = wx.Button(self, label=_('&Add Bib Exceptions'), style=wx.BU_EXACTFIT) self.Bind( wx.EVT_BUTTON, self.onAddExceptions, self.addExceptionsButton ) hs.Add( self.addExceptionsButton, 0, border = border, flag = (flag & ~wx.LEFT) ) hs.AddSpacer( 8 ) self.updateStartWaveNumbersButton = wx.Button(self, label=_('&Update Start Wave Numbers'), style=wx.BU_EXACTFIT) self.Bind( wx.EVT_BUTTON, self.onUpdateStartWaveNumbers, self.updateStartWaveNumbersButton ) hs.Add( self.updateStartWaveNumbersButton, 0, border = border, flag = (flag & ~wx.LEFT) ) self.normalizeButton = wx.Button(self, label=_('N&ormalize'), style=wx.BU_EXACTFIT) self.Bind( wx.EVT_BUTTON, self.onNormalize, self.normalizeButton ) hs.Add( self.normalizeButton, 0, border = border, flag = (flag & ~wx.LEFT) ) 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 & ~wx.LEFT) ) 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'), (_('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 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_CHANGE, 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)
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( 220, 220, 220 ) 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 ) self.deactivateAllButton = wx.Button(self, label=_('&Deactivate All'), style=wx.BU_EXACTFIT) self.Bind( wx.EVT_BUTTON, self.onDeactivateAll, self.deactivateAllButton ) hs.Add( self.deactivateAllButton, 0, border = border, flag = (flag & ~wx.LEFT) ) hs.AddSpacer( 8 ) self.newCategoryButton = wx.Button(self, label=_('&New Category'), 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 Category'), style=wx.BU_EXACTFIT) self.Bind( wx.EVT_BUTTON, self.onDelCategory, self.delCategoryButton ) hs.Add( self.delCategoryButton, 0, border = border, flag = flag ) hs.AddSpacer( 8 ) self.upCategoryButton = wx.Button(self, label=_('Move &Up'), 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=_('Move D&own'), 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( 8 ) self.addExceptionsButton = wx.Button(self, label=_('&Add Bib Exceptions'), style=wx.BU_EXACTFIT) self.Bind( wx.EVT_BUTTON, self.onAddExceptions, self.addExceptionsButton ) hs.Add( self.addExceptionsButton, 0, border = border, flag = (flag & ~wx.LEFT) ) hs.AddSpacer( 8 ) self.updateStartWaveNumbersButton = wx.Button(self, label=_('&Update Start Wave Numbers'), style=wx.BU_EXACTFIT) self.Bind( wx.EVT_BUTTON, self.onUpdateStartWaveNumbers, self.updateStartWaveNumbersButton ) hs.Add( self.updateStartWaveNumbersButton, 0, border = border, flag = (flag & ~wx.LEFT) ) self.normalizeButton = wx.Button(self, label=_('N&ormalize'), style=wx.BU_EXACTFIT) self.Bind( wx.EVT_BUTTON, self.onNormalize, self.normalizeButton ) hs.Add( self.normalizeButton, 0, border = border, flag = (flag & ~wx.LEFT) ) 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 & ~wx.LEFT) ) 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'), (_('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 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_CHANGE, 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 onNormalize( self, event ): self.commit() if Model.race: Model.race.normalizeCategories() 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' 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]: 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 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() 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.refresh() def _setRow( self, r, active, name, catStr, startOffset = '00:00:00', numLaps = 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 '' ) self.grid.SetCellValue( r, self.iCol['lappedRidersMustContinue'], u'1' if lappedRidersMustContinue else u'0' ) self.grid.SetCellValue( r, self.iCol['rule80Time'], '' ) self.grid.SetCellValue( r, self.iCol['suggestedLaps'], '' ) self.grid.SetCellValue( r, self.iCol['distance'], ('%.3f' % distance) if distance else '' ) 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'], '{}'.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] if col in self.dependentCols: self.grid.SetCellBackgroundColour( row, col, colour ) else: self.grid.SetCellBackgroundColour( row, col, 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 ): if Model.race: for c in Model.race.getAllCategories(): if not c.active: c.active = True Model.race.setChanged() wx.CallAfter( self.refresh ) def onDeactivateAll( self, event ): if Model.race: for c in Model.race.getAllCategories(): if c.active: c.active = False Model.race.setChanged() 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 ): if not 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, 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 )
def __init__(self, parent, id=wx.ID_ANY, size=wx.DefaultSize): super(Pulled, self).__init__(parent, id, size=size) self.state = RaceInputState() vsOverall = wx.BoxSizer(wx.VERTICAL) self.hbs = wx.BoxSizer(wx.HORIZONTAL) self.showingCategoryLabel = wx.StaticText(self, label=u'{}:'.format( _('Start Wave'))) self.showingCategory = wx.StaticText(self) self.showingCategory.SetFont(self.showingCategory.GetFont().Bold()) self.categoryLabel = wx.StaticText(self, label=_('Category:')) self.categoryChoice = wx.Choice(self) self.Bind(wx.EVT_CHOICE, self.doChooseCategory, self.categoryChoice) self.useTableToPullRidersCkBox = wx.CheckBox( self, label=_('Use this Table to Pull Riders')) self.useTableToPullRidersCkBox.SetToolTip( wx.ToolTip( _('Also requires Laps to be set in Categories screen.'))) self.commitBtn = wx.Button(self, label=_('Commit')) self.commitBtn.Bind(wx.EVT_BUTTON, self.doCommit) self.hbs.Add(self.showingCategoryLabel, flag=wx.LEFT | wx.ALIGN_CENTRE_VERTICAL, border=0) self.hbs.Add(self.showingCategory, flag=wx.LEFT | wx.ALIGN_CENTRE_VERTICAL, border=2) self.hbs.Add(self.categoryLabel, flag=wx.LEFT | wx.ALIGN_CENTRE_VERTICAL, border=18) self.hbs.Add(self.categoryChoice, flag=wx.LEFT | wx.ALIGN_CENTRE_VERTICAL, border=2) self.hbs.Add(self.useTableToPullRidersCkBox, flag=wx.LEFT | wx.ALIGN_CENTRE_VERTICAL, border=18) self.hbs.Add(self.commitBtn, flag=wx.LEFT | wx.ALIGN_CENTRE_VERTICAL, border=32) #--------------------------------------------------------------- self.colNameFields = ( (_('Laps to Go'), 'lapsToGo', 'i'), (u' ' + _('Bib'), 'pulledBib', 'i'), (u'Name', 'pulledName', 's'), (u'Team', 'pulledTeam', 's'), (u'Component', 'pulledComponent', 's'), (u'Error', 'pulledError', 's'), ) self.colnames = [ colName for colName, fieldName, dataType in self.colNameFields ] self.iCol = dict((fieldName, i) for i, (colName, fieldName, dataType) in enumerate(self.colNameFields) if fieldName) self.grid = ReorderableGrid(self) self.grid.CreateGrid(0, len(self.colNameFields)) GetTranslation = _ for col, (colName, fieldName, dataType) in enumerate(self.colNameFields): self.grid.SetColLabelValue(col, colName) attr = wx.grid.GridCellAttr() if dataType == 'i': attr.SetAlignment(wx.ALIGN_RIGHT, wx.ALIGN_TOP) attr.SetEditor(wx.grid.GridCellFloatEditor(precision=0)) attr.SetRenderer(wx.grid.GridCellFloatRenderer(precision=0)) elif dataType == 'f': attr.SetAlignment(wx.ALIGN_RIGHT, wx.ALIGN_TOP) attr.SetEditor(wx.grid.GridCellFloatEditor(precision=2)) attr.SetRenderer(wx.grid.GridCellFloatRenderer(precision=2)) elif dataType == 't': attr.SetAlignment(wx.ALIGN_RIGHT, wx.ALIGN_CENTRE) attr.SetEditor(TimeEditor()) self.grid.SetColAttr(col, attr) self.grid.Bind(wx.grid.EVT_GRID_CELL_CHANGED, self.onCellChange) self.grid.AutoSizeColumns(False) self.grid.AutoSizeRows(False) #--------------------------------------------------------------- vsOverall.Add(self.hbs, 0, flag=wx.EXPAND | wx.ALL, border=4) vsOverall.Add(self.grid, 1, flag=wx.EXPAND | wx.ALL, border=4) self.SetSizer(vsOverall)
def __init__( self, parent, id=wx.ID_ANY, size=wx.DefaultSize ): super(Primes, self).__init__( parent, id, size=size ) self.state = RaceInputState() vsOverall = wx.BoxSizer( wx.VERTICAL ) #--------------------------------------------------------------- self.colNameFields = ( (_('Prime For'), 'effortType', 's'), (_('or Custom'), 'effortCustom', 's'), (_('Position'), 'position', 'i'), (_('Laps\nTo Go'), 'lapsToGo', 'i'), (_('Sponsor'), 'sponsor', 's'), (_('Cash'), 'cash', 'f'), (_('Merchandise'), 'merchandise', 's'), (_('Points'), 'points', 'i'), (_('Time\nBonus'), 'timeBonus', 't'), (_('Winner\nBib'), 'winnerBib', 'i'), (u'', 'winnerInfo', 's'), ) self.colnames = [colName for colName, fieldName, dataType in self.colNameFields] self.iCol = dict( (fieldName, i) for i, (colName, fieldName, dataType) in enumerate(self.colNameFields) if fieldName ) self.grid = ReorderableGrid( self ) self.grid.CreateGrid( 0, len(self.colNameFields) ) GetTranslation = _ for col, (colName, fieldName, dataType) in enumerate(self.colNameFields): self.grid.SetColLabelValue( col, colName ) attr = wx.grid.GridCellAttr() if fieldName == 'effortType': attr.SetEditor( wx.grid.GridCellChoiceEditor(choices=[GetTranslation(name) for code, name in EffortChoices]) ) attr.SetAlignment( wx.ALIGN_CENTRE, wx.ALIGN_TOP ) elif fieldName == 'position': attr.SetAlignment( wx.ALIGN_CENTRE, wx.ALIGN_TOP ) elif fieldName == 'winnerInfo': attr.SetReadOnly( True ) elif dataType == 'i': attr.SetAlignment( wx.ALIGN_RIGHT, wx.ALIGN_TOP ) attr.SetEditor( wx.grid.GridCellFloatEditor(precision=0) ) attr.SetRenderer( wx.grid.GridCellFloatRenderer(precision=0) ) elif dataType == 'f': attr.SetAlignment( wx.ALIGN_RIGHT, wx.ALIGN_TOP ) attr.SetEditor( wx.grid.GridCellFloatEditor(precision=2) ) attr.SetRenderer( wx.grid.GridCellFloatRenderer(precision=2) ) elif dataType == 't': attr.SetAlignment( wx.ALIGN_CENTRE, wx.ALIGN_CENTRE ) attr.SetEditor( TimeEditor() ) self.grid.SetColAttr( col, attr ) if fieldName == 'lapsToGo': self.lapsToGoCol = col self.grid.Bind( wx.grid.EVT_GRID_CELL_CHANGE, self.onCellChange ) self.grid.AutoSizeColumns( False ) self.grid.AutoSizeRows( False ) #--------------------------------------------------------------- self.photosButton = wx.Button( self, label=u'{}...'.format(_('Photos')) ) self.photosButton.Bind( wx.EVT_BUTTON, self.onPhotos ) self.finishStrip = wx.Button( self, label=u'{}...'.format(_('Finish Strip')) ) self.finishStrip.Bind( wx.EVT_BUTTON, self.onFinishStrip ) self.history = wx.Button( self, label=u'{}...'.format(_('Passings')) ) self.history.Bind( wx.EVT_BUTTON, self.onHistory ) self.newButton = wx.Button( self, id=wx.ID_NEW ) self.newButton.SetToolTip( wx.ToolTip(_('Create a new Prime')) ) self.newButton.Bind( wx.EVT_BUTTON, self.onNew ) self.nextPositionButton = wx.Button( self, label=('Next Position') ) self.nextPositionButton.SetToolTip( wx.ToolTip(_('Create a Prime from an Existing Prime for the Next Position')) ) self.nextPositionButton.Bind( wx.EVT_BUTTON, self.onNextPosition ) self.nextPrimeButton = wx.Button( self, label=('Next Prime') ) self.nextPrimeButton.SetToolTip( wx.ToolTip(_('Create a Prime from an Existing Prime')) ) self.nextPrimeButton.Bind( wx.EVT_BUTTON, self.onNextPrime ) self.deleteButton = wx.Button( self, id=wx.ID_DELETE ) self.deleteButton.SetToolTip( wx.ToolTip(_('Delete a Prime')) ) self.deleteButton.Bind( wx.EVT_BUTTON, self.onDelete ) hsButtons = wx.BoxSizer( wx.HORIZONTAL ) hsButtons.Add( self.photosButton, flag=wx.ALL, border=4 ) hsButtons.Add( self.finishStrip, flag=wx.ALL, border=4 ) hsButtons.Add( self.history, flag=wx.ALL, border=4 ) hsButtons.AddStretchSpacer() hsButtons.Add( self.newButton, flag=wx.ALL, border=4 ) hsButtons.Add( self.nextPositionButton, flag=wx.ALL, border=4 ) hsButtons.Add( self.nextPrimeButton, flag=wx.ALL, border=4 ) hsButtons.Add( self.deleteButton, flag=wx.ALL, border=4 ) #--------------------------------------------------------------- vsOverall.Add( self.grid, 1, flag=wx.EXPAND|wx.ALL, border=4 ) vsOverall.Add( hsButtons, flag=wx.EXPAND|wx.LEFT|wx.RIGHT|wx.BOTTOM, border=4 ) self.SetSizer( vsOverall )
class Primes( wx.Panel ): def __init__( self, parent, id=wx.ID_ANY, size=wx.DefaultSize ): super(Primes, self).__init__( parent, id, size=size ) self.state = RaceInputState() vsOverall = wx.BoxSizer( wx.VERTICAL ) #--------------------------------------------------------------- self.colNameFields = ( (_('Prime For'), 'effortType', 's'), (_('or Custom'), 'effortCustom', 's'), (_('Position'), 'position', 'i'), (_('Laps\nTo Go'), 'lapsToGo', 'i'), (_('Sponsor'), 'sponsor', 's'), (_('Cash'), 'cash', 'f'), (_('Merchandise'), 'merchandise', 's'), (_('Points'), 'points', 'i'), (_('Time\nBonus'), 'timeBonus', 't'), (_('Winner\nBib'), 'winnerBib', 'i'), (u'', 'winnerInfo', 's'), ) self.colnames = [colName for colName, fieldName, dataType in self.colNameFields] self.iCol = dict( (fieldName, i) for i, (colName, fieldName, dataType) in enumerate(self.colNameFields) if fieldName ) self.grid = ReorderableGrid( self ) self.grid.CreateGrid( 0, len(self.colNameFields) ) GetTranslation = _ for col, (colName, fieldName, dataType) in enumerate(self.colNameFields): self.grid.SetColLabelValue( col, colName ) attr = wx.grid.GridCellAttr() if fieldName == 'effortType': attr.SetEditor( wx.grid.GridCellChoiceEditor(choices=[GetTranslation(name) for code, name in EffortChoices]) ) attr.SetAlignment( wx.ALIGN_CENTRE, wx.ALIGN_TOP ) elif fieldName == 'position': attr.SetAlignment( wx.ALIGN_CENTRE, wx.ALIGN_TOP ) elif fieldName == 'winnerInfo': attr.SetReadOnly( True ) elif dataType == 'i': attr.SetAlignment( wx.ALIGN_RIGHT, wx.ALIGN_TOP ) attr.SetEditor( wx.grid.GridCellFloatEditor(precision=0) ) attr.SetRenderer( wx.grid.GridCellFloatRenderer(precision=0) ) elif dataType == 'f': attr.SetAlignment( wx.ALIGN_RIGHT, wx.ALIGN_TOP ) attr.SetEditor( wx.grid.GridCellFloatEditor(precision=2) ) attr.SetRenderer( wx.grid.GridCellFloatRenderer(precision=2) ) elif dataType == 't': attr.SetAlignment( wx.ALIGN_CENTRE, wx.ALIGN_CENTRE ) attr.SetEditor( TimeEditor() ) self.grid.SetColAttr( col, attr ) if fieldName == 'lapsToGo': self.lapsToGoCol = col self.grid.Bind( wx.grid.EVT_GRID_CELL_CHANGE, self.onCellChange ) self.grid.AutoSizeColumns( False ) self.grid.AutoSizeRows( False ) #--------------------------------------------------------------- self.photosButton = wx.Button( self, label=u'{}...'.format(_('Photos')) ) self.photosButton.Bind( wx.EVT_BUTTON, self.onPhotos ) self.finishStrip = wx.Button( self, label=u'{}...'.format(_('Finish Strip')) ) self.finishStrip.Bind( wx.EVT_BUTTON, self.onFinishStrip ) self.history = wx.Button( self, label=u'{}...'.format(_('Passings')) ) self.history.Bind( wx.EVT_BUTTON, self.onHistory ) self.newButton = wx.Button( self, id=wx.ID_NEW ) self.newButton.SetToolTip( wx.ToolTip(_('Create a new Prime')) ) self.newButton.Bind( wx.EVT_BUTTON, self.onNew ) self.nextPositionButton = wx.Button( self, label=('Next Position') ) self.nextPositionButton.SetToolTip( wx.ToolTip(_('Create a Prime from an Existing Prime for the Next Position')) ) self.nextPositionButton.Bind( wx.EVT_BUTTON, self.onNextPosition ) self.nextPrimeButton = wx.Button( self, label=('Next Prime') ) self.nextPrimeButton.SetToolTip( wx.ToolTip(_('Create a Prime from an Existing Prime')) ) self.nextPrimeButton.Bind( wx.EVT_BUTTON, self.onNextPrime ) self.deleteButton = wx.Button( self, id=wx.ID_DELETE ) self.deleteButton.SetToolTip( wx.ToolTip(_('Delete a Prime')) ) self.deleteButton.Bind( wx.EVT_BUTTON, self.onDelete ) hsButtons = wx.BoxSizer( wx.HORIZONTAL ) hsButtons.Add( self.photosButton, flag=wx.ALL, border=4 ) hsButtons.Add( self.finishStrip, flag=wx.ALL, border=4 ) hsButtons.Add( self.history, flag=wx.ALL, border=4 ) hsButtons.AddStretchSpacer() hsButtons.Add( self.newButton, flag=wx.ALL, border=4 ) hsButtons.Add( self.nextPositionButton, flag=wx.ALL, border=4 ) hsButtons.Add( self.nextPrimeButton, flag=wx.ALL, border=4 ) hsButtons.Add( self.deleteButton, flag=wx.ALL, border=4 ) #--------------------------------------------------------------- vsOverall.Add( self.grid, 1, flag=wx.EXPAND|wx.ALL, border=4 ) vsOverall.Add( hsButtons, flag=wx.EXPAND|wx.LEFT|wx.RIGHT|wx.BOTTOM, border=4 ) self.SetSizer( vsOverall ) def onCellChange( self, event ): row, col = event.GetRow(), event.GetCol() colName = self.colNameFields[col][1] GetTranslation = _ if colName == 'effortCustom': if self.grid.GetCellValue(row, col).strip(): self.grid.SetCellValue( row, col-1, GetTranslation('Custom') ) elif colName == 'effortType': if self.grid.GetCellValue(row, col) != 'Custom': self.grid.SetCellValue( row, col+1, u'' ) elif colName == 'winnerBib': bib = int( u''.join(c for c in self.grid.GetCellValue(row, col) if c.isdigit()) ) self.grid.SetCellValue( row, col+1, getWinnerInfo(bib) ) wx.CallAfter( self.grid.AutoSizeColumns, False ) def getT( self ): race = Model.race if not race: return 0 row = self.grid.GetGridCursorRow() if row is None or row < 0: return lapsToGo = int( u''.join(c for c in self.grid.GetCellValue(row, self.lapsToGoCol) if c.isdigit()) ) tMax = 0.0 for rr in GetResults(None): try: tMax = min( tMax, rr.raceTimes[-1-lapsToGo] ) except IndexError: pass return tMax def onPhotos( self, event ): mainWin = Utils.getMainWin() if not mainWin: return mainWin.photoDialog.SetT( self.getT() ) mainWin.photoDialog.Show() def onFinishStrip( self, event ): ShowFinishStrip( self, self.getT() ) def onHistory( self, event ): mainWin = Utils.getMainWin() if not mainWin: return mainWin.openMenuWindow( 'Passings' ) def selectGridRow( self, row ): self.grid.SelectRow( row ) self.grid.SetGridCursor( row, 0 ) self.grid.ShowCellEditControl() def onNew( self, event ): race = Model.race if not race: Utils.MessageOK( self, _('You must have a Race to create a Prime.'), _('Missing Race') ) return self.commit() race.primes = getattr(race, 'primes', []) rowNew = len( race.primes ) race.primes.append( {} ) self.updateGrid() self.selectGridRow( rowNew ) def onNextPosition( self, event ): rowNext = self.grid.GetGridCursorRow() if rowNext is None or rowNext < 0: return self.commit() race = Model.race if not race: Utils.MessageOK( self, _('You must have a Race to create a next Prime.'), _('Missing Race') ) return nextPrime = race.primes[rowNext].copy() nextPoints = { (1, 5): 3, (2, 3): 2, (3, 2): 1, }.get( (nextPrime['position'], nextPrime['points']), None ) if nextPoints is not None: nextPrime['points'] = nextPoints try: nextPrime['position'] += 1 except: pass nextPrime['winnerBib'] = None race.primes = race.primes[:rowNext+1] + [nextPrime] + race.primes[rowNext+1:] self.updateGrid() self.selectGridRow( rowNext + 1 ) def onNextPrime( self, event ): rowNext = self.grid.GetGridCursorRow() if rowNext is None or rowNext < 0: return self.commit() race = Model.race if not race: Utils.MessageOK( self, _('You must have a Race to create a next Prime.'), _('Missing Race') ) return nextPrime = race.primes[rowNext].copy() nextPrime['position'] = 1 if nextPrime['points']: nextPrime['points'] = 5 if nextPrime['lapsToGo'] > 0: nextPrime['lapsToGo'] -= 1 nextPrime['winnerBib'] = None race.primes = race.primes[:rowNext+1] + [nextPrime] + race.primes[rowNext+1:] self.updateGrid() self.selectGridRow( rowNext + 1 ) def onDelete( self, event ): rowDelete = self.grid.GetGridCursorRow() if rowDelete is None or rowDelete < 0: return self.commit() race = Model.race if race and Utils.MessageOKCancel( self, u'{}: {} ?'.format(_('Delete Prime'), rowDelete+1), _('Confirm Delete Primes') ): race.primes = getattr(race, 'primes', []) try: del race.primes[rowDelete] except Exception as e: return self.updateGrid() if race.primes: self.grid.SetGridCursor( rowDelete, 0 ) def getSponsors( self ): race = Model.race if not race: return [] sponsors = [prime.get('sponsor', u'') for prime in getattr(race, 'primes', [])] + [race.organizer] sponsors = [s for s in sponsors if s] return sponsors def getMerchandise( self ): race = Model.race if not race: return [] merchandise = [prime.get('merchandise', u'') for prime in getattr(race, 'primes', [])] merchandise = [m for m in merchandise if m] return merchandise def setRow( self, prime, row, updateGrid=True ): GetTranslation = _ data = [] for col, (name, attr, dataType) in enumerate(self.colNameFields): if attr == 'effortType': effortType = prime.get('effortType', 'Pack') v = GetTranslation(effortType) elif attr == 'position': position = prime.get('position', 1) v = u'' if position == 0 else unicode(position) elif attr == 'points': points = prime.get('points', 0) v = u'' if points == 0 else unicode(points) elif attr == 'winnerBib': winnerBib = prime.get('winnerBib', None) v = u'' if not winnerBib else unicode(winnerBib) elif attr == 'winnerInfo': v = getWinnerInfo(winnerBib) elif dataType == 'f': f = prime.get(attr, 0.0) v = u'{:.2f}'.format(f) if f else u'' elif dataType == 't': t = prime.get(attr, 0.0) v = Utils.formatTime(t, forceHours=True, twoDigitHours=True) if t != 0 else u'' else: v = unicode(prime.get(attr, u'')) if updateGrid: self.grid.SetCellValue( row, col, v ) data.append( v ) return data def getRow( self, row ): values = {} for col, (name, attr, dataType) in enumerate(self.colNameFields): v = self.grid.GetCellValue( row, col ).strip() if dataType == 'i': v = u''.join( c for c in v if c.isdigit() ) v = int( v or 0 ) elif dataType == 'f': v = u''.join( c for c in v if c.isdigit() or c == '.') v = float( v or 0.0 ) elif dataType == 't': v = Utils.StrToSeconds( v ) if attr == 'position' and not v: v = 1 values[attr] = v GetTranslation = _ for code, name in EffortChoices: if values['effortType'] == GetTranslation(name): values['effortType'] = name break if values['effortCustom']: values['effortType'] = 'Custom' return values def updateGrid( self ): race = Model.race if not race or not getattr(race, 'primes', None): self.grid.ClearGrid() return Utils.AdjustGridSize( self.grid, len(race.primes) ) for row, prime in enumerate(race.primes): self.setRow( prime, row ) self.grid.AutoSizeColumns( False ) # Resize to fit the column name. self.grid.AutoSizeRows( False ) def refresh( self ): if self.state.changed(): self.updateGrid() def commit( self ): self.grid.SaveEditControlValue() # Make sure the current edit is committed. self.grid.DisableCellEditControl() race = Model.race if not race: return race.primes = [self.getRow(row) for row in xrange(self.grid.GetNumberRows())]