1 package tim.prune.function.estimate;
3 import java.awt.BorderLayout;
4 import java.awt.Component;
5 import java.awt.FlowLayout;
7 import java.awt.GridLayout;
8 import java.awt.event.ActionEvent;
9 import java.awt.event.ActionListener;
10 import java.awt.event.KeyAdapter;
11 import java.awt.event.KeyEvent;
13 import javax.swing.BorderFactory;
14 import javax.swing.Box;
15 import javax.swing.BoxLayout;
16 import javax.swing.JButton;
17 import javax.swing.JDialog;
18 import javax.swing.JLabel;
19 import javax.swing.JPanel;
20 import javax.swing.SwingUtilities;
23 import tim.prune.GenericFunction;
24 import tim.prune.I18nManager;
25 import tim.prune.config.Config;
26 import tim.prune.data.RangeStatsWithGradients;
27 import tim.prune.data.Selection;
28 import tim.prune.data.Unit;
29 import tim.prune.gui.DecimalNumberField;
30 import tim.prune.gui.DisplayUtils;
31 import tim.prune.gui.GuiGridLayout;
32 import tim.prune.tips.TipManager;
35 * Class to calculate and show the results of estimating (hike) time for the current range
37 public class EstimateTime extends GenericFunction
40 private JDialog _dialog = null;
41 /** Labels for distances */
42 private JLabel _distanceLabel = null;
43 /** Labels for durations */
44 private JLabel _estimatedDurationLabel = null, _actualDurationLabel = null;
45 /** Labels for climbs */
46 private JLabel _gentleClimbLabel = null, _steepClimbLabel = null;
47 /** Labels for descents */
48 private JLabel _gentleDescentLabel = null, _steepDescentLabel = null;
49 /** Labels and text fields for parameters */
50 private JLabel _flatSpeedLabel = null;
51 private DecimalNumberField _flatSpeedField = null;
52 private JLabel _climbParamLabel = null;
53 private DecimalNumberField _gentleClimbField = null, _steepClimbField = null;
54 private JLabel _descentParamLabel = null;
55 private DecimalNumberField _gentleDescentField = null, _steepDescentField = null;
57 private RangeStatsWithGradients _stats = null;
62 * @param inApp App object
64 public EstimateTime(App inApp)
69 /** Get the name key */
70 public String getNameKey() {
71 return "function.estimatetime";
79 // Get the stats on the selection before launching the dialog
80 Selection selection = _app.getTrackInfo().getSelection();
81 _stats = new RangeStatsWithGradients(_app.getTrackInfo().getTrack(),
82 selection.getStart(), selection.getEnd());
84 if (_stats.getMovingDistance() < 0.01)
86 _app.showErrorMessage(getNameKey(), "dialog.estimatetime.error.nodistance");
91 // First time in, check whether params are at default, show tip message if unaltered
93 _dialog = new JDialog(_parentFrame, I18nManager.getText(getNameKey()), true);
94 _dialog.setLocationRelativeTo(_parentFrame);
95 _dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
96 _dialog.getContentPane().add(makeDialogComponents());
100 _dialog.setVisible(true);
104 * Create dialog components
105 * @return Panel containing all gui elements in dialog
107 private Component makeDialogComponents()
109 JPanel dialogPanel = new JPanel();
110 dialogPanel.setLayout(new BorderLayout(5, 5));
112 // main panel with a box layout
113 JPanel mainPanel = new JPanel();
114 mainPanel.setLayout(new BoxLayout(mainPanel, BoxLayout.Y_AXIS));
116 JLabel introLabel = new JLabel(I18nManager.getText("dialog.fullrangedetails.intro") + ":");
117 introLabel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
118 introLabel.setAlignmentX(Component.LEFT_ALIGNMENT);
119 mainPanel.add(introLabel);
120 mainPanel.add(Box.createVerticalStrut(4));
122 // Details panel in a grid
123 JPanel detailsPanel = new JPanel();
124 detailsPanel.setLayout(new GridLayout(0, 4, 6, 2));
125 detailsPanel.setBorder(BorderFactory.createTitledBorder(
126 I18nManager.getText("dialog.estimatetime.details")));
129 JLabel distLabel = new JLabel(I18nManager.getText("fieldname.distance") + ": ");
130 distLabel.setHorizontalAlignment(JLabel.RIGHT);
131 detailsPanel.add(distLabel);
132 _distanceLabel = new JLabel("5 km");
133 detailsPanel.add(_distanceLabel);
134 detailsPanel.add(new JLabel("")); detailsPanel.add(new JLabel("")); // two blank cells
136 detailsPanel.add(new JLabel(""));
137 detailsPanel.add(new JLabel(I18nManager.getText("dialog.estimatetime.gentle")));
138 detailsPanel.add(new JLabel(I18nManager.getText("dialog.estimatetime.steep")));
139 detailsPanel.add(new JLabel("")); // blank cells
142 JLabel climbLabel = new JLabel(I18nManager.getText("dialog.estimatetime.climb") + ": ");
143 climbLabel.setHorizontalAlignment(JLabel.RIGHT);
144 detailsPanel.add(climbLabel);
145 _gentleClimbLabel = new JLabel("1500 m");
146 detailsPanel.add(_gentleClimbLabel);
147 _steepClimbLabel = new JLabel("1500 m");
148 detailsPanel.add(_steepClimbLabel);
149 detailsPanel.add(new JLabel(""));
152 JLabel descentLabel = new JLabel(I18nManager.getText("dialog.estimatetime.descent") + ": ");
153 descentLabel.setHorizontalAlignment(JLabel.RIGHT);
154 detailsPanel.add(descentLabel);
155 _gentleDescentLabel = new JLabel("1500 m");
156 detailsPanel.add(_gentleDescentLabel);
157 _steepDescentLabel = new JLabel("1500 m");
158 detailsPanel.add(_steepDescentLabel);
159 detailsPanel.add(new JLabel(""));
161 detailsPanel.setAlignmentX(Component.LEFT_ALIGNMENT);
162 mainPanel.add(detailsPanel);
163 mainPanel.add(Box.createVerticalStrut(4));
165 // Parameters panel in a flexible grid
166 JPanel paramsPanel = new JPanel();
167 GuiGridLayout paramsGrid = new GuiGridLayout(paramsPanel, new double[] {1.5, 0.2, 1.0, 0.2, 0.5},
168 new boolean[] {true, false, false, false, false});
169 paramsPanel.setBorder(BorderFactory.createTitledBorder(
170 I18nManager.getText("dialog.estimatetime.parameters")));
171 KeyAdapter paramChangeListener = new KeyAdapter() {
172 public void keyTyped(KeyEvent inE) {
173 SwingUtilities.invokeLater(new Runnable() {
175 calculateEstimatedTime();
179 public void keyPressed(KeyEvent inE) {
180 if (inE.getKeyCode() == KeyEvent.VK_ESCAPE) {_dialog.dispose();}
185 _flatSpeedLabel = new JLabel(I18nManager.getText("dialog.estimatetime.parameters.timefor") + ": "); // (filled in later)
186 _flatSpeedLabel.setHorizontalAlignment(JLabel.RIGHT);
187 paramsGrid.add(_flatSpeedLabel);
188 _flatSpeedField = new DecimalNumberField();
189 _flatSpeedField.addKeyListener(paramChangeListener);
190 paramsGrid.add(_flatSpeedField);
191 JLabel minsLabel = new JLabel(I18nManager.getText("units.minutes"));
192 paramsGrid.add(minsLabel);
193 paramsGrid.nextRow();
194 // Headers for gentle and steep
195 paramsGrid.add(new JLabel(""));
196 paramsGrid.add(new JLabel(I18nManager.getText("dialog.estimatetime.gentle")));
197 paramsGrid.add(new JLabel("")); // blank cell
198 paramsGrid.add(new JLabel(I18nManager.getText("dialog.estimatetime.steep")));
199 paramsGrid.nextRow();
201 _climbParamLabel = new JLabel(I18nManager.getText("dialog.estimatetime.parameters.timefor") + ": "); // (filled in later)
202 _climbParamLabel.setHorizontalAlignment(JLabel.RIGHT);
203 paramsGrid.add(_climbParamLabel);
204 _gentleClimbField = new DecimalNumberField();
205 _gentleClimbField.addKeyListener(paramChangeListener);
206 paramsGrid.add(_gentleClimbField);
207 paramsGrid.add(new JLabel(minsLabel.getText()));
209 _steepClimbField = new DecimalNumberField();
210 _steepClimbField.addKeyListener(paramChangeListener);
211 paramsGrid.add(_steepClimbField);
212 paramsGrid.add(new JLabel(minsLabel.getText()));
215 _descentParamLabel = new JLabel(I18nManager.getText("dialog.estimatetime.parameters.timefor") + ": "); // (filled in later)
216 _descentParamLabel.setHorizontalAlignment(JLabel.RIGHT);
217 paramsGrid.add(_descentParamLabel);
218 _gentleDescentField = new DecimalNumberField(true); // negative numbers allowed
219 _gentleDescentField.addKeyListener(paramChangeListener);
220 paramsGrid.add(_gentleDescentField);
221 paramsGrid.add(new JLabel(minsLabel.getText()));
223 _steepDescentField = new DecimalNumberField(true); // negative numbers allowed
224 _steepDescentField.addKeyListener(paramChangeListener);
225 paramsGrid.add(_steepDescentField);
226 paramsGrid.add(new JLabel(minsLabel.getText()));
228 paramsPanel.setAlignmentX(Component.LEFT_ALIGNMENT);
229 mainPanel.add(paramsPanel);
230 mainPanel.add(Box.createVerticalStrut(12));
233 JPanel resultsPanel = new JPanel();
234 resultsPanel.setBorder(BorderFactory.createTitledBorder(
235 I18nManager.getText("dialog.estimatetime.results")));
236 resultsPanel.setLayout(new GridLayout(0, 2, 3, 3));
238 _estimatedDurationLabel = new JLabel(I18nManager.getText("dialog.estimatetime.results.estimatedtime") + ": "); // filled in later
239 Font origFont = _estimatedDurationLabel.getFont();
240 _estimatedDurationLabel.setFont(origFont.deriveFont(Font.BOLD, origFont.getSize2D() + 2.0f));
242 resultsPanel.add(_estimatedDurationLabel);
243 // actual time (if available)
244 _actualDurationLabel = new JLabel(I18nManager.getText("dialog.estimatetime.results.actualtime") + ": "); // filled in later
245 resultsPanel.add(_actualDurationLabel);
247 resultsPanel.setAlignmentX(Component.LEFT_ALIGNMENT);
248 mainPanel.add(resultsPanel);
249 mainPanel.add(Box.createVerticalStrut(4));
251 dialogPanel.add(mainPanel, BorderLayout.NORTH);
252 // button panel at bottom
253 JPanel buttonPanel = new JPanel();
254 buttonPanel.setLayout(new FlowLayout(FlowLayout.RIGHT));
255 JButton closeButton = new JButton(I18nManager.getText("button.close"));
256 closeButton.addActionListener(new ActionListener() {
257 public void actionPerformed(ActionEvent e)
262 closeButton.addKeyListener(new KeyAdapter() {
263 public void keyPressed(KeyEvent inE) {
264 if (inE.getKeyCode() == KeyEvent.VK_ESCAPE) {_dialog.dispose();}
267 buttonPanel.add(closeButton);
268 dialogPanel.add(buttonPanel, BorderLayout.SOUTH);
274 * Recalculate the values and update the labels
276 private void updateDetails()
278 // Warn if the current track hasn't got any height information
279 if (!_stats.getMovingAltitudeRange().hasRange()) {
280 _app.showErrorMessage(getNameKey(), "dialog.estimatetime.error.noaltitudes");
283 // Distance in current units
284 final Unit distUnit = Config.getUnitSet().getDistanceUnit();
285 final String distUnitsStr = I18nManager.getText(distUnit.getShortnameKey());
286 final double movingDist = _stats.getMovingDistance();
287 _distanceLabel.setText(DisplayUtils.roundedNumber(movingDist) + " " + distUnitsStr);
289 // Climb and descent values
290 final Unit altUnit = Config.getUnitSet().getAltitudeUnit();
291 final String altUnitsStr = " " + I18nManager.getText(altUnit.getShortnameKey());
292 _gentleClimbLabel.setText(_stats.getGentleAltitudeRange().getClimb(altUnit) + altUnitsStr);
293 _steepClimbLabel.setText(_stats.getSteepAltitudeRange().getClimb(altUnit) + altUnitsStr);
294 _gentleDescentLabel.setText(_stats.getGentleAltitudeRange().getDescent(altUnit) + altUnitsStr);
295 _steepDescentLabel.setText(_stats.getSteepAltitudeRange().getDescent(altUnit) + altUnitsStr);
297 // Try to get parameters from config
298 EstimationParameters estParams = new EstimationParameters(Config.getConfigString(Config.KEY_ESTIMATION_PARAMS));
300 String[] paramValues = estParams.getStrings();
301 if (paramValues == null || paramValues.length != 5)
303 // TODO: What to do in case of error?
306 // Flat time is either for 5 km, 3 miles or 3 nm
307 _flatSpeedLabel.setText(I18nManager.getText("dialog.estimatetime.parameters.timefor") +
308 " " + EstimationParameters.getStandardDistance() + ": ");
309 _flatSpeedField.setText(paramValues[0]);
311 final String heightString = " " + EstimationParameters.getStandardClimb() + ": ";
312 _climbParamLabel.setText(I18nManager.getText("dialog.estimatetime.climb") + heightString);
313 _gentleClimbField.setText(paramValues[1]);
314 _steepClimbField.setText(paramValues[2]);
315 _descentParamLabel.setText(I18nManager.getText("dialog.estimatetime.descent") + heightString);
316 _gentleDescentField.setText(paramValues[3]);
317 _steepDescentField.setText(paramValues[4]);
319 // Use the entered parameters to estimate the time
320 calculateEstimatedTime();
322 // Get the actual time if available, for comparison
323 if (_stats.getMovingDurationInSeconds() > 0)
325 _actualDurationLabel.setText(I18nManager.getText("dialog.estimatetime.results.actualtime") + ": "
326 + DisplayUtils.buildDurationString(_stats.getMovingDurationInSeconds()));
329 _actualDurationLabel.setText("");
335 * Use the current parameter and the range stats to calculate the estimated time
336 * and populate the answer in the dialog
338 private void calculateEstimatedTime()
340 // Populate an EstimationParameters object from the four strings
341 EstimationParameters params = new EstimationParameters();
342 params.populateWithStrings(_flatSpeedField.getText(), _gentleClimbField.getText(),
343 _steepClimbField.getText(), _gentleDescentField.getText(), _steepDescentField.getText());
344 if (!params.isValid()) {
345 _estimatedDurationLabel.setText("- - -");
349 final long numSeconds = (long) (params.applyToStats(_stats) * 60.0);
350 _estimatedDurationLabel.setText(I18nManager.getText("dialog.estimatetime.results.estimatedtime") + ": "
351 + DisplayUtils.buildDurationString(numSeconds));
357 * Finish with the dialog, by pressing the "Close" button
359 private void finishDialog()
361 // Make estimation parameters from entered strings, if valid save to config
362 EstimationParameters params = new EstimationParameters();
363 params.populateWithStrings(_flatSpeedField.getText(), _gentleClimbField.getText(),
364 _steepClimbField.getText(), _gentleDescentField.getText(), _steepDescentField.getText());
365 if (params.isValid()) {
366 Config.setConfigString(Config.KEY_ESTIMATION_PARAMS, params.toConfigString());
372 * Show a tip to use the learn function, if appropriate
374 private void showTip()
376 EstimationParameters currParams = new EstimationParameters(
377 Config.getConfigString(Config.KEY_ESTIMATION_PARAMS));
378 if (currParams.sameAsDefaults()) {
379 _app.showTip(TipManager.Tip_LearnTimeParams);