Package de.sciss.eisenkraut.timeline

Source Code of de.sciss.eisenkraut.timeline.Timeline$ActionSelToPos

/*
*  Timeline.java
*  Eisenkraut
*
*  Copyright (c) 2004-2014 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
*
*
*  Changelog:
*    28-Jan-05  created from de.sciss.meloncillo.timeline.Timeline
*    08-Sep-05  rate changed to floating point precision
*    17-Sep-05  rate can be set using map manager (allows out-of-the-box undoable edit)
*    21-Jan-06  added edit-methods, added OSC support
*    25-Feb-06  rate is double precision
*/

package de.sciss.eisenkraut.timeline;

import java.awt.event.ActionEvent;
import java.util.Set;

import javax.swing.AbstractAction;
import javax.swing.undo.CompoundEdit;

import de.sciss.app.BasicEvent;
import de.sciss.app.EventManager;
import de.sciss.eisenkraut.edit.BasicCompoundEdit;
import de.sciss.eisenkraut.edit.TimelineVisualEdit;
import de.sciss.eisenkraut.net.OSCRoot;
import de.sciss.eisenkraut.net.OSCRouter;
import de.sciss.eisenkraut.net.OSCRouterWrapper;
import de.sciss.eisenkraut.net.RoutedOSCMessage;
import de.sciss.eisenkraut.session.AbstractSessionObject;
import de.sciss.eisenkraut.session.Session;
import de.sciss.eisenkraut.util.MapManager;
import de.sciss.io.Span;

/**
*  This class describes the document's timeline
*  properties, e.g. length, selection, visible span.
*  It contains an event dispatcher for TimelineEvents
*  which get fired when methods like setPosition are
*  called.
*
@author    Hanns Holger Rutz
@version  0.71, 05-Nov-08
*
*  @todo    view based stuff (visible span) should be removed
*/
public class Timeline
extends AbstractSessionObject
implements EventManager.Processor, OSCRouter
{
//  private static final boolean    DEBUG_CONCURRENCY    = true;
 
  public static final String      MAP_KEY_RATE      = "rate";
  private static final String      MAP_KEY_LENGTH      = "len";
  private static final String      MAP_KEY_POSITION    = "pos";

  private double            rate;        // sampleframes per second
  private long            length;        // total number of sampleframes
  private long            position;      // current head position
    private Span            visibleSpan;    // what's being viewed in the TimelineFrame
    private Span            selectionSpan;
 
    protected final Session        doc;

  /**
   *  Name attribute of the object
   *  element in a session's XML file
   */
  public static final String      XML_OBJECT_NAME      = "timeline";

  // --- event handling ---

  private final EventManager      elm            = new EventManager( this );

  // --- actions ---

  private final ActionSelToPos  actionPosToSelBeginC  = new ActionSelToPos( 0.0f, true );
  private final ActionSelToPos  actionPosToSelEndC    = new ActionSelToPos( 1.0f, true );
  private final ActionSelToPos  actionPosToSelBegin    = new ActionSelToPos( 0.0f, false );
  private final ActionSelToPos  actionPosToSelEnd    = new ActionSelToPos( 1.0f, false );
 
  private static final String      OSC_TIMELINE      = "timeline";
 
  private final OSCRouterWrapper    osc;

  /**
   *  Creates a new empty timeline
   */
  public Timeline( Session doc )
  {
    super();

    this.doc    = doc;
   
    final MapManager map = getMap();

    map.putContext( this, MAP_KEY_RATE, new MapManager.Context( 0, MapManager.Context.TYPE_DOUBLE, null, null, null,
                                  new Double( 1000 )));
    map.putContext( this, MAP_KEY_LENGTH, new MapManager.Context( 0, MapManager.Context.TYPE_LONG, null, null, null,
                                    new Long( 0 )));
    map.putContext( this, MAP_KEY_POSITION, new MapManager.Context( 0, MapManager.Context.TYPE_LONG, null, null, null,
                                    new Long( 0 )));

    osc  = new OSCRouterWrapper( doc, this );

    clear( this );
    setName( XML_OBJECT_NAME );
  }
 
  public AbstractAction getPosToSelAction( boolean begin, boolean deselect )
  {
    return begin ? (deselect ? actionPosToSelBeginC : actionPosToSelBegin) :
             (deselect ? actionPosToSelEndC : actionPosToSelEnd);
  }
 
  /**
   *  Clears the timeline, i.e. the length is set to zero,
   *  selection and visible span are cleared.
   *  The caller should ensure
   *  that event dispatching is paused!
   *
   *  @param  source  who originated the action
   */
  public void clear( Object source )
  {
    getMap().clearValues( source );
    setRate( source, 1000 );
    setPosition( source, 0 );
    setSelectionSpan( source, new Span() );
    setVisibleSpan( source, new Span() );
    setLength( source, 0 );
  }

  /**
   *  Pauses the event dispatching. No
   *  events are destroyed, only execution
   *  is deferred until resumeDispatcher is
   *  called.
   */
  public void pauseDispatcher()
  {
    elm.pause();
  }

  /**
   *  Resumes the event dispatching. Pending
   *  events will be diffused during the
   *  next run of the event thread.
   */
  public void resumeDispatcher()
  {
    elm.resume();
  }

  /**
   *  Queries the timeline sample rate
   *
   *  @return the rate of timeline data (trajectories etc.)
   *      in frames per second
   */
  public double getRate()
  {
    if( !java.awt.EventQueue.isDispatchThread() ) throw new IllegalMonitorStateException();
    return rate;
  }

  /**
   *  Queries the timeline's length
   *
   *  @return the timeline length in frames
   */
  public long getLength()
  {
    if( !java.awt.EventQueue.isDispatchThread() ) throw new IllegalMonitorStateException();
    return length;
  }

  /**
   *  Queries the timeline's position
   *
   *  @return the current timeline offset (the position
   *      of the vertical bar in the timeline frame)
   *      in frames
   */
  public long getPosition()
  {
    if( !java.awt.EventQueue.isDispatchThread() ) throw new IllegalMonitorStateException();
    return position;
  }

  /**
   *  Queries the timeline's visible span
   *
   *  @return the portion of the timeline currently
   *      visible in the timeline frame. start and
   *      stop are measured in frames
   */
  public Span getVisibleSpan()
  {
    if( !java.awt.EventQueue.isDispatchThread() ) throw new IllegalMonitorStateException();
    return visibleSpan;
  }

  /**
   *  Queries the timeline's selected span
   *
   *  @return the portion of the timeline currently
   *      selected (highlighted blue). start and
   *      stop are measured in frames
   */
  public Span getSelectionSpan()
  {
    if( !java.awt.EventQueue.isDispatchThread() ) throw new IllegalMonitorStateException();
    return selectionSpan;
  }

  /**
   *  Changes the timeline sample rate. This
   *  fires a <code>TimelineEvent</code> (<code>CHANGED</code>). Note
   *  that there's no corresponding undoable edit.
   *
   *  @param  source  the source of the <code>TimelineEvent</code>
   @param  rate  the new rate of timeline data (trajectories etc.)
   *          in frames per second
   *
   *  @see  TimelineEvent#CHANGED
   */
    public void setRate( Object source, double rate )
    {
    if( !java.awt.EventQueue.isDispatchThread() ) throw new IllegalMonitorStateException();
        this.rate = rate;
    if( source != null ) dispatchChange( source );
    getMap().putValue( this, MAP_KEY_RATE, new Double( rate ));
    }

  /**
   *  Changes the timeline's length. This is
   *  normally done indirectly by creating an
   *  <code>EditInsertTimeSpan</code> or
   *  <code>EditRemoveTimeSpan</code> object.
   *  Alternatively <code>EditSetTimelineLength</code> provides
   *  a more low level object.
   *  This fires a <code>TimelineEvent</code> (<code>CHANGED</code>).
   *
   *  @param  source  the source of the <code>TimelineEvent</code>
   @param  length  the new timeline length in frames
   *
   *  @see  de.sciss.eisenkraut.edit.EditSetTimelineLength
   *  @see  TimelineEvent#CHANGED
   */
    public void setLength( Object source, long length )
    {
    if( !java.awt.EventQueue.isDispatchThread() ) throw new IllegalMonitorStateException();
        this.length = length;
    if( source != null ) dispatchChange( source );
    getMap().putValue( this, MAP_KEY_LENGTH, new Long( length ));
    }

  /**
   *  Changes the current the timeline's playback/insert position. This
   *  fires a <code>TimelineEvent</code> (<code>POSITIONED</code>).
   *  Often you'll want to use an <code>EditSetTimelinePosition</code> object.
   *
   *  @param  source    the source of the <code>TimelineEvent</code>
   @param  position  the new timeline offset (the position
   *            of the vertical bar in the timeline frame)
   *            in frames
   *
   *  @see  de.sciss.eisenkraut.edit.TimelineVisualEdit
   *  @see  TimelineEvent#POSITIONED
   */
    public void setPosition( Object source, long position )
  {
    if( !java.awt.EventQueue.isDispatchThread() ) throw new IllegalMonitorStateException();
        this.position = position;
    if( source != null ) dispatchPosition( source );
    getMap().putValue( this, MAP_KEY_POSITION, new Long( position ));
  }

  /**
   *  Changes the timeline's visible span. This
   *  fires a <code>TimelineEvent</code> (<code>SCROLLED</code>).
   *  Often you'll want to use an <code>EditSetTimelineScroll</code> object.
   *
   *  @param  source  the source of the <code>TimelineEvent</code>
   @param  span  the portion of the timeline that should be
   *          displayed in the timeline frame. start and
   *          stop are measured in frames
   *
   *  @see  de.sciss.eisenkraut.edit.TimelineVisualEdit
   *  @see  TimelineEvent#SCROLLED
   */
     public void setVisibleSpan( Object source, Span span )
  {
     if( !java.awt.EventQueue.isDispatchThread() ) throw new IllegalMonitorStateException();
        this.visibleSpan = new Span( span );
    if( source != null ) dispatchScroll( source );
  }

  /**
   *  Changes the timeline's selected span. This
   *  fires a <code>TimelineEvent</code> (<code>SELECTED</code>).
   *  Often you'll want to use an <code>EditSetTimelineSelection</code> object.
   *
   *  @param  source  the source of the <code>TimelineEvent</code>
   @param  span  the portion of the timeline which should be
   *          selected (highlighted blue). start and
   *          stop are measured in frames
   *
   *  @see  de.sciss.eisenkraut.edit.TimelineVisualEdit
   *  @see  TimelineEvent#SELECTED
   */
    public void setSelectionSpan( Object source, Span span )
  {
    if( !java.awt.EventQueue.isDispatchThread() ) throw new IllegalMonitorStateException();
        this.selectionSpan = new Span( span );
        if( source != null ) dispatchSelection( source );
  }

  /**
   *  Register a <code>TimelineListener</code>
   *  which will be informed about changes of
   *  the timeline (i.e. changes in rate and length,
   *  scrolling in the timeline frame and selection
   *  of timeline portions by the user).
   *
   *  @param  listener  the <code>TimelineListener</code> to register
   *  @see  de.sciss.app.EventManager#addListener( Object )
   */
  public void addTimelineListener( TimelineListener listener )
  {
    elm.addListener( listener );
  }

  /**
   *  Unregister a <code>TimelineListener</code>
   *  from receiving timeline events.
   *
   *  @param  listener  the <code>TimelineListener</code> to unregister
   *  @see  de.sciss.app.EventManager#removeListener( Object )
   */
  public void removeTimelineListener( TimelineListener listener )
  {
    elm.removeListener( listener );
  }

  public void editPosition( Object source, long pos )
  {
    doc.getUndoManager().addEdit( TimelineVisualEdit.position( source, doc, pos ).perform() )
  }

  public void editScroll( Object source, Span span )
  {
    doc.getUndoManager().addEdit( TimelineVisualEdit.scroll( source, doc, span ).perform() )
  }

  public void editSelect( Object source, Span span )
  {
    doc.getUndoManager().addEdit( TimelineVisualEdit.select( source, doc, span ).perform() );
  }

  /**
   *  This is called by the EventManager
   *  if new events are to be processed
   */
  public void processEvent( BasicEvent e )
  {
    TimelineListener listener;
    int i;
   
    for( i = 0; i < elm.countListeners(); i++ ) {
      listener = (TimelineListener) elm.getListener( i );
      switch( e.getID() ) {
      case TimelineEvent.CHANGED:
        listener.timelineChanged( (TimelineEvent) e );
        break;
      case TimelineEvent.POSITIONED:
        listener.timelinePositioned( (TimelineEvent) e );
        break;
      case TimelineEvent.SELECTED:
        listener.timelineSelected( (TimelineEvent) e );
        break;
      case TimelineEvent.SCROLLED:
        listener.timelineScrolled( (TimelineEvent) e );
        break;
      }
    } // for( i = 0; i < elm.countListeners(); i++ )
  }

  // utility function to create and dispatch a TimelineEvent
  private void dispatchChange( Object source )
  {
    TimelineEvent e2 = new TimelineEvent( source, TimelineEvent.CHANGED,
                          System.currentTimeMillis(), 0, null );
    elm.dispatchEvent( e2 );
  }

  // utility function to create and dispatch a TimelineEvent
  private void dispatchPosition( Object source )
  {
    TimelineEvent e2 = new TimelineEvent( source, TimelineEvent.POSITIONED,
                        System.currentTimeMillis(), 0, new Double( getPosition() ));
    elm.dispatchEvent( e2 );
  }
   

  // utility function to create and dispatch a TimelineEvent
  private void dispatchSelection( Object source )
  {
    TimelineEvent e2 = new TimelineEvent( source, TimelineEvent.SELECTED,
                        System.currentTimeMillis(), 0, getSelectionSpan() );
    elm.dispatchEvent( e2 );
  }

  // utility function to create and dispatch a TimelineEvent
  private void dispatchScroll( Object source )
  {
    TimelineEvent e2 = new TimelineEvent( source, TimelineEvent.SCROLLED,
                        System.currentTimeMillis(), 0, getVisibleSpan() );
    elm.dispatchEvent( e2 );
  }

  public Class getDefaultEditor() { return null; }

// ---------------- MapManager.Listener interface ----------------

  public void mapChanged( MapManager.Event e )
  {
    super.mapChanged( e );
   
    final Object source = e.getSource();
   
    if( source == this ) return;
   
    final Set  keySet    = e.getPropertyNames();
    Object    val;
    boolean    dChange    = false;
    boolean    dPosition  = false;

    if( keySet.contains( MAP_KEY_RATE )) {
      val    = e.getManager().getValue( MAP_KEY_RATE );
      if( val != null ) {
        rate  = ((Number) val).doubleValue(); // Float.parseFloat( val.toString() );
        dChange  = true;
      }
    }
    if( keySet.contains( MAP_KEY_LENGTH )) {
      val    = e.getManager().getValue( MAP_KEY_LENGTH );
      if( val != null ) {
        length  = ((Number) val).longValue();
        dChange  = true;
        if( visibleSpan.isEmpty() && length > 0 ) setVisibleSpan( this, new Span( 0, length ));
      }
    }
    if( keySet.contains( MAP_KEY_POSITION )) {
      val    = e.getManager().getValue( MAP_KEY_POSITION );
      if( val != null ) {
        position  = ((Number) val).longValue();
        dPosition  = true;
      }
    }

    if( dChange ) dispatchChange( source );
    if( dPosition ) dispatchPosition( source );
  }

  // ------------- OSCRouter interface -------------
 
  public String oscGetPathComponent()
  {
    return OSC_TIMELINE;
  }
 
  public void oscRoute( RoutedOSCMessage rom )
  {
    osc.oscRoute( rom );
  }
 
  public void oscAddRouter( OSCRouter subRouter )
  {
    osc.oscAddRouter( subRouter );
  }

  public void oscRemoveRouter( OSCRouter subRouter )
  {
    osc.oscRemoveRouter( subRouter );
  }
 
  public Object oscQuery_position()
  {
    return new Long( getPosition() );
  }

  public Object oscQuery_selectionStart()
  {
    return new Long( getSelectionSpan().start );
  }

  public Object oscQuery_selectionStop()
  {
    return new Long( getSelectionSpan().stop );
  }

  public Object oscQuery_viewStart()
  {
    return new Long( getVisibleSpan().start );
  }

  public Object oscQuery_viewStop()
  {
    return new Long( getVisibleSpan().stop );
  }
 
  public Object oscQuery_rate()
  {
    return new Double( getRate() );
  }

  public Object oscQuery_length()
  {
    return new Long( getLength() );
  }

  public void oscCmd_position( RoutedOSCMessage rom )
  {
    try {
      final long pos = ((Number) rom.msg.getArg( 1 )).longValue();
      editPosition( this, Math.max( 0, Math.min( getLength(), pos )));
    }
    catch( IndexOutOfBoundsException e1 ) {
      OSCRoot.failedArgCount( rom );
    }
    catch( ClassCastException e1 ) {
      OSCRoot.failedArgType( rom, 0 );
    }
  }

  public void oscCmd_select( RoutedOSCMessage rom )
  {
    final long    start, stop;
    int        argIdx  = 1;

    try  {
      start  = Math.max( 0, Math.min( getLength(), ((Number) rom.msg.getArg( argIdx )).longValue() ));
      argIdx++;
      stop  = Math.max( start, Math.min( getLength(), ((Number) rom.msg.getArg( argIdx )).longValue() ));
      editSelect( this, new Span( start, stop ));
    }
    catch( IndexOutOfBoundsException e1 ) {
      OSCRoot.failedArgCount( rom );
    }
    catch( ClassCastException e1 ) {
      OSCRoot.failedArgType( rom, argIdx );
    }
  }

  public void oscCmd_view( RoutedOSCMessage rom )
  {
    final long    start, stop;
    int        argIdx  = 1;

    try  {
      start  = Math.max( 0, Math.min( getLength(), ((Number) rom.msg.getArg( argIdx )).longValue() ));
      argIdx++;
      stop  = Math.max( start, Math.min( getLength(), ((Number) rom.msg.getArg( argIdx )).longValue() ));
      editScroll( this, new Span( start, stop ));
    }
    catch( IndexOutOfBoundsException e1 ) {
      OSCRoot.failedArgCount( rom );
    }
    catch( ClassCastException e1 ) {
      OSCRoot.failedArgType( rom, argIdx );
    }
  }

// ---------------- internal classes ----------------

  /*
   *  @warning  have to keep an eye on this. with weight as float
   *        there were quantization errors. with double seems
   *        to be fine. haven't checked with really long files!!
   */
  private class ActionSelToPos
  extends AbstractAction
  {
    private final double  weight;
    private final boolean  deselect;
 
    protected ActionSelToPos( double weight, boolean deselect )
    {
      super();
     
      this.weight    = weight;
      this.deselect  = deselect;
    }
 
    public void actionPerformed( ActionEvent e )
    {
      perform();
    }
   
    private void perform()
    {
      Span      selSpan;
      CompoundEdit  edit;
      long      pos;
   
      selSpan    = getSelectionSpan();
      if( selSpan.isEmpty() ) return;
     
      edit  = new BasicCompoundEdit();
      if( deselect ) edit.addEdit( TimelineVisualEdit.select( this, doc, new Span() ).perform() );
      pos    = (long) (selSpan.getStart() + selSpan.getLength() * weight + 0.5);
      edit.addEdit( TimelineVisualEdit.position( this, doc, pos ).perform() );
      edit.end();
      doc.getUndoManager().addEdit( edit );
    }
  } // class actionSelToPosClass
}
TOP

Related Classes of de.sciss.eisenkraut.timeline.Timeline$ActionSelToPos

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.