From f37b331f39c67b2b94296fa7ac98eff29a48107e Mon Sep 17 00:00:00 2001 From: gered Date: Wed, 2 Dec 2015 14:27:26 -0500 Subject: [PATCH] add support for delayed starts for runs --- src/main/java/org/fenix/llanfair/Run.java | 21 ++++- .../org/fenix/llanfair/dialog/EditRun.java | 89 +++++++++++-------- .../java/org/fenix/llanfair/gui/Core.java | 20 ++++- src/main/resources/language.properties | 10 +-- 4 files changed, 93 insertions(+), 47 deletions(-) diff --git a/src/main/java/org/fenix/llanfair/Run.java b/src/main/java/org/fenix/llanfair/Run.java index abe45c7..8566c5c 100644 --- a/src/main/java/org/fenix/llanfair/Run.java +++ b/src/main/java/org/fenix/llanfair/Run.java @@ -12,6 +12,7 @@ import java.beans.PropertyChangeSupport; import java.io.IOException; import java.io.ObjectInputStream; import java.io.Serializable; +import java.security.InvalidParameterException; import java.util.ArrayList; import java.util.List; @@ -150,6 +151,13 @@ public class Run implements TableModel, Serializable { */ 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 boolean segmented; @@ -171,6 +179,7 @@ public class Run implements TableModel, Serializable { throw new NullPointerException("null run name"); } this.name = name; + this.delayedStart = 1000; segments = new ArrayList(); goal = ""; segmented = false; @@ -201,6 +210,10 @@ public class Run implements TableModel, Serializable { return goal; } + public long getDelayedStart() { + return delayedStart; + } + public boolean isSegmented() { return segmented; } @@ -585,6 +598,12 @@ public class Run implements TableModel, Serializable { 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 * 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) { throw new IllegalStateException("illegal state to start"); } - startTime = System.nanoTime() / 1000000L; + startTime = (System.nanoTime() / 1000000L) + delayedStart; current = 0; state = State.ONGOING; segments.get(current).setStartTime(startTime); diff --git a/src/main/java/org/fenix/llanfair/dialog/EditRun.java b/src/main/java/org/fenix/llanfair/dialog/EditRun.java index 8dbc70b..4e86fa8 100644 --- a/src/main/java/org/fenix/llanfair/dialog/EditRun.java +++ b/src/main/java/org/fenix/llanfair/dialog/EditRun.java @@ -1,42 +1,16 @@ 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 org.fenix.llanfair.*; +import org.fenix.llanfair.config.Accuracy; +import org.fenix.utils.gui.GBC; -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.*; 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; +import javax.swing.table.*; +import java.awt.*; +import java.awt.event.*; /** * Boîte de dialogue permettant l’édition d’une course. {@code EditDialog} @@ -131,6 +105,8 @@ implements ActionListener, ListSelectionListener { private JTextField runGoal; + private JTextField runDelayedStart; + // ----------------------------------------------------------- CONSTRUCTEURS /** @@ -148,9 +124,12 @@ implements ActionListener, ListSelectionListener { setTitle(Language.EDITING.get()); + String delayedStartString = new Time(run.getDelayedStart()).toString(Accuracy.HUNDREDTH); + runTitle = new JTextField(run.getName(), 61); runTitleLabel = new JLabel(Language.RUN_TITLE.get()); runGoal = new JTextField(run.getGoal(), 48); + runDelayedStart = new JTextField(delayedStartString, 5); segments = new JTable(run) { @Override protected JTableHeader createDefaultTableHeader() { return new JTableHeader(columnModel) { @@ -192,14 +171,16 @@ implements ActionListener, ListSelectionListener { */ private void placeComponents() { 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(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(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(new JLabel("Delayed Start"), GBC.grid(0, 2).insets(4, 4, 0, 4).anchor(GBC.LE)); + add(runDelayedStart, GBC.grid(1, 2).insets(4, 0, 0, 4).anchor(GBC.LS)); + add(segmented, GBC.grid(2, 2, 2, 1).insets(4, 0, 0, 4).anchor(GBC.LS)); + 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(remSegment, GBC.grid(3, 4).insets(4, 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); 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(); } + 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 * 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.setGoal(runGoal.getText()); run.setSegmented(segmented.isSelected()); + + long delayedStart = parseDelayedStartTime(runDelayedStart.getText()); + run.setDelayedStart(delayedStart == -1 ? 0 : delayedStart); + dispose(); } else if (source.equals(cancel)) { diff --git a/src/main/java/org/fenix/llanfair/gui/Core.java b/src/main/java/org/fenix/llanfair/gui/Core.java index 639be79..d324c89 100644 --- a/src/main/java/org/fenix/llanfair/gui/Core.java +++ b/src/main/java/org/fenix/llanfair/gui/Core.java @@ -59,6 +59,12 @@ class Core extends JPanel implements ActionListener { */ 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 /** @@ -299,6 +305,11 @@ class Core extends JPanel implements ActionListener { Time splitElapsed = new Time(now - run.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)) { splitTimer.setText("" + pauseTime); if (blinkTime == 0L || now - blinkTime >= 400L) { @@ -315,8 +326,13 @@ class Core extends JPanel implements ActionListener { blinkTime = now; } } else { - splitTimer.setText("" + splitElapsed); - segmentTimer.setText("" + segmentElapsed); + if (splitElapsed.getMilliseconds() < 0) { + splitTimer.setText("-" + splitElapsed); + segmentTimer.setText("" + zeroTime); + } else { + splitTimer.setText("" + splitElapsed); + segmentTimer.setText("" + segmentElapsed); + } if (!splitLoss && splitElapsed.compareTo(splitTime) > 0) { splitLoss = true; diff --git a/src/main/resources/language.properties b/src/main/resources/language.properties index eba3d4d..7fd1840 100644 --- a/src/main/resources/language.properties +++ b/src/main/resources/language.properties @@ -114,14 +114,14 @@ 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 ! +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. +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?