Package de.sciss.eisenkraut.timeline

Source Code of de.sciss.eisenkraut.timeline.TimelineScroll

/*
*  TimelineScroll.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:
*    12-May-05  re-created from de.sciss.meloncillo.timeline.TimelineScroll
*    15-Jul-05  fix in setPosition to avoid duplicate event generation
*/

package de.sciss.eisenkraut.timeline;

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Paint;
import java.awt.Shape;
import java.awt.Stroke;
import java.awt.event.AdjustmentEvent;
import java.awt.event.AdjustmentListener;
import java.awt.geom.Line2D;
import java.awt.geom.Rectangle2D;
import java.util.prefs.PreferenceChangeEvent;
import java.util.prefs.PreferenceChangeListener;

import javax.swing.JScrollBar;
import javax.swing.LookAndFeel;
import javax.swing.UIManager;

import de.sciss.app.AbstractApplication;
import de.sciss.app.DynamicAncestorAdapter;
import de.sciss.app.DynamicListening;
import de.sciss.app.DynamicPrefChangeManager;
import de.sciss.eisenkraut.gui.GraphicsUtil;
import de.sciss.eisenkraut.session.Session;
import de.sciss.eisenkraut.util.PrefsUtil;

import de.sciss.io.Span;

/**
*  A GUI element for allowing
*  horizontal timeline scrolling.
*  Subclasses <code>JScrollBar</code>
*  simply to override the <code>paintComponent</code>
*  method: an additional hairline is drawn
*  to visualize the current timeline position.
*  also a translucent blue rectangle is drawn
*  to show the current timeline selection.
<p>
*  This class tracks the catch preferences
*
@author    Hanns Holger Rutz
@version  0.70, 21-Oct-08
*
*  @todo    the display properties work well
*        with the Aqua look+and+feel, however
*        are slightly wrong on Linux with platinum look+feel
*        because the scroll gadgets have different positions.
*/
public class TimelineScroll
extends JScrollBar
implements AdjustmentListener, TimelineListener, DynamicListening, PreferenceChangeListener
{
  public static final int   TYPE_UNKNOWN    = 0;
  public static final int   TYPE_DRAG      = 1;
  public static final int   TYPE_TRANSPORT    = 2;

    private final Session     doc;

  private Dimension      recentSize      = getMinimumSize();
    private Shape        shpSelection    = null;
    private Shape        shpPosition       = null;
  private Span        timelineSel      = null;
  private long        timelineLen      = 0;
  private int          timelineLenShift  = 0;
  private long        timelinePos      = 0;
  private Span        timelineVis      = new Span();
  private boolean        prefCatch;
 
  private final  Object    adjustmentSource  = new Object();
   
    private static final Color  colrSelection     = GraphicsUtil.colrSelection;
    private static final Color  colrPosition      = Color.red;
    private static final Stroke  strkPosition      = new BasicStroke( 0.5f );

  private final int      trackMarginLeft;
  private final int      trackMargin;
 
  private boolean        wasAdjusting    = false;
  private boolean        adjustCatchBypass  = false;
  private int          catchBypassCount  = 0;
  private boolean        catchBypassWasSynced= false;
 
  /**
   *  Constructs a new <code>TimelineScroll</code> object.
   *
   *  @param  doc    session Session
   *
   *  @todo  a clean way to determine the track rectangle ...
   */
    public TimelineScroll( Session doc )
    {
        super( HORIZONTAL );
        this.doc    = doc;

    LookAndFeel laf = UIManager.getLookAndFeel();
    if( (laf != null) && laf.isNativeLookAndFeel() && (laf.getName().toLowerCase().indexOf( "aqua" ) >= 0) ) {
      trackMarginLeft = 6// for Aqua look and feel 
      trackMargin    = 39;
    } else {
      trackMarginLeft = 16// works for Metal, Motif, Liquid, Metouia
      trackMargin    = 32;
    }

    timelineLen = doc.timeline.getLength();
    timelineVis = doc.timeline.getVisibleSpan();
    for( timelineLenShift = 0; (timelineLen >> timelineLenShift) > 0x3FFFFFFF; timelineLenShift++ );
    recalcTransforms();
    recalcBoundedRange();

    // --- Listener ---
   
    new DynamicAncestorAdapter( this ).addTo( this );
        this.addAdjustmentListener( this );

//        new DynamicAncestorAdapter( new DynamicPreferenceChangeManager( Main.prefs.node( PrefsUtil.NODE_SHARED ),
//      new String[] { PrefsUtil.KEY_CATCH }, this )).addTo( this );
        new DynamicAncestorAdapter( new DynamicPrefChangeManager( AbstractApplication.getApplication().getUserPrefs(),
      new String[] { PrefsUtil.KEY_CATCH }, this )).addTo( this );
       
    setFocusable( false );
   
//        HelpGlassPane.setHelp( this, "TimelineScroll" );
    }
   
  /**
   *  Paints the normal scroll bar using
   *  the super class's method. Additionally
   *  paints timeline position and selection cues
   */
    public void paintComponent( Graphics g )
    {
        super.paintComponent( g );
    Dimension   d           = getSize();
        Graphics2D  g2          = (Graphics2D) g;
    Stroke    strkOrig  = g2.getStroke();
    Paint    pntOrig    = g2.getPaint();

    if( d.width != recentSize.width || d.height != recentSize.height ) {
      recentSize = d;
      recalcTransforms();
    }
       
        if( shpSelection != null ) {
            g2.setColor( colrSelection );
            g2.fill( shpSelection );
        }
        if( shpPosition != null ) {
            g2.setColor( colrPosition );
            g2.setStroke( strkPosition );
            g2.draw( shpPosition );
        }

        g2.setStroke( strkOrig );
    g2.setPaint( pntOrig );
    }

    private void recalcBoundedRange()
    {
    final int len  = (int) (timelineLen >> timelineLenShift);
    final int len2  = (int) (timelineVis.getLength() >> timelineLenShift);
    if( len > 0 ) {
      if( !isEnabled() ) setEnabled( true );
      setValues( (int) (timelineVis.getStart() >> timelineLenShift), len2, 0, len );   // val, extent, min, max
      setUnitIncrement( Math.max( 1, (len2 >> 5) ));             // 1/32 extent
      setBlockIncrement( Math.max( 1, ((len2 * 3) >> 2) ));      // 3/4 extent
    } else {
      if( isEnabled() ) setEnabled( false );
      setValues( 0, 100, 0, 100 )// full view will hide the scrollbar knob
    }
    }

    /*
     *  Calculates virtual->screen coordinates
     *  for timeline position and selection
     */
    private void recalcTransforms()
    {
        double  scale, x;

//for( int i = 0; i < getComponentCount(); i++ ) {
//  Component c = getComponent( i );
//  System.err.println( "scroll container component : "+c.getClass().getName()+" ; at "+c.getLocation().x+", "+
//    c.getLocation().y+"; w = "+c.getWidth()+"; h = "+c.getHeight() );
//}
//       
    if( timelineLen > 0 ) {
      scale           = (double) (recentSize.width - trackMargin) / (double) timelineLen;
      if( timelineSel != null ) {
        shpSelection = new Rectangle2D.Double( timelineSel.getStart() * scale + trackMarginLeft, 0,
                             timelineSel.getLength() * scale, recentSize.height );
      } else {
        shpSelection = null;                  
      }
      x               = timelinePos * scale + trackMarginLeft;
      shpPosition     = new Line2D.Double( x, 0, x, recentSize.height );
    } else {
      shpSelection    = null;
      shpPosition     = null;
    }
    }
   
  /**
   *  Updates the red hairline representing
   *  the current timeline position in the
   *  overall timeline span.
   *  Called directly from TimelineFrame
   *  to improve performance. Don't use
   *  elsewhere.
   *
   *  @param  pos      new position in absolute frames
   *  @param  patience  allowed graphic update interval
   *
   *  @see  java.awt.Component#repaint( long )
   */
  public void setPosition( long pos, long patience, int type )
  {
    if( prefCatch && (catchBypassCount == 0) /* && timelineVis.contains( timelinePos ) */ &&
//      (timelineVis.stop < timelineLen) &&
      ((timelineVis.stop != timelineLen) || (pos < timelineVis.start)) &&
      !timelineVis.contains( pos + (type == TYPE_TRANSPORT ? timelineVis.getLength() >> 3 : 0) )) {
     
      timelinePos = pos;
      long    start;
      final long  stop;
     
      start  = timelinePos;
      if( type == TYPE_TRANSPORT ) {
        start -= timelineVis.getLength() >> 3;
      } else if( type == TYPE_DRAG ) {
        if( timelineVis.getStop() <= timelinePos ) {
          start -= timelineVis.getLength();
        }
      } else {
        start -= timelineVis.getLength() >> 2;
      }
      stop  = Math.min( timelineLen, Math.max( 0, start ) + timelineVis.getLength() );
      start  = Math.max( 0, stop - timelineVis.getLength() );
//        if( (stop > start) && ((start != timelineVis.getStart()) || (stop != timelineVis.getStop())) ) {
      if( stop > start ) {
        // it's crucial to update internal var timelineVis here because
        // otherwise the delay between emitting the edit and receiving the
        // change via timelineScrolled might be two big, causing setPosition
        // to fire more than one edit!
        timelineVis = new Span( start, stop );
        doc.timeline.editScroll( this, timelineVis );
//          doc.getUndoManager().addEdit( TimelineVisualEdit.scroll( this, doc, timelineVis ));
        return;
      }
    }
    timelinePos = pos;
    recalcTransforms();
    repaint( patience );
  }
 
  public void addCatchBypass()
  {
    if( ++catchBypassCount == 1 ) {
      catchBypassWasSynced = timelineVis.contains( timelinePos );
    }
  }
 
  public void removeCatchBypass()
  {
    if( (--catchBypassCount == 0) && catchBypassWasSynced ) {
      catchBypassWasSynced = false;
      if( prefCatch && !timelineVis.contains( timelinePos )) {
        long    start;
        final long  stop;
         
        start  = timelinePos - (timelineVis.getLength() >> 2);
        stop  = Math.min( timelineLen, Math.max( 0, start ) + timelineVis.getLength() );
        start  = Math.max( 0, stop - timelineVis.getLength() );
        if( stop > start ) {
          // it's crucial to update internal var timelineVis here because
          // otherwise the delay between emitting the edit and receiving the
          // change via timelineScrolled might be two big, causing setPosition
          // to fire more than one edit!
          timelineVis = new Span( start, stop );
          doc.timeline.editScroll( this, timelineVis );
        }
      }
    }
  }
 
// ---------------- DynamicListening interface ----------------

    public void startListening()
    {
        doc.timeline.addTimelineListener( this );
    recalcTransforms();
        repaint();
    }

    public void stopListening()
    {
        doc.timeline.removeTimelineListener( this );
    }
// ---------------- PreferenceChangeListener interface ----------------

  public void preferenceChange( PreferenceChangeEvent e )
  {
    final String  key  = e.getKey();
    final String  value  = e.getNewValue();

    if( !key.equals( PrefsUtil.KEY_CATCH )) return;
   
    prefCatch  = Boolean.valueOf( value ).booleanValue();
    if( !prefCatch ) return;
   
    catchBypassCount  = 0;
    adjustCatchBypass  = false;
    if( !(timelineVis.contains( timelinePos ))) {
      long    start  = Math.max( 0, timelinePos - (timelineVis.getLength() >> 2) );
      final long  stop  = Math.min( timelineLen, start + timelineVis.getLength() );
      start        = Math.max( 0, stop - timelineVis.getLength() );
      if( stop > start ) {
        doc.timeline.editScroll( this, new Span( start, stop ));
      }
    }
  }

// ---------------- TimelineListener interface ----------------

  public void timelineSelected( TimelineEvent e )
    {
    timelineSel = doc.timeline.getSelectionSpan();
    recalcTransforms();
        repaint();
    }
   
  public void timelineChanged( TimelineEvent e )
    {
    timelineLen = doc.timeline.getLength();
    timelineVis = doc.timeline.getVisibleSpan();
    for( timelineLenShift = 0; (timelineLen >> timelineLenShift) > 0x3FFFFFFF; timelineLenShift++ );
    recalcTransforms();
    recalcBoundedRange();
        repaint();
    }

  // ignored since the timeline frame will inform us
  public void timelinePositioned( TimelineEvent e ) { /* ignore */ }

    public void timelineScrolled( TimelineEvent e )
    {
    timelineVis = doc.timeline.getVisibleSpan();
    if( e.getSource() != adjustmentSource ) {
      recalcBoundedRange();
    }
    }

// ---------------- AdjustmentListener interface ----------------
// we're listening to ourselves

    public void adjustmentValueChanged( AdjustmentEvent e )
    {
      if( !isEnabled() ) return;
     
      final boolean  isAdjusting  = e.getValueIsAdjusting();
     
    final Span    oldVisi    = doc.timeline.getVisibleSpan();
    final Span    newVisi    = new Span( this.getValue() << timelineLenShift,
                     (this.getValue() + this.getVisibleAmount()) << timelineLenShift );
   
    if( prefCatch && isAdjusting && !wasAdjusting ) {
      adjustCatchBypass = true;
      addCatchBypass();
    } else if( wasAdjusting && !isAdjusting && adjustCatchBypass ) {
      if( prefCatch && !newVisi.contains( timelinePos )) {
        // we need to set prefCatch here even though laterInvocation will handle it,
        // because removeCatchBypass might look at it!
        prefCatch = false;
        AbstractApplication.getApplication().getUserPrefs().putBoolean( PrefsUtil.KEY_CATCH, false );
      }
      adjustCatchBypass = false;
      removeCatchBypass();
    }
   
    if( !newVisi.equals( oldVisi )) {
//      if( prefCatch && oldVisi.contains( timelinePos ) && !newVisi.contains( timelinePos )) {
//        AbstractApplication.getApplication().getUserPrefs().putBoolean( PrefsUtil.KEY_CATCH, false );
//      }
      doc.timeline.editScroll( adjustmentSource, newVisi );
        }
   
    wasAdjusting  = isAdjusting;
    }
}
TOP

Related Classes of de.sciss.eisenkraut.timeline.TimelineScroll

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.