/**
* Copyright (C) 2009-2014 Cars and Tracks Development Project (CTDP).
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
package net.ctdp.rfdynhud.widgets.standard.standings;
import java.awt.Font;
import java.io.IOException;
import java.util.Arrays;
import net.ctdp.rfdynhud.gamedata.FinishStatus;
import net.ctdp.rfdynhud.gamedata.GamePhase;
import net.ctdp.rfdynhud.gamedata.LiveGameData;
import net.ctdp.rfdynhud.gamedata.ProfileInfo.SpeedUnits;
import net.ctdp.rfdynhud.gamedata.ScoringInfo;
import net.ctdp.rfdynhud.gamedata.SessionType;
import net.ctdp.rfdynhud.gamedata.VehicleScoringInfo;
import net.ctdp.rfdynhud.input.InputAction;
import net.ctdp.rfdynhud.properties.BooleanProperty;
import net.ctdp.rfdynhud.properties.ColorProperty;
import net.ctdp.rfdynhud.properties.EnumProperty;
import net.ctdp.rfdynhud.properties.PropertiesContainer;
import net.ctdp.rfdynhud.properties.PropertyLoader;
import net.ctdp.rfdynhud.render.DrawnString;
import net.ctdp.rfdynhud.render.DrawnString.Alignment;
import net.ctdp.rfdynhud.render.DrawnStringFactory;
import net.ctdp.rfdynhud.render.TextureImage2D;
import net.ctdp.rfdynhud.util.NumberUtil;
import net.ctdp.rfdynhud.util.PropertyWriter;
import net.ctdp.rfdynhud.util.StandingsTools;
import net.ctdp.rfdynhud.util.SubTextureCollector;
import net.ctdp.rfdynhud.util.TimingUtil;
import net.ctdp.rfdynhud.valuemanagers.Clock;
import net.ctdp.rfdynhud.values.LongValue;
import net.ctdp.rfdynhud.values.NameDisplayType;
import net.ctdp.rfdynhud.values.StandingsView;
import net.ctdp.rfdynhud.widgets.base.widget.StatefulWidget;
import net.ctdp.rfdynhud.widgets.base.widget.Widget;
import net.ctdp.rfdynhud.widgets.standard._util.StandardWidgetSet;
/**
* The {@link StandingsWidget} displays engine information.
*
* @author Marvin Froehlich (CTDP)
*/
public class StandingsWidget extends StatefulWidget<Object, LocalStore>
{
private static final InputAction INPUT_ACTION_CYCLE_VIEW = new InputAction( "CycleStandingsViewAction" );
private final ColorProperty fontColor_me = new ColorProperty( "fontColor_me", "#367727" );
private final ColorProperty fontColor_out = new ColorProperty( "fontColor_out", "#646464" );
private final ColorProperty fontColor_finished = new ColorProperty( "fontColor_finished", "#00FF00" );
private final BooleanProperty useAutoWidth = new BooleanProperty( "useAutoWidth", false );
private final EnumProperty<StandingsView> initialView = new EnumProperty<StandingsView>( "initialView", StandingsView.RELATIVE_TO_ME )
{
@Override
protected void onValueChanged( StandingsView oldValue, StandingsView newValue )
{
getLocalStore().view = null;
}
};
private final BooleanProperty allowRelToLeaderView = new BooleanProperty( "allowRelToLeaderView", true );
private final BooleanProperty allowRelToMeView = new BooleanProperty( "allowRelToMeView", true );
private final BooleanProperty allowAbsTimesView = new BooleanProperty( "allowAbsTimesView", true );
private final BooleanProperty forceLeaderDisplayed = new BooleanProperty( "forceLeaderDisplayed", "forceLeaderDispl", true );
private final EnumProperty<NameDisplayType> nameDisplayType = new EnumProperty<NameDisplayType>( "nameDisplayType", NameDisplayType.FULL_NAME );
private final BooleanProperty showLapsOrStops = new BooleanProperty( "showLapsOrStops", true );
private final BooleanProperty abbreviate = new BooleanProperty( "abbreviate", false );
private final BooleanProperty showTopspeeds = new BooleanProperty( "showTopspeeds", true );
private final LongValue lastScoringUpdateId = new LongValue();
private DrawnString[] positionStrings = null;
private int maxDisplayedDrivers = 100;
private String[][] currPosStrings = null;
private final int[] oldColWidths = { 0, 0, 0, 0, 0, 0, 0 };
private final int[] colWidths = { 0, 0, 0, 0, 0, 0, 0 };
private final Alignment[] colAligns = { Alignment.RIGHT, Alignment.LEFT, Alignment.RIGHT, Alignment.RIGHT, Alignment.LEFT, Alignment.RIGHT, Alignment.LEFT };
private final int colPadding = 8;
//private int[] vsiIndices = null;
private int numVehicles = -1;
private int maxNumVehicles = -1;
private VehicleScoringInfo[] vehicleScoringInfos = null;
private int oldNumVehicles = -1;
private String[][] oldPosStrings = null;
private final float[] relTimes = new float[ 64 ];
public StandingsWidget()
{
super( StandardWidgetSet.INSTANCE, StandardWidgetSet.WIDGET_PACKAGE_TIMING, 36.328125f, 14.916667f );
getFontProperty().setFont( StandardWidgetSet.BIGGER_FONT.getKey() );
}
/**
* {@inheritDoc}
*/
@Override
public void prepareForMenuItem()
{
super.prepareForMenuItem();
getFontProperty().setFont( "Dialog", Font.PLAIN, 5, false, true );
}
/**
* {@inheritDoc}
*/
@Override
public void saveProperties( PropertyWriter writer ) throws IOException
{
super.saveProperties( writer );
writer.writeProperty( fontColor_me, "The font color used for myself in the format #RRGGBB (hex)." );
writer.writeProperty( fontColor_out, "The font color used for retired drivers in the format #RRGGBB (hex)." );
writer.writeProperty( fontColor_finished, "The font color used for finished drivers in the format #RRGGBB (hex)." );
writer.writeProperty( useAutoWidth, "Automatically compute and display the width?" );
writer.writeProperty( initialView, "the initial kind of standings view. Valid values: RELATIVE_TO_LEADER, RELATIVE_TO_ME." );
//writer.writeProperty( allowAbsTimesView, "" );
//writer.writeProperty( allowRelToLeaderView, "" );
//writer.writeProperty( allowRelToMeView, "" );
writer.writeProperty( forceLeaderDisplayed, "Display leader regardless of maximum displayed drivers setting?" );
writer.writeProperty( nameDisplayType, "How to display driver names." );
writer.writeProperty( showLapsOrStops, "Whether to show the number of laps or stops done or not." );
writer.writeProperty( abbreviate, "Whether to abbreviate \"Stops\", or not." );
writer.writeProperty( showTopspeeds, "Whether to show a topspeeds column or not." );
}
/**
* {@inheritDoc}
*/
@Override
public void loadProperty( PropertyLoader loader )
{
super.loadProperty( loader );
if ( loader.loadProperty( fontColor_me ) );
else if ( loader.loadProperty( fontColor_out ) );
else if ( loader.loadProperty( fontColor_finished ) );
else if ( loader.loadProperty( useAutoWidth ) );
else if ( loader.loadProperty( initialView ) );
//else if ( loader.loadProperty( allowAbsTimesView ) );
//else if ( loader.loadProperty( allowRelToLeaderView ) );
//else if ( loader.loadProperty( allowRelToMeView ) );
else if ( loader.loadProperty( forceLeaderDisplayed ) );
else if ( loader.loadProperty( nameDisplayType ) );
else if ( loader.loadProperty( showLapsOrStops ) );
else if ( loader.loadProperty( abbreviate ) );
else if ( loader.loadProperty( showTopspeeds ) );
}
/**
* {@inheritDoc}
*/
@Override
protected void addPositionAndSizePropertiesToContainer( PropertiesContainer propsCont, boolean forceAll )
{
super.addPositionAndSizePropertiesToContainer( propsCont, forceAll );
propsCont.addProperty( useAutoWidth );
}
/**
* {@inheritDoc}
*/
@Override
public void getProperties( PropertiesContainer propsCont, boolean forceAll )
{
super.getProperties( propsCont, forceAll );
propsCont.addGroup( "Misc" );
propsCont.addProperty( fontColor_me );
propsCont.addProperty( fontColor_out );
propsCont.addProperty( fontColor_finished );
propsCont.addProperty( initialView );
//propsCont.addProperty( allowAbsTimesView );
//propsCont.addProperty( allowRelToLeaderView );
//propsCont.addProperty( allowRelToMeView );
propsCont.addProperty( forceLeaderDisplayed );
propsCont.addProperty( nameDisplayType );
propsCont.addProperty( showLapsOrStops );
if ( forceAll || showLapsOrStops.getBooleanValue() )
propsCont.addProperty( abbreviate );
propsCont.addProperty( showTopspeeds );
}
/**
* {@inheritDoc}
*/
@Override
public Object createGeneralStore()
{
return ( null );
}
/**
* {@inheritDoc}
*/
@Override
protected LocalStore createLocalStore()
{
return ( new LocalStore() );
}
/**
* {@inheritDoc}
*/
@Override
public InputAction[] getInputActions()
{
return ( new InputAction[] { INPUT_ACTION_CYCLE_VIEW } );
}
/**
* {@inheritDoc}
*/
@Override
public int getNeededData()
{
return ( Widget.NEEDED_DATA_SCORING );
}
private static final String[][] ensureCapacity( String[][] array, int minCapacity, boolean preserveValues )
{
if ( array == null )
{
array = new String[ minCapacity ][];
}
else if ( array.length < minCapacity )
{
String[][] tmp = new String[ minCapacity ][];
if ( preserveValues )
System.arraycopy( array, 0, tmp, 0, array.length );
array = tmp;
}
return ( array );
}
private final boolean getUseClassScoring()
{
return ( getConfiguration().getUseClassScoring() );
}
public void setView( StandingsView view )
{
getLocalStore().view = view;
lastScoringUpdateId.reset( true );
forceCompleteRedraw( false );
setDirtyFlag();
}
public final StandingsView getView()
{
if ( getLocalStore().view == null )
{
getLocalStore().view = initialView.getEnumValue();
}
return ( getLocalStore().view );
}
public void allowViews( boolean relToLeader, boolean relToMe, boolean absTimes )
{
this.allowRelToLeaderView.setBooleanValue( relToLeader );
this.allowRelToMeView.setBooleanValue( relToMe );
this.allowAbsTimesView.setBooleanValue( absTimes );
}
public final boolean isRelToLeaderViewAllowed()
{
return ( allowRelToLeaderView.getBooleanValue() );
}
public final boolean isRelToMeViewAllowed()
{
return ( allowRelToMeView.getBooleanValue() );
}
public final boolean isAbsTimesViewAllowed( boolean isEditorMode, SessionType sessionType )
{
return ( ( isEditorMode || !sessionType.isRace() ) && allowAbsTimesView.getBooleanValue() );
}
private final boolean checkView( boolean isEditorMode, StandingsView view, SessionType sessionType )
{
switch ( view )
{
case RELATIVE_TO_LEADER:
return ( isRelToLeaderViewAllowed() );
case RELATIVE_TO_ME:
return ( isRelToMeViewAllowed() );
case ABSOLUTE_TIMES:
return ( isAbsTimesViewAllowed( isEditorMode, sessionType ) );
}
// Unreachable code!
return ( true );
}
private StandingsView cycleView( boolean isEditorMode, SessionType sessionType, boolean includeVisibility )
{
final StandingsView[] views = StandingsView.values();
if ( includeVisibility )
{
//StandingsView oldView = getView();
if ( !getInputVisibility() )
{
for ( int i = 0; i < views.length; i++ )
{
if ( checkView( isEditorMode, views[i], sessionType ) )
{
setView( views[i] );
return ( getView() );
}
}
return ( getView() );
}
int v0 = getView().ordinal();
for ( int i = 1; i < views.length + 1; i++ )
{
int i_ = ( v0 + i ) % ( views.length + 1 );
if ( i_ == views.length )
{
return ( null );
}
StandingsView sv = views[i_];
if ( checkView( isEditorMode, sv, sessionType ) )
{
setView( sv );
return ( getView() );
}
}
return ( null );
}
int offset = getView().ordinal();
for ( int i = 1; i < views.length; i++ )
{
if ( checkView( isEditorMode, views[( offset + i ) % views.length], sessionType ) )
{
setView( views[( offset + i ) % views.length] );
return ( getView() );
}
}
return ( getView() );
}
private void resetArrays( LiveGameData gameData, boolean isEditorMode )
{
if ( oldPosStrings != null )
Arrays.fill( oldPosStrings, null );
Arrays.fill( oldColWidths, -1 );
this.oldNumVehicles = -1;
if ( isEditorMode )
this.maxNumVehicles = 23;
else
this.maxNumVehicles = Math.min( gameData.getModInfo().getMaxOpponents() + 1, 256 );
}
/**
* {@inheritDoc}
*/
@Override
public void onCockpitEntered( LiveGameData gameData, boolean isEditorMode )
{
super.onCockpitEntered( gameData, isEditorMode );
lastScoringUpdateId.reset();
SessionType sessionType = gameData.getScoringInfo().getSessionType();
if ( !isEditorMode && sessionType.isRace() && ( getView() == StandingsView.ABSOLUTE_TIMES ) )
cycleView( false, sessionType, false );
resetArrays( gameData, isEditorMode );
}
/**
* {@inheritDoc}
*/
@Override
protected Boolean onVehicleControlChanged( VehicleScoringInfo viewedVSI, LiveGameData gameData, boolean isEditorMode )
{
Boolean result = super.onVehicleControlChanged( viewedVSI, gameData, isEditorMode );
lastScoringUpdateId.reset();
forceCompleteRedraw( false );
return ( result );
}
/**
* {@inheritDoc}
*/
@Override
protected Boolean onBoundInputStateChanged( InputAction action, boolean state, int modifierMask, long when, LiveGameData gameData, boolean isEditorMode )
{
if ( action == INPUT_ACTION_CYCLE_VIEW )
{
return ( cycleView( isEditorMode, gameData.getScoringInfo().getSessionType(), true ) != null );
}
return ( null );
}
/**
* {@inheritDoc}
*/
@Override
protected void initSubTextures( LiveGameData gameData, boolean isEditorMode, int widgetInnerWidth, int widgetInnerHeight, SubTextureCollector collector )
{
}
private final String getDisplayedDriverName( VehicleScoringInfo vsi )
{
switch ( nameDisplayType.getEnumValue() )
{
case FULL_NAME:
return ( vsi.getDriverName() );
case SHORT_FORM:
return ( vsi.getDriverNameShort() );
case THREE_LETTER_CODE:
return ( vsi.getDriverNameTLC() );
}
// Unreachable code!
return ( null );
}
private static final String getSpeedUnits( SpeedUnits speedUnits )
{
if ( speedUnits == SpeedUnits.MIH )
return ( Loc.column_topspeed_units_IMPERIAL );
return ( Loc.column_topspeed_units_METRIC );
}
private String[] getPositionStringRaceRelToLeader( GamePhase gamePhase, VehicleScoringInfo vsi, SpeedUnits speedUnits )
{
String[] ss = new String[ 7 ];
ss[0] = vsi.getPlace( getUseClassScoring() ) + ".";
ss[1] = getDisplayedDriverName( vsi );
FinishStatus finishStatus = vsi.getFinishStatus();
if ( finishStatus.isNone() )
{
if ( ( gamePhase == GamePhase.RECONNAISSANCE_LAPS ) || ( gamePhase == GamePhase.FORMATION_LAP ) )
{
ss[2] = null;
}
else
{
int lbl = vsi.getLapsBehindLeader( getUseClassScoring() );
if ( lbl > 0 )
{
ss[2] = "(+" + lbl + ( abbreviate.getBooleanValue() ? Loc.column_time_gap_laps_short : ( ( lbl == 1 ) ? " " + Loc.column_time_gap_laps_singular + ")" : " " + Loc.column_time_gap_laps_plural + ")" ) );
}
else
{
float sbl = -vsi.getTimeBehindLeader( getUseClassScoring() );
ss[2] = "(" + TimingUtil.getTimeAsGapString( sbl ) + ")";
}
}
if ( showLapsOrStops.getBooleanValue() )
{
int stops = vsi.getNumPitstopsMade();
if ( abbreviate.getBooleanValue() )
{
ss[3] = String.valueOf( stops ) + Loc.column_stops_short;
ss[4] = null;
}
else if ( stops == 1 )
{
ss[3] = String.valueOf( stops );
ss[4] = Loc.column_stops_singular;
}
else
{
ss[3] = String.valueOf( stops );
ss[4] = Loc.column_stops_plural;
}
}
else
{
ss[3] = null;
ss[4] = null;
}
if ( showTopspeeds.getBooleanValue() )
{
ss[5] = NumberUtil.formatFloat( vsi.getTopspeed(), 1, true );
ss[6] = getSpeedUnits( speedUnits );
}
else
{
ss[5] = null;
ss[6] = null;
}
}
else if ( finishStatus == FinishStatus.FINISHED )
{
ss[2] = "(" + Loc.finishsstatus_FINISHED + ")";
ss[3] = null;
ss[4] = null;
ss[5] = null;
ss[6] = null;
}
else
{
switch ( finishStatus )
{
case DNF:
ss[2] = Loc.out + " (" + Loc.finishsstatus_DNF + ")";
break;
case DQ:
ss[2] = Loc.out + " (" + Loc.finishsstatus_DQ + ")";
break;
}
ss[3] = null;
ss[4] = null;
ss[5] = null;
ss[6] = null;
}
return ( ss );
}
private String[] getPositionStringRaceRelToMe( int ownPlace, int ownLaps, float ownLapDistance, float relTime, GamePhase gamePhase, VehicleScoringInfo vsi, SpeedUnits speedUnits )
{
String[] ss = new String[ 7 ];
ss[0] = vsi.getPlace( getUseClassScoring() ) + ".";
ss[1] = getDisplayedDriverName( vsi );
FinishStatus finishStatus = vsi.getFinishStatus();
if ( finishStatus.isNone() )
{
if ( ( gamePhase != GamePhase.FORMATION_LAP ) && ( gamePhase != GamePhase.RECONNAISSANCE_LAPS ) && ( vsi.getPlace( getUseClassScoring() ) != ownPlace ) )
{
int lapDiff = ownLaps - vsi.getLapsCompleted();
if ( ( lapDiff > 0 ) && ( ownLapDistance < vsi.getLapDistance() ) )
lapDiff--;
else if ( ( lapDiff < 0 ) && ( ownLapDistance > vsi.getLapDistance() ) )
lapDiff++;
if ( lapDiff < 0 )
{
if ( abbreviate.getBooleanValue() )
ss[2] = "(" + lapDiff + Loc.column_time_gap_laps_short + ")";
else
ss[2] = "(" + lapDiff + " " + ( ( lapDiff < -1 ) ? Loc.column_time_gap_laps_plural : Loc.column_time_gap_laps_singular ) + ")";
}
else if ( lapDiff > 0 )
{
if ( abbreviate.getBooleanValue() )
ss[2] = "(+" + lapDiff + Loc.column_time_gap_laps_short + ")";
else
ss[2] = "(+" + lapDiff + " " + ( ( lapDiff > 1 ) ? Loc.column_time_gap_laps_plural : Loc.column_time_gap_laps_singular ) + ")";
}
else
{
ss[2] = "(" + TimingUtil.getTimeAsGapString( relTime ) + ")";
}
}
else
{
ss[2] = null;
}
if ( showLapsOrStops.getBooleanValue() )
{
int stops = vsi.getNumPitstopsMade();
if ( abbreviate.getBooleanValue() )
{
ss[3] = String.valueOf( stops ) + Loc.column_stops_short;
ss[4] = null;
}
else if ( stops == 1 )
{
ss[3] = String.valueOf( stops );
ss[4] = Loc.column_stops_singular;
}
else
{
ss[3] = String.valueOf( stops );
ss[4] = Loc.column_stops_plural;
}
}
else
{
ss[3] = null;
ss[4] = null;
}
if ( showTopspeeds.getBooleanValue() )
{
ss[5] = NumberUtil.formatFloat( vsi.getTopspeed(), 1, true );
ss[6] = getSpeedUnits( speedUnits );
}
else
{
ss[5] = null;
ss[6] = null;
}
}
else if ( finishStatus == FinishStatus.FINISHED )
{
ss[2] = "(" + Loc.finishsstatus_FINISHED + ")";
ss[3] = null;
ss[4] = null;
ss[5] = null;
ss[6] = null;
}
else
{
switch ( finishStatus )
{
case DNF:
ss[2] = Loc.out + " (" + Loc.finishsstatus_DNF + ")";
break;
case DQ:
ss[2] = Loc.out + " (" + Loc.finishsstatus_DQ + ")";
break;
}
ss[3] = null;
ss[4] = null;
ss[5] = null;
ss[6] = null;
}
return ( ss );
}
private String[] getPosStringRace( int ownPlace, int ownLaps, float ownLapDistance, GamePhase gamePhase, VehicleScoringInfo vsi, SpeedUnits speedUnits )
{
switch ( getView() )
{
case RELATIVE_TO_LEADER:
return ( getPositionStringRaceRelToLeader( gamePhase, vsi, speedUnits ) );
case RELATIVE_TO_ME:
return ( getPositionStringRaceRelToMe( ownPlace, ownLaps, ownLapDistance, relTimes[vsi.getPlace( false ) - 1], gamePhase, vsi, speedUnits ) );
case ABSOLUTE_TIMES: // Only possible in the editor!
return ( getPositionStringNonRaceAbsTimes( vsi, speedUnits ) );
}
// unreachable code
return ( null );
}
private int initPosStringsRace( LiveGameData gameData )
{
final ScoringInfo scoringInfo = gameData.getScoringInfo();
SpeedUnits speedUnits = gameData.getProfileInfo().getSpeedUnits();
GamePhase gamePhase = scoringInfo.getGamePhase();
VehicleScoringInfo myVSI = scoringInfo.getViewedVehicleScoringInfo();
numVehicles = StandingsTools.getDisplayedVSIsForScoring( scoringInfo, myVSI, getUseClassScoring(), getView(), forceLeaderDisplayed.getBooleanValue(), vehicleScoringInfos );
int ownPlace = myVSI.getPlace( getUseClassScoring() );
int ownLaps = myVSI.getLapsCompleted();
float ownLapDistance = myVSI.getLapDistance();
if ( getView() == StandingsView.RELATIVE_TO_ME )
{
StandingsTools.computeRaceGapsRelativeToPosition( scoringInfo, myVSI, relTimes );
}
currPosStrings = ensureCapacity( currPosStrings, numVehicles, false );
for ( int i = 0; i < numVehicles; i++ )
{
currPosStrings[i] = getPosStringRace( ownPlace, ownLaps, ownLapDistance, gamePhase, vehicleScoringInfos[i], speedUnits );
}
return ( numVehicles );
}
private String[] getPositionStringNonRaceRelToLeader( int firstVisiblePlace, float bestTime, VehicleScoringInfo vsi, SpeedUnits speedUnits )
{
String[] ss = new String[ 7 ];
ss[0] = vsi.getPlace( getUseClassScoring() ) + ".";
ss[1] = getDisplayedDriverName( vsi );
float t = vsi.getBestLapTime();
if ( t > 0f )
{
if ( vsi.getPlace( getUseClassScoring() ) == firstVisiblePlace )
ss[2] = "(" + TimingUtil.getTimeAsLaptimeString( t ) + ")";
else
ss[2] = "(" + TimingUtil.getTimeAsGapString( t - bestTime ) + ")";
}
else
{
ss[2] = null;
}
if ( showLapsOrStops.getBooleanValue() )
{
int lapsCompleted = vsi.getLapsCompleted();
if ( abbreviate.getBooleanValue() )
{
ss[3] = String.valueOf( lapsCompleted ) + Loc.column_laps_short;
ss[4] = null;
}
else if ( lapsCompleted == 1 )
{
ss[3] = String.valueOf( lapsCompleted );
ss[4] = Loc.column_laps_singular;
}
else
{
ss[3] = String.valueOf( lapsCompleted );
ss[4] = Loc.column_laps_plural;
}
}
else
{
ss[3] = null;
ss[4] = null;
}
if ( showTopspeeds.getBooleanValue() )
{
ss[5] = NumberUtil.formatFloat( vsi.getTopspeed(), 1, true );
ss[6] = getSpeedUnits( speedUnits );
}
else
{
ss[5] = null;
ss[6] = null;
}
return ( ss );
}
private String[] getPositionStringNonRaceRelToMe( int ownPlace, float ownTime, VehicleScoringInfo vsi, SpeedUnits speedUnits )
{
String[] ss = new String[ 7 ];
ss[0] = vsi.getPlace( getUseClassScoring() ) + ".";
ss[1] = getDisplayedDriverName( vsi );
float t = vsi.getBestLapTime();
if ( t > 0f )
{
if ( vsi.getPlace( getUseClassScoring() ) == ownPlace )
ss[2] = "(" + TimingUtil.getTimeAsLaptimeString( t ) + ")";
else
ss[2] = "(" + TimingUtil.getTimeAsGapString( t - ownTime ) + ")";
}
else
{
ss[2] = null;
}
if ( showLapsOrStops.getBooleanValue() )
{
int lapsCompleted = vsi.getLapsCompleted();
if ( abbreviate.getBooleanValue() )
{
ss[3] = String.valueOf( lapsCompleted ) + Loc.column_laps_short;
ss[4] = null;
}
else if ( lapsCompleted == 1 )
{
ss[3] = String.valueOf( lapsCompleted );
ss[4] = Loc.column_laps_singular;
}
else
{
ss[3] = String.valueOf( lapsCompleted );
ss[4] = Loc.column_laps_plural;
}
}
else
{
ss[3] = null;
ss[4] = null;
}
if ( showTopspeeds.getBooleanValue() )
{
ss[5] = NumberUtil.formatFloat( vsi.getTopspeed(), 1, true );
ss[6] = getSpeedUnits( speedUnits );
}
else
{
ss[5] = null;
ss[6] = null;
}
return ( ss );
}
private String[] getPositionStringNonRaceAbsTimes( VehicleScoringInfo vsi, SpeedUnits speedUnits )
{
String[] ss = new String[ 7 ];
ss[0] = vsi.getPlace( getUseClassScoring() ) + ".";
ss[1] = getDisplayedDriverName( vsi );
float t = vsi.getBestLapTime();
if ( t > 0f )
{
ss[2] = TimingUtil.getTimeAsLaptimeString( t );
}
else
{
ss[2] = null;
}
if ( showLapsOrStops.getBooleanValue() )
{
int lapsCompleted = vsi.getLapsCompleted();
if ( abbreviate.getBooleanValue() )
{
ss[3] = String.valueOf( lapsCompleted ) + Loc.column_laps_short;
ss[4] = null;
}
else if ( lapsCompleted == 1 )
{
ss[3] = String.valueOf( lapsCompleted );
ss[4] = Loc.column_laps_singular;
}
else
{
ss[3] = String.valueOf( lapsCompleted );
ss[4] = Loc.column_laps_plural;
}
}
else
{
ss[3] = null;
ss[4] = null;
}
if ( showTopspeeds.getBooleanValue() )
{
ss[5] = NumberUtil.formatFloat( vsi.getTopspeed(), 1, true );
ss[6] = getSpeedUnits( speedUnits );
}
else
{
ss[5] = null;
ss[6] = null;
}
return ( ss );
}
private String[] getPosStringNonRace( VehicleScoringInfo vsi, int firstVisiblePlace, int ownPlace, float ownTime, float bestTime, SpeedUnits speedUnits )
{
if ( getView() == StandingsView.ABSOLUTE_TIMES )
return ( getPositionStringNonRaceAbsTimes( vsi, speedUnits ) );
if ( ( getView() == StandingsView.RELATIVE_TO_LEADER ) || ( ownTime <= 0f ) )
return ( getPositionStringNonRaceRelToLeader( firstVisiblePlace, bestTime, vsi, speedUnits ) );
return ( getPositionStringNonRaceRelToMe( ownPlace, ownTime, vsi, speedUnits ) );
}
private int initPosStringsNonRace( LiveGameData gameData )
{
final ScoringInfo scoringInfo = gameData.getScoringInfo();
SpeedUnits speedUnits = gameData.getProfileInfo().getSpeedUnits();
VehicleScoringInfo ownVSI = scoringInfo.getViewedVehicleScoringInfo();
numVehicles = StandingsTools.getDisplayedVSIsForScoring( scoringInfo, ownVSI, getUseClassScoring(), getView(), forceLeaderDisplayed.getBooleanValue(), vehicleScoringInfos );
int firstVisiblePlace = vehicleScoringInfos[0].getPlace( getUseClassScoring() );
float bestTime = vehicleScoringInfos[0].getBestLapTime();
int ownPlace = ownVSI.getPlace( getUseClassScoring() );
float ownTime = ownVSI.getBestLapTime();
currPosStrings = ensureCapacity( currPosStrings, numVehicles, false );
for ( int i = 0; i < numVehicles; i++ )
{
VehicleScoringInfo vsi = vehicleScoringInfos[i];
currPosStrings[i] = getPosStringNonRace( vsi, firstVisiblePlace, ownPlace, ownTime, bestTime, speedUnits );
}
return ( numVehicles );
}
/**
* {@inheritDoc}
*/
@Override
public int getMaxWidth( LiveGameData gameData, boolean isEditorMode )
{
if ( !useAutoWidth.getBooleanValue() )
return ( super.getMaxWidth( gameData, isEditorMode ) );
DrawnString ds = getDrawnStringFactory().newDrawnString( null, 10, 0, Alignment.LEFT, false, getFont(), isFontAntiAliased(), getFontColor() );
String[] strs = { "99.", ( nameDisplayType.getEnumValue() == NameDisplayType.THREE_LETTER_CODE ) ? "AAAA" : ( ( nameDisplayType.getEnumValue() == NameDisplayType.SHORT_FORM ) ? "G. Fisichella___" : "Giancarlo Fisichella___" ), "-1:99:99.999", "99" + ( abbreviate.getBooleanValue() ? "S" : " Stops" ) };
int total = ds.getMinColWidths( strs, colAligns, colPadding, colWidths );
return ( total );
}
private void initPositionStrings( boolean isEditorMode, DrawnStringFactory dsf )
{
if ( isEditorMode || ( positionStrings == null ) || ( positionStrings.length < maxNumVehicles ) )
{
positionStrings = new DrawnString[ maxNumVehicles ];
for ( int i = 0; i < maxNumVehicles; i++ )
{
if ( i == 0 )
positionStrings[i] = dsf.newDrawnString( "positionStrings" + i, 0, 0, Alignment.LEFT, false, getFont(), isFontAntiAliased(), getFontColor() );
else
positionStrings[i] = dsf.newDrawnString( "positionStrings" + i, null, positionStrings[i - 1], 0, 0, Alignment.LEFT, false, getFont(), isFontAntiAliased(), getFontColor() );
}
}
}
private int updateColumnWidths( int widgetWidth )
{
Arrays.fill( colWidths, 0 );
int minWidth = 0;
for ( int i = 0; i < numVehicles; i++ )
{
int w = positionStrings[i].getMaxColWidths( currPosStrings[i], colAligns, colPadding, colWidths );
if ( w > minWidth )
minWidth = w;
}
if ( colWidths[5] > 0 )
{
colWidths[5] += 15;
minWidth += 15;
}
if ( !useAutoWidth.getBooleanValue() )
{
if ( minWidth < widgetWidth )
colWidths[1] += widgetWidth - minWidth;
}
return ( minWidth );
}
/**
* {@inheritDoc}
*/
@Override
protected void initialize( LiveGameData gameData, boolean isEditorMode, DrawnStringFactory dsf, TextureImage2D texture, int width, int height )
{
resetArrays( gameData, isEditorMode );
lastScoringUpdateId.reset( true );
initPositionStrings( isEditorMode, dsf );
int h = height + getBorder().getInnerBottomHeight() - getBorder().getOpaqueBottomHeight();
int rowHeight = positionStrings[0].calcMaxHeight( false );
maxDisplayedDrivers = Math.max( 1, h / rowHeight );
vehicleScoringInfos = new VehicleScoringInfo[ maxDisplayedDrivers ];
numVehicles = -1;
oldPosStrings = null;
currPosStrings = null;
}
/**
* {@inheritDoc}
*/
@Override
protected boolean checkForChanges( LiveGameData gameData, boolean isEditorMode, TextureImage2D texture, int width, int height )
{
final ScoringInfo scoringInfo = gameData.getScoringInfo();
lastScoringUpdateId.update( scoringInfo.getUpdateId() );
if ( !lastScoringUpdateId.hasChanged() )
return ( false );
if ( gameData.getScoringInfo().getSessionType().isRace() )
initPosStringsRace( gameData );
else
initPosStringsNonRace( gameData );
boolean result = false;
if ( oldNumVehicles != numVehicles )
{
oldNumVehicles = numVehicles;
result = true;
}
int minWidth = updateColumnWidths( width );
if ( !useAutoWidth.getBooleanValue() )
return ( result );
//int padding = 2 * 8;
int padding = 0;
minWidth += padding;
if ( ( isEditorMode && ( Math.abs( ( width + padding ) - minWidth ) > 1 ) ) || ( width + padding != minWidth ) )
{
getSize().setEffectiveSize( getBorder().getWidgetWidth( minWidth ), getBorder().getWidgetHeight( height ) );
result = true;
}
return ( result );
}
@Override
protected void drawWidget( Clock clock, boolean needsCompleteRedraw, LiveGameData gameData, boolean isEditorMode, TextureImage2D texture, int offsetX, int offsetY, int width, int height )
{
final boolean clock2 = clock.c3();
oldPosStrings = ensureCapacity( oldPosStrings, numVehicles, true );
if ( clock2 && !Arrays.equals( oldColWidths, colWidths ) )
{
needsCompleteRedraw = true;
System.arraycopy( colWidths, 0, oldColWidths, 0, colWidths.length );
}
VehicleScoringInfo viewedVSI = gameData.getScoringInfo().getViewedVehicleScoringInfo();
for ( int i = 0; i < numVehicles; i++ )
{
if ( needsCompleteRedraw || ( clock2 && !Arrays.equals( currPosStrings[i], oldPosStrings[i] ) ) )
{
java.awt.Color fc = null;
switch ( vehicleScoringInfos[i].getFinishStatus() )
{
case NONE:
if ( vehicleScoringInfos[i].equals( viewedVSI ) )
fc = fontColor_me.getColor();
break;
case DNF:
case DQ:
fc = fontColor_out.getColor();
break;
case FINISHED:
fc = fontColor_finished.getColor();
break;
}
positionStrings[i].drawColumns( offsetX, offsetY, currPosStrings[i], colAligns, colPadding, colWidths, fc, texture );
oldPosStrings[i] = currPosStrings[i];
}
}
}
}