/** * 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.timecomp; import java.awt.Font; import java.io.IOException; import java.util.Arrays; import net.ctdp.rfdynhud.gamedata.Laptime; import net.ctdp.rfdynhud.gamedata.LiveGameData; import net.ctdp.rfdynhud.gamedata.SessionType; import net.ctdp.rfdynhud.gamedata.VehicleScoringInfo; import net.ctdp.rfdynhud.properties.BooleanProperty; 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.PropertyWriter; import net.ctdp.rfdynhud.util.SubTextureCollector; import net.ctdp.rfdynhud.util.TimingUtil; import net.ctdp.rfdynhud.valuemanagers.Clock; import net.ctdp.rfdynhud.values.IntValue; import net.ctdp.rfdynhud.widgets.base.widget.StatefulWidget; import net.ctdp.rfdynhud.widgets.standard._util.StandardWidgetSet; /** * The {@link TimeCompareWidget} displays lap- and sector times of the last few laps to compare them. * * @author Marvin Froehlich (CTDP) */ public class TimeCompareWidget extends StatefulWidget<Object, LocalStore> { private final BooleanProperty abbreviate = new BooleanProperty( "abbreviate", false ); private final BooleanProperty displaySectors = new BooleanProperty( "displaySectors", true ); private DrawnString headerString = null; private DrawnString[] timeStrings = null; private final IntValue lap = new IntValue(); private int numDisplayedLaps = 0; private static final Alignment[] colAligns = { Alignment.RIGHT, Alignment.RIGHT, Alignment.RIGHT, Alignment.RIGHT, Alignment.RIGHT }; private int colPadding = 10; private int[] colWidths = null; public TimeCompareWidget() { super( StandardWidgetSet.INSTANCE, StandardWidgetSet.WIDGET_PACKAGE_TIMING, 24.0625f, 13.916667f ); } @Override public void prepareForMenuItem() { super.prepareForMenuItem(); colPadding = 4; getFontProperty().setFont( "Dialog", Font.PLAIN, 3, false, true ); } /** * {@inheritDoc} */ @Override public void saveProperties( PropertyWriter writer ) throws IOException { super.saveProperties( writer ); writer.writeProperty( abbreviate, "Whether to abbreviate \"Sector\" to \"Sec\", or not." ); writer.writeProperty( displaySectors, "Display sector times?" ); } /** * {@inheritDoc} */ @Override public void loadProperty( PropertyLoader loader ) { super.loadProperty( loader ); if ( loader.loadProperty( abbreviate ) ); else if ( loader.loadProperty( displaySectors ) ); } /** * {@inheritDoc} */ @Override public void getProperties( PropertiesContainer propsCont, boolean forceAll ) { super.getProperties( propsCont, forceAll ); propsCont.addGroup( "Misc" ); propsCont.addProperty( abbreviate ); propsCont.addProperty( displaySectors ); } /** * {@inheritDoc} */ @Override public Object createGeneralStore() { return ( null ); } /** * {@inheritDoc} */ @Override protected LocalStore createLocalStore() { return ( new LocalStore() ); } /** * {@inheritDoc} */ @Override public void onSessionStarted( SessionType sessionType, LiveGameData gameData, boolean isEditorMode ) { super.onSessionStarted( sessionType, gameData, isEditorMode ); lap.reset(); } private void updateLaps( VehicleScoringInfo vsi ) { LocalStore store = getLocalStore(); int lap = vsi.getCurrentLap(); int n = 0; for ( int i = lap - 1; i >= 1 && n < numDisplayedLaps; i-- ) { Laptime lt = vsi.getLaptime( i ); if ( ( lt != null ) && ( lt.isInlap() != Boolean.TRUE ) && ( lt.isOutlap() != Boolean.TRUE ) && ( lt.getLapTime() > 0f ) ) { store.displayedLaps[numDisplayedLaps - n - 1] = lt; n++; } } if ( n < numDisplayedLaps ) { System.arraycopy( store.displayedLaps, numDisplayedLaps - n, store.displayedLaps, 0, n ); for ( int i = n; i < numDisplayedLaps; i++ ) store.displayedLaps[i] = null; } } /** * {@inheritDoc} */ @Override public void onLapStarted( VehicleScoringInfo vsi, LiveGameData gameData, boolean isEditorMode ) { super.onLapStarted( vsi, gameData, isEditorMode ); if ( vsi == gameData.getScoringInfo().getViewedVehicleScoringInfo() ) { updateLaps( vsi ); } } /** * {@inheritDoc} */ @Override protected Boolean onVehicleControlChanged( VehicleScoringInfo viewedVSI, LiveGameData gameData, boolean isEditorMode ) { Boolean result = super.onVehicleControlChanged( viewedVSI, gameData, isEditorMode ); updateLaps( viewedVSI ); forceCompleteRedraw( false ); return ( result ); } /** * {@inheritDoc} */ @Override protected void initSubTextures( LiveGameData gameData, boolean isEditorMode, int widgetInnerWidth, int widgetInnerHeight, SubTextureCollector collector ) { } /** * {@inheritDoc} */ @Override protected void initialize( LiveGameData gameData, boolean isEditorMode, DrawnStringFactory dsf, TextureImage2D texture, int width, int height ) { final java.awt.Font font = getFont(); final boolean fontAntiAliased = isFontAntiAliased(); final java.awt.Color fontColor = getFontColor(); LocalStore store = getLocalStore(); headerString = dsf.newDrawnString( "headerString", 0, 0, Alignment.LEFT, false, font, fontAntiAliased, fontColor ); int h = height + getBorder().getInnerBottomHeight() - getBorder().getOpaqueBottomHeight(); int rowHeight = headerString.calcMaxHeight( false ); numDisplayedLaps = Math.max( 1, ( h - rowHeight - rowHeight - 5 ) / rowHeight ); store.displayedLaps = new Laptime[ numDisplayedLaps ]; int numStrings = displaySectors.getBooleanValue() ? 5 : 2; this.colWidths = new int[ numStrings ]; this.timeStrings = new DrawnString[ numDisplayedLaps + 1 ]; DrawnString relY = headerString; for ( int j = 0; j < numDisplayedLaps; j++ ) { timeStrings[j] = dsf.newDrawnString( "timeStrings" + j, null, relY, 0, 0, Alignment.LEFT, false, font, fontAntiAliased, fontColor ); relY = timeStrings[j]; } timeStrings[numDisplayedLaps] = dsf.newDrawnString( "timeStrings" + numDisplayedLaps, null, relY, 0, 5, Alignment.LEFT, false, font, fontAntiAliased, fontColor ); Arrays.fill( colWidths, 0 ); int totalWidth; if ( displaySectors.getBooleanValue() ) { if ( abbreviate.getBooleanValue() ) headerString.getMaxColWidths( new String[] { Loc.header_lap_number, Loc.header_sector1_short, Loc.header_sector2_short, Loc.header_sector3_short, Loc.header_lap_short }, colAligns, colPadding, colWidths ); else headerString.getMaxColWidths( new String[] { Loc.header_lap_number, Loc.header_sector1, Loc.header_sector2, Loc.header_sector3, Loc.header_lap }, colAligns, colPadding, colWidths ); totalWidth = timeStrings[0].getMaxColWidths( new String[] { "00", "-00.000", "-00.000", "-00.000", "-0:00.000" }, colAligns, colPadding, colWidths ); } else { if ( abbreviate.getBooleanValue() ) headerString.getMaxColWidths( new String[] { Loc.header_lap_number, Loc.header_lap_short }, colAligns, colPadding, colWidths ); else headerString.getMaxColWidths( new String[] { Loc.header_lap_number, Loc.header_lap }, colAligns, colPadding, colWidths ); totalWidth = timeStrings[0].getMaxColWidths( new String[] { "00", "-0:00.000" }, colAligns, colPadding, colWidths ); } if ( totalWidth < width ) { int rest = width - totalWidth; if ( displaySectors.getBooleanValue() ) { colWidths[2] += rest / 3; rest -= rest / 3; colWidths[3] += rest / 2; rest -= rest / 2; colWidths[4] += rest; } } VehicleScoringInfo vsi = gameData.getScoringInfo().getViewedVehicleScoringInfo(); if ( vsi.getCurrentLap() > 0 ) updateLaps( vsi ); } /** * {@inheritDoc} */ @Override protected void drawWidget( Clock clock, boolean needsCompleteRedraw, LiveGameData gameData, boolean isEditorMode, TextureImage2D texture, int offsetX, int offsetY, int width, int height ) { LocalStore store = getLocalStore(); if ( needsCompleteRedraw ) { if ( displaySectors.getBooleanValue() ) { if ( abbreviate.getBooleanValue() ) headerString.drawColumns( offsetX, offsetY, new String[] { Loc.header_lap_number, Loc.header_sector1_short, Loc.header_sector2_short, Loc.header_sector3_short, Loc.header_lap_short }, colAligns, colPadding, colWidths, texture ); else headerString.drawColumns( offsetX, offsetY, new String[] { Loc.header_lap_number, Loc.header_sector1, Loc.header_sector2, Loc.header_sector3, Loc.header_lap }, colAligns, colPadding, colWidths, texture ); } else { if ( abbreviate.getBooleanValue() ) headerString.drawColumns( offsetX, offsetY, new String[] { Loc.header_lap_number, Loc.header_lap_short }, colAligns, colPadding, colWidths, texture ); else headerString.drawColumns( offsetX, offsetY, new String[] { Loc.header_lap_number, Loc.header_lap }, colAligns, colPadding, colWidths, texture ); } } VehicleScoringInfo vsi = gameData.getScoringInfo().getViewedVehicleScoringInfo(); lap.update( vsi.getCurrentLap() ); if ( needsCompleteRedraw || lap.hasChanged() ) { int lastDspIdx = -1; for ( int i = 0; i < numDisplayedLaps; i++ ) { String[] s; if ( store.displayedLaps[i] == null ) { if ( displaySectors.getBooleanValue() ) s = new String[] { "--", "--.---", "--.---", "--.---", "-:--.---" }; else s = new String[] { "--", "-:--.---" }; } else { if ( displaySectors.getBooleanValue() ) s = new String[] { String.valueOf( store.displayedLaps[i].getLap() ), TimingUtil.getTimeAsString( store.displayedLaps[i].getSector1(), false, false, true ), TimingUtil.getTimeAsString( store.displayedLaps[i].getSector2(), false, false, true ), TimingUtil.getTimeAsString( store.displayedLaps[i].getSector3(), false, false, true ), TimingUtil.getTimeAsString( store.displayedLaps[i].getLapTime(), false, false, true ) }; else s = new String[] { String.valueOf( store.displayedLaps[i].getLap() ), TimingUtil.getTimeAsString( store.displayedLaps[i].getLapTime(), false, false, true ) }; lastDspIdx = i; } timeStrings[i].drawColumns( offsetX, offsetY, s, colAligns, colPadding, colWidths, texture ); } Laptime lt = ( lastDspIdx >= 0 ) ? store.displayedLaps[lastDspIdx] : null; Laptime lastDriven = vsi.getLaptime( vsi.getLapsCompleted() ); Laptime avgLaptime = vsi.getOldAverageLaptime(); String[] s; if ( ( lt != null ) && ( lastDriven != null ) && ( lastDriven.isOutlap() == Boolean.FALSE ) && ( avgLaptime != null ) ) { if ( displaySectors.getBooleanValue() ) s = new String[] { Loc.footer_gap, TimingUtil.getTimeAsGapString( lt.getSector1() - avgLaptime.getSector1() ), TimingUtil.getTimeAsGapString( lt.getSector2() - avgLaptime.getSector2() ), TimingUtil.getTimeAsGapString( lt.getSector3() - avgLaptime.getSector3() ), TimingUtil.getTimeAsGapString( lt.getLapTime() - avgLaptime.getLapTime() ) }; else s = new String[] { Loc.footer_gap, TimingUtil.getTimeAsGapString( lt.getLapTime() - avgLaptime.getLapTime() ) }; } else { if ( displaySectors.getBooleanValue() ) s = new String[] { Loc.footer_gap, "--.---", "--.---", "--.---", "-:--.---" }; else s = new String[] { Loc.footer_gap, "-:--.---" }; } timeStrings[numDisplayedLaps].drawColumns( offsetX, offsetY, s, colAligns, colPadding, colWidths, texture ); } } }