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;
import tim.prune.tips.TipManager;
/**
* 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)
{
// First time in, check whether params are at default, show tip message if unaltered
showTip();
_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();
}
/**
* Show a tip to use the learn function, if appropriate
*/
private void showTip()
{
EstimationParameters currParams = new EstimationParameters(
Config.getConfigString(Config.KEY_ESTIMATION_PARAMS));
if (currParams.sameAsDefaults()) {
_app.showTip(TipManager.Tip_LearnTimeParams);
}
}
}