+package tim.prune.data;
+
+import tim.prune.DataSubscriber;
+import tim.prune.UpdateMessageBroker;
+
+/**
+ * Class to represent a selected portion of a Track
+ * and its properties
+ */
+public class Selection
+{
+ private Track _track = null;
+ private int _currentPoint = -1;
+ private boolean _valid = false;
+ private int _prevNumPoints = 0;
+ private int _startIndex = -1, _endIndex = -1;
+ private int _currentPhotoIndex = -1;
+ private int _currentAudioIndex = -1;
+ private AltitudeRange _altitudeRange = null;
+ private long _movingMilliseconds = 0L;
+ private double _angMovingDistance = -1.0;
+
+
+ /**
+ * Constructor
+ * @param inTrack track object
+ */
+ public Selection(Track inTrack)
+ {
+ _track = inTrack;
+ }
+
+
+ /**
+ * Mark selection invalid so it will be recalculated
+ */
+ public void markInvalid()
+ {
+ _valid = false;
+ }
+
+
+ /**
+ * @return the current point index
+ */
+ public int getCurrentPointIndex()
+ {
+ return _currentPoint;
+ }
+
+
+ /**
+ * @return true if range is selected
+ */
+ public boolean hasRangeSelected()
+ {
+ return _startIndex >= 0 && _endIndex > _startIndex;
+ }
+
+
+ /**
+ * Recalculate all selection details
+ */
+ private void recalculate()
+ {
+ final int numPoints = _track.getNumPoints();
+ // Recheck if the number of points has changed
+ if (numPoints != _prevNumPoints)
+ {
+ _prevNumPoints = numPoints;
+ check();
+ }
+ if (numPoints > 0 && hasRangeSelected())
+ {
+ _altitudeRange = new AltitudeRange();
+ Altitude altitude = null;
+ Timestamp time = null, previousTime = null;
+ DataPoint lastPoint = null, currPoint = null;
+ _angMovingDistance = 0.0;
+ _movingMilliseconds = 0L;
+ // Loop over points in selection
+ for (int i=_startIndex; i<=_endIndex; i++)
+ {
+ currPoint = _track.getPoint(i);
+ altitude = currPoint.getAltitude();
+ // Ignore waypoints in altitude calculations
+ if (!currPoint.isWaypoint() && altitude.isValid())
+ {
+ if (currPoint.getSegmentStart()) {
+ _altitudeRange.ignoreValue(altitude);
+ }
+ else {
+ _altitudeRange.addValue(altitude);
+ }
+ }
+ // Compare timestamps within the segments
+ time = currPoint.getTimestamp();
+ if (time.isValid())
+ {
+ // add moving time
+ if (!currPoint.getSegmentStart() && previousTime != null && time.isAfter(previousTime)) {
+ _movingMilliseconds += time.getMillisecondsSince(previousTime);
+ }
+ previousTime = time;
+ }
+ // Calculate distances, again ignoring waypoints
+ if (!currPoint.isWaypoint())
+ {
+ if (lastPoint != null)
+ {
+ double radians = DataPoint.calculateRadiansBetween(lastPoint, currPoint);
+ if (!currPoint.getSegmentStart()) {
+ _angMovingDistance += radians;
+ }
+ }
+ lastPoint = currPoint;
+ }
+ }
+ }
+ _valid = true;
+ }
+
+
+ /**
+ * @return start index
+ */
+ public int getStart()
+ {
+ if (!_valid) recalculate();
+ return _startIndex;
+ }
+
+
+ /**
+ * @return end index
+ */
+ public int getEnd()
+ {
+ if (!_valid) recalculate();
+ return _endIndex;
+ }
+
+ /**
+ * @return altitude range
+ */
+ public AltitudeRange getAltitudeRange()
+ {
+ if (!_valid) recalculate();
+ return _altitudeRange;
+ }
+
+
+ /**
+ * @return number of seconds spanned by segments within selection
+ */
+ public long getMovingSeconds()
+ {
+ if (!_valid) recalculate();
+ return _movingMilliseconds / 1000L;
+ }
+
+ /**
+ * @return moving distance of Selection in current units
+ */
+ public double getMovingDistance()
+ {
+ return Distance.convertRadiansToDistance(_angMovingDistance);
+ }
+
+ /**
+ * Clear selected point, range, photo and audio
+ */
+ public void clearAll()
+ {
+ _currentPoint = -1;
+ selectRange(-1, -1);
+ _currentPhotoIndex = -1;
+ _currentAudioIndex = -1;
+ check();
+ }
+
+
+ /**
+ * Select range from start to end
+ * @param inStartIndex index of start of range
+ * @param inEndIndex index of end of range
+ */
+ public void selectRange(int inStartIndex, int inEndIndex)
+ {
+ _startIndex = inStartIndex;
+ _endIndex = inEndIndex;
+ markInvalid();
+ check();
+ }
+
+
+ /**
+ * Select the range from the current point
+ */
+ public void selectRangeStart()
+ {
+ selectRangeStart(_currentPoint);
+ }
+
+
+ /**
+ * Set the index for the start of the range selection
+ * @param inStartIndex start index
+ */
+ private void selectRangeStart(int inStartIndex)
+ {
+ if (inStartIndex < 0)
+ {
+ _startIndex = _endIndex = -1;
+ }
+ else
+ {
+ _startIndex = inStartIndex;
+ // Move end of selection to max if necessary
+ if (_endIndex <= _startIndex)
+ {
+ _endIndex = _track.getNumPoints() - 1;
+ }
+ }
+ markInvalid();
+ UpdateMessageBroker.informSubscribers();
+ }
+
+
+ /**
+ * Select the range up to the current point
+ */
+ public void selectRangeEnd()
+ {
+ selectRangeEnd(_currentPoint);
+ }
+
+
+ /**
+ * Set the index for the end of the range selection
+ * @param inEndIndex end index
+ */
+ public void selectRangeEnd(int inEndIndex)
+ {
+ if (inEndIndex < 0)
+ {
+ _startIndex = _endIndex = -1;
+ }
+ else
+ {
+ _endIndex = inEndIndex;
+ // Move start of selection to min if necessary
+ if (_startIndex > _endIndex || _startIndex < 0) {
+ _startIndex = 0;
+ }
+ }
+ markInvalid();
+ UpdateMessageBroker.informSubscribers();
+ }
+
+
+ /**
+ * Modify the selection given that the selected range has been deleted
+ */
+ public void modifyRangeDeleted()
+ {
+ // Modify current point, if any
+ if (_currentPoint > _endIndex)
+ {
+ _currentPoint -= (_endIndex - _startIndex);
+ }
+ else if (_currentPoint > _startIndex)
+ {
+ _currentPoint = _startIndex;
+ }
+ // Clear selected range
+ _startIndex = _endIndex = -1;
+ // Check for consistency and fire update
+ check();
+ }
+
+
+ /**
+ * Modify the selection when a point is deleted
+ */
+ public void modifyPointDeleted()
+ {
+ // current point index doesn't change, just gets checked
+ // range needs to get altered if deleted point is inside or before
+ if (hasRangeSelected() && _currentPoint <= _endIndex)
+ {
+ _endIndex--;
+ if (_currentPoint < _startIndex)
+ _startIndex--;
+ markInvalid();
+ }
+ check();
+ }
+
+
+ /**
+ * Select the specified photo and point
+ * @param inPointIndex index of selected point
+ * @param inPhotoIndex index of selected photo in PhotoList
+ * @param inAudioIndex index of selected audio item
+ */
+ public void selectPointPhotoAudio(int inPointIndex, int inPhotoIndex, int inAudioIndex)
+ {
+ _currentPoint = inPointIndex;
+ _currentPhotoIndex = inPhotoIndex;
+ _currentAudioIndex = inAudioIndex;
+ check();
+ }
+
+
+ /**
+ * @return currently selected photo index
+ */
+ public int getCurrentPhotoIndex()
+ {
+ return _currentPhotoIndex;
+ }
+
+ /**
+ * @return currently selected audio index
+ */
+ public int getCurrentAudioIndex()
+ {
+ return _currentAudioIndex;
+ }
+
+ /**
+ * Check that the selection still makes sense
+ * and fire update message to listeners
+ */
+ private void check()
+ {
+ if (_track != null && _track.getNumPoints() > 0)
+ {
+ int maxIndex = _track.getNumPoints() - 1;
+ if (_currentPoint > maxIndex)
+ {
+ _currentPoint = maxIndex;
+ }
+ if (_endIndex > maxIndex)
+ {
+ _endIndex = maxIndex;
+ }
+ if (_startIndex > maxIndex)
+ {
+ _startIndex = maxIndex;
+ }
+ }
+ else
+ {
+ // track is empty, clear selections
+ _currentPoint = _startIndex = _endIndex = -1;
+ }
+ UpdateMessageBroker.informSubscribers(DataSubscriber.SELECTION_CHANGED);
+ }
+}