initial commit. Xunkar's release of 1.4.3 sources
10
.gitignore
vendored
Normal file
|
@ -0,0 +1,10 @@
|
|||
.DS_Store
|
||||
.idea/
|
||||
.settings/
|
||||
target/
|
||||
out/
|
||||
.project
|
||||
.classpath
|
||||
*.iml
|
||||
*.ipr
|
||||
*.iws
|
BIN
lib/JNativeHook-1.3.jar
Normal file
BIN
lib/Sidekick-1.2.jar
Normal file
BIN
lib/XStream-1.4.4.jar
Normal file
444
org/fenix/llanfair/Actions.java
Normal 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();
|
||||
}
|
||||
}
|
171
org/fenix/llanfair/Counters.java
Normal 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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
292
org/fenix/llanfair/Language.java
Normal 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();
|
||||
}
|
||||
}
|
467
org/fenix/llanfair/Llanfair.java
Normal 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();
|
||||
}
|
||||
|
||||
}
|
231
org/fenix/llanfair/MenuItem.java
Normal 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
328
org/fenix/llanfair/Segment.java
Normal 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 segment’s icons. When setting an icon for
|
||||
* a segment, it will be scaled down to that size if it’s bigger, but will
|
||||
* not be scaled up if it’s 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 it’s 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();
|
||||
}
|
||||
|
||||
}
|
298
org/fenix/llanfair/Time.java
Normal 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));
|
||||
}
|
||||
|
||||
}
|
20
org/fenix/llanfair/config/Accuracy.java
Normal 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();
|
||||
}
|
||||
|
||||
|
||||
}
|
22
org/fenix/llanfair/config/Compare.java
Normal 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();
|
||||
}
|
||||
}
|
21
org/fenix/llanfair/config/Merge.java
Normal 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();
|
||||
}
|
||||
}
|
428
org/fenix/llanfair/config/Settings.java
Normal 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();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
512
org/fenix/llanfair/dialog/EditRun.java
Normal 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 d’une course. {@code EditDialog}
|
||||
* permet de modifier le titre de la course, d’ajouter ou de retirer des
|
||||
* segments et d’éditer l’icô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 l’odre du modèle.
|
||||
*/
|
||||
private static final int[] TABLE_COLUMN_WIDTHS = new int[] {
|
||||
Segment.ICON_MAX_SIZE, 130, 100, 100, 100
|
||||
};
|
||||
|
||||
/**
|
||||
* Dimension d’un petit bouton dont l’étiquette n’est qu’un 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 d’insérer un nouveau segment.
|
||||
*/
|
||||
private JButton addSegment;
|
||||
|
||||
/**
|
||||
* Bouton permettant de supprimer un segment.
|
||||
*/
|
||||
private JButton remSegment;
|
||||
|
||||
private JCheckBox segmented;
|
||||
|
||||
/**
|
||||
* Bouton permettant d’enregistrer 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 d’une 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
|
||||
// l’utilisateur à 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 d’action des sous-composants, comme
|
||||
* l’appui sur un bouton.
|
||||
*
|
||||
* @param event - l’évènement d’action.
|
||||
* @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 d’un 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 d’afficher une valeur de type {@link Icon}
|
||||
* au sein d’une table.
|
||||
*
|
||||
* @author Xavier Sencert
|
||||
* @see TableCellRenderer
|
||||
*/
|
||||
private class IconRenderer extends DefaultTableCellRenderer {
|
||||
|
||||
/**
|
||||
* Construction d’un gestionnaire de rendu d’icô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 l’icô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 d’un délégué par défaut.
|
||||
*/
|
||||
public TimeEditor() {
|
||||
super(new JTextField());
|
||||
editor = (JTextField) getComponent();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retourne la valeur entrée par l’utilisateur.
|
||||
*
|
||||
* @return la valeur entrée par l’utilisateur.
|
||||
*/
|
||||
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 à l’utilisateur. Tant que l’édition est en erreur
|
||||
* l’édition persiste.
|
||||
*
|
||||
* @return {@code true} si l’édition s’est 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 s’agit d’un {@code JButton}
|
||||
* ce qui permet de capturer le clic de l’utilisateur et ainsi d’ouvrir
|
||||
* le gestionnaire de sélection de fichier.
|
||||
*/
|
||||
private JButton editor;
|
||||
|
||||
private Window owner;
|
||||
|
||||
/**
|
||||
* Création d’un é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
|
||||
* s’agit 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 l’utilisateur s’il y en a un.
|
||||
*/
|
||||
public Object getCellEditorValue() {
|
||||
if (chooser.getSelectedFile() == null) {
|
||||
return null;
|
||||
}
|
||||
return new ImageIcon(chooser.getSelectedFile().getPath());
|
||||
}
|
||||
|
||||
/**
|
||||
* Lors du clic de l’utilisateur sur le bouton, on affiche le
|
||||
* gestionnaire de sélection de fichier puis l’on force la fin de
|
||||
* l’édition lorsque celui-ci retourne.
|
||||
*/
|
||||
@Override public void actionPerformed(ActionEvent e) {
|
||||
chooser.showOpenDialog(owner);
|
||||
fireEditingStopped();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
153
org/fenix/llanfair/dialog/EditSettings.java
Normal 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 d’une 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 à l’aide d’un {@link GridBagLayout} dont l’accè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 l’application) 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 lorsqu’un sous-composant réalise une action. Cela
|
||||
* signifie pour nous que l’utilisateur à effectuer un réglage de paramètre.
|
||||
*
|
||||
* @param evt - l’évènement d’action.
|
||||
* @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();
|
||||
}
|
||||
|
||||
}
|
36
org/fenix/llanfair/dialog/LlanfairDialog.java
Normal 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);
|
||||
}
|
||||
|
||||
}
|
43
org/fenix/llanfair/dialog/SettingsTab.java
Normal 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());
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
242
org/fenix/llanfair/dialog/TabComponents.java
Normal 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));
|
||||
}
|
||||
|
||||
}
|
191
org/fenix/llanfair/dialog/TabGeneral.java
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
348
org/fenix/llanfair/dialog/TabHistory.java
Normal 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));
|
||||
}
|
||||
|
||||
}
|
211
org/fenix/llanfair/dialog/TabHotkeys.java
Normal 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) {}
|
||||
|
||||
}
|
||||
}
|
323
org/fenix/llanfair/dialog/TabLook.java
Normal 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 it’s 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 view’s 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
|
@ -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 );
|
||||
}
|
||||
}
|
||||
|
||||
}
|
595
org/fenix/llanfair/gui/Core.java
Normal 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 we’ve 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
492
org/fenix/llanfair/gui/Footer.java
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
362
org/fenix/llanfair/gui/Graph.java
Normal 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 run’s
|
||||
* 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 segment’s 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 pane’s 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
700
org/fenix/llanfair/gui/History.java
Normal 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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
407
org/fenix/llanfair/gui/RunPane.java
Normal 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
|
||||
* segment’s 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 it’s the time of
|
||||
* the run we’re 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
BIN
res/img/ARROW_DOWN
Normal file
After Width: | Height: | Size: 3.1 KiB |
BIN
res/img/ARROW_UP
Normal file
After Width: | Height: | Size: 3 KiB |
BIN
res/img/Llanfair
Normal file
After Width: | Height: | Size: 35 KiB |
BIN
res/img/MINUS
Normal file
After Width: | Height: | Size: 2.9 KiB |
BIN
res/img/PLUS
Normal file
After Width: | Height: | Size: 3 KiB |
BIN
res/img/REVERT
Normal file
After Width: | Height: | Size: 3.1 KiB |
BIN
res/img/de
Normal file
After Width: | Height: | Size: 3 KiB |
BIN
res/img/donate
Normal file
After Width: | Height: | Size: 4.3 KiB |
BIN
res/img/en
Normal file
After Width: | Height: | Size: 3.2 KiB |
BIN
res/img/fr
Normal file
After Width: | Height: | Size: 3 KiB |
BIN
res/img/jmi/ABOUT.png
Normal file
After Width: | Height: | Size: 3.1 KiB |
BIN
res/img/jmi/EDIT.png
Normal file
After Width: | Height: | Size: 3 KiB |
BIN
res/img/jmi/EXIT.png
Normal file
After Width: | Height: | Size: 3.3 KiB |
BIN
res/img/jmi/IMPORT.png
Normal file
After Width: | Height: | Size: 3.1 KiB |
BIN
res/img/jmi/LOCK.png
Normal file
After Width: | Height: | Size: 3.2 KiB |
BIN
res/img/jmi/NEW.png
Normal file
After Width: | Height: | Size: 2.9 KiB |
BIN
res/img/jmi/OPEN.png
Normal file
After Width: | Height: | Size: 3 KiB |
BIN
res/img/jmi/OPEN_RECENT.png
Normal file
After Width: | Height: | Size: 3 KiB |
BIN
res/img/jmi/RESET.png
Normal file
After Width: | Height: | Size: 3.3 KiB |
BIN
res/img/jmi/RESIZE_DEFAULT.png
Normal file
After Width: | Height: | Size: 3.2 KiB |
BIN
res/img/jmi/RESIZE_PREFERRED.png
Normal file
After Width: | Height: | Size: 3.4 KiB |
BIN
res/img/jmi/SAVE.png
Normal file
After Width: | Height: | Size: 3.1 KiB |
BIN
res/img/jmi/SAVE_AS.png
Normal file
After Width: | Height: | Size: 3.1 KiB |
BIN
res/img/jmi/SETTINGS.png
Normal file
After Width: | Height: | Size: 3.5 KiB |
BIN
res/img/jmi/UNLOCK.png
Normal file
After Width: | Height: | Size: 3.2 KiB |
BIN
res/img/nl
Normal file
After Width: | Height: | Size: 3 KiB |
BIN
res/img/sv
Normal file
After Width: | Height: | Size: 3 KiB |
182
res/language.properties
Normal 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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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> Xunkar<br><br><b>Localization provided by</b><br> Kokarn (Swedish)<br> MickeyG (Dutch)<br> Vulpone (German)<br> Xunkar (French)<br><br><b>Special Thanks to</b><br> ChristosOwen & Ketran</html>
|