]> gitweb.fperrin.net Git - GpsPrune.git/blob - tim/prune/function/estimate/EstimateTime.java
7a502e4332ed03e9aca1d451d170f7bd70aa4800
[GpsPrune.git] / 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.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
33 /**
34  * Class to calculate and show the results of estimating (hike) time for the current range
35  */
36 public class EstimateTime extends GenericFunction
37 {
38         /** Dialog */
39         private JDialog _dialog = null;
40         /** Labels for distances */
41         private JLabel _distanceLabel = null;
42         /** Labels for durations */
43         private JLabel _estimatedDurationLabel = null, _actualDurationLabel = null;
44         /** Labels for climbs */
45         private JLabel _gentleClimbLabel = null, _steepClimbLabel = null;
46         /** Labels for descents */
47         private JLabel _gentleDescentLabel = null, _steepDescentLabel = null;
48         /** Labels and text fields for parameters */
49         private JLabel _flatSpeedLabel = null;
50         private DecimalNumberField _flatSpeedField = null;
51         private JLabel _climbParamLabel = null;
52         private DecimalNumberField _gentleClimbField = null, _steepClimbField = null;
53         private JLabel _descentParamLabel = null;
54         private DecimalNumberField _gentleDescentField = null, _steepDescentField = null;
55         /** Range stats */
56         private RangeStats _stats = null;
57
58
59         /**
60          * Constructor
61          * @param inApp App object
62          */
63         public EstimateTime(App inApp)
64         {
65                 super(inApp);
66         }
67
68         /** Get the name key */
69         public String getNameKey() {
70                 return "function.estimatetime";
71         }
72
73         /**
74          * Begin the function
75          */
76         public void begin()
77         {
78                 // Get the stats on the selection before launching the dialog
79                 Selection selection = _app.getTrackInfo().getSelection();
80                 _stats = new RangeStats(_app.getTrackInfo().getTrack(), selection.getStart(), selection.getEnd());
81
82                 if (_stats.getMovingDistance() < 0.01)
83                 {
84                         _app.showErrorMessage(getNameKey(), "dialog.estimatetime.error.nodistance");
85                         return;
86                 }
87                 if (_dialog == null)
88                 {
89                         // TODO: Check whether params are at default, show tip message if unaltered?
90                         _dialog = new JDialog(_parentFrame, I18nManager.getText(getNameKey()), true);
91                         _dialog.setLocationRelativeTo(_parentFrame);
92                         _dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
93                         _dialog.getContentPane().add(makeDialogComponents());
94                         _dialog.pack();
95                 }
96                 updateDetails();
97                 _dialog.setVisible(true);
98         }
99
100         /**
101          * Create dialog components
102          * @return Panel containing all gui elements in dialog
103          */
104         private Component makeDialogComponents()
105         {
106                 JPanel dialogPanel = new JPanel();
107                 dialogPanel.setLayout(new BorderLayout(5, 5));
108
109                 // main panel with a box layout
110                 JPanel mainPanel = new JPanel();
111                 mainPanel.setLayout(new BoxLayout(mainPanel, BoxLayout.Y_AXIS));
112                 // Label at top
113                 JLabel introLabel = new JLabel(I18nManager.getText("dialog.fullrangedetails.intro") + ":");
114                 introLabel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
115                 introLabel.setAlignmentX(Component.LEFT_ALIGNMENT);
116                 mainPanel.add(introLabel);
117                 mainPanel.add(Box.createVerticalStrut(4));
118
119                 // Details panel in a grid
120                 JPanel detailsPanel = new JPanel();
121                 detailsPanel.setLayout(new GridLayout(0, 4, 6, 2));
122                 detailsPanel.setBorder(BorderFactory.createTitledBorder(
123                         I18nManager.getText("dialog.estimatetime.details")));
124
125                 // Distance
126                 JLabel distLabel = new JLabel(I18nManager.getText("fieldname.distance") + ": ");
127                 distLabel.setHorizontalAlignment(JLabel.RIGHT);
128                 detailsPanel.add(distLabel);
129                 _distanceLabel = new JLabel("5 km");
130                 detailsPanel.add(_distanceLabel);
131                 detailsPanel.add(new JLabel("")); detailsPanel.add(new JLabel("")); // two blank cells
132
133                 detailsPanel.add(new JLabel(""));
134                 detailsPanel.add(new JLabel(I18nManager.getText("dialog.estimatetime.gentle")));
135                 detailsPanel.add(new JLabel(I18nManager.getText("dialog.estimatetime.steep")));
136                 detailsPanel.add(new JLabel("")); // blank cells
137
138                 // Climb
139                 JLabel climbLabel = new JLabel(I18nManager.getText("dialog.estimatetime.climb") + ": ");
140                 climbLabel.setHorizontalAlignment(JLabel.RIGHT);
141                 detailsPanel.add(climbLabel);
142                 _gentleClimbLabel = new JLabel("1500 m");
143                 detailsPanel.add(_gentleClimbLabel);
144                 _steepClimbLabel = new JLabel("1500 m");
145                 detailsPanel.add(_steepClimbLabel);
146                 detailsPanel.add(new JLabel(""));
147
148                 // Descent
149                 JLabel descentLabel = new JLabel(I18nManager.getText("dialog.estimatetime.descent") + ": ");
150                 descentLabel.setHorizontalAlignment(JLabel.RIGHT);
151                 detailsPanel.add(descentLabel);
152                 _gentleDescentLabel = new JLabel("1500 m");
153                 detailsPanel.add(_gentleDescentLabel);
154                 _steepDescentLabel = new JLabel("1500 m");
155                 detailsPanel.add(_steepDescentLabel);
156                 detailsPanel.add(new JLabel(""));
157
158                 detailsPanel.setAlignmentX(Component.LEFT_ALIGNMENT);
159                 mainPanel.add(detailsPanel);
160                 mainPanel.add(Box.createVerticalStrut(4));
161
162                 // Parameters panel in a flexible grid
163                 JPanel paramsPanel = new JPanel();
164                 GuiGridLayout paramsGrid = new GuiGridLayout(paramsPanel, new double[] {1.5, 0.2, 1.0, 0.2, 0.5},
165                         new boolean[] {true, false, false, false, false});
166                 paramsPanel.setBorder(BorderFactory.createTitledBorder(
167                         I18nManager.getText("dialog.estimatetime.parameters")));
168                 KeyAdapter paramChangeListener = new KeyAdapter() {
169                         public void keyTyped(KeyEvent inE) {
170                                 SwingUtilities.invokeLater(new Runnable() {
171                                         public void run() {
172                                                 calculateEstimatedTime();
173                                         }
174                                 });
175                         }
176                         public void keyPressed(KeyEvent inE) {
177                                 if (inE.getKeyCode() == KeyEvent.VK_ESCAPE) {_dialog.dispose();}
178                         }
179                 };
180
181                 // Flat speed
182                 _flatSpeedLabel = new JLabel(I18nManager.getText("dialog.estimatetime.parameters.timefor") + ": "); // (filled in later)
183                 _flatSpeedLabel.setHorizontalAlignment(JLabel.RIGHT);
184                 paramsGrid.add(_flatSpeedLabel);
185                 _flatSpeedField = new DecimalNumberField();
186                 _flatSpeedField.addKeyListener(paramChangeListener);
187                 paramsGrid.add(_flatSpeedField);
188                 JLabel minsLabel = new JLabel(I18nManager.getText("units.minutes"));
189                 paramsGrid.add(minsLabel);
190                 paramsGrid.nextRow();
191                 // Headers for gentle and steep
192                 paramsGrid.add(new JLabel(""));
193                 paramsGrid.add(new JLabel(I18nManager.getText("dialog.estimatetime.gentle")));
194                 paramsGrid.add(new JLabel("")); // blank cell
195                 paramsGrid.add(new JLabel(I18nManager.getText("dialog.estimatetime.steep")));
196                 paramsGrid.nextRow();
197                 // Gentle climb
198                 _climbParamLabel = new JLabel(I18nManager.getText("dialog.estimatetime.parameters.timefor") + ": "); // (filled in later)
199                 _climbParamLabel.setHorizontalAlignment(JLabel.RIGHT);
200                 paramsGrid.add(_climbParamLabel);
201                 _gentleClimbField = new DecimalNumberField();
202                 _gentleClimbField.addKeyListener(paramChangeListener);
203                 paramsGrid.add(_gentleClimbField);
204                 paramsGrid.add(new JLabel(minsLabel.getText()));
205                 // Steep climb
206                 _steepClimbField = new DecimalNumberField();
207                 _steepClimbField.addKeyListener(paramChangeListener);
208                 paramsGrid.add(_steepClimbField);
209                 paramsGrid.add(new JLabel(minsLabel.getText()));
210
211                 // Gentle descent
212                 _descentParamLabel = new JLabel(I18nManager.getText("dialog.estimatetime.parameters.timefor") + ": "); // (filled in later)
213                 _descentParamLabel.setHorizontalAlignment(JLabel.RIGHT);
214                 paramsGrid.add(_descentParamLabel);
215                 _gentleDescentField = new DecimalNumberField(true); // negative numbers allowed
216                 _gentleDescentField.addKeyListener(paramChangeListener);
217                 paramsGrid.add(_gentleDescentField);
218                 paramsGrid.add(new JLabel(minsLabel.getText()));
219                 // Steep climb
220                 _steepDescentField = new DecimalNumberField(true); // negative numbers allowed
221                 _steepDescentField.addKeyListener(paramChangeListener);
222                 paramsGrid.add(_steepDescentField);
223                 paramsGrid.add(new JLabel(minsLabel.getText()));
224
225                 paramsPanel.setAlignmentX(Component.LEFT_ALIGNMENT);
226                 mainPanel.add(paramsPanel);
227                 mainPanel.add(Box.createVerticalStrut(12));
228
229                 // Results panel
230                 JPanel resultsPanel = new JPanel();
231                 resultsPanel.setBorder(BorderFactory.createTitledBorder(
232                         I18nManager.getText("dialog.estimatetime.results")));
233                 resultsPanel.setLayout(new GridLayout(0, 2, 3, 3));
234                 // estimated time
235                 _estimatedDurationLabel = new JLabel(I18nManager.getText("dialog.estimatetime.results.estimatedtime") + ": "); // filled in later
236                 Font origFont = _estimatedDurationLabel.getFont();
237                 _estimatedDurationLabel.setFont(origFont.deriveFont(Font.BOLD, origFont.getSize2D() + 2.0f));
238
239                 resultsPanel.add(_estimatedDurationLabel);
240                 // actual time (if available)
241                 _actualDurationLabel = new JLabel(I18nManager.getText("dialog.estimatetime.results.actualtime") + ": "); // filled in later
242                 resultsPanel.add(_actualDurationLabel);
243
244                 resultsPanel.setAlignmentX(Component.LEFT_ALIGNMENT);
245                 mainPanel.add(resultsPanel);
246                 mainPanel.add(Box.createVerticalStrut(4));
247
248                 dialogPanel.add(mainPanel, BorderLayout.NORTH);
249                 // button panel at bottom
250                 JPanel buttonPanel = new JPanel();
251                 buttonPanel.setLayout(new FlowLayout(FlowLayout.RIGHT));
252                 JButton closeButton = new JButton(I18nManager.getText("button.close"));
253                 closeButton.addActionListener(new ActionListener() {
254                         public void actionPerformed(ActionEvent e)
255                         {
256                                 finishDialog();
257                         }
258                 });
259                 closeButton.addKeyListener(new KeyAdapter() {
260                         public void keyPressed(KeyEvent inE) {
261                                 if (inE.getKeyCode() == KeyEvent.VK_ESCAPE) {_dialog.dispose();}
262                         }
263                 });
264                 buttonPanel.add(closeButton);
265                 dialogPanel.add(buttonPanel, BorderLayout.SOUTH);
266                 return dialogPanel;
267         }
268
269
270         /**
271          * Recalculate the values and update the labels
272          */
273         private void updateDetails()
274         {
275                 // Warn if the current track hasn't got any height information
276                 if (!_stats.getMovingAltitudeRange().hasRange()) {
277                         _app.showErrorMessage(getNameKey(), "dialog.estimatetime.error.noaltitudes");
278                 }
279
280                 // Distance in current units
281                 final Unit distUnit = Config.getUnitSet().getDistanceUnit();
282                 final String distUnitsStr = I18nManager.getText(distUnit.getShortnameKey());
283                 final double movingDist = _stats.getMovingDistance();
284                 _distanceLabel.setText(DisplayUtils.roundedNumber(movingDist) + " " + distUnitsStr);
285
286                 // Climb and descent values
287                 final Unit altUnit = Config.getUnitSet().getAltitudeUnit();
288                 final String altUnitsStr = " " + I18nManager.getText(altUnit.getShortnameKey());
289                 _gentleClimbLabel.setText(_stats.getGentleAltitudeRange().getClimb(altUnit) + altUnitsStr);
290                 _steepClimbLabel.setText(_stats.getSteepAltitudeRange().getClimb(altUnit) + altUnitsStr);
291                 _gentleDescentLabel.setText(_stats.getGentleAltitudeRange().getDescent(altUnit) + altUnitsStr);
292                 _steepDescentLabel.setText(_stats.getSteepAltitudeRange().getDescent(altUnit) + altUnitsStr);
293
294                 // Try to get parameters from config
295                 EstimationParameters estParams = new EstimationParameters(Config.getConfigString(Config.KEY_ESTIMATION_PARAMS));
296
297                 String[] paramValues = estParams.getStrings();
298                 if (paramValues == null || paramValues.length != 5) {return;} // TODO: What to do in case of error?
299                 // Flat time is either for 5 km, 3 miles or 3 nm
300                 _flatSpeedLabel.setText(I18nManager.getText("dialog.estimatetime.parameters.timefor") +
301                         " " + EstimationParameters.getStandardDistance() + ": ");
302                 _flatSpeedField.setText(paramValues[0]);
303
304                 final String heightString = " " + EstimationParameters.getStandardClimb() + ": ";
305                 _climbParamLabel.setText(I18nManager.getText("dialog.estimatetime.climb") + heightString);
306                 _gentleClimbField.setText(paramValues[1]);
307                 _steepClimbField.setText(paramValues[2]);
308                 _descentParamLabel.setText(I18nManager.getText("dialog.estimatetime.descent") + heightString);
309                 _gentleDescentField.setText(paramValues[3]);
310                 _steepDescentField.setText(paramValues[4]);
311
312                 // Use the entered parameters to estimate the time
313                 calculateEstimatedTime();
314
315                 // Get the actual time if available, for comparison
316                 if (_stats.getMovingDurationInSeconds() > 0)
317                 {
318                         _actualDurationLabel.setText(I18nManager.getText("dialog.estimatetime.results.actualtime") + ": "
319                                 + DisplayUtils.buildDurationString(_stats.getMovingDurationInSeconds()));
320                 }
321                 else {
322                         _actualDurationLabel.setText("");
323                 }
324         }
325
326
327         /**
328          * Use the current parameter and the range stats to calculate the estimated time
329          * and populate the answer in the dialog
330          */
331         private void calculateEstimatedTime()
332         {
333                 // Populate an EstimationParameters object from the four strings
334                 EstimationParameters params = new EstimationParameters();
335                 params.populateWithStrings(_flatSpeedField.getText(), _gentleClimbField.getText(),
336                         _steepClimbField.getText(), _gentleDescentField.getText(), _steepDescentField.getText());
337                 if (!params.isValid()) {
338                         _estimatedDurationLabel.setText("- - -");
339                 }
340                 else
341                 {
342                         final long numSeconds = (long) (params.applyToStats(_stats) * 60.0);
343                         _estimatedDurationLabel.setText(I18nManager.getText("dialog.estimatetime.results.estimatedtime") + ": "
344                                 + DisplayUtils.buildDurationString(numSeconds));
345                 }
346         }
347
348
349         /**
350          * Finish with the dialog, by pressing the "Close" button
351          */
352         private void finishDialog()
353         {
354                 // Make estimation parameters from entered strings, if valid save to config
355                 EstimationParameters params = new EstimationParameters();
356                 params.populateWithStrings(_flatSpeedField.getText(), _gentleClimbField.getText(),
357                         _steepClimbField.getText(), _gentleDescentField.getText(), _steepDescentField.getText());
358                 if (params.isValid()) {
359                         Config.setConfigString(Config.KEY_ESTIMATION_PARAMS, params.toConfigString());
360                 }
361                 _dialog.dispose();
362         }
363 }