--- /dev/null
+package tim.prune.function.estimate;
+
+import java.awt.BorderLayout;
+import java.awt.Component;
+import java.awt.FlowLayout;
+import java.awt.Font;
+import java.awt.GridLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.KeyAdapter;
+import java.awt.event.KeyEvent;
+
+import javax.swing.BorderFactory;
+import javax.swing.Box;
+import javax.swing.BoxLayout;
+import javax.swing.JButton;
+import javax.swing.JDialog;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.SwingUtilities;
+
+import tim.prune.App;
+import tim.prune.GenericFunction;
+import tim.prune.I18nManager;
+import tim.prune.config.Config;
+import tim.prune.data.RangeStats;
+import tim.prune.data.Selection;
+import tim.prune.data.Unit;
+import tim.prune.gui.DecimalNumberField;
+import tim.prune.gui.DisplayUtils;
+import tim.prune.gui.GuiGridLayout;
+
+/**
+ * Class to calculate and show the results of estimating (hike) time for the current range
+ */
+public class EstimateTime extends GenericFunction
+{
+ /** Dialog */
+ private JDialog _dialog = null;
+ /** Labels for distances */
+ private JLabel _distanceLabel = null;
+ /** Labels for durations */
+ private JLabel _estimatedDurationLabel = null, _actualDurationLabel = null;
+ /** Labels for climbs */
+ private JLabel _gentleClimbLabel = null, _steepClimbLabel = null;
+ /** Labels for descents */
+ private JLabel _gentleDescentLabel = null, _steepDescentLabel = null;
+ /** Labels and text fields for parameters */
+ private JLabel _flatSpeedLabel = null;
+ private DecimalNumberField _flatSpeedField = null;
+ private JLabel _climbParamLabel = null;
+ private DecimalNumberField _gentleClimbField = null, _steepClimbField = null;
+ private JLabel _descentParamLabel = null;
+ private DecimalNumberField _gentleDescentField = null, _steepDescentField = null;
+ /** Range stats */
+ private RangeStats _stats = null;
+
+
+ /**
+ * Constructor
+ * @param inApp App object
+ */
+ public EstimateTime(App inApp)
+ {
+ super(inApp);
+ }
+
+ /** Get the name key */
+ public String getNameKey() {
+ return "function.estimatetime";
+ }
+
+ /**
+ * Begin the function
+ */
+ public void begin()
+ {
+ // Get the stats on the selection before launching the dialog
+ Selection selection = _app.getTrackInfo().getSelection();
+ _stats = new RangeStats(_app.getTrackInfo().getTrack(), selection.getStart(), selection.getEnd());
+
+ if (_stats.getMovingDistance() < 0.01)
+ {
+ _app.showErrorMessage(getNameKey(), "dialog.estimatetime.error.nodistance");
+ return;
+ }
+ if (_dialog == null)
+ {
+ // TODO: Check whether params are at default, show tip message if unaltered?
+ _dialog = new JDialog(_parentFrame, I18nManager.getText(getNameKey()), true);
+ _dialog.setLocationRelativeTo(_parentFrame);
+ _dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
+ _dialog.getContentPane().add(makeDialogComponents());
+ _dialog.pack();
+ }
+ updateDetails();
+ _dialog.setVisible(true);
+ }
+
+ /**
+ * Create dialog components
+ * @return Panel containing all gui elements in dialog
+ */
+ private Component makeDialogComponents()
+ {
+ JPanel dialogPanel = new JPanel();
+ dialogPanel.setLayout(new BorderLayout(5, 5));
+
+ // main panel with a box layout
+ JPanel mainPanel = new JPanel();
+ mainPanel.setLayout(new BoxLayout(mainPanel, BoxLayout.Y_AXIS));
+ // Label at top
+ JLabel introLabel = new JLabel(I18nManager.getText("dialog.fullrangedetails.intro") + ":");
+ introLabel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
+ introLabel.setAlignmentX(Component.LEFT_ALIGNMENT);
+ mainPanel.add(introLabel);
+ mainPanel.add(Box.createVerticalStrut(4));
+
+ // Details panel in a grid
+ JPanel detailsPanel = new JPanel();
+ detailsPanel.setLayout(new GridLayout(0, 4, 6, 2));
+ detailsPanel.setBorder(BorderFactory.createTitledBorder(
+ I18nManager.getText("dialog.estimatetime.details")));
+
+ // Distance
+ JLabel distLabel = new JLabel(I18nManager.getText("fieldname.distance") + ": ");
+ distLabel.setHorizontalAlignment(JLabel.RIGHT);
+ detailsPanel.add(distLabel);
+ _distanceLabel = new JLabel("5 km");
+ detailsPanel.add(_distanceLabel);
+ detailsPanel.add(new JLabel("")); detailsPanel.add(new JLabel("")); // two blank cells
+
+ detailsPanel.add(new JLabel(""));
+ detailsPanel.add(new JLabel(I18nManager.getText("dialog.estimatetime.gentle")));
+ detailsPanel.add(new JLabel(I18nManager.getText("dialog.estimatetime.steep")));
+ detailsPanel.add(new JLabel("")); // blank cells
+
+ // Climb
+ JLabel climbLabel = new JLabel(I18nManager.getText("dialog.estimatetime.climb") + ": ");
+ climbLabel.setHorizontalAlignment(JLabel.RIGHT);
+ detailsPanel.add(climbLabel);
+ _gentleClimbLabel = new JLabel("1500 m");
+ detailsPanel.add(_gentleClimbLabel);
+ _steepClimbLabel = new JLabel("1500 m");
+ detailsPanel.add(_steepClimbLabel);
+ detailsPanel.add(new JLabel(""));
+
+ // Descent
+ JLabel descentLabel = new JLabel(I18nManager.getText("dialog.estimatetime.descent") + ": ");
+ descentLabel.setHorizontalAlignment(JLabel.RIGHT);
+ detailsPanel.add(descentLabel);
+ _gentleDescentLabel = new JLabel("1500 m");
+ detailsPanel.add(_gentleDescentLabel);
+ _steepDescentLabel = new JLabel("1500 m");
+ detailsPanel.add(_steepDescentLabel);
+ detailsPanel.add(new JLabel(""));
+
+ detailsPanel.setAlignmentX(Component.LEFT_ALIGNMENT);
+ mainPanel.add(detailsPanel);
+ mainPanel.add(Box.createVerticalStrut(4));
+
+ // Parameters panel in a flexible grid
+ JPanel paramsPanel = new JPanel();
+ GuiGridLayout paramsGrid = new GuiGridLayout(paramsPanel, new double[] {1.5, 0.2, 1.0, 0.2, 0.5},
+ new boolean[] {true, false, false, false, false});
+ paramsPanel.setBorder(BorderFactory.createTitledBorder(
+ I18nManager.getText("dialog.estimatetime.parameters")));
+ KeyAdapter paramChangeListener = new KeyAdapter() {
+ public void keyTyped(KeyEvent inE) {
+ SwingUtilities.invokeLater(new Runnable() {
+ public void run() {
+ calculateEstimatedTime();
+ }
+ });
+ }
+ public void keyPressed(KeyEvent inE) {
+ if (inE.getKeyCode() == KeyEvent.VK_ESCAPE) {_dialog.dispose();}
+ }
+ };
+
+ // Flat speed
+ _flatSpeedLabel = new JLabel(I18nManager.getText("dialog.estimatetime.parameters.timefor") + ": "); // (filled in later)
+ _flatSpeedLabel.setHorizontalAlignment(JLabel.RIGHT);
+ paramsGrid.add(_flatSpeedLabel);
+ _flatSpeedField = new DecimalNumberField();
+ _flatSpeedField.addKeyListener(paramChangeListener);
+ paramsGrid.add(_flatSpeedField);
+ JLabel minsLabel = new JLabel(I18nManager.getText("units.minutes"));
+ paramsGrid.add(minsLabel);
+ paramsGrid.nextRow();
+ // Headers for gentle and steep
+ paramsGrid.add(new JLabel(""));
+ paramsGrid.add(new JLabel(I18nManager.getText("dialog.estimatetime.gentle")));
+ paramsGrid.add(new JLabel("")); // blank cell
+ paramsGrid.add(new JLabel(I18nManager.getText("dialog.estimatetime.steep")));
+ paramsGrid.nextRow();
+ // Gentle climb
+ _climbParamLabel = new JLabel(I18nManager.getText("dialog.estimatetime.parameters.timefor") + ": "); // (filled in later)
+ _climbParamLabel.setHorizontalAlignment(JLabel.RIGHT);
+ paramsGrid.add(_climbParamLabel);
+ _gentleClimbField = new DecimalNumberField();
+ _gentleClimbField.addKeyListener(paramChangeListener);
+ paramsGrid.add(_gentleClimbField);
+ paramsGrid.add(new JLabel(minsLabel.getText()));
+ // Steep climb
+ _steepClimbField = new DecimalNumberField();
+ _steepClimbField.addKeyListener(paramChangeListener);
+ paramsGrid.add(_steepClimbField);
+ paramsGrid.add(new JLabel(minsLabel.getText()));
+
+ // Gentle descent
+ _descentParamLabel = new JLabel(I18nManager.getText("dialog.estimatetime.parameters.timefor") + ": "); // (filled in later)
+ _descentParamLabel.setHorizontalAlignment(JLabel.RIGHT);
+ paramsGrid.add(_descentParamLabel);
+ _gentleDescentField = new DecimalNumberField(true); // negative numbers allowed
+ _gentleDescentField.addKeyListener(paramChangeListener);
+ paramsGrid.add(_gentleDescentField);
+ paramsGrid.add(new JLabel(minsLabel.getText()));
+ // Steep climb
+ _steepDescentField = new DecimalNumberField(true); // negative numbers allowed
+ _steepDescentField.addKeyListener(paramChangeListener);
+ paramsGrid.add(_steepDescentField);
+ paramsGrid.add(new JLabel(minsLabel.getText()));
+
+ paramsPanel.setAlignmentX(Component.LEFT_ALIGNMENT);
+ mainPanel.add(paramsPanel);
+ mainPanel.add(Box.createVerticalStrut(12));
+
+ // Results panel
+ JPanel resultsPanel = new JPanel();
+ resultsPanel.setBorder(BorderFactory.createTitledBorder(
+ I18nManager.getText("dialog.estimatetime.results")));
+ resultsPanel.setLayout(new GridLayout(0, 2, 3, 3));
+ // estimated time
+ _estimatedDurationLabel = new JLabel(I18nManager.getText("dialog.estimatetime.results.estimatedtime") + ": "); // filled in later
+ Font origFont = _estimatedDurationLabel.getFont();
+ _estimatedDurationLabel.setFont(origFont.deriveFont(Font.BOLD, origFont.getSize2D() + 2.0f));
+
+ resultsPanel.add(_estimatedDurationLabel);
+ // actual time (if available)
+ _actualDurationLabel = new JLabel(I18nManager.getText("dialog.estimatetime.results.actualtime") + ": "); // filled in later
+ resultsPanel.add(_actualDurationLabel);
+
+ resultsPanel.setAlignmentX(Component.LEFT_ALIGNMENT);
+ mainPanel.add(resultsPanel);
+ mainPanel.add(Box.createVerticalStrut(4));
+
+ dialogPanel.add(mainPanel, BorderLayout.NORTH);
+ // button panel at bottom
+ JPanel buttonPanel = new JPanel();
+ buttonPanel.setLayout(new FlowLayout(FlowLayout.RIGHT));
+ JButton closeButton = new JButton(I18nManager.getText("button.close"));
+ closeButton.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e)
+ {
+ finishDialog();
+ }
+ });
+ closeButton.addKeyListener(new KeyAdapter() {
+ public void keyPressed(KeyEvent inE) {
+ if (inE.getKeyCode() == KeyEvent.VK_ESCAPE) {_dialog.dispose();}
+ }
+ });
+ buttonPanel.add(closeButton);
+ dialogPanel.add(buttonPanel, BorderLayout.SOUTH);
+ return dialogPanel;
+ }
+
+
+ /**
+ * Recalculate the values and update the labels
+ */
+ private void updateDetails()
+ {
+ // Warn if the current track hasn't got any height information
+ if (!_stats.getMovingAltitudeRange().hasRange()) {
+ _app.showErrorMessage(getNameKey(), "dialog.estimatetime.error.noaltitudes");
+ }
+
+ // Distance in current units
+ final Unit distUnit = Config.getUnitSet().getDistanceUnit();
+ final String distUnitsStr = I18nManager.getText(distUnit.getShortnameKey());
+ final double movingDist = _stats.getMovingDistance();
+ _distanceLabel.setText(DisplayUtils.roundedNumber(movingDist) + " " + distUnitsStr);
+
+ // Climb and descent values
+ final Unit altUnit = Config.getUnitSet().getAltitudeUnit();
+ final String altUnitsStr = " " + I18nManager.getText(altUnit.getShortnameKey());
+ _gentleClimbLabel.setText(_stats.getGentleAltitudeRange().getClimb(altUnit) + altUnitsStr);
+ _steepClimbLabel.setText(_stats.getSteepAltitudeRange().getClimb(altUnit) + altUnitsStr);
+ _gentleDescentLabel.setText(_stats.getGentleAltitudeRange().getDescent(altUnit) + altUnitsStr);
+ _steepDescentLabel.setText(_stats.getSteepAltitudeRange().getDescent(altUnit) + altUnitsStr);
+
+ // Try to get parameters from config
+ EstimationParameters estParams = new EstimationParameters(Config.getConfigString(Config.KEY_ESTIMATION_PARAMS));
+
+ String[] paramValues = estParams.getStrings();
+ if (paramValues == null || paramValues.length != 5) {return;} // TODO: What to do in case of error?
+ // Flat time is either for 5 km, 3 miles or 3 nm
+ _flatSpeedLabel.setText(I18nManager.getText("dialog.estimatetime.parameters.timefor") +
+ " " + EstimationParameters.getStandardDistance() + ": ");
+ _flatSpeedField.setText(paramValues[0]);
+
+ final String heightString = " " + EstimationParameters.getStandardClimb() + ": ";
+ _climbParamLabel.setText(I18nManager.getText("dialog.estimatetime.climb") + heightString);
+ _gentleClimbField.setText(paramValues[1]);
+ _steepClimbField.setText(paramValues[2]);
+ _descentParamLabel.setText(I18nManager.getText("dialog.estimatetime.descent") + heightString);
+ _gentleDescentField.setText(paramValues[3]);
+ _steepDescentField.setText(paramValues[4]);
+
+ // Use the entered parameters to estimate the time
+ calculateEstimatedTime();
+
+ // Get the actual time if available, for comparison
+ if (_stats.getMovingDurationInSeconds() > 0)
+ {
+ _actualDurationLabel.setText(I18nManager.getText("dialog.estimatetime.results.actualtime") + ": "
+ + DisplayUtils.buildDurationString(_stats.getMovingDurationInSeconds()));
+ }
+ else {
+ _actualDurationLabel.setText("");
+ }
+ }
+
+
+ /**
+ * Use the current parameter and the range stats to calculate the estimated time
+ * and populate the answer in the dialog
+ */
+ private void calculateEstimatedTime()
+ {
+ // Populate an EstimationParameters object from the four strings
+ EstimationParameters params = new EstimationParameters();
+ params.populateWithStrings(_flatSpeedField.getText(), _gentleClimbField.getText(),
+ _steepClimbField.getText(), _gentleDescentField.getText(), _steepDescentField.getText());
+ if (!params.isValid()) {
+ _estimatedDurationLabel.setText("- - -");
+ }
+ else
+ {
+ final long numSeconds = (long) (params.applyToStats(_stats) * 60.0);
+ _estimatedDurationLabel.setText(I18nManager.getText("dialog.estimatetime.results.estimatedtime") + ": "
+ + DisplayUtils.buildDurationString(numSeconds));
+ }
+ }
+
+
+ /**
+ * Finish with the dialog, by pressing the "Close" button
+ */
+ private void finishDialog()
+ {
+ // Make estimation parameters from entered strings, if valid save to config
+ EstimationParameters params = new EstimationParameters();
+ params.populateWithStrings(_flatSpeedField.getText(), _gentleClimbField.getText(),
+ _steepClimbField.getText(), _gentleDescentField.getText(), _steepDescentField.getText());
+ if (params.isValid()) {
+ Config.setConfigString(Config.KEY_ESTIMATION_PARAMS, params.toConfigString());
+ }
+ _dialog.dispose();
+ }
+}