Package de.sciss.swingosc

Source Code of de.sciss.swingosc.EnvelopeView$MouseAdapter

/*
*  EnvelopeView.java
*  SwingOSC
*
*  Copyright (c) 2005-2011 Hanns Holger Rutz. All rights reserved.
*
*  This software is free software; you can redistribute it and/or
*  modify it under the terms of the GNU General Public License
*  as published by the Free Software Foundation; either
*  version 2, june 1991 of the License, or (at your option) any later version.
*
*  This software is distributed in the hope that it will be useful,
*  but WITHOUT ANY WARRANTY; without even the implied warranty of
*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
*  General Public License for more details.
*
*  You should have received a copy of the GNU General Public
*  License (gpl.txt) along with this software; if not, write to the Free Software
*  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*
*
*  For further information, please contact Hanns Holger Rutz at
*  contact@sciss.de
*
*
*  Changelog:
*    05-Feb-07  created
*    26-Jul-07  added support for connection shapes, added horizEditMode
*    04-Apr-09  completed and fixed shapes drawing
*/

package de.sciss.swingosc;

import java.awt.BasicStroke;
import java.awt.Color;
//import java.awt.Composite;
import java.awt.FontMetrics;
import java.awt.Graphics2D;
import java.awt.Insets;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.Stroke;
import java.awt.event.MouseEvent;
import java.awt.geom.GeneralPath;
import java.awt.geom.Rectangle2D;
import java.awt.geom.RoundRectangle2D;
import java.io.IOException;

import javax.swing.event.MouseInputAdapter;

import de.sciss.net.OSCMessage;

/**
*  Swing implementation and extension of SCEnvelopeView by Jan Truetzschler.
*
@author    Hanns Holger Rutz
@version  0.61, 23-Apr-09
*/
public class EnvelopeView
extends AbstractMultiSlider
{
  /**
   *   Horizontal editing mode: Points can be freely moved unconstrainted
   */
  public static final int      HEDIT_FREE      = 0;
  /**
   *   Horizontal editing mode: Points can be moved only between adjectant nodes
   */
  public static final int      HEDIT_CLAMP      = 1;
  /**
   *   Horizontal editing mode: Points can be freely moved, but the node indices
   *  are relayed in order to maintain "causality"
   */
  public static final int      HEDIT_RELAY      = 2;
 
  protected Node[]         nodes      = new Node[ 0 ];
  // when setValues is called and the number of values
  // increases, the new nodes are created from the prototype
  // (thumb size + colour)
  private final Node        protoNode    = new Node( -1 );
 
  protected boolean         clipThumbs;
 
  private Color          selectionColor  = Color.black;
  protected int          recentWidth    = -1;
  protected int          recentHeight  = -1;
 
  private int            index       = -1;
 
  private boolean          connectionsUsed = false;
  protected int          horizEditMode  = HEDIT_FREE;
  protected boolean        lockBounds    = false;
 
//  private boolean          shiftMode    = false;
 
  private static final Stroke[]  strkRubber;
  private int            strkRubberIdx  = 0;
  protected final Rectangle    rubberRect    = new Rectangle();
  private static final Color    colrRubber    = new Color( 0x40, 0x40, 0x40 );
 
  // helper constants for shape calculation
  private static final float    EXPM1      = (float) Math.exp(-1);
  private static final float    EXPM1R      = (float) (1.0 - Math.exp(-1));

  private Node[]           dirtyNodes    = new Node[ 0 ];
  private int            numDirty    = 0;
  protected int          lastIndex    = -1;
   
  static {
    final float[] dash = new float[] { 4f, 4f };
    strkRubber = new Stroke[ 8 ];
    for( int i = 0; i < 8; i++ ) {
      strkRubber[ i ] = new BasicStroke( 1f, BasicStroke.CAP_SQUARE, BasicStroke.JOIN_MITER, 10f, dash, i );
    }
  }
 
  public EnvelopeView()
  {
    this( true );
  }

  public EnvelopeView( boolean clipThumbs )
  {
    super();
    this.clipThumbs = clipThumbs;
    drawLines    = true;

    final MouseAdapter ma = new MouseAdapter();
    addMouseListener( ma );
    addMouseMotionListener( ma );
  }
 
  public void setHorizontalEditMode( int mode )
  {
    horizEditMode = mode;
  }
 
  public void setLockBounds( boolean onOff ) {
    lockBounds = onOff;
  }

  protected void paintKnob( Graphics2D g2, int cw, int ch )
  {
//    final AffineTransform  atOrig  = g2.getTransform();
    final Shape        clipOrig  = g2.getClip();
    final int        h, w;
    final int         numValues    = nodes.length;
    final boolean      reallyDrawLines = drawLines && hasStroke && (numValues > 0);
    final GeneralPath    gpLines;
    final GeneralPath    gpOutline;
    final boolean      invalidAll    = (cw != recentWidth) || (ch != recentHeight);
    final boolean      drawORects    = drawRects && hasStroke;
    final FontMetrics    fm        = g2.getFontMetrics();
    float          bx, by, thumbWidthH, thumbHeightH;
    Node          n, n2;
    Shape          subClip;
   
    if( invalidAll ) {
      recentWidth    = cw;
      recentHeight  = ch;
    }
   
//    g2.setColor( getBackground() );
//    g2.fillRect( 0, 0, getWidth(), getHeight() );
//    g2.clearRect( 0, 0, getWidth(), getHeight() );

    cw -= 2;
    ch -= 2;
    g2.translate( 1, 1 );
    g2.clipRect( 0, 0, cw, ch );
//    cw -= 2;
//    ch -= 2;
   
    g2.setRenderingHint( RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON );
   
    w  = cw; // getWidth() - 2;
    h  = ch; // getHeight() - 2;

    gpLines   = reallyDrawLines ? new GeneralPath() : null;
    gpOutline  = (drawRects && hasStroke) ? new GeneralPath() : null;

    for( int i = 0; i < numValues; i++ ) {
      n = nodes[ i ];
      if( invalidAll || n.invalid ) {
        thumbWidthH    = n.thumbWidth / 2;
        thumbHeightH  = n.thumbHeight / 2;
        if( clipThumbs ) {
          n.cx  = w * n.x;
          n.cy  = h * (1f - n.y);
          bx    = n.cx - thumbWidthH;
          by    = n.cy - thumbHeightH;
        } else {
          bx    = (w - n.thumbWidth) * n.x;
          by    = (h - n.thumbHeight) * (1f - n.y);
          n.cx  = bx + thumbWidthH;
          n.cy  = by + thumbHeightH;
        }
        n.r.setFrame( bx, by, n.thumbWidth, n.thumbHeight );
        n.rr.setFrame( bx - 0.5f, by - 0.5f, n.thumbWidth, n.thumbHeight );
       
        if( n.label != null ) {
          n.tx = n.cx - fm.stringWidth( n.label ) / 2;
          n.ty = n.cy + (fm.getAscent() - fm.getDescent()) / 2 - 1;
        }
       
        n.invalid = false;
      }
      if( drawORects ) {
        gpOutline.append( n.rr, false );
      }
    }
    if( reallyDrawLines ) { // implies numValues >= 1
      if( connectionsUsed ) {  // -------------------- custom connections --------------------
        for( int i = 0; i < numValues; i++ ) {
          n = nodes[ i ];
          switch( n.shape ) {
          case Node.SHP_STEP:
            for( int j = 0; j < n.connections.length; j++ ) {
              n2 = n.connections[ j ];
              gpLines.moveTo( n.cx, n.cy );
              gpLines.lineTo( n.cx, n2.cy );
              gpLines.lineTo( n2.cx, n2.cy );
            }
            break;

          case Node.SHP_LINEAR:
            for( int j = 0; j < n.connections.length; j++ ) {
              n2 = n.connections[ j ];
              gpLines.moveTo( n.cx, n.cy );
              gpLines.lineTo( n2.cx, n2.cy );
            }
            break;

          case Node.SHP_SINE:
            for( int j = 0; j < n.connections.length; j++ ) {
              n2 = n.connections[ j ];
              gpLines.moveTo( n.cx, n.cy );
              gpLines.curveTo( n.cx * EXPM1R + n2.cx * EXPM1, n.cy, n.cx * EXPM1 + n2.cx * EXPM1R, n2.cy, n2.cx, n2.cy );
            }
            break;
 
          case Node.SHP_EXPONENTIAL:
          case Node.SHP_CURVE:
          case Node.SHP_WELCH:
          case Node.SHP_SQUARED:
          case Node.SHP_CUBED:
            for( int j = 0; j < n.connections.length; j++ ) {
              n2 = n.connections[ j ];
              final float oy1, oy2, sy1, sy2;
//              sx  = 1.0f / Math.max( 1, w );
//              final int   istep;
              final Node  n1s, n2s;
              if( n.x == n2.x ) {
                gpLines.moveTo( n.cx, n.cy );
                gpLines.lineTo( n2.cx, n2.cy );
                break;
              } else if( n.x < n2.x ) {
//                istep  = 2;
                n1s    = n;
                n2s    = n2;
              } else {
//                istep = -2;
                n1s    = n2;
                n2s    = n;
              }
              final float dx = n2s.cx - n1s.cx;
              if( clipThumbs ) {
                oy1 = 0f;
                oy2  = 0f;
                sy1  = h;
                sy2  = h;
              } else {
                oy1 = n1s.thumbHeight / 2;
                oy2  = n2s.thumbHeight / 2;
                sy1  = h - n1s.thumbHeight;
                sy2  = h - n2s.thumbHeight;
              }
              float ix = 0f; // n.cx - n1s.cx;
              float rx, ry;
              gpLines.moveTo( n1s.cx, n1s.cy );
              do {
//                ix = Math.max( 0, Math.min( dx, ix + istep ));
                ix = Math.min( dx, ix + 2 );
                rx = ix / dx;
                ry = (1f - envAt( n1s, n2s, rx ));
                gpLines.lineTo( ix + n1s.cx, (sy1 * ry + oy1) * (1 - rx) +
                                             (sy2 * ry + oy2) * rx );
              } while( ix != dx );
            }
            break;
          }
        }
      } else // -------------------- sequential connections --------------------
        n = nodes[ 0 ];
        for( int i = 1; i < numValues; i++ ) {
          n2 = nodes[ i ];
          switch( n.shape ) {
          case Node.SHP_STEP:
            gpLines.moveTo( n.cx, n.cy );
            gpLines.lineTo( n.cx, n2.cy );
            gpLines.lineTo( n2.cx, n2.cy );
//            n = n2;
            break;
           
          case Node.SHP_LINEAR:
            gpLines.moveTo( n.cx, n.cy );
            gpLines.lineTo( n2.cx, n2.cy );
            break;

          case Node.SHP_EXPONENTIAL:
          case Node.SHP_CURVE:
          case Node.SHP_WELCH:
          case Node.SHP_SQUARED:
          case Node.SHP_CUBED:
//            gpLines.moveTo( n.cx, n.cy );
            final float oy1, oy2, sy1, sy2;
//            sx  = 1.0f / Math.max( 1, w );
//            final int   istep;
            final Node  n1s, n2s;
            if( n.x == n2.x ) {
              gpLines.moveTo( n.cx, n.cy );
              gpLines.lineTo( n2.cx, n2.cy );
              break;
            } else if( n.x < n2.x ) {
//              istep  = 2;
              n1s    = n;
              n2s    = n2;
            } else {
//              istep = -2;
              n1s    = n2;
              n2s    = n;
            }
            final float dx = n2s.cx - n1s.cx;
            if( clipThumbs ) {
              oy1 = 0f;
              oy2  = 0f;
              sy1  = h;
              sy2  = h;
            } else {
              oy1 = n1s.thumbHeight / 2;
              oy2  = n2s.thumbHeight / 2;
              sy1  = h - n1s.thumbHeight;
              sy2  = h - n2s.thumbHeight;
            }
            float ix = 0f; // n.cx - n1s.cx;
            float rx, ry;
            gpLines.moveTo( n1s.cx, n1s.cy );
            do {
//              ix = Math.max( 0, Math.min( dx, ix + istep ));
              ix = Math.min( dx, ix + 2 );
              rx = ix / dx;
              ry = (1f - envAt( n1s, n2s, rx ));
              gpLines.lineTo( ix + n1s.cx, (sy1 * ry + oy1) * (1 - rx) +
                                           (sy2 * ry + oy2) * rx );
            } while( ix != dx );
            break;

          case Node.SHP_SINE:
            gpLines.moveTo( n.cx, n.cy );
            gpLines.curveTo( n.cx * EXPM1R + n2.cx * EXPM1, n.cy, n.cx * EXPM1 + n2.cx * EXPM1R, n2.cy, n2.cx, n2.cy );
            break;

//          case Node.SHP_WELCH:
//            // approximate... WRONG
//            gpLines.moveTo( n.cx, n.cy );
//            if( n.y < n2.y ) {
//              gpLines.curveTo( n.cx * 0.556f + n2.cx * 0.444f, n.cy * 0.278f + n2.cy * 0.722f, n.cx * 0.29f + n2.cx * 0.71f, n2.cy, n2.cx, n2.cy );
//            } else {
//              gpLines.curveTo( n2.cx * 0.29f + n.cx * 0.71f, n.cy, n2.cx * 0.556f + n.cx * 0.444f, n2.cy * 0.278f + n.cy * 0.722f, n2.cx, n2.cy );
//            }
//            break;

//          case Node.SHP_SQUARED:
//            // approximate... WRONG
//            gpLines.moveTo( n.cx, n.cy );
//            if( n.y < n2.y ) {
//              gpLines.curveTo( n.cx * 0.9f + n2.cx * 0.1f, n.cy, n.cx * 0.5f + n2.cx * 0.5f, n.cy, n2.cx, n2.cy );
//            } else {
//              gpLines.curveTo( n2.cx * 0.5f + n.cx * 0.5f, n2.cy, n2.cx * 0.9f + n.cx * 0.1f, n2.cy, n2.cx, n2.cy );
//            }
//            break;

//          case Node.SHP_CUBED:
//            // approximate... WRONG
//            gpLines.moveTo( n.cx, n.cy );
//            if( n.y < n2.y ) {
//              gpLines.curveTo( n.cx * 0.6667f + n2.cx * 0.3333f, n.cy, n.cx * 0.3333f + n2.cx * 0.6667f, n.cy, n2.cx, n2.cy );
//            } else {
//              gpLines.curveTo( n2.cx * 0.3333f + n.cx * 0.6667f, n2.cy, n2.cx * 0.6667f + n.cx * 0.3333f, n2.cy, n2.cx, n2.cy );
//            }
//            break;
          }
          n = n2;
        }
      }
      g2.setColor( strokeColor );
      g2.draw( gpLines );
    }
    if( drawRects ) {
      for( int i = 0; i < numValues; i++ ) {
        n = nodes[ i ];
        if( n.selected ) {
          g2.setColor( selectionColor );
          g2.fill( n.r );
        } else {
          if( n.fillColor != null ) {
            g2.setColor( n.fillColor );
            g2.fill( n.r );
          }
        }
        if( n.label !=  null ) {
          g2.setColor( strokeColor );
          subClip = g2.getClip();
          g2.clip( n.r );
          g2.drawString( n.label, n.tx, n.ty );
          g2.setClip( subClip );
        }
      }
      if( hasStroke ) {
        g2.setColor( strokeColor );
        g2.draw( gpOutline );
      }
    }
//      if( showIndex && (selectedIndex >= 0) && (n == 0) ) {
//        r.setFrame( (selectedIndex - startIndex) * xStep, 0, selectionSize * xStep - xOffset, h );
//        g2.setColor( indexColor );
//        g2.fill( r );
//      }
//    g2.setTransform( atOrig );
   
    if( !rubberRect.isEmpty() ) {
// XOR-mode not working properly on Linux / Windows !!
//      final Composite cmpOrig    = g2.getComposite();
      final Stroke  strkOrig  = g2.getStroke();
//      g2.setXORMode( colrRubber );
      g2.setColor( colrRubber );
      g2.setStroke( strkRubber[ (strkRubberIdx++) & 0x07 ]);
      g2.draw( rubberRect );
      g2.setStroke( strkOrig );
//      g2.setComposite( cmpOrig );
    }
   
    g2.setClip( clipOrig );
  }
 
  public void setClipThumbs( boolean onOff )
  {
    if( onOff != clipThumbs ) {
      clipThumbs = onOff;
      recentWidth   = -1; // triggers re-calculations
      recentHeight = -1;
      repaint();
    }
  }
 
  public void setIndex( int idx )
  {
    index = idx;
  }
 
  public int getLastIndex()
  {
    return lastIndex;
  }
 
  public void setX( float x )
  {
    if( (index >= 0) && (index < nodes.length) ) {
      nodes[ index ].x = x;
      nodes[ index ].invalid = true;
      repaint();
    }
  }
 
  public void setY( float y )
  {
    if( (index >= 0) && (index < nodes.length) ) {
      nodes[ index ].y = y;
      nodes[ index ].invalid = true;
      repaint();
    }
  }
 
//  public void setSelectedIndex( int idx )
//  {
//    if( idx != selectedIndex ) {
//      selectedIndex = idx;
//      repaint();
//    }
//  }
 
  public void setSelectionColor( Color c )
  {
    selectionColor = c;
  }
 
  public void setLabel( int index, String label )
  {
    if( index == -1 ) {
      for( int i = 0; i < nodes.length; i++ ) {
        nodes[ i ].label = label;
        nodes[ i ].invalid = true;
      }
    } else {
      nodes[ index ].label = label;
      nodes[ index ].invalid = true;
    }
//    protoNode.label = label;
    repaint();
  }

  public void setShape( int index, int shape, float curve )
  {
    if( index == -1 ) {
      for( int i = 0; i < nodes.length; i++ ) {
        nodes[ i ].shape = shape;
        nodes[ i ].curve = curve;
//        nodes[ i ].invalid = true;
      }
    } else {
      nodes[ index ].shape = shape;
      nodes[ index ].curve = curve;
//      nodes[ index ].invalid = true;
    }
    protoNode.shape = shape;
    protoNode.curve = curve;
    repaint();
  }

  public void sendDirtyValues( Object id )
  {
    final SwingOSC    osc      = SwingOSC.getInstance();
    final SwingClient  client    = osc.getCurrentClient();
    final Object[]     replyArgs  = new Object[ 2 + (numDirty * 4) ];
    Node n;
    replyArgs[ 0 ]          = id;
    replyArgs[ 1 ]          = new Integer( numDirty );

    for( int i = 0, j = 2; i < numDirty; i++ ) {
      n = dirtyNodes[ i ];
      replyArgs[ j++ ] = new Integer( n.idx );
      replyArgs[ j++ ] = new Float( n.x );
      replyArgs[ j++ ] = new Float( n.y );
      replyArgs[ j++ ] = new Integer( n.selected ? 1 : 0 );
    }
   
    try {
      client.reply( new OSCMessage( "/values", replyArgs ));
    }
    catch( IOException ex ) {
      SwingOSC.printException( ex, "sendDirtyValues" );
    }
  }
 
  public void sendDirtyValuesAndClear( Object id )
  {
    sendDirtyValues( id );
    clearDirty();
  }

  public void setStepSize( float stepSize )
  { 
    super.setStepSize( stepSize );
   
    Node n;
   
    if( stepSize > 0f ) {
      for( int i = 0; i < nodes.length; i++ ) {
        n = nodes[ i ];
        n.x = snap( n.x );
        n.y = snap( n.y );
        n.invalid = true;
      }
      repaint();
    }
  }
 
  public void setReadOnly( int index, boolean readOnly )
  {
    if( index == -1 ) {
      for( int i = 0; i < nodes.length; i++ ) {
        nodes[ i ].readOnly = readOnly;
      }
    } else {
      nodes[ index ].readOnly = readOnly;
    }
//    protoNode.readOnly = readOnly;
  }

  public void setSelected( int index, boolean selected )
  {
    if( index == -1 ) {
      for( int i = 0; i < nodes.length; i++ ) {
        nodes[ i ].selected = selected;
      }
    } else {
      nodes[ index ].selected = selected;
    }
    if( drawRects ) repaint();
  }

  public void setFillColor( int index, Color c )
  {
    if( c.getAlpha() == 0 ) c = null;
    if( index == -1 ) {
      for( int i = 0; i < nodes.length; i++ ) {
        nodes[ i ].fillColor = c;
      }
    } else {
      nodes[ index ].fillColor = c;
    }
    protoNode.fillColor = c;
    if( drawRects ) repaint();
  }

  public void setStrokeColor( Color c )
  {
    strokeColor  = c;
    repaint();
  }

  public void setThumbSize( int index, float size )
  {
    if( index == -1 ) {
      for( int i = 0; i < nodes.length; i++ ) {
        nodes[ i ].thumbWidth = size;
        nodes[ i ].thumbHeight = size;
        nodes[ i ].invalid = true;
      }
    } else {
      nodes[ index ].thumbWidth = size;
      nodes[ index ].thumbHeight = size;
      nodes[ index ].invalid = true;
    }
    protoNode.thumbWidth = size;
    protoNode.thumbHeight = size;
    repaint();
  }
 
  public void setThumbWidth( int index, float w )
  {
    if( index == -1 ) {
      for( int i = 0; i < nodes.length; i++ ) {
        nodes[ i ].thumbWidth = w;
        nodes[ i ].invalid = true;
      }
    } else {
      nodes[ index ].thumbWidth = w;
      nodes[ index ].invalid = true;
    }
    protoNode.thumbWidth = w;
    repaint();
  }

  public void setThumbHeight( int index, float h )
  {
    if( index == -1 ) {
      for( int i = 0; i < nodes.length; i++ ) {
        nodes[ i ].thumbHeight = h;
        nodes[ i ].invalid = true;
      }
    } else {
      nodes[ index ].thumbHeight = h;
      nodes[ index ].invalid = true;
    }
    protoNode.thumbHeight = h;
    repaint();
  }

  public void setValues( float[] x, float[] y )
  {
    final int  newNumVals = x.length;
    final int  oldNumVals = nodes.length;
    final int  minNum;
    Node[]    tmp;
   
    if( y.length != newNumVals ) throw new IllegalArgumentException();
   
//    if( selectedIndex >= 0 ) {
//      selectedIndex = Math.min( selectedIndex, newNumVals - 1 );
//      selectionSize = Math.min( selectionSize, newNumVals - selectedIndex );
//    }
   
    if( oldNumVals != newNumVals ) {
      tmp      = new Node[ newNumVals ];
      minNum    = Math.min( oldNumVals, newNumVals );
      System.arraycopy( nodes, 0, tmp, 0, minNum );
      for( int i = minNum; i < newNumVals; i++ ) {
        tmp[ i ] = new Node( i, protoNode );
      }
      nodes    = tmp;
      tmp      = new Node[ newNumVals ];
      System.arraycopy( dirtyNodes, 0, tmp, 0, minNum );
      dirtyNodes  = tmp;
      numDirty  = Math.min( numDirty, newNumVals );
    }
   
    for( int i = 0; i < newNumVals; i++ ) {
      nodes[ i ].x = x[ i ];
      nodes[ i ].y = y[ i ];
      nodes[ i ].invalid = true;
    }

    repaint();
  }

  public void setValues( float[] x, float[] y, int[] shapes, float[] curves )
  {
    final int  newNumVals = x.length;
    final int  oldNumVals = nodes.length;
    final int  minNum;
    Node[]    tmp;
   
    if( y.length != newNumVals ) throw new IllegalArgumentException();
   
//    if( selectedIndex >= 0 ) {
//      selectedIndex = Math.min( selectedIndex, newNumVals - 1 );
//      selectionSize = Math.min( selectionSize, newNumVals - selectedIndex );
//    }
   
    if( oldNumVals != newNumVals ) {
      tmp      = new Node[ newNumVals ];
      minNum    = Math.min( oldNumVals, newNumVals );
      System.arraycopy( nodes, 0, tmp, 0, minNum );
      for( int i = minNum; i < newNumVals; i++ ) {
        tmp[ i ] = new Node( i, protoNode );
      }
      nodes    = tmp;
      tmp      = new Node[ newNumVals ];
      System.arraycopy( dirtyNodes, 0, tmp, 0, minNum );
      dirtyNodes  = tmp;
      numDirty  = Math.min( numDirty, newNumVals );
    }
   
    for( int i = 0; i < newNumVals; i++ ) {
      nodes[ i ].x    = x[ i ];
      nodes[ i ].y    = y[ i ];
      nodes[ i ].shape  = shapes[ i ];
      nodes[ i ].curve  = curves[ i ];
      nodes[ i ].invalid  = true;
    }

    repaint();
  }

  public void setValues( Object[] x, Object[] y )
  {
    final float[] fx = new float[ x.length ];
    final float[] fy = new float[ y.length ];
    for( int i = 0; i < fx.length; i++ ) {
      fx[ i ] = ((Number) x[ i ]).floatValue();
      fy[ i ] = ((Number) y[ i ]).floatValue();
    }
    setValues( fx, fy );
  }

  public void setValues( Object[] x, Object[] y, Object[] shapes, Object[] curves )
  {
    final float[]  fx    = new float[ x.length ];
    final float[]  fy    = new float[ y.length ];
    final int[]    ishapes  = new int[ shapes.length ];
    final float[]  fcurves  = new float[ curves.length ];
    for( int i = 0; i < fx.length; i++ ) {
      fx[ i ]      = ((Number) x[ i ]).floatValue();
      fy[ i ]     = ((Number) y[ i ]).floatValue();
      ishapes[ i = ((Number) shapes[ i ]).intValue();
      fcurves[ i = ((Number) curves[ i ]).floatValue();
    }
    setValues( fx, fy, ishapes, fcurves );
  }

  public void setValues( Object[] x, Object[] y, int shape, float curve )
  {
    final float[]  fx    = new float[ x.length ];
    final float[]  fy    = new float[ y.length ];
    final int[]    shapes  = new int[ x.length ];
    final float[]  curves  = new float[ x.length ];
    for( int i = 0; i < fx.length; i++ ) {
      fx[ i ]   = ((Number) x[ i ]).floatValue();
      fy[ i ]   = ((Number) y[ i ]).floatValue();
      shapes[ i = shape;
      curves[ i = curve;
    }
    setValues( fx, fy, shapes, curves );
  }

//  public float[] getValues()
//  {
//    return values;
//  }
 
  public void setConnections( int index, int[] targets )
  {
    final Node[] nTargets = new Node[ targets.length ];
    for( int i = 0; i < targets.length; i++ ) {
      nTargets[ i ] = nodes[ targets[ i ]];
    }
    nodes[ index ].connections = nTargets;
    if( drawLines && connectionsUsed ) repaint();
  }
 
  public void setConnections( int index, Object[] targets )
  {
    final int[] iTargets = new int[ targets.length ];
    for( int i = 0; i < targets.length; i++ ) {
      iTargets[ i ] = ((Number) targets[ i ]).intValue();
    }
    setConnections( index, iTargets );
  }
 
  public void setConnectionsUsed( boolean onOff )
  {
    if( onOff != connectionsUsed ) {
      connectionsUsed = onOff;
      repaint();
    }
  }
 
  public int getDirtySize()
  {
    return( numDirty );
  }
 
  public void clearDirty()
  {
    for( int i = 0; i < numDirty; i++ ) {
      dirtyNodes[ i ].dirty = false;
    }
    numDirty = 0;
  }

  protected void dirty( Node n )
  {
    if( !n.dirty ) {
      n.dirty = true;
      dirtyNodes[ numDirty++ ] = n;
    }
  }
 
  /*
   *   Analoguous to the code in PyrArrayPrimitives.cpp.
   *  
   *   @param  x  a relative position between 0.0 (at n1)
   *         and 1.0 (at n2)
   */
  private float envAt( Node n1, Node n2, float pos )
  {
    if( nodes.length == 0 ) return 0f;
   
    final float /* pos, */ y;
   
//    for( idx = 0; idx < nodes.length; idx++ ) {
//      if( nodes[ idx ].x > x ) break;
//    }
//    n1  = nodes[ Math.max( 0, idx - 1 )];
//    n2  = nodes[ Math.min( nodes.length - 1, idx )];
//    if( n1.x < n2.x ) {
//      tmp  = n1;
//      n1  = n2;
//      n2  = tmp;
//    }
   
//    if( x <= n1.x ) return n1.y;
//    if( x >= n2.x ) return n2.y;
//    pos  = (x - n1.x) / (n2.x - n1.x);
    if( pos <= 0f ) return n1.y;
    if( pos >= 1f ) return n2.y;

    switch( n1.shape ) {
    case Node.SHP_STEP:
      y = n2.y;
      break;
     
    case Node.SHP_LINEAR:
    default:
      y = pos * (n2.y - n1.y) + n1.y;
      break;
     
    case Node.SHP_EXPONENTIAL:
      final float y1Lim = Math.max( 0.0001f, n1.y );
      y = (float) (y1Lim * Math.pow( n2.y / y1Lim, pos ));
      break;
     
    case Node.SHP_SINE:
      y = (float) (n1.y + (n2.y - n1.y) * (-Math.cos( Math.PI * pos ) * 0.5 + 0.5));
      break;
     
    case Node.SHP_WELCH:
      if( n1.y < n2.y ) {
        y = (float) (n1.y + (n2.y - n1.y) * Math.sin( Math.PI * 0.5 * pos ));
      } else {
        y = (float) (n2.y - (n2.y - n1.y) * Math.sin( Math.PI * 0.5 * (1 - pos) ));
      }
      break;
     
    case Node.SHP_CURVE:
      if( Math.abs( n1.curve ) < 0.0001f ) {
        y = pos * (n2.y - n1.y) + n1.y;
      } else {
        final double denom  = 1.0 - Math.exp( n1.curve );
        final double numer  = 1.0 - Math.exp( pos * n1.curve );
        y = (float) (n1.y + (n2.y - n1.y) * (numer / denom));
      }
      break;
     
    case Node.SHP_SQUARED:
      final double y1Pow2  = Math.sqrt( n1.y );
      final double y2Pow2  = Math.sqrt( n2.y );
      final double yPow2  = pos * (y2Pow2 - y1Pow2) + y1Pow2;
      y = (float) (yPow2 * yPow2);
      break;

    case Node.SHP_CUBED:
      final double y1Pow3  = Math.pow( n1.y, 0.3333333 );
      final double y2Pow3  = Math.pow( n2.y, 0.3333333 );
      final double yPow3  = pos * (y2Pow3 - y1Pow3) + y1Pow3;
      y = (float) (yPow3 * yPow3 * yPow3);
      break;
    }

    return y;
  }

 
// --------------- internal classes ---------------
 
  private class MouseAdapter
  extends MouseInputAdapter
  {
    private boolean      shiftDrag    = false;
    private Point      dragFirstPt    = null;
    private boolean      dragRubber;
    private final Rectangle  oldRubberRect  = new Rectangle();
    private final Rectangle  mouseRect    = new Rectangle(); // e.g. rect around mouse for easier nav

    protected MouseAdapter() { /* empty */ }
   
    private Point insetMouse( MouseEvent e )
    {
      final Insets ins  = getInsets();
      final Point  pt    = e.getPoint();
     
      pt.translate( -(ins.left + 1), -(ins.top + 1) );
     
      return pt;
    }
   
    private Node findNode( Point pt )
    {
      Node n;
      // note that we do a little outline since we check against n.r not n.rr!
      mouseRect.setBounds( pt.x - 1, pt.y - 1, 4, 4 );
     
      for( int i = 0; i < nodes.length; i++ ) {
        n = nodes[ i ];
//        if( !n.invalid && n.r.contains( pt )) return n;
        if( !n.invalid && mouseRect.intersects( n.r )) return n;
      }
      return null;
    }
   
//    private Point2D screenToVirtual( Point screen )
//    {
//     
//    }

    public void mousePressed( MouseEvent e )
    {
      if( !isEnabled() || e.isControlDown() ) return;

      requestFocus();
     
//      final Point2D  pt  = screenToVirtual( e.getPoint() );
      dragFirstPt    = insetMouse( e );
      final Node    n    = findNode( dragFirstPt );
      boolean     repaint = false;
      boolean     action  = false;
      Node      n2;

      shiftDrag    = e.isShiftDown();
      dragRubber    = n == null;
     
      if( shiftDrag ) {
        if( dragRubber ) return;
        n.selected  = !n.selected;
        dirty( n );
        repaint    = true;
        action    = true;
      } else {
        if( dragRubber || !n.selected ) {
          lastIndex  = -1;
          for( int i = 0; i < nodes.length; i++ ) {
            n2 = nodes[ i ];
            if( (n != n2) && n2.selected ) {
              n2.selected  = false;
              dirty( n2 );
              repaint    = true;
              action    = true;
            }
          }
        }
        if( !dragRubber && !n.selected ) {
          n.selected  = true;
          dirty( n );
          repaint    = true;
          action    = true;
        }
      }
      if( !dragRubber ) {
        lastIndex  = n.idx;
        for( int i = 0; i < nodes.length; i++ ) {
          n2    = nodes[ i ];
          n2.oldX = n2.x;
          n2.oldY = n2.y;
        }
      }
      if( repaint ) repaint();
      if( action ) fireActionPerformed();
    }
   
    public void mouseReleased( MouseEvent e )
    {
      dragFirstPt = null;
// macht das sinn?
//      lastIndex  = -1;
      if( dragRubber ) {
        rubberRect.setBounds( 0, 0, 0, 0 );
        repaint();
      }
    }
   
//private boolean korrupt = false;
   
    public void mouseDragged( MouseEvent e )
    {
//if( korrupt ) return;
      if( !isEnabled() || (dragFirstPt == null) ) return;
     
      final Point dragCurrentPt = insetMouse( e );
//      final int idxStart, idxInc;
      Node n, n2, nPred = null, nSucc;
      boolean repaint  = false;
      boolean action  = false;
      boolean reallyNotLocked;
     
      if( dragRubber ) {    // ------------------ rubber band ------------------
        oldRubberRect.setBounds( rubberRect );
        rubberRect.setFrameFromDiagonal( dragFirstPt, dragCurrentPt );
        for( int i = 0; i < nodes.length; i++ ) {
          n = nodes[ i ];
          if( n.invalid ) continue;
          if( oldRubberRect.intersects( n.r ) != rubberRect.intersects( n.r )) {
            n.selected = !n.selected;
            dirty( n );
            action  = true;
          }
        }
        repaint = true;
      } else if( (recentWidth > 0) && (recentHeight > 0) ) {    // ------------------ move ------------------
        final float dx, dy;
        float x, y;
        if( clipThumbs ) {
          dx = (float) (dragCurrentPt.x - dragFirstPt.x) / recentWidth;
          dy = (float) (dragFirstPt.y - dragCurrentPt.y) / recentHeight;
        } else {
          dx = dragCurrentPt.x - dragFirstPt.x;
          dy = dragFirstPt.y - dragCurrentPt.y;
        }
//        if( dx >= 0f ) {
//          idxStart = 0;
//          idxInc   = 1;
//        } else {
//          idxStart = nodes.length - 1;
//          idxInc   = -1;
//        }
        final Node[] nodesBak = new Node[ nodes.length ];
        for( int i = 0; i < nodes.length; i++ ) {
          nodes[ i ].done = false;
          nodesBak[ i ] = new Node( nodes[ i ].idx, nodes[ i ]);
          nodesBak[ i ].x = nodes[ i ].x;
          nodesBak[ i ].y = nodes[ i ].y;
          nodesBak[ i ].selected = nodes[ i ].selected;
        }
        for( int i = 0; i < nodes.length; i++ ) {
          n = nodes[ i ];
          if( !n.selected || n.done || n.readOnly ) {
            n.done = true;
            continue;
          }

          reallyNotLocked = !lockBounds || ((i > 0) && (i < nodes.length - 1));
          if( clipThumbs ) {
            if( reallyNotLocked ) {
              x  = snap( Math.max( 0f, Math.min( 1f, n.oldX + dx )));
            } else {
              x  = n.x;
            }
            y    = snap( Math.max( 0f, Math.min( 1f, n.oldY + dy )));
          } else {
            if( reallyNotLocked ) {
              x  = snap( Math.max( 0f, Math.min( 1f, n.oldX +
                  dx / Math.max( 1, recentWidth - n.thumbWidth ))));
            } else {
              x  = n.x;
            }
            y    = snap( Math.max( 0f, Math.min( 1f, n.oldY +
                  dy / Math.max( 1, recentHeight - n.thumbHeight ))));
          }
          n2 = n;
          if( reallyNotLocked && (horizEditMode != HEDIT_FREE) ) {
            if( horizEditMode == HEDIT_CLAMP ) {  ///////////////////
              if( x < n.x ) {
                for( int j = i - 1; j >= 0; j-- ) {
                  if( !nodes[ j ].selected ) {
                    nPred  = nodes[ j ];
                    x = Math.max( x, nPred.x );
                    break;
                  }
                }
              } else if( x > n.x ) {
                for( int j = i + 1; j < nodes.length; j++ ) {
                  if( !nodes[ j ].selected ) {
                    nSucc  = nodes[ j ];
                    x    = Math.min( x, nSucc.x );
                    break;
                  }
                }
              } // else nothing (no x change)
             
            } else if( horizEditMode == HEDIT_RELAY ) {  ////////////////
              int pos = i;
              for( int j = i + 1; j < nodes.length; j++ ) {
                if( !nodes[ j ].selected || nodes[ j ].readOnly ) {
                  if( nodes[ j ].x >= x ) break;
                  pos = j;
                }
              }
              if( pos > i ) {  // shift left
                nPred = n;
                final float oldX = n.oldX;
                final float oldY = n.oldY;
                for( int j = i + 1; j <= pos; j++ ) {
                  nSucc     = nodes[ j ];
                  nPred.x    = nSucc.x;
                  nPred.y    = nSucc.y;
                  nPred.selected = nSucc.selected;
                  nPred.oldX  = nSucc.oldX;
                  nPred.oldY  = nSucc.oldY;
                  dirty( nPred );
                  nPred.invalid  = true;
                  nPred    = nSucc;
                }
                nPred.x      = x;
                nPred.y      = y;
                nPred.selected  = true;
                nPred.oldX    = oldX;
                nPred.oldY    = oldY;
                nPred.done    = true;
                dirty( nPred );
                nPred.invalid  = true;
                repaint      = true;
                action      = true;
                n2        = nPred;
               
              } else { // check backwards
                for( int j = i - 1; j >= 0; j-- ) {
                  if( !nodes[ j ].selected || nodes[ j ].readOnly ) {
                    if( nodes[ j ].x <= x ) break;
                    pos = j;
                  }
                }
                if( pos < i ) {  // shift right
                  nSucc = n;
                  final float oldX = n.oldX;
                  final float oldY = n.oldY;
                  for( int j = i - 1; j >= pos; j-- ) {
                    nPred     = nodes[ j ];
                    nSucc.x    = nPred.x;
                    nSucc.y    = nPred.y;
                    nSucc.selected = nPred.selected;
                    nSucc.oldX  = nPred.oldX;
                    nSucc.oldY  = nPred.oldY;
                    dirty( nSucc );
                    nSucc.invalid  = true;
                    nSucc    = nPred;
                  }
                  nSucc.x      = x;
                  nSucc.y      = y;
                  nSucc.selected  = true;
                  nSucc.oldX    = oldX;
                  nSucc.oldY    = oldY;
                  nSucc.done    = true;
                  dirty( nSucc );
                  nSucc.invalid  = true;
                  repaint      = true;
                  action      = true;
                  n2        = nSucc;
                }
              }
            }
          }
          if( (x != n2.x) || (y != n2.y) ) {
            n2.x    = x;
            n2.y    = y;
            dirty( n2 );
            n2.invalid  = true;
            repaint    = true;
            action    = true;
          }
        }

//        if( !korrupt ) {
//          for( int ii = 1; ii < nodes.length; ii++ ) {
//            if( nodes[ ii ].x < nodes[ ii - 1 ].x ) {
//              System.out.println( "KORRUPT. Bak = " );
//              korrupt = true;
//              for( int jj = 0; jj < nodes.length; jj++ ) {
//                System.out.println( "" + jj + " (" + nodesBak[ jj ].x + ", " + nodesBak[ jj ].y + ") " + nodesBak[ jj ].selected );
//              }
//              System.out.println( "         New = " );
//              for( int jj = 0; jj < nodes.length; jj++ ) {
//                System.out.println( "" + jj + " (" + nodes[ jj ].x + ", " + nodes[ jj ].y + ")" + nodes[ jj ].selected );
//              }
//            }
//          }
//        }

      }

      if( repaint ) repaint();
      if( action ) fireActionPerformed();

//      if( selectedIndex != -1 ) {
//        nodeJump( e );
//      }
//      processMouse( e, false );
    }
  }

  private static class Node
  {
    private static final Node[]    NO_CONNECTIONS  = new Node[ 0 ];
   
    private static final int    SHP_STEP    = 0;
    private static final int    SHP_LINEAR    = 1;
    private static final int    SHP_EXPONENTIAL  = 2;
    private static final int    SHP_SINE    = 3;
    private static final int    SHP_WELCH    = 4;
    private static final int    SHP_CURVE    = 5;
    private static final int    SHP_SQUARED    = 6;
    private static final int    SHP_CUBED    = 7;
   
    protected float          x        = 0f;
    protected float          y        = 0f;
    protected int          shape      = SHP_LINEAR;
    protected float          curve      = 0f;
    protected Color          fillColor     = Color.black;
    protected float          thumbWidth     = 5f; // 12f;
    protected float          thumbHeight    = 5f; // 12f;
   
    protected boolean        readOnly    = false;
    protected boolean        selected    = false;
    protected Node[]        connections    = NO_CONNECTIONS;
    protected String        label      = null;
    protected final Rectangle2D    r        = new Rectangle2D.Float();
    protected final RoundRectangle2D rr        = new RoundRectangle2D.Float( 0f, 0f, 0f, 0f, 2f, 2f );
    protected boolean        invalid      = true;
    protected boolean        done;  // for dnd
    protected float          cx, cy, tx, ty;
   
    // dnd
    protected boolean        dirty      = false;
    protected float          oldX, oldY;
   
    protected final int        idx;
   
    protected Node( int idx )
    {
      this.idx  = idx;
    }
   
    protected Node( int idx, Node orig )
    {
      this.fillColor    = orig.fillColor;
      this.thumbWidth    = orig.thumbWidth;
      this.thumbHeight  = orig.thumbHeight;
      this.shape      = orig.shape;
      this.curve      = orig.curve;
      this.idx      = idx;
    }
  }
}
TOP

Related Classes of de.sciss.swingosc.EnvelopeView$MouseAdapter

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.
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) })(window,document,'script','//www.google-analytics.com/analytics.js','ga'); ga('create', 'UA-20639858-1', 'auto'); ga('send', 'pageview');