initial commit. Xunkar's release of 1.4.3 sources

This commit is contained in:
Gered 2015-11-29 21:18:49 -05:00
commit e85143f9eb
65 changed files with 9519 additions and 0 deletions

10
.gitignore vendored Normal file
View file

@ -0,0 +1,10 @@
.DS_Store
.idea/
.settings/
target/
out/
.project
.classpath
*.iml
*.ipr
*.iws

BIN
lib/JNativeHook-1.3.jar Normal file

Binary file not shown.

BIN
lib/Sidekick-1.2.jar Normal file

Binary file not shown.

BIN
lib/XStream-1.4.4.jar Normal file

Binary file not shown.

View file

@ -0,0 +1,444 @@
package org.fenix.llanfair;
import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.io.xml.DomDriver;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ResourceBundle;
import javax.swing.Action;
import javax.swing.JFileChooser;
import javax.swing.JOptionPane;
import org.fenix.llanfair.config.Settings;
import org.fenix.llanfair.dialog.EditRun;
import org.fenix.llanfair.dialog.EditSettings;
import org.fenix.llanfair.extern.WSplit;
import org.fenix.utils.about.AboutDialog;
import org.jnativehook.keyboard.NativeKeyEvent;
/**
* Regroups all actions, in the meaning of {@link Action}, used by Llanfair.
* All inputs and menu items callbacks are processed by this delegate to
* simplify the work of the main class.
*
* @author Xavier "Xunkar" Sencert
* @version 1.0
*/
final class Actions {
private static final long GHOST_DELAY = 300L;
private static ResourceBundle BUNDLE = null;
private Llanfair master;
private File file;
private JFileChooser fileChooser;
private volatile long lastUnsplit;
private volatile long lastSkip;
/**
* Creates a new delegate. This constructor is package private since it
* only need be called by the main class.
*
* @param owner the Llanfair instance owning this delegate
*/
Actions( Llanfair owner ) {
assert ( owner != null );
master = owner;
file = null;
fileChooser = new JFileChooser( "." );
lastUnsplit = 0L;
lastSkip = 0L;
if ( BUNDLE == null ) {
BUNDLE = Llanfair.getResources().getBundle( "llanfair" );
}
}
/**
* Processes the given native key event. This method must be called from
* a thread to prevent possible deadlock.
*
* @param event the native key event to process
*/
void process( NativeKeyEvent event ) {
assert ( event != null );
int keyCode = event.getKeyCode();
Run run = master.getRun();
Run.State state = run.getState();
if ( keyCode == Settings.KEY_SPLT.get() ) {
split();
} else if ( keyCode == Settings.KEY_RSET.get() ) {
reset();
} else if ( keyCode == Settings.KEY_USPL.get() ) {
unsplit();
} else if ( keyCode == Settings.KEY_SKIP.get() ) {
skip();
} else if ( keyCode == Settings.KEY_STOP.get() ) {
if ( state == Run.State.ONGOING ) {
run.stop();
}
} else if ( keyCode == Settings.KEY_PAUS.get() ) {
if ( state == Run.State.ONGOING ) {
run.pause();
} else if ( state == Run.State.PAUSED ) {
run.resume();
}
} else if ( keyCode == Settings.KEY_LOCK.get() ) {
master.setIgnoreNativeInputs( !master.ignoresNativeInputs() );
}
}
/**
* Processes the given action event. It is assumed here that the action
* event is one triggered by a menu item. This method must be called from
* a thread to prevent possible deadlock.
*
* @param event the event to process
*/
void process( ActionEvent event ) {
assert ( event != null );
Run run = master.getRun();
MenuItem source = ( MenuItem ) event.getSource();
if ( source == MenuItem.EDIT ) {
EditRun dialog = new EditRun( run );
dialog.display( true, master );
} else if ( source == MenuItem.NEW ) {
if ( confirmOverwrite() ) {
master.setRun( new Run() );
}
} else if ( source == MenuItem.OPEN ) {
open( null );
} else if ( source == MenuItem.OPEN_RECENT ) {
open( new File( event.getActionCommand() ) );
} else if ( source == MenuItem.IMPORT ) {
imprt();
} else if ( source == MenuItem.SAVE ) {
run.saveLiveTimes( !run.isPersonalBest() );
run.reset();
save();
} else if ( source == MenuItem.SAVE_AS ) {
file = null;
save();
} else if ( source == MenuItem.RESET ) {
reset();
} else if ( source == MenuItem.LOCK ) {
master.setIgnoreNativeInputs( true );
} else if ( source == MenuItem.UNLOCK ) {
master.setIgnoreNativeInputs( false );
} else if ( source == MenuItem.SETTINGS ) {
EditSettings dialog = new EditSettings();
dialog.display( true, master );
} else if ( source == MenuItem.ABOUT ) {
about();
} else if ( source == MenuItem.EXIT ) {
if ( confirmOverwrite() ) {
master.dispose();
}
}
}
/**
* Performs a split or starts the run if it is ready. Can also resume a
* paused run in case the run is segmented.
*/
private void split() {
Run run = master.getRun();
Run.State state = run.getState();
if ( state == Run.State.ONGOING ) {
long milli = System.nanoTime() / 1000000L;
long start = run.getSegment( run.getCurrent() ).getStartTime();
if ( milli - start > GHOST_DELAY ) {
run.split();
}
} else if ( state == Run.State.READY ) {
run.start();
} else if ( state == Run.State.PAUSED && run.isSegmented() ) {
run.resume();
}
}
/**
* Resets the current run to a ready state. If the user asked to be warned
* a pop-up will ask confirmation in case some live times are better.
*/
private void reset() {
Run run = master.getRun();
if ( run.getState() != Run.State.NULL ) {
if ( !Settings.GNR_WARN.get() || confirmOverwrite() ) {
run.reset();
}
}
}
/**
* Performs an "unsplit" on the current run. If a split has been made, it
* is canceled and the time that passed after said split is added back to
* the timer, as if the split had not taken place.
*/
private void unsplit() {
Run run = master.getRun();
Run.State state = run.getState();
if ( state == Run.State.ONGOING || state == Run.State.STOPPED ) {
long milli = System.nanoTime() / 1000000L;
if ( milli - lastUnsplit > GHOST_DELAY ) {
lastUnsplit = milli;
run.unsplit();
}
}
}
/**
* Skips the current split in the run. Skipping a split sets an undefined
* time for the current segment and merges the live time of the current
* segment with the following one.
*/
private void skip() {
Run run = master.getRun();
if ( run.getState() == Run.State.ONGOING ) {
long milli = System.nanoTime() / 1000000L;
if ( milli - lastSkip > GHOST_DELAY ) {
lastSkip = milli;
run.skip();
}
}
}
/**
* Displays a dialog to let the user select a file. The user is able to
* cancel this action, which results in a {@code null} being returned.
*
* @return a file selected by the user or {@code null} if he canceled
*/
private File selectFile() {
int option = fileChooser.showDialog( master,
Language.action_accept.get() );
if ( option == JFileChooser.APPROVE_OPTION ) {
return fileChooser.getSelectedFile();
} else {
return null;
}
}
/**
* Asks the user to confirm the discard of the current run. The popup
* window will only trigger if the current run has not been saved after
* some editing or if the run presents better times.
*
* @return {@code true} if the user wants to discard the run
*/
private boolean confirmOverwrite() {
boolean before = master.ignoresNativeInputs();
master.setIgnoreNativeInputs( true );
Run run = master.getRun();
boolean betterRun = run.isPersonalBest();
boolean betterSgt = run.hasSegmentsBest();
if ( betterRun || betterSgt ) {
String message = betterRun
? Language.WARN_BETTER_RUN.get()
: Language.WARN_BETTER_TIMES.get();
int option = JOptionPane.showConfirmDialog( master, message,
Language.WARNING.get(), JOptionPane.YES_NO_CANCEL_OPTION,
JOptionPane.WARNING_MESSAGE );
if ( option == JOptionPane.CANCEL_OPTION ) {
master.setIgnoreNativeInputs( false );
return false;
} else if ( option == JOptionPane.YES_OPTION ) {
run.saveLiveTimes( !betterRun );
run.reset();
save();
}
}
master.setIgnoreNativeInputs( before );
return true;
}
/**
* Opens the given file. If the file is {@code null}, the user is asked
* to select one. Before anything is done, the user is also asked for
* a confirmation if the current run has not been saved.
*
* @param file the file to open
*/
void open( File file ) {
if ( !confirmOverwrite() ) {
return;
}
if ( file == null ) {
if ( ( file = selectFile() ) == null ) {
return;
}
}
this.file = file;
String name = file.getName();
try {
open();
} catch ( Exception ex ) {
master.showError( Language.error_read_file.get( name ) );
this.file = null;
}
}
/**
* Opens the currently selected file. This method will first try to open
* the file using the new method (XStream XML) and if it fails will try to
* use the legacy method (Java ObjectStream.)
*
* @throws Exception if the reading operation fails
*/
private void open() throws Exception {
BufferedInputStream in = null;
try {
in = new BufferedInputStream( new FileInputStream( file ) );
try {
in.mark( Integer.MAX_VALUE );
xmlRead( in );
} catch ( Exception ex ) {
in.reset();
legacyRead( new ObjectInputStream( in ) );
}
MenuItem.recentlyOpened( "" + file );
} catch ( Exception ex ) {
throw ex;
} finally {
try {
in.close();
} catch ( Exception ex ) {
//$FALL-THROUGH$
}
}
}
/**
* Reads a stream on a run file using the new (since 1.5) XML method. This
* method will not be able to read a legacy run file and will throw an
* exception if confronted with such run file.
*
* @param in the input stream on the run file
*/
private void xmlRead( InputStream in ) {
XStream xml = new XStream( new DomDriver() );
master.setRun( ( Run ) xml.fromXML( in ) );
}
/**
* Reads a stream on a run file using the legacy Java method. This method
* might fail if the given file is not a Llanfair run.
*
* @param in an input stream on the run file
* @throws Exception if the stream cannot be read
*/
private void legacyRead( ObjectInputStream in ) throws Exception {
master.setRun( ( Run ) in.readObject() );
try {
Settings.GNR_SIZE.set( ( Dimension ) in.readObject(), true );
} catch ( Exception ex ) {
// $FALL-THROUGH$
}
}
/**
* Saves the currently opened run to the currently selected file. If no
* file has been selected, the user is asked for one.
*/
private void save() {
if ( file == null ) {
if ( ( file = selectFile() ) == null ) {
return;
}
}
Settings.GNR_COOR.set( master.getLocationOnScreen(), true );
Settings.GNR_SIZE.set( master.getSize(), true );
String name = file.getName();
BufferedOutputStream out = null;
try {
XStream xml = new XStream( new DomDriver() );
out = new BufferedOutputStream( new FileOutputStream( file ) );
xml.toXML( master.getRun(), out );
} catch ( Exception ex ) {
master.showError( Language.error_write_file.get( name ) );
} finally {
try {
out.close();
} catch ( Exception ex ) {
// $FALL-THROUGH$
}
}
}
/**
* Imports a run from another timer application. If no file has been
* selected, the user is asked for one. As of now, only WSplit run files
* are supported.
*/
private void imprt() {
if ( !confirmOverwrite() ) {
return;
}
if ( file == null ) {
if ( ( file = selectFile() ) == null ) {
return;
}
}
String name = file.getName();
BufferedReader in = null;
try {
in = new BufferedReader( new FileReader( file ) );
WSplit.parse( master, in );
} catch ( Exception ex ) {
master.showError( Language.error_import_run.get( name ) );
} finally {
try {
in.close();
} catch ( Exception ex ) {
// $FALL-THROUGH$
}
}
}
/**
* Displays the "about" dialog. The dialog displays the version of Llanfair,
* the creative commons licence, the credits of development, a link to
* Llanfair's website and a link to donate.
*/
private void about() {
AboutDialog dialog = new AboutDialog(
master, Language.title_about.get() );
dialog.setMessage( BUNDLE.getString( "about" ));
try {
dialog.setWebsite( new URL( BUNDLE.getString( "website" ) ) );
} catch ( MalformedURLException ex ) {
// $FALL-THROUGH$
}
try {
dialog.setDonateLink( new URL( BUNDLE.getString( "donate" ) ),
Llanfair.getResources().getIcon( "donate" ) );
} catch ( MalformedURLException ex ) {
// $FALL-THROUGH$
}
dialog.display();
}
}

View file

@ -0,0 +1,171 @@
package org.fenix.llanfair;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import javax.swing.Icon;
import javax.swing.event.TableModelListener;
import javax.swing.table.TableModel;
import org.fenix.llanfair.Language;
import org.fenix.utils.TableModelSupport;
/**
*
* @author Xavier "Xunkar" Sencert
*/
public class Counters implements TableModel, Serializable {
// -------------------------------------------------------------- CONSTANTES
public static final long serialVersionUID = 1000L;
public static final int COLUMN_ICON = 0;
public static final int COLUMN_NAME = 1;
public static final int COLUMN_START = 2;
public static final int COLUMN_INCREMENT = 3;
public static final int COLUMN_COUNT = 4;
// -------------------------------------------------------------- ATTRIBUTS
private List<Counter> data;
private TableModelSupport tmSupport;
// ---------------------------------------------------------- CONSTRUCTEURS
public Counters() {
data = new ArrayList<Counter>();
tmSupport = new TableModelSupport(this);
}
public int getColumnCount() {
return COLUMN_COUNT;
}
public int getRowCount() {
return data.size();
}
public Object getValueAt(int rowIndex, int columnIndex) {
if (rowIndex < 0 || rowIndex >= data.size()) {
throw new IllegalArgumentException("illegal counter id " + rowIndex);
}
return data.get(rowIndex).get(columnIndex);
}
public String getColumnName(int columnIndex) {
switch (columnIndex) {
case COLUMN_ICON: return "" + Language.ICON;
case COLUMN_INCREMENT: return "" + Language.INCREMENT;
case COLUMN_NAME: return "" + Language.NAME;
case COLUMN_START: return "" + Language.START_VALUE;
}
return null;
}
public Class<?> getColumnClass(int columnIndex) {
switch (columnIndex) {
case COLUMN_ICON: return Icon.class;
case COLUMN_INCREMENT: return Integer.class;
case COLUMN_NAME: return String.class;
case COLUMN_START: return Integer.class;
}
return null;
}
public boolean isCellEditable(int rowIndex, int columnIndex) {
return true;
}
public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
if (rowIndex < 0 || rowIndex >= data.size()) {
throw new IllegalArgumentException("illegal counter id " + rowIndex);
}
data.get(rowIndex).set(columnIndex, aValue);
}
public void addTableModelListener(TableModelListener l) {
tmSupport.addTableModelListener(l);
}
public void removeTableModelListener(TableModelListener l) {
tmSupport.removeTableModelListener(l);
}
// ----------------------------------------------------------- CLASSES
public static class Counter implements Serializable {
public static final long serialVersionUID = 1000L;
private String name;
private Icon icon;
private int increment;
private int start;
private int saved;
private int live;
public Counter() {
name = "" + Language.UNTITLED;
icon = null;
start = 0;
live = 0;
increment = 1;
}
public Object get(int columnIndex) {
switch (columnIndex) {
case COLUMN_ICON: return icon;
case COLUMN_INCREMENT: return increment;
case COLUMN_NAME: return name;
case COLUMN_START: return start;
default: return name;
}
}
public void set(int columnIndex, Object value) {
switch (columnIndex) {
case COLUMN_ICON: icon = (Icon) value; break;
case COLUMN_INCREMENT: increment = (Integer) value; break;
case COLUMN_NAME: name = (String) value; break;
case COLUMN_START: start = (Integer) value; break;
}
}
public Icon getIcon() {
return icon;
}
public String getName() {
return name;
}
public int getLive() {
return live;
}
public int getStart() {
return start;
}
public int getSaved() {
return saved;
}
public void nextStep() {
live += increment;
}
}
}

View file

@ -0,0 +1,292 @@
package org.fenix.llanfair;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import org.fenix.utils.Resources;
/**
* Enumeration of all externalized strings used by {@code Llanfair}. While it is
* possible to ask directly the {@link Resources} singleton that this class uses
* we prefer declaring explicitely every token here to make sure we never call
* a non-existent reference.
*
* @author Xavier "Xunkar" Sencert
* @see Resources
*/
public enum Language {
// Settings > Generic
setting_alwaysOnTop,
setting_language,
setting_viewerLanguage,
setting_recentFiles,
setting_coordinates,
setting_dimension,
setting_compareMethod,
setting_accuracy,
setting_locked,
setting_warnOnReset,
// Settings > Color
setting_color_background,
setting_color_foreground,
setting_color_time,
setting_color_timer,
setting_color_timeGained,
setting_color_timeLost,
setting_color_newRecord,
setting_color_title,
setting_color_highlight,
setting_color_separators,
// Settings > Hotkey
setting_hotkey_split,
setting_hotkey_unsplit,
setting_hotkey_skip,
setting_hotkey_reset,
setting_hotkey_stop,
setting_hotkey_pause,
setting_hotkey_lock,
// Settings > Header
setting_header_goal,
setting_header_title,
// Settings > History
setting_history_rowCount,
setting_history_tabular,
setting_history_blankRows,
setting_history_multiline,
setting_history_merge,
setting_history_liveTimes,
setting_history_deltas,
setting_history_icons,
setting_history_iconSize,
setting_history_offset,
setting_history_alwaysShowLast,
setting_history_segmentFont,
setting_history_timeFont,
// Settings > Core
setting_core_accuracy,
setting_core_icons,
setting_core_iconSize,
setting_core_segmentName,
setting_core_splitTime,
setting_core_segmentTime,
setting_core_bestTime,
setting_core_segmentTimer,
setting_core_timerFont,
setting_core_segmentTimerFont,
// Settings > Graph
setting_graph_display,
setting_graph_scale,
// Settings > Footer
setting_footer_display,
setting_footer_useSplitData,
setting_footer_verbose,
setting_footer_bestTime,
setting_footer_multiline,
setting_footer_deltaLabels,
// Accuracy
accuracy_seconds,
accuracy_tenth,
accuracy_hundredth,
// Compare
compare_best_overall_run,
compare_sum_of_best_segments,
// Merge
merge_none,
merge_live,
merge_delta,
// MenuItem
menuItem_edit,
menuItem_new,
menuItem_open,
menuItem_open_recent,
menuItem_import,
menuItem_save,
menuItem_save_as,
menuItem_reset,
menuItem_lock,
menuItem_unlock,
menuItem_resize_default,
menuItem_resize_preferred,
menuItem_settings,
menuItem_about,
menuItem_exit,
// Errors
error_read_file,
error_write_file,
error_import_run,
// Actions
action_accept,
// Titles
title_about,
GENERAL,
TIMER,
FOOTER,
MISC,
USE_MAIN_FONT,
LB_GOAL,
ICON,
COLORS,
// Edit Dialog
ED_SEGMENTED,
TT_ED_SEGMENTED,
// Panels Title
PN_DIMENSION,
PN_DISPLAY,
PN_FONTS,
PN_SCROLLING,
// History
HISTORY,
MERGE_DELTA,
MERGE_LIVE,
MERGE_NONE,
TT_HS_OFFSET,
// Core
LB_CR_BEST,
LB_CR_SEGMENT,
LB_CR_SPLIT,
// Footer
LB_FT_BEST,
LB_FT_DELTA,
LB_FT_DELTA_BEST,
LB_FT_LIVE,
LB_FT_SEGMENT,
LB_FT_SPLIT,
/*
* Messages.
*/
ICON_TOO_BIG,
ILLEGAL_TIME,
ILLEGAL_SEGMENT_TIME,
INPUT_NAN,
INPUT_NEGATIVE,
INVALID_TIME_STAMP,
WARN_BETTER_RUN,
WARN_BETTER_TIMES,
WARN_RESET_SETTINGS,
/*
* Tooltips.
*/
TT_ADD_SEGMENT,
TT_COLOR_PICK,
TT_COLUMN_BEST,
TT_COLUMN_SEGMENT,
TT_COLUMN_TIME,
TT_REMOVE_SEGMENT,
TT_MOVE_SEGMENT_UP,
TT_MOVE_SEGMENT_DOWN,
/*
* Run.State enumeration.
*/
RUN_NULL,
RUN_OVER,
RUN_READY,
RUN_STOPPED,
/*
* Time.Accuracy enumeration.
*/
ACCURACY,
/*
* Miscellaneous tokens.
*/
ACCEPT,
APPLICATION,
BEST,
CANCEL,
COMPARE_METHOD,
COMPONENTS,
DISABLED,
EDITING,
ERROR,
GOAL,
IMAGE,
INPUTS,
MAX_ORDINATE,
NAME,
RUN_TITLE,
SEGMENT,
SEGMENTS,
SPLIT,
TIME,
UNTITLED,
WARNING,
/*
* 1.4
*/
INCREMENT,
START_VALUE;
public static final Locale[] LANGUAGES = new Locale[] {
Locale.ENGLISH,
Locale.FRENCH,
Locale.GERMAN,
new Locale("nl"),
new Locale("sv")
};
public static final Map<String, String> LOCALE_NAMES =
new HashMap<String, String>();
static {
LOCALE_NAMES.put("de", "Deutsch");
LOCALE_NAMES.put("en", "English");
LOCALE_NAMES.put("fr", "Français");
LOCALE_NAMES.put("nl", "Nederlands");
LOCALE_NAMES.put("sv", "Svenska");
}
// -------------------------------------------------------------- INTERFACE
/**
* Returns the localized string get of this language element.
*
* @return the localized string for this element.
*/
public String get() {
return Llanfair.getResources().getString(name());
}
/**
* Returns the localized string get of this language element. This method
* also passes down an array of parameters to replace the tokens with.
*
* @param parameters - the array of values for each token of the string.
* @return the localized string filled with the given parameters.
*/
public String get(Object... parameters) {
return Llanfair.getResources().getString(name(), parameters);
}
/**
* The string representation of an enumerate is the localized string
* corresponding to its name.
*/
@Override public String toString() {
return get();
}
}

View file

@ -0,0 +1,467 @@
package org.fenix.llanfair;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.GraphicsEnvironment;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseWheelEvent;
import java.awt.event.MouseWheelListener;
import java.awt.event.WindowEvent;
import java.awt.event.WindowListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.util.Locale;
import javax.swing.JOptionPane;
import javax.swing.JPopupMenu;
import javax.swing.SwingUtilities;
import javax.swing.ToolTipManager;
import javax.swing.UIManager;
import javax.swing.event.TableModelEvent;
import javax.swing.event.TableModelListener;
import org.fenix.llanfair.config.Settings;
import org.fenix.llanfair.gui.RunPane;
import org.fenix.utils.gui.BorderlessFrame;
import org.fenix.utils.Resources;
import org.fenix.utils.locale.LocaleDelegate;
import org.fenix.utils.locale.LocaleEvent;
import org.fenix.utils.locale.LocaleListener;
import org.jnativehook.GlobalScreen;
import org.jnativehook.NativeHookException;
import org.jnativehook.keyboard.NativeKeyEvent;
import org.jnativehook.keyboard.NativeKeyListener;
/**
* Main frame executing Llanfair.
*
* @author Xavier "Xunkar" Sencert
* @version 1.5
*/
public class Llanfair extends BorderlessFrame implements TableModelListener,
LocaleListener, MouseWheelListener, ActionListener, NativeKeyListener,
PropertyChangeListener, WindowListener {
private static Resources RESOURCES = null;
static {
ToolTipManager.sharedInstance().setInitialDelay( 1000 );
ToolTipManager.sharedInstance().setDismissDelay( 7000 );
ToolTipManager.sharedInstance().setReshowDelay( 0 );
}
private Run run;
private RunPane runPane;
private Actions actions;
private JPopupMenu popupMenu;
private volatile boolean ignoreNativeInputs;
private Dimension preferredSize;
/**
* Creates and initializes the application. As with any Swing application
* this constructor should be called from within a thread to avoid
* dead-lock.
*/
private Llanfair() {
super( "Llanfair" );
LocaleDelegate.setDefault( Settings.GNR_LANG.get() );
LocaleDelegate.addLocaleListener( this );
RESOURCES = new Resources( "res" );
registerFonts();
setLookAndFeel();
run = new Run();
runPane = null;
ignoreNativeInputs = false;
preferredSize = null;
actions = new Actions( this );
setMenu();
setBehavior();
setRun( run );
setVisible( true );
}
/**
* Main entry point of the application. This is the method called by Java
* when a user executes the JAR. Simply instantiantes a new Llanfair object.
* If an argument is passed, the program will not launch but will instead
* enter localization mode, dumping all language variables for the specified
* locale.
*
* @param args array of command line parameters supplied at launch
*/
public static void main( String[] args ) {
if ( args.length > 0 ) {
String locale = args[0];
LocaleDelegate.setDefault( new Locale( locale ) );
RESOURCES = new Resources( "res" );
dumpLocalization();
System.exit( 0 );
}
SwingUtilities.invokeLater( new Runnable() {
@Override public void run() {
new Llanfair();
}
} );
}
/**
* Grabs the resources of Llanfair. The Resources object is a front-end
* for every classpath resources associated with the application, including
* localization strings, icons, and properties.
*
* @return the resources object
*/
public static Resources getResources() {
return RESOURCES;
}
/**
* Returns the run currently associated with this instance of Llanfair.
* While the run can be empty, it cannot be {@code null}.
*
* @return the current run
*/
Run getRun() {
return run;
}
/**
* Sets the run to represent in this application to the given run. If the
* GUI does not exist (in other words, we are registering the first run) it
* is created on the fly.
*
* @param run the run to represent, cannot be {@code null}
*/
public final void setRun( Run run ) {
if ( run == null ) {
throw new NullPointerException( "Null run" );
}
this.run = run;
// If we have a GUI, set the new model; else, create the GUI
if ( runPane != null ) {
runPane.setRun( run );
} else {
runPane = new RunPane( run );
add( runPane );
}
Settings.setRun( run );
run.addTableModelListener( this );
run.addPropertyChangeListener( this );
MenuItem.setActiveState( run.getState() );
setPreferredSize( preferredSize );
pack();
// Replace the window to the run preferred location; center if none
Point location = Settings.GNR_COOR.get();
if ( location == null ) {
setLocationRelativeTo( null );
} else {
setLocation( location );
}
}
/**
* Indicates whether or not Llanfair currently ignores all native inputs.
* Since native inputs can be caught even when the application does not have
* the focus, it is necessary to be able to lock the application when the
* user needs to do something else whilst not interfering with the behavior
* of Llanfair.
*
* @return {@code true} if the current instance ignores native inputs
*/
public synchronized boolean ignoresNativeInputs() {
return ignoreNativeInputs;
}
/**
* Tells Llanfair whether to ignore native input events or not. Since native
* inputs can be caught even when the application does not have the focus,
* it is necessary to be able to lock the application when the user needs
* to do something else whilst not interfering with the behavior of
* Llanfair.
*
* @param ignore if Llanfair must ignore the native inputs or not
*/
public synchronized void setIgnoreNativeInputs( boolean ignore ) {
ignoreNativeInputs = ignore;
}
/**
* Outputs the given error in a dialog box. Only errors that are made for
* and useful to the user need to be displayed that way.
*
* @param message the localized error message
*/
void showError( String message ) {
JOptionPane.showMessageDialog(
this, message, Language.ERROR.get(), JOptionPane.ERROR_MESSAGE
);
}
/**
* Sets the look and feel of the application. Provides a task bar icon and
* a general system dependent theme.
*/
private void setLookAndFeel() {
setIconImage( RESOURCES.getImage( "Llanfair" ) );
try {
UIManager.setLookAndFeel(
UIManager.getSystemLookAndFeelClassName()
);
} catch ( Exception ex ) {
// $FALL-THROUGH$
}
}
/**
* Register the fonts provided with Llanfair with its environment.
*/
private void registerFonts() {
InputStream fontFile = RESOURCES.getStream( "digitalism.ttf" );
try {
Font digitalism = Font.createFont( Font.TRUETYPE_FONT, fontFile );
GraphicsEnvironment.getLocalGraphicsEnvironment().registerFont(
digitalism
);
} catch ( Exception ex ) {
// $FALL-THROUGH$
}
}
/**
* Writes all values from the {@code Language} enum in a property file.
* This method will append all the newly defined entries to the list of
* already existing values.
*/
private static void dumpLocalization() {
try {
String iso = Locale.getDefault().getLanguage();
FileWriter fw = new FileWriter( "language_" + iso + ".properties" );
for ( Language lang : Language.values() ) {
String old = RESOURCES.getString( lang.name() );
fw.write( lang.name() + " = " );
if ( old != null ) {
fw.write( old );
}
fw.write( "\n" );
}
fw.close();
} catch ( IOException ex ) {
// $FALL-THROUGH$
}
}
/**
* When the locale changes, we first ask the resources to reload the locale
* dependent resources and pass the event to the GUI.
*/
@Override public void localeChanged( LocaleEvent event ) {
RESOURCES.defaultLocaleChanged();
if ( runPane != null ) {
runPane.processLocaleEvent( event );
}
MenuItem.localeChanged( event );
}
/**
* If we do not ignore the native inputs, register the input and invokes
* a new thread to treat the input whenever possible without hogging the
* main thread.
*/
@Override public void nativeKeyPressed( final NativeKeyEvent event ) {
int keyCode = event.getKeyCode();
boolean hotkeysEnabler = ( keyCode == Settings.KEY_LOCK.get() );
if ( !ignoresNativeInputs() || hotkeysEnabler ) {
SwingUtilities.invokeLater( new Runnable() {
@Override public void run() {
actions.process( event );
}
} );
}
}
@Override public void nativeKeyReleased( NativeKeyEvent event ) {}
@Override public void nativeKeyTyped( NativeKeyEvent event ) {}
/**
* A property change event might be fired from either the settings
* singleton or the run itself. In either case, we propagate the event to
* our children and update ourself with the new value of the given property.
*/
@Override public void propertyChange( PropertyChangeEvent event ) {
runPane.processPropertyChangeEvent( event );
String property = event.getPropertyName();
if ( Run.STATE_PROPERTY.equals( property ) ) {
MenuItem.setActiveState( run.getState() );
} else if ( Settings.GNR_ATOP.equals( property ) ) {
setAlwaysOnTop( Settings.GNR_ATOP.get() );
} else if (Settings.HST_ROWS.equals(property)
|| Settings.GPH_SHOW.equals(property)
|| Settings.FOO_SHOW.equals(property)
|| Settings.FOO_SPLT.equals(property)
|| Settings.COR_ICSZ.equals(property)
|| Settings.GNR_ACCY.equals(property)
|| Settings.HDR_TTLE.equals(property)
|| Settings.HDR_GOAL.equals(property)
|| Settings.HST_DLTA.equals(property)
|| Settings.HST_SFNT.equals(property)
|| Settings.HST_TFNT.equals(property)
|| Settings.HST_LIVE.equals(property)
|| Settings.HST_MERG.equals(property)
|| Settings.HST_BLNK.equals(property)
|| Settings.HST_ICON.equals(property)
|| Settings.HST_ICSZ.equals(property)
|| Settings.HST_LINE.equals(property)
|| Settings.COR_NAME.equals(property)
|| Settings.COR_SPLT.equals(property)
|| Settings.COR_SEGM.equals(property)
|| Settings.COR_BEST.equals(property)
|| Settings.COR_ICON.equals(property)
|| Settings.COR_TFNT.equals(property)
|| Settings.COR_SFNT.equals(property)
|| Settings.COR_STMR.equals(property)
|| Settings.FOO_BEST.equals(property)
|| Settings.FOO_DLBL.equals(property)
|| Settings.FOO_VERB.equals(property)
|| Settings.FOO_LINE.equals(property)
|| Run.NAME_PROPERTY.equals(property)) {
setPreferredSize(null);
pack();
}
}
/**
* When the run's table of segments is updated, we ask the main panel to
* update itself accordingly and repack the frame as its dimensions may
* have changed.
*/
@Override public void tableChanged( TableModelEvent event ) {
runPane.processTableModelEvent( event );
// No need to recompute the size if we receive a HEADER_ROW UPDATE
// as we only use them when a segment is moved up or down and when
// the user cancel any changes made to his run.
if ( event.getType() == TableModelEvent.UPDATE
&& event.getFirstRow() == TableModelEvent.HEADER_ROW ) {
setPreferredSize( preferredSize );
} else {
setPreferredSize( null );
}
pack();
}
/**
* When the user scrolls the mouse wheel, we update the graph's scale to
* zoom in or out depending on the direction of the scroll.
*/
@Override public void mouseWheelMoved( MouseWheelEvent event ) {
int rotations = event.getWheelRotation();
float percent = Settings.GPH_SCAL.get();
if ( percent == 0.5F ) {
percent = 1.0F;
rotations--;
}
float newValue = Math.max( 0.5F, percent + rotations );
Settings.GPH_SCAL.set( newValue );
}
/**
* When the user clicks on the mouse's right-button, we bring up the
* context menu at the click's location.
*/
@Override public void mousePressed( MouseEvent event ) {
super.mousePressed( event );
if ( SwingUtilities.isRightMouseButton( event ) ) {
popupMenu.show( this, event.getX(), event.getY() );
}
}
/**
* Whenever the frame is being disposed of, we save the settings and
* unregister the native hook of {@code JNativeHook}.
*/
@Override public void windowClosed( WindowEvent event ) {
Settings.save();
GlobalScreen.unregisterNativeHook();
}
@Override public void windowClosing(WindowEvent event) {}
@Override public void windowOpened(WindowEvent event) {}
@Override public void windowActivated(WindowEvent event) {}
@Override public void windowDeactivated(WindowEvent event) {}
@Override public void windowIconified(WindowEvent event) {}
@Override public void windowDeiconified(WindowEvent event) {}
/**
* Action events are fired by clicking on the entries of the context menu.
*/
@Override public synchronized void actionPerformed( final ActionEvent ev ) {
MenuItem source = ( MenuItem ) ev.getSource();
SwingUtilities.invokeLater( new Runnable() {
@Override public void run() {
actions.process( ev );
}
} );
if (source.equals(MenuItem.EDIT)) {
} else if (source.equals(MenuItem.RESIZE_DEFAULT)) {
setPreferredSize(null);
pack();
} else if (source.equals(MenuItem.RESIZE_PREFERRED)) {
setPreferredSize(preferredSize);
pack();
}
}
/**
* Sets the persistent behavior of the application and its components.
*
* @throws IllegalStateException if JNativeHook cannot be registered.
*/
private void setBehavior() {
try {
GlobalScreen.registerNativeHook();
} catch (NativeHookException e) {
throw new IllegalStateException("cannot register native hook");
}
setAlwaysOnTop(Settings.GNR_ATOP.get());
addWindowListener(this);
addMouseWheelListener(this);
Settings.addPropertyChangeListener(this);
GlobalScreen.getInstance().addNativeKeyListener(this);
}
/**
* Initializes the right-click context menu.
*/
private void setMenu() {
popupMenu = MenuItem.getPopupMenu();
MenuItem.addActionListener( this );
MenuItem.populateRecentlyOpened();
}
}

View file

@ -0,0 +1,231 @@
package org.fenix.llanfair;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import java.util.Arrays;
import java.util.List;
import javax.swing.Icon;
import javax.swing.JMenu;
import javax.swing.JMenuItem;
import javax.swing.JPopupMenu;
import javax.swing.JSeparator;
import javax.swing.event.EventListenerList;
import org.fenix.utils.locale.LocaleEvent;
import org.fenix.llanfair.Run.State;
import org.fenix.llanfair.config.Settings;
/**
* Enumerates the menu items available in the right-click context menu of
* Llanfair. An item knows how to look but does not know how to behave. The
* behavior is abstracted in the {@code Actions} class.
*
* @author Xavier "Xunkar" Sencert
* @version 1.1
*/
enum MenuItem implements ActionListener {
EDIT( false, State.NULL, State.READY ),
NEW( true, State.NULL, State.READY, State.STOPPED ),
OPEN( false, State.NULL, State.READY, State.STOPPED ),
OPEN_RECENT( false, State.NULL, State.READY, State.STOPPED ),
IMPORT( false, State.NULL, State.READY, State.STOPPED ),
SAVE( false, State.READY, State.STOPPED ),
SAVE_AS( true, State.READY ),
RESET( true, State.ONGOING, State.STOPPED, State.PAUSED ),
LOCK( false, State.NULL, State.READY, State.STOPPED, State.ONGOING ),
UNLOCK( false, State.NULL, State.READY, State.STOPPED, State.ONGOING ),
RESIZE_DEFAULT( false, State.NULL, State.READY ),
RESIZE_PREFERRED( true, State.NULL, State.READY ),
SETTINGS( true, State.NULL, State.READY, State.STOPPED ),
ABOUT( true, State.NULL, State.READY, State.STOPPED, State.ONGOING ),
EXIT( false, State.NULL, State.READY, State.STOPPED, State.ONGOING );
/**
* Static list of listeners that listen to all the menu items. Whenever
* one item fires an {@code ActionEvent}, the type statically fires an
* other {@code ActionEvent} with the {@code MenuItem} enumerate as the
* source component.
*/
private static EventListenerList listeners = new EventListenerList();
private static final int MAX_FILES = 5;
private static final int TRUNCATE = 30;
private boolean isEndOfGroup;
private List<State> activeStates;
private JMenuItem menuItem;
/**
* Internal constructor used to set the attributes. Only called by the
* enum type itself.
*
* @param isEndOfGroup indicates if this item is a group ender
* @param activeStates list of active run states of this item
*/
private MenuItem( boolean isEndOfGroup, Run.State... activeStates ) {
this.isEndOfGroup = isEndOfGroup;
this.activeStates = Arrays.asList( activeStates );
if ( name().equals( "OPEN_RECENT" ) ) {
menuItem = new JMenu( toString() );
} else {
menuItem = new JMenuItem( toString() );
}
Icon icon = Llanfair.getResources().getIcon( "jmi/" + name() + ".png" );
menuItem.setIcon( icon );
menuItem.addActionListener( this );
// LOCK & UNLOCK mask each other and Llanfair always start unlocked
if ( name().equals( "UNLOCK" ) ) {
menuItem.setVisible( false );
}
}
/**
* Returns a popup menu composed of all the menu items. A separator is
* inserted after each item which are indicated as end of their group.
*
* @return a popup menu containing every menu item
*/
static JPopupMenu getPopupMenu() {
JPopupMenu menu = new JPopupMenu();
for ( MenuItem item : values() ) {
menu.add( item.menuItem );
if ( item.isEndOfGroup ) {
menu.add( new JSeparator() );
}
}
return menu;
}
/**
* Enables or disables every menu items depending on the given run state.
* If this state is present in the list of active states for an item, then
* its GUI component is enabled, else it is disabled.
*
* @param state the current run state, cannot be {@code null}
*/
static void setActiveState( Run.State state ) {
if ( state == null ) {
throw new IllegalArgumentException( "Null run state" );
}
for ( MenuItem item : values() ) {
item.menuItem.setEnabled( item.activeStates.contains( state ) );
}
}
/**
* Registers the given {@code ActionListener} to the list of listeners
* interested in capturing events from every menu items.
*
* @param listener the action listener to register
*/
static void addActionListener( ActionListener listener ) {
listeners.add( ActionListener.class, listener );
}
/**
* Callback to invoke whenever a file is opened. This method will sort the
* recent files menu to put the recently opened file at the top.
*
* @param path the name of the recently opened file
*/
static void recentlyOpened( String path ) {
assert ( path != null );
List<String> recentFiles = Settings.GNR_RCNT.get();
if ( recentFiles.contains( path ) ) {
recentFiles.remove( path );
}
recentFiles.add( 0, path );
if ( recentFiles.size() > MAX_FILES ) {
recentFiles.remove( MAX_FILES );
}
Settings.GNR_RCNT.set( recentFiles );
populateRecentlyOpened();
}
/**
* Fills the {@code OPEN_RECENT} item with the list of recently opened
* files. If {@code MAX_FILES} is somehow lower than the recent files list
* length, the overflowing files are removed.
*/
static void populateRecentlyOpened() {
List<String> recentFiles = Settings.GNR_RCNT.get();
for ( int i = MAX_FILES; i < recentFiles.size(); i++ ) {
recentFiles.remove( i - 1 );
}
OPEN_RECENT.menuItem.removeAll();
for ( String fileName : Settings.GNR_RCNT.get() ) {
String text = fileName;
int index = text.lastIndexOf( File.separatorChar );
if ( index == -1 ) {
int length = text.length();
int start = length - Math.min( length, TRUNCATE );
text = text.substring( start );
if ( start == 0 ) {
text = "[...]" + text;
}
} else {
text = text.substring( index + 1 );
}
JMenuItem jmi = new JMenuItem( text );
jmi.setName( "RECENT" + fileName );
jmi.addActionListener( OPEN_RECENT );
OPEN_RECENT.menuItem.add( jmi );
}
}
/**
* Returns the localized name of this menu item.
*/
@Override public String toString() {
return Language.valueOf( "menuItem_" + name().toLowerCase() ).get();
}
/**
* When a GUI component fires an action event, capture it and fire it
* back, setting the source as the enumerate value instead of the GUI
* component.
*/
@Override public void actionPerformed( ActionEvent event ) {
Object source = event.getSource();
if ( source.equals( LOCK.menuItem ) ) {
LOCK.menuItem.setVisible( false );
UNLOCK.menuItem.setVisible( true );
} else if ( source.equals( UNLOCK.menuItem ) ) {
LOCK.menuItem.setVisible( true );
UNLOCK.menuItem.setVisible( false );
}
JMenuItem jmi = ( JMenuItem ) source;
String name = jmi.getName();
ActionListener[] als = listeners.getListeners( ActionListener.class );
if ( name != null && name.startsWith( "RECENT" ) ) {
event = new ActionEvent(
this, ActionEvent.ACTION_PERFORMED, name.substring( 6 )
);
} else {
event = new ActionEvent(
this, ActionEvent.ACTION_PERFORMED, jmi.getText()
);
}
for ( ActionListener al : als ) {
al.actionPerformed( event );
}
}
/**
* When the locale changes, every menu item must be updated to enforce the
* new locale setting.
*/
public static void localeChanged( LocaleEvent event ) {
for ( MenuItem item : values() ) {
item.menuItem.setText( "" + item );
}
}
}

1151
org/fenix/llanfair/Run.java Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,328 @@
package org.fenix.llanfair;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.Serializable;
import javax.swing.Icon;
import org.fenix.llanfair.Language;
import org.fenix.llanfair.config.Settings;
import org.fenix.utils.Images;
/**
* Represents a portion of a run. As such, a segment is associated to a
* registered time as well as a best time and a live time set on the fly by
* the run owning the segment.
*
* @author Xavier "Xunkar" Sencert
*/
public class Segment implements Cloneable, Serializable {
// -------------------------------------------------------------- CONSTANTS
/**
* The serial version identifier used to determine the compatibility of the
* different serialized versions of this type. This identifier must change
* when modifications that break backward-compatibility are made to the
* type.
*/
private static final long serialVersionUID = 1001L;
/**
* Array of legit display sizes for the segments icons.
*/
public static final Integer[] ICON_SIZES = new Integer[] {
16, 24, 32, 40, 48, 56, 64
};
/**
* Maximum size allowed for the segments icons. When setting an icon for
* a segment, it will be scaled down to that size if its bigger, but will
* not be scaled up if its smaller.
*/
public static final int ICON_MAX_SIZE = ICON_SIZES[ICON_SIZES.length - 1];
/**
* Identifier for the time of the segment as defined by the currently set
* compare method.
*/
public static final int SET = 0;
/**
* Identifier for the registered time of the segment.
*/
public static final int RUN = 1;
/**
* Identifier for the best ever registered time of the segment.
*/
public static final int BEST = 2;
/**
* Identifier for the live time realized on this segment.
*/
public static final int LIVE = 3;
/**
* Identifier for the delta between the live time and the time as defined
* by the currently set compare method, i.e. {@code LIVE - SET}.
*/
public static final int DELTA = 4;
/**
* Identifier for the delta between the live time and the registered time
* of the segment, i.e. {@code LIVE - RUN}.
*/
public static final int DELTA_RUN = 5;
/**
* Identifier for the delta between the live time and the best ever
* registered time of the segment, i.e. {@code LIVE - BEST}.
*/
public static final int DELTA_BEST = 6;
// ------------------------------------------------------------- ATTRIBUTES
/**
* Name of the segment.
*/
private String name;
/**
* Icon associated with this segment. Can be {@code null} if no icon is to
* be displayed.
*/
private Icon icon;
/**
* Registered time for this segment during the best run.
*/
private Time runTime;
/**
* Best time ever registered for this segment.
*/
private Time bestTime;
/**
* Live time realized on this segment during a run. This value is never
* saved as is but can overwrite {@code runTime} or {@code bestTime}.
*/
private transient Time liveTime;
/**
* Number of milliseconds on the clock when the segment started.
*/
private transient long startTime;
// ----------------------------------------------------------- CONSTRUCTORS
/**
* Creates a new segment of given name and undefined times.
*
* @param name - the name of the segment.
*/
public Segment(String name) {
if (name == null) {
throw new NullPointerException("null segment name");
}
this.name = name;
icon = null;
runTime = null;
bestTime = null;
initializeTransients();
}
/**
* Creates a default segment with a default name and undefined times.
*/
public Segment() {
this("" + Language.UNTITLED);
}
// ---------------------------------------------------------------- GETTERS
/**
* Returns the name of the segment.
*
* @return the name of the segment.
*/
public String getName() {
return name;
}
/**
* Returns the icon associated with this segment. Can be {@code null}.
*
* @return the icon of the segment or {@code null}.
*/
public Icon getIcon() {
return icon;
}
/**
* Returns the number of milliseconds on the clock when the segment started.
*
* @return the start time of this segment.
*/
public long getStartTime() {
return startTime;
}
/**
* Returns the given type of time for this segment.
*
* @param type - one of the type identifier.
* @return the segment time of given type.
*/
public Time getTime(int type) {
switch (type) {
case BEST:
return bestTime;
case LIVE:
return liveTime;
case RUN:
return runTime;
case DELTA_RUN:
if (runTime == null) {
return null;
}
return Time.getDelta(liveTime, runTime);
case DELTA_BEST:
if (bestTime == null) {
return null;
}
return Time.getDelta(liveTime, bestTime);
case DELTA:
Time time = getTime();
return (time == null ? null : Time.getDelta(liveTime, time));
default:
return getTime();
}
}
/**
* As specified by {@code Cloneable}, returns a deep copy of the segment.
*/
public Segment clone() {
Segment segment = new Segment(name);
segment.icon = icon;
segment.runTime = (runTime == null ? null : runTime.clone());
segment.bestTime = (bestTime == null ? null : bestTime.clone());
segment.liveTime = (liveTime == null ? null : liveTime.clone());
segment.startTime = startTime;
return segment;
}
// ---------------------------------------------------------------- SETTERS
/**
* Sets the name of the segment to the given string.
*
* @param name - the new name of the segment.
*/
public void setName(String name) {
if (name == null) {
throw new NullPointerException("null name");
}
this.name = name;
}
/**
* Sets the current icon of this segment. Can be {@code null} to remove
* the current icon or indicate that no icon should be used. The icon wil
* be scale down to {@code ICON_MAX_SIZE} if its bigger.
*
* @param icon - the new icon for this segment.
*/
public void setIcon(Icon icon) {
if (icon == null) {
this.icon = null;
} else {
this.icon = Images.rescale(icon, ICON_MAX_SIZE);
}
}
/**
* Sets the number of milliseconds on the clock when the segment started.
* Should only be called by the run owning this segment.
*
* @param startTime - the starting time of this segment.
* @throws IllegalArgumentException if the time is negative.
*/
void setStartTime(long startTime) {
if (startTime < 0L) {
throw new IllegalArgumentException("negative start time");
}
this.startTime = startTime;
}
/**
* Sets the given type of time to the new value. Note that some type of
* times cannot be set (such as {@code DELTA}s.) The new value can be
* {@code null} to indicate undefined times.
*
* @param time - the new time value for the given type.
* @param type - one of the type identifier.
* @throws IllegalArgumentException if the new time value is lower than or
* equal to zero.
*/
public void setTime(Time time, int type, boolean bypass) {
if (!bypass) {
if (time != null && time.compareTo(Time.ZERO) <= 0) {
throw new IllegalArgumentException("" + Language.ILLEGAL_TIME);
}
}
switch (type) {
case BEST: bestTime = time; break;
case LIVE: liveTime = time; break;
case RUN: runTime = time; break;
}
}
public void setTime(Time time, int type) {
setTime(time, type, false);
}
// -------------------------------------------------------------- UTILITIES
/**
* Initialize all transient fields.
*/
private void initializeTransients() {
liveTime = null;
startTime = 0L;
}
/**
* Returns the time of this segment as specified by the currently set
* compare method.
*
* @return the time as defined by the current compare method.
*/
private Time getTime() {
switch (Settings.GNR_COMP.get()) {
case BEST_OVERALL_RUN: return runTime;
case SUM_OF_BEST_SEGMENTS: return bestTime;
}
// Should not be reached.
return null;
}
/**
* Deserialization process. Redefined to initialize transients fields upon
* deserialization.
*/
private void readObject(ObjectInputStream in)
throws IOException, ClassNotFoundException {
in.defaultReadObject();
initializeTransients();
}
}

View file

@ -0,0 +1,298 @@
package org.fenix.llanfair;
import java.io.Serializable;
import org.fenix.llanfair.Language;
import org.fenix.llanfair.config.Accuracy;
import static org.fenix.llanfair.config.Accuracy.HUNDREDTH;
import static org.fenix.llanfair.config.Accuracy.SECONDS;
import static org.fenix.llanfair.config.Accuracy.TENTH;
import org.fenix.llanfair.config.Settings;
/**
* Represents independent time values. Times can be compared between each other
* or to zero using the constant {@link Time.ZERO}. Times can also be displayed
* as a user-friendly string according to a given accuracy. Accuracy is a
* float statically set for every Time objects but that can also be passed as a
* parameter with {@code toString()}.
*
* There exists two ways of representing a time: the standard format (H:M:S) or
* the delta format (+/-H:M:S) if the time object represents a delta between two
* times.
*
* @author Xavier "Xunkar" Sencert
* @version 1.1
*/
public class Time implements Cloneable, Comparable<Time>, Serializable {
/**
* A time of zero milliseconds. This constant can be used for comparisons
* with other times and nothing else.
*/
public static final Time ZERO = new Time();
private static final long serialVersionUID = 1000L;
private long milliseconds;
/**
* Creates a default time of zero milliseconds.
*/
public Time() {
milliseconds = 0L;
}
/**
* Creates a time representing a given number of milliseconds. This number
* is truncated to the milliseconds to prevent discrepencies resulting from
* successive calls to {@link System.currentTimeMillis()} and thus ensures
* that the compare method remains accurate.
*
* @param ms the number of milliseconds to represent
*/
public Time(long ms) {
milliseconds = (ms / 10L) * 10L;
}
/**
* Creates a time representing the given decimal number of seconds. This
* number is truncated to the milliseconds to prevent discrepencies
* resulting from successive calls to {@link System.currentTimeMillis()}
* and thus ensures that the compare method remains accurate.
*
* @param seconds the number of seconds to represent
*/
public Time(double seconds) {
this((long) (seconds * 1000.0));
}
/**
* Creates a time representing the given time-stamp. The time-stamp is a
* string describing the time in a format understandable by the user. The
* time-stamp must be formatted according to the following:
*
* <pre>((H)H:)((M)M:)(S)S(.T(H(M)))</pre>
*
* Where a letter represents a digit and parenthesis indicate optionality.
* H stands for hours, M for minutes, S for seconds, T for tenths, H for
* hundredth and M for milliseconds.
*
* @param timestamp a string representation of the time to parse
* @throws IllegalArgumentException if the timestamp cannot be parsed
*/
public Time(String timeStamp) {
try {
milliseconds = parseTimeStamp(timeStamp);
} catch (Exception ex) {
throw new IllegalArgumentException(
Language.INVALID_TIME_STAMP.get(timeStamp)
);
}
}
/**
* Returns the delta of time between two times. The returned time is
* equivalent to, but more convenient than, the following code:
* {@code new Time(t1.getMilliseconds() - t2.getMilliseconds())}.
*
* @param t1 the first time
* @param t2 the time to substract from the first
* @return the delta of time between the two times
*/
public static Time getDelta(Time t1, Time t2) {
if (t1 == null) {
t1 = new Time();
} else if (t2 == null) {
t2 = new Time();
}
return new Time(t1.milliseconds - t2.milliseconds);
}
/**
* Returns the number of milliseconds represented by that time.
*
* @return the number of milliseconds represented by that time
*/
public long getMilliseconds() {
return milliseconds;
}
/**
* Adds the given time to this time. The number of milliseconds
* represented by this time object is now equals to:
* {@code getMilliseconds() + time.getMilliseconds()}
*
* @param time the time object to add to this time
*/
public void add(Time time) {
milliseconds += (time == null ? 0L : time.milliseconds);
}
public String toString(boolean signed, Accuracy accuracy) {
if (signed) {
return (milliseconds > 0L ? "+" : "-") + toString(false, accuracy);
}
long time = Math.abs(milliseconds);
long cen = (time % 1000L) / 10L;
long sec;
// Round to the nearest tenth.
if (accuracy == Accuracy.TENTH) {
cen = Math.round((double) cen / 10L);
if (cen == 10L) {
cen = 0L;
time = time + 1000L;
}
}
// Round to the nearest second.
if (accuracy == Accuracy.SECONDS) {
sec = Math.round((double) time / 1000);
} else {
sec = time / 1000L;
}
long min = sec / 60L;
sec = sec % 60L;
long hou = min / 60L;
min = min % 60L;
if (hou == 0L) {
if (min == 0L) {
switch (accuracy) {
case HUNDREDTH:
return String.format("%d.%02d", sec, cen);
case TENTH:
return String.format("%d.%d", sec, cen);
case SECONDS:
return String.format("%d", sec);
}
}
switch (accuracy) {
case HUNDREDTH:
return String.format("%d:%02d.%02d", min, sec, cen);
case TENTH:
return String.format("%d:%02d.%d", min, sec, cen);
case SECONDS:
return String.format("%d:%02d", min, sec);
}
}
switch (accuracy) {
case HUNDREDTH:
return String.format("%d:%02d:%02d.%02d", hou, min, sec, cen);
case TENTH:
return String.format("%d:%02d:%02d.%d", hou, min, sec, cen);
case SECONDS:
return String.format("%d:%02d:%02d", hou, min, sec);
}
// Should not be reachable.
return null;
}
/**
* A time represents itself as a string using the traditional format
* {@code H:M:S}. The global accuracy determines the presence of tenths
* or hundredths of a second and if the get should be rounded. Every digit
* of higher-order than a second is only displayed if necessary. The sign
* parameter determines if the string should be preceded by a plus or minus
* sign depending on the number of milliseconds. If not, only the absolute
* number is used.
*
* @param signed if this time is to be displayed as a delta of time
* @return a string representation of this time object
*/
public String toString(boolean signed) {
return toString(signed, Settings.GNR_ACCY.get());
}
/**
* A time represents itself as a string using the traditional format
* {@code H:M:S}. The accuracy parameter determines the presence of tenths
* or hundredths of a second and if the get should be rounded. Every digit
* of higher-order than a second is only displayed if necessary.
*
* @param accuracy the target accuracy to display this time in
* @return a string representation of this time object
*/
public String toString(Accuracy accuracy) {
return toString(false, accuracy);
}
/**
* A time represents itself as a string using the traditional format
* {@code H:M:S}. The accuracy setting determines the presence of tenths
* or hundredths of a second and if the get should be rounded. Every digit
* of higher-order than a second is only displayed if necessary.
*
* @return a string representation of this time object
*/
@Override public String toString() {
return toString(false);
}
/**
* {@inheritDoc} Returns a deep-copy of this time object.
*/
@Override public Time clone() {
return new Time(milliseconds);
}
/**
* {@inheritDoc} Two time objects are equal if and only if they represent
* the same amount of milliseconds.
*/
@Override public boolean equals(Object obj) {
if (!(obj instanceof Time)) {
return false;
}
Time time = (Time) obj;
return (milliseconds == time.milliseconds);
}
/**
* {@inheritDoc} The hash code of a time object is its number of
* milliseconds cast into an integer get. As such, the load factor of a
* table storing time objects is floored by {@code Integer#MAX_VALUE}.
*/
@Override public int hashCode() {
return (int) milliseconds;
}
/**
* {@inheritDoc} Time objects are compared using their amount of
* milliseconds
*/
@Override public int compareTo(Time time) {
if (time == null) {
return -1;
}
return ((Long) milliseconds).compareTo(time.milliseconds);
}
/**
* Parses a given time-stamp and converts it to a number of milliseconds.
*
* @param timestamp the timestamp to parse
* @return the number of milliseconds represented by the stamp
*/
private long parseTimeStamp(String timestamp) {
String seconds = timestamp;
long millis = 0L;
// Hours or minutes in the stamp.
if (timestamp.contains(":")) {
String[] split = timestamp.split(":");
if (split.length > 3) {
throw new IllegalArgumentException();
}
// We keep the number of seconds and lower for later.
seconds = split[split.length - 1];
for (int i = 0; i < split.length - 1; i++) {
long power = (long) Math.pow(60, split.length - i - 1);
millis += Long.valueOf(split[i]) * power * 1000L;
}
}
double value = Double.parseDouble(seconds);
return (millis + (long) (value * 1000.0));
}
}

View file

@ -0,0 +1,20 @@
package org.fenix.llanfair.config;
import java.io.Serializable;
import org.fenix.llanfair.Language;
public enum Accuracy implements Serializable {
SECONDS,
TENTH,
HUNDREDTH;
private static final long serialVersionUID = 1000L;
@Override public String toString() {
return Language.valueOf("accuracy_" + name().toLowerCase()).get();
}
}

View file

@ -0,0 +1,22 @@
package org.fenix.llanfair.config;
import java.io.Serializable;
import org.fenix.llanfair.Language;
/**
*
* @author Xavier
*/
public enum Compare implements Serializable {
BEST_OVERALL_RUN,
SUM_OF_BEST_SEGMENTS;
private static final long serialVersionUID = 1000L;
@Override public String toString() {
return Language.valueOf(
"compare_" + name().toLowerCase().replaceAll("\\.", "_")
).get();
}
}

View file

@ -0,0 +1,21 @@
package org.fenix.llanfair.config;
import java.io.Serializable;
import org.fenix.llanfair.Language;
/**
*
* @author Xavier
*/
public enum Merge implements Serializable {
NONE,
LIVE,
DELTA;
private static final long serialVersionUID = 1000L;
@Override public String toString() {
return Language.valueOf("merge_" + name().toLowerCase()).get();
}
}

View file

@ -0,0 +1,428 @@
package org.fenix.llanfair.config;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Point;
import java.beans.PropertyChangeListener;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import org.fenix.llanfair.Language;
import org.fenix.llanfair.Run;
import org.fenix.utils.config.Configuration;
/**
* Stores the configuration of Llanfair. This class actually provides two levels
* of configuration. The <em>global</em> configuration is read from the file
* {@code llanfair.xml} stored in the working directory. By default, every
* property uses their global values. The <em>local</em> configuration is
* read from a run file and passed down by the main class. Local values have a
* higher priority and thus are always used instead of global values if
* defined. This allows users to set properties on a per-run basis.
*
* @author Xavier "Xunkar" Sencert
* @version 1.3
*/
public class Settings {
/* GENERIC properties */
public static final Property<Boolean> GNR_ATOP =
new Property<Boolean>( "alwaysOnTop" );
public static final Property<Locale> GNR_LANG =
new Property<Locale>( "language" );
public static final Property<Locale> GNR_VLNG =
new Property<Locale>( "viewerLanguage" );
public static final Property<List<String>> GNR_RCNT =
new Property<List<String>>( "recentFiles" );
public static final Property<Point> GNR_COOR =
new Property<Point>( "coordinates" );
public static final Property<Dimension> GNR_SIZE =
new Property<Dimension>( "dimension" );
public static final Property<Compare> GNR_COMP =
new Property<Compare>( "compareMethod" );
public static final Property<Accuracy> GNR_ACCY =
new Property<Accuracy>( "accuracy" );
public static final Property<Boolean> GNR_WARN =
new Property<Boolean>( "warnOnReset" );
/* COLOR properties */
public static final Property<Color> CLR_BACK =
new Property<Color>( "color.background" );
public static final Property<Color> CLR_FORE =
new Property<Color>( "color.foreground" );
public static final Property<Color> CLR_TIME =
new Property<Color>( "color.time" );
public static final Property<Color> CLR_TIMR =
new Property<Color>( "color.timer" );
public static final Property<Color> CLR_GAIN =
new Property<Color>( "color.timeGained" );
public static final Property<Color> CLR_LOST =
new Property<Color>( "color.timeLost" );
public static final Property<Color> CLR_RCRD =
new Property<Color>( "color.newRecord" );
public static final Property<Color> CLR_TITL =
new Property<Color>( "color.title" );
public static final Property<Color> CLR_HIGH =
new Property<Color>( "color.highlight" );
public static final Property<Color> CLR_SPRT =
new Property<Color>( "color.separators" );
/* HOTKEY properties */
public static final Property<Integer> KEY_SPLT =
new Property<Integer>( "hotkey.split" );
public static final Property<Integer> KEY_USPL =
new Property<Integer>( "hotkey.unsplit" );
public static final Property<Integer> KEY_SKIP =
new Property<Integer>( "hotkey.skip" );
public static final Property<Integer> KEY_RSET =
new Property<Integer>( "hotkey.reset" );
public static final Property<Integer> KEY_STOP =
new Property<Integer>( "hotkey.stop" );
public static final Property<Integer> KEY_PAUS =
new Property<Integer>( "hotkey.pause" );
public static final Property<Integer> KEY_LOCK =
new Property<Integer>( "hotkey.lock" );
/* HEADER properties */
public static final Property<Boolean> HDR_TTLE =
new Property<Boolean>( "header.goal" );
public static final Property<Boolean> HDR_GOAL =
new Property<Boolean>( "header.title" );
/* HISTORY properties */
public static final Property<Integer> HST_ROWS =
new Property<Integer>( "history.rowCount" );
public static final Property<Boolean> HST_TABL =
new Property<Boolean>( "history.tabular" );
public static final Property<Boolean> HST_BLNK =
new Property<Boolean>( "history.blankRows" );
public static final Property<Boolean> HST_LINE =
new Property<Boolean>( "history.multiline" );
public static final Property<Merge> HST_MERG =
new Property<Merge>( "history.merge" );
public static final Property<Boolean> HST_LIVE =
new Property<Boolean>( "history.liveTimes" );
public static final Property<Boolean> HST_DLTA =
new Property<Boolean>( "history.deltas" );
public static final Property<Boolean> HST_ICON =
new Property<Boolean>( "history.icons" );
public static final Property<Integer> HST_ICSZ =
new Property<Integer>( "history.iconSize" );
public static final Property<Integer> HST_OFFS =
new Property<Integer>( "history.offset" );
public static final Property<Boolean> HST_LAST =
new Property<Boolean>( "history.alwaysShowLast" );
public static final Property<Font> HST_SFNT =
new Property<Font>( "history.segmentFont" );
public static final Property<Font> HST_TFNT =
new Property<Font>( "history.timeFont" );
/* CORE properties */
public static final Property<Accuracy> COR_ACCY =
new Property<Accuracy>( "core.accuracy" );
public static final Property<Boolean> COR_ICON =
new Property<Boolean>( "core.icons" );
public static final Property<Integer> COR_ICSZ =
new Property<Integer>( "core.iconSize" );
public static final Property<Boolean> COR_NAME =
new Property<Boolean>( "core.segmentName" );
public static final Property<Boolean> COR_SPLT =
new Property<Boolean>( "core.splitTime" );
public static final Property<Boolean> COR_SEGM =
new Property<Boolean>( "core.segmentTime" );
public static final Property<Boolean> COR_BEST =
new Property<Boolean>( "core.bestTime" );
public static final Property<Boolean> COR_STMR =
new Property<Boolean>( "core.segmentTimer" );
public static final Property<Font> COR_TFNT =
new Property<Font>( "core.timerFont" );
public static final Property<Font> COR_SFNT =
new Property<Font>( "core.segmentTimerFont" );
/* GRAPH properties */
public static final Property<Boolean> GPH_SHOW =
new Property<Boolean>( "graph.display" );
public static final Property<Float> GPH_SCAL =
new Property<Float>( "graph.scale" );
/* FOOTER properties */
public static final Property<Boolean> FOO_SHOW =
new Property<Boolean>( "footer.display" );
public static final Property<Boolean> FOO_SPLT =
new Property<Boolean>( "footer.useSplitData" );
public static final Property<Boolean> FOO_VERB =
new Property<Boolean>( "footer.verbose" );
public static final Property<Boolean> FOO_BEST =
new Property<Boolean>( "footer.bestTime" );
public static final Property<Boolean> FOO_LINE =
new Property<Boolean>( "footer.multiline" );
public static final Property<Boolean> FOO_DLBL =
new Property<Boolean>( "footer.deltaLabels" );
private static Configuration global = null;
private static Run run = null;
/**
* Sets the currently opened run. The run will be asked for its local
* configuration first when retrieving a property. If the run does not have
* the given property its value will be taken from the global configuration.
*
* @param run the run currently viewed by Llanfair, cannot be {@code null}
*/
public static void setRun( Run run ) {
if ( run == null ) {
throw new NullPointerException( "Null run" );
}
Settings.run = run;
}
/**
* Returns a list of all the properties whose key starts with the given
* prefix. If given an empty string, every properties are returned.
*
* @param prefix the prefix for which a list of property is asked
* @return the list of all properties whose key starts with the prefix
*/
public static List<Property<?>> getAll( String prefix ) {
if ( prefix == null ) {
throw new NullPointerException( "Null prefix string" );
}
List<Property<?>> list = new ArrayList<Property<?>>();
for ( Property<?> property : Property.P ) {
if ( property.key.startsWith( prefix ) ) {
list.add( property );
}
}
return list;
}
/**
* Registers a new {@code PropertyChangeListener} with the settings.
* Whenever a property sees its value updated globally or locally, listeners
* are warned of the update.
*
* @param pcl the listener to register with the settings
*/
public static void addPropertyChangeListener( PropertyChangeListener pcl ) {
if ( pcl == null ) {
throw new NullPointerException( "Null property listener" );
}
global.addPropertyChangeListener( pcl );
if ( run != null ) {
run.addSettingChangeListener( pcl );
}
}
/**
* Saves the global configuration in {@code llanfair.xml} in the working
* directory. If such a file does not exist, it is created.
*/
public static void save() {
global.serialize();
}
/**
* Retrieves the configuration of Llanfair. The configuration is read from
* {@code llanfair.xml} placed in the working directory. If such a file
* cannot be found, a default configuration is loaded. No local
* configuration is loaded here, a call to {@code setRun} is required to
* do just that. This method is lenient and called by the first property
* whose value is requested.
*/
private static void retrieve() {
global = Configuration.newInstance( new File( "./llanfair.xml" ) );
if ( global.isEmpty() ) {
setDefaultValues();
}
}
/**
* Fills the global configuration with every property, assigning them their
* default value. This method can be called even when the global
* configuration is not empty, and will thus function as a reset.
*/
private static void setDefaultValues() {
global.put( GNR_ATOP.key, true );
global.put( GNR_LANG.key, Locale.ENGLISH );
global.put( GNR_VLNG.key, Locale.ENGLISH );
global.put( GNR_RCNT.key, new ArrayList<String>() );
global.put( GNR_COOR.key, null );
global.put( GNR_SIZE.key, null );
global.put( GNR_COMP.key, Compare.BEST_OVERALL_RUN );
global.put( GNR_ACCY.key, Accuracy.TENTH );
global.put( GNR_WARN.key, true );
global.put( CLR_BACK.key, Color.decode( "0x000000" ) );
global.put( CLR_FORE.key, Color.decode( "0xc0c0c0" ) );
global.put( CLR_TIME.key, Color.decode( "0xffffff" ) );
global.put( CLR_TIMR.key, Color.decode( "0x22cc22" ) );
global.put( CLR_GAIN.key, Color.decode( "0x6295fc" ) );
global.put( CLR_LOST.key, Color.decode( "0xe82323" ) );
global.put( CLR_RCRD.key, Color.decode( "0xf0b012" ) );
global.put( CLR_TITL.key, Color.decode( "0xf0b012" ) );
global.put( CLR_HIGH.key, Color.decode( "0xffffff" ) );
global.put( CLR_SPRT.key, Color.decode( "0x666666" ) );
global.put( KEY_SPLT.key, -1 );
global.put( KEY_USPL.key, -1 );
global.put( KEY_SKIP.key, -1 );
global.put( KEY_RSET.key, -1 );
global.put( KEY_STOP.key, -1 );
global.put( KEY_PAUS.key, -1 );
global.put( KEY_LOCK.key, -1 );
global.put( HDR_TTLE.key, true );
global.put( HDR_GOAL.key, true );
global.put( HST_ROWS.key, 8 );
global.put( HST_TABL.key, true );
global.put( HST_BLNK.key, false );
global.put( HST_LINE.key, false );
global.put( HST_MERG.key, Merge.LIVE );
global.put( HST_LIVE.key, true );
global.put( HST_DLTA.key, true );
global.put( HST_ICON.key, true );
global.put( HST_ICSZ.key, 16 );
global.put( HST_OFFS.key, 0 );
global.put( HST_LAST.key, true );
global.put( HST_SFNT.key, Font.decode( "Arial-12" ) );
global.put( HST_TFNT.key, Font.decode( "Arial-11" ) );
global.put( COR_ACCY.key, Accuracy.HUNDREDTH );
global.put( COR_ICON.key, true );
global.put( COR_ICSZ.key, 40 );
global.put( COR_NAME.key, true );
global.put( COR_SPLT.key, false );
global.put( COR_SEGM.key, true );
global.put( COR_BEST.key, true );
global.put( COR_STMR.key, true );
global.put( COR_TFNT.key, Font.decode( "Digitalism-32" ) );
global.put( COR_SFNT.key, Font.decode( "Digitalism-18" ) );
global.put( GPH_SHOW.key, true );
global.put( GPH_SCAL.key, 3.0F );
global.put( FOO_SHOW.key, true );
global.put( FOO_VERB.key, true );
global.put( FOO_SPLT.key, false );
global.put( FOO_BEST.key, true );
global.put( FOO_LINE.key, true );
global.put( FOO_DLBL.key, true );
}
/**
* Polymorphic property object. It is merely a string identifying the
* property and serving as an interface to the configuration. When asked
* for its value, the property will return the global value unless a local
* value has been defined. Will return a statically typecasted value.
*
* @param <T> the type of the values of the property
* @author Xavier "Xunkar" Sencert
* @version 1.0
*/
public static class Property<T> {
private static final List<Property<?>> P = new ArrayList<Property<?>>();
private String key;
/**
* Creates a new property of given key. If the key contains a dot, the
* property name is interpreted as {@code section.key} allowing callers
* to grab a submap of the configuration with all properties starting
* with {@code section}.
*
* @param fullKey the name of the property
*/
private Property( String fullKey ) {
this.key = fullKey;
P.add( this );
}
/**
* Returns the key string of this property.
*
* @return the key string of this property
*/
public String getKey() {
return key;
}
/**
* Returns the value assigned to this property. The value will be first
* read from the local configuration, and if no value has been defined
* for this property, it will be read from the global configuration. The
* first property to call this method will trigger the global
* configuration to be read and loaded in memory.
*
* @return the local value of this property, or the global one if there
* isn't a locally defined value
*/
public T get() {
if ( global == null ) {
retrieve();
}
if ( run != null && run.containsSetting( key ) ) {
return run.<T>getSetting( key );
}
return global.<T>get( key );
}
/**
* Sets the value of this property in the global configuration.
*
* @param value the value to assign to this property
*/
public void set( T value ) {
set( value, false );
}
/**
* Sets the value of this property. The value of {@code locally}
* determines if the value must be stored in the local or global
* configuration.
*
* @param valuethe value to assign to this property
* @param locally if the value must be stored in the local configuration
*/
public void set( T value, boolean locally ) {
if ( locally ) {
run.putSetting( key, value );
} else {
global.put( key, value );
}
}
/**
* Compares a property to a given string. This property is equal to
* the given string if and only if the string is equal to the full key
* of this property.
*
* @param str the string to compare this property against
* @return {@code true} if the string equals this property full key
*/
public boolean equals( String str ) {
return key.equals( str );
}
/**
* Returns the localized name of this property.
*/
@Override public String toString() {
return Language.valueOf(
"setting_" + key.replaceAll( "\\.", "_" )
).get();
}
}
}

View file

@ -0,0 +1,512 @@
package org.fenix.llanfair.dialog;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.GridBagLayout;
import java.awt.Rectangle;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import javax.swing.AbstractCellEditor;
import javax.swing.DefaultCellEditor;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JFileChooser;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.JTextField;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.filechooser.FileNameExtensionFilter;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.JTableHeader;
import javax.swing.table.TableCellEditor;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableColumn;
import org.fenix.llanfair.Language;
import org.fenix.llanfair.Llanfair;
import org.fenix.llanfair.Run;
import org.fenix.llanfair.Segment;
import org.fenix.llanfair.Time;
import org.fenix.utils.gui.GBC;
/**
* Boîte de dialogue permettant lédition dune course. {@code EditDialog}
* permet de modifier le titre de la course, dajouter ou de retirer des
* segments et déditer licône, le nom ainsi que les temps de chaque segment.
*
* @author Xavier "Xunkar" Sencert
* @see AbstractDialog
* @see Run
*/
public class EditRun extends LlanfairDialog
implements ActionListener, ListSelectionListener {
// ------------------------------------------------------------- CONSTANTES
/**
* Largeurs en pixels des colonnes de la table dédition des segments. Les
* largeurs sont données dans lodre du modèle.
*/
private static final int[] TABLE_COLUMN_WIDTHS = new int[] {
Segment.ICON_MAX_SIZE, 130, 100, 100, 100
};
/**
* Dimension dun petit bouton dont létiquette nest quun caractère.
*/
private static final Dimension SMALL_BUTTON_SIZE = new Dimension(40, 25);
// -------------------------------------------------------------- ATTRIBUTS
/**
* Course éditée par cette boîte de dialogue.
*/
private Run run;
/**
* Zone dédition du titre de la course.
*/
private JTextField runTitle;
/**
* Étiquette du {@link runTitle}.
*/
private JLabel runTitleLabel;
/**
* Table dédition des segments de la course.
*/
private JTable segments;
/**
* Panneau avec ascenseur dans lequel est inséré la table {@link segments}.
*/
private JScrollPane scrollPane;
/**
* Étiquette de {@code segments}.
*/
private JLabel segmentsLabel;
/**
* Bouton permettant dinsérer un nouveau segment.
*/
private JButton addSegment;
/**
* Bouton permettant de supprimer un segment.
*/
private JButton remSegment;
private JCheckBox segmented;
/**
* Bouton permettant denregistrer lédition et de quitter le dialogue.
*/
private JButton save;
/**
* Bouton permettant de quitter le dialogue sans modifier la course.
*/
private JButton cancel;
/**
* Button moving the currently selected segment one position up.
*/
private JButton moveUp;
/**
* Button moving the currently selected segment one position down.
*/
private JButton moveDown;
private JTextField runGoal;
// ----------------------------------------------------------- CONSTRUCTEURS
/**
* Création dune boîte de dialogue permettant déditer la course fournie.
*
* @param run - la course a éditer.
*/
public EditRun(Run run) {
super();
if (run == null) {
throw new NullPointerException("EditDialog.EditDialog(): null run");
}
this.run = run;
run.saveBackup();
setTitle(Language.EDITING.get());
runTitle = new JTextField(run.getName(), 61);
runTitleLabel = new JLabel(Language.RUN_TITLE.get());
runGoal = new JTextField(run.getGoal(), 48);
segments = new JTable(run) {
@Override protected JTableHeader createDefaultTableHeader() {
return new JTableHeader(columnModel) {
@Override public String getToolTipText(MouseEvent event) {
int col = columnModel.getColumnIndexAtX(event.getX());
int ind = columnModel.getColumn(col).getModelIndex();
switch (ind) {
case Run.COLUMN_BEST:
return "" + Language.TT_COLUMN_BEST;
case Run.COLUMN_SEGMENT:
return "" + Language.TT_COLUMN_SEGMENT;
case Run.COLUMN_TIME:
return "" + Language.TT_COLUMN_TIME;
default:
return null;
}
}
};
}
};
segmentsLabel = new JLabel("" + Language.SEGMENTS);
addSegment = new JButton(Llanfair.getResources().getIcon("PLUS"));
remSegment = new JButton(Llanfair.getResources().getIcon("MINUS"));
save = new JButton("" + Language.menuItem_save);
cancel = new JButton("" + Language.CANCEL);
scrollPane = new JScrollPane(segments);
moveUp = new JButton(Llanfair.getResources().getIcon("ARROW_UP"));
moveDown = new JButton(Llanfair.getResources().getIcon("ARROW_DOWN"));
segmented = new JCheckBox("" + Language.ED_SEGMENTED, run.isSegmented());
placeComponents();
setBehavior();
}
// ---------------------------------------------------------------- MÉTHODES
/**
* Dispose les sous-composants au sein de la boîte de dialogue.
*/
private void placeComponents() {
setLayout(new GridBagLayout());
add(runTitleLabel, GBC.grid(0, 0).insets(4, 4, 0, 4));
add(runTitle, GBC.grid(1, 0, 3, 1).insets(4, 0, 0, 4).anchor(GBC.LS));
add(new JLabel("" + Language.LB_GOAL), GBC.grid(0, 1).insets(4, 4, 0, 4));
add(runGoal, GBC.grid(1, 1).insets(4, 0, 0, 4).anchor(GBC.LS));
add(segmented, GBC.grid(2, 1, 2, 1).insets(4, 0, 0, 4).anchor(GBC.LS));
add(segmentsLabel, GBC.grid(0, 2, 4, 1).insets(5, 4, 4, 0)
.anchor(GBC.BL));
add(scrollPane, GBC.grid(0, 3, 3, 4).insets(0, 4, 0, 0));
add(addSegment, GBC.grid(3, 3).insets(0, 4).anchor(GBC.FLS));
add(remSegment, GBC.grid(3, 4).insets(4, 4).anchor(GBC.FLS));
add(moveUp, GBC.grid(3, 5).insets(0, 4).anchor(GBC.FLS));
add(moveDown, GBC.grid(3, 6).insets(4, 4).anchor(GBC.FLS));
JPanel controls = new JPanel();
controls.add(save);
controls.add(cancel);
add(controls, GBC.grid(0, 7, 4, 1));
}
/**
* Définit le comportement des sous-composants du dialogue.
*/
private void setBehavior() {
// Ne pas permettre la fermeture du dialogue par la croix pour forcer
// lutilisateur à utiliser les boutons save et cancel.
setDefaultCloseOperation(DO_NOTHING_ON_CLOSE);
// Définir les dimensions des composants.
setResizable(false);
addSegment.setPreferredSize(SMALL_BUTTON_SIZE);
remSegment.setPreferredSize(SMALL_BUTTON_SIZE);
moveUp.setPreferredSize(SMALL_BUTTON_SIZE);
moveDown.setPreferredSize(SMALL_BUTTON_SIZE);
segments.setRowHeight(Segment.ICON_MAX_SIZE);
for (int i = 0; i < run.getColumnCount(); i++) {
TableColumn column = segments.getColumnModel().getColumn(i);
column.setPreferredWidth(TABLE_COLUMN_WIDTHS[i]);
}
int totalWidth = 0;
for (int width : TABLE_COLUMN_WIDTHS) {
totalWidth += width;
}
Dimension size = new Dimension(totalWidth, Segment.ICON_MAX_SIZE * 5);
segments.setPreferredScrollableViewportSize(size);
// Enregistrement des écouteurs des composants.
addSegment.addActionListener(this);
remSegment.addActionListener(this);
moveUp.addActionListener(this);
moveDown.addActionListener(this);
cancel.addActionListener(this);
save.addActionListener(this);
// Insertion des délégués de rendus et dédition.
segments.setDefaultRenderer(Icon.class, new IconRenderer());
segments.setDefaultEditor(Icon.class, new FileChooserEditor(this));
segments.setDefaultEditor(Time.class, new TimeEditor());
// Ajuster le comportement de la table.
segments.setAutoResizeMode(JTable.AUTO_RESIZE_LAST_COLUMN);
segments.putClientProperty("terminateEditOnFocusLost", Boolean.TRUE);
segments.getSelectionModel().addListSelectionListener(this);
addSegment.setToolTipText("" + Language.TT_ADD_SEGMENT);
remSegment.setToolTipText("" + Language.TT_REMOVE_SEGMENT);
moveDown.setToolTipText("" + Language.TT_MOVE_SEGMENT_DOWN);
moveUp.setToolTipText("" + Language.TT_MOVE_SEGMENT_UP);
segmented.setToolTipText("" + Language.TT_ED_SEGMENTED);
updateButtons();
}
/**
* Procédure à invoquer lorsque ce composant réalise une action. Sont donc
* capturés ici, tous les évènements daction des sous-composants, comme
* lappui sur un bouton.
*
* @param event - lévènement daction.
* @see ActionListener
*/
@Override public void actionPerformed(ActionEvent event) {
Object source = event.getSource();
if (source.equals(addSegment)) {
run.addSegment(new Segment());
Rectangle rect = segments.getCellRect(run.getRowCount() - 1, 0, true);
segments.scrollRectToVisible(rect);
updateButtons();
} else if (source.equals(remSegment)) {
run.removeSegment(segments.getSelectedRow());
updateButtons();
} else if (source.equals(save)) {
run.setName(runTitle.getText());
run.setGoal(runGoal.getText());
run.setSegmented(segmented.isSelected());
dispose();
} else if (source.equals(cancel)) {
if (!segments.isEditing()) {
run.loadBackup();
dispose();
}
} else if (source.equals(moveUp)) {
int selected = segments.getSelectedRow();
run.moveSegmentUp(selected);
segments.setRowSelectionInterval(selected - 1, selected - 1);
} else if (source.equals(moveDown)) {
int selected = segments.getSelectedRow();
run.moveSegmentDown(selected);
segments.setRowSelectionInterval(selected + 1, selected + 1);
}
}
/**
* Méthode invoquée lors dun changement de sélection dans la table.
*
* @param event - lévènement de sélection.
*/
public void valueChanged(ListSelectionEvent event) {
if (!event.getValueIsAdjusting()) {
updateButtons();
}
}
private void updateButtons() {
int selected = segments.getSelectedRow();
boolean enabled = (selected >= 0);
remSegment.setEnabled(enabled);
moveUp.setEnabled(enabled && selected > 0);
moveDown.setEnabled(enabled && selected < run.getRowCount() - 1);
}
// ---------------------------------------------------------- CLASSE INTERNE
/**
* Gestionnaire de rendu capable dafficher une valeur de type {@link Icon}
* au sein dune table.
*
* @author Xavier Sencert
* @see TableCellRenderer
*/
private class IconRenderer extends DefaultTableCellRenderer {
/**
* Construction dun gestionnaire de rendu dicônes.
*/
public IconRenderer() {
super();
}
/**
* Retourne le composant de rendu à afficher dans la table. Le composant
* est ici préparé selon la valeur de la cellule et différentes
* informations sur létat du composant.
*/
@Override public Component getTableCellRendererComponent(JTable table,
Object value, boolean isSelected, boolean hasFocus, int row,
int column) {
// Préparer le label comme un label par défaut.
JLabel label = (JLabel) super.getTableCellRendererComponent(
table, value, isSelected, hasFocus, row, column);
// Modifier licône du label par celle de la valeur.
label.setIcon((value == null) ? null : (Icon) value);
label.setText("");
label.setHorizontalAlignment(JLabel.CENTER);
return label;
}
}
// ---------------------------------------------------------- CLASSE INTERNE
/**
* Délégué dédition des temps de course et de segments. Cette classe permet
* de récupérer les exceptions potentiellement levés lorsque lédition se
* termine et que la valeur est modifié dans la table des segments.
*
* @author Xavier "Xunkar" Sencert
*/
private class TimeEditor extends DefaultCellEditor {
/**
* Composant dédition. Conservé ici en doublon pour éviter de devoir le
* caster à chaque fois.
*/
private JTextField editor;
/**
* Construction dun délégué par défaut.
*/
public TimeEditor() {
super(new JTextField());
editor = (JTextField) getComponent();
}
/**
* Retourne la valeur entrée par lutilisateur.
*
* @return la valeur entrée par lutilisateur.
*/
public Object getCellEditorValue() {
String text = editor.getText();
return text.equals("") ? null : new Time(text);
}
/**
* Retourne le composant dédition, formatté comme il se doit selon les
* informations de la table et de la cellule.
*
* @param table - la table source demandant lédition.
* @param get - la valeur actuellement présente à représenter.
* @param isSelected - indique si la ligne est sélectionnée.
* @param row - indice de ligne la cellule éditée.
* @param column - indice de colonne de la cellule éditée.
* @return le composant dédition.
*/
public Component getTableCellEditorComponent(JTable table, Object value,
boolean isSelected, int row, int column) {
editor.setText(value == null ? "" : value.toString());
editor.selectAll();
return editor;
}
/**
* Arrête lédition de la cellule. Le délégué récupère ici les exceptions
* levées et les remonte à lutilisateur. Tant que lédition est en erreur
* lédition persiste.
*
* @return {@code true} si lédition sest arrêtée.
*/
public boolean stopCellEditing() {
try {
return super.stopCellEditing();
} catch (Exception e) {
JOptionPane.showMessageDialog(editor, e.getMessage(),
Language.ERROR.get(), JOptionPane.ERROR_MESSAGE);
return false;
}
}
}
// ---------------------------------------------------------- CLASSE INTERNE
/**
* Éditeur de cellules permettant de sélectionner un fichier via un
* {@code JFileChooser}.
*
* @author Xavier Sencert
* @see TableCellEditor
* @see ActionListener
*/
private class FileChooserEditor extends AbstractCellEditor
implements TableCellEditor, ActionListener {
/**
* Gestionnaire de sélection de fichier.
*/
private JFileChooser chooser;
/**
* Composant dédition de léditeur. Il sagit dun {@code JButton}
* ce qui permet de capturer le clic de lutilisateur et ainsi douvrir
* le gestionnaire de sélection de fichier.
*/
private JButton editor;
private Window owner;
/**
* Création dun éditeur par défaut.
*/
public FileChooserEditor(Window owner) {
super();
this.owner = owner;
chooser = new JFileChooser(".");
editor = new JButton();
chooser.setFileFilter(new FileNameExtensionFilter(
"" + Language.IMAGE, "gif", "jpg", "jpeg", "png"));
editor.addActionListener(this);
}
/**
* Retourne le composant dédition à afficher au sein de la table, il
* sagit donc du bouton {@link #editor}.
*/
public Component getTableCellEditorComponent(JTable table, Object value,
boolean isSelected, int row, int column) {
return editor;
}
/**
* Retourne la valeur stockée par léditeur à savoir le fichier
* sélectionner par lutilisateur sil y en a un.
*/
public Object getCellEditorValue() {
if (chooser.getSelectedFile() == null) {
return null;
}
return new ImageIcon(chooser.getSelectedFile().getPath());
}
/**
* Lors du clic de lutilisateur sur le bouton, on affiche le
* gestionnaire de sélection de fichier puis lon force la fin de
* lédition lorsque celui-ci retourne.
*/
@Override public void actionPerformed(ActionEvent e) {
chooser.showOpenDialog(owner);
fireEditingStopped();
}
}
}

View file

@ -0,0 +1,153 @@
package org.fenix.llanfair.dialog;
import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowEvent;
import java.awt.event.WindowListener;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JButton;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JTabbedPane;
import org.fenix.llanfair.Language;
import org.fenix.llanfair.config.Settings;
import org.fenix.utils.gui.GBC;
import org.fenix.utils.locale.LocaleDelegate;
import org.fenix.utils.locale.LocaleEvent;
import org.fenix.utils.locale.LocaleListener;
/**
* ConfigDialog
*
* @author Xavier Sencert
* @date 22 juil. 2012
*/
public class EditSettings extends LlanfairDialog
implements ActionListener, LocaleListener, WindowListener {
// ATTRIBUTS
/**
* Bouton permettant de valider et de fermer la boîte de dialogue.
*/
private JButton actionOK;
private JButton reset;
private List<SettingsTab> settingsTabs;
// CONSTRUCTEURS
/**
* Construction dune boîte de dialogue dédition de paramètres.
*/
public EditSettings() {
settingsTabs = new ArrayList<SettingsTab>();
settingsTabs.add(new TabGeneral());
settingsTabs.add(new TabLook());
settingsTabs.add(new TabHotkeys());
settingsTabs.add(new TabHistory());
settingsTabs.add(new TabComponents());
createResources();
placeComponents();
setPersistentBehavior();
LocaleDelegate.addLocaleListener(this);
}
@Override public void localeChanged(LocaleEvent event) {
// for (SettingsTab tab : settingsTabs) {
// tab.processLocaleEvent(event);
// }
}
// MÉTHODES
/**
* Instanciation des sous-composants utilisés par cette boîte de dialogue.
*/
private void createResources() {
reset = new JButton("" + Language.setting_hotkey_reset);
actionOK = new JButton(Language.ACCEPT.get());
}
/**
* Dispose les sous-composants au sein de ce panneau. Les sous-composants
* sont placés à laide dun {@link GridBagLayout} dont laccès est
* simplifié par la classe-proxy {@link GBC}.
*/
private void placeComponents() {
setLayout(new GridBagLayout());
JTabbedPane tabPane = new JTabbedPane(); {
for (SettingsTab tab : settingsTabs) {
tabPane.add(tab.toString(), tab);
}
}
JPanel controls = new JPanel(); {
controls.add(actionOK);
controls.add(reset);
}
add(tabPane, GBC.grid(0, 0));
add(controls, GBC.grid(0, 1).insets(6, 0, 4, 0));
}
/**
* Définit le comportement persistant (non sujet aux variations détats du
* modèle ou de la configuration de lapplication) pour ce composant et
* ses sous-composants.
*/
private void setPersistentBehavior() {
setResizable(false);
setTitle(Language.menuItem_settings.get());
actionOK.addActionListener(this);
reset.addActionListener(this);
addWindowListener(this);
}
/**
* Procédure à invoquer lorsquun sous-composant réalise une action. Cela
* signifie pour nous que lutilisateur à effectuer un réglage de paramètre.
*
* @param evt - lévènement daction.
* @see ActionListener
*/
public void actionPerformed(ActionEvent e) {
Object source = e.getSource();
if (source.equals(actionOK)) {
for (SettingsTab tab : settingsTabs) {
tab.doDelayedSettingChange();
}
dispose();
} else if (source.equals(reset)) {
int option = JOptionPane.showConfirmDialog(this,
"" + Language.WARN_RESET_SETTINGS);
if (option == JOptionPane.YES_OPTION) {
// Settings.reset();
dispose();
}
}
}
@Override public void windowActivated(WindowEvent e) {}
@Override public void windowClosed(WindowEvent e) {}
@Override public void windowDeactivated(WindowEvent e) {}
@Override public void windowDeiconified(WindowEvent e) {}
@Override public void windowIconified(WindowEvent e) {}
@Override public void windowOpened(WindowEvent e) {}
@Override public void windowClosing(WindowEvent e) {
for (SettingsTab tab : settingsTabs) {
tab.doDelayedSettingChange();
}
dispose();
}
}

View file

@ -0,0 +1,36 @@
package org.fenix.llanfair.dialog;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import javax.swing.JDialog;
import org.fenix.llanfair.Llanfair;
/**
* LlanfairDialog
*
* @author Xavier Sencert
* @date 24 juil. 2012
*/
public class LlanfairDialog extends JDialog {
// ATTRIBUTS
public void display(boolean lockNativeInputs, final Llanfair llanfair) {
if (lockNativeInputs) {
llanfair.setIgnoreNativeInputs(true);
addWindowListener(new WindowAdapter() {
@Override public void windowClosed(WindowEvent e) {
llanfair.setIgnoreNativeInputs(false);
}
});
}
setAlwaysOnTop(true);
setModalityType(ModalityType.APPLICATION_MODAL);
pack();
setLocationRelativeTo(getOwner());
setVisible(true);
}
}

View file

@ -0,0 +1,43 @@
package org.fenix.llanfair.dialog;
import java.awt.event.ItemEvent;
import java.util.HashMap;
import java.util.Map;
import javax.swing.JPanel;
import org.fenix.llanfair.Language;
import org.fenix.llanfair.config.Settings;
import org.fenix.utils.gui.LinkedCheckBox;
/**
*
* @author Xavier "Xunkar" Sencert
*/
abstract class SettingsTab extends JPanel {
protected Map<String, SCheckBox> checkBoxes;
protected SettingsTab() {
checkBoxes = new HashMap<String, SCheckBox>();
}
abstract void doDelayedSettingChange();
protected class SCheckBox extends LinkedCheckBox {
private Settings.Property<Boolean> setting;
SCheckBox(Settings.Property<Boolean> setting) {
super();
this.setting = setting;
setText("" + setting);
setSelected(setting.get());
}
@Override public void itemStateChanged(ItemEvent e) {
super.itemStateChanged(e);
setting.set(isSelected());
}
}
}

View file

@ -0,0 +1,242 @@
package org.fenix.llanfair.dialog;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.GraphicsEnvironment;
import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.List;
import javax.swing.BorderFactory;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JSpinner;
import javax.swing.SpinnerNumberModel;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import org.fenix.llanfair.Language;
import org.fenix.llanfair.Segment;
import org.fenix.llanfair.config.Settings;
import org.fenix.utils.gui.GBC;
/**
*
* @author Xavier "Xunkar" Sencert
*/
public class TabComponents extends SettingsTab
implements ActionListener, ChangeListener {
private static final List<Settings.Property<Boolean>> SCB_SETTINGS
= new ArrayList<Settings.Property<Boolean>>();
static {
SCB_SETTINGS.add(Settings.FOO_SHOW);
SCB_SETTINGS.add(Settings.FOO_SPLT);
SCB_SETTINGS.add(Settings.FOO_DLBL);
SCB_SETTINGS.add(Settings.FOO_BEST);
SCB_SETTINGS.add(Settings.FOO_LINE);
SCB_SETTINGS.add(Settings.FOO_VERB);
SCB_SETTINGS.add(Settings.COR_NAME);
SCB_SETTINGS.add(Settings.COR_SPLT);
SCB_SETTINGS.add(Settings.COR_SEGM);
SCB_SETTINGS.add(Settings.COR_BEST);
SCB_SETTINGS.add(Settings.HDR_GOAL);
SCB_SETTINGS.add(Settings.GPH_SHOW);
SCB_SETTINGS.add(Settings.COR_STMR);
SCB_SETTINGS.add(Settings.COR_ICON);
SCB_SETTINGS.add(Settings.HDR_TTLE);
};
private JComboBox iconSizes;
private JComboBox timerFont;
private JSpinner timerSize;
private JComboBox timerSegFont;
private JSpinner timerSegSize;
private JCheckBox timerSameFont;
TabComponents() {
super();
for (Settings.Property<Boolean> setting : SCB_SETTINGS) {
checkBoxes.put(setting.getKey(), new SCheckBox(setting));
}
// Checkboxes side effects
checkBoxes.get(Settings.FOO_SPLT.getKey()).deactivates(
checkBoxes.get(Settings.FOO_BEST.getKey())
);
// Checkboxes requirements
checkBoxes.get(Settings.FOO_BEST.getKey()).requires(
checkBoxes.get(Settings.FOO_SPLT.getKey()), false
);
checkBoxes.get(Settings.FOO_LINE.getKey()).requires(
checkBoxes.get(Settings.FOO_BEST.getKey()), true
);
iconSizes = new JComboBox(Segment.ICON_SIZES);
iconSizes.setSelectedItem(Settings.COR_ICSZ.get());
iconSizes.addActionListener(this);
GraphicsEnvironment gEnv = GraphicsEnvironment
.getLocalGraphicsEnvironment();
String mainFont = Settings.COR_TFNT.get().getName();
timerFont = new JComboBox(gEnv.getAvailableFontFamilyNames());
timerFont.setSelectedItem(mainFont);
timerFont.setPreferredSize(new Dimension(130, 22));
timerFont.addActionListener(this);
String segFont = Settings.COR_SFNT.get().getName();
timerSegFont = new JComboBox(gEnv.getAvailableFontFamilyNames());
timerSegFont.setSelectedItem(segFont);
timerSegFont.setPreferredSize(new Dimension(130, 22));
timerSegFont.addActionListener(this);
timerSize = new JSpinner(new SpinnerNumberModel(
Settings.COR_TFNT.get().getSize(), 8, 240, 1)
);
timerSize.addChangeListener(this);
timerSegSize = new JSpinner(new SpinnerNumberModel(
Settings.COR_SFNT.get().getSize(), 8, 240, 1)
);
timerSegSize.addChangeListener(this);
timerSameFont = new JCheckBox("" + Language.USE_MAIN_FONT);
timerSameFont.setSelected(segFont.equals(mainFont));
timerSegFont.setEnabled(!timerSameFont.isSelected());
timerSameFont.addActionListener(this);
place();
}
// -------------------------------------------------------------- CALLBACKS
@Override public void actionPerformed(ActionEvent event) {
Object source = event.getSource();
if (source.equals(iconSizes)) {
Settings.COR_ICSZ.set((Integer) iconSizes.getSelectedItem());
} else if (source.equals(timerFont)) {
String fontName = timerFont.getSelectedItem().toString();
Font font = Font.decode(fontName).deriveFont(
(float) Settings.COR_TFNT.get().getSize()
);
Settings.COR_TFNT.set(font);
if (timerSameFont.isSelected()) {
timerSegFont.setSelectedItem(fontName);
}
} else if (source.equals(timerSegFont)) {
String fontName = timerSegFont.getSelectedItem().toString();
Font font = Font.decode(fontName).deriveFont(
(float) Settings.COR_SFNT.get().getSize()
);
Settings.COR_SFNT.set(font);
} else if (source.equals(timerSameFont)) {
timerSegFont.setEnabled(!timerSameFont.isSelected());
if (timerSameFont.isSelected()) {
int size = (Integer) timerSegSize.getValue();
Settings.COR_SFNT.set(
Settings.COR_TFNT.get().deriveFont((float) size)
);
}
}
}
@Override public void stateChanged(ChangeEvent event) {
Object source = event.getSource();
if (source.equals(timerSize)) {
int size = (Integer) timerSize.getValue();
Settings.COR_TFNT.set(
Settings.COR_TFNT.get().deriveFont((float) size)
);
} else if (source.equals(timerSegSize)) {
int size = (Integer) timerSegSize.getValue();
Settings.COR_SFNT.set(
Settings.COR_SFNT.get().deriveFont((float) size)
);
}
}
// -------------------------------------------------------------- INHERITED
@Override void doDelayedSettingChange() {}
@Override public String toString() {
return "" + Language.COMPONENTS;
}
// -------------------------------------------------------------- UTILITIES
private void place() {
setLayout(new GridBagLayout());
JPanel fontPanel = new JPanel(new GridBagLayout()); {
fontPanel.add(
new JLabel("" + Language.setting_core_timerFont),
GBC.grid(0, 0).anchor(GBC.LS).insets(0, 5)
);
fontPanel.add(timerFont, GBC.grid(1, 0));
fontPanel.add(timerSize, GBC.grid(2, 0).insets(0, 5));
fontPanel.add(
new JLabel("" + Language.setting_core_segmentTimerFont),
GBC.grid(0, 1).anchor(GBC.LS).insets(3, 5)
);
fontPanel.add(
timerSameFont,
GBC.grid(1, 1, 2, 1).anchor(GBC.LS).insets(3, 0)
);
fontPanel.add(timerSegFont, GBC.grid(1, 2));
fontPanel.add(timerSegSize, GBC.grid(2, 2).insets(0, 5));
fontPanel.setBorder(
BorderFactory.createTitledBorder("" + Language.PN_FONTS)
);
}
JPanel timerPanel = new JPanel(new GridBagLayout()); {
timerPanel.add(
new JLabel("" + Language.setting_core_iconSize),
GBC.grid(0, 1).anchor(GBC.LE).insets(5, 5)
);
timerPanel.add(iconSizes, GBC.grid(1, 1).insets(5, 5));
timerPanel.add(checkBoxes.get(Settings.COR_ICON.getKey()), GBC.grid(0, 2, 2, 1).anchor(GBC.LS));
timerPanel.add(checkBoxes.get(Settings.COR_NAME.getKey()), GBC.grid(0, 3, 2, 1).anchor(GBC.LS));
timerPanel.add(checkBoxes.get(Settings.COR_SPLT.getKey()), GBC.grid(0, 4, 2, 1).anchor(GBC.LS));
timerPanel.add(checkBoxes.get(Settings.COR_SEGM.getKey()), GBC.grid(0, 5, 2, 1).anchor(GBC.LS));
timerPanel.add(checkBoxes.get(Settings.COR_BEST.getKey()), GBC.grid(0, 6, 2, 1).anchor(GBC.LS));
timerPanel.add(checkBoxes.get(Settings.COR_STMR.getKey()), GBC.grid(0, 7, 2, 1).anchor(GBC.LS));
timerPanel.setBorder(
BorderFactory.createTitledBorder("" + Language.TIMER)
);
}
JPanel footerPanel = new JPanel(new GridBagLayout()); {
footerPanel.add(checkBoxes.get(Settings.FOO_SHOW.getKey()), GBC.grid(0, 0).anchor(GBC.LS));
footerPanel.add(checkBoxes.get(Settings.FOO_SPLT.getKey()), GBC.grid(0, 1).anchor(GBC.LS));
footerPanel.add(checkBoxes.get(Settings.FOO_VERB.getKey()), GBC.grid(0, 2).anchor(GBC.LS));
footerPanel.add(checkBoxes.get(Settings.FOO_DLBL.getKey()), GBC.grid(1, 0).anchor(GBC.LS));
footerPanel.add(checkBoxes.get(Settings.FOO_BEST.getKey()), GBC.grid(1, 1).anchor(GBC.LS));
footerPanel.add(checkBoxes.get(Settings.FOO_LINE.getKey()), GBC.grid(1, 2).anchor(GBC.LS));
footerPanel.setBorder(
BorderFactory.createTitledBorder("" + Language.FOOTER)
);
}
JPanel miscPanel = new JPanel(new GridBagLayout()); {
miscPanel.add(checkBoxes.get(Settings.HDR_TTLE.getKey()), GBC.grid(0, 0).anchor(GBC.LS));
miscPanel.add(checkBoxes.get(Settings.HDR_GOAL.getKey()), GBC.grid(0, 1).anchor(GBC.LS));
miscPanel.add(checkBoxes.get(Settings.GPH_SHOW.getKey()), GBC.grid(0, 2).anchor(GBC.LS));
miscPanel.setBorder(
BorderFactory.createTitledBorder("" + Language.MISC)
);
}
add(fontPanel, GBC.grid(0, 0, 2, 1).fill(GBC.B).anchor(GBC.FLS));
add(timerPanel, GBC.grid(2, 0, 1, 2).fill(GBC.B).anchor(GBC.FLS));
add(footerPanel, GBC.grid(0, 1).fill(GBC.B).anchor(GBC.FLS));
add(miscPanel, GBC.grid(1, 1).fill(GBC.B).anchor(GBC.FLS));
}
}

View file

@ -0,0 +1,191 @@
package org.fenix.llanfair.dialog;
import java.awt.Component;
import java.awt.GridBagLayout;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Enumeration;
import java.util.Locale;
import javax.swing.AbstractButton;
import javax.swing.ButtonGroup;
import javax.swing.DefaultListCellRenderer;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.JRadioButton;
import org.fenix.llanfair.Language;
import org.fenix.llanfair.Llanfair;
import org.fenix.llanfair.config.Settings;
import org.fenix.llanfair.config.Compare;
import org.fenix.llanfair.config.Accuracy;
import org.fenix.utils.gui.GBC;
import org.fenix.utils.Resources;
/**
*
* @author Xavier "Xunkar" Sencert
*/
public class TabGeneral extends SettingsTab implements ActionListener {
// ------------------------------------------------------------- ATTRIBUTES
private JComboBox language;
private JLabel languageText;
private JCheckBox alwaysOnTop;
private JLabel alwaysOnTopText;
private ButtonGroup compare;
private JLabel compareText;
private ButtonGroup accuracy;
private JLabel accuracyText;
private JCheckBox warnOnReset;
// ----------------------------------------------------------- CONSTRUCTORS
TabGeneral() {
language = new JComboBox(Language.LANGUAGES);
language.setRenderer(new LocaleListRenderer());
language.setSelectedItem(Settings.GNR_LANG.get());
language.addActionListener(this);
alwaysOnTop = new JCheckBox("" + Language.setting_alwaysOnTop);
alwaysOnTop.setSelected(Settings.GNR_ATOP.get());
compare = new ButtonGroup();
Compare setCmp = Settings.GNR_COMP.get();
for (Compare method : Compare.values()) {
JRadioButton radio = new JRadioButton("" + method);
radio.setName(method.name());
radio.setSelected(setCmp == method);
radio.addActionListener(this);
compare.add(radio);
}
accuracy = new ButtonGroup();
Accuracy setAcc = Settings.GNR_ACCY.get();
for (Accuracy value : Accuracy.values()) {
JRadioButton radio = new JRadioButton("" + value);
radio.setName(value.name());
radio.setSelected(setAcc == value);
radio.addActionListener(this);
accuracy.add(radio);
}
warnOnReset = new JCheckBox("" + Language.setting_warnOnReset);
warnOnReset.setSelected(Settings.GNR_WARN.get());
warnOnReset.addActionListener(this);
languageText = new JLabel("" + Language.setting_language);
alwaysOnTopText = new JLabel("" + Language.APPLICATION);
compareText = new JLabel("" + Language.COMPARE_METHOD);
accuracyText = new JLabel("" + Language.ACCURACY);
place();
}
// -------------------------------------------------------------- CALLBACKS
public void actionPerformed(ActionEvent event) {
Object source = event.getSource();
if (source.equals(language)) {
Settings.GNR_LANG.set((Locale) language.getSelectedItem());
} else if (source instanceof JRadioButton) {
JRadioButton radio = (JRadioButton) source;
try {
Settings.GNR_COMP.set(
Compare.valueOf(radio.getName())
);
} catch (Exception e) {
Settings.GNR_ACCY.set(Accuracy.valueOf(radio.getName()));
}
} else if (source.equals(warnOnReset)) {
Settings.GNR_WARN.set(warnOnReset.isSelected());
}
}
// -------------------------------------------------------------- INHERITED
void doDelayedSettingChange() {
Settings.GNR_ATOP.set(alwaysOnTop.isSelected());
}
/**
* Returns the localized name of this tab.
*/
@Override public String toString() {
return "" + Language.GENERAL;
}
// -------------------------------------------------------------- UTILITIES
/**
* Places all sub-components within this panel.
*/
private void place() {
setLayout(new GridBagLayout());
add(
alwaysOnTopText, GBC.grid(0, 0).anchor(GBC.LE).insets(5, 10)
);
add(alwaysOnTop, GBC.grid(1, 0).anchor(GBC.LS));
add(warnOnReset, GBC.grid(1, 1).anchor(GBC.LS));
add(languageText, GBC.grid(0, 2).anchor(GBC.LE).insets(10, 10));
add(language, GBC.grid(1, 2).fill(GBC.H));
JPanel comparePanel = new JPanel(new GridLayout(0, 1)); {
Enumeration<AbstractButton> buttons = compare.getElements();
while (buttons.hasMoreElements()) {
comparePanel.add(buttons.nextElement());
}
}
add(compareText, GBC.grid(0, 3).anchor(GBC.FLE).insets(14, 10));
add(comparePanel, GBC.grid(1, 3).fill(GBC.H).insets(10, 0, 0, 0));
JPanel accuracyPanel = new JPanel(new GridLayout(0, 1)); {
Enumeration<AbstractButton> buttons = accuracy.getElements();
while (buttons.hasMoreElements()) {
accuracyPanel.add(buttons.nextElement());
}
}
add(accuracyText, GBC.grid(0, 4).anchor(GBC.FLE).insets(14, 10));
add(accuracyPanel, GBC.grid(1, 4).fill(GBC.H).insets(10, 0));
}
// --------------------------------------------------------- INTERNAL TYPES
class LocaleListRenderer extends DefaultListCellRenderer {
public LocaleListRenderer() {
setVerticalAlignment(CENTER);
}
@Override public Component getListCellRendererComponent(
JList list, Object value, int index, boolean isSelected,
boolean cellHasFocus
) {
super.getListCellRendererComponent(
list, value, index, isSelected, cellHasFocus
);
Locale selected = (Locale) value;
setIcon(Llanfair.getResources().getIcon("" + selected));
setText(Language.LOCALE_NAMES.get("" + selected));
return this;
}
}
}

View file

@ -0,0 +1,348 @@
package org.fenix.llanfair.dialog;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.GraphicsEnvironment;
import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.BorderFactory;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JSpinner;
import javax.swing.JTextField;
import javax.swing.SpinnerNumberModel;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import org.fenix.llanfair.Language;
import org.fenix.llanfair.Segment;
import org.fenix.llanfair.config.Settings;
import org.fenix.llanfair.config.Merge;
import org.fenix.utils.gui.GBC;
class TabHistory extends SettingsTab implements ActionListener, ChangeListener {
// ------------------------------------------------------------- ATTRIBUTES
/**
* Text field allowing the user to edit the height of the history. The
* height is provided as a number of segments to display.
*/
private JTextField rows;
/**
* Label displaying the name of the text field {@code height}.
*/
private JLabel heightText;
/**
* Label displaying the unit of the value of the text field {@code height}.
*/
private JLabel heightUnit;
/**
* Combox box displaying the available merging methods, determining which,
* if any, column should be merged.
*/
private JComboBox merge;
/**
* Check box determining wether or not the history should display the
* delta column.
*/
private JCheckBox deltas;
/**
* Check box determining wether or not the history should display the
* live times column.
*/
private JCheckBox lives;
private JCheckBox blankRows;
private JCheckBox icons;
private JComboBox iconSize;
private JCheckBox showLast;
private JSpinner offset;
private JLabel offsetHelper;
private JCheckBox twoLines;
private JCheckBox tabular;
private JComboBox nameFont;
private JSpinner nameSize;
private JComboBox timeFont;
private JSpinner timeSize;
// ----------------------------------------------------------- CONSTRUCTORS
/**
* Creates the "History" settings tab. Only called by {@link EditSettings}.
*/
TabHistory() {
rows = new JTextField();
rows.setHorizontalAlignment(JTextField.TRAILING);
rows.setText("" + Settings.HST_ROWS.get());
deltas = new JCheckBox("" + Language.setting_history_deltas);
deltas.setSelected(Settings.HST_DLTA.get());
deltas.addActionListener(this);
lives = new JCheckBox("" + Language.setting_history_liveTimes);
lives.setSelected(Settings.HST_LIVE.get());
lives.addActionListener(this);
twoLines = new JCheckBox("" + Language.setting_history_multiline);
twoLines.setSelected(Settings.HST_LINE.get());
twoLines.addActionListener(this);
blankRows = new JCheckBox("" + Language.setting_history_blankRows);
blankRows.setSelected(Settings.HST_BLNK.get());
blankRows.addActionListener(this);
icons = new JCheckBox("" + Language.setting_history_icons);
icons.setSelected(Settings.HST_ICON.get());
icons.addActionListener(this);
tabular = new JCheckBox("" + Language.setting_history_tabular);
tabular.setSelected(Settings.HST_TABL.get());
tabular.addActionListener(this);
showLast = new JCheckBox("" + Language.setting_history_alwaysShowLast);
showLast.setSelected(Settings.HST_LAST.get());
showLast.addActionListener(this);
merge = new JComboBox(Merge.values());
merge.setSelectedItem(Settings.HST_MERG.get());
merge.addActionListener(this);
iconSize = new JComboBox(Segment.ICON_SIZES);
iconSize.setSelectedItem(Settings.HST_ICSZ.get());
iconSize.addActionListener(this);
offset = new JSpinner(new SpinnerNumberModel(
(int) Settings.HST_OFFS.get(), -5, 5, 1)
);
offset.addChangeListener(this);
offsetHelper = new JLabel("<html>[<a href=''>?</a>]</html>");
offsetHelper.setToolTipText(
"<html><div width=200>" + Language.TT_HS_OFFSET + "</div></html>"
);
GraphicsEnvironment gEnv = GraphicsEnvironment
.getLocalGraphicsEnvironment();
String mainFont = Settings.HST_SFNT.get().getName();
nameFont = new JComboBox(gEnv.getAvailableFontFamilyNames());
nameFont.setSelectedItem(mainFont);
nameFont.setPreferredSize(new Dimension(130, 22));
nameFont.addActionListener(this);
String font = Settings.HST_TFNT.get().getName();
timeFont = new JComboBox(gEnv.getAvailableFontFamilyNames());
timeFont.setSelectedItem(font);
timeFont.setPreferredSize(new Dimension(130, 22));
timeFont.addActionListener(this);
nameSize = new JSpinner(new SpinnerNumberModel(
Settings.HST_SFNT.get().getSize(), 8, 240, 1)
);
nameSize.addChangeListener(this);
timeSize = new JSpinner(new SpinnerNumberModel(
Settings.HST_TFNT.get().getSize(), 8, 240, 1)
);
timeSize.addChangeListener(this);
place();
}
// -------------------------------------------------------------- CALLBACKS
/**
* Update the settings with the user input when he validates.
*/
@Override public void actionPerformed(ActionEvent event) {
Object source = event.getSource();
if (source.equals(merge)) {
Merge value = (Merge) merge.getSelectedItem();
Settings.HST_MERG.set(value);
if (value == Merge.DELTA) {
Settings.HST_DLTA.set(false);
deltas.setSelected(false);
} else if (value == Merge.LIVE) {
Settings.HST_LIVE.set(false);
lives.setSelected(false);
}
} else if (source.equals(deltas)) {
Settings.HST_DLTA.set(deltas.isSelected());
} else if (source.equals(lives)) {
Settings.HST_LIVE.set(lives.isSelected());
} else if (source.equals(blankRows)) {
Settings.HST_BLNK.set(blankRows.isSelected());
} else if (source.equals(icons)) {
Settings.HST_ICON.set(icons.isSelected());
} else if (source.equals(showLast)) {
Settings.HST_LAST.set(showLast.isSelected());
} else if (source.equals(twoLines)) {
Settings.HST_LINE.set(twoLines.isSelected());
} else if (source.equals(tabular)) {
Settings.HST_TABL.set(tabular.isSelected());
} else if (source.equals(iconSize)) {
Settings.HST_ICSZ.set(
(Integer) iconSize.getSelectedItem()
);
} else if (source.equals(nameFont)) {
String fontName = nameFont.getSelectedItem().toString();
Font font = Font.decode(fontName).deriveFont(
(float) Settings.HST_SFNT.get().getSize()
);
Settings.HST_SFNT.set(font);
} else if (source.equals(timeFont)) {
String fontName = timeFont.getSelectedItem().toString();
Font font = Font.decode(fontName).deriveFont(
(float) Settings.HST_TFNT.get().getSize()
);
Settings.HST_TFNT.set(font);
}
}
public void stateChanged(ChangeEvent event) {
Object source = event.getSource();
if (source.equals(offset)) {
int size = (Integer) offset.getValue();
Settings.HST_OFFS.set(size);
} else if (source.equals(nameSize)) {
int size = (Integer) nameSize.getValue();
Settings.HST_SFNT.set(
Settings.HST_SFNT.get().deriveFont((float) size)
);
} else if (source.equals(timeSize)) {
int size = (Integer) timeSize.getValue();
Settings.HST_TFNT.set(
Settings.HST_TFNT.get().deriveFont((float) size)
);
}
}
// -------------------------------------------------------------- INHERITED
@Override void doDelayedSettingChange() {
int input = 0;
try {
input = Integer.parseInt(rows.getText());
} catch (Exception e) {
//$FALL-THROUGH$
} finally {
if (input <= 0) {
input = 0;
rows.setText("0");
}
Settings.HST_ROWS.set(input);
}
}
/**
* Returns the localized name of this tab.
*/
@Override public String toString() {
return "" + Language.HISTORY;
}
// -------------------------------------------------------------- UTILITIES
/**
* Places all sub-components within this panel.
*/
private void place() {
setLayout(new GridBagLayout());
// Set Components Orientation
rows.setHorizontalAlignment(JLabel.CENTER);
twoLines.setHorizontalTextPosition(JLabel.LEADING);
blankRows.setHorizontalTextPosition(JLabel.LEADING);
showLast.setHorizontalTextPosition(JLabel.LEADING);
// Display
JPanel display = new JPanel(new GridBagLayout()); {
display.add(icons , GBC.grid(0, 0).anchor(GBC.LS));
display.add(lives , GBC.grid(0, 1).anchor(GBC.LS));
display.add(deltas, GBC.grid(0, 2).anchor(GBC.LS));
// display.add(tabular, GBC.grid(0, 3).anchor(GBC.LS));
display.add(merge , GBC.grid(0, 4).anchor(GBC.LS));
display.setBorder(
BorderFactory.createTitledBorder("" + Language.PN_DISPLAY)
);
}
// Dimension
JPanel dimension = new JPanel(new GridBagLayout()); {
dimension.add(
new JLabel("" + Language.setting_history_iconSize),
GBC.grid(0, 0).anchor(GBC.LE)
);
dimension.add(iconSize, GBC.grid(1, 0).anchor(GBC.LE).insets(2, 3));
dimension.add(
new JLabel("" + Language.setting_history_rowCount),
GBC.grid(0, 1).anchor(GBC.LE)
);
dimension.add(
rows, GBC.grid(1, 1).anchor(GBC.LE).fill(GBC.H).insets(2, 3)
);
dimension.add(blankRows, GBC.grid(0, 2, 2, 1).anchor(GBC.LE));
dimension.add(
twoLines , GBC.grid(0, 3, 2, 1).anchor(GBC.LE).insets(0, 1)
);
dimension.setBorder(
BorderFactory.createTitledBorder("" + Language.PN_DIMENSION)
);
}
// Fonts
JPanel fonts = new JPanel(new GridBagLayout()); {
fonts.add(
new JLabel("" + Language.setting_history_segmentFont),
GBC.grid(0, 0).anchor(GBC.LE)
);
fonts.add(nameFont, GBC.grid(1, 0).anchor(GBC.LS).insets(5, 5));
fonts.add(nameSize, GBC.grid(2, 0).anchor(GBC.LS));
fonts.add(
new JLabel("" + Language.setting_history_timeFont),
GBC.grid(0, 1).anchor(GBC.LE)
);
fonts.add(timeFont, GBC.grid(1, 1).anchor(GBC.LS).insets(5, 5));
fonts.add(timeSize, GBC.grid(2, 1).anchor(GBC.LS));
fonts.setBorder(
BorderFactory.createTitledBorder("" + Language.PN_FONTS)
);
}
// Scrolling
JPanel scrolling = new JPanel(new GridBagLayout()); {
scrolling.add(
offsetHelper, GBC.grid(0, 0).anchor(GBC.LE).insets(0, 8)
);
scrolling.add(
new JLabel("" + Language.setting_history_offset),
GBC.grid(1, 0).anchor(GBC.LE)
);
scrolling.add(offset , GBC.grid(2, 0).anchor(GBC.LE));
scrolling.add(showLast, GBC.grid(0, 1, 3, 1).anchor(GBC.LE));
scrolling.setBorder(
BorderFactory.createTitledBorder("" + Language.PN_SCROLLING)
);
}
add(fonts , GBC.grid(0, 0).fill(GBC.B).padding(10, 10));
add(dimension, GBC.grid(1, 0).fill(GBC.B).padding(10, 10));
add(display , GBC.grid(0, 1).fill(GBC.B).padding(10, 10));
add(scrolling, GBC.grid(1, 1).fill(GBC.B).padding(10, 10));
}
}

View file

@ -0,0 +1,211 @@
package org.fenix.llanfair.dialog;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.GridBagLayout;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JLabel;
import javax.swing.JTextField;
import org.fenix.llanfair.Language;
import org.fenix.llanfair.config.Settings;
import org.fenix.utils.gui.GBC;
import org.jnativehook.GlobalScreen;
import org.jnativehook.keyboard.NativeKeyEvent;
import org.jnativehook.keyboard.NativeKeyListener;
/**
*
* @author Xavier "Xunkar" Sencert
*/
class TabHotkeys extends SettingsTab {
// ------------------------------------------------------------- ATTRIBUTES
/**
* List of all key fields customizable by the user.
*/
private List<KeyField> keyFields;
/**
* List of labels displaying the name of each key field.
*/
private List<JLabel> keyLabels;
// ----------------------------------------------------------- CONSTRUCTORS
/**
* Creates the "Hotkeys" settings tab. Only called by {@link EditSettings}.
*/
TabHotkeys() {
keyFields = new ArrayList<KeyField>();
keyLabels = new ArrayList<JLabel>();
int index = 0;
for (Settings.Property<?> setting : Settings.getAll("hotkey")) {
keyFields.add(new KeyField(
index, (Settings.Property<Integer>) setting)
);
keyLabels.add(new JLabel("" + setting));
index++;
}
place();
}
// -------------------------------------------------------------- INHERITED
@Override void doDelayedSettingChange() {}
/**
* Returns the localized name of this tab.
*/
@Override public String toString() {
return "" + Language.INPUTS;
}
// -------------------------------------------------------------- UTILITIES
/**
* Places all sub-components within this panel.
*/
private void place() {
setLayout(new GridBagLayout());
for (int row = 0; row < keyFields.size(); row++) {
add(
keyLabels.get(row),
GBC.grid(0, row).insets(10, 0, 10, 10).anchor(GBC.LE)
);
add(keyFields.get(row), GBC.grid(1, row));
}
}
// --------------------------------------------------------- INTERNAL TYPES
/**
* A text field representing a hotkey setting. Clicking such field allows
* the user to define a key for this particular setting, using the tables
* from {@code JNativeHook}.
*
* @author Xavier "Xunkar" Sencert
*/
static class KeyField extends JTextField
implements MouseListener, NativeKeyListener {
// ----------------------------------------------------- CONSTANTS
/**
* Preferred dimension of a text field.
*/
private static final Dimension SIZE = new Dimension(85, 20);
// ---------------------------------------------------- ATTRIBUTES
private static boolean isEditing = false;
/**
* Internal index of this field, used by the superclass.
*/
private int index;
/**
* Setting represented by this key field.
*/
private Settings.Property<Integer> setting;
// -------------------------------------------------- CONSTRUCTORS
/**
* Creates a new key field for the given setting. Only called by
* {@link TabHotkeys}.
*
* @param index - internal index to identify this field.
* @param setting - setting represented by this field.
*/
KeyField(int index, Settings.Property<Integer> setting) {
this.index = index;
this.setting = setting;
setEditable(false);
setName(setting.getKey());
setPreferredSize(SIZE);
setHorizontalAlignment(CENTER);
String text = NativeKeyEvent.getKeyText(setting.get());
setText(setting.get() == -1 ? "" + Language.DISABLED : text);
addMouseListener(this);
}
// ------------------------------------------------------- GETTERS
/**
* Returns the setting represented by this key field.
*/
public Settings.Property<Integer> getSetting() {
return setting;
}
// ----------------------------------------------------- CALLBACKS
/**
* When a key field is clicked, we register ourselves as native key
* listener to capture the new key setting. The background adorns a
* new color to signify that this field is now listening.
*/
public void mouseClicked(MouseEvent event) {
if (!isEditing) {
setBackground(Color.YELLOW);
GlobalScreen.getInstance().addNativeKeyListener(this);
isEditing = true;
}
}
// $UNUSED$
public void mouseEntered(MouseEvent event) {}
// $UNUSED$
public void mouseExited(MouseEvent event) {}
// $UNUSED$
public void mousePressed(MouseEvent event) {}
// $UNUSED$
public void mouseReleased(MouseEvent event) {}
/**
* When a key is pressed we set the captured key as the new value
* for the setting we represent. If escape is pressed, the hotkey
* becomes disabled. After registering the update, we no longer
* listen for native key events.
*/
public void nativeKeyPressed(NativeKeyEvent event) {
int code = event.getKeyCode();
String text = null;
if (code == NativeKeyEvent.VK_ESCAPE) {
code = -1;
text = "" + Language.DISABLED;
} else {
text = NativeKeyEvent.getKeyText(code);
}
setText(text);
setting.set(code);
setBackground(Color.GREEN);
GlobalScreen.getInstance().removeNativeKeyListener(this);
isEditing = false;
}
// $UNUSED$
public void nativeKeyReleased(NativeKeyEvent event) {}
// $UNUSED$
public void nativeKeyTyped(NativeKeyEvent event) {}
}
}

View file

@ -0,0 +1,323 @@
package org.fenix.llanfair.dialog;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JButton;
import javax.swing.JColorChooser;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import org.fenix.llanfair.Language;
import org.fenix.llanfair.Llanfair;
import org.fenix.llanfair.config.Settings;
import org.fenix.utils.gui.GBC;
/**
* A panel allowing the user the change the settings related to the look of
* the application: colors, fonts and fonts sizes.
*
* @author Xavier "Xunkar" Sencert
*/
class TabLook extends SettingsTab implements ActionListener, ChangeListener {
// -------------------------------------------------------------- CONSTANTS
/**
* Dimension of the revert button.
*/
private static final Dimension REVERT_SIZE = new Dimension(18, 18);
// ------------------------------------------------------------- ATTRIBUTES
/**
* List of all color buttons, inserted in the order of their name.
*/
private List<ColorButton> colorButtons;
/**
* List of labels displaying the name of the color button.
*/
private List<JLabel> colorTexts;
/**
* Panel allowing the user to select a color.
*/
private JColorChooser colorChooser;
/**
* The index of the currently selected color button. Any color change will
* be made to this button.
*/
private int selected;
/**
* Label displaying a text explaining to the user that he must first
* select a color button before editing its color.
*/
private JLabel helperText;
/**
* Panel listing the color buttons and their name label.
*/
private JPanel colorPanel;
/**
* Small button displayed next to the selected color button and resetting
* its color to its original value.
*/
private JButton revert;
/**
* Invisible panel the size of the revert button used to take up space when
* the button is not showing.
*/
private JPanel placeHolder;
// ----------------------------------------------------------- CONSTRUCTORS
/**
* Creates the "Look" settings tab. Only called by {@link EditSettings}.
*/
TabLook() {
selected = -1;
helperText = new JLabel("" + Language.TT_COLOR_PICK);
colorTexts = new ArrayList<JLabel>();
colorButtons = new ArrayList<ColorButton>();
int index = 0;
for (Settings.Property<?> colorSetting : Settings.getAll("color")) {
ColorButton colorButton = new ColorButton(
index, (Settings.Property<Color>) colorSetting
);
colorButton.addActionListener(this);
colorButtons.add(colorButton);
colorTexts.add(new JLabel("" + colorSetting));
index++;
}
colorChooser = new JColorChooser();
colorChooser.setPreviewPanel(new JPanel());
colorChooser.getSelectionModel().addChangeListener(this);
colorChooser.setEnabled(false);
revert = new JButton(Llanfair.getResources().getIcon("REVERT"));
revert.setPreferredSize(REVERT_SIZE);
revert.addActionListener(this);
placeHolder = new JPanel();
placeHolder.setPreferredSize(REVERT_SIZE);
place();
}
// -------------------------------------------------------------- CALLBACKS
/**
* When a color button is pressed, we bring up the color chooser and
* change the color of the text indicate its been selected. We also
* display the revert button next to the selected color. If the event
* emanates from a revert button, we revert its associated color.
*/
public void actionPerformed(ActionEvent event) {
Object source = event.getSource();
if (source.equals(revert)) {
colorButtons.get(selected).resetColor();
} else {
if (selected != -1) {
colorTexts.get(selected).setForeground(Color.BLACK);
colorPanel.remove(revert);
} else {
colorPanel.remove(placeHolder);
colorChooser.setEnabled(true);
}
selected = ((ColorButton) source).getIndex();
colorTexts.get(selected).setForeground(Color.RED);
colorPanel.add(revert, GBC.grid(2, selected).insets(0, 2));
colorChooser.setColor(colorButtons.get(selected).getColor());
revalidate();
}
}
/**
* When a color is selected in the color chooser, update the selected
* color button (if any.)
*/
public void stateChanged(ChangeEvent event) {
if (selected != -1) {
colorButtons.get(selected).setColor(colorChooser.getColor());
}
}
// -------------------------------------------------------------- INHERITED
@Override void doDelayedSettingChange() {}
/**
* Returns the localized name of this tab.
*/
@Override public String toString() {
return "" + Language.COLORS;
}
// -------------------------------------------------------------- UTILITIES
/**
* Places all sub-components within this panel.
*/
private void place() {
setLayout(new GridBagLayout());
colorPanel = new JPanel(new GridBagLayout()); {
for (int row = 0; row < colorButtons.size(); row++) {
colorPanel.add(
colorTexts.get(row),
GBC.grid(0, row).anchor(GBC.LE).insets(1, 4)
);
colorPanel.add(
colorButtons.get(row), GBC.grid(1, row).insets(2, 0)
);
}
colorPanel.add(placeHolder, GBC.grid(2, 0).insets(0, 2));
}
JPanel swatchPanel = new JPanel(new GridBagLayout()); {
swatchPanel.add(colorChooser, GBC.grid(0, 0));
swatchPanel.add(helperText, GBC.grid(0, 1));
}
add(colorPanel, GBC.grid(0, 0).fill(GBC.B));
add(swatchPanel, GBC.grid(1, 0).fill(GBC.B));
}
// --------------------------------------------------------- INTERNAL TYPES
/**
* A kind of {@code JButton} that displays a color setting by painting
* itself in that color. When the color is programatically set, the color
* setting is automatically updated by the button. Each button stores an
* index only valid in the owning views context and the initial value it
* was constructed with, allowing for a reset.
*
* @author Xavier "Xunkar" Sencert
*/
class ColorButton extends JButton implements Comparable<ColorButton> {
// ---------------------------------------------------- ATTRIBUTES
/**
* Index of this color button in the englobing model.
*/
private int index;
/**
* Color currently represented by this button.
*/
private Color color;
/**
* Color initially represented by this button. Used to revert to the
* setting initial state.
*/
private Color initialColor;
/**
* Color setting that this button represent. When the user selectes
* a new color, this setting is updated with the user's choice.
*/
private Settings.Property<Color> setting;
// -------------------------------------------------- CONSTRUCTORS
/**
* Creates a button representing the given setting. An index must be
* supplied to identify this button. The name of the setting becomes
* the name of the button.
*
* @param index - the index of this button.
* @param setting - the setting represented by this button.
*/
ColorButton(int index, Settings.Property<Color> setting) {
super(".");
color = setting.get();
initialColor = color;
this.index = index;
this.setting = setting;
setName(setting.getKey());
}
// ------------------------------------------------------- GETTERS
/**
* Returns the current color of this button.
*
* @return the current color.
*/
Color getColor() {
return color;
}
/**
* Returns the index of this button.
*
* @return the index of this button.
*/
int getIndex() {
return index;
}
// ------------------------------------------------------- SETTERS
/**
* Sets the color that this button should display. Updates the setting
* and repaints itself.
*
* @param color - the new color to display.
*/
void setColor(Color color) {
this.color = color;
setting.set(color);
repaint();
}
/**
* Resets the color to be displayed by this button to the initial
* color it was constructed with.
*/
void resetColor() {
setColor(initialColor);
}
// ----------------------------------------------------- INHERITED
/**
* Two {@code ColorButton}s are compared using the lexicographic
* comparison of their name.
*/
public int compareTo(ColorButton o) {
if (o == null) {
return 1;
}
return getName().compareTo(o.getName());
}
/**
* A {@code ColorButton} paints itself in the color he should
* display and does not display any string of text.
*/
@Override protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(color);
g.fillRect(0, 0, getWidth(), getHeight());
}
}
}

109
org/fenix/llanfair/extern/WSplit.java vendored Normal file
View file

@ -0,0 +1,109 @@
package org.fenix.llanfair.extern;
import java.io.BufferedReader;
import java.io.IOException;
import javax.swing.ImageIcon;
import org.fenix.llanfair.Llanfair;
import org.fenix.llanfair.Run;
import org.fenix.llanfair.Segment;
import org.fenix.llanfair.Time;
/**
* Utility class that provides method to interface with WSplit.
*
* @author Xavier "Xunkar" Sencert
* @version 1.1
*/
public class WSplit {
/**
* Parses the given stream opened on a WSplit run file and sets it as
* the currently opened run in Llanfair.
*
* @param master the Llanfair instance calling the parser
* @param in an opened stream on a WSplit run file
* @throws Exception if the reading operation cannot complete
*/
public static void parse( Llanfair master, BufferedReader in )
throws Exception {
if ( master == null ) {
throw new NullPointerException( "Null Llanfair instance" );
}
if ( in == null ) {
throw new NullPointerException( "Null file reader" );
}
try {
// Title
String line = in.readLine();
Run run = new Run( line.split( "=" )[1] );
// Attempts, Offset, Size
in.readLine();
in.readLine();
in.readLine();
// Segment List
line = parseSegments( in, run );
// Segment Icons
parseIcons( line, run );
master.setRun( run );
} catch ( Exception ex ) {
throw ex;
}
}
/**
* Parses the list of segment in a WSplit run file. The segment list is
* formatted like this: each segment is on one line and comprised of the
* following information: Name, Old Time, Best Time, Best Segment
* comma-separated.
*
* @param in the opened stream on a WSplit run file
* @param run the run currently built by the parser
* @return the currently read line marker
* @throws IOException if reading operations fail
*/
private static String parseSegments( BufferedReader in, Run run )
throws IOException {
String line;
while ( !( line = in.readLine() ).startsWith( "Icons" ) ) {
String[] args = line.split( "," );
Segment segment = new Segment( args[0] );
run.addSegment( segment );
double parsed = Double.parseDouble( args[2] );
run.setValueAt(
parsed == 0.0 ? null : new Time( parsed ),
run.getRowCount() - 1, Run.COLUMN_TIME
);
parsed = Double.parseDouble( args[3] );
segment.setTime(
parsed == 0.0 ? null : new Time(parsed), Segment.BEST
);
}
return line;
}
/**
* Parses the list of segment icons in a WSplit run file. The list is a
* single line, listing the icons in the segment order and formatted as
* follows: Icons="icon1","icon2",...,"iconN"
*
* @param line the line containing the icon list
* @param run the run currently built by the parser
* @throws IOException if reading operations fail
*/
private static void parseIcons( String line, Run run ) throws IOException {
line = line.substring( line.indexOf( "=" ) + 1 );
String[] args = line.split( "," );
for ( int i = 0; i < args.length; i++ ) {
ImageIcon icon = null;
String[] lst = args[i].split( "\\\"" );
if ( lst.length > 0 && !lst[1].equals( "" ) ) {
icon = new ImageIcon( lst[1] );
}
run.getSegment( i ).setIcon( icon );
}
}
}

View file

@ -0,0 +1,595 @@
package org.fenix.llanfair.gui;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.beans.PropertyChangeEvent;
import javax.swing.Icon;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.Timer;
import javax.swing.event.TableModelEvent;
import org.fenix.llanfair.Language;
import org.fenix.llanfair.Run;
import org.fenix.llanfair.Run.State;
import org.fenix.llanfair.Segment;
import org.fenix.llanfair.config.Settings;
import org.fenix.llanfair.Time;
import org.fenix.utils.gui.GBC;
import org.fenix.utils.Images;
import org.fenix.utils.locale.LocaleEvent;
/**
* Core panel displaying the main informations for a run, namely: the run and
* segment timers and the current segment being runned.
*
* @author Xavier "Xunkar" Sencert
*/
class Core extends JPanel implements ActionListener {
// -------------------------------------------------------------- CONSTANTS
/**
* Update identifier for every category.
*/
private static final int ALL = 0xff;
/**
* Update identifier for time variables.
*/
private static final int TIME = 0x01;
/**
* Update identifier for timer variables.
*/
private static final int TIMER = 0x02;
/**
* Update identifier for icon variables.
*/
private static final int ICON = 0x04;
/**
* Update identifier for name variables.
*/
private static final int NAME = 0x08;
private static final int FONT = 0x10;
/**
* Minimum width in pixels of this component.
*/
private static final int MIN_WIDTH = 50;
// ------------------------------------------------------------- ATTRIBUTES
/**
* Run instance represented by the panel.
*/
private Run run;
/**
* Thread updating the value of the timers and repainting them every
* hundredth of a second.
*/
private Timer timer;
/**
* Label displaying the main timer, timing the whole run.
*/
private JLabel splitTimer;
/**
* Label displaying the segment timer, timing the current segment.
*/
private JLabel segmentTimer;
/**
* Label displaying the name of the current segment.
*/
private JLabel name;
/**
* Label displayong the icon (if any) of the current segment.
*/
private JLabel icon;
/**
* Label displaying the registered split time of the current segment.
*/
private JLabel split;
/**
* Label displaying the registered segment time of the current segment.
*/
private JLabel segment;
private JLabel best;
/**
* Registered split time of the current segment.
*/
private Time splitTime;
/**
* Registered segment time of the current segment.
*/
private Time segmentTime;
/**
* Time when the run was paused, if it was.
*/
private Time pauseTime;
/**
* Flag indicating wether or not the split time for the current segment
* has been reached, meaning that we are now loosing time on the split.
*/
private volatile boolean splitLoss;
/**
* Flag indicating wether or not the segment time for the current segment
* has been reached, meaning that we are now loosing time on the segment.
*/
private volatile boolean segmentLoss;
private long blinkTime;
/**
* The ideal display size of this component.
*/
private Dimension preferredSize;
/**
* Wether or not the component should recompute its ideal size.
*/
private boolean resize;
private JLabel labelSplit;
private JLabel labelSegment;
private JLabel labelBest;
// ----------------------------------------------------------- CONSTRUCTORS
/**
* Creates a default panel displaying information for the given run.
*
* @param run - the run to represent.
*/
Core(Run run) {
timer = new Timer(10, this);
splitTimer = new JLabel();
segmentTimer = new JLabel();
name = new JLabel();
icon = new JLabel();
split = new JLabel();
segment = new JLabel();
best = new JLabel();
labelSplit = new JLabel("" + Language.LB_CR_SPLIT);
labelSegment = new JLabel("" + Language.LB_CR_SEGMENT);
labelBest = new JLabel("" + Language.LB_CR_BEST);
blinkTime = 0L;
preferredSize = null;
resize = false;
setRun(run);
setOpaque(false);
setDoubleBuffered(true);
placeComponents();
updateColors(ALL);
}
// -------------------------------------------------------------- INTERFACE
/**
* Sets the run to represent. All components are resetted to their initial
* state.
*
* @param run - the new run to represent.
*/
final void setRun(Run run) {
this.run = run;
updateValues(ALL);
updateVisibility(ALL);
resize = true;
revalidate();
}
/**
* Make sure to stop the updating thread when this component is being
* diposed of.
*/
@Override protected void finalize() throws Throwable {
timer.stop();
super.finalize();
}
/**
* Returns the preferred size of this component. This method is heap-cheap
* as it recomputes the preferred size only when necessary.
*/
@Override public Dimension getPreferredSize() {
if (resize) {
Graphics graphics = getGraphics();
if (graphics != null) {
Time tmFake = new Time(600000L);
Time tmRun = run.getTime(Segment.SET);
// Segment Name
FontMetrics metric = graphics.getFontMetrics();
int wName = 0;
int hName = 0;
if (Settings.COR_NAME.get()) {
for (int i = 0; i < run.getRowCount(); i++) {
String sName = run.getSegment(i).getName();
wName = Math.max(wName, metric.stringWidth(sName));
}
hName = metric.getHeight();
}
// Segment Times
int wTime = 0;
int hTime = 0;
int hBuff = metric.getHeight();
int wBuff = metric.stringWidth(
"" + (tmRun == null ? tmFake : tmRun)
);
wBuff += metric.stringWidth("XX:");
if (Settings.COR_BEST.get()) {
hTime = hTime + hBuff;
wTime = wBuff;
}
if (Settings.COR_SEGM.get()) {
hTime = hTime + hBuff;
wTime = wBuff;
}
if (Settings.COR_SPLT.get()) {
hTime = hTime + hBuff;
wTime = wBuff;
}
// Segment Icon
int hIcon = 0;
int wIcon = 0;
// TODO hasIcon ?
if (Settings.COR_ICON.get() || run.getMaxIconHeight() != 0) {
hIcon = Settings.COR_ICSZ.get();
wIcon = hIcon;
}
// Run Timer
metric = graphics.getFontMetrics(Settings.COR_TFNT.get());
int wSpTimer = metric.stringWidth(
"" + (tmRun == null ? tmFake : tmRun)
);
int hSpTimer = metric.getHeight();
// Segment Timer
int wSeTimer = 0;
int hSeTimer = 0;
if (Settings.COR_STMR.get()) {
metric = graphics.getFontMetrics(
Settings.COR_SFNT.get()
);
wSeTimer = metric.stringWidth(
"" + (tmRun == null ? tmFake : tmRun)
);
hSeTimer = metric.getHeight();
}
int maxHeight = Math.max(hIcon, hSpTimer + hSeTimer);
maxHeight = Math.max(maxHeight, hTime + hName);
int maxWidth = wIcon + Math.max(wName, wTime)
+ Math.max(wSpTimer, wSeTimer) + 5;
preferredSize = new Dimension(maxWidth, maxHeight);
setMinimumSize(new Dimension(MIN_WIDTH, maxHeight));
}
resize = false;
}
return (preferredSize == null ? getMinimumSize() : preferredSize);
}
// -------------------------------------------------------------- CALLBACKS
/**
* Callback invoked by the updater thread {@code timer}. Every hundredth
* of a second we update the values of the timers and change their color
* if weve reached a loss of time.
*/
@Override public synchronized void actionPerformed(ActionEvent event) {
long now = System.nanoTime() / 1000000L;
Segment current = run.getSegment(run.getCurrent());
Time splitElapsed = new Time(now - run.getStartTime());
Time segmentElapsed = new Time(now - current.getStartTime());
if (run.getState().equals(State.PAUSED)) {
splitTimer.setText("" + pauseTime);
if (blinkTime == 0L || now - blinkTime >= 400L) {
Color bg = Settings.CLR_BACK.get();
if (splitTimer.getForeground().equals(bg)) {
if (pauseTime.compareTo(splitTime) > 0) {
splitTimer.setForeground(Settings.CLR_LOST.get());
} else {
splitTimer.setForeground(Settings.CLR_TIMR.get());
}
} else {
splitTimer.setForeground(bg);
}
blinkTime = now;
}
} else {
splitTimer.setText("" + splitElapsed);
segmentTimer.setText("" + segmentElapsed);
if (!splitLoss && splitElapsed.compareTo(splitTime) > 0) {
splitLoss = true;
splitTimer.setForeground(Settings.CLR_LOST.get());
}
if (!segmentLoss && segmentElapsed.compareTo(segmentTime) > 0) {
segmentLoss = true;
segmentTimer.setForeground(Settings.CLR_LOST.get());
}
}
}
/**
* Callback invoked by the parent when the run or the application's
* settings have seen one of their properties updated.
*
* @param event - the event describing the update.
*/
void processPropertyChangeEvent(PropertyChangeEvent event) {
String property = event.getPropertyName();
if (Run.STATE_PROPERTY.equals(property)) {
updateValues(ALL);
updateVisibility(TIME);
if (run.getState().equals(State.PAUSED)) {
long now = System.nanoTime() / 1000000L;
pauseTime = new Time(now - run.getStartTime());
} else {
updateColors(TIMER);
}
} else if (Run.CURRENT_SEGMENT_PROPERTY.equals(property)) {
updateValues(ALL & ~TIMER);
updateColors(TIMER);
} else if (Settings.CLR_FORE.equals(property)) {
updateColors(NAME);
} else if (Settings.CLR_TIME.equals(property)) {
updateColors(TIME);
} else if (Settings.CLR_TIMR.equals(property)) {
updateColors(TIMER);
} else if (Settings.GNR_COMP.equals(property)) {
updateValues(TIME);
updateColors(TIMER);
resize = true;
revalidate();
} else if (Settings.GNR_ACCY.equals(property)) {
updateValues(TIME | TIMER);
resize = true;
revalidate();
} else if (Settings.COR_BEST.equals(property)
|| Settings.COR_SEGM.equals(property)
|| Settings.COR_SPLT.equals(property)) {
updateVisibility(TIME);
resize = true;
revalidate();
} else if (Settings.COR_NAME.equals(property)) {
updateVisibility(NAME);
resize = true;
revalidate();
} else if (Settings.COR_ICSZ.equals(property)) {
resize = true;
revalidate();
} else if (Settings.COR_TFNT.equals(property)
|| Settings.COR_SFNT.equals(property)) {
updateValues(FONT);
resize = true;
revalidate();
} else if (Settings.COR_STMR.equals(property)) {
updateVisibility(TIMER);
resize = true;
revalidate();
} else if (Settings.COR_ICON.equals(property)) {
updateVisibility(ICON);
resize = true;
revalidate();
}
}
/**
* Callback invoked by the parent when the run table of segments is
* updated.
*
* @param event - the event describing the update.
*/
void processTableModelEvent(TableModelEvent event) {
resize = true;
}
/**
* Callback invoked by the parent when default local for this instance of
* the JVM has changed.
*
* @param event - the event describing the update.
*/
public void processLocaleEvent(LocaleEvent event) {
updateValues(TIMER);
}
// -------------------------------------------------------------- UTILITIES
/**
* Places the sub-components within this component.
*/
private void placeComponents() {
setLayout(new GridBagLayout());
JPanel infoPanel = new JPanel(new GridBagLayout()); {
infoPanel.add(
name, GBC.grid(0, 0, 2, 1).anchor(GBC.LS).weight(1.0, 0.0)
);
infoPanel.add(
labelSplit, GBC.grid(0, 1).anchor(GBC.LS).insets(0, 0, 0, 3)
);
infoPanel.add(
split, GBC.grid(1, 1).anchor(GBC.LS).weight(1.0, 0.0)
);
infoPanel.add(
labelSegment,
GBC.grid(0, 2).anchor(GBC.LS).insets(0, 0, 0, 3)
);
infoPanel.add(
segment, GBC.grid(1, 2).anchor(GBC.LS).weight(1.0, 0.0)
);
infoPanel.add(
labelBest, GBC.grid(0, 3).anchor(GBC.LS).insets(0, 0, 0, 3)
);
infoPanel.add(best, GBC.grid(1, 3).anchor(GBC.LS).weight(1.0, 0.0));
infoPanel.setOpaque(false);
}
JPanel timePanel = new JPanel(new GridBagLayout()); {
timePanel.add(splitTimer, GBC.grid(0, 0).anchor(GBC.LE));
timePanel.add(segmentTimer, GBC.grid(0, 1).anchor(GBC.LE));
timePanel.setOpaque(false);
}
add(icon, GBC.grid(0, 0).insets(0, 0, 0, 8));
add(infoPanel, GBC.grid(1, 0).fill(GBC.B).weight(1.0, 1.0));
add(timePanel, GBC.grid(2, 0).fill(GBC.H));
}
private void updateVisibility(int identifier) {
if ((identifier & NAME) == NAME) {
name.setVisible(Settings.COR_NAME.get());
}
if ((identifier & TIME) == TIME) {
State state = run.getState();
boolean visible = (state == State.ONGOING || state == State.PAUSED);
split.setVisible(Settings.COR_SPLT.get());
labelSplit.setVisible(visible && Settings.COR_SPLT.get());
segment.setVisible(Settings.COR_SEGM.get());
labelSegment.setVisible(visible && Settings.COR_SEGM.get());
best.setVisible(Settings.COR_BEST.get());
labelBest.setVisible(visible && Settings.COR_BEST.get());
}
if ((identifier & TIMER) == TIMER) {
segmentTimer.setVisible(Settings.COR_STMR.get());
}
if ((identifier & ICON) == ICON) {
icon.setVisible(Settings.COR_ICON.get());
}
}
/**
* Updates the values of the group of components specified by the
* identifier.
*
* @param identifier - one of the constant update identifier.
*/
private void updateValues(int identifier) {
State state = run.getState();
boolean hasCurrent = (state == State.ONGOING || state == State.PAUSED);
int currentIdx = run.getCurrent();
Segment currentSgt = null;
if (hasCurrent) {
currentSgt = run.getSegment(currentIdx);
}
if ((identifier & TIME) == TIME) {
if (hasCurrent) {
splitLoss = false;
segmentLoss = false;
splitTime = run.getTime(currentIdx, Segment.SET);
segmentTime = currentSgt.getTime(Segment.SET);
split.setText("" + (splitTime == null ? "?" : splitTime));
segment.setText("" + (segmentTime == null ? "?" : segmentTime));
Time bestTime = currentSgt.getTime(Segment.BEST);
best.setText("" + (bestTime == null ? "?" : bestTime));
} else {
split.setText("");
segment.setText("");
best.setText("");
}
}
if ((identifier & NAME) == NAME) {
if (hasCurrent) {
name.setText(currentSgt.getName());
} else {
name.setText("");
}
}
if ((identifier & ICON) == ICON) {
if (hasCurrent) {
Icon img = currentSgt.getIcon();
if (img != null) {
icon.setIcon(
Images.rescale(img, Settings.COR_ICSZ.get()));
} else {
icon.setIcon(null);
}
} else {
icon.setIcon(null);
}
}
if ((identifier & TIMER) == TIMER) {
synchronized (this) {
if (state == State.STOPPED) {
timer.stop();
splitLoss = false;
segmentLoss = false;
segmentTimer.setText("");
Time time = run.getTime(Segment.LIVE);
splitTimer.setText(
"" + (time == null ? Language.RUN_STOPPED : time));
} else if (state == State.NULL) {
splitTimer.setText("" + Language.RUN_NULL);
segmentTimer.setText("");
} else if (state == State.READY) {
timer.stop();
splitLoss = false;
segmentLoss = false;
splitTimer.setText("" + Language.RUN_READY);
segmentTimer.setText("");
} else if (state == State.ONGOING) {
timer.restart();
}
}
}
if ((identifier & FONT) == FONT) {
splitTimer.setFont(Settings.COR_TFNT.get());
segmentTimer.setFont(Settings.COR_SFNT.get());
}
}
/**
* Updates the colors of the group of components specified by the
* identifier.
*
* @param identifier - one of the constant update identifier.
*/
private void updateColors(int identifier) {
if ((identifier & TIME) == TIME) {
Color color = Settings.CLR_TIME.get();
split.setForeground(color);
segment.setForeground(color);
best.setForeground(color);
}
if ((identifier & NAME) == NAME) {
Color color = Settings.CLR_FORE.get();
name.setForeground(color);
labelBest.setForeground(color);
labelSegment.setForeground(color);
labelSplit.setForeground(color);
}
if ((identifier & TIMER) == TIMER) {
synchronized (this) {
Color color = Settings.CLR_TIMR.get();
splitTimer.setForeground(color);
segmentTimer.setForeground(color);
}
}
}
}

View file

@ -0,0 +1,492 @@
package org.fenix.llanfair.gui;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.GridBagLayout;
import java.beans.PropertyChangeEvent;
import javax.swing.JLabel;
import javax.swing.JPanel;
import org.fenix.llanfair.Language;
import org.fenix.llanfair.Run;
import org.fenix.llanfair.Segment;
import org.fenix.llanfair.config.Settings;
import org.fenix.llanfair.Time;
import org.fenix.llanfair.Run.State;
import org.fenix.utils.gui.GBC;
import org.fenix.utils.locale.LocaleEvent;
/**
* A simple pane displaying the bare minimum of information concerning the
* previous segment of a run, namely the cumulative gain/loss on the run
* (the delta between the previous segment's live and registered split times)
* and the segment time of the previous segment.
*
* @author Xavier "Xunkar" Sencert
*/
class Footer extends JPanel {
private static final int ALL = 0xff;
private static final int TIME = 0x01;
private static final int DELTA = 0x02;
private static final int TEXT = 0x04;
private static final int BEST = 0x08;
private static final int VERBOSE = 0x10;
private static final int INSET = 3;
private Run run;
private Time tmDlta;
private JLabel labelPrev; // P.Se:
private JLabel liveL; // Left-hand Live Time
private JLabel liveR; // Right-hand Live Time
private JLabel time; // Segment Set Time
private JLabel labelDelta; // Delta:
private JLabel labelLive; // Live:
private JLabel delta; // Delta Segment Set/Live Time
private JLabel labelBest; // P.Be:
private JLabel best; // Segment Best Time
private JLabel inlineBest; // Segment Best Time (inline)
private JLabel labelDeltaBest; // B.Delta:
private JLabel deltaBest; // Delta Segment Best/Live Time
private JLabel inlineDeltaBest; // Delta Segment Best/Live Time (inline)
private JPanel panelBest; // labelBest + best
private JPanel panelDeltaBest; // labelDeltaBest + deltaBest
private boolean resize;
private Dimension preferredSize;
/**
* Creates a default panel displaying informations for the given run.
*
* @param run - the run to represent.
*/
Footer(Run run) {
time = new JLabel();
liveL = new JLabel();
liveR = new JLabel();
delta = new JLabel();
best = new JLabel();
deltaBest = new JLabel();
inlineBest = new JLabel();
inlineDeltaBest = new JLabel();
labelLive = new JLabel();
labelPrev = new JLabel();
labelBest = new JLabel();
labelDelta = new JLabel();
labelDeltaBest = new JLabel();
preferredSize = null;
resize = false;
setRun(run);
setOpaque(false);
placeComponents();
updateValues(TEXT);
updateColors(ALL);
updateVisibility(ALL);
forceResize();
}
@Override public Dimension getPreferredSize() {
Graphics graphics = getGraphics();
if (resize && (graphics != null)) {
FontMetrics metrics = graphics.getFontMetrics();
int timeW;
int timeH = metrics.getHeight();
int smtmW;
if (run.getRowCount() > 0) {
Time segmentTime = run.getSegment(0).getTime(Segment.RUN);
Time tenthTime = new Time(segmentTime.getMilliseconds() / 10L);
timeW = metrics.stringWidth("" + segmentTime);
smtmW = metrics.stringWidth("" + tenthTime);
} else {
timeW = metrics.stringWidth("" + Time.ZERO);
smtmW = timeW;
}
int liveW = metrics.stringWidth("" + Language.LB_FT_LIVE);
int prevW = metrics.stringWidth("" + Language.LB_FT_SEGMENT);
int bestW = metrics.stringWidth("" + Language.LB_FT_BEST);
int dltaW = metrics.stringWidth("" + Language.LB_FT_DELTA);
int dltbW = metrics.stringWidth("" + Language.LB_FT_DELTA_BEST);
boolean ftBest = Settings.FOO_BEST.get();
boolean ftLabels = Settings.FOO_DLBL.get();
boolean ftVerbose = Settings.FOO_VERB.get();
boolean ftTwoLines = Settings.FOO_LINE.get();
int height = timeH;
int width = prevW + timeW + smtmW + INSET * 2;
if (ftLabels) {
width += dltaW;
}
if (ftVerbose) {
width += timeW + liveW - (ftLabels ? 0 : dltaW)
+ metrics.stringWidth(" []");
}
if (ftBest) {
if (ftTwoLines) {
height *= 2;
int breakW = bestW + timeW + smtmW + (ftLabels ? dltbW : 0);
width = Math.max(width, breakW);
} else {
width += timeW + smtmW + metrics.stringWidth("| ");
}
if (ftVerbose) {
width += 5;
}
}
preferredSize = new Dimension(width, height);
setMinimumSize(new Dimension(50, height));
resize = false;
}
return (preferredSize == null ? getMinimumSize() : preferredSize);
}
/**
* Sets the run to represent. All components are resetted to their initial
* state.
*
* @param run - the new run to represent.
*/
final void setRun(Run run) {
this.run = run;
updateValues(ALL & ~TEXT);
}
// -------------------------------------------------------------- CALLBACKS
/**
* Callback invoked by the parent when the run or the application's
* settings have seen one of their properties updated.
*
* @param event - the event describing the update.
*/
void processPropertyChangeEvent(PropertyChangeEvent event) {
String property = event.getPropertyName();
if (Run.CURRENT_SEGMENT_PROPERTY.equals(property)) {
updateValues(ALL & ~TEXT);
updateColors(TIME | DELTA);
updateVisibility(ALL);
} else if (Settings.CLR_LOST.equals(property)
|| Settings.CLR_GAIN.equals(property)) {
updateColors(DELTA);
} else if (Settings.CLR_TIME.equals(property)
|| Settings.CLR_RCRD.equals(property)) {
updateColors(TIME | DELTA);
} else if (Settings.CLR_FORE.equals(property)) {
updateColors(TEXT);
} else if (Settings.GNR_ACCY.equals(property)
|| Settings.GNR_COMP.equals(property)) {
updateValues(ALL & ~TEXT);
forceResize();
} else if (Run.STATE_PROPERTY.equals(property)) {
if (run.getState() == State.NULL || run.getState() == State.READY) {
updateValues(ALL & ~TEXT);
}
updateVisibility(ALL);
} else if (Settings.FOO_SPLT.equals(property)) {
updateValues(ALL);
} else if (Settings.FOO_BEST.equals(property)
|| Settings.FOO_LINE.equals(property)) {
updateVisibility(BEST);
forceResize();
} else if (Settings.FOO_DLBL.equals(property)) {
updateVisibility(TEXT);
forceResize();
} else if (Settings.FOO_VERB.equals(property)) {
updateValues(DELTA);
updateVisibility(VERBOSE);
forceResize();
}
}
private void forceResize() {
resize = true;
revalidate();
}
/**
* Callback invoked by the parent when default local for this instance of
* the JVM has changed.
*
* @param event - the event describing the update.
*/
void processLocaleEvent(LocaleEvent event) {
updateValues(TEXT);
}
// -------------------------------------------------------------- UTILITIES
/**
* Places the sub-components within this component.
*/
private void placeComponents() {
setLayout(new GridBagLayout());
JPanel timePanel = new JPanel(new GridBagLayout()); {
timePanel.add(
labelPrev,
GBC.grid(0, 0).anchor(GBC.LS).insets(0, 0, 0, INSET)
);
timePanel.add(liveL, GBC.grid(1, 0).anchor(GBC.LS));
timePanel.add(time, GBC.grid(2, 0).anchor(GBC.LS));
timePanel.add(
inlineBest,
GBC.grid(3, 0).anchor(GBC.LS).insets(0, INSET, 0, 0)
);
timePanel.setOpaque(false);
}
JPanel deltaPanel = new JPanel(new GridBagLayout()); {
deltaPanel.add(
labelDelta,
GBC.grid(0, 0).anchor(GBC.LE).insets(0, 0, 0, INSET)
);
deltaPanel.add(
labelLive,
GBC.grid(1, 0).anchor(GBC.LE).insets(0, 0, 0, INSET)
);
deltaPanel.add(
liveR, GBC.grid(2, 0).anchor(GBC.LE).insets(0, 0, 0, INSET)
);
deltaPanel.add(delta, GBC.grid(3, 0).anchor(GBC.LE));
deltaPanel.add(
inlineDeltaBest,
GBC.grid(4, 0).anchor(GBC.LE).insets(0, INSET, 0, 0)
);
deltaPanel.setOpaque(false);
}
panelBest = new JPanel(new GridBagLayout()); {
panelBest.add(
labelBest,
GBC.grid(0, 0).anchor(GBC.LS).insets(0, 0, 0, INSET)
);
panelBest.add(best, GBC.grid(1, 0).anchor(GBC.LS));
panelBest.setOpaque(false);
}
panelDeltaBest = new JPanel(new GridBagLayout()); {
panelDeltaBest.add(
labelDeltaBest,
GBC.grid(0, 0).anchor(GBC.LE).insets(0, 0, 0, INSET)
);
panelDeltaBest.add(deltaBest, GBC.grid(1, 0).anchor(GBC.LE));
panelDeltaBest.setOpaque(false);
}
add(timePanel, GBC.grid(0, 0).anchor(GBC.LS).weight(0.5, 0.0));
add(deltaPanel, GBC.grid(1, 0).anchor(GBC.LE).weight(0.5, 0.0));
add(panelBest, GBC.grid(0, 1).anchor(GBC.LS).weight(0.5, 0.0));
add(panelDeltaBest, GBC.grid(1, 1).anchor(GBC.LE).weight(0.5, 0.0));
}
private void updateVisibility(int identifier) {
if ((identifier & BEST) == BEST) {
boolean ftTwoLines = Settings.FOO_LINE.get();
boolean ftBest = Settings.FOO_BEST.get();
panelBest.setVisible(ftTwoLines);
panelDeltaBest.setVisible(ftTwoLines);
inlineBest.setVisible(!ftTwoLines && ftBest);
inlineDeltaBest.setVisible(!ftTwoLines && ftBest);
}
if ((identifier & TEXT) == TEXT) {
boolean ftLabels = Settings.FOO_DLBL.get();
boolean ftVerbose = Settings.FOO_VERB.get();
labelLive.setVisible(ftLabels && ftVerbose);
labelDelta.setVisible(ftLabels && !ftVerbose);
labelDeltaBest.setVisible(ftLabels);
}
if ((identifier & VERBOSE) == VERBOSE) {
boolean ftVerbose = Settings.FOO_VERB.get();
boolean ftLabels = Settings.FOO_DLBL.get();
labelLive.setVisible(ftVerbose && ftLabels);
labelDelta.setVisible(!ftVerbose && ftLabels);
time.setVisible(ftVerbose);
liveL.setVisible(!ftVerbose);
liveR.setVisible(ftVerbose);
}
}
/**
* Updates the colors of the group of components specified by the
* identifier.
*
* @param identifier - one of the constant update identifier.
*/
private void updateColors(int identifier) {
if ((identifier & TIME) == TIME) {
Color colorTM = Settings.CLR_TIME.get();
Color colorNR = Settings.CLR_RCRD.get();
if (run.hasPreviousSegment() && run.isBestSegment(run.getPrevious())) {
liveL.setForeground(colorNR);
liveR.setForeground(colorNR);
} else {
liveL.setForeground(colorTM);
liveR.setForeground(colorTM);
}
time.setForeground(colorTM);
best.setForeground(colorTM);
inlineBest.setForeground(colorTM);
}
if ((identifier & DELTA) == DELTA) {
if (run.hasPreviousSegment()) {
Color colorTM = Settings.CLR_TIME.get();
deltaBest.setForeground(colorTM);
inlineDeltaBest.setForeground(colorTM);
if (delta.getText().equals("--")) {
delta.setForeground(colorTM);
} else if (run.isBestSegment(run.getPrevious())){
Color colorNR = Settings.CLR_RCRD.get();
delta.setForeground(colorNR);
deltaBest.setForeground(colorNR);
inlineDeltaBest.setForeground(colorNR);
} else {
int compare = tmDlta.compareTo(Time.ZERO);
if (compare > 0) {
delta.setForeground(Settings.CLR_LOST.get());
} else {
delta.setForeground(Settings.CLR_GAIN.get());
}
}
}
}
if ((identifier & TEXT) == TEXT) {
Color color = Settings.CLR_FORE.get();
labelPrev.setForeground(color);
labelDelta.setForeground(color);
labelLive.setForeground(color);
labelBest.setForeground(color);
labelDeltaBest.setForeground(color);
}
}
/**
* Updates the values of the group of components specified by the
* identifier.
*
* @param identifier - one of the constant update identifier.
*/
private void updateValues(int identifier) {
boolean useSplit = Settings.FOO_SPLT.get();
boolean hasPrevious = run.hasPreviousSegment();
int pIndex = run.getPrevious();
Segment pSegment = null;
Time live;
if (hasPrevious) {
pSegment = run.getSegment(pIndex);
}
if ((identifier & TIME) == TIME) {
Time set;
if (hasPrevious) {
if (useSplit) {
live = run.getTime(pIndex, Segment.LIVE);
set = run.getTime(pIndex, Segment.SET);
} else {
live = pSegment.getTime(Segment.LIVE);
set = pSegment.getTime(Segment.SET);
}
time.setText("" + (set == null ? "--" : set));
liveL.setText("" + (live == null ? "--" : live));
liveR.setText(liveL.getText());
Time bTime = pSegment.getTime(Segment.BEST);
inlineBest.setText("| " + (bTime == null ? "--" : bTime));
best.setText("" + (bTime == null ? "--" : bTime));
} else {
time.setText("");
liveL.setText("");
liveR.setText("");
best.setText("");
inlineBest.setText("");
}
}
if ((identifier & DELTA) == DELTA) {
if (hasPrevious) {
if (useSplit) {
tmDlta = run.getTime(pIndex, Segment.DELTA);
live = run.getTime(pIndex, Segment.LIVE);
if (tmDlta == null || live == null) {
delta.setText("--");
} else {
delta.setText(tmDlta.toString(true));
}
} else {
tmDlta = pSegment.getTime(Segment.DELTA);
live = pSegment.getTime(Segment.LIVE);
Time set = pSegment.getTime(Segment.SET);
Time dBst = pSegment.getTime(Segment.DELTA_BEST);
inlineDeltaBest.setText("| " + (dBst == null ? "--" : dBst.toString(true)));
deltaBest.setText("" + (dBst == null ? "--" : dBst.toString(true)));
if (set != null && pIndex > 1) {
set = set.clone();
for (int i = pIndex - 1; i >= 0; i--) {
Segment pSeg = run.getSegment(i);
Time ante = pSeg.getTime(Segment.LIVE);
if (ante == null) {
set.add(pSeg.getTime(Segment.SET));
} else {
break;
}
}
tmDlta = Time.getDelta(live, set);
}
if (tmDlta == null || live == null) {
delta.setText("--");
inlineDeltaBest.setText("| --");
deltaBest.setText("--");
} else {
delta.setText(tmDlta.toString(true));
}
if (pIndex > 0) {
Time sTime = run.getSegment(pIndex - 1)
.getTime(Segment.SET);
if (sTime == null) {
delta.setText("--");
}
}
}
if (Settings.FOO_VERB.get()) {
delta.setText("[" + delta.getText() + "]");
}
updateColors(DELTA);
} else {
delta.setText("");
inlineDeltaBest.setText("");
deltaBest.setText("");
}
}
if ((identifier & TEXT) == TEXT) {
if (useSplit) {
labelPrev.setText("" + Language.LB_FT_SPLIT);
} else {
labelPrev.setText("" + Language.LB_FT_SEGMENT);
}
labelLive.setText("" + Language.LB_FT_LIVE);
labelBest.setText("" + Language.LB_FT_BEST);
labelDelta.setText("" + Language.LB_FT_DELTA);
labelDeltaBest.setText("" + Language.LB_FT_DELTA_BEST);
}
}
}

View file

@ -0,0 +1,362 @@
package org.fenix.llanfair.gui;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridBagLayout;
import java.awt.RenderingHints;
import java.awt.Stroke;
import java.beans.PropertyChangeEvent;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.event.TableModelEvent;
import org.fenix.llanfair.Language;
import org.fenix.llanfair.Run;
import org.fenix.llanfair.Segment;
import org.fenix.llanfair.config.Settings;
import org.fenix.llanfair.Time;
import org.fenix.llanfair.Run.State;
import org.fenix.utils.gui.GBC;
import org.fenix.utils.locale.LocaleEvent;
/**
* Graph panel displaying information concerning a run. It includes an actual
* graph where each vertex is a live split time and a scale. The vertices are
* placed in accordance to {@link Run#getCompareTime()}.
*
* @author Xavier "Xunkar" Sencert
*/
class Graph extends JPanel {
// -------------------------------------------------------------- CONSTANTS
/**
* Half the thickness of the stroke used to paint the graph. This value is
* used to make sure the stroke retains its full thickness when reaching
* the top or the bottom of the canvas.
*/
protected static final int HALF_THICKNESS = 1;
/**
* The stroke used to paint the graph in itself (i.e. the lines connecting
* the vertices.)
*/
protected static final Stroke GRAPH_STROKE = new BasicStroke(2.0F);
/**
* The dashed stroke used to paint the projection of the vertices.
*/
protected static final Stroke DASHED_STROKE = new BasicStroke(
1.0F, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 1.0F,
new float[] { 2.0F }, 0.0F );
/**
* Update identifier for every category.
*/
private static final int ALL = 0xff;
/**
* Update identifier for text variables.
*/
private static final int TEXT = 0x01;
/**
* Update identifier for time variables.
*/
private static final int TIME = 0x02;
/**
* Minimum width in pixels of this component.
*/
private static final int PACK_WIDTH = 50;
/**
* Minimum height in pixels of this component.
*/
private static final int PACK_HEIGHT = 50;
// ------------------------------------------------------------- ATTRIBUTES
/**
* Run instance represented by this component.
*/
protected Run run;
/**
* Canvas where the graph will be drawn.
*/
private Canvas canvas;
/**
* Label displaying the current scale of the graph. The scale actually
* displays the time represented by the maximum ordinate.
*/
private JLabel scale;
/**
* Label describing the scale value being displayed.
*/
private JLabel scaleText;
// ----------------------------------------------------------- CONSTRUCTORS
/**
* Creates a default graph panel representing the given run.
*
* @param run - the run to represent.
*/
Graph(Run run) {
canvas = new Canvas();
scale = new JLabel();
scaleText = new JLabel();
setRun(run);
setOpaque(false);
updateValues(TEXT);
updateColors(ALL);
placeComponents();
Dimension size = new Dimension(PACK_WIDTH, PACK_HEIGHT);
setPreferredSize(size);
setMinimumSize(size);
}
// -------------------------------------------------------------- INTERFACE
/**
* Sets the new run to represent.
*
* @param run - the new run to represent.
*/
void setRun(Run run) {
this.run = run;
updateValues(TIME);
}
/**
* Callback invoked by the parent when the run or the application's
* settings have seen one of their properties updated.
*
* @param event - the event describing the update.
*/
void processPropertyChangeEvent(PropertyChangeEvent event) {
String property = event.getPropertyName();
// Settings.COLOR_FOREGROUND
if (Settings.CLR_FORE.equals(property)) {
updateColors(TEXT);
canvas.repaint();
// Settings.COLOR_TIME
} else if (Settings.CLR_TIME.equals(property)) {
updateColors(TIME);
// Settings.COLOR_BACKGROUND, COLOR_TIME_LOST, COLOR_TIME_GAINED
// or Run.CURRENT_SEGMENT_PROPERTY
} else if (Settings.CLR_BACK.equals(property)
|| Settings.CLR_LOST.equals(property)
|| Settings.CLR_GAIN.equals(property)
|| Settings.CLR_RCRD.equals(property)
|| Run.CURRENT_SEGMENT_PROPERTY.equals(property)) {
canvas.repaint();
// Settings.COMPARE_PERCENT or Settings.COMPARE_METHOD
} else if (Settings.GPH_SCAL.equals(property)
|| Settings.GNR_COMP.equals(property)) {
updateValues(TIME);
canvas.repaint();
// Settings.ACCURACY
} else if (Settings.GNR_ACCY.equals(property)) {
updateValues(TIME);
// Run.STATE_PROPERTY
} else if (Run.STATE_PROPERTY.equals(property)) {
if (run.getState() == State.READY) {
canvas.repaint();
} else if (run.getState() == State.NULL) {
updateValues(TIME);
}
}
}
/**
* Callback invoked by the parent when the run table of segments is
* updated.
*
* @param event - the event describing the update.
*/
void processTableModelEvent(TableModelEvent event) {
int type = event.getType();
int column = event.getColumn();
if (type != TableModelEvent.UPDATE
|| column == TableModelEvent.ALL_COLUMNS
|| column == Run.COLUMN_BEST
|| column == Run.COLUMN_SEGMENT
|| column == Run.COLUMN_TIME) {
updateValues(TIME);
canvas.repaint();
}
}
/**
* Callback invoked by the parent when default local for this instance of
* the JVM has changed.
*
* @param event - the event describing the update.
*/
void processLocaleEvent(LocaleEvent event) {
updateValues(TEXT);
}
// -------------------------------------------------------------- UTILITIES
/**
* Returns a percent representing the delta split time of the segment of
* given index in relation to a set fraction of the whole run given by
* {@link Run#getCompareTime()}.
*
* @param index - the index of the segment to compare.
* @return the percent of the segment delta split time and the runs
* compare time.
*/
protected long getCompareTimePercent(int index) {
long compare = run.getCompareTime().getMilliseconds();
long delta = run.getTime(index, Segment.DELTA).getMilliseconds();
return (delta * 100L) / compare;
}
/**
* Places the sub-components within this component.
*/
private void placeComponents() {
setLayout(new GridBagLayout());
JPanel scalePanel = new JPanel(); {
scalePanel.add(scaleText);
scalePanel.add(scale);
scalePanel.setOpaque(false);
}
add(scalePanel, GBC.grid(0, 0).anchor(GBC.LS));
add(canvas, GBC.grid(0, 1).fill(GBC.B).weight(1.0, 1.0));
}
/**
* Updates the values of the group of components specified by the
* identifier.
*
* @param identifier - one of the constant update identifier.
*/
private void updateValues(int identifier) {
// TIME
if ((identifier & TIME) == TIME) {
Time time = run.getCompareTime();
scale.setText("" + (time == null ? "???" : time));
}
// TEXT
if ((identifier & TEXT) == TEXT) {
scaleText.setText("" + Language.MAX_ORDINATE);
}
}
/**
* Updates the colors of the group of components specified by the
* identifier.
*
* @param identifier - one of the constant update identifier.
*/
private void updateColors(int identifier) {
// TIME
if ((identifier & TIME) == TIME) {
scale.setForeground(Settings.CLR_TIME.get());
}
// TEXT
if ((identifier & TEXT) == TEXT) {
scaleText.setForeground(Settings.CLR_FORE.get());
}
}
// ---------------------------------------------------------- INTERNAL TYPE
/**
* A simple panel whose paint method has been overriden to draw the graph.
*
* @author Xavier "Xunkar" Sencert
*/
protected class Canvas extends JPanel {
// ----------------------------------------------------- INTERFACE
/**
* Draws the graph onto the canvas.
*/
@Override protected void paintComponent(Graphics g) {
Graphics2D g2 = (Graphics2D) g;
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
int clipH = getHeight();
int clipW = getWidth();
int halfH = clipH / 2;
g2.setColor(Settings.CLR_BACK.get());
g2.fillRect(0, 0, clipW, clipH);
Color colorFG = Settings.CLR_FORE.get();
Color colorTG = Settings.CLR_GAIN.get();
Color colorTL = Settings.CLR_LOST.get();
Color colorRC = Settings.CLR_RCRD.get();
// Draw the axis.
g2.setColor(colorFG);
g2.drawLine(0, halfH, clipW, halfH);
if (run.getState() != State.NULL) {
int segCnt = run.getRowCount();
double segGap = (double) clipW / segCnt;
if (run.hasPreviousSegment()) {
// Coordinates of the last drawn vertex.
int prevX = 0;
int prevY = halfH;
for (int i = 0; i < run.getCurrent(); i++) {
Time delta = run.getTime(i, Segment.DELTA);
Time live = run.getTime(i, Segment.LIVE);
if (delta != null && live != null) {
int percent = (int) getCompareTimePercent(i);
g2.setColor(run.isBetterSegment(i) ? colorTG : colorTL);
if (run.isBestSegment(i)) {
g2.setColor(colorRC);
}
// Coordinates of this segments vertex.
int coordY = halfH - ((percent * halfH) / 100);
coordY = Math.min(clipH - HALF_THICKNESS, coordY);
coordY = Math.max(HALF_THICKNESS, coordY);
int coordX = (int) ((i + 1) * segGap);
// Set the brush depending on the delta.
g2.setStroke(GRAPH_STROKE);
// Make sure the last vertex reaches the panes end.
if (i == segCnt - 1) {
coordX = Math.min(coordX - 1, clipW);
}
g2.drawLine(prevX, prevY, coordX, coordY);
// Projection along the x axis.
g2.setColor(colorFG);
g2.setStroke(DASHED_STROKE);
g2.drawLine(coordX, halfH, coordX, coordY);
prevY = coordY;
prevX = coordX;
}
}
}
}
}
}
}

View file

@ -0,0 +1,700 @@
package org.fenix.llanfair.gui;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.GridBagLayout;
import java.beans.PropertyChangeEvent;
import java.util.ArrayList;
import java.util.List;
import javax.swing.Icon;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.event.TableModelEvent;
import org.fenix.llanfair.Language;
import org.fenix.llanfair.Run;
import org.fenix.llanfair.Segment;
import org.fenix.llanfair.config.Settings;
import org.fenix.llanfair.Time;
import org.fenix.llanfair.Run.State;
import org.fenix.llanfair.config.Merge;
import org.fenix.utils.gui.GBC;
import org.fenix.utils.Images;
/**
* A scrolling pane capable of displaying a specific number of segments with
* information concerning their times. This pane contains a viewport on
* a number of segments equals to the minimum between the total number of
* segments in the run and the number of segments to display as per the
* settings. If the history is set to display blank rows, the number of
* segments in the viewport is always the number of desired segments.
*
* @author Xavier "Xunkar" Sencert
* @version 1.2
* @see Run
* @see Segment
*/
public class History extends JPanel {
// Update Identifiers
private static final int ALL = 0xff;
private static final int TIME = 0x01;
private static final int LIVE = 0x02;
private static final int NAME = 0x04;
private static final int MARKER = 0x08;
private static final int TABS = 0x10;
private static final int DELTA = 0x20;
private static final int ICON = 0x30;
private static final int LINE = 0x40;
/**
* Run instance represented by the panel. Package-private as to make it
* available to inner types. Cannot be {@code null}.
*/
Run run;
/**
* Current number of segments to display in the history, in other words,
* the number of rows currently displayed in the viewport.
*/
private int rowCount;
/**
* List containing the rows of segment to be displayed in the history. This
* list will contain all the rows we could need.
*
* @see SegmentRow
*/
private List<SegmentRow> segmentRows;
/**
* The ideal display size of this component. Stored in an attribute to
* be retrieved easily without recomputing the size.
*/
private Dimension preferredSize;
/**
* Wether or not the component should recompute its ideal size.
*/
private boolean resize;
// ----------------------------------------------------------- CONSTRUCTORS
/**
* Creates a default panel displaying information for the given run.
* Package-private as an history must be created in a global GUI context.
*
* @param run - the run to represent. Cannot be {@code null}.
*/
History(Run run) {
super(new GridBagLayout());
segmentRows = new ArrayList<SegmentRow>();
preferredSize = null;
resize = false;
setRun(run);
setOpaque(false);
}
// -------------------------------------------------------------- INTERFACE
/**
* Returns the current number of segments to display in the history
* meaning, the number of rows in the viewport.
*
* @return the number of rows in the viewport.
*/
public int getRowCount() {
return rowCount;
}
/**
* Sets the run to represent. All rows are cleared and recreated using
* the segments from the given run.
*
* @param run - the new run to represent.
*/
final void setRun(Run run) {
this.run = run;
populateRows();
}
/**
* Returns the preferred size of this component. This method is heap-cheap
* as it recomputes the preferred size only when necessary.
*/
@Override public Dimension getPreferredSize() {
if (resize) {
Graphics graphics = getGraphics();
if (graphics != null) {
// Segment Names
FontMetrics nameMetric = graphics.getFontMetrics(
Settings.HST_SFNT.get()
);
int wName = 0;
for (int i = 0; i < run.getRowCount(); i++) {
String name = run.getSegment(i).getName();
wName = Math.max(wName, nameMetric.stringWidth(name));
}
// Split Time
FontMetrics timeMetric = graphics.getFontMetrics(
Settings.HST_TFNT.get()
);
Time tmFake = new Time(600000L);
Time tmRun = run.getTime(Segment.SET);
int wRun = timeMetric.stringWidth(
"" + (tmRun == null ? tmFake : tmRun)
);
Merge merge = Settings.HST_MERG.get();
// Live Time
int wLive = 0;
if (Settings.HST_LIVE.get() && merge != Merge.LIVE) {
wLive = wRun;
}
// Delta Time
int wDelta = 0;
if (Settings.HST_DLTA.get() && merge != Merge.DELTA) {
wDelta = wRun + timeMetric.stringWidth("[+]");
}
// Segment Icons
int wIcon = 0;
if (Settings.HST_ICON.get() && run.getMaxIconHeight() > 0) {
wIcon = Settings.HST_ICSZ.get();
}
// MAX WIDTH
int maxWidth;
if (Settings.HST_LINE.get()) {
maxWidth = Math.max(wName + wIcon, wRun + wLive + wDelta);
} else {
maxWidth = wName + wRun + wLive + wDelta + wIcon;
}
// Segment Names
int hName = nameMetric.getHeight();
// Times
int hTime = timeMetric.getHeight();
// Segment Icons.
int hIcon = 0;
if (Settings.HST_ICON.get() && run.getMaxIconHeight() > 0) {
hIcon = Settings.HST_ICSZ.get();
}
// MAX HEIGHT
int maxHeight;
if (Settings.HST_LINE.get()) {
maxHeight = Math.max(hName + hTime, hIcon);
} else {
maxHeight = Math.max(hIcon, Math.max(hName, hTime));
}
maxHeight = rowCount * maxHeight;
preferredSize = new Dimension(maxWidth + 10, maxHeight);
setMinimumSize(new Dimension(50, maxHeight));
}
resize = false;
}
return (preferredSize == null ? getMinimumSize() : preferredSize);
}
// -------------------------------------------------------------- CALLBACKS
/**
* Callback invoked by the parent when the run or the application's
* settings have seen one of their properties updated.
*
* @param event - the event describing the update.
*/
void processPropertyChangeEvent(PropertyChangeEvent event) {
String property = event.getPropertyName();
if (Run.CURRENT_SEGMENT_PROPERTY.equals(property)) {
int neu = (Integer) event.getNewValue();
int old = (Integer) event.getOldValue();
// Display the live time for the segment we just split.
int previous = run.getPrevious();
if (previous > -1) {
updateValues(LIVE, previous, previous);
}
// And move to the next segment in the history.
updateColors(MARKER);
computeViewport();
// If we unsplit, restore the previous segment values.
if (neu < old) {
updateValues(TIME, neu, neu);
updateColors(TIME, neu, neu);
segmentRows.get(neu).live.setText("");
segmentRows.get(neu).delta.setText("");
}
// updateColumnWidth();
} else if (Run.STATE_PROPERTY.equals(property)) {
// Clear the history when the run is reset.
if (run.getState() == State.READY) {
computeViewport();
updateValues(TIME | LIVE);
updateColors(MARKER | TIME);
// updateColumnWidth();
// When the run stops, clear the marker.
} else if (run.getState() == State.STOPPED) {
updateColors(MARKER);
}
} else if (Settings.HST_TABL.equals(property)) {
updateVisibility(LIVE | DELTA);
updateColumnWidth();
} else if (Settings.HST_SFNT.equals(property)) {
updateFonts(NAME);
forceResize();
} else if (Settings.HST_TFNT.equals(property)) {
updateFonts(TIME);
forceResize();
} else if (Settings.HST_LINE.equals(property)) {
updateValues(LINE);
forceResize();
} else if (Settings.HST_ICSZ.equals(property)) {
updateValues(ICON);
forceResize();
} else if (Settings.HST_ICON.equals(property)) {
updateVisibility(ICON);
forceResize();
} else if (Settings.HST_LAST.equals(property)
|| Settings.HST_OFFS.equals(property)) {
computeViewport();
} else if (Settings.HST_ROWS.equals(property)
|| Settings.HST_BLNK.equals(property)) {
populateRows();
} else if (Settings.CLR_GAIN.equals(property)
|| Settings.CLR_LOST.equals(property)
|| Settings.CLR_RCRD.equals(property)) {
updateColors(LIVE);
} else if (Settings.CLR_HIGH.equals(property)) {
updateColors(MARKER);
} else if (Settings.CLR_TIME.equals(property)) {
updateColors(TIME | LIVE);
} else if (Settings.CLR_FORE.equals(property)) {
updateColors(NAME);
} else if (Settings.GNR_ACCY.equals(property)
|| Settings.GNR_COMP.equals(property)) {
updateValues(LIVE | TIME);
forceResize();
} else if (Settings.HST_DLTA.equals(property)) {
updateVisibility(DELTA);
forceResize();
} else if (Settings.HST_LIVE.equals(property)) {
updateVisibility(LIVE);
forceResize();
} else if (Settings.HST_MERG.equals(property)) {
updateValues(TIME | LIVE);
updateColors(TIME);
forceResize();
}
}
/**
* Callback invoked by the parent when the run table of segments is
* updated.
*
* @param event - the event describing the update.
*/
void processTableModelEvent(TableModelEvent event) {
int type = event.getType();
int firstRow = event.getFirstRow();
int lastRow = event.getLastRow();
if (type == TableModelEvent.INSERT) {
populateRows();
} else if (type == TableModelEvent.DELETE) {
populateRows();
repaint();
} else if (type == TableModelEvent.UPDATE) {
if (firstRow == TableModelEvent.HEADER_ROW) {
populateRows();
} else {
updateValues(TIME | NAME | ICON, firstRow, lastRow);
updateVisibility(ICON);
}
}
}
// -------------------------------------------------------------- UTILITIES
/**
* Creates a segment row for each segment in the run, counting possible
* blank rows in advance and places them in the panel. Each row is then
* displayed using {@code setVisible()} depending on wether we want it
* in the viewport or not. This function should be called everytime the
* run structure is changed and rows must be added or removed.
*/
private void populateRows() {
// Clear the panel and the row list.
removeAll();
segmentRows.clear();
// At most, we need as much segments as the run has or as much as
// is demanded by the user.
int count = Settings.HST_ROWS.get();
count = Math.max(count, run.getRowCount());
// Create and place the rows.
for (int i = 0; i < count; i++) {
SegmentRow row = new SegmentRow();
add(row, GBC.grid(0, i).fill(GBC.H).weight(1.0, 0.0));
segmentRows.add(i, row);
}
// Fill the rows with the segment data and colorize.
updateValues(ALL);
updateColors(ALL);
updateFonts(ALL);
updateVisibility(ALL);
// Only display the segments we can currently see.
computeViewport();
// Force computation of minimum component size.
forceResize();
}
/**
* Asks the component to compute its minimum size and assume it. Should be
* called everytime an update to the run or the settings could impact the
* height/width of the history.
*/
private void forceResize() {
resize = true;
revalidate();
}
/**
* Computes which segments need to be displayed in the history and sets
* their visibility accordingly.
*/
private void computeViewport() {
// If we display blank rows, the row count is always the value
// from the settings, else check how much segments are in the run.
rowCount = Settings.HST_ROWS.get();
if (!Settings.HST_BLNK.get()) {
rowCount = Math.min(run.getRowCount(), rowCount);
}
// If we always display the last segment, we scroll on n-1 segments.
boolean showLast = Settings.HST_LAST.get();
int realCount = showLast ? rowCount - 1 : rowCount;
int endOffset = showLast ? 2 : 1;
// Find out which segment will be at the end of the history.
int desired = run.getCurrent() + Settings.HST_OFFS.get();
int lastSeg = (desired < realCount) ? realCount - 1 : desired;
if (lastSeg > run.getRowCount() - endOffset) {
lastSeg = run.getRowCount() - endOffset;
}
// Set the visibility of every segments accordingly.
for (int i = 0; i < segmentRows.size(); i++) {
segmentRows.get(i).setVisible(
(i > lastSeg - realCount) && (i <= lastSeg)
);
}
// Display the last segment if the setting is enabled.
if (Settings.HST_LAST.get() && run.getRowCount() > 0) {
segmentRows.get(run.getRowCount() - 1).setVisible(true);
}
}
private void updateColumnWidth() {
int width = 0;
int height = 0;
if (run.hasPreviousSegment()) {
SegmentRow previous = segmentRows.get(run.getPrevious());
FontMetrics metrics = getGraphics().getFontMetrics(
Settings.HST_TFNT.get()
);
width = metrics.stringWidth(previous.delta.getText());
height = metrics.getHeight();
}
for (int i = 0; i < segmentRows.size(); i++) {
segmentRows.get(i).delta.setPreferredSize(
new Dimension(width, height)
);
segmentRows.get(i).revalidate();
}
}
private void updateColors(int identifier, int first, int last) {
for (int i = first; i <= last; i++) {
segmentRows.get(i).updateColors(i, identifier);
}
}
private void updateColors(int identifier) {
updateColors(identifier, 0, run.getRowCount() - 1);
}
private void updateValues(int identifier, int first, int last) {
for (int i = first; i <= last; i++) {
segmentRows.get(i).updateValues(i, identifier);
}
}
private void updateValues(int identifier) {
updateValues(identifier, 0, run.getRowCount() - 1);
}
private void updateVisibility(int identifier, int first, int last) {
for (int i = first; i <= last; i++) {
segmentRows.get(i).updateVisibility(i, identifier);
}
}
private void updateVisibility(int identifier) {
updateVisibility(identifier, 0, run.getRowCount() - 1);
}
private void updateFonts(int identifier, int first, int last) {
for (int i = first; i <= last; i++) {
segmentRows.get(i).updateFonts(i, identifier);
}
}
private void updateFonts(int identifier) {
updateFonts(identifier, 0, run.getRowCount() - 1);
}
// --------------------------------------------------------- INTERNAL TYPES
private class SegmentRow extends JPanel {
// ------------------------------------------------------ CONSTANTS
/**
* Margin in pixels between the labels.
*/
static final int INSET = 3;
// ----------------------------------------------------- ATTRIBUTES
/**
* The icon (if any) of this segment.
*/
JLabel icon;
/**
* The name of the segment.
*/
JLabel name;
/**
* The registered split time of this segment.
*/
JLabel time;
/**
* The achieved split time of this segment.
*/
JLabel live;
/**
* The delta between the registered and achieved split time.
*/
JLabel delta;
/**
* The counters updates that occured during this segment.
*/
List<JLabel> counters;
// --------------------------------------------------- CONSTRUCTORS
/**
* Creates an empty new segment row.
*/
SegmentRow() {
super(new GridBagLayout());
icon = new JLabel();
name = new JLabel();
time = new JLabel();
live = new JLabel();
delta = new JLabel();
counters = new ArrayList<JLabel>();
icon.setHorizontalAlignment(JLabel.CENTER);
setOpaque(false);
placeComponents(Settings.HST_LINE.get());
}
// ------------------------------------------------------ INTERFACE
/**
* Updates the values of the group of components specified by the
* identifier for this segment row. The row must know which segment
* he represents by passing the segment index.
*
* @param index - index of the segment represented by this row.
* @param identifier - one of the constant update identifier.
*/
void updateValues(int index, int identifier) {
if ((identifier & NAME) == NAME) {
name.setText(run.getSegment(index).getName());
}
if ((identifier & TIME) == TIME) {
Time setTime = run.getTime(index, Segment.SET);
time.setText("" + (setTime == null ? "?" : setTime));
}
if ((identifier & ICON) == ICON) {
int iconSize = Settings.HST_ICSZ.get();
Icon runIcon = run.getSegment(index).getIcon();
if (runIcon != null) {
icon.setIcon(Images.rescale(runIcon, iconSize));
} else {
icon.setIcon(null);
}
icon.setPreferredSize(new Dimension(iconSize, iconSize));
icon.setMinimumSize(new Dimension(iconSize, iconSize));
}
if ((identifier & LINE) == LINE) {
removeAll();
placeComponents(Settings.HST_LINE.get());
}
if ((identifier & LIVE) == LIVE && (index > -1)) {
Merge merge = Settings.HST_MERG.get();
JLabel realDelta = (merge == Merge.DELTA) ? time : delta;
JLabel realLive = (merge == Merge.LIVE ) ? time : live;
if (index < run.getCurrent()) {
Time liveTime = run.getTime(index, Segment.LIVE);
if (liveTime == null) {
realLive.setText("?");
realDelta.setText("[?]");
} else {
realLive.setText("" + liveTime);
String text = "?";
Time deltaTime = run.getTime(index, Segment.DELTA);
if (deltaTime != null) {
text = deltaTime.toString(true);
}
if (merge == Merge.DELTA) {
realDelta.setText(text);
} else {
realDelta.setText("[" + text + "]");
}
}
} else {
Time setTime = run.getTime(index, Segment.SET);
realLive.setText(
(merge == Merge.LIVE) ?
"" + (setTime == null ? "?" : setTime)
: ""
);
realDelta.setText(
(merge == Merge.DELTA) ?
"" + (setTime == null ? "?" : setTime)
: ""
);
}
updateColors(index, LIVE);
}
}
void updateVisibility(int index, int identifier) {
if ((identifier & LIVE) == LIVE) {
live.setVisible(
Settings.HST_LIVE.get() || Settings.HST_TABL.get()
);
}
if ((identifier & DELTA) == DELTA) {
delta.setVisible(Settings.HST_DLTA.get() || Settings.HST_TABL.get());
}
if ((identifier & ICON) == ICON) {
icon.setVisible(Settings.HST_ICON.get()
&& run.getMaxIconHeight() > 0);
}
}
void updateColors(int index, int identifier) {
if ((identifier & NAME) == NAME) {
name.setForeground(Settings.CLR_FORE.get());
}
if ((identifier & TIME) == TIME) {
time.setForeground(Settings.CLR_TIME.get());
}
if ((identifier & MARKER) == MARKER) {
if (run.getCurrent() == index) {
name.setForeground(Settings.CLR_HIGH.get());
} else {
name.setForeground(Settings.CLR_FORE.get());
}
}
if ((identifier & LIVE) == LIVE && (index > -1)) {
Color lost = Settings.CLR_LOST.get();
Color gain = Settings.CLR_GAIN.get();
Color neut = Settings.CLR_TIME.get();
Color recd = Settings.CLR_RCRD.get();
int prev = run.getPrevious();
Merge merge = Settings.HST_MERG.get();
JLabel realDelta = (merge == Merge.DELTA) ? time : delta;
JLabel realLive = (merge == Merge.LIVE ) ? time : live;
if (index <= prev) {
Time liveTime = run.getTime(index, Segment.LIVE);
Time deltaTime = run.getTime(index, Segment.DELTA);
if (run.isBestSegment(index)) {
realLive.setForeground(recd);
realDelta.setForeground(recd);
} else {
if (deltaTime != null) {
int compare = deltaTime.compareTo(Time.ZERO);
if (liveTime == null) {
realLive.setForeground(neut);
realDelta.setForeground(neut);
} else {
if (compare > 0) {
realDelta.setForeground(lost);
realLive.setForeground(lost);
} else {
realDelta.setForeground(gain);
realLive.setForeground(gain);
}
}
} else {
realDelta.setForeground(neut);
realLive.setForeground(neut);
}
}
}
}
}
void updateFonts(int index, int identifier) {
if ((identifier & NAME) == NAME) {
name.setFont(Settings.HST_SFNT.get());
}
if ((identifier & TIME) == TIME) {
Font font = Settings.HST_TFNT.get();
time.setFont(font);
live.setFont(font);
delta.setFont(font);
}
}
// ------------------------------------------------------ UTILITIES
private void placeComponents(boolean multiline) {
if (!multiline) {
add(icon , GBC.grid(0, 0).anchor(GBC.C));
add(
name, GBC.grid(1, 0).insets(0, INSET, 0, 0).anchor(GBC.LS)
.fill(GBC.H).weight(1.0, 0.0)
);
add(time , GBC.grid(2, 0).insets(0, INSET, 0, 0).anchor(GBC.LE));
add(live , GBC.grid(3, 0).insets(0, INSET, 0, 0).anchor(GBC.LE));
add(delta, GBC.grid(4, 0).insets(0, INSET, 0, 0).anchor(GBC.LE));
} else {
add(icon, GBC.grid(0, 0, 1, 2).anchor(GBC.C));
add(
name, GBC.grid(1, 0, 3, 1).anchor(GBC.LS).fill(GBC.H)
.weight(1.0, 0.0).insets(0, INSET, 0, 0)
);
add(time, GBC.grid(1, 1).anchor(GBC.LE).insets(0, INSET, 0, 0));
add(live, GBC.grid(2, 1).anchor(GBC.LE).insets(0, INSET, 0, 0));
add(delta, GBC.grid(3, 1).anchor(GBC.LE));
}
}
}
}

View file

@ -0,0 +1,407 @@
package org.fenix.llanfair.gui;
import java.awt.Color;
import java.awt.Font;
import java.awt.GridBagLayout;
import java.beans.PropertyChangeEvent;
import java.util.ArrayList;
import java.util.List;
import javax.swing.BorderFactory;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.event.TableModelEvent;
import org.fenix.llanfair.Language;
import org.fenix.llanfair.Run;
import org.fenix.llanfair.Segment;
import org.fenix.llanfair.config.Settings;
import org.fenix.llanfair.Run.State;
import org.fenix.llanfair.Time;
import org.fenix.utils.gui.GBC;
import org.fenix.utils.locale.LocaleEvent;
/**
* A panel representing informations on a given run. This panel uses numerous
* sub-component to given different kind of informations like a graph or a
* segments history that can be toggled on or off via the {@link Settings}.
*
* @author Xavier "Xunkar" Sencert
*/
public class RunPane extends JPanel {
// -------------------------------------------------------------- CONSTANTS
/**
* Font used to render the title of the run.
*/
private static final Font RUN_TITLE_FONT = Font.decode("Arial-12-BOLD");
/**
* Update identifier for every category.
*/
private static final int ALL = 0xff;
/**
* Update identifier for time variables.
*/
private static final int GOAL = 0x01;
/**
* Update identifier for separator variables.
*/
private static final int SEPARATOR = 0x02;
/**
* Update identifier for text variables.
*/
private static final int TEXT = 0x04;
/**
* Update identifier for title variables.
*/
private static final int TITLE = 0x08;
/**
* Update identifier for background variables.
*/
private static final int BACKGROUND = 0x10;
/**
* Update identifier for the graph component.
*/
private static final int GRAPH = 0x20;
/**
* Update identifier for the footer component.
*/
private static final int FOOTER = 0x40;
// ------------------------------------------------------------- ATTRIBUTES
/**
* Run instance represented by this component.
*/
private Run run;
/**
* Label displaying the title of the current run.
*/
private JLabel title;
/**
* Label displaying the current goal of run. By default its the time of
* the run were comparing against, but it can be a customized string.
*/
private JLabel goal;
/**
* Label describing the goal value being displayed.
*/
private JLabel goalText;
/**
* Panel containing both goal value and text.
*/
private JPanel goalPane;
/**
* A list containing empty labels serving as separators.
*/
private List<JLabel> separators;
/**
* Simple footer displaying information on the previous segment if any.
* Is only visible if {@link Settings#FOOTER_DISPLAY} is {@code true}.
*/
private Footer footer;
/**
* Panel displaying the core information like the current segment and the
* necessary timers.
*/
private Core core;
/**
* Panel representing the current run as a graph.
*/
private Graph graph;
/**
* Scrolling panel displaying information regarding every segment of the
* run up to the last segment or {@link Settings#DISPLAYED_SEGMENTS}.
*/
private History history;
// ----------------------------------------------------------- CONSTRUCTORS
/**
* Creates a default panel representing the given run.
*
* @param run - the run to represent.
*/
public RunPane(Run run) {
super(new GridBagLayout());
if (run == null) {
throw new NullPointerException("null run");
}
title = new JLabel();
goal = new JLabel();
goalText = new JLabel();
core = new Core(run);
graph = new Graph(run);
history = new History(run);
footer = new Footer(run);
separators = new ArrayList<JLabel>();
placeComponents();
setRun(run);
updateValues(TEXT);
updateColors(ALL);
updateVisibility(ALL);
title.setFont(RUN_TITLE_FONT);
}
// -------------------------------------------------------------- INTERFACE
/**
* Sets the new run to represent.
*
* @param run - the new run to represent.
*/
public final void setRun(Run run) {
if (run == null) {
throw new NullPointerException("null run");
}
this.run = run;
core.setRun(run);
graph.setRun(run);
history.setRun(run);
footer.setRun(run);
updateValues(ALL & ~TEXT);
}
// -------------------------------------------------------------- CALLBACKS
/**
* Callback invoked by the parent when default local for this instance of
* the JVM has changed.
*
* @param event - the event describing the update.
*/
public void processLocaleEvent(LocaleEvent event) {
core.processLocaleEvent(event);
graph.processLocaleEvent(event);
footer.processLocaleEvent(event);
updateValues(TEXT);
}
/**
* Callback invoked by the parent when the run or the application's
* settings have seen one of their properties updated.
*
* @param event - the event describing the update.
*/
public void processPropertyChangeEvent(PropertyChangeEvent event) {
core.processPropertyChangeEvent(event);
graph.processPropertyChangeEvent(event);
history.processPropertyChangeEvent(event);
footer.processPropertyChangeEvent(event);
String property = event.getPropertyName();
if (Run.STATE_PROPERTY.equals(property)) {
if (run.getState() == State.READY
|| run.getState() == State.NULL) {
updateValues(GOAL | SEPARATOR);
}
} else if (Run.NAME_PROPERTY.equals(property)) {
updateValues(TITLE);
} else if (Settings.CLR_BACK.equals(property)) {
updateColors(BACKGROUND);
} else if (Settings.CLR_FORE.equals(property)) {
updateColors(TEXT);
} else if (Settings.CLR_SPRT.equals(property)) {
updateColors(SEPARATOR);
} else if (Settings.HST_ROWS.equals(property)) {
updateValues(SEPARATOR);
} else if (Settings.CLR_TIME.equals(property)) {
updateColors(GOAL);
} else if (Settings.CLR_TITL.equals(property)) {
updateColors(TITLE);
} else if (Settings.GNR_COMP.equals(property)) {
updateValues(GOAL);
} else if (Settings.GPH_SHOW.equals(property)) {
updateVisibility(GRAPH);
} else if (Settings.FOO_SHOW.equals(property)) {
updateVisibility(FOOTER);
} else if (Settings.HDR_GOAL.equals(property)) {
updateVisibility(GOAL);
updateValues(SEPARATOR);
} else if (Settings.HDR_TTLE.equals(property)) {
updateVisibility(TITLE | GOAL);
updateValues(SEPARATOR);
} else if (Settings.GNR_ACCY.equals(property)
|| Run.GOAL_PROPERTY.equals(property)) {
updateValues(GOAL);
}
}
/**
* Callback invoked by the parent when the run table of segments is
* updated.
*
* @param event - the event describing the update.
*/
public void processTableModelEvent(TableModelEvent event) {
core.processTableModelEvent(event);
graph.processTableModelEvent(event);
history.processTableModelEvent(event);
if (event.getType() == TableModelEvent.INSERT
|| event.getType() == TableModelEvent.DELETE
|| event.getType() == TableModelEvent.UPDATE) {
updateValues(GOAL);
}
}
// -------------------------------------------------------------- UTILITIES
/**
* Adds a new separator to the list of separators used by the component
* and returns it.
*
* @param a new separator.
*/
private JLabel createSeparator() {
JLabel label = new JLabel();
separators.add(label);
return label;
}
/**
* Places the sub-components within this component.
*/
private void placeComponents() {
goalPane = new JPanel(new GridBagLayout()); {
goalPane.add(goalText, GBC.grid(0, 0));
goalPane.add(goal, GBC.grid(1, 0).insets(0, 3, 0, 0));
goalPane.setOpaque(false);
}
add(title,GBC.grid(0, 0).insets(3, 0, 1, 0));
add(createSeparator(), GBC.grid(0, 2).insets(3, 0).fill(GBC.H));
add(history, GBC.grid(0, 3).fill(GBC.H).insets(0, 5));
add(createSeparator(), GBC.grid(0, 4).insets(3, 0).fill(GBC.H));
add(createSeparator(), GBC.grid(0, 6).insets(3, 0, 0, 0).fill(GBC.H));
updateVisibility(ALL);
}
/**
* Updates the values of the group of components specified by the
* identifier.
*
* @param identifier - one of the constant update identifier.
*/
private void updateValues(int identifier) {
if ((identifier & GOAL) == GOAL) {
if (run.getGoal() == null || run.getGoal().equals("")) {
Time time = run.getTime(Segment.SET);
goal.setText("" + (time == null ? "???" : time));
} else {
goal.setText(run.getGoal());
}
}
if ((identifier & TEXT) == TEXT) {
goalText.setText("" + Language.GOAL);
}
if ((identifier & TITLE) == TITLE) {
title.setText("" + run.getName());
}
if ((identifier & SEPARATOR) == SEPARATOR) {
boolean hdTitle = Settings.HDR_TTLE.get();
boolean hdGoal = Settings.HDR_GOAL.get();
boolean hsRows = history.getRowCount() > 0;
separators.get(0).setVisible(hdTitle || hdGoal);
separators.get(1).setVisible(hsRows);
}
}
/**
* Updates the colors of the group of components specified by the
* identifier.
*
* @param identifier - one of the constant update identifier.
*/
private void updateColors(int identifier) {
if ((identifier & GOAL) == GOAL) {
goal.setForeground(Settings.CLR_TIME.get());
}
if ((identifier & TEXT) == TEXT) {
goalText.setForeground(Settings.CLR_FORE.get());
}
if ((identifier & TITLE) == TITLE) {
title.setForeground(Settings.CLR_TITL.get());
}
if ((identifier & BACKGROUND) == BACKGROUND) {
setBackground(Settings.CLR_BACK.get());
}
if ((identifier & SEPARATOR) == SEPARATOR) {
Color color = Settings.CLR_SPRT.get();
for (JLabel separator : separators) {
separator.setBorder(
BorderFactory.createMatteBorder(1, 0, 0, 0, color));
}
}
}
/**
* Updates the visibility of the components specified by the
* identifier.
*
* @param identifier - one of the constant update identifier.
*/
private void updateVisibility(int identifier) {
if ((identifier & GRAPH) == GRAPH) {
if (Settings.GPH_SHOW.get()) {
remove(core);
add(core, GBC.grid(0, 5).insets(0, 5).fill(GBC.H));
add(graph, GBC.grid(0, 7).fill(GBC.B).insets(0, 0, 3, 0)
.weight(1.0, 1.0));
} else {
remove(graph);
remove(core);
add(core, GBC.grid(0, 5).insets(0, 5).fill(GBC.H)
.weight(1.0, 1.0));
}
}
if ((identifier & FOOTER) == FOOTER) {
if (Settings.FOO_SHOW.get()) {
add(footer, GBC.grid(0, 8).insets(0, 3).fill(GBC.H));
} else {
remove(footer);
}
}
if ((identifier & GOAL) == GOAL) {
if (Settings.HDR_GOAL.get()) {
if (Settings.HDR_TTLE.get()) {
add(goalPane, GBC.grid(0, 1));
} else {
add(goalPane, GBC.grid(0, 1).insets(3, 0, 0, 0));
}
} else {
remove(goalPane);
}
}
if ((identifier & TITLE) == TITLE) {
title.setVisible(Settings.HDR_TTLE.get());
}
revalidate();
repaint();
}
}

BIN
res/digitalism.ttf Normal file

Binary file not shown.

BIN
res/img/ARROW_DOWN Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

BIN
res/img/ARROW_UP Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3 KiB

BIN
res/img/Llanfair Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

BIN
res/img/MINUS Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

BIN
res/img/PLUS Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3 KiB

BIN
res/img/REVERT Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

BIN
res/img/de Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3 KiB

BIN
res/img/donate Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

BIN
res/img/en Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

BIN
res/img/fr Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3 KiB

BIN
res/img/jmi/ABOUT.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

BIN
res/img/jmi/EDIT.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3 KiB

BIN
res/img/jmi/EXIT.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

BIN
res/img/jmi/IMPORT.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

BIN
res/img/jmi/LOCK.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

BIN
res/img/jmi/NEW.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

BIN
res/img/jmi/OPEN.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3 KiB

BIN
res/img/jmi/OPEN_RECENT.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3 KiB

BIN
res/img/jmi/RESET.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

BIN
res/img/jmi/SAVE.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

BIN
res/img/jmi/SAVE_AS.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

BIN
res/img/jmi/SETTINGS.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

BIN
res/img/jmi/UNLOCK.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

BIN
res/img/nl Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3 KiB

BIN
res/img/sv Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3 KiB

182
res/language.properties Normal file
View file

@ -0,0 +1,182 @@
setting_alwaysOnTop = Always on Top
setting_language = Language
setting_viewerLanguage = Viewer's Language
setting_recentFiles =
setting_coordinates =
setting_dimension =
setting_compareMethod = Compare Method
setting_accuracy = Accuracy
setting_locked =
setting_warnOnReset = Warn on Reset if better times
setting_color_background = Background
setting_color_foreground = Foreground
setting_color_time = Time
setting_color_timer = Timer
setting_color_timeGained = Time Gained
setting_color_timeLost = Time Lost
setting_color_newRecord = New Record
setting_color_title = Title
setting_color_highlight = Highlight
setting_color_separators = Separators
setting_hotkey_split = Start / Split
setting_hotkey_unsplit = Unsplit
setting_hotkey_skip = Skip
setting_hotkey_reset = Reset
setting_hotkey_stop = Stop
setting_hotkey_pause = Pause
setting_hotkey_lock = Lock / Unlock
setting_header_goal = Display Goal
setting_header_title = Display Run Title
setting_history_rowCount = Number of Rows
setting_history_tabular = Display in Columns
setting_history_blankRows = Display Blank Rows
setting_history_multiline = Two Lines per Row
setting_history_merge = Merge
setting_history_liveTimes = Live Times
setting_history_deltas = Deltas
setting_history_icons = Icons
setting_history_iconSize = Icon Size (in pixels)
setting_history_offset = Scrolling Offset
setting_history_alwaysShowLast = Always Show Last Segment
setting_history_segmentFont = Names
setting_history_timeFont = Times
setting_core_accuracy = Accuracy
setting_core_icons = Segment Icon
setting_core_iconSize = Icon Size (in pixels)
setting_core_segmentName = Segment Name
setting_core_splitTime = Split Time
setting_core_segmentTime = Segment Time
setting_core_bestTime = Best Time
setting_core_segmentTimer = Segment Timer
setting_core_timerFont = Main Timer
setting_core_segmentTimerFont = Segment Timer
setting_graph_display = Display Graph
setting_graph_scale =
setting_footer_display = Display
setting_footer_useSplitData = Use Split Data
setting_footer_verbose = Show More Info
setting_footer_bestTime = Best Time
setting_footer_multiline = Display on Two Lines
setting_footer_deltaLabels = Delta Labels
accuracy_seconds = Seconds
accuracy_tenth = 10th of a second
accuracy_hundredth = 100th of a second
compare_best_overall_run = Best Overall Run
compare_sum_of_best_segments = Sum of Best Segments
merge_none = Don't Merge
merge_live = Merge Live Times
merge_delta = Merge Deltas
menuItem_edit = Edit...
menuItem_new = New
menuItem_open = Open...
menuItem_open_recent = Open Recent
menuItem_import = Import...
menuItem_save = Save
menuItem_save_as = Save As...
menuItem_reset = Reset
menuItem_lock = Lock
menuItem_unlock = Unlock
menuItem_resize_default = Resize (Default)
menuItem_resize_preferred = Resize (Preferred)
menuItem_settings = Settings
menuItem_about = About
menuItem_exit = Exit
error_read_file = "{0}" isn't a valid Llanfair run or you do not have permission to read it.
error_write_file = You do not have permission to write in that folder.
error_import_run = "{0}" isn't a recognized run file.
GENERAL = General
TIMER = Timer
FOOTER = Footer
MISC = Miscellaneous
USE_MAIN_FONT = Use Main Timer's font
LB_GOAL = Goal
ICON = Icon
COLORS = Colors
ED_SEGMENTED = Segmented Run
TT_ED_SEGMENTED = A segmented run automatically pauses after each split, useful for per-map timing.
PN_DIMENSION = Dimension
PN_DISPLAY = Display
PN_FONTS = Fonts
PN_SCROLLING = Scrolling
HISTORY = History
MERGE_DELTA = Merge Deltas
MERGE_LIVE = Merge Live Times
MERGE_NONE = Don't Merge
TT_HS_OFFSET = Segment, relative to the current one, to follow while scrolling e.g. with +1 the history will scroll while always displaying the next segment (+1 from the current.)
LB_CR_BEST = Be:
LB_CR_SEGMENT = Se:
LB_CR_SPLIT = Sp:
LB_FT_BEST = P.Be:
LB_FT_DELTA = Delta:
LB_FT_DELTA_BEST = B.Delta:
LB_FT_LIVE = Live:
LB_FT_SEGMENT = P.Se:
LB_FT_SPLIT = P.Sp:
ABOUT_MESSAGE =
ERROR_OPENING = There was an error opening Â?{0},Â? the file may no longer exist, may be corrupted or incompatible or you may not be authorized to read in that folder.
ERROR_SAVING = There was an error saving Â?{0},Â? you may not be authorized to write inside this folder. Warning: The run was NOT saved !
ICON_TOO_BIG =
ILLEGAL_TIME = Your time cannot be lower than or equal to zero.
ILLEGAL_SEGMENT_TIME = When setting a previously undefined segment time, the new time cannot exceed or equal the next segment time.
INPUT_NAN = Value of Â?{0}Â? must be a number.
INPUT_NEGATIVE = Value of Â?{0}Â? must be a positive number.
INVALID_TIME_STAMP = The input string Â?{0}Â? is not a valid time stamp.
WARN_BETTER_RUN = It seems that you've established a new personal best. Do you want to save your run?
WARN_BETTER_TIMES = It seems that you've beaten some of your best segment times (up to your last split.) Do you want to save them? (The run itself won't be saved.)
WARN_RESET_SETTINGS = Are you sure you want to reset your settings?
TT_ADD_SEGMENT = Inserts a new empty segment at the end of the run.
TT_COLOR_PICK = Select a color in the column on your left and select its new color with the selector.
TT_COLUMN_BEST = The best time ever registered on this segment. Must be lower than or equal to the segment time.
TT_COLUMN_SEGMENT = The segment time registered during your best overall run.
TT_COLUMN_TIME = The split time of the segment during your best overall run.
TT_REMOVE_SEGMENT = Deletes the selected segment.
TT_MOVE_SEGMENT_UP = Moves the selected segment up one position.
TT_MOVE_SEGMENT_DOWN = Moves the selected segment down one position.
RUN_NULL = No Segments
RUN_OVER =
RUN_READY = Ready
RUN_STOPPED = Stopped
BEST_RUN = Best Overall Run
BEST_SEGMENTS = Sum of Best Segments
ACCURACY = Accuracy
SECONDS = Seconds
TENTH = 10th of a second
HUNDREDTH = 100th of a second
title_about = About
EDIT = Edit
EXIT = Exit
HOTKEYS = Disable Hotkeys
HOTKEYS_ON = Enable Hotkeys
IMPORT = Import
NEW = New
OPEN = Open
OPEN_RECENT = Open Recent
RESET = Reset
RESIZE_DEFAULT = Resize (Default)
RESIZE_PREFERRED = Resize (Preferred)
SAVE = Save
SAVE_AS = Save As...
SETTINGS = Settings
ACCEPT = Accept
APPLICATION = Application
BEST = Best Segment
CANCEL = Cancel
COMPARE_METHOD = Compare Method
COMPONENTS = Components
DISABLED = <Disabled>
EDITING = Editing Run
ERROR = Error
GOAL = Goal:
IMAGE = Image
INPUTS = Hotkeys
MAX_ORDINATE = Max Ord.:
NAME = Name
RUN_TITLE = Run Title
SEGMENT = Time (Segment)
SEGMENTS = Segments
SPLIT = Split:
TIME = Time (Split)
UNTITLED = <untitled>
WARNING = Warning
INCREMENT =
START_VALUE =

174
res/language_de.properties Normal file
View file

@ -0,0 +1,174 @@
# Color settings
COLOR_BACKGROUND = Hintergrund
COLOR_FOREGROUND = Vordergrund
COLOR_SEPARATORS = Zwischenr\u00e4ume
COLOR_TIME = Zeit
COLOR_TIME_GAINED = Gewonnene Zeit
COLOR_TIME_LOST = Verlorene Zeit
COLOR_TIMER = Timer
COLOR_TITLE = Titel
COLORS = Farben
# Hotkeys settings
KEY_RESET = Reset
KEY_START_SPLIT = Start / Split
KEY_SKIP = \u00dcberspringen
KEY_STOP = Stopp
KEY_UNSPLIT = L\u00f6schen
# Run states
RUN_NULL = Keine Segmente
RUN_READY = Bereit
RUN_STOPPED = Gestoppt
# History settings
HISTORY = Geschichte
# General settings
GENERAL = Allgemein
# Input settings
INPUTS = Hotkeys
# Compare methods
BEST_RUN = Bester Run
BEST_SEGMENTS = Summe der besten Segmente
# Name of the configurable display elements
SHOW_DELTAS = Deltas
# Accuracy
ACCURACY = Genauigkeit
SECONDS = Sekunden
TENTH = Zehntel einer Sekunde
HUNDREDTH = Hundertstel einer Sekunde
# Menu Items
ABOUT = \u00dcber
EDIT = Bearbeiten
EXIT = Verlassen
HOTKEYS = Hotkeys deaktivieren
HOTKEYS_ON = Hotkeys aktivieren
IMPORT = Importieren
NEW = Neu
OPEN = \u00d6ffnen
OPEN_RECENT = Zuletzt ge\u00f6ffnet
RESIZE_DEFAULT = Gr\u00f6\u00dfe (Voreingestellt)
RESIZE_PREFERRED= Gr\u00f6\u00dfe (Bevorzugt)
SAVE = Speichern
SETTINGS = Einstellungen
# Messages
ABOUT_MESSAGE = This application is distributed under the creative commons licence <b>BY-NC-SA</b> which stipulates that you must abide by the following rules: the application must be redistributed under the same licence, the name(s) of the author(s) must always be cited and you cannot make a commercial use of it.
ERROR_OPENING = Fehler beim \u00d6fnnen von \u0093{0},\u0094 m\u00f6glicherweise existiert die Datei nicht mehr; sie ist besch\u00e4digt, nicht kompatibel bzw. es fehlen notwendige Rechte im Zielordner.
ERROR_SAVING = Fehler beim Speichern von \u0093{0},\u0094 m\u00f6glicherweise ist der Orndner schreibgesch\u00fctzt. Warnung: Die Zeit wurde NICHT gespeichert!
ILLEGAL_TIME = Deine Zeit kann nicht 0 oder weniger sein.
ILLEGAL_SEGMENT_TIME = Eine bislang nicht definierte Zeit kann nicht gr\u00f6\u00dfer/gleich der folgenden Zeit sein!
INPUT_NAN = Wert von \u0093{0}\u0094 muss eine Zahl sein.
INPUT_NEGATIVE = Wert von \u0093{0}\u0094 muss eine positive Zahl sein.
INVALID_TIME_STAMP = Diese Eingabe \u0093{0}\u0094 ist nicht m\u00f6glich.
WARN_BETTER_RUN = Neue Bestzeit! M\u00f6chtest du diese Zeit speichern?
WARN_BETTER_TIMES = Teile deines Runs sind besser als der Vorherige. M\u00f6chtest du diese speichern? (Der Run selbst wird NICHT gespeichert)
WARN_RESET_SETTINGS = Bist du sicher, dass du deine Einstellungen zur\u00fccksetzen willst?
# Tooltips
TT_ADD_SEGMENT = Neues Segment einf\u00fcgen.
TT_COLOR_PICK = W\u00e4hle eine Farbe in der Spalte auf der linken Seite und w\u00e4hle die neue Farbe mit dem Tool.
TT_COLUMN_BEST = Die bis jetzt beste Zeit in diesem Segment. Muss gleich oder niedriger als die Segment Zeit sein.
TT_COLUMN_SEGMENT = Die Segment Zeit des besten Runs.
TT_COLUMN_TIME = Die Split Zeit des besten Runs.
TT_REMOVE_SEGMENT = Ausgew\u00e4hltes Segment l\u00f6schen.
TT_MOVE_SEGMENT_UP = Ausgew\u00e4hltes Segment um eine Position nach oben verschieben.
TT_MOVE_SEGMENT_DOWN = Ausgew\u00e4hltes Segment um eine Position nach unten verschieben.
# Other properties
ACCEPT = \u00dcbernehmen
ALWAYS_ON_TOP = Immer im Vordergrund
APPLICATION = Anwendung
BEST = Bestes Segment
CANCEL = Abbrechen
COMPARE_METHOD = Vergleichsmethoden
COMPONENTS = Komponenten
DISABLED = <Deaktiviert>
EDITING = Run bearbeiten
ERROR = Fehler
GOAL = Ziel:
ICON = Symbol
IMAGE = Bild
LANGUAGE = Sprache
MAX_ORDINATE = Max Ord.:
NAME = Name
RUN_TITLE = Run Titel
SEGMENT = Zeit (Segment)
SEGMENTS = Segments
SPLIT = Split:
TIME = Zeit (Split)
UNTITLED = <Unbenannt>
WARNING = Warnung
# 1.3
ICON_SIZE = Icon Gr\u00f6\u00dfe (in Pixel)
TIMER = Timer
FOOTER = Fu\u00dfzeile
MISC = Sonstiges
FOOTER_DISPLAY = Anzeige
FOOTER_USE_SPLIT = Split Daten verwenden
SHOW_GOAL = Ziel anzeigen
SHOW_GRAPH = Graphen anzeigen
KEY_PAUSE = Pause
TIMER_FONT = Allgemeiner Timer
TIMER_SEG_FONT = Segment Timer
USE_MAIN_FONT = Font des allg. Timers benutzen
PN_FONTS = Fonts
WARN_ON_RESET = Warnung bei Reset, wenn bessere Zeiten vorhanden sind
KEY_HOTKEYS = Lock / Unlock
LB_GOAL = Ziel
# 1.4
ED_SEGMENTED = Segmentierter Run
TT_ED_SEGMENTED = Ein segmentierter Run pausiert automatisch nach jedem Split; n\u00fctzlich f\u00fcr per-map timing.
PN_DISPLAY = Anzeige
PN_SCROLLING = Scrolling
PN_DIMENSION = Dimension
HS_ICONS = Icons
HS_LIVES = Live Zeiten
HS_DELTAS = Deltas
HS_ROWS = Anzahl der Reihen
HS_ICON_SIZE = Icon Gr\u00f6\u00dfe (in Pixel)
HS_BLANK_ROWS = Blanke Reihen anzeigen
HS_TWO_LINES = Zwei Linien per Reihe
HS_NAMES = Namen
HS_TIMES = Zeiten
HS_SHOW_LAST = Letztes Segment immer anzeigen
HS_OFFSET = Split scrollen
MERGE_DELTA = Deltas mergen
MERGE_LIVE = Live Zeiten mergen
MERGE_NONE = Nicht mergen
TT_HS_OFFSET = Aktiviert relatives Segment scrolling; basierend auf dem jetzigen. Bei z.B. +1 wird die history immer scrollen, sodass das n\u00e4chste (+1) Segment ganz unten angezeigt wird.
CR_ICON = Segment Icon
COLOR_RECORD = Neuer Rekord
SAVE_AS = Speichern als...
CR_NAME = Segment Name
CR_SPLIT = Split Time
CR_SEGMENT = Segment Zeit
CR_BEST = Beste Zeit
CR_TIMER = Segment Timer
LB_CR_SPLIT = Spl.:
LB_CR_SEGMENT = Seg.:
LB_CR_BEST = Best.:
# 1.4.2
COLOR_HIGHLIGHT = Highlight
RESET = Reset
FT_BEST = Best Time
FT_LABELS = Delta Labels
FT_TWO_LINES = Display on Two Lines
LB_FT_BEST = P.Be:
LB_FT_SEGMENT = P.Se:
LB_FT_DELTA = Delta:
LB_FT_SPLIT = P.Sp:
HD_TITLE = Display Run Title
LB_FT_DELTA_BEST= B.Delta:
LB_FT_LIVE = Live:
FT_VERBOSE = Show More Info
HS_TABULAR = Display in Columns

179
res/language_fr.properties Normal file
View file

@ -0,0 +1,179 @@
# Color settings
COLOR_BACKGROUND = Fond
COLOR_FOREGROUND = Texte
COLOR_SEPARATORS = S\u00e9parateurs
COLOR_TIME = Temps
COLOR_TIME_GAINED = Temps Gagn\u00e9
COLOR_TIME_LOST = Temps Perdu
COLOR_TIMER = Chronom\u00e8tre
COLOR_TITLE = Titre
COLORS = Couleurs
# Hotkeys settings
KEY_RESET = R\u00e9initialiser
KEY_SKIP = Suivant
KEY_START_SPLIT = D\u00e9marrer / Split
KEY_STOP = Arr\u00eater
KEY_UNSPLIT = Pr\u00e9c\u00e9dent
# Run states
RUN_NULL = Aucun Segment
RUN_READY = Pr\u00eat
RUN_STOPPED = Arr\u00eat\u00e9
# History settings
HISTORY = Historique
# General settings
GENERAL = G\u00e9n\u00e9ral
# Input settings
INPUTS = Raccourcis
# Compare methods
BEST_RUN = Meilleur Temps de Course
BEST_SEGMENTS = Somme des Meilleurs Segments
# Name of the configurable display elements
SHOW_DELTAS = Deltas
# Accuracy
ACCURACY = Pr\u00e9cision
SECONDS = Secondes
TENTH = Dixi\u00e8mes
HUNDREDTH = Centi\u00e8mes
# Menu Items
ABOUT = \u00c0 Propos
EDIT = \u00c9diter
EXIT = Quitter
HOTKEYS = D\u00e9sactiver les Raccourcis
HOTKEYS_ON = Activer les Raccourcis
IMPORT = Importer
NEW = Nouveau
OPEN = Ouvrir
OPEN_RECENT = Ouvrir R\u00e9cent
RESIZE_DEFAULT = Retailler (D\u00e9faut)
RESIZE_PREFERRED = Retailler (Pr\u00e9f\u00e9r\u00e9e)
SAVE = Enregister
SETTINGS = Param\u00e8tres
# Messages
ABOUT_MESSAGE = Cette application est distribu\u00e9e sous licence Creation Commons <b>BY-NC-SA</b> dont les conditions sont les suivantes : l'application doit \u00eatre redistribu\u00e9e sous la m\u00eame licence, les noms des auteurs doivent toujours figurer en clair et il est interdit d'en faire un quelconque usage commercial.
ERROR_OPENING = Une erreur est survenue lors de l'ouverture de \u00ab {0}, \u00bb le fichier est peut-\u00eatre corrompu ou vous ne disposez pas des droits de lecture.
ERROR_SAVING = Une erreur est survenue lors de l'\u00e9criture de \u00ab {0}, \u00bb vous ne disposez peut-\u00eatre pas des droits d'\u00e9criture. Attention : la course n'a PAS \u00e9t\u00e9 enregistr\u00e9e.
ILLEGAL_TIME = Votre temps ne peut \u00eatre inf\u00e9rieur ou \u00e9gal \u00e0 z\u00e9ro.
ILLEGAL_SEGMENT_TIME = Lors de la d\u00e9finition d'un temps de segment pr\u00e9c\u00e9demment vide, la nouvelle valeur ne peut \u00eatre sup\u00e9rieure ou \u00e9gale \u00e0 celle du prochain segment.
INPUT_NAN = La valeur pour \u00ab {0} \u00bb doit \u00eatre un nombre.
INPUT_NEGATIVE = La valeur pour \u00ab {0} \u00bb doit \u00eatre un nombre positif.
INVALID_TIME_STAMP = La cha\u00eene de caract\u00e8re \u00ab {0} \u00bb ne correspond pas \u00e0 un temps.
WARN_BETTER_RUN = Il semble que vous venez d'\u00e9tablir un nouveau record personnel, voulez-vous enregistrer vos temps ?
WARN_BETTER_TIMES = Il semble que vous ayez \u00e9tabli un nouveau record sur certains segments (jusqu'au dernier split,) voulez-vous les enregistrer ? (La course elle-m\u00eame ne sera pas enregistr\u00e9e.)
WARN_RESET_SETTINGS = \u00cates-vous s\u00fbr de vouloir r\u00e9initialiser vos r\u00e9glages ?
# Tooltips
TT_ADD_SEGMENT = Ins\u00e8re un segment vide \u00e0 la fin de la course.
TT_COLOR_PICK = S\u00e9lectionnez une couleur dans la colonne de gauche puis s\u00e9lectionnez la nouvelle couleur.
TT_COLUMN_BEST = Le meilleur temps de segment jamais enregistr\u00e9. Doit \u00eatre inf\u00e9rieur ou \u00e9gal au temps de segment.
TT_COLUMN_SEGMENT = Le temps de segment enregistr\u00e9 lors de la meilleure course.
TT_COLUMN_TIME = Le temps de split enregistr\u00e9 lors de la meilleure course.
TT_REMOVE_SEGMENT = Supprime le segment s\u00e9lectionn\u00e9.
TT_MOVE_SEGMENT_UP = D\u00e9place le segment s\u00e9lectionn\u00e9 d'un cran vers le haut.
TT_MOVE_SEGMENT_DOWN = D\u00e9place le segment s\u00e9lectionn\u00e9 d'un cran vers le bas.
# Labels
LB_DELTA = Delta:
LB_SEGMENT = Segment:
LB_SPLIT = Split:
# Other properties
ACCEPT = Valider
ALWAYS_ON_TOP = Toujours au-dessus
APPLICATION = Application
BEST = Meilleur Segment
CANCEL = Annuler
COMPARE_METHOD = M\u00e9thode de Comparaison
COMPONENTS = Composants
DISABLED = <D\u00e9sactiv\u00e9>
EDITING = \u00c9dition de la Course
ERROR = Erreur
GOAL = Objectif :
ICON = Ic\u00f4ne
IMAGE = Image
LANGUAGE = Langue
MAX_ORDINATE = Ord. Max :
NAME = Nom
RUN_TITLE = Titre de la Course
SEGMENT = Temps (Segment)
SEGMENTS = Segments
SPLIT = Split :
TIME = Temps (Split)
UNTITLED = <sans titre>
WARNING = Attention
# 1.3
ICON_SIZE = Taille d'ic\u00f4ne (en pixels)
TIMER = Chronom\u00e8tre
FOOTER = Pied-de-page
MISC = Divers
FOOTER_DISPLAY = Afficher
FOOTER_USE_SPLIT = Utiliser les infos de split
SHOW_GOAL = Afficher l'objectif
SHOW_GRAPH = Afficher le graph
KEY_PAUSE = Pause
TIMER_FONT = Chrono. Principal
TIMER_SEG_FONT = Chrono. Segment
USE_MAIN_FONT = Utiliser la police principale
PN_FONTS = Polices
WARN_ON_RESET = Avertir lors d'une R\u00e0Z si meilleur temps
KEY_HOTKEYS = Verrouiller / D\u00e9verrouiller
LB_GOAL = Objectif
# 1.4
ED_SEGMENTED = Course Segment\u00e9e
TT_ED_SEGMENTED = Une course segment\u00e9e est mise en pause apr\u00e8s chaque segment.
PN_DISPLAY = Affichage
PN_SCROLLING = D\u00e9filement
PN_DIMENSION = Dimension
HS_ICONS = Ic\u00f4nes
HS_LIVES = Temps R\u00e9alis\u00e9s
HS_DELTAS = Deltas
HS_ROWS = Nombre de lignes
HS_ICON_SIZE = Taille d'Ic\u00f4ne (en pixels)
HS_BLANK_ROWS = Afficher les lignes vides
HS_TWO_LINES = Affichage sur deux lignes
HS_NAMES = Noms
HS_TIMES = Temps
HS_SHOW_LAST = Toujours afficher le dernier
HS_OFFSET = D\u00e9calage du D\u00e9filement
MERGE_DELTA = Fusionner les Deltas
MERGE_LIVE = Fusionner les Temps R\u00e9alis\u00e9s
MERGE_NONE = Ne pas fusionner
TT_HS_OFFSET = Le segment, par rapport au segment courant, \u00e0 suivre lors d'un d\u00e9filement ex: +1 fait en sorte que le prochain segment soit toujours visible.
CR_ICON = Ic\u00f4ne de Segment
COLOR_RECORD = Nouveau Record
SAVE_AS = Enregistrer sous...
CR_NAME = Nom de Segment
CR_SPLIT = Temps de Split
CR_SEGMENT = Temps de Segment
CR_BEST = Meilleur Temps
CR_TIMER = Chronom\u00e8tre Segment
LB_CR_SPLIT = Sp:
LB_CR_SEGMENT = Se:
LB_CR_BEST = Me:
# 1.4.2
COLOR_HIGHLIGHT = Surlignage
RESET = R\u00e9initialiser
FT_BEST = Meilleur Temps
FT_LABELS = \u00c9tiquettes "Delta"
FT_TWO_LINES = Afficher sur deux lignes
LB_FT_BEST = Me.P:
LB_FT_SEGMENT = Se.P:
LB_FT_DELTA = Delta:
LB_FT_SPLIT = Sp.P:
HD_TITLE = Afficher le titre
LB_FT_DELTA_BEST= M.Delta:
LB_FT_LIVE = Live:
FT_VERBOSE = Afficher plus d'infos
HS_TABULAR = Afficher en colonnes

193
res/language_nl.properties Normal file
View file

@ -0,0 +1,193 @@
# Name of the configurable colors
COLOR_BACKGROUND = Achtergrond
COLOR_FOREGROUND = Voorgrond
COLOR_SEPARATORS = Verdeellijn
COLOR_TIME = Tijd
COLOR_TIME_GAINED = Tijd Gewonnen
COLOR_TIME_LOST = Tijd Verloren
COLOR_TIMER = Tijdklok
COLOR_TITLE = Titel
COLORS = Kleuren
# Footer settings
FOOTER_DISPLAY = Voetstuk weergeven
FOOTER_USE_SPLIT = Geef Split Details weer
# Name of the configurable inputs
KEY_RESET = Herstart
KEY_SKIP = Overslaan
KEY_START_SPLIT = Start / Split
KEY_STOP = Stop
KEY_UNSPLIT = Niet Gesplit
# Name of the configurable display elements
SHOW_DELTAS = Deltas
SHOW_DETAILED_FOOTER = Gedetailleerd Voetstuk
SHOW_GOAL = Doel
SHOW_GRAPH = Diagram
# Run states
RUN_NULL = Geen Onderdelen
RUN_READY = Klaar
RUN_STOPPED = Gestopt
# Compare methods
BEST_RUN = Beste Tijden
BEST_SEGMENTS = Beste Onderdeel Tijden
# Accuracies
ACCURACY = Nauwkeurigheid
SECONDS = Seconden
TENTH = 10de van een seconde
HUNDREDTH = 100ste van een seconde
# Menu Items
ABOUT = Over Ons
EDIT = Bewerk
EXIT = Verlaat
HOTKEYS = Schakel Sneltoetsen Uit
HOTKEYS_ON = Sneltoetsen Activeren
RESIZE_DEFAULT = Formaat wijzigen (Standaard)
RESIZE_PREFERRED = Formaat wijzigen (Aanbevolen)
IMPORT = Importeer
OPEN = Openen
OPEN_RECENT = Recent Geopend
SAVE = Opslaan
SETTINGS = Instellingen
COLUMNS = Kolommen
HEIGHT = Hoogte
HEIGHT_UNIT = onderdelen
HISTORY_MERGE = Samenvoegen
MERGE_DELTA = Tijden & Deltas
MERGE_LIVE = Tijden & Live Tijden
MERGE_NONE = Niet Samenvoegen
HISTORY = Geschiedenis
HISTORY_LIVE = Live Tijden
INPUTS = Sneltoetsen
GENERAL = Algemeen
# Messages
ABOUT_MESSAGE = Deze applicatie valt onder het \u0093creative commons licence\u0094 <b>BY-NC-SA</b> Dat betekend dat je moet voldoen aan de volgende regels: de applicatie moet onder de zelfde licentie uitgegeven worden, de naam/namen van de auteur(-en) moeten altijd aangegeven worden, en het mag niet gebruikt worden voor commerciele doelen.
ERROR_OPENING = Er is een fout opgetreden tijdens het openen van de tijden \u0093{0},\u0094 het bestaat waarschijnlijk niet, is corrupt of U bent niet geautoriseerd om in deze map te lezen.
ERROR_SAVING = Er is een fout opgetreden tijdens het opslaan \u0093{0},\u0094 Het kan zijn dat U niet geautoriseerd bent om in deze map op te slaan. Waarschuwing: Uw tijden zijn niet opgeslagen!
ILLEGAL_TIME = Uw tijd kan niet onder de nul en/of nul zijn.
INPUT_NAN = De waarde van \u0093{0}\u0094 moet een nummer zijn.
INPUT_NEGATIVE = De waarde van \u0093{0}\u0094 moet een nummer boven de nul zijn.
INVALID_TIME_STAMP = De ingevoerde tijd \u0093{0}\u0094 is geen geldige tijdstempel.
WARN_BETTER_RUN = Het lijkt erop dat U een nieuw Persoonlijk Record heeft, Wilt U deze opslaan?
WARN_BETTER_TIMES = Het lijkt erop dat U uw onderdeel tijden heeft verbeterd, Wilt U deze opslaan? Waarschuwing: de run word niet opgeslagen!
WARN_RESET_SETTINGS = Weet u zeker dat U de instellingen opnieuw wilt zetten?
# Tooltips
TT_ADD_SEGMENT = Een nieuw onderdeel toevoegen aan het einde van de lijst.
TT_COLOR_PICK = Selecteer een kleur in de kolom aan de linkerkant en selecteer de nieuwe kleur met de kleurenkiezer.
TT_COLUMN_BEST = De beste tijd ooit geregistreerd op dit onderdeel. Moet lager of gelijk zijn aan de tijd van dit onderdeel.
TT_COLUMN_SEGMENT = Onderdeeltijd geregistreerd op uw beste run.
TT_COLUMN_TIME = Beste split tijdens uw beste run.
TT_REMOVE_SEGMENT = Verwijderd het geselecteerde onderdeel.
TT_MOVE_SEGMENT_UP = Verplaatst het geselecteerde onderdeel een plaats omhoog.
TT_MOVE_SEGMENT_DOWN = Verplaatst het geselecteerde onderdeel een plaats naar beneden.
# Labels
LB_DELTA = Delta:
LB_SEGMENT = Onderdeel tijd:
LB_SPLIT = Split:
# Other properties
ACCEPT = Accepteer
ALWAYS_ON_TOP = Altijd Bovenaan
APPLICATION = Algemeen
BEST = Beste Onderdeel Tijd
CANCEL = Annuleren
COMPARE_METHOD = Vergelijk Methode
COMPONENTS = Componenten
DISABLED = <Uitgeschakeld>
EDITING = Bewerken
ERROR = Foutmelding
GOAL = Doel:
ICON = Icoon
ICON_SIZE = Grootte:
IMAGE = Plaatje
INPUTS = Sneltoetsen
LANGUAGE = Taal
LAST_SEGMENT = Laatste Onderdeel:
MAX_ORDINATE = Max Ord.:
NAME = Naam
NEW = Nieuw
RUN_TITLE = Titel
SEGMENT = Tijd (Onderdeel)
SEGMENTS = Onderdelen
SPLIT = Split:
TIME = Tijd (Split)
UNTITLED = <Naamloos>
WARNING = Waarschuwing
# 1.3
ICON_SIZE = Icoon formaat (in pixels)
TIMER = Timer
FOOTER = Voetstuk
MISC = Overige
FOOTER_DISPLAY = Scherm
FOOTER_USE_SPLIT = Gebruik Split Data
CORE_TEXT = Laat onderdeel info zien
SHOW_GOAL = Laat het doel zien
SHOW_GRAPH = Laat de grafiek zien
KEY_PAUSE = Pauze
TIMER_FONT = Hoofd Timer
TIMER_SEG_FONT = Onderdeel Timer
USE_MAIN_FONT = Gebruik het hoofdtimer lettertype
FONTS = Lettertypes
WARN_ON_RESET = Waarschuw bij reset als je een betere tijd hebt.
SHOW_SEG_TIMER = Laat onderdeel timer zien
KEY_HOTKEYS = Opslot / Open
LB_GOAL = Doel
# 1.4
ED_SEGMENTED = Segmented Run
TT_ED_SEGMENTED = A segmented run automatically pauses after each split, useful for per-map timing.
PN_DISPLAY = Display
PN_SCROLLING = Scrolling
PN_DIMENSION = Dimension
HS_ICONS = Icons
HS_LIVES = Live Times
HS_DELTAS = Deltas
HS_ROWS = Number of Rows
HS_ICON_SIZE = Icon Size (in pixels)
HS_BLANK_ROWS = Display Blank Rows
HS_TWO_LINES = Two Lines per Row
HS_NAMES = Names
HS_TIMES = Times
HS_SHOW_LAST = Always Show Last Segment
HS_OFFSET = Scrolling Offset
MERGE_DELTA = Merge Deltas
MERGE_LIVE = Merge Live Times
MERGE_NONE = Don't Merge
TT_HS_OFFSET = Segment, relative to the current one, to follow while scrolling e.g. with +1 the history will scroll while always displaying the next segment (+1 from the current.)
CR_ICON = Segment Icon
COLOR_RECORD = New Record
SAVE_AS = Save As...
CR_NAME = Segment Name
CR_SPLIT = Split Time
CR_SEGMENT = Segment Time
CR_BEST = Best Time
CR_TIMER = Segment Timer
LB_CR_SPLIT = Sp:
LB_CR_SEGMENT = Se:
LB_CR_BEST = Be:
# 1.4.2
COLOR_HIGHLIGHT = Highlight
RESET = Reset
FT_BEST = Best Time
FT_LABELS = Delta Labels
FT_TWO_LINES = Display on Two Lines
LB_FT_BEST = P.Be:
LB_FT_SEGMENT = P.Se:
LB_FT_DELTA = Delta:
LB_FT_SPLIT = P.Sp:
HD_TITLE = Display Run Title
LB_FT_DELTA_BEST= B.Delta:
LB_FT_LIVE = Live:
FT_VERBOSE = Show More Info
HS_TABULAR = Display in Columns

179
res/language_sv.properties Normal file
View file

@ -0,0 +1,179 @@
# Color settings
COLOR_BACKGROUND = Bakgrund
COLOR_FOREGROUND = F\u00f6rgrund
COLOR_SEPARATORS = Separerare
COLOR_TIME = Tid
COLOR_TIME_GAINED = Tid Intj\u00e4nad
COLOR_TIME_LOST = Tid F\u00f6rlorad
COLOR_TIMER = Tidtagare
COLOR_TITLE = Titel
COLORS = F\u00e4rger
# Hotkeys settings
KEY_RESET = Nollst\u00e4ll
KEY_SKIP = Hoppa \u00f6ver
KEY_START_SPLIT = Start / Del
KEY_STOP = Stopp
KEY_UNSPLIT = Backa
# Run states
RUN_NULL = Inga segment
RUN_READY = Redo
RUN_STOPPED = Stoppad
# History settings
HISTORY = Historia
# General settings
GENERAL = Generellt
# Input settings
INPUTS = Snabbknappar
# Compare methods
BEST_RUN = B\u00e4sta Loppet
BEST_SEGMENTS = Summan av de b\u00e4sta segmenten
# Name of the configurable display elements
SHOW_DELTAS = Delta tider
# Accuracy
ACCURACY = Precision
SECONDS = Sekunder
TENTH = Tiondels sekund
HUNDREDTH = Hundradels sekund
# Menu Items
ABOUT = Om
EDIT = \u00c4ndra
EXIT = Avsluta
HOTKEYS = Inaktivera Snabbknapar
HOTKEYS_ON = Aktivera Snabbknapar
IMPORT = Importera
NEW = Ny
OPEN = \u00d6ppna
OPEN_RECENT = \u00d6ppna Nyligen Anv\u00e4nd
RESIZE_DEFAULT = \u00c4ndra storlek (Standard)
RESIZE_PREFERRED = \u00c4ndra storlek (Optimal)
SAVE = Spara
SETTINGS = Inst\u00e4llningar
# Messages
ABOUT_MESSAGE = Denna applikation distribueras under "creative commons licence <b>BY-NC-SA</b>" vilken stipulerar att du m\u00e5ste f\u00f6lja dessa regler: applikationen m\u00e5ste \u00e5terdistribueras under samma licens, namn(en) p\u00e5 f\u00f6rfattaren(na) m\u00e5ste alltid kvars\u00e5 samt att det inte \u00e4r till\u00e5tet att anv\u00e4nda applikationen i ett kommersiellt syfte.
ERROR_OPENING = Det blev ett fel vid \u00f6ppnandet av \u0093{0},\u0094 filen kanske inte l\u00e4ngre existerar, \u00e4r korrupt, inkompatibel eller s\u00e5 har du inte r\u00e4ttigheter att l\u00e4sa fr\u00e5n den katalogen.
ERROR_SAVING = Det blev ett fel vid sparandet av \u0093{0},\u0094 du kanske inte har tillst\u00e5nd att skriva i den katalogen. Varning: Loppet sparades INTE!
ILLEGAL_TIME = Din tid kan inte vara mindre \u00e4n eller lika med noll.
ILLEGAL_SEGMENT_TIME = N\u00e4r du matar in ett tidigare odefinerat segment s\u00e5 kan tiden inte vara samma som eller mer \u00e4n n\u00e4sta segmenttid.
INPUT_NAN = V\u00e4rdet f\u00f6r \u0093{0}\u0094 m\u00e5ste vara ett nummer.
INPUT_NEGATIVE = V\u00e4rdet f\u00f6r \u0093{0}\u0094 m\u00e5ste vara mer \u00e4n noll.
INVALID_TIME_STAMP = Str\u00e4ngen \u0093{0}\u0094 \u00e4r inte en giltig tidpunkt.
WARN_BETTER_RUN = Det verkar som du fick ett nytt personb\u00e4sta. Grattis! Vill du spara ditt lopp?
WARN_BETTER_TIMES = Det verkar som du fick n\u00e5gra b\u00e4ttre segmenttider (fram till sista delen.) Vill du spara dem? (Sj\u00e4lva loppet kommer inte att sparas)
WARN_RESET_SETTINGS = \u00c4r du s\u00e4ker p\u00e5 att du vill nollst\u00e4lla inst\u00e4llningarna?
# Tooltips
TT_ADD_SEGMENT = L\u00e4gg till ett tomt segment i slutet p\u00e5 loppet.
TT_COLOR_PICK = V\u00e4lj en f\u00e4rg i kolumnen till v\u00e4nster och v\u00e4lj dess nya f\u00e4rg i v\u00e4ljaren.
TT_COLUMN_BEST = Den b\u00e4sta tiden n\u00e5gonsin registrerat for detta segment. M\u00e5ste vara mindre \u00e4n eller lika med segmenttiden.
TT_COLUMN_SEGMENT = Segmenttiden registrerad under ditt totalt sett b\u00e4sta lopp.
TT_COLUMN_TIME = Deltiden for det markerade segmented under ditt b\u00e4sta lopp.
TT_REMOVE_SEGMENT = Tar bort det markerade segmentet.
TT_MOVE_SEGMENT_UP = Flyttar upp det markerade segmentet ett steg.
TT_MOVE_SEGMENT_DOWN = Flyttar ner det markerade segmentet ett steg.
# Labels
LB_DELTA = Delta:
LB_SEGMENT = Segment:
LB_SPLIT = Del:
# Other properties
ACCEPT = Acceptera
ALWAYS_ON_TOP = Alltid h\u00f6gst up
APPLICATION = Applikation
BEST = B\u00e4st Segment
CANCEL = Avbryt
COMPARE_METHOD = J\u00e4mf\u00f6r Metod
COMPONENTS = Komponenter
DISABLED = <Inaktiverad>
EDITING = \u00c4ndrar Lopp
ERROR = Error
GOAL = M\u00e5l:
ICON = Ikon
IMAGE = Bild
LANGUAGE = Spr\u00e5k
MAX_ORDINATE = Max Ord.:
NAME = Namn
RUN_TITLE = Lopp Titel
SEGMENT = Tid (Segment)
SEGMENTS = Segment
SPLIT = Del:
TIME = Tid (Split)
UNTITLED = <namnl\u00f6s>
WARNING = Varning
# 1.3
ICON_SIZE = Ikonstorlek
TIMER = Tidtagare
FOOTER = Fot
MISC = \u00d6vrigt
FOOTER_DISPLAY = Visning
FOOTER_USE_SPLIT = Anv\u00e4nd split information
SHOW_GOAL = Visa m\u00e5l
SHOW_GRAPH = Visa graf
KEY_PAUSE = Paus
TIMER_FONT = Huvudtimer
TIMER_SEG_FONT = Segmenttimer
USE_MAIN_FONT = Anv\u00e4nd huvudtimerns typsnitt
PN_FONTS = Typsnitt
WARN_ON_RESET = Varna vid nollst\u00e4llning om det finns b\u00e4ttre tider
KEY_HOTKEYS = L\u00e5s / L\u00e5s upp
LB_GOAL = M\u00e5l
# 1.4
ED_SEGMENTED = Uppdelat Lopp
TT_ED_SEGMENTED = Ett uppdelat lopp pausas automatiskt efter varje del, anv\u00e4ndbart f\u00f6r kart-uppdelad timing.
PN_DISPLAY = Visa
PN_SCROLLING = Scrollning
PN_DIMENSION = Dimension
HS_ICONS = Ikoner
HS_LIVES = Aktuella tider
HS_DELTAS = Deltan
HS_ROWS = Antal rader
HS_ICON_SIZE = Ikonstorlek (i pixlar)
HS_BLANK_ROWS = Visa tomma rader
HS_TWO_LINES = Tv\u00e5 textrader per rad
HS_NAMES = Namn
HS_TIMES = Tider
HS_SHOW_LAST = Visa alltid sista segmentet
HS_OFFSET = Skrollavvikelse
MERGE_DELTA = Sammanfoga deltan
MERGE_LIVE = Sammanfoga aktuella tider
MERGE_NONE = Sammanfoga inte
TT_HS_OFFSET = Segment, relativt till set nuvarande, att f\u00f6lja medans man stroller. T.ex med +1 kommer historiken skrollas men kommer ocks\u00e5 alltid visa n\u00e4sta segment (+1 fr\u00e5n nuvarande)
CR_ICON = Segment Ikon
COLOR_RECORD = Nytt rekord
SAVE_AS = Spara som
CR_NAME = Segment Namn
CR_SPLIT = Split Tid
CR_SEGMENT = Segment Tid
CR_BEST = B\u00e4sta Tid
CR_TIMER = Segment Tidtagare
LB_CR_SPLIT = Sp:
LB_CR_SEGMENT = Se:
LB_CR_BEST = B\u00e4:
# 1.4.2
COLOR_HIGHLIGHT = H\u00f6jdpunkt
RESET = \u00c5terst\u00e4l
FT_BEST = B\u00e4sta tid
FT_LABELS = Delta etiketter
FT_TWO_LINES = Visa p\u00e5 tv\u00e5 linjer
LB_FT_BEST = P.B\u00e4:
LB_FT_SEGMENT = P.Se:
LB_FT_DELTA = Delta:
LB_FT_SPLIT = P.Del:
HD_TITLE = Visa lopp titel
LB_FT_DELTA_BEST= B.Delta:
LB_FT_LIVE = Nu:
FT_VERBOSE = Visa mer information
HS_TABULAR = Visa i kolumner

5
res/llanfair.properties Normal file
View file

@ -0,0 +1,5 @@
website = http://www.jenmaarai.com/llanfair
donate = https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=EPTSR32CQTHBY
about = <html><div width=260 align=justify>Llanfair 1.5 "E.Honda" [rev 20]<br><br>This application is distributed under the creative commons licence <b>BY-NC-SA</b> which stipulates that you must abide by the following rules: the application must be redistributed under the same licence, the name(s) of the author(s) must always be cited and you cannot make a commercial use of it.<br><br><b>Author</b><br>&nbsp;&nbsp;Xunkar<br><br><b>Localization provided by</b><br>&nbsp;&nbsp;Kokarn (Swedish)<br>&nbsp;&nbsp;MickeyG (Dutch)<br>&nbsp;&nbsp;Vulpone (German)<br>&nbsp;&nbsp;Xunkar (French)<br><br><b>Special Thanks to</b><br>&nbsp;&nbsp;ChristosOwen & Ketran</html>