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.AdjustmentEvent;
11 import java.awt.event.AdjustmentListener;
12 import java.text.NumberFormat;
14 import javax.swing.BorderFactory;
15 import javax.swing.Box;
16 import javax.swing.BoxLayout;
17 import javax.swing.JButton;
18 import javax.swing.JComboBox;
19 import javax.swing.JLabel;
20 import javax.swing.JList;
21 import javax.swing.JPanel;
22 import javax.swing.JScrollBar;
23 import javax.swing.JScrollPane;
24 import javax.swing.JTabbedPane;
25 import javax.swing.border.EtchedBorder;
26 import javax.swing.event.ListSelectionEvent;
27 import javax.swing.event.ListSelectionListener;
30 import tim.prune.DataSubscriber;
31 import tim.prune.I18nManager;
32 import tim.prune.data.Altitude;
33 import tim.prune.data.Coordinate;
34 import tim.prune.data.DataPoint;
35 import tim.prune.data.Distance;
36 import tim.prune.data.IntegerRange;
37 import tim.prune.data.Selection;
38 import tim.prune.data.TrackInfo;
41 * Class to hold point details and selection details
42 * as a visual component
44 public class DetailsDisplay extends GenericDisplay
46 // App object to be notified of editing commands
47 private App _app = null;
50 private JLabel _trackpointsLabel = null;
51 private JLabel _filenameLabel = null;
53 private JLabel _indexLabel = null;
54 private JLabel _latLabel = null, _longLabel = null;
55 private JLabel _altLabel = null, _nameLabel = null;
56 private JLabel _timeLabel = null, _photoFileLabel = null;
58 private JScrollBar _scroller = null;
59 private boolean _ignoreScrollEvents = false;
61 private JButton _startRangeButton = null, _endRangeButton = null;
62 private JButton _deletePointButton = null, _deleteRangeButton = null;
65 private JLabel _rangeLabel = null;
66 private JLabel _distanceLabel = null, _durationLabel = null;
67 private JLabel _altRangeLabel = null, _updownLabel = null;
69 private JList _photoList = null;
70 private PhotoListModel _photoListModel = null;
72 private JList _waypointList = null;
73 private WaypointListModel _waypointListModel = null;
75 private JComboBox _unitsDropdown = null;
77 private NumberFormat _distanceFormatter = NumberFormat.getInstance();
80 private static final String LABEL_POINT_SELECTED1 = I18nManager.getText("details.index.selected") + ": ";
81 private static final String LABEL_POINT_LATITUDE = I18nManager.getText("fieldname.latitude") + ": ";
82 private static final String LABEL_POINT_LONGITUDE = I18nManager.getText("fieldname.longitude") + ": ";
83 private static final String LABEL_POINT_ALTITUDE = I18nManager.getText("fieldname.altitude") + ": ";
84 private static final String LABEL_POINT_TIMESTAMP = I18nManager.getText("fieldname.timestamp") + ": ";
85 private static final String LABEL_POINT_WAYPOINTNAME = I18nManager.getText("fieldname.waypointname") + ": ";
86 private static final String LABEL_RANGE_SELECTED1 = I18nManager.getText("details.range.selected") + ": ";
87 private static final String LABEL_RANGE_DURATION = I18nManager.getText("fieldname.duration") + ": ";
88 private static final String LABEL_RANGE_DISTANCE = I18nManager.getText("fieldname.distance") + ": ";
89 private static final String LABEL_RANGE_ALTITUDE = I18nManager.getText("fieldname.altitude") + ": ";
90 private static final String LABEL_RANGE_CLIMB = I18nManager.getText("details.range.climb") + ": ";
91 private static final String LABEL_RANGE_DESCENT = ", " + I18nManager.getText("details.range.descent") + ": ";
92 private static String LABEL_POINT_ALTITUDE_UNITS = null;
93 private static int LABEL_POINT_ALTITUDE_FORMAT = Altitude.FORMAT_NONE;
95 private static final int SCROLLBAR_INTERVAL = 50;
100 * @param inApp App object for callbacks
101 * @param inTrackInfo Track info object
103 public DetailsDisplay(App inApp, TrackInfo inTrackInfo)
107 setLayout(new BorderLayout());
109 JPanel mainPanel = new JPanel();
110 mainPanel.setLayout(new BoxLayout(mainPanel, BoxLayout.Y_AXIS));
111 mainPanel.setBorder(BorderFactory.createEmptyBorder(3, 3, 3, 3));
112 // Track details panel
113 JPanel trackDetailsPanel = new JPanel();
114 trackDetailsPanel.setLayout(new BoxLayout(trackDetailsPanel, BoxLayout.Y_AXIS));
115 trackDetailsPanel.setBorder(BorderFactory.createCompoundBorder(
116 BorderFactory.createEtchedBorder(EtchedBorder.LOWERED), BorderFactory.createEmptyBorder(3, 3, 3, 3))
118 JLabel trackDetailsLabel = new JLabel(I18nManager.getText("details.trackdetails"));
119 Font biggerFont = trackDetailsLabel.getFont();
120 biggerFont = biggerFont.deriveFont(Font.BOLD, biggerFont.getSize2D() + 2.0f);
121 trackDetailsLabel.setFont(biggerFont);
122 trackDetailsPanel.add(trackDetailsLabel);
123 _trackpointsLabel = new JLabel(I18nManager.getText("details.notrack"));
124 trackDetailsPanel.add(_trackpointsLabel);
125 _filenameLabel = new JLabel("");
126 trackDetailsPanel.add(_filenameLabel);
128 // Point details panel
129 JPanel pointDetailsPanel = new JPanel();
130 pointDetailsPanel.setLayout(new BoxLayout(pointDetailsPanel, BoxLayout.Y_AXIS));
131 pointDetailsPanel.setBorder(BorderFactory.createCompoundBorder(
132 BorderFactory.createEtchedBorder(EtchedBorder.LOWERED), BorderFactory.createEmptyBorder(3, 3, 3, 3))
134 JLabel pointDetailsLabel = new JLabel(I18nManager.getText("details.pointdetails"));
135 pointDetailsLabel.setFont(biggerFont);
136 pointDetailsPanel.add(pointDetailsLabel);
137 _indexLabel = new JLabel(I18nManager.getText("details.nopointselection"));
138 pointDetailsPanel.add(_indexLabel);
139 _latLabel = new JLabel("");
140 pointDetailsPanel.add(_latLabel);
141 _longLabel = new JLabel("");
142 pointDetailsPanel.add(_longLabel);
143 _altLabel = new JLabel("");
144 pointDetailsPanel.add(_altLabel);
145 _timeLabel = new JLabel("");
146 pointDetailsPanel.add(_timeLabel);
147 _photoFileLabel = new JLabel("");
148 pointDetailsPanel.add(_photoFileLabel);
149 _nameLabel = new JLabel("");
150 pointDetailsPanel.add(_nameLabel);
151 pointDetailsPanel.setAlignmentX(Component.LEFT_ALIGNMENT);
154 _scroller = new JScrollBar(JScrollBar.HORIZONTAL, 0, SCROLLBAR_INTERVAL, 0, 100);
155 _scroller.addAdjustmentListener(new AdjustmentListener() {
156 public void adjustmentValueChanged(AdjustmentEvent e)
158 selectPoint(e.getValue());
161 _scroller.setEnabled(false);
164 JPanel buttonPanel = new JPanel();
165 buttonPanel.setLayout(new GridLayout(2, 2, 3, 3));
166 _startRangeButton = new JButton(I18nManager.getText("button.startrange"));
167 _startRangeButton.addActionListener(new ActionListener()
169 public void actionPerformed(ActionEvent e)
171 _trackInfo.getSelection().selectRangeStart();
174 _startRangeButton.setEnabled(false);
175 buttonPanel.add(_startRangeButton);
176 _endRangeButton = new JButton(I18nManager.getText("button.endrange"));
177 _endRangeButton.addActionListener(new ActionListener()
179 public void actionPerformed(ActionEvent e)
181 _trackInfo.getSelection().selectRangeEnd();
184 _endRangeButton.setEnabled(false);
185 buttonPanel.add(_endRangeButton);
186 _deletePointButton = new JButton(I18nManager.getText("button.deletepoint"));
187 _deletePointButton.addActionListener(new ActionListener()
189 public void actionPerformed(ActionEvent e)
191 _app.deleteCurrentPoint();
194 _deletePointButton.setEnabled(false);
195 buttonPanel.add(_deletePointButton);
196 _deleteRangeButton = new JButton(I18nManager.getText("button.deleterange"));
197 _deleteRangeButton.addActionListener(new ActionListener()
199 public void actionPerformed(ActionEvent e)
201 _app.deleteSelectedRange();
204 _deleteRangeButton.setEnabled(false);
205 buttonPanel.add(_deleteRangeButton);
206 buttonPanel.setAlignmentX(Component.LEFT_ALIGNMENT);
208 // range details panel
209 JPanel otherDetailsPanel = new JPanel();
210 otherDetailsPanel.setLayout(new BoxLayout(otherDetailsPanel, BoxLayout.Y_AXIS));
211 otherDetailsPanel.setBorder(BorderFactory.createCompoundBorder(
212 BorderFactory.createEtchedBorder(EtchedBorder.LOWERED), BorderFactory.createEmptyBorder(3, 3, 3, 3))
215 JLabel otherDetailsLabel = new JLabel(I18nManager.getText("details.rangedetails"));
216 otherDetailsLabel.setFont(biggerFont);
217 otherDetailsPanel.add(otherDetailsLabel);
218 _rangeLabel = new JLabel(I18nManager.getText("details.norangeselection"));
219 otherDetailsPanel.add(_rangeLabel);
220 _distanceLabel = new JLabel("");
221 otherDetailsPanel.add(_distanceLabel);
222 _durationLabel = new JLabel("");
223 otherDetailsPanel.add(_durationLabel);
224 _altRangeLabel = new JLabel("");
225 otherDetailsPanel.add(_altRangeLabel);
226 _updownLabel = new JLabel("");
227 otherDetailsPanel.add(_updownLabel);
228 otherDetailsPanel.setAlignmentX(Component.LEFT_ALIGNMENT);
230 // Add tab panel for waypoints / photos
231 JPanel waypointsPanel = new JPanel();
232 waypointsPanel.setLayout(new BoxLayout(waypointsPanel, BoxLayout.Y_AXIS));
233 waypointsPanel.setBorder(BorderFactory.createCompoundBorder(
234 BorderFactory.createEtchedBorder(EtchedBorder.LOWERED), BorderFactory.createEmptyBorder(3, 3, 3, 3))
236 JTabbedPane tabPane = new JTabbedPane();
237 _waypointListModel = new WaypointListModel(_trackInfo.getTrack());
238 _waypointList = new JList(_waypointListModel);
239 _waypointList.setVisibleRowCount(5);
240 _waypointList.addListSelectionListener(new ListSelectionListener() {
241 public void valueChanged(ListSelectionEvent e)
243 if (!e.getValueIsAdjusting()) selectWaypoint(_waypointList.getSelectedIndex());
245 tabPane.addTab(I18nManager.getText("details.waypointsphotos.waypoints"), new JScrollPane(_waypointList));
246 _photoListModel = new PhotoListModel(_trackInfo.getPhotoList());
247 _photoList = new JList(_photoListModel);
248 _photoList.setVisibleRowCount(5);
249 _photoList.addListSelectionListener(new ListSelectionListener() {
250 public void valueChanged(ListSelectionEvent e)
252 if (!e.getValueIsAdjusting()) selectPhoto(_photoList.getSelectedIndex());
254 // TODO: Re-add photos list after v2
255 // tabPane.addTab(I18nManager.getText("details.waypointsphotos.photos"), new JScrollPane(_photoList));
256 tabPane.setAlignmentX(Component.LEFT_ALIGNMENT);
257 waypointsPanel.add(tabPane);
258 waypointsPanel.setAlignmentX(Component.LEFT_ALIGNMENT);
260 // add the slider, point details, and the other details to the main panel
261 mainPanel.add(buttonPanel);
262 mainPanel.add(Box.createVerticalStrut(5));
263 mainPanel.add(_scroller);
264 mainPanel.add(Box.createVerticalStrut(5));
265 mainPanel.add(trackDetailsPanel);
266 mainPanel.add(Box.createVerticalStrut(5));
267 mainPanel.add(pointDetailsPanel);
268 mainPanel.add(Box.createVerticalStrut(5));
269 mainPanel.add(otherDetailsPanel);
270 mainPanel.add(Box.createVerticalStrut(5));
271 mainPanel.add(waypointsPanel);
272 // add the main panel at the top
273 add(mainPanel, BorderLayout.NORTH);
275 // Add units selection
276 JPanel lowerPanel = new JPanel();
277 lowerPanel.setLayout(new FlowLayout(FlowLayout.LEFT));
278 lowerPanel.add(new JLabel(I18nManager.getText("details.distanceunits") + ": "));
279 String[] distUnits = {I18nManager.getText("units.kilometres"), I18nManager.getText("units.miles")};
280 _unitsDropdown = new JComboBox(distUnits);
281 _unitsDropdown.addActionListener(new ActionListener() {
282 public void actionPerformed(ActionEvent e)
284 dataUpdated(DataSubscriber.UNITS_CHANGED);
287 lowerPanel.add(_unitsDropdown);
288 add(lowerPanel, BorderLayout.SOUTH);
293 * Select the specified point
294 * @param inValue value to select
296 private void selectPoint(int inValue)
298 if (_track != null && !_ignoreScrollEvents)
300 _trackInfo.getSelection().selectPoint(inValue);
306 * Select the specified photo
307 * @param inPhotoIndex index of selected photo
309 private void selectPhoto(int inPhotoIndex)
311 if (_photoListModel.getPhoto(inPhotoIndex) != null)
313 // TODO: Deselect the photo when another point is selected
314 // TODO: show photo thumbnail
315 // select associated point, if any
316 DataPoint point = _photoListModel.getPhoto(inPhotoIndex).getDataPoint();
319 _trackInfo.selectPoint(point);
326 * Select the specified waypoint
327 * @param inWaypointIndex index of selected waypoint
329 private void selectWaypoint(int inWaypointIndex)
331 if (inWaypointIndex >= 0)
333 _trackInfo.selectPoint(_waypointListModel.getWaypoint(inWaypointIndex));
339 * Notification that Track has been updated
341 public void dataUpdated(byte inUpdateType)
344 if (_track == null || _track.getNumPoints() <= 0)
346 _trackpointsLabel.setText(I18nManager.getText("details.notrack"));
347 _filenameLabel.setText("");
351 _trackpointsLabel.setText(I18nManager.getText("details.track.points") + ": "
352 + _track.getNumPoints());
353 int numFiles = _trackInfo.getFileInfo().getNumFiles();
356 _filenameLabel.setText(I18nManager.getText("details.track.file") + ": "
357 + _trackInfo.getFileInfo().getFilename());
359 else if (numFiles > 1)
361 _filenameLabel.setText(I18nManager.getText("details.track.numfiles") + ": "
364 else _filenameLabel.setText("");
367 // Update current point data, if any
368 DataPoint currentPoint = _trackInfo.getCurrentPoint();
369 Selection selection = _trackInfo.getSelection();
370 int currentPointIndex = selection.getCurrentPointIndex();
371 if (_track == null || currentPoint == null)
373 _indexLabel.setText(I18nManager.getText("details.nopointselection"));
374 _latLabel.setText("");
375 _longLabel.setText("");
376 _altLabel.setText("");
377 _timeLabel.setText("");
378 _photoFileLabel.setText("");
379 _nameLabel.setText("");
383 _indexLabel.setText(LABEL_POINT_SELECTED1
384 + (currentPointIndex+1) + " " + I18nManager.getText("details.index.of")
385 + " " + _track.getNumPoints());
386 _latLabel.setText(LABEL_POINT_LATITUDE + currentPoint.getLatitude().output(Coordinate.FORMAT_NONE));
387 _longLabel.setText(LABEL_POINT_LONGITUDE + currentPoint.getLongitude().output(Coordinate.FORMAT_NONE));
388 _altLabel.setText(LABEL_POINT_ALTITUDE
389 + (currentPoint.hasAltitude()?
390 (currentPoint.getAltitude().getValue() + getAltitudeUnitsLabel(currentPoint.getAltitude().getFormat())):
392 if (currentPoint.getTimestamp().isValid())
393 _timeLabel.setText(LABEL_POINT_TIMESTAMP + currentPoint.getTimestamp().getText());
395 _timeLabel.setText("");
396 if (currentPoint.getPhoto() != null && currentPoint.getPhoto().getFile() != null)
398 _photoFileLabel.setText(I18nManager.getText("details.photofile") + ": "
399 + currentPoint.getPhoto().getFile().getName());
402 _photoFileLabel.setText("");
403 String name = currentPoint.getWaypointName();
404 if (name != null && !name.equals(""))
406 _nameLabel.setText(LABEL_POINT_WAYPOINTNAME + name);
408 else _nameLabel.setText("");
411 // Update scroller settings
412 _ignoreScrollEvents = true;
413 if (_track == null || _track.getNumPoints() < 2)
415 // careful to avoid event loops here
416 // _scroller.setValue(0);
417 _scroller.setEnabled(false);
421 _scroller.setMaximum(_track.getNumPoints() + SCROLLBAR_INTERVAL);
422 if (currentPointIndex >= 0)
423 _scroller.setValue(currentPointIndex);
424 _scroller.setEnabled(true);
426 _ignoreScrollEvents = false;
428 // Update button panel
429 boolean hasPoint = (_track != null && currentPointIndex >= 0);
430 _startRangeButton.setEnabled(hasPoint);
431 _endRangeButton.setEnabled(hasPoint);
432 _deletePointButton.setEnabled(hasPoint);
433 _deleteRangeButton.setEnabled(selection.hasRangeSelected());
435 // Update range details
436 if (_track == null || !selection.hasRangeSelected())
438 _rangeLabel.setText(I18nManager.getText("details.norangeselection"));
439 _distanceLabel.setText("");
440 _durationLabel.setText("");
441 _altRangeLabel.setText("");
442 _updownLabel.setText("");
446 _rangeLabel.setText(LABEL_RANGE_SELECTED1
447 + (selection.getStart()+1) + " " + I18nManager.getText("details.range.to")
448 + " " + (selection.getEnd()+1));
449 if (_unitsDropdown.getSelectedIndex() == 0)
450 _distanceLabel.setText(LABEL_RANGE_DISTANCE + buildDistanceString(
451 selection.getDistance(Distance.UNITS_KILOMETRES))
452 + " " + I18nManager.getText("units.kilometres.short"));
454 _distanceLabel.setText(LABEL_RANGE_DISTANCE + buildDistanceString(
455 selection.getDistance(Distance.UNITS_MILES))
456 + " " + I18nManager.getText("units.miles.short"));
457 if (selection.getNumSeconds() > 0)
458 _durationLabel.setText(LABEL_RANGE_DURATION + buildDurationString(selection.getNumSeconds()));
460 _durationLabel.setText("");
461 String altUnitsLabel = getAltitudeUnitsLabel(selection.getAltitudeFormat());
462 IntegerRange altRange = selection.getAltitudeRange();
463 if (altRange.getMinimum() >= 0 && altRange.getMaximum() >= 0)
465 _altRangeLabel.setText(LABEL_RANGE_ALTITUDE
466 + altRange.getMinimum() + altUnitsLabel + " "
467 + I18nManager.getText("details.altitude.to") + " "
468 + altRange.getMaximum() + altUnitsLabel);
469 _updownLabel.setText(LABEL_RANGE_CLIMB + selection.getClimb() + altUnitsLabel
470 + LABEL_RANGE_DESCENT + selection.getDescent() + altUnitsLabel);
474 _altRangeLabel.setText("");
475 _updownLabel.setText("");
478 // update waypoints and photos if necessary
480 (DataSubscriber.DATA_ADDED_OR_REMOVED | DataSubscriber.DATA_EDITED | DataSubscriber.WAYPOINTS_MODIFIED)) > 0)
482 _waypointListModel.fireChanged();
485 (DataSubscriber.DATA_ADDED_OR_REMOVED | DataSubscriber.DATA_EDITED | DataSubscriber.PHOTOS_MODIFIED)) > 0)
487 _photoListModel.fireChanged();
489 // Deselect selected waypoint if selected point has since changed
490 if (_waypointList.getSelectedIndex() >= 0)
492 if (_trackInfo.getCurrentPoint() == null
493 || !_waypointListModel.getWaypoint(_waypointList.getSelectedIndex()).equals(_trackInfo.getCurrentPoint()))
495 // point is selected in list but different from current point - deselect
496 _waypointList.clearSelection();
499 // Do the same for the photos
500 if (_photoList.getSelectedIndex() >= 0)
502 if (_trackInfo.getCurrentPoint() == null
503 || !_photoListModel.getPhoto(_photoList.getSelectedIndex()).getDataPoint().equals(_trackInfo.getCurrentPoint()))
505 // photo is selected in list but different from current point - deselect
506 _photoList.clearSelection();
513 * Choose the appropriate altitude units label for the specified format
514 * @param inFormat altitude format
515 * @return language-sensitive string
517 private static String getAltitudeUnitsLabel(int inFormat)
519 if (inFormat == LABEL_POINT_ALTITUDE_FORMAT && LABEL_POINT_ALTITUDE_UNITS != null)
520 return LABEL_POINT_ALTITUDE_UNITS;
521 LABEL_POINT_ALTITUDE_FORMAT = inFormat;
522 if (inFormat == Altitude.FORMAT_METRES)
523 return " " + I18nManager.getText("units.metres.short");
524 return " " + I18nManager.getText("units.feet.short");
529 * Build a String to describe a time duration
530 * @param inNumSecs number of seconds
531 * @return time as a string, days, hours, mins, secs as appropriate
533 private static String buildDurationString(long inNumSecs)
535 if (inNumSecs <= 0L) return "";
536 if (inNumSecs < 60L) return "" + inNumSecs + I18nManager.getText("display.range.time.secs");
537 if (inNumSecs < 3600L) return "" + (inNumSecs / 60) + I18nManager.getText("display.range.time.mins")
538 + " " + (inNumSecs % 60) + I18nManager.getText("display.range.time.secs");
539 if (inNumSecs < 86400L) return "" + (inNumSecs / 60 / 60) + I18nManager.getText("display.range.time.hours")
540 + " " + ((inNumSecs / 60) % 60) + I18nManager.getText("display.range.time.mins");
541 if (inNumSecs < 8640000L) return "" + (inNumSecs / 86400L) + I18nManager.getText("display.range.time.days");
547 * Build a String to describe a distance
548 * @param inDist distance
549 * @return formatted String
551 private String buildDistanceString(double inDist)
553 // Set precision of formatter
557 else if (inDist < 10.0)
559 else if (inDist < 100.0)
562 _distanceFormatter.setMaximumFractionDigits(numDigits);
563 _distanceFormatter.setMinimumFractionDigits(numDigits);
564 return _distanceFormatter.format(inDist);