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 set = new TreeSet(); int numPoints = inTrack.getNumPoints(); for (int i=0; i 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