/* * TransportToolBar.java * Eisenkraut * * Copyright (c) 2004-2016 Hanns Holger Rutz. All rights reserved. * * This software is published under the GNU General Public License v3+ * * * For further information, please contact Hanns Holger Rutz at * contact@sciss.de */ package de.sciss.eisenkraut.realtime; import de.sciss.app.AbstractApplication; import de.sciss.app.Application; import de.sciss.common.BasicWindowHandler; import de.sciss.eisenkraut.gui.GraphicsUtil; import de.sciss.eisenkraut.gui.TimeLabel; import de.sciss.eisenkraut.gui.ToolBar; import de.sciss.eisenkraut.session.Session; import de.sciss.eisenkraut.timeline.TimelineEvent; import de.sciss.eisenkraut.timeline.TimelineListener; import de.sciss.gui.GUIUtil; import de.sciss.gui.ParamField; import de.sciss.io.Span; import de.sciss.util.DefaultUnitTranslator; import de.sciss.util.Disposable; import de.sciss.util.Param; import de.sciss.util.ParamSpace; import javax.swing.*; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.InputEvent; import java.awt.event.KeyEvent; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; /** * A GUI component showing * basic transport gadgets. This class * invokes the appropriate methods in the * <code>Transport</code> class when these * gadgets are clicked. * <p><pre> * Keyboard shortcuts : space or numpad-0 : play / stop * G : go to time * shift + (alt) + space : play half or double speed * numpad 1 / 2 : rewind / fast forward * </pre> * * TODO: (FIXED?) cueing sometimes uses an obsolete start position. * idea: cue speed changes with zoom level * * TODO: (FIXED?) when palette is opened when transport is running(?) * realtime listener is not registered (only after timeline change) */ @SuppressWarnings("serial") public class TransportToolBar extends Box implements TimelineListener, TransportListener, // RealtimeConsumer, Disposable { protected final Session doc; protected final Transport transport; protected final JButton ggPlay, ggStop; private final JToggleButton ggLoop; private final ActionLoop actionLoop; private final ToolBar toolBar; protected final TimeLabel lbTime; protected double rate; private int customGroup = 3; // forward / rewind cueing protected boolean isCueing = false; protected int cueStep; protected final Timer cueTimer; protected long cuePos; private final Timer playTimer; /** * Creates a new transport palette. Other classes * may wish to add custom gadgets using <code>addButton</code> * afterwards. * * @param doc Session Session */ public TransportToolBar(final Session doc) { super(BoxLayout.X_AXIS); this.doc = doc; transport = doc.getTransport(); rate = doc.timeline.getRate(); final AbstractAction actionPlay, actionStop, actionGoToTime; final JButton ggFFwd, ggRewind; final InputMap iMap = this.getInputMap( JComponent.WHEN_IN_FOCUSED_WINDOW ); final ActionMap aMap = this.getActionMap(); toolBar = new ToolBar(SwingConstants.HORIZONTAL); ggRewind = new JButton(); GraphicsUtil.setToolIcons(ggRewind, GraphicsUtil.createToolIcons(GraphicsUtil.ICON_REWIND)); ggRewind.addChangeListener(new CueListener(ggRewind, -100)); ActionCue actionRwdOn = new ActionCue(ggRewind, true); ActionCue actionRwdOff = new ActionCue(ggRewind, false); actionRwdOn.setPair(actionRwdOff); actionRwdOff.setPair(actionRwdOn); iMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_OPEN_BRACKET, 0, false), "startrwd"); aMap.put("startrwd", actionRwdOn); iMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_OPEN_BRACKET, 0, true), "stoprwd"); aMap.put("stoprwd", actionRwdOff); actionStop = new ActionStop(); ggStop = new JButton(actionStop); GraphicsUtil.setToolIcons(ggStop, GraphicsUtil.createToolIcons(GraphicsUtil.ICON_STOP)); actionPlay = new ActionPlay(); ggPlay = new JButton(actionPlay); GraphicsUtil.setToolIcons(ggPlay, GraphicsUtil.createToolIcons(GraphicsUtil.ICON_PLAY)); iMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_SPACE, 0), "playstop"); iMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_SPACE, InputEvent.SHIFT_MASK), "playstop"); iMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_SPACE, InputEvent.SHIFT_MASK | InputEvent.ALT_MASK), "playstop"); iMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_NUMPAD0, 0), "playstop"); aMap.put("playstop", new ActionTogglePlayStop()); iMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_SPACE, InputEvent.CTRL_MASK), "playsel"); aMap.put("playsel", new ActionPlaySelection()); ggFFwd = new JButton(); GraphicsUtil.setToolIcons(ggFFwd, GraphicsUtil.createToolIcons(GraphicsUtil.ICON_FASTFORWARD)); ggFFwd.addChangeListener( new CueListener( ggFFwd, 100 )); iMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_CLOSE_BRACKET, 0, false), "startfwd"); ActionCue actionFwdOn = new ActionCue( ggFFwd, true ); ActionCue actionFwdOff = new ActionCue( ggFFwd, false ); actionFwdOn .setPair(actionFwdOff); actionFwdOff.setPair(actionFwdOn); aMap.put("startfwd", actionFwdOn); iMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_CLOSE_BRACKET, 0, true), "stopfwd"); aMap.put("stopfwd", actionFwdOff); actionLoop = new ActionLoop(); ggLoop = new JToggleButton(actionLoop); GraphicsUtil.setToolIcons(ggLoop, GraphicsUtil.createToolIcons(GraphicsUtil.ICON_LOOP)); GUIUtil.createKeyAction(ggLoop, KeyStroke.getKeyStroke(KeyEvent.VK_SLASH, 0)); toolBar.addButton(ggRewind); toolBar.addButton(ggStop); toolBar.addButton(ggPlay); toolBar.addButton(ggFFwd); toolBar.addToggleButton(ggLoop, 2); actionGoToTime = new ActionGoToTime(); lbTime = new TimeLabel(); lbTime.setToolTipText(AbstractApplication.getApplication().getResourceString("inputDlgGoToTime")); lbTime.setCursor(new Cursor(Cursor.HAND_CURSOR)); lbTime.addMouseListener(new MouseAdapter() { public void mouseClicked(MouseEvent e) { actionGoToTime.actionPerformed(null); lbTime.normalState(); } public void mouseEntered(MouseEvent e) { lbTime.hoverState(); } public void mouseExited(MouseEvent e) { lbTime.normalState(); } }); iMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_G, 0), "gototime"); aMap.put("gototime", actionGoToTime); this.add(toolBar); final Box b2 = Box.createVerticalBox(); b2.add(Box.createVerticalGlue()); b2.add(lbTime); b2.add(Box.createVerticalGlue()); this.add(Box.createHorizontalStrut(4)); this.add(b2); // --- Listener --- // new DynamicAncestorAdapter(this).addTo(this); cueTimer = new Timer(25, new ActionListener() { public void actionPerformed(ActionEvent e) { cuePos = Math.max(0, Math.min(doc.timeline.getLength(), cuePos + (long) (cueStep * rate) / 1000)); doc.timeline.editPosition(this, cuePos); GraphicsUtil.sync(); } }); playTimer = new Timer(27, new ActionListener() { public void actionPerformed(ActionEvent e) { lbTime.setTime(transport.getCurrentFrame() / rate); GraphicsUtil.sync(); } }); doc.timeline.addTimelineListener (this); transport .addTransportListener(this); } public void setLoop(boolean onOff) { ggLoop.setSelected(onOff); } /** * Adds a new button to the transport palette * * @param b the button to add */ public void addButton(AbstractButton b) { if (b instanceof JToggleButton) { toolBar.addToggleButton((JToggleButton) b, customGroup); customGroup++; } else { toolBar.addButton(b); } } public void setOpaque(boolean b) { toolBar.setOpaque(b); lbTime .setOpaque(b); super .setOpaque(b); } // ---------------- TimelineListener interface ---------------- public void timelineSelected(TimelineEvent e) { if (ggLoop.isSelected()) { actionLoop.updateLoop(); } } public void timelineChanged(TimelineEvent e) { rate = doc.timeline.getRate(); lbTime.setTime(transport.getCurrentFrame() / rate); } public void timelineScrolled(TimelineEvent e) { /* ignore */ } public void timelinePositioned(TimelineEvent e) { final long pos = doc.timeline.getPosition(); if (!isCueing) cuePos = pos; lbTime.setTime(pos / rate); } // ---------------- TransportListener interface ---------------- public void transportStop( Transport t, long pos ) { ggPlay.setSelected( false ); if( isCueing ) { cuePos = pos; cueTimer.restart(); } playTimer.stop(); } public void transportPlay( Transport t, long pos, double pRate ) { ggPlay.setSelected( true ); cueTimer.stop(); playTimer.restart(); } public void transportQuit( Transport t ) { cueTimer.stop(); playTimer.stop(); } public void transportPosition( Transport t, long pos, double pRate ) { /* ignore */ } public void transportReadjust( Transport t, long pos, double pRate ) { /* ignore */ } // ---------------- Disposable interface ---------------- public void dispose() { playTimer.stop(); } // ---------------- actions ---------------- @SuppressWarnings("serial") private class ActionGoToTime extends AbstractAction { private Param value = null; private ParamSpace space = null; protected ActionGoToTime() { /* empty */ } public void actionPerformed(ActionEvent e) { final int result; final Param positionSmps; final Box msgPane; final DefaultUnitTranslator timeTrans; final ParamField ggPosition; final Application app = AbstractApplication.getApplication(); msgPane = Box.createVerticalBox(); // XXX sync timeTrans = new DefaultUnitTranslator(); ggPosition = new ParamField( timeTrans ); ggPosition.addSpace( ParamSpace.spcTimeHHMMSS ); ggPosition.addSpace( ParamSpace.spcTimeSmps ); ggPosition.addSpace( ParamSpace.spcTimeMillis ); ggPosition.addSpace( ParamSpace.spcTimePercentF ); timeTrans.setLengthAndRate( doc.timeline.getLength(), doc.timeline.getRate() ); // XXX sync if( value != null ) { ggPosition.setSpace( space ); ggPosition.setValue( value ); } // ggPosition.setValue( position ); // lbCurrentTime = new TimeLabel( new Color( 0xE0, 0xE0, 0xE0 )); // ggPosition.setBorder( new ComboBoxEditorBorder() ); // ggPosCombo = new JComboBox(); // ggPosCombo.setEditor( ggPosition ); // ggPosCombo.setEditable( true ); // msgPane.gridAdd( ggPosCombo, 0, 1, -1, 1 ); msgPane.add( Box.createVerticalGlue() ); // msgPane.add( ggPosCombo ); JButton ggCurrent = new JButton( app.getResourceString( "buttonSetCurrent" )); // "Current" ggCurrent.setFocusable( false ); //JLabel lbArrow = new JLabel( "\u2193" ); // "\u2939" //Box b = Box.createHorizontalBox(); //b.add( lbArrow ); //b.add( ggCurrent ); ggCurrent.addActionListener( new ActionListener() { public void actionPerformed( ActionEvent ae ) { final long pos = transport.isRunning() ? transport.getCurrentFrame() : doc.timeline.getPosition(); ggPosition.setValue( new Param( pos, ParamSpace.TIME | ParamSpace.SMPS )); // XXX sync ggPosition.requestFocusInWindow(); } }); //msgPane.add( b ); msgPane.add( ggCurrent ); msgPane.add( ggPosition ); msgPane.add( Box.createVerticalGlue() ); GUIUtil.setInitialDialogFocus( ggPosition ); // ggPosCombo.removeAllItems(); // // XXX sync // ggPosCombo.addItem( new StringItem( new Param( doc.timeline.getPosition() / doc.timeline.getRate(), ParamSpace.TIME | ParamSpace.SECS | ParamSpace.HHMMSS ).toString(), "Current" )); final JOptionPane op = new JOptionPane( msgPane, JOptionPane.QUESTION_MESSAGE, JOptionPane.OK_CANCEL_OPTION ); // result = JOptionPane.showOptionDialog( BasicWindowHandler.getWindowAncestor( lbTime ), msgPane, // app.getResourceString( "inputDlgGoToTime" ), // JOptionPane.OK_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE, null, null, null ); result = BasicWindowHandler.showDialog( op, BasicWindowHandler.getWindowAncestor( lbTime ), app.getResourceString( "inputDlgGoToTime" )); if( result == JOptionPane.OK_OPTION ) { value = ggPosition.getValue(); space = ggPosition.getSpace(); positionSmps = timeTrans.translate( value, ParamSpace.spcTimeSmps ); doc.timeline.editPosition( this, Math.max( 0, Math.min( doc.timeline.getLength(), (long) positionSmps.val ))); } } } // class actionGoToTimeClass @SuppressWarnings("serial") private class ActionTogglePlayStop extends AbstractAction { protected ActionTogglePlayStop() { /* empty */ } public void actionPerformed(ActionEvent e) { if (transport.isRunning()) { ggStop.doClick(); } else { ggPlay.doClick(); } } } // class actionTogglePlayStopClass @SuppressWarnings("serial") private class ActionPlaySelection extends AbstractAction { protected ActionPlaySelection() { /* empty */ } public void actionPerformed( ActionEvent e ) { final Span span; if( transport.isRunning() ) { transport.stop(); } span = doc.timeline.getSelectionSpan(); if( !span.isEmpty() ) { transport.playSpan( span, 1.0 ); } else { transport.play( 1.0 ); } } } // class actionPlaySelectionClass @SuppressWarnings("serial") private static class ActionCue extends AbstractAction { private final boolean onOff; private final AbstractButton b; private final Timer t; private ActionCue pair; private long lastWhen = 0L; public void setPair(ActionCue p) { pair = p; } public long getLastWhen() { if (!onOff) t.stop(); return lastWhen; } protected ActionCue(AbstractButton b, boolean onOff) { this.onOff = onOff; this.b = b; if (onOff) t = null; else t = new javax.swing.Timer(5, new ActionListener() { public void actionPerformed(ActionEvent e) { perform(); } }); } private void perform() { final ButtonModel bm = b.getModel(); if( bm.isPressed() != onOff ) bm.setPressed( onOff ); if( bm.isArmed() != onOff ) bm.setArmed( onOff ); } public void actionPerformed(ActionEvent e) { lastWhen = e.getWhen(); if (onOff) { if (pair.getLastWhen() == lastWhen) return; // Linux repeat bullshit perform(); } else { t.restart(); } } } // class actionCueClass @SuppressWarnings("serial") private class ActionLoop extends AbstractAction { protected ActionLoop() { super(); } public void actionPerformed( ActionEvent e ) { if( ((AbstractButton) e.getSource()).isSelected() ) { // if( doc.bird.attemptShared( Session.DOOR_TIME, 200 )) { // try { updateLoop(); // } // finally { // doc.bird.releaseShared( Session.DOOR_TIME ); // } // } else { // ((AbstractButton) e.getSource()).setSelected( false ); // } } else { transport.setLoop( null ); } } protected void updateLoop() { Span span; // if( !doc.bird.attemptShared( Session.DOOR_TIME, 250 )) return; // try { span = doc.timeline.getSelectionSpan(); transport.setLoop( span.isEmpty() ? null : span ); // } // finally { // doc.bird.releaseShared( Session.DOOR_TIME ); // } } } // class actionLoopClass private class CueListener implements ChangeListener { private final ButtonModel bm; private boolean transportWasRunning = false; private final int step; // step = in millisecs, > 0 = fwd, < = rwd protected CueListener( AbstractButton b, int step ) { bm = b.getModel(); this.step = step; } public void stateChanged( ChangeEvent e ) { if( isCueing && !bm.isArmed() ) { // System.out.println("---1"); isCueing = false; cueTimer.stop(); if( transportWasRunning ) { transport.play( 1.0f ); } } else if( !isCueing && bm.isArmed() ) { // System.out.println("---2"); transportWasRunning = transport.isRunning(); cueStep = step; isCueing = true; if( transportWasRunning ) { transport.stop(); } else { cueTimer.restart(); } } } } // --------------- internal actions --------------- @SuppressWarnings("serial") private class ActionPlay extends AbstractAction { protected ActionPlay() { super(); } public void actionPerformed( ActionEvent e ) { perform( (e.getModifiers() & ActionEvent.SHIFT_MASK) == 0 ? 1.0f : ((e.getModifiers() & ActionEvent.ALT_MASK) == 0 ? 0.5f : 2.0f) ); } protected void perform( float scale ) { if( doc.timeline.getPosition() == doc.timeline.getLength() ) { // doc.getFrame().addCatchBypass(); doc.timeline.editPosition( transport, 0 ); // doc.getFrame().removeCatchBypass(); } transport.play( scale ); } } // class actionPlayClass @SuppressWarnings("serial") private class ActionStop extends AbstractAction { protected ActionStop() { super(); } public void actionPerformed( ActionEvent e ) { transport.stop(); } } // class actionStopClass }