add support for delayed starts for runs
This commit is contained in:
parent
20bd866eba
commit
f37b331f39
|
@ -12,6 +12,7 @@ import java.beans.PropertyChangeSupport;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.ObjectInputStream;
|
import java.io.ObjectInputStream;
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
|
import java.security.InvalidParameterException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
@ -150,6 +151,13 @@ public class Run implements TableModel, Serializable {
|
||||||
*/
|
*/
|
||||||
private transient TableModelSupport tmSupport;
|
private transient TableModelSupport tmSupport;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Number of milliseconds to delay the clock for when the run is started. A
|
||||||
|
* non-zero value here means the clock starts at a negative time and counts
|
||||||
|
* up to zero at which point the run starts being timed normally.
|
||||||
|
*/
|
||||||
|
private long delayedStart;
|
||||||
|
|
||||||
private String goal;
|
private String goal;
|
||||||
|
|
||||||
private boolean segmented;
|
private boolean segmented;
|
||||||
|
@ -171,6 +179,7 @@ public class Run implements TableModel, Serializable {
|
||||||
throw new NullPointerException("null run name");
|
throw new NullPointerException("null run name");
|
||||||
}
|
}
|
||||||
this.name = name;
|
this.name = name;
|
||||||
|
this.delayedStart = 1000;
|
||||||
segments = new ArrayList<Segment>();
|
segments = new ArrayList<Segment>();
|
||||||
goal = "";
|
goal = "";
|
||||||
segmented = false;
|
segmented = false;
|
||||||
|
@ -201,6 +210,10 @@ public class Run implements TableModel, Serializable {
|
||||||
return goal;
|
return goal;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public long getDelayedStart() {
|
||||||
|
return delayedStart;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isSegmented() {
|
public boolean isSegmented() {
|
||||||
return segmented;
|
return segmented;
|
||||||
}
|
}
|
||||||
|
@ -585,6 +598,12 @@ public class Run implements TableModel, Serializable {
|
||||||
pcSupport.firePropertyChange(GOAL_PROPERTY, old, goal);
|
pcSupport.firePropertyChange(GOAL_PROPERTY, old, goal);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setDelayedStart(long delayedStart) {
|
||||||
|
if (delayedStart < 0)
|
||||||
|
throw new InvalidParameterException("negative delayed start");
|
||||||
|
this.delayedStart = delayedStart;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Inserts the given segment at the end. If it's the first segment being
|
* Inserts the given segment at the end. If it's the first segment being
|
||||||
* added, the run becomes {@link State#READY}, meaning it can be started.
|
* added, the run becomes {@link State#READY}, meaning it can be started.
|
||||||
|
@ -663,7 +682,7 @@ public class Run implements TableModel, Serializable {
|
||||||
if (state == null || state == State.ONGOING) {
|
if (state == null || state == State.ONGOING) {
|
||||||
throw new IllegalStateException("illegal state to start");
|
throw new IllegalStateException("illegal state to start");
|
||||||
}
|
}
|
||||||
startTime = System.nanoTime() / 1000000L;
|
startTime = (System.nanoTime() / 1000000L) + delayedStart;
|
||||||
current = 0;
|
current = 0;
|
||||||
state = State.ONGOING;
|
state = State.ONGOING;
|
||||||
segments.get(current).setStartTime(startTime);
|
segments.get(current).setStartTime(startTime);
|
||||||
|
|
|
@ -1,42 +1,16 @@
|
||||||
package org.fenix.llanfair.dialog;
|
package org.fenix.llanfair.dialog;
|
||||||
|
|
||||||
import java.awt.Component;
|
import org.fenix.llanfair.*;
|
||||||
import java.awt.Dimension;
|
import org.fenix.llanfair.config.Accuracy;
|
||||||
import java.awt.GridBagLayout;
|
import org.fenix.utils.gui.GBC;
|
||||||
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.*;
|
||||||
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.ListSelectionEvent;
|
||||||
import javax.swing.event.ListSelectionListener;
|
import javax.swing.event.ListSelectionListener;
|
||||||
import javax.swing.filechooser.FileNameExtensionFilter;
|
import javax.swing.filechooser.FileNameExtensionFilter;
|
||||||
import javax.swing.table.DefaultTableCellRenderer;
|
import javax.swing.table.*;
|
||||||
import javax.swing.table.JTableHeader;
|
import java.awt.*;
|
||||||
import javax.swing.table.TableCellEditor;
|
import java.awt.event.*;
|
||||||
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}
|
* Boîte de dialogue permettant l’édition d’une course. {@code EditDialog}
|
||||||
|
@ -131,6 +105,8 @@ implements ActionListener, ListSelectionListener {
|
||||||
|
|
||||||
private JTextField runGoal;
|
private JTextField runGoal;
|
||||||
|
|
||||||
|
private JTextField runDelayedStart;
|
||||||
|
|
||||||
// ----------------------------------------------------------- CONSTRUCTEURS
|
// ----------------------------------------------------------- CONSTRUCTEURS
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -148,9 +124,12 @@ implements ActionListener, ListSelectionListener {
|
||||||
|
|
||||||
setTitle(Language.EDITING.get());
|
setTitle(Language.EDITING.get());
|
||||||
|
|
||||||
|
String delayedStartString = new Time(run.getDelayedStart()).toString(Accuracy.HUNDREDTH);
|
||||||
|
|
||||||
runTitle = new JTextField(run.getName(), 61);
|
runTitle = new JTextField(run.getName(), 61);
|
||||||
runTitleLabel = new JLabel(Language.RUN_TITLE.get());
|
runTitleLabel = new JLabel(Language.RUN_TITLE.get());
|
||||||
runGoal = new JTextField(run.getGoal(), 48);
|
runGoal = new JTextField(run.getGoal(), 48);
|
||||||
|
runDelayedStart = new JTextField(delayedStartString, 5);
|
||||||
segments = new JTable(run) {
|
segments = new JTable(run) {
|
||||||
@Override protected JTableHeader createDefaultTableHeader() {
|
@Override protected JTableHeader createDefaultTableHeader() {
|
||||||
return new JTableHeader(columnModel) {
|
return new JTableHeader(columnModel) {
|
||||||
|
@ -192,14 +171,16 @@ implements ActionListener, ListSelectionListener {
|
||||||
*/
|
*/
|
||||||
private void placeComponents() {
|
private void placeComponents() {
|
||||||
setLayout(new GridBagLayout());
|
setLayout(new GridBagLayout());
|
||||||
add(runTitleLabel, GBC.grid(0, 0).insets(4, 4, 0, 4));
|
add(runTitleLabel, GBC.grid(0, 0).insets(4, 4, 0, 4).anchor(GBC.LE));
|
||||||
add(runTitle, GBC.grid(1, 0, 3, 1).insets(4, 0, 0, 4).anchor(GBC.LS));
|
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(new JLabel("" + Language.LB_GOAL), GBC.grid(0, 1).insets(4, 4, 0, 4).anchor(GBC.LE));
|
||||||
add(runGoal, GBC.grid(1, 1).insets(4, 0, 0, 4).anchor(GBC.LS));
|
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(new JLabel("Delayed Start"), GBC.grid(0, 2).insets(4, 4, 0, 4).anchor(GBC.LE));
|
||||||
add(segmentsLabel, GBC.grid(0, 2, 4, 1).insets(5, 4, 4, 0)
|
add(runDelayedStart, GBC.grid(1, 2).insets(4, 0, 0, 4).anchor(GBC.LS));
|
||||||
.anchor(GBC.BL));
|
add(segmented, GBC.grid(2, 2, 2, 1).insets(4, 0, 0, 4).anchor(GBC.LS));
|
||||||
add(scrollPane, GBC.grid(0, 3, 3, 4).insets(0, 4, 0, 0));
|
add(segmentsLabel, GBC.grid(0, 3).insets(5, 4, 4, 0)
|
||||||
|
.anchor(GBC.LE));
|
||||||
|
add(scrollPane, GBC.grid(1, 3, 3, 4).insets(4, 4, 0, 0).anchor(GBC.LS));
|
||||||
add(addSegment, GBC.grid(3, 3).insets(0, 4).anchor(GBC.FLS));
|
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(remSegment, GBC.grid(3, 4).insets(4, 4).anchor(GBC.FLS));
|
||||||
add(moveUp, GBC.grid(3, 5).insets(0, 4).anchor(GBC.FLS));
|
add(moveUp, GBC.grid(3, 5).insets(0, 4).anchor(GBC.FLS));
|
||||||
|
@ -263,9 +244,35 @@ implements ActionListener, ListSelectionListener {
|
||||||
moveUp.setToolTipText("" + Language.TT_MOVE_SEGMENT_UP);
|
moveUp.setToolTipText("" + Language.TT_MOVE_SEGMENT_UP);
|
||||||
segmented.setToolTipText("" + Language.TT_ED_SEGMENTED);
|
segmented.setToolTipText("" + Language.TT_ED_SEGMENTED);
|
||||||
|
|
||||||
|
// parse the entered delayed start time whenever focus leaves the field
|
||||||
|
// (this lets us prompt up errors if the user enters something invalid immediately)
|
||||||
|
runDelayedStart.addFocusListener(new FocusAdapter() {
|
||||||
|
@Override
|
||||||
|
public void focusLost(FocusEvent e) {
|
||||||
|
long result = parseDelayedStartTime(runDelayedStart.getText());
|
||||||
|
if (result == -1)
|
||||||
|
runDelayedStart.setBackground(Color.RED);
|
||||||
|
else
|
||||||
|
runDelayedStart.setBackground(Color.WHITE);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
updateButtons();
|
updateButtons();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private long parseDelayedStartTime(String text) {
|
||||||
|
long result;
|
||||||
|
try {
|
||||||
|
Time time = new Time(text);
|
||||||
|
result = time.getMilliseconds();
|
||||||
|
} catch (Exception e) {
|
||||||
|
JOptionPane.showMessageDialog(this, e.getMessage(), Language.ERROR.get(), JOptionPane.ERROR_MESSAGE);
|
||||||
|
result = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Procédure à invoquer lorsque ce composant réalise une action. Sont donc
|
* 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
|
* capturés ici, tous les évènements d’action des sous-composants, comme
|
||||||
|
@ -290,6 +297,10 @@ implements ActionListener, ListSelectionListener {
|
||||||
run.setName(runTitle.getText());
|
run.setName(runTitle.getText());
|
||||||
run.setGoal(runGoal.getText());
|
run.setGoal(runGoal.getText());
|
||||||
run.setSegmented(segmented.isSelected());
|
run.setSegmented(segmented.isSelected());
|
||||||
|
|
||||||
|
long delayedStart = parseDelayedStartTime(runDelayedStart.getText());
|
||||||
|
run.setDelayedStart(delayedStart == -1 ? 0 : delayedStart);
|
||||||
|
|
||||||
dispose();
|
dispose();
|
||||||
|
|
||||||
} else if (source.equals(cancel)) {
|
} else if (source.equals(cancel)) {
|
||||||
|
|
|
@ -59,6 +59,12 @@ class Core extends JPanel implements ActionListener {
|
||||||
*/
|
*/
|
||||||
private static final int MIN_WIDTH = 50;
|
private static final int MIN_WIDTH = 50;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dummy object used so we can get a string format for zero time instead of new'ing up
|
||||||
|
* a Time object each time.
|
||||||
|
*/
|
||||||
|
private static final Time zeroTime = new Time(0);
|
||||||
|
|
||||||
// ------------------------------------------------------------- ATTRIBUTES
|
// ------------------------------------------------------------- ATTRIBUTES
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -299,6 +305,11 @@ class Core extends JPanel implements ActionListener {
|
||||||
Time splitElapsed = new Time(now - run.getStartTime());
|
Time splitElapsed = new Time(now - run.getStartTime());
|
||||||
Time segmentElapsed = new Time(now - current.getStartTime());
|
Time segmentElapsed = new Time(now - current.getStartTime());
|
||||||
|
|
||||||
|
if (splitElapsed.getMilliseconds() < 0)
|
||||||
|
splitTimer.setForeground(Color.GRAY);
|
||||||
|
else
|
||||||
|
splitTimer.setForeground(Settings.colorTimer.get());
|
||||||
|
|
||||||
if (run.getState().equals(State.PAUSED)) {
|
if (run.getState().equals(State.PAUSED)) {
|
||||||
splitTimer.setText("" + pauseTime);
|
splitTimer.setText("" + pauseTime);
|
||||||
if (blinkTime == 0L || now - blinkTime >= 400L) {
|
if (blinkTime == 0L || now - blinkTime >= 400L) {
|
||||||
|
@ -314,9 +325,14 @@ class Core extends JPanel implements ActionListener {
|
||||||
}
|
}
|
||||||
blinkTime = now;
|
blinkTime = now;
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
if (splitElapsed.getMilliseconds() < 0) {
|
||||||
|
splitTimer.setText("-" + splitElapsed);
|
||||||
|
segmentTimer.setText("" + zeroTime);
|
||||||
} else {
|
} else {
|
||||||
splitTimer.setText("" + splitElapsed);
|
splitTimer.setText("" + splitElapsed);
|
||||||
segmentTimer.setText("" + segmentElapsed);
|
segmentTimer.setText("" + segmentElapsed);
|
||||||
|
}
|
||||||
|
|
||||||
if (!splitLoss && splitElapsed.compareTo(splitTime) > 0) {
|
if (!splitLoss && splitElapsed.compareTo(splitTime) > 0) {
|
||||||
splitLoss = true;
|
splitLoss = true;
|
||||||
|
|
|
@ -114,14 +114,14 @@ LB_FT_LIVE = Live:
|
||||||
LB_FT_SEGMENT = P.Se:
|
LB_FT_SEGMENT = P.Se:
|
||||||
LB_FT_SPLIT = P.Sp:
|
LB_FT_SPLIT = P.Sp:
|
||||||
ABOUT_MESSAGE =
|
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_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 !
|
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 =
|
ICON_TOO_BIG =
|
||||||
ILLEGAL_TIME = Your time cannot be lower than or equal to zero.
|
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.
|
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_NAN = Value of "{0}" must be a number.
|
||||||
INPUT_NEGATIVE = Value of Â?{0}Â? must be a positive number.
|
INPUT_NEGATIVE = Value of "{0}" must be a positive number.
|
||||||
INVALID_TIME_STAMP = The input string Â?{0}Â? is not a valid time stamp.
|
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_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_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?
|
WARN_RESET_SETTINGS = Are you sure you want to reset your settings?
|
||||||
|
|
Reference in a new issue