+package tim.prune.function.autoplay;
+
+import java.awt.Component;
+import java.awt.FlowLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+import java.util.Iterator;
+import java.util.TreeSet;
+
+import javax.swing.BoxLayout;
+import javax.swing.JButton;
+import javax.swing.JCheckBox;
+import javax.swing.JDialog;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+
+import tim.prune.App;
+import tim.prune.GenericFunction;
+import tim.prune.I18nManager;
+import tim.prune.data.Field;
+import tim.prune.data.Timestamp;
+import tim.prune.data.Track;
+import tim.prune.gui.GuiGridLayout;
+import tim.prune.gui.IconManager;
+import tim.prune.gui.WholeNumberField;
+
+/**
+ * Function to handle the autoplay of a track
+ */
+public class AutoplayFunction extends GenericFunction implements Runnable
+{
+ /** Dialog */
+ private JDialog _dialog = null;
+ /** Entry field for number of seconds to autoplay for */
+ private WholeNumberField _durationField = null;
+ /** Checkbox for using point timestamps */
+ private JCheckBox _useTimestampsCheckbox = null;
+ /** Buttons for controlling autoplay */
+ private JButton _rewindButton = null, _pauseButton = null, _playButton = null;
+ /** Flag for recalculating all the times */
+ private boolean _needToRecalculate = true;
+ /** Point list */
+ private PointList _pointList = null;
+ /** Flag to see if we're still running or not */
+ private boolean _running = false;
+ /** Remember the time we started playing */
+ private long _startTime = 0L;
+
+
+ /**
+ * Constructor
+ * @param inApp App object
+ */
+ public AutoplayFunction(App inApp) {
+ super(inApp);
+ }
+
+ @Override
+ public String getNameKey() {
+ return "function.autoplay";
+ }
+
+ /**
+ * Begin the function
+ */
+ public void begin()
+ {
+ if (_dialog == null)
+ {
+ _dialog = new JDialog(_parentFrame, I18nManager.getText(getNameKey()), true);
+ _dialog.setLocationRelativeTo(_parentFrame);
+ _dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
+ _dialog.getContentPane().add(makeDialogComponents());
+ _dialog.pack();
+ _dialog.setResizable(false);
+ _dialog.addWindowListener(new WindowAdapter() {
+ public void windowClosing(WindowEvent e) {
+ _running = false;
+ super.windowClosing(e);
+ }
+ });
+ }
+ // Don't select any point
+ _app.getTrackInfo().selectPoint(-1);
+ // enable buttons
+ enableButtons(false, true); // can't pause, can play
+ // MAYBE: reset duration if it's too long
+ // Disable point checkbox if there aren't any times
+ final boolean hasTimes = _app.getTrackInfo().getTrack().hasData(Field.TIMESTAMP);
+ _useTimestampsCheckbox.setEnabled(hasTimes);
+ if (!hasTimes)
+ {
+ _useTimestampsCheckbox.setSelected(false);
+ }
+
+ _needToRecalculate = true;
+ _dialog.setVisible(true);
+ }
+
+
+ /**
+ * Create dialog components
+ * @return Panel containing all gui elements in dialog
+ */
+ private Component makeDialogComponents()
+ {
+ JPanel dialogPanel = new JPanel();
+ dialogPanel.setLayout(new BoxLayout(dialogPanel, BoxLayout.Y_AXIS));
+ // Duration panel
+ JPanel durationPanel = new JPanel();
+ GuiGridLayout grid = new GuiGridLayout(durationPanel);
+ grid.add(new JLabel(I18nManager.getText("dialog.autoplay.duration") + " :"));
+ _durationField = new WholeNumberField(3);
+ _durationField.setValue(60); // default is one minute
+ _durationField.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ onParamsChanged();
+ }
+ });
+ grid.add(_durationField);
+ durationPanel.setAlignmentX(Component.CENTER_ALIGNMENT);
+ dialogPanel.add(durationPanel);
+ // Checkbox
+ _useTimestampsCheckbox = new JCheckBox(I18nManager.getText("dialog.autoplay.usetimestamps"));
+ _useTimestampsCheckbox.setAlignmentX(Component.CENTER_ALIGNMENT);
+ dialogPanel.add(_useTimestampsCheckbox);
+ _useTimestampsCheckbox.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ onParamsChanged();
+ }
+ });
+ // Button panel
+ JPanel buttonPanel = new JPanel();
+ buttonPanel.setLayout(new FlowLayout(FlowLayout.CENTER));
+ buttonPanel.add(_rewindButton = new JButton(IconManager.getImageIcon(IconManager.AUTOPLAY_REWIND)));
+ _rewindButton.setToolTipText(I18nManager.getText("dialog.autoplay.rewind"));
+ _rewindButton.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent arg0) {
+ onRewindPressed();
+ }
+ });
+ buttonPanel.add(_pauseButton = new JButton(IconManager.getImageIcon(IconManager.AUTOPLAY_PAUSE)));
+ _pauseButton.setToolTipText(I18nManager.getText("dialog.autoplay.pause"));
+ _pauseButton.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent arg0) {
+ onPausePressed();
+ }
+ });
+ buttonPanel.add(_playButton = new JButton(IconManager.getImageIcon(IconManager.AUTOPLAY_PLAY)));
+ _playButton.setToolTipText(I18nManager.getText("dialog.autoplay.play"));
+ _playButton.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent arg0) {
+ onPlayPressed();
+ }
+ });
+ buttonPanel.setAlignmentX(Component.CENTER_ALIGNMENT);
+ dialogPanel.add(buttonPanel);
+ return dialogPanel;
+ }
+
+ /**
+ * React to a change to either the duration or the checkbox
+ */
+ private void onParamsChanged()
+ {
+ onRewindPressed();
+ enableButtons(false, _durationField.getValue() > 0);
+ _needToRecalculate = true;
+ }
+
+ /**
+ * React to rewind button pressed - stop and go back to the first point
+ */
+ private void onRewindPressed()
+ {
+ //System.out.println("Rewind! Stop thread if playing");
+ _running = false;
+ if (_pointList != null)
+ {
+ _pointList.set(0);
+ _app.getTrackInfo().selectPoint(_pointList.getCurrentPointIndex());
+ }
+ }
+
+ /**
+ * React to pause button pressed - stop scrolling but maintain position
+ */
+ private void onPausePressed()
+ {
+ //System.out.println("Pause! Stop thread if playing");
+ _running = false;
+ enableButtons(false, true);
+ }
+
+ /**
+ * React to play button being pressed - either start or resume
+ */
+ private void onPlayPressed()
+ {
+ //System.out.println("Play!");
+ if (_needToRecalculate) {
+ recalculateTimes();
+ }
+ enableButtons(true, false);
+ if (_pointList.isAtStart() || _pointList.isFinished())
+ {
+ _pointList.set(0);
+ _startTime = System.currentTimeMillis();
+ }
+ else
+ {
+ // Get current millis from pointList, reset _startTime
+ _startTime = System.currentTimeMillis() - _pointList.getCurrentMilliseconds();
+ }
+ new Thread(this).start();
+ }
+
+ /**
+ * Recalculate the times using the dialog settings
+ */
+ private void recalculateTimes()
+ {
+ //System.out.println("Recalculate using params " + _durationField.getValue()
+ // + " and " + (_useTimestampsCheckbox.isSelected() ? "times" : "indexes"));
+ if (_useTimestampsCheckbox.isSelected()) {
+ _pointList = generatePointListUsingTimes(_app.getTrackInfo().getTrack(), _durationField.getValue());
+ }
+ else {
+ _pointList = generatePointListUsingIndexes(_app.getTrackInfo().getTrack().getNumPoints(), _durationField.getValue());
+ }
+ _needToRecalculate = false;
+ }
+
+ /**
+ * Enable and disable the pause and play buttons
+ * @param inCanPause true to enable pause button
+ * @param inCanPlay true to enable play button
+ */
+ private void enableButtons(boolean inCanPause, boolean inCanPlay)
+ {
+ _pauseButton.setEnabled(inCanPause);
+ _playButton.setEnabled(inCanPlay);
+ }
+
+ /**
+ * Generate a points list based just on the point timestamps
+ * (points without timestamps will be ignored)
+ * @param inTrack track object
+ * @param inDuration number of seconds to play
+ * @return PointList object
+ */
+ private static PointList generatePointListUsingTimes(Track inTrack, int inDuration)
+ {
+ // Make a Set of all the points with timestamps and sort them
+ TreeSet<PointInfo> set = new TreeSet<PointInfo>();
+ int numPoints = inTrack.getNumPoints();
+ for (int i=0; i<numPoints; i++)
+ {
+ PointInfo info = new PointInfo(inTrack.getPoint(i), i);
+ if (info.getTimestamp() != null) {
+ set.add(info);
+ }
+ }
+ // For each point, keep track of the time since the previous time
+ Timestamp previousTime = null;
+ long trackMillis = 0L;
+ // Copy info to point list
+ numPoints = set.size();
+ PointList list = new PointList(numPoints);
+ Iterator<PointInfo> it = set.iterator();
+ while (it.hasNext())
+ {
+ PointInfo info = it.next();
+ if (previousTime != null)
+ {
+ if (info.getSegmentFlag()) {
+ trackMillis += 1000; // just add a second if it's a new segment
+ }
+ else {
+ trackMillis += (info.getTimestamp().getMillisecondsSince(previousTime));
+ }
+ }
+ previousTime = info.getTimestamp();
+ list.setPoint(trackMillis, info.getIndex());
+ }
+ // Now normalize the list to the requested length
+ list.normalize(inDuration);
+ return list;
+ }
+
+
+ /**
+ * Generate a points list based just on indexes, ignoring timestamps
+ * @param inNumPoints number of points in track
+ * @param inDuration number of seconds to play
+ * @return PointList object
+ */
+ private static PointList generatePointListUsingIndexes(int inNumPoints, int inDuration)
+ {
+ // simple case, just take all the points in the track
+ PointList list = new PointList(inNumPoints);
+ // Add each of the points in turn
+ for (int i=0; i<inNumPoints; i++)
+ {
+ list.setPoint(i, i);
+ }
+ list.normalize(inDuration);
+ return list;
+ }
+
+ /**
+ * Run method, for scrolling in separate thread
+ */
+ public void run()
+ {
+ _running = true;
+ _app.getTrackInfo().selectPoint(_pointList.getCurrentPointIndex());
+ while (_running && !_pointList.isFinished())
+ {
+ _pointList.set(System.currentTimeMillis() - _startTime);
+ final int pointIndex = _pointList.getCurrentPointIndex();
+ //System.out.println("Set point index to " + pointIndex);
+ _app.getTrackInfo().selectPoint(pointIndex);
+ long waitInterval = _pointList.getMillisUntilNextPoint(System.currentTimeMillis() - _startTime);
+ if (waitInterval < 20) {waitInterval = 20;}
+ try {Thread.sleep(waitInterval);}
+ catch (InterruptedException ie) {}
+ }
+ _running = false;
+ enableButtons(false, true);
+ }
+}