def updatedExpectedTimes(self, tRace=None):
     if not self.quickExpected:
         return
     race = Model.race
     if not tRace:
         tRace = race.curRaceTime()
     getT = self.getETATimeFunc()
     self.expectedGrid.SetColumn(
         iTimeCol,
         [
             formatTime(getT(e) - tRace)
             if e.lap > 0
             else ("[{}]".format(formatTime(max(0.0, getT(e) - tRace + 0.0000001))))
             for e in self.quickExpected
         ],
     )
Exemple #2
0
	def updatedExpectedTimes( self, tRace = None ):
		if not self.quickExpected:
			return
		if not tRace:
			tRace = Model.race.curRaceTime()
		self.expectedGrid.SetColumn( iTimeCol, [formatTime(e.t - tRace) if e.lap > 0 else ('[{}]'.format(formatTime(max(0.0, e.t - tRace + 0.99999999))))\
										for e in self.quickExpected] )
 def updatedExpectedTimes(self, tRace=None):
     if not self.quickExpected:
         return
     race = Model.race
     if not tRace:
         tRace = race.curRaceTime()
     getT = self.getETATimeFunc()
     self.expectedGrid.SetColumn( iTimeCol, [formatTime(getT(e) - tRace) if e.lap > 0 else ('[{}]'.format(formatTime(max(0.0, getT(e) - tRace + 0.0000001))))\
             for e in self.quickExpected] )
Exemple #4
0
	def onVerticalLines( self, event=None ):
		try:
			t = self.tBitmap + datetime.timedelta(
				seconds=self.direction * (self.getPosition() - self.frontWheelEdge) / self.pixelsPerSecond
			)
		except Exception as e:
			return
		secs = (t - self.tStart).total_seconds()
		self.speed.SetLabel( formatTime(secs, True) )
		self.GetSizer().Layout()
Exemple #5
0
 def onVerticalLines(self, event=None):
     try:
         t = self.tBitmap + datetime.timedelta(
             seconds=self.direction *
             (self.getPosition() - self.frontWheelEdge) /
             self.pixelsPerSecond)
     except Exception as e:
         return
     secs = (t - self.tStart).total_seconds()
     self.speed.SetLabel(formatTime(secs, True))
     self.GetSizer().Layout()
Exemple #6
0
	def refresh( self ):
		with Model.LockRace() as race:
			if race is None or not race.isRunning():
				self.quickExpected = None
				self.clearGrids()
				return
				
			try:
				externalInfo = race.excelLink.read( True )
			except:
				externalInfo = {}
						
			tRace = race.curRaceTime()
			tRaceLength = race.minutes * 60.0
			
			entries = interpolateNonZeroFinishers()
			
			isTimeTrial = getattr(race, 'isTimeTrial', False)
			if isTimeTrial:
				# Update the start times in as recorded times.
				startTimes = [(rider.firstTime, rider.num) for rider in race.riders.itervalues() \
								if rider.status == Model.Rider.Finisher and rider.firstTime]
				startTimes.sort()
				
				# Find the next start time so we can update the display.
				iClosestStartTime = bisect.bisect_left( startTimes, (tRace, 0) )
				if iClosestStartTime < len(startTimes):
					tClosestStartTime = startTimes[iClosestStartTime][0]
					milliSeconds = max( 0, int((tClosestStartTime - tRace)*1000.0 + 10.0) )
					if self.callLaterRefresh is None:
						self.callLaterRefresh = wx.CallLater( milliSeconds, self.refresh )
					self.callLaterRefresh.Restart( milliSeconds )
				
				startTimeEntries = [Model.Entry(st[1], 0, st[0], False) for st in startTimes]
				
				# Add the rider firstTime to correct the times back to race times.
				correctedEntries = [Model.Entry(e.num, e.lap, (race.riders[e.num].firstTime or 0.0) + e.t, e.interp) for e in entries]
				startTimeEntries.extend( correctedEntries )
				entries = startTimeEntries
			
			#------------------------------------------------------------------
			# Select the interpolated entries around now.
			leaderPrev, leaderNext = race.getPrevNextLeader( tRace )
			averageLapTime = race.getAverageLapTime()
			backSecs = averageLapTime
			
			expectedShowMax = 80
			
			tMin = tRace - backSecs
			tMax = tRace + averageLapTime
			iCur = bisect.bisect_left( entries, Model.Entry(0, 0, tRace, True) )
			iLeft = max(0, iCur - expectedShowMax/2)
			seen = {}
			expected = [ seen.setdefault(e.num, e) for e in entries[iLeft:] if e.interp and tMin <= e.t <= tMax and e.num not in seen ]
			if isTimeTrial:
				# Update the expected start times.
				expectedStarters = [(rider.firstTime, rider.num) for rider in race.riders.itervalues() \
								if rider.status == Model.Rider.Finisher and rider.firstTime and rider.firstTime >= tRace]
				expectedStarters.sort()
				expectedStarterEntries = [Model.Entry(st[1], 0, st[0], False) for st in expectedStarters]
				expectedStarterEntries.extend( expected )
				expected = expectedStarterEntries
				
			expected = expected[:expectedShowMax]
			
			prevCatLeaders, nextCatLeaders = race.getCatPrevNextLeaders( tRace )
			prevRiderPosition, nextRiderPosition = race.getPrevNextRiderPositions( tRace )
			prevRiderGap, nextRiderGap = race.getPrevNextRiderGaps( tRace )
			
			backgroundColour = {}
			textColour = {}
			#------------------------------------------------------------------
			# Highlight the missing riders.
			tMissing = tRace - averageLapTime / 8.0
			iNotMissing = 0
			for r in (i for i, e in enumerate(expected) if e.t < tMissing):
				for c in xrange(iColMax):
					backgroundColour[(r, c)] = self.orangeColour
				iNotMissing = r + 1
				
			#------------------------------------------------------------------
			# Highlight the leaders in the expected list.
			iBeforeLeader = None
			# Highlight the leader by category.
			catNextTime = {}
			outsideTimeBound = set()
			for r, e in enumerate(expected):
				if e.num in nextCatLeaders:
					backgroundColour[(r, iNoteCol)] = wx.GREEN
					catNextTime[nextCatLeaders[e.num]] = e.t
					if e.num == leaderNext:
						backgroundColour[(r, iNumCol)] = wx.GREEN
						iBeforeLeader = r
				elif tRace < tRaceLength and race.isOutsideTimeBound(e.num):
					backgroundColour[(r, iNumCol)] = backgroundColour[(r, iNoteCol)] = self.redColour
					textColour[(r, iNumCol)] = textColour[(r, iNoteCol)] = wx.WHITE
					outsideTimeBound.add( e.num )
			
			data = [None] * iColMax
			data[iNumCol] = ['{}'.format(e.num) for e in expected]
			data[iTimeCol] = [formatTime(e.t - tRace) if e.lap > 0 else ('[%s]' % formatTime(max(0.0, e.t - tRace + 0.99999999)))\
										for e in expected]
			data[iLapCol] = ['{}'.format(e.lap) for e in expected]
			def getNoteExpected( e ):
				if e.lap == 0:
					return _('Start')
				try:
					position = prevRiderPosition.get(e.num, -1) if e.t < catNextTime[race.getCategory(e.num)] else \
							   nextRiderPosition.get(e.num, -1)
				except KeyError:
					position = prevRiderPosition.get(e.num, -1)
					
				if position == 1:
					return _('Lead')
				elif e.t < tMissing:
					return _('miss')
				elif position >= 0:
					return Utils.ordinal(position)
				else:
					return ' '
			data[iNoteCol] = [getNoteExpected(e) for e in expected]
			def getGapExpected( e ):
				try:
					gap = prevRiderGap.get(e.num, ' ') if e.t < catNextTime[race.getCategory(e.num)] else \
							   nextRiderGap.get(e.num, ' ')
				except KeyError:
					gap = prevRiderGap.get(e.num, ' ')
				return gap
			data[iGapCol] = [getGapExpected(e) for e in expected]
			def getName( e ):
				info = externalInfo.get(e.num, {})
				last = info.get('LastName','')
				first = info.get('FirstName','')
				if last and first:
					return u'{}, {}'.format(last, first)
				return last or first or u' '
			data[iNameCol] = [getName(e) for e in expected]
			
			def getWave( e ):
				try:
					return race.getCategory( e.num ).fullname
				except:
					return u' '
			data[iWaveCol] = [getWave(e) for e in expected]
			
			self.quickExpected = expected
			
			self.expectedGrid.Set( data = data, backgroundColour = backgroundColour, textColour = textColour )
			self.expectedGrid.AutoSizeColumns()
			self.expectedGrid.AutoSizeRows()
			
			if iBeforeLeader:
				Utils.SetLabel( self.expectedName, u'{}: {} {}'.format(_('Expected'), iBeforeLeader, _('before race leader')) )
			else:
				Utils.SetLabel( self.expectedName, _('Expected') )
			
			#------------------------------------------------------------------
			# Update recorded.
			recordedDisplayMax = 64
			recorded = [ e for e in entries if not e.interp and e.t <= tRace ]
			recorded = recorded[-recordedDisplayMax:]
			self.quickRecorded = recorded
				
			backgroundColour = {}
			textColour = {}
			outsideTimeBound = set()
			# Highlight the leader in the recorded list.
			for r, e in enumerate(recorded):
				if prevRiderPosition.get(e.num,-1) == 1:
					backgroundColour[(r, iNoteCol)] = wx.GREEN
					if e.num == leaderPrev:
						backgroundColour[(r, iNumCol)] = wx.GREEN
				elif tRace < tRaceLength and race.isOutsideTimeBound(e.num):
					backgroundColour[(r, iNumCol)] = backgroundColour[(r, iNoteCol)] = self.redColour
					textColour[(r, iNumCol)] = textColour[(r, iNoteCol)] = wx.WHITE
					outsideTimeBound.add( e.num )
									
			data = [None] * iColMax
			data[iNumCol] = ['{}'.format(e.num) for e in recorded]
			data[iTimeCol] = [formatTime(e.t) if e.lap > 0 else '[{}]'.format(formatTime(e.t)) for e in recorded]
			data[iLapCol] = ['{}'.format(e.lap) for e in recorded]
			def getNoteHistory( e ):
				if e.lap == 0:
					return 'Start'

				position = nextRiderPosition.get(e.num, -1)
				if position == 1:
					return _('Lead')
				elif position >= 0:
					return Utils.ordinal(position)
				else:
					return ' '
			data[iNoteCol] = [getNoteHistory(e) for e in recorded]
			def getGapHistory( e ):
				if e.lap == 0:
					return ' '
				return prevRiderGap.get(e.num, ' ')
			data[iGapCol] = [getGapHistory(e) for e in recorded]
			data[iNameCol] = [getName(e) for e in recorded]
			data[iWaveCol] = [getWave(e) for e in recorded]

			self.historyGrid.Set( data = data, backgroundColour = backgroundColour, textColour = textColour )
			self.historyGrid.AutoSizeColumns()
			self.historyGrid.AutoSizeRows()
			
			# Show the relevant cells in each table.
			if recorded:
				self.historyGrid.MakeCellVisible( len(recorded)-1, 0 )
			if iNotMissing < self.expectedGrid.GetNumberRows():
				self.expectedGrid.MakeCellVisible( iNotMissing, 0 )
	def refresh( self ):
		race = Model.race
		if race is None or not race.isRunning():
			self.quickExpected = None
			self.clearGrids()
			return
			
		try:
			externalInfo = race.excelLink.read( True )
		except:
			externalInfo = {}
					
		tRace = race.curRaceTime()
		tRaceLength = race.minutes * 60.0
		
		expected, recorded = getExpectedRecorded()
		
		isTimeTrial = race.isTimeTrial
		if isTimeTrial and expected:
			for e in expected:
				if e.lap == 0:
					# Schedule a refresh later to update started riders.
					milliSeconds = max( 1, int((e.t - tRace)*1000.0 + 10.0) )
					if self.callLaterRefresh is None:
						def doRefresh():
							self.refresh()
							if Utils.mainWin:
								Utils.mainWin.refreshTTStart()
						self.callLaterRefresh = wx.CallLater( milliSeconds, doRefresh )
					self.callLaterRefresh.Restart( milliSeconds )
					break

		#------------------------------------------------------------------
		# Highlight interpolated entries at race time.
		leaderPrev, leaderNext = race.getPrevNextLeader( tRace )
		averageLapTime = race.getAverageLapTime()
		
		expectedShowMax = 80
		
		expected = expected[:expectedShowMax]
		
		#-----------------------------------------------------------
		nextCatLeaders = {}
		prevRiderPosition, nextRiderPosition = {}, {}
		prevRiderGap = {}
		
		if race.riders and race.isRunning():
			Finisher = Model.Rider.Finisher
			for c in race.getCategories(startWaveOnly=True):
				results = GetResultsWithData( c )
				if not results:
					continue
				rr = results[0]
				if rr.status != Finisher:
					continue
				nextCatLeaders[rr.num] = c
				for pos, rr in enumerate(results, 1):
					if rr.status != Finisher:
						break
					prevRiderPosition[rr.num] = nextRiderPosition[rr.num] = pos
					prevRiderGap[rr.num] = rr.gap
	
		#-----------------------------------------------------------
		
		backgroundColour = {}
		textColour = {}
		#------------------------------------------------------------------
		# Highlight the missing riders.
		tMissing = tRace - averageLapTime / 8.0
		iNotMissing = 0
		for r in (i for i, e in enumerate(expected) if e.t < tMissing):
			for c in xrange(iExpectedColMax):
				backgroundColour[(r, c)] = self.orangeColour
			iNotMissing = r + 1
			
		#------------------------------------------------------------------
		# Highlight the leaders in the expected list.
		iBeforeLeader = None
		# Highlight the leader by category.
		catNextTime = {}
		outsideTimeBound = set()
		for r, e in enumerate(expected):
			if e.num in nextCatLeaders:
				backgroundColour[(r, iExpectedNoteCol)] = wx.GREEN
				catNextTime[nextCatLeaders[e.num]] = e.t
				if e.num == leaderNext:
					backgroundColour[(r, iExpectedNumCol)] = wx.GREEN
					iBeforeLeader = r
			elif tRace < tRaceLength and race.isOutsideTimeBound(e.num):
				backgroundColour[(r, iExpectedNoteCol)] = self.redColour
				textColour[(r, iExpectedNoteCol)] = wx.WHITE
				outsideTimeBound.add( e.num )
		
		data = [None] * iExpectedColMax
		data[iExpectedNumCol] = [u'{}'.format(e.num) for e in expected]
		getT = self.getETATimeFunc()
		data[iExpectedTimeCol] = [formatTime(getT(e) - tRace) if e.lap > 0
			else (u'[{}]'.format(formatTime(max(0.0, getT(e) - tRace + 0.99999999)))) for e in expected]
		data[iExpectedLapCol] = [u'{}'.format(e.lap) if e.lap > 0 else u'' for e in expected]
		def getNoteExpected( e ):
			if e.lap == 0:
				return _('Start')
			try:
				position = prevRiderPosition.get(e.num, -1) if e.t < catNextTime[race.getCategory(e.num)] else \
						   nextRiderPosition.get(e.num, -1)
			except KeyError:
				position = prevRiderPosition.get(e.num, -1)
				
			if position == 1:
				return _('Lead')
			elif e.t < tMissing:
				return _('miss')
			elif position >= 0:
				return Utils.ordinal(position)
			else:
				return u' '
		data[iExpectedNoteCol] = [getNoteExpected(e) for e in expected]
		def getName( e ):
			info = externalInfo.get(e.num, {})
			last = info.get('LastName','')
			first = info.get('FirstName','')
			if last and first:
				return u'{}, {}'.format(last, first)
			return last or first or u' '
		data[iExpectedNameCol] = [getName(e) for e in expected]
		
		def getWave( e ):
			try:
				return race.getCategory(e.num).fullname
			except:
				return u' '
		data[iExpectedWaveCol] = [getWave(e) for e in expected]
		
		self.quickExpected = expected
		
		self.expectedGrid.Set( data = data, backgroundColour = backgroundColour, textColour = textColour )
		self.expectedGrid.AutoSizeColumns()
		self.expectedGrid.AutoSizeRows()
		
		if iBeforeLeader:
			Utils.SetLabel( self.expectedName, u'{}: {} {}'.format(_('Expected'), iBeforeLeader, _('before race leader')) )
		else:
			Utils.SetLabel( self.expectedName, _('Expected (click Bib to record entry)') )
		
		#------------------------------------------------------------------
		# Update recorded.
		recorded = self.quickRecorded = self.addGaps( recorded )
			
		backgroundColour = {}
		textColour = {}
		outsideTimeBound = set()
		# Highlight the leader in the recorded list.
		for r, e in enumerate(recorded):
			if e.isGap():
				for i in xrange( iRecordedColMax ):
					backgroundColour[(r, i)] = self.groupColour
			if prevRiderPosition.get(e.num,-1) == 1:
				backgroundColour[(r, iRecordedNoteCol)] = wx.GREEN
				if e.num == leaderPrev:
					backgroundColour[(r, iRecordedNumCol)] = wx.GREEN
			elif tRace < tRaceLength and race.isOutsideTimeBound(e.num):
				backgroundColour[(r, iRecordedNoteCol)] = self.redColour
				textColour[(r, iRecordedNoteCol)] = wx.WHITE
				outsideTimeBound.add( e.num )
								
		data = [None] * iRecordedColMax
		data[iRecordedNumCol] = [u'{}{}'.format(e.num,u"\u2190" if IsRiderFinished(e.num, e.t) else u'') if e.num > 0 else u' ' for e in recorded]
		data[iRecordedTimeCol] = [
			formatTime(e.t) if e.lap > 0 else
			(u'{}'.format(formatTimeGap(e.t)) if e.t is not None else u' ') if e.isGap() else
			u'[{}]'.format(formatTime(e.t)) for e in recorded]
		data[iRecordedLapCol] = [u'{}'.format(e.lap) if e.lap else u' ' for e in recorded]
		def getNoteHistory( e ):
			if e.isGap():
				return u'{}'.format(e.groupCount)
			
			if e.lap == 0:
				return _('Start')

			position = nextRiderPosition.get(e.num, -1)
			if position == 1:
				return _('Lead')
			elif position >= 0:
				return Utils.ordinal(position)
			else:
				return ' '
		data[iRecordedNoteCol] = [getNoteHistory(e) for e in recorded]
		def getGapHistory( e ):
			if e.lap == 0:
				return ' '
			return prevRiderGap.get(e.num, u'')
		data[iRecordedGapCol] = [getGapHistory(e) for e in recorded]
		data[iRecordedNameCol] = [getName(e) for e in recorded]
		data[iRecordedWaveCol] = [getWave(e) for e in recorded]

		self.historyGrid.Set( data = data, backgroundColour = backgroundColour, textColour = textColour )
		self.historyGrid.AutoSizeColumns()
		self.historyGrid.AutoSizeRows()
		
		# Show the relevant cells in each table.
		if recorded:
			self.historyGrid.MakeCellVisible( len(recorded)-1, 0 )
		if iNotMissing < self.expectedGrid.GetNumberRows():
			self.expectedGrid.MakeCellVisible( iNotMissing, 0 )
    def refresh(self):
        race = Model.race
        if race is None or not race.isRunning():
            self.quickExpected = None
            self.clearGrids()
            return

        try:
            externalInfo = race.excelLink.read(True)
        except:
            externalInfo = {}

        tRace = race.curRaceTime()
        tRaceLength = race.minutes * 60.0

        tMin = tRace - max(race.getAverageLapTime(), 10 * 60.0)
        tMax = tRace + max(race.getAverageLapTime(), 10 * 60.0)
        expected, recorded = getExpectedRecorded(tMin)

        isTimeTrial = race.isTimeTrial
        if isTimeTrial and expected:
            for e in expected:
                if e.lap == 0:
                    # Schedule a refresh later to update started riders.
                    milliSeconds = max(1, int((e.t - tRace) * 1000.0 + 10.0))
                    if self.callLaterRefresh is None:

                        def doRefresh():
                            self.refresh()
                            if Utils.mainWin:
                                Utils.mainWin.refreshTTStart()

                        self.callLaterRefresh = wx.CallLater(
                            milliSeconds, doRefresh)
                    self.callLaterRefresh.Restart(milliSeconds)
                    break

        #------------------------------------------------------------------
        # Highlight interpolated entries at race time.
        leaderPrev, leaderNext = race.getPrevNextLeader(tRace)
        averageLapTime = race.getAverageLapTime()
        backSecs = averageLapTime

        expectedShowMax = 80

        expected = expected[:expectedShowMax]

        prevCatLeaders, nextCatLeaders = race.getCatPrevNextLeaders(tRace)
        prevRiderPosition, nextRiderPosition = race.getPrevNextRiderPositions(
            tRace)
        prevRiderGap, nextRiderGap = race.getPrevNextRiderGaps(tRace)

        backgroundColour = {}
        textColour = {}
        #------------------------------------------------------------------
        # Highlight the missing riders.
        tMissing = tRace - averageLapTime / 8.0
        iNotMissing = 0
        for r in (i for i, e in enumerate(expected) if e.t < tMissing):
            for c in xrange(iColMax):
                backgroundColour[(r, c)] = self.orangeColour
            iNotMissing = r + 1

        #------------------------------------------------------------------
        # Highlight the leaders in the expected list.
        iBeforeLeader = None
        # Highlight the leader by category.
        catNextTime = {}
        outsideTimeBound = set()
        for r, e in enumerate(expected):
            if e.num in nextCatLeaders:
                backgroundColour[(r, iNoteCol)] = wx.GREEN
                catNextTime[nextCatLeaders[e.num]] = e.t
                if e.num == leaderNext:
                    backgroundColour[(r, iNumCol)] = wx.GREEN
                    iBeforeLeader = r
            elif tRace < tRaceLength and race.isOutsideTimeBound(e.num):
                backgroundColour[(r, iNoteCol)] = self.redColour
                textColour[(r, iNoteCol)] = wx.WHITE
                outsideTimeBound.add(e.num)

        data = [None] * iColMax
        data[iNumCol] = [u'{}'.format(e.num) for e in expected]
        getT = self.getETATimeFunc()
        data[iTimeCol] = [
            formatTime(getT(e) - tRace) if e.lap > 0 else
            (u'[{}]'.format(formatTime(max(0.0,
                                           getT(e) - tRace + 0.99999999))))
            for e in expected
        ]
        data[iLapCol] = [
            u'{}'.format(e.lap) if e.lap > 0 else u'' for e in expected
        ]

        def getNoteExpected(e):
            if e.lap == 0:
                return _('Start')
            try:
                position = prevRiderPosition.get(e.num, -1) if e.t < catNextTime[race.getCategory(e.num)] else \
                     nextRiderPosition.get(e.num, -1)
            except KeyError:
                position = prevRiderPosition.get(e.num, -1)

            if position == 1:
                return _('Lead')
            elif e.t < tMissing:
                return _('miss')
            elif position >= 0:
                return Utils.ordinal(position)
            else:
                return ' '

        data[iNoteCol] = [getNoteExpected(e) for e in expected]

        def getGapExpected(e):
            try:
                gap = prevRiderGap.get(e.num, ' ') if e.t < catNextTime[race.getCategory(e.num)] else \
                     nextRiderGap.get(e.num, ' ')
            except KeyError:
                gap = prevRiderGap.get(e.num, ' ')
            return gap

        data[iGapCol] = [getGapExpected(e) for e in expected]

        def getName(e):
            info = externalInfo.get(e.num, {})
            last = info.get('LastName', '')
            first = info.get('FirstName', '')
            if last and first:
                return u'{}, {}'.format(last, first)
            return last or first or u' '

        data[iNameCol] = [getName(e) for e in expected]

        def getWave(e):
            try:
                return race.getCategory(e.num).fullname
            except:
                return u' '

        data[iWaveCol] = [getWave(e) for e in expected]

        self.quickExpected = expected

        self.expectedGrid.Set(data=data,
                              backgroundColour=backgroundColour,
                              textColour=textColour)
        self.expectedGrid.AutoSizeColumns()
        self.expectedGrid.AutoSizeRows()

        if iBeforeLeader:
            Utils.SetLabel(
                self.expectedName,
                u'{}: {} {}'.format(_('Expected'), iBeforeLeader,
                                    _('before race leader')))
        else:
            Utils.SetLabel(self.expectedName,
                           _('Expected (click Bib to record entry)'))

        #------------------------------------------------------------------
        # Update recorded.
        recorded = self.quickRecorded = self.addGaps(recorded)

        backgroundColour = {}
        textColour = {}
        outsideTimeBound = set()
        # Highlight the leader in the recorded list.
        for r, e in enumerate(recorded):
            if e.isGap():
                for i in xrange(iColMax):
                    backgroundColour[(r, i)] = self.groupColour
            if prevRiderPosition.get(e.num, -1) == 1:
                backgroundColour[(r, iNoteCol)] = wx.GREEN
                if e.num == leaderPrev:
                    backgroundColour[(r, iNumCol)] = wx.GREEN
            elif tRace < tRaceLength and race.isOutsideTimeBound(e.num):
                backgroundColour[(r, iNoteCol)] = self.redColour
                textColour[(r, iNoteCol)] = wx.WHITE
                outsideTimeBound.add(e.num)

        data = [None] * iColMax
        data[iNumCol] = [
            u'{}{}'.format(e.num, u"\u25C0" if IsRiderFinished(e.num, e.t) else
                           u'') if e.num > 0 else u' ' for e in recorded
        ]
        data[iTimeCol] = [
            formatTime(e.t) if e.lap > 0 else
            (u'{}'.format(formatTimeGap(e.t)) if e.t is not None else u' ')
            if e.isGap() else u'[{}]'.format(formatTime(e.t)) for e in recorded
        ]
        data[iLapCol] = [
            u'{}'.format(e.lap) if e.lap else u' ' for e in recorded
        ]

        def getNoteHistory(e):
            if e.isGap():
                return u'{}'.format(e.groupCount)

            if e.lap == 0:
                return _('Start')

            position = nextRiderPosition.get(e.num, -1)
            if position == 1:
                return _('Lead')
            elif position >= 0:
                return Utils.ordinal(position)
            else:
                return ' '

        data[iNoteCol] = [getNoteHistory(e) for e in recorded]

        def getGapHistory(e):
            if e.lap == 0:
                return ' '
            return prevRiderGap.get(e.num, ' ')

        data[iGapCol] = [getGapHistory(e) for e in recorded]
        data[iNameCol] = [getName(e) for e in recorded]
        data[iWaveCol] = [getWave(e) for e in recorded]

        self.historyGrid.Set(data=data,
                             backgroundColour=backgroundColour,
                             textColour=textColour)
        self.historyGrid.AutoSizeColumns()
        self.historyGrid.AutoSizeRows()

        # Show the relevant cells in each table.
        if recorded:
            self.historyGrid.MakeCellVisible(len(recorded) - 1, 0)
        if iNotMissing < self.expectedGrid.GetNumberRows():
            self.expectedGrid.MakeCellVisible(iNotMissing, 0)
    def refresh(self):
        race = Model.race
        if race is None or not race.isRunning():
            self.quickExpected = None
            self.clearGrids()
            return

        try:
            externalInfo = race.excelLink.read(True)
        except:
            externalInfo = {}

        tRace = race.curRaceTime()
        tRaceLength = race.minutes * 60.0

        tMin = tRace - max(race.getAverageLapTime(), 10 * 60.0)
        tMax = tRace + max(race.getAverageLapTime(), 10 * 60.0)
        expected, recorded = getExpectedRecorded(tMin)

        isTimeTrial = race.isTimeTrial
        if isTimeTrial and expected:
            for e in expected:
                if e.lap == 0:
                    # Schedule a refresh later to update started riders.
                    milliSeconds = max(1, int((e.t - tRace) * 1000.0 + 10.0))
                    if self.callLaterRefresh is None:
                        self.callLaterRefresh = wx.CallLater(milliSeconds, self.refresh)
                    self.callLaterRefresh.Restart(milliSeconds)
                    break

                    # ------------------------------------------------------------------
                    # Highlight interpolated entries at race time.
        leaderPrev, leaderNext = race.getPrevNextLeader(tRace)
        averageLapTime = race.getAverageLapTime()
        backSecs = averageLapTime

        expectedShowMax = 80

        expected = expected[:expectedShowMax]

        prevCatLeaders, nextCatLeaders = race.getCatPrevNextLeaders(tRace)
        prevRiderPosition, nextRiderPosition = race.getPrevNextRiderPositions(tRace)
        prevRiderGap, nextRiderGap = race.getPrevNextRiderGaps(tRace)

        backgroundColour = {}
        textColour = {}
        # ------------------------------------------------------------------
        # Highlight the missing riders.
        tMissing = tRace - averageLapTime / 8.0
        iNotMissing = 0
        for r in (i for i, e in enumerate(expected) if e.t < tMissing):
            for c in xrange(iColMax):
                backgroundColour[(r, c)] = self.orangeColour
            iNotMissing = r + 1

            # ------------------------------------------------------------------
            # Highlight the leaders in the expected list.
        iBeforeLeader = None
        # Highlight the leader by category.
        catNextTime = {}
        outsideTimeBound = set()
        for r, e in enumerate(expected):
            if e.num in nextCatLeaders:
                backgroundColour[(r, iNoteCol)] = wx.GREEN
                catNextTime[nextCatLeaders[e.num]] = e.t
                if e.num == leaderNext:
                    backgroundColour[(r, iNumCol)] = wx.GREEN
                    iBeforeLeader = r
            elif tRace < tRaceLength and race.isOutsideTimeBound(e.num):
                backgroundColour[(r, iNoteCol)] = self.redColour
                textColour[(r, iNoteCol)] = wx.WHITE
                outsideTimeBound.add(e.num)

        data = [None] * iColMax
        data[iNumCol] = ["{}".format(e.num) for e in expected]
        getT = self.getETATimeFunc()
        data[iTimeCol] = [
            formatTime(getT(e) - tRace) if e.lap > 0 else ("[%s]" % formatTime(max(0.0, getT(e) - tRace + 0.99999999)))
            for e in expected
        ]
        data[iLapCol] = [u"{}".format(e.lap) if e.lap > 0 else u"" for e in expected]

        def getNoteExpected(e):
            if e.lap == 0:
                return _("Start")
            try:
                position = (
                    prevRiderPosition.get(e.num, -1)
                    if e.t < catNextTime[race.getCategory(e.num)]
                    else nextRiderPosition.get(e.num, -1)
                )
            except KeyError:
                position = prevRiderPosition.get(e.num, -1)

            if position == 1:
                return _("Lead")
            elif e.t < tMissing:
                return _("miss")
            elif position >= 0:
                return Utils.ordinal(position)
            else:
                return " "

        data[iNoteCol] = [getNoteExpected(e) for e in expected]

        def getGapExpected(e):
            try:
                gap = (
                    prevRiderGap.get(e.num, " ")
                    if e.t < catNextTime[race.getCategory(e.num)]
                    else nextRiderGap.get(e.num, " ")
                )
            except KeyError:
                gap = prevRiderGap.get(e.num, " ")
            return gap

        data[iGapCol] = [getGapExpected(e) for e in expected]

        def getName(e):
            info = externalInfo.get(e.num, {})
            last = info.get("LastName", "")
            first = info.get("FirstName", "")
            if last and first:
                return u"{}, {}".format(last, first)
            return last or first or u" "

        data[iNameCol] = [getName(e) for e in expected]

        def getWave(e):
            try:
                return race.getCategory(e.num).fullname
            except:
                return u" "

        data[iWaveCol] = [getWave(e) for e in expected]

        self.quickExpected = expected

        self.expectedGrid.Set(data=data, backgroundColour=backgroundColour, textColour=textColour)
        self.expectedGrid.AutoSizeColumns()
        self.expectedGrid.AutoSizeRows()

        if iBeforeLeader:
            Utils.SetLabel(
                self.expectedName, u"{}: {} {}".format(_("Expected"), iBeforeLeader, _("before race leader"))
            )
        else:
            Utils.SetLabel(self.expectedName, _("Expected"))

            # ------------------------------------------------------------------
            # Update recorded.
        recorded = self.quickRecorded = self.addGaps(recorded)

        backgroundColour = {}
        textColour = {}
        outsideTimeBound = set()
        # Highlight the leader in the recorded list.
        for r, e in enumerate(recorded):
            if e.isGap():
                for i in xrange(iColMax):
                    backgroundColour[(r, i)] = self.groupColour
            if prevRiderPosition.get(e.num, -1) == 1:
                backgroundColour[(r, iNoteCol)] = wx.GREEN
                if e.num == leaderPrev:
                    backgroundColour[(r, iNumCol)] = wx.GREEN
            elif tRace < tRaceLength and race.isOutsideTimeBound(e.num):
                backgroundColour[(r, iNoteCol)] = self.redColour
                textColour[(r, iNoteCol)] = wx.WHITE
                outsideTimeBound.add(e.num)

        data = [None] * iColMax
        data[iNumCol] = [u"{}".format(e.num) if e.num > 0 else u" " for e in recorded]
        data[iTimeCol] = [
            formatTime(e.t)
            if e.lap > 0
            else (u"{}".format(formatTimeGap(e.t)) if e.t is not None else u" ")
            if e.isGap()
            else u"[{}]".format(formatTime(e.t))
            for e in recorded
        ]
        data[iLapCol] = [u"{}".format(e.lap) if e.lap else u" " for e in recorded]

        def getNoteHistory(e):
            if e.isGap():
                return u"{}".format(e.groupCount)

            if e.lap == 0:
                return _("Start")

            position = nextRiderPosition.get(e.num, -1)
            if position == 1:
                return _("Lead")
            elif position >= 0:
                return Utils.ordinal(position)
            else:
                return " "

        data[iNoteCol] = [getNoteHistory(e) for e in recorded]

        def getGapHistory(e):
            if e.lap == 0:
                return " "
            return prevRiderGap.get(e.num, " ")

        data[iGapCol] = [getGapHistory(e) for e in recorded]
        data[iNameCol] = [getName(e) for e in recorded]
        data[iWaveCol] = [getWave(e) for e in recorded]

        self.historyGrid.Set(data=data, backgroundColour=backgroundColour, textColour=textColour)
        self.historyGrid.AutoSizeColumns()
        self.historyGrid.AutoSizeRows()

        # Show the relevant cells in each table.
        if recorded:
            self.historyGrid.MakeCellVisible(len(recorded) - 1, 0)
        if iNotMissing < self.expectedGrid.GetNumberRows():
            self.expectedGrid.MakeCellVisible(iNotMissing, 0)