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.RangeStats;
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 RangeStats _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 RangeStats(_app.getTrackInfo().getTrack(), selection.getStart(), selection.getEnd());
83 if (_stats.getMovingDistance() < 0.01)
85 _app.showErrorMessage(getNameKey(), "dialog.estimatetime.error.nodistance");
90 // First time in, check whether params are at default, show tip message if unaltered
92 _dialog = new JDialog(_parentFrame, I18nManager.getText(getNameKey()), true);
93 _dialog.setLocationRelativeTo(_parentFrame);
94 _dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
95 _dialog.getContentPane().add(makeDialogComponents());
99 _dialog.setVisible(true);
103 * Create dialog components
104 * @return Panel containing all gui elements in dialog
106 private Component makeDialogComponents()
108 JPanel dialogPanel = new JPanel();
109 dialogPanel.setLayout(new BorderLayout(5, 5));
111 // main panel with a box layout
112 JPanel mainPanel = new JPanel();
113 mainPanel.setLayout(new BoxLayout(mainPanel, BoxLayout.Y_AXIS));
115 JLabel introLabel = new JLabel(I18nManager.getText("dialog.fullrangedetails.intro") + ":");
116 introLabel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
117 introLabel.setAlignmentX(Component.LEFT_ALIGNMENT);
118 mainPanel.add(introLabel);
119 mainPanel.add(Box.createVerticalStrut(4));
121 // Details panel in a grid
122 JPanel detailsPanel = new JPanel();
123 detailsPanel.setLayout(new GridLayout(0, 4, 6, 2));
124 detailsPanel.setBorder(BorderFactory.createTitledBorder(
125 I18nManager.getText("dialog.estimatetime.details")));
128 JLabel distLabel = new JLabel(I18nManager.getText("fieldname.distance") + ": ");
129 distLabel.setHorizontalAlignment(JLabel.RIGHT);
130 detailsPanel.add(distLabel);
131 _distanceLabel = new JLabel("5 km");
132 detailsPanel.add(_distanceLabel);
133 detailsPanel.add(new JLabel("")); detailsPanel.add(new JLabel("")); // two blank cells
135 detailsPanel.add(new JLabel(""));
136 detailsPanel.add(new JLabel(I18nManager.getText("dialog.estimatetime.gentle")));
137 detailsPanel.add(new JLabel(I18nManager.getText("dialog.estimatetime.steep")));
138 detailsPanel.add(new JLabel("")); // blank cells
141 JLabel climbLabel = new JLabel(I18nManager.getText("dialog.estimatetime.climb") + ": ");
142 climbLabel.setHorizontalAlignment(JLabel.RIGHT);
143 detailsPanel.add(climbLabel);
144 _gentleClimbLabel = new JLabel("1500 m");
145 detailsPanel.add(_gentleClimbLabel);
146 _steepClimbLabel = new JLabel("1500 m");
147 detailsPanel.add(_steepClimbLabel);
148 detailsPanel.add(new JLabel(""));
151 JLabel descentLabel = new JLabel(I18nManager.getText("dialog.estimatetime.descent") + ": ");
152 descentLabel.setHorizontalAlignment(JLabel.RIGHT);
153 detailsPanel.add(descentLabel);
154 _gentleDescentLabel = new JLabel("1500 m");
155 detailsPanel.add(_gentleDescentLabel);
156 _steepDescentLabel = new JLabel("1500 m");
157 detailsPanel.add(_steepDescentLabel);
158 detailsPanel.add(new JLabel(""));
160 detailsPanel.setAlignmentX(Component.LEFT_ALIGNMENT);
161 mainPanel.add(detailsPanel);
162 mainPanel.add(Box.createVerticalStrut(4));
164 // Parameters panel in a flexible grid
165 JPanel paramsPanel = new JPanel();
166 GuiGridLayout paramsGrid = new GuiGridLayout(paramsPanel, new double[] {1.5, 0.2, 1.0, 0.2, 0.5},
167 new boolean[] {true, false, false, false, false});
168 paramsPanel.setBorder(BorderFactory.createTitledBorder(
169 I18nManager.getText("dialog.estimatetime.parameters")));
170 KeyAdapter paramChangeListener = new KeyAdapter() {
171 public void keyTyped(KeyEvent inE) {
172 SwingUtilities.invokeLater(new Runnable() {
174 calculateEstimatedTime();
178 public void keyPressed(KeyEvent inE) {
179 if (inE.getKeyCode() == KeyEvent.VK_ESCAPE) {_dialog.dispose();}
184 _flatSpeedLabel = new JLabel(I18nManager.getText("dialog.estimatetime.parameters.timefor") + ": "); // (filled in later)
185 _flatSpeedLabel.setHorizontalAlignment(JLabel.RIGHT);
186 paramsGrid.add(_flatSpeedLabel);
187 _flatSpeedField = new DecimalNumberField();
188 _flatSpeedField.addKeyListener(paramChangeListener);
189 paramsGrid.add(_flatSpeedField);
190 JLabel minsLabel = new JLabel(I18nManager.getText("units.minutes"));
191 paramsGrid.add(minsLabel);
192 paramsGrid.nextRow();
193 // Headers for gentle and steep
194 paramsGrid.add(new JLabel(""));
195 paramsGrid.add(new JLabel(I18nManager.getText("dialog.estimatetime.gentle")));
196 paramsGrid.add(new JLabel("")); // blank cell
197 paramsGrid.add(new JLabel(I18nManager.getText("dialog.estimatetime.steep")));
198 paramsGrid.nextRow();
200 _climbParamLabel = new JLabel(I18nManager.getText("dialog.estimatetime.parameters.timefor") + ": "); // (filled in later)
201 _climbParamLabel.setHorizontalAlignment(JLabel.RIGHT);
202 paramsGrid.add(_climbParamLabel);
203 _gentleClimbField = new DecimalNumberField();
204 _gentleClimbField.addKeyListener(paramChangeListener);
205 paramsGrid.add(_gentleClimbField);
206 paramsGrid.add(new JLabel(minsLabel.getText()));
208 _steepClimbField = new DecimalNumberField();
209 _steepClimbField.addKeyListener(paramChangeListener);
210 paramsGrid.add(_steepClimbField);
211 paramsGrid.add(new JLabel(minsLabel.getText()));
214 _descentParamLabel = new JLabel(I18nManager.getText("dialog.estimatetime.parameters.timefor") + ": "); // (filled in later)
215 _descentParamLabel.setHorizontalAlignment(JLabel.RIGHT);
216 paramsGrid.add(_descentParamLabel);
217 _gentleDescentField = new DecimalNumberField(true); // negative numbers allowed
218 _gentleDescentField.addKeyListener(paramChangeListener);
219 paramsGrid.add(_gentleDescentField);
220 paramsGrid.add(new JLabel(minsLabel.getText()));
222 _steepDescentField = new DecimalNumberField(true); // negative numbers allowed
223 _steepDescentField.addKeyListener(paramChangeListener);
224 paramsGrid.add(_steepDescentField);
225 paramsGrid.add(new JLabel(minsLabel.getText()));
227 paramsPanel.setAlignmentX(Component.LEFT_ALIGNMENT);
228 mainPanel.add(paramsPanel);
229 mainPanel.add(Box.createVerticalStrut(12));
232 JPanel resultsPanel = new JPanel();
233 resultsPanel.setBorder(BorderFactory.createTitledBorder(
234 I18nManager.getText("dialog.estimatetime.results")));
235 resultsPanel.setLayout(new GridLayout(0, 2, 3, 3));
237 _estimatedDurationLabel = new JLabel(I18nManager.getText("dialog.estimatetime.results.estimatedtime") + ": "); // filled in later
238 Font origFont = _estimatedDurationLabel.getFont();
239 _estimatedDurationLabel.setFont(origFont.deriveFont(Font.BOLD, origFont.getSize2D() + 2.0f));
241 resultsPanel.add(_estimatedDurationLabel);
242 // actual time (if available)
243 _actualDurationLabel = new JLabel(I18nManager.getText("dialog.estimatetime.results.actualtime") + ": "); // filled in later
244 resultsPanel.add(_actualDurationLabel);
246 resultsPanel.setAlignmentX(Component.LEFT_ALIGNMENT);
247 mainPanel.add(resultsPanel);
248 mainPanel.add(Box.createVerticalStrut(4));
250 dialogPanel.add(mainPanel, BorderLayout.NORTH);
251 // button panel at bottom
252 JPanel buttonPanel = new JPanel();
253 buttonPanel.setLayout(new FlowLayout(FlowLayout.RIGHT));
254 JButton closeButton = new JButton(I18nManager.getText("button.close"));
255 closeButton.addActionListener(new ActionListener() {
256 public void actionPerformed(ActionEvent e)
261 closeButton.addKeyListener(new KeyAdapter() {
262 public void keyPressed(KeyEvent inE) {
263 if (inE.getKeyCode() == KeyEvent.VK_ESCAPE) {_dialog.dispose();}
266 buttonPanel.add(closeButton);
267 dialogPanel.add(buttonPanel, BorderLayout.SOUTH);
273 * Recalculate the values and update the labels
275 private void updateDetails()
277 // Warn if the current track hasn't got any height information
278 if (!_stats.getMovingAltitudeRange().hasRange()) {
279 _app.showErrorMessage(getNameKey(), "dialog.estimatetime.error.noaltitudes");
282 // Distance in current units
283 final Unit distUnit = Config.getUnitSet().getDistanceUnit();
284 final String distUnitsStr = I18nManager.getText(distUnit.getShortnameKey());
285 final double movingDist = _stats.getMovingDistance();
286 _distanceLabel.setText(DisplayUtils.roundedNumber(movingDist) + " " + distUnitsStr);
288 // Climb and descent values
289 final Unit altUnit = Config.getUnitSet().getAltitudeUnit();
290 final String altUnitsStr = " " + I18nManager.getText(altUnit.getShortnameKey());
291 _gentleClimbLabel.setText(_stats.getGentleAltitudeRange().getClimb(altUnit) + altUnitsStr);
292 _steepClimbLabel.setText(_stats.getSteepAltitudeRange().getClimb(altUnit) + altUnitsStr);
293 _gentleDescentLabel.setText(_stats.getGentleAltitudeRange().getDescent(altUnit) + altUnitsStr);
294 _steepDescentLabel.setText(_stats.getSteepAltitudeRange().getDescent(altUnit) + altUnitsStr);
296 // Try to get parameters from config
297 EstimationParameters estParams = new EstimationParameters(Config.getConfigString(Config.KEY_ESTIMATION_PARAMS));
299 String[] paramValues = estParams.getStrings();
300 if (paramValues == null || paramValues.length != 5) {return;} // TODO: What to do in case of error?
301 // Flat time is either for 5 km, 3 miles or 3 nm
302 _flatSpeedLabel.setText(I18nManager.getText("dialog.estimatetime.parameters.timefor") +
303 " " + EstimationParameters.getStandardDistance() + ": ");
304 _flatSpeedField.setText(paramValues[0]);
306 final String heightString = " " + EstimationParameters.getStandardClimb() + ": ";
307 _climbParamLabel.setText(I18nManager.getText("dialog.estimatetime.climb") + heightString);
308 _gentleClimbField.setText(paramValues[1]);
309 _steepClimbField.setText(paramValues[2]);
310 _descentParamLabel.setText(I18nManager.getText("dialog.estimatetime.descent") + heightString);
311 _gentleDescentField.setText(paramValues[3]);
312 _steepDescentField.setText(paramValues[4]);
314 // Use the entered parameters to estimate the time
315 calculateEstimatedTime();
317 // Get the actual time if available, for comparison
318 if (_stats.getMovingDurationInSeconds() > 0)
320 _actualDurationLabel.setText(I18nManager.getText("dialog.estimatetime.results.actualtime") + ": "
321 + DisplayUtils.buildDurationString(_stats.getMovingDurationInSeconds()));
324 _actualDurationLabel.setText("");
330 * Use the current parameter and the range stats to calculate the estimated time
331 * and populate the answer in the dialog
333 private void calculateEstimatedTime()
335 // Populate an EstimationParameters object from the four strings
336 EstimationParameters params = new EstimationParameters();
337 params.populateWithStrings(_flatSpeedField.getText(), _gentleClimbField.getText(),
338 _steepClimbField.getText(), _gentleDescentField.getText(), _steepDescentField.getText());
339 if (!params.isValid()) {
340 _estimatedDurationLabel.setText("- - -");
344 final long numSeconds = (long) (params.applyToStats(_stats) * 60.0);
345 _estimatedDurationLabel.setText(I18nManager.getText("dialog.estimatetime.results.estimatedtime") + ": "
346 + DisplayUtils.buildDurationString(numSeconds));
352 * Finish with the dialog, by pressing the "Close" button
354 private void finishDialog()
356 // Make estimation parameters from entered strings, if valid save to config
357 EstimationParameters params = new EstimationParameters();
358 params.populateWithStrings(_flatSpeedField.getText(), _gentleClimbField.getText(),
359 _steepClimbField.getText(), _gentleDescentField.getText(), _steepDescentField.getText());
360 if (params.isValid()) {
361 Config.setConfigString(Config.KEY_ESTIMATION_PARAMS, params.toConfigString());
367 * Show a tip to use the learn function, if appropriate
369 private void showTip()
371 EstimationParameters currParams = new EstimationParameters(
372 Config.getConfigString(Config.KEY_ESTIMATION_PARAMS));
373 if (currParams.sameAsDefaults()) {
374 _app.showTip(TipManager.Tip_LearnTimeParams);