1 package tim.prune.function;
3 import java.awt.BorderLayout;
4 import java.awt.Component;
5 import java.awt.FlowLayout;
6 import java.awt.GridLayout;
7 import java.awt.event.ActionEvent;
8 import java.awt.event.ActionListener;
9 import java.awt.event.KeyAdapter;
10 import java.awt.event.KeyEvent;
11 import java.text.NumberFormat;
13 import javax.swing.BorderFactory;
14 import javax.swing.JButton;
15 import javax.swing.JDialog;
16 import javax.swing.JLabel;
17 import javax.swing.JPanel;
20 import tim.prune.GenericFunction;
21 import tim.prune.I18nManager;
22 import tim.prune.config.Config;
23 import tim.prune.data.Altitude;
24 import tim.prune.data.AltitudeRange;
25 import tim.prune.data.DataPoint;
26 import tim.prune.data.Selection;
27 import tim.prune.data.Unit;
28 import tim.prune.gui.DisplayUtils;
29 import tim.prune.gui.profile.SpeedData;
32 * Class to show the full range details in a separate popup
34 public class FullRangeDetails extends GenericFunction
37 private JDialog _dialog = null;
38 /** Label for number of points */
39 private JLabel _numPointsLabel = null;
40 /** Label for number of segments */
41 private JLabel _numSegsLabel = null;
42 /** Label for the maximum speed */
43 private JLabel _maxSpeedLabel = null;
45 /** Label for heading of "total" column */
46 private JLabel _colTotalLabel = null;
47 /** Label for heading of "segments" column */
48 private JLabel _colSegmentsLabel = null;
49 /** Labels for distances */
50 private JLabel _totalDistanceLabel = null, _movingDistanceLabel = null;
51 /** Labels for durations */
52 private JLabel _totalDurationLabel = null, _movingDurationLabel = null;
53 /** Labels for climbs */
54 private JLabel _totalClimbLabel = null, _movingClimbLabel = null;
55 /** Labels for descents */
56 private JLabel _totalDescentLabel = null, _movingDescentLabel = null;
57 /** Labels for pace */
58 private JLabel _totalPaceLabel = null, _movingPaceLabel = null;
59 /** Labels for gradient */
60 private JLabel _totalGradientLabel = null, _movingGradientLabel = null;
61 /** Labels for speed */
62 private JLabel _totalSpeedLabel, _movingSpeedLabel = null;
63 /** Labels for vertical speed */
64 private JLabel _totalVertSpeedLabel, _movingVertSpeedLabel = null;
66 /** Number formatter for one decimal place */
67 private static final NumberFormat FORMAT_ONE_DP = NumberFormat.getNumberInstance();
68 /** Flexible number formatter for different decimal places */
69 private NumberFormat _distanceFormatter = NumberFormat.getInstance();
73 * @param inApp App object
75 public FullRangeDetails(App inApp)
78 FORMAT_ONE_DP.setMaximumFractionDigits(1);
79 FORMAT_ONE_DP.setMinimumFractionDigits(1);
82 /** Get the name key */
83 public String getNameKey() {
84 return "function.fullrangedetails";
94 _dialog = new JDialog(_parentFrame, I18nManager.getText(getNameKey()), true);
95 _dialog.setLocationRelativeTo(_parentFrame);
96 _dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
97 _dialog.getContentPane().add(makeDialogComponents());
101 _dialog.setVisible(true);
105 * Create dialog components
106 * @return Panel containing all gui elements in dialog
108 private Component makeDialogComponents()
110 JPanel dialogPanel = new JPanel();
111 dialogPanel.setLayout(new BorderLayout(5, 5));
113 JLabel topLabel = new JLabel(I18nManager.getText("dialog.fullrangedetails.intro") + ":");
114 topLabel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
115 dialogPanel.add(topLabel, BorderLayout.NORTH);
117 // Details panel in middle
118 JPanel midPanel = new JPanel();
119 midPanel.setLayout(new GridLayout(0, 3, 6, 2));
120 midPanel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 15));
122 JLabel pointsLabel = new JLabel(I18nManager.getText("details.track.points") + ": ");
123 pointsLabel.setHorizontalAlignment(JLabel.RIGHT);
124 midPanel.add(pointsLabel);
125 _numPointsLabel = new JLabel("100");
126 midPanel.add(_numPointsLabel);
127 midPanel.add(new JLabel(" "));
128 // Number of segments
129 JLabel segLabel = new JLabel(I18nManager.getText("details.range.numsegments") + ": ");
130 segLabel.setHorizontalAlignment(JLabel.RIGHT);
131 midPanel.add(segLabel);
132 _numSegsLabel = new JLabel("100");
133 midPanel.add(_numSegsLabel);
134 midPanel.add(new JLabel(" "));
136 JLabel maxSpeedLabel = new JLabel(I18nManager.getText("details.range.maxspeed") + ": ");
137 maxSpeedLabel.setHorizontalAlignment(JLabel.RIGHT);
138 midPanel.add(maxSpeedLabel);
139 _maxSpeedLabel = new JLabel("10 km/h");
140 midPanel.add(_maxSpeedLabel);
141 midPanel.add(new JLabel(" "));
144 for (int i=0; i<3; i++) midPanel.add(new JLabel(" "));
146 // Row for column headings
147 midPanel.add(new JLabel(" "));
148 _colTotalLabel = new JLabel(I18nManager.getText("dialog.fullrangedetails.coltotal"));
149 midPanel.add(_colTotalLabel);
150 _colSegmentsLabel = new JLabel(I18nManager.getText("dialog.fullrangedetails.colsegments"));
151 midPanel.add(_colSegmentsLabel);
154 JLabel distLabel = new JLabel(I18nManager.getText("fieldname.distance") + ": ");
155 distLabel.setHorizontalAlignment(JLabel.RIGHT);
156 midPanel.add(distLabel);
157 _totalDistanceLabel = new JLabel("5 km");
158 midPanel.add(_totalDistanceLabel);
159 _movingDistanceLabel = new JLabel("5 km");
160 midPanel.add(_movingDistanceLabel);
163 JLabel durationLabel = new JLabel(I18nManager.getText("fieldname.duration") + ": ");
164 durationLabel.setHorizontalAlignment(JLabel.RIGHT);
165 midPanel.add(durationLabel);
166 _totalDurationLabel = new JLabel("15 min");
167 midPanel.add(_totalDurationLabel);
168 _movingDurationLabel = new JLabel("15 min");
169 midPanel.add(_movingDurationLabel);
172 JLabel speedLabel = new JLabel(I18nManager.getText("details.range.avespeed") + ": ");
173 speedLabel.setHorizontalAlignment(JLabel.RIGHT);
174 midPanel.add(speedLabel);
175 _totalSpeedLabel = new JLabel("5.5 km/h");
176 midPanel.add(_totalSpeedLabel);
177 _movingSpeedLabel = new JLabel("5.5 km/h");
178 midPanel.add(_movingSpeedLabel);
181 JLabel paceLabel = new JLabel(I18nManager.getText("details.range.pace") + ": ");
182 paceLabel.setHorizontalAlignment(JLabel.RIGHT);
183 midPanel.add(paceLabel);
184 _totalPaceLabel = new JLabel("8 min/km");
185 midPanel.add(_totalPaceLabel);
186 _movingPaceLabel = new JLabel("8 min/km");
187 midPanel.add(_movingPaceLabel);
190 JLabel climbLabel = new JLabel(I18nManager.getText("details.range.climb") + ": ");
191 climbLabel.setHorizontalAlignment(JLabel.RIGHT);
192 midPanel.add(climbLabel);
193 _totalClimbLabel = new JLabel("1000 m");
194 midPanel.add(_totalClimbLabel);
195 _movingClimbLabel = new JLabel("1000 m");
196 midPanel.add(_movingClimbLabel);
198 JLabel descentLabel = new JLabel(I18nManager.getText("details.range.descent") + ": ");
199 descentLabel.setHorizontalAlignment(JLabel.RIGHT);
200 midPanel.add(descentLabel);
201 _totalDescentLabel = new JLabel("1000 m");
202 midPanel.add(_totalDescentLabel);
203 _movingDescentLabel = new JLabel("1000 m");
204 midPanel.add(_movingDescentLabel);
207 JLabel gradientLabel = new JLabel(I18nManager.getText("details.range.gradient") + ": ");
208 gradientLabel.setHorizontalAlignment(JLabel.RIGHT);
209 midPanel.add(gradientLabel);
210 _totalGradientLabel = new JLabel("10 %");
211 midPanel.add(_totalGradientLabel);
212 _movingGradientLabel = new JLabel("10 %");
213 midPanel.add(_movingGradientLabel);
216 JLabel vSpeedLabel = new JLabel(I18nManager.getText("fieldname.verticalspeed") + ": ");
217 vSpeedLabel.setHorizontalAlignment(JLabel.RIGHT);
218 midPanel.add(vSpeedLabel);
219 _totalVertSpeedLabel = new JLabel("1 m/s");
220 midPanel.add(_totalVertSpeedLabel);
221 _movingVertSpeedLabel = new JLabel("1 m/s");
222 midPanel.add(_movingVertSpeedLabel);
224 dialogPanel.add(midPanel, BorderLayout.CENTER);
225 // button panel at bottom
226 JPanel buttonPanel = new JPanel();
227 buttonPanel.setLayout(new FlowLayout(FlowLayout.RIGHT));
228 JButton closeButton = new JButton(I18nManager.getText("button.close"));
229 closeButton.addActionListener(new ActionListener() {
230 public void actionPerformed(ActionEvent e)
235 closeButton.addKeyListener(new KeyAdapter() {
236 public void keyPressed(KeyEvent inE) {
237 if (inE.getKeyCode() == KeyEvent.VK_ESCAPE) {_dialog.dispose();}
238 super.keyPressed(inE);
241 buttonPanel.add(closeButton);
242 dialogPanel.add(buttonPanel, BorderLayout.SOUTH);
248 * Update the labels with the current details
250 private void updateDetails()
252 Selection selection = _app.getTrackInfo().getSelection();
254 _numPointsLabel.setText("" + (selection.getEnd()-selection.getStart()+1));
255 // Number of segments
256 _numSegsLabel.setText("" + selection.getNumSegments());
257 final boolean isMultiSegments = (selection.getNumSegments() > 1);
258 // Set visibility of third column accordingly
259 _movingDistanceLabel.setVisible(isMultiSegments);
260 _movingDurationLabel.setVisible(isMultiSegments);
261 _movingClimbLabel.setVisible(isMultiSegments);
262 _movingDescentLabel.setVisible(isMultiSegments);
263 _movingSpeedLabel.setVisible(isMultiSegments);
264 _movingPaceLabel.setVisible(isMultiSegments);
265 _movingGradientLabel.setVisible(isMultiSegments);
266 _movingVertSpeedLabel.setVisible(isMultiSegments);
268 // Distance in current units
269 final Unit distUnit = Config.getUnitSet().getDistanceUnit();
270 final String distUnitsStr = I18nManager.getText(distUnit.getShortnameKey());
271 final double selectionDistance = selection.getDistance();
272 _totalDistanceLabel.setText(roundedNumber(selectionDistance) + " " + distUnitsStr);
275 long numSecs = selection.getNumSeconds();
276 _totalDurationLabel.setText(DisplayUtils.buildDurationString(numSecs));
278 final Unit altUnit = Config.getUnitSet().getAltitudeUnit();
279 final String altUnitsStr = " " + I18nManager.getText(altUnit.getShortnameKey());
280 if (selection.getAltitudeRange().hasRange()) {
281 _totalClimbLabel.setText(selection.getAltitudeRange().getClimb(altUnit) + altUnitsStr);
282 _totalDescentLabel.setText(selection.getAltitudeRange().getDescent(altUnit) + altUnitsStr);
285 _totalClimbLabel.setText("");
286 _totalDescentLabel.setText("");
289 // Overall pace and speed
290 final String speedUnitsStr = I18nManager.getText(Config.getUnitSet().getSpeedUnit().getShortnameKey());
291 if (numSecs > 0 && selectionDistance > 0)
293 _totalPaceLabel.setText(
294 DisplayUtils.buildDurationString((long) (numSecs/selectionDistance))
295 + " / " + distUnitsStr);
296 _totalSpeedLabel.setText(roundedNumber(selectionDistance/numSecs*3600.0)
297 + " " + speedUnitsStr);
300 _totalPaceLabel.setText("");
301 _totalSpeedLabel.setText("");
305 double movingDist = selection.getMovingDistance();
306 _movingDistanceLabel.setText(roundedNumber(movingDist) + " " + distUnitsStr);
307 // Moving average speed
308 long numMovingSecs = selection.getMovingSeconds();
309 if (numMovingSecs > 0)
311 _movingDurationLabel.setText(DisplayUtils.buildDurationString(numMovingSecs));
312 _movingSpeedLabel.setText(roundedNumber(movingDist/numMovingSecs*3600.0)
313 + " " + speedUnitsStr);
314 _movingPaceLabel.setText(
315 DisplayUtils.buildDurationString((long) (numMovingSecs/movingDist))
316 + " / " + distUnitsStr);
320 _movingDurationLabel.setText("");
321 _movingSpeedLabel.setText("");
322 _movingPaceLabel.setText("");
325 // Moving gradient and moving climb/descent
326 Altitude firstAlt = null, lastAlt = null;
327 Altitude veryFirstAlt = null, veryLastAlt = null;
328 AltitudeRange altRange = new AltitudeRange();
329 double movingHeightDiff = 0.0;
330 if (movingDist > 0.0)
332 for (int pNum = selection.getStart(); pNum <= selection.getEnd(); pNum++)
334 DataPoint p = _app.getTrackInfo().getTrack().getPoint(pNum);
335 if (p != null && !p.isWaypoint())
337 // If we're starting a new segment, calculate the height diff of the previous one
338 if (p.getSegmentStart())
340 if (firstAlt != null && firstAlt.isValid() && lastAlt != null && lastAlt.isValid())
341 movingHeightDiff = movingHeightDiff + lastAlt.getMetricValue() - firstAlt.getMetricValue();
342 firstAlt = null; lastAlt = null;
344 Altitude alt = p.getAltitude();
345 if (alt != null && alt.isValid())
347 if (firstAlt == null) firstAlt = alt;
349 if (veryFirstAlt == null) veryFirstAlt = alt;
350 else veryLastAlt = alt;
352 // Keep track of climb and descent too
353 if (p.getSegmentStart())
354 altRange.ignoreValue(alt);
356 altRange.addValue(alt);
359 // deal with last segment
360 if (firstAlt != null && firstAlt.isValid() && lastAlt != null && lastAlt.isValid())
361 movingHeightDiff = movingHeightDiff + lastAlt.getMetricValue() - firstAlt.getMetricValue();
362 final double metricMovingDist = movingDist / distUnit.getMultFactorFromStd(); // convert back to metres
363 final double gradient = movingHeightDiff * 100.0 / metricMovingDist;
364 _movingGradientLabel.setText(FORMAT_ONE_DP.format(gradient) + " %");
366 if (!altRange.hasRange()) {
367 _movingGradientLabel.setText("");
369 final boolean hasAltitudes = veryFirstAlt != null && veryFirstAlt.isValid() && veryLastAlt != null && veryLastAlt.isValid();
372 final double metreDist = selection.getDistance() / distUnit.getMultFactorFromStd(); // convert back to metres
373 if (hasAltitudes && metreDist > 0.0)
375 // got an altitude and range
376 int altDiffInMetres = veryLastAlt.getValue(Altitude.Format.METRES) - veryFirstAlt.getValue(Altitude.Format.METRES);
377 double gradient = altDiffInMetres * 100.0 / metreDist;
378 _totalGradientLabel.setText(FORMAT_ONE_DP.format(gradient) + " %");
382 _totalGradientLabel.setText("");
385 // Moving climb/descent
386 if (altRange.hasRange()) {
387 _movingClimbLabel.setText(altRange.getClimb(altUnit) + altUnitsStr);
388 _movingDescentLabel.setText(altRange.getDescent(altUnit) + altUnitsStr);
391 _movingClimbLabel.setText("");
392 _movingDescentLabel.setText("");
395 SpeedData speeds = new SpeedData(_app.getTrackInfo().getTrack());
396 speeds.init(Config.getUnitSet());
397 double maxSpeed = 0.0;
398 for (int i=selection.getStart(); i<=selection.getEnd(); i++)
400 if (speeds.hasData(i) && (speeds.getData(i) > maxSpeed)) {
401 maxSpeed = speeds.getData(i);
404 if (maxSpeed > 0.0) {
405 _maxSpeedLabel.setText(roundedNumber(maxSpeed) + " " + speedUnitsStr);
408 _maxSpeedLabel.setText("");
412 final String vertSpeedUnitsStr = I18nManager.getText(Config.getUnitSet().getVerticalSpeedUnit().getShortnameKey());
413 if (hasAltitudes && metreDist > 0.0 && numSecs > 0)
415 // got an altitude and time - do total
416 final int altDiffInMetres = veryLastAlt.getValue(Altitude.Format.METRES) - veryFirstAlt.getValue(Altitude.Format.METRES);
417 final double altDiff = altDiffInMetres * altUnit.getMultFactorFromStd();
418 _totalVertSpeedLabel.setText(roundedNumber(altDiff/numSecs) + " " + vertSpeedUnitsStr);
420 _movingVertSpeedLabel.setText(roundedNumber(movingHeightDiff * altUnit.getMultFactorFromStd() / numMovingSecs) + " " + vertSpeedUnitsStr);
423 // no vertical speed available
424 _totalVertSpeedLabel.setText("");
425 _movingVertSpeedLabel.setText("");
430 * Format a number to a sensible precision
431 * @param inDist distance
432 * @return formatted String
434 private String roundedNumber(double inDist)
436 // Set precision of formatter
440 else if (inDist < 10.0)
442 else if (inDist < 100.0)
445 _distanceFormatter.setMaximumFractionDigits(numDigits);
446 _distanceFormatter.setMinimumFractionDigits(numDigits);
447 return _distanceFormatter.format(inDist);