]> gitweb.fperrin.net Git - GpsPrune.git/blob - src/tim/prune/function/estimate/EstimateTime.java
Version 20.4, May 2021
[GpsPrune.git] / src / tim / prune / function / estimate / EstimateTime.java
1 package tim.prune.function.estimate;
2
3 import java.awt.BorderLayout;
4 import java.awt.Component;
5 import java.awt.FlowLayout;
6 import java.awt.Font;
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;
12
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;
21
22 import tim.prune.App;
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;
33
34 /**
35  * Class to calculate and show the results of estimating (hike) time for the current range
36  */
37 public class EstimateTime extends GenericFunction
38 {
39         /** Dialog */
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;
56         /** Range stats */
57         private RangeStatsWithGradients _stats = null;
58
59
60         /**
61          * Constructor
62          * @param inApp App object
63          */
64         public EstimateTime(App inApp)
65         {
66                 super(inApp);
67         }
68
69         /** Get the name key */
70         public String getNameKey() {
71                 return "function.estimatetime";
72         }
73
74         /**
75          * Begin the function
76          */
77         public void begin()
78         {
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());
83
84                 if (_stats.getMovingDistance() < 0.01)
85                 {
86                         _app.showErrorMessage(getNameKey(), "dialog.estimatetime.error.nodistance");
87                         return;
88                 }
89                 if (_dialog == null)
90                 {
91                         // First time in, check whether params are at default, show tip message if unaltered
92                         showTip();
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());
97                         _dialog.pack();
98                 }
99                 updateDetails();
100                 _dialog.setVisible(true);
101         }
102
103         /**
104          * Create dialog components
105          * @return Panel containing all gui elements in dialog
106          */
107         private Component makeDialogComponents()
108         {
109                 JPanel dialogPanel = new JPanel();
110                 dialogPanel.setLayout(new BorderLayout(5, 5));
111
112                 // main panel with a box layout
113                 JPanel mainPanel = new JPanel();
114                 mainPanel.setLayout(new BoxLayout(mainPanel, BoxLayout.Y_AXIS));
115                 // Label at top
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));
121
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")));
127
128                 // Distance
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
135
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
140
141                 // Climb
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(""));
150
151                 // Descent
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(""));
160
161                 detailsPanel.setAlignmentX(Component.LEFT_ALIGNMENT);
162                 mainPanel.add(detailsPanel);
163                 mainPanel.add(Box.createVerticalStrut(4));
164
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() {
174                                         public void run() {
175                                                 calculateEstimatedTime();
176                                         }
177                                 });
178                         }
179                         public void keyPressed(KeyEvent inE) {
180                                 if (inE.getKeyCode() == KeyEvent.VK_ESCAPE) {_dialog.dispose();}
181                         }
182                 };
183
184                 // Flat speed
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();
200                 // Gentle climb
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()));
208                 // Steep climb
209                 _steepClimbField = new DecimalNumberField();
210                 _steepClimbField.addKeyListener(paramChangeListener);
211                 paramsGrid.add(_steepClimbField);
212                 paramsGrid.add(new JLabel(minsLabel.getText()));
213
214                 // Gentle descent
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()));
222                 // Steep climb
223                 _steepDescentField = new DecimalNumberField(true); // negative numbers allowed
224                 _steepDescentField.addKeyListener(paramChangeListener);
225                 paramsGrid.add(_steepDescentField);
226                 paramsGrid.add(new JLabel(minsLabel.getText()));
227
228                 paramsPanel.setAlignmentX(Component.LEFT_ALIGNMENT);
229                 mainPanel.add(paramsPanel);
230                 mainPanel.add(Box.createVerticalStrut(12));
231
232                 // Results panel
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));
237                 // estimated time
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));
241
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);
246
247                 resultsPanel.setAlignmentX(Component.LEFT_ALIGNMENT);
248                 mainPanel.add(resultsPanel);
249                 mainPanel.add(Box.createVerticalStrut(4));
250
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)
258                         {
259                                 finishDialog();
260                         }
261                 });
262                 closeButton.addKeyListener(new KeyAdapter() {
263                         public void keyPressed(KeyEvent inE) {
264                                 if (inE.getKeyCode() == KeyEvent.VK_ESCAPE) {_dialog.dispose();}
265                         }
266                 });
267                 buttonPanel.add(closeButton);
268                 dialogPanel.add(buttonPanel, BorderLayout.SOUTH);
269                 return dialogPanel;
270         }
271
272
273         /**
274          * Recalculate the values and update the labels
275          */
276         private void updateDetails()
277         {
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");
281                 }
282
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);
288
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);
296
297                 // Try to get parameters from config
298                 EstimationParameters estParams = new EstimationParameters(Config.getConfigString(Config.KEY_ESTIMATION_PARAMS));
299
300                 String[] paramValues = estParams.getStrings();
301                 if (paramValues == null || paramValues.length != 5)
302                 {
303                         // TODO: What to do in case of error?
304                         return;
305                 }
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]);
310
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]);
318
319                 // Use the entered parameters to estimate the time
320                 calculateEstimatedTime();
321
322                 // Get the actual time if available, for comparison
323                 if (_stats.getMovingDurationInSeconds() > 0)
324                 {
325                         _actualDurationLabel.setText(I18nManager.getText("dialog.estimatetime.results.actualtime") + ": "
326                                 + DisplayUtils.buildDurationString(_stats.getMovingDurationInSeconds()));
327                 }
328                 else {
329                         _actualDurationLabel.setText("");
330                 }
331         }
332
333
334         /**
335          * Use the current parameter and the range stats to calculate the estimated time
336          * and populate the answer in the dialog
337          */
338         private void calculateEstimatedTime()
339         {
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("- - -");
346                 }
347                 else
348                 {
349                         final long numSeconds = (long) (params.applyToStats(_stats) * 60.0);
350                         _estimatedDurationLabel.setText(I18nManager.getText("dialog.estimatetime.results.estimatedtime") + ": "
351                                 + DisplayUtils.buildDurationString(numSeconds));
352                 }
353         }
354
355
356         /**
357          * Finish with the dialog, by pressing the "Close" button
358          */
359         private void finishDialog()
360         {
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());
367                 }
368                 _dialog.dispose();
369         }
370
371         /**
372          * Show a tip to use the learn function, if appropriate
373          */
374         private void showTip()
375         {
376                 EstimationParameters currParams = new EstimationParameters(
377                         Config.getConfigString(Config.KEY_ESTIMATION_PARAMS));
378                 if (currParams.sameAsDefaults()) {
379                         _app.showTip(TipManager.Tip_LearnTimeParams);
380                 }
381         }
382 }