/**
* 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.drivingaids;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import net.ctdp.rfdynhud.gamedata.DrivingAids;
import net.ctdp.rfdynhud.gamedata.LiveGameData;
import net.ctdp.rfdynhud.properties.EnumProperty;
import net.ctdp.rfdynhud.properties.FloatProperty;
import net.ctdp.rfdynhud.properties.ImagePropertyWithTexture;
import net.ctdp.rfdynhud.properties.IntProperty;
import net.ctdp.rfdynhud.properties.ListProperty;
import net.ctdp.rfdynhud.properties.PropertiesContainer;
import net.ctdp.rfdynhud.properties.PropertyLoader;
import net.ctdp.rfdynhud.properties.StringProperty;
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.valuemanagers.Clock;
import net.ctdp.rfdynhud.widgets.WidgetsConfiguration;
import net.ctdp.rfdynhud.widgets.base.widget.Widget;
import net.ctdp.rfdynhud.widgets.standard._util.StandardWidgetSet;
/**
* The {@link DrivingAidsWidget} displays driving aids' states.
*
* @author Marvin Froehlich (CTDP)
*/
public class DrivingAidsWidget extends Widget implements DrivingAids.DrivingAidStateChangeListener
{
private static class AidStateValue
{
final Integer state;
final String stateString;
final String caption;
@Override
public String toString()
{
return ( caption );
}
AidStateValue( Integer state, String stateString, String caption )
{
this.state = state;
this.stateString = stateString;
this.caption = caption;
}
static final AidStateValue never = new AidStateValue( null, "N", "Never" );
static final AidStateValue always = new AidStateValue( null, "A", "Always" );
}
private static enum Alignment
{
TOP_LEFT,
CENTER,
TOP_RIGHT,
BOTTOM_LEFT,
BOTTOM_RIGHT,
;
}
private final FloatProperty columns = new FloatProperty( "columns", 2, 1, Integer.MAX_VALUE );
private final IntProperty gap = new IntProperty( "gap", 5 );
private final EnumProperty<Alignment> alignment = new EnumProperty<Alignment>( "alignment", Alignment.TOP_LEFT );
private final StringProperty stateVisibilities = new StringProperty( "stateVisibilities", "" );
private ListProperty<AidStateValue, List<AidStateValue>>[] stateVisibilities_ = null;
private String stateVisibilitiesToParse = null;
private final ImagePropertyWithTexture imageBackgroundImageOff = new ImagePropertyWithTexture( "iconBackgroundOff", null, "standard/drivingaid_background_off.png", false, true );
private final ImagePropertyWithTexture imageBackgroundImageLow = new ImagePropertyWithTexture( "iconBackgroundLow", null, "standard/drivingaid_background_low.png", false, true );
private final ImagePropertyWithTexture imageBackgroundImageMedium = new ImagePropertyWithTexture( "iconBackgroundMedium", null, "standard/drivingaid_background_medium.png", false, true );
private final ImagePropertyWithTexture imageBackgroundImageHigh = new ImagePropertyWithTexture( "iconBackgroundHigh", null, "standard/drivingaid_background_high.png", false, true );
private TextureImage2D[][] icons = null;
private int iconSize = 0;
private int innerIconSize = 0;
private int innerIconOffset = 0;
private boolean[] stateChanged = null;
private int numRows = -1;
public DrivingAidsWidget()
{
super( StandardWidgetSet.INSTANCE, StandardWidgetSet.WIDGET_PACKAGE, 8.125f, 34.417f );
getBorderProperty().setBorder( null );
getBackgroundProperty().setColorValue( "#00000000" );
setPadding( 0 );
}
/**
* {@inheritDoc}
*/
@Override
public void saveProperties( PropertyWriter writer ) throws IOException
{
super.saveProperties( writer );
writer.writeProperty( columns, "Number of columns to display" );
writer.writeProperty( gap, "Gap between the icons" );
writer.writeProperty( alignment, "Alignment of icons" );
if ( ( stateVisibilities_ != null ) && ( stateVisibilities_.length > 0 ) )
{
StringBuilder sb = new StringBuilder();
for ( int i = 0; i < stateVisibilities_.length; i++ )
{
if ( i > 0 )
sb.append( "," );
sb.append( stateVisibilities_[i].getValue().stateString );
}
stateVisibilities.setValue( sb.toString() );
writer.writeProperty( stateVisibilities, "Aid state visibilities" );
}
writer.writeProperty( imageBackgroundImageOff, "Background image for an aid's 'off' state" );
writer.writeProperty( imageBackgroundImageLow, "Background image for an aid's 'low' state" );
writer.writeProperty( imageBackgroundImageMedium, "Background image for an aid's 'medium' state" );
writer.writeProperty( imageBackgroundImageHigh, "Background image for an aid's 'high' state" );
}
private void parseStateVisibilities()
{
String[] tokens = stateVisibilitiesToParse.split( "," );
stateVisibilitiesToParse = null;
if ( tokens.length != stateVisibilities_.length )
return;
for ( int i = 0; i < tokens.length; i++ )
{
stateVisibilities_[i].setValue( stateVisibilities_[i].getDefaultValue() );
for ( AidStateValue asv : stateVisibilities_[i].getList() )
{
if ( asv.stateString.equals( tokens[i] ) )
{
stateVisibilities_[i].setValue( asv );
break;
}
}
}
}
/**
* {@inheritDoc}
*/
@Override
public void loadProperty( PropertyLoader loader, LiveGameData gameData )
{
super.loadProperty( loader, gameData );
if ( loader.loadProperty( columns ) );
else if ( loader.loadProperty( gap ) );
else if ( loader.loadProperty( alignment ) );
else if ( loader.loadProperty( stateVisibilities ) && !stateVisibilities.getValue().isEmpty() )
{
stateVisibilitiesToParse = stateVisibilities.getValue();
if ( stateVisibilities_ != null )
parseStateVisibilities();
}
else if ( loader.loadProperty( imageBackgroundImageOff ) );
else if ( loader.loadProperty( imageBackgroundImageLow ) );
else if ( loader.loadProperty( imageBackgroundImageMedium ) );
else if ( loader.loadProperty( imageBackgroundImageHigh ) );
}
@Override
protected boolean hasText()
{
return ( false );
}
/**
* {@inheritDoc}
*/
@Override
public void getProperties( PropertiesContainer propsCont, boolean forceAll )
{
super.getProperties( propsCont, forceAll );
propsCont.addProperty( imageBackgroundImageOff );
propsCont.addProperty( imageBackgroundImageLow );
propsCont.addProperty( imageBackgroundImageMedium );
propsCont.addProperty( imageBackgroundImageHigh );
propsCont.addGroup( "Misc" );
propsCont.addProperty( columns );
propsCont.addProperty( gap );
propsCont.addProperty( alignment );
if ( ( stateVisibilities_ != null ) && ( stateVisibilities_.length > 0 ) )
{
propsCont.addGroup( "Visibility and states" );
for ( int i = 0; i < stateVisibilities_.length; i++ )
propsCont.addProperty( stateVisibilities_[i] );
}
}
@Override
protected void initSubTextures( LiveGameData gameData, boolean isEditorMode, int widgetInnerWidth, int widgetInnerHeight, SubTextureCollector collector )
{
final DrivingAids drivingAids = gameData.getDrivingAids();
int numAids = drivingAids.getNumAids();
//int numRows = (int)Math.ceil( numAids / columns.getFloatValue() );
int smallerSize = Math.min( widgetInnerWidth, widgetInnerHeight );
iconSize = (int)Math.floor( ( smallerSize - gap.getFloatValue() * ( columns.getFloatValue() - 1 ) ) / columns.getFloatValue() );
innerIconSize = iconSize * 3 / 4;
innerIconOffset = ( iconSize - innerIconSize ) / 2;
imageBackgroundImageOff.updateSize( iconSize, iconSize, isEditorMode );
imageBackgroundImageLow.updateSize( iconSize, iconSize, isEditorMode );
imageBackgroundImageMedium.updateSize( iconSize, iconSize, isEditorMode );
imageBackgroundImageHigh.updateSize( iconSize, iconSize, isEditorMode );
if ( icons == null )
icons = new TextureImage2D[ numAids ][];
stateChanged = new boolean[ numAids ];
for ( int i = 0; i < numAids; i++ )
{
int numStates = drivingAids.getNumStates( i );
icons[i] = new TextureImage2D[ numStates ];
for ( int j = 0; j < numStates; j++ )
{
icons[i][j] = drivingAids.getAidIcon( i, j ).getScaledTextureImage( innerIconSize, innerIconSize, icons[i][j], isEditorMode );
}
stateChanged[i] = true;
}
}
@Override
protected void onWidgetAttached( WidgetsConfiguration config, LiveGameData gameData )
{
gameData.getDrivingAids().registerListener( this );
}
@Override
protected void onWidgetDetached( WidgetsConfiguration config, LiveGameData gameData )
{
gameData.getDrivingAids().unregisterListener( this );
}
@SuppressWarnings( { "unchecked", "cast" } )
private void initStateVisibilityProperties( DrivingAids aids )
{
if ( stateVisibilities_ == null )
{
int n = aids.getNumAids();
//stateVisibilities_ = new ListProperty<AidStateValue, List<AidStateValue>>[ n ];
stateVisibilities_ = (ListProperty<AidStateValue, List<AidStateValue>>[])new ListProperty[ n ];
for ( int i = 0; i < n; i++ )
{
List<AidStateValue> values = new ArrayList<AidStateValue>();
values.add( AidStateValue.never );
values.add( AidStateValue.always );
for ( int s = aids.getMinState( i ); s <= aids.getMaxState( i ); s++ )
{
values.add( new AidStateValue( s, String.valueOf( s ), "If not " + aids.getAidStateName( i, s ) ) );
}
stateVisibilities_[i] = new ListProperty<AidStateValue, List<AidStateValue>>( aids.getAidName( i ), AidStateValue.always, values )
{
@Override
protected void onValueChanged( AidStateValue oldValue, AidStateValue newValue )
{
forceCompleteRedraw( false );
}
};
}
}
if ( stateVisibilitiesToParse != null )
parseStateVisibilities();
}
/**
* {@inheritDoc}
*/
@Override
protected void initialize( LiveGameData gameData, boolean isEditorMode, DrawnStringFactory dsf, TextureImage2D texture, int width, int height )
{
initStateVisibilityProperties( gameData.getDrivingAids() );
}
@Override
public void onSessionStarted( LiveGameData gameData, boolean isEditorMode )
{
}
@Override
public void onGamePauseStateChanged( LiveGameData gameData, boolean isEditorMode, boolean isPaused )
{
}
@Override
public void onDrivingAidsUpdated( LiveGameData gameData, boolean isEditorMode )
{
}
private void countRows( DrivingAids drivingAids, boolean isEditorMode )
{
numRows = -1;
if ( alignment.getValue() == Alignment.CENTER )
{
numRows = 0;
int column = 0;
int numAids = drivingAids.getNumAids();
for ( int i = 0; i < numAids; i++ )
{
AidStateValue asv = stateVisibilities_[i].getValue();
if ( asv == AidStateValue.never )
continue;
if ( !isEditorMode && ( asv != AidStateValue.always ) )
{
if ( asv.state.intValue() == drivingAids.getAidState( i ) )
continue;
}
if ( numRows == 0 )
numRows = 1;
column++;
if ( column >= columns.getIntValue() )
{
column = 0;
numRows++;
}
}
}
}
@Override
public void onDrivingAidStateChanged( LiveGameData gameData, int aidIndex, int oldState, int newState )
{
if ( stateChanged != null )
stateChanged[aidIndex] = true;
//if ( ( stateVisibilities_ != null ) && ( stateVisibilities_.length == gameData.getDrivingAids().getNumAids() ) )
{
//if ( ( stateVisibilities_[aidIndex].getValue() != AidStateValue.never ) && ( stateVisibilities_[aidIndex].getValue() != AidStateValue.always ) )
forceCompleteRedraw( false );
}
countRows( gameData.getDrivingAids(), false );
/*
StringBuilder sb = new StringBuilder();
for ( int i = 0; i < gameData.getDrivingAids().getNumAids(); i++ )
{
if ( i > 0 )
sb.append( ", " );
sb.append( gameData.getDrivingAids().getAidName( i ) );
sb.append( ": " );
sb.append( gameData.getDrivingAids().getAidState( i ) + " - " + gameData.getDrivingAids().getAidStateName( i ) );
}
log( sb.toString() );
*/
}
private TextureImage2D getIconBackground( int state )
{
switch ( state )
{
case 0:
return ( imageBackgroundImageOff.getTexture() );
case 1:
return ( imageBackgroundImageLow.getTexture() );
case 2:
return ( imageBackgroundImageMedium.getTexture() );
case 3:
default:
return ( imageBackgroundImageHigh.getTexture() );
}
}
@Override
protected void drawWidget( Clock clock, boolean needsCompleteRedraw, LiveGameData gameData, boolean isEditorMode, TextureImage2D texture, int offsetX, int offsetY, int width, int height )
{
final DrivingAids drivingAids = gameData.getDrivingAids();
int numAids = drivingAids.getNumAids();
Alignment align = alignment.getValue();
int x = 0;
int y = 0;
int column = 0;
if ( ( align == Alignment.CENTER ) && ( ( numRows < 0 ) || isEditorMode ) )
countRows( drivingAids, isEditorMode );
for ( int i = 0; i < numAids; i++ )
{
AidStateValue asv = stateVisibilities_[i].getValue();
if ( asv == AidStateValue.never )
continue;
if ( !isEditorMode && ( asv != AidStateValue.always ) )
{
if ( asv.state.intValue() == drivingAids.getAidState( i ) )
continue;
}
if ( stateChanged[i] || needsCompleteRedraw )
{
int x2 = x;
int y2 = y;
if ( getInnerSize().getEffectiveWidth() > getInnerSize().getEffectiveHeight() )
{
x2 = y;
y2 = x;
}
int x3 = x2;
int y3 = y2;
if ( align == Alignment.CENTER )
{
if ( getInnerSize().getEffectiveWidth() > getInnerSize().getEffectiveHeight() )
x3 = ( getInnerSize().getEffectiveWidth() - ( numRows * iconSize ) - ( numRows * gap.getIntValue() - gap.getIntValue() ) ) / 2 + x2;
else
y3 = ( getInnerSize().getEffectiveHeight() - ( numRows * iconSize ) - ( numRows * gap.getIntValue() - gap.getIntValue() ) ) / 2 + y2;
}
else if ( ( align == Alignment.TOP_RIGHT ) || ( align == Alignment.BOTTOM_RIGHT ) )
{
x3 = getInnerSize().getEffectiveWidth() - x2 - iconSize;
}
int x4 = x3;
int y4 = y3;
if ( ( align == Alignment.BOTTOM_LEFT ) || ( align == Alignment.BOTTOM_RIGHT ) )
{
y4 = getInnerSize().getEffectiveHeight() - y3 - iconSize;
}
clearBackgroundRegion( texture, offsetX, offsetY, x4, y4, iconSize, iconSize, true, null );
int state = drivingAids.getAidState( i );
int state2 = state;
int numStates = drivingAids.getNumStates( i );
if ( numStates == 2 )
state2 = ( state == 0 ) ? 0 : 3;
else if ( numStates == 3 )
state2 = ( state == 0 ) ? 0 : ( ( state == 1 ) ? 1 : 3 );
TextureImage2D background = getIconBackground( state2 );
if ( background != null )
texture.drawImage( background, offsetX + x4, offsetY + y4, false, null );
texture.drawImage( icons[i][state], offsetX + x4 + innerIconOffset, offsetY + y4 + innerIconOffset, false, null );
stateChanged[i] = false;
}
column++;
if ( column < columns.getIntValue() )
{
x += iconSize;
x += gap.getIntValue();
}
else
{
column = 0;
x = 0;
y += iconSize;
y += gap.getIntValue();
}
}
}
}