Exemple #1
0
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)
Exemple #2
0
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)