/*
* SurfacePane.java
* Meloncillo
*
* Copyright (c) 2004-2008 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:
* 13-Jun-04 Removed SurfacePaneListener interface
* 23-Jul-04 collTransmitterShapes/Path sync removed because it was superfluous
* 27-Jul-04 is a RealtimeConsumer
* 01-Aug-04 commented. added checkTrnsNames(); moved trns+rcv coll. listener
* registration from dyn.list to constructor.
* 08-Aug-04 dynamic prefs listener, new view flags. freehand works.
* 11-Aug-04 extends JComponent, not JPanel
* 02-Sep-04 darker green colour for transmitters when not showing rcv sense
* 27-Dec-04 added online help. opening receiver editor switches to observer cursor tab
* 15-Jan-05 renamed to SurfacePane, moved to separate package
* 02-Feb-05 pencil tool will automatically start/stop transport
* 19-Mar-05 only active transmitters are displayed
* 18-Apr-05 fixed arc tool
*
* XXX TO-DO : dragging multiple receivers should collapse into a compound edit!
*/
package de.sciss.meloncillo.surface;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.MediaTracker;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.Stroke;
import java.awt.Toolkit;
import java.awt.Transparency;
import java.awt.color.ColorSpace;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionAdapter;
import java.awt.event.MouseMotionListener;
import java.awt.geom.AffineTransform;
import java.awt.geom.Arc2D;
import java.awt.geom.Area;
import java.awt.geom.CubicCurve2D;
import java.awt.geom.Ellipse2D;
import java.awt.geom.GeneralPath;
import java.awt.geom.Line2D;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.awt.image.ComponentColorModel;
import java.awt.image.DataBuffer;
import java.awt.image.Raster;
import java.awt.image.WritableRaster;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.prefs.PreferenceChangeEvent;
import java.util.prefs.PreferenceChangeListener;
import javax.swing.JComponent;
import javax.swing.Timer;
import de.sciss.app.AbstractApplication;
import de.sciss.app.AbstractCompoundEdit;
import de.sciss.app.AbstractWindow;
import de.sciss.app.DynamicAncestorAdapter;
import de.sciss.app.DynamicListening;
import de.sciss.app.DynamicPrefChangeManager;
import de.sciss.app.PerformableEdit;
import de.sciss.common.ProcessingThread;
import de.sciss.gui.ProgressComponent;
import de.sciss.gui.StringItem;
import de.sciss.gui.TopPainter;
import de.sciss.io.Span;
import de.sciss.meloncillo.Main;
import de.sciss.meloncillo.edit.BasicCompoundEdit;
import de.sciss.meloncillo.edit.CompoundSessionObjEdit;
import de.sciss.meloncillo.edit.EditAddSessionObjects;
import de.sciss.meloncillo.edit.EditSetReceiverAnchor;
import de.sciss.meloncillo.edit.EditSetSessionObjects;
import de.sciss.meloncillo.gui.AbstractTool;
import de.sciss.meloncillo.gui.GraphicsUtil;
import de.sciss.meloncillo.gui.ObserverPalette;
import de.sciss.meloncillo.gui.ToolAction;
import de.sciss.meloncillo.gui.ToolActionEvent;
import de.sciss.meloncillo.gui.ToolActionListener;
import de.sciss.meloncillo.gui.VirtualSurface;
import de.sciss.meloncillo.io.AudioStake;
import de.sciss.meloncillo.io.AudioTrail;
import de.sciss.meloncillo.io.BlendContext;
import de.sciss.meloncillo.io.DecimatedWaveTrail;
import de.sciss.meloncillo.io.DecimationInfo;
import de.sciss.meloncillo.math.MathUtil;
import de.sciss.meloncillo.realtime.RealtimeConsumer;
import de.sciss.meloncillo.realtime.RealtimeConsumerRequest;
import de.sciss.meloncillo.realtime.RealtimeContext;
import de.sciss.meloncillo.realtime.RealtimeProducer;
import de.sciss.meloncillo.realtime.Transport;
import de.sciss.meloncillo.realtime.TransportListener;
import de.sciss.meloncillo.receiver.Receiver;
import de.sciss.meloncillo.receiver.ReceiverEditor;
import de.sciss.meloncillo.session.BasicSessionCollection;
import de.sciss.meloncillo.session.GroupableSessionObject;
import de.sciss.meloncillo.session.Session;
import de.sciss.meloncillo.session.SessionCollection;
import de.sciss.meloncillo.session.SessionGroup;
import de.sciss.meloncillo.session.SessionObject;
import de.sciss.meloncillo.timeline.TimelineEvent;
import de.sciss.meloncillo.timeline.TimelineListener;
import de.sciss.meloncillo.transmitter.TrajectoryGenerator;
import de.sciss.meloncillo.transmitter.Transmitter;
import de.sciss.meloncillo.util.MapManager;
import de.sciss.meloncillo.util.PointInTime;
import de.sciss.meloncillo.util.PrefsUtil;
/**
* The <code>SurfacePane</code> is one of the core GUI
* elements of Meloncillo : a two dimensional
* planar display of the receiver distribution
* and a momentary display of transmitter locations.
* <p>
* It implements the <code>VirtualSurfacePane</code>
* interface which describes the transition from
* screen space to a normalized virtual space.
* It is also a <code>RealtimeConsumer</code> which
* tracks the movement of the transmitters in time.
* It implements the <code>ToolActionListener</code>
* interface and has a internal classes for different
* geometric and other tools.
*
* @author Hanns Holger Rutz
* @version 0.75, 10-Jun-08
*
* @todo opening multiple editor windows for the
* same receiver should be avoided
*/
public class SurfacePane
extends JComponent // JPanel
implements VirtualSurface, TimelineListener, TransportListener,
ToolActionListener, DynamicListening, RealtimeConsumer, PreferenceChangeListener
{
// --- global communication ---
private final Main root;
private final Session doc;
private final Transport transport;
// private double timelineRate;
private long timelinePos;
private long timelineLen;
private final Timer playTimer;
// private double playRate = 1.0;
// --- shapes and paints ---
/**
* Stroke used to paint Receiver outlines
* and transmitter paths
*/
protected static final Stroke strkDottedLine;
/**
* Stroke to paint solid lines
*/
protected static final Stroke strkLine;
/**
* Shape for painting the anchors
* of Receivers and current Transmitter positions
*/
protected static final Shape shpCrossHair;
/**
* Color for selected objects and lines
*/
protected static final Color colrSelection = GraphicsUtil.colrSelection;
/**
* Color for selected text
*/
protected static final Color colrTextSelection = Color.lightGray;
// private static final Color colrAdjusting = GraphicsUtil.colrAdjusting;
private static final Color colrReceiver = new Color( 0xFF, 0x00, 0x00 );
private static final Color colrTrnsLight = new Color( 0x00, 0xFF, 0x00 );
private static final Color colrTrnsDark = new Color( 0x00, 0xAB, 0x00 );
// XXX move to GraphicsUtil
// private static final Font fntLabel = new Font( "Gill Sans", Font.ITALIC, 5 );
private static final Font fntLabel = new Font( "Gill Sans", Font.ITALIC, 10 );
private final BufferedImage bufImg;
private final int bufImgExtent = 256; // XXX user prefs
private final float[] bufImgRow = new float[ bufImgExtent ];
private final float[] bufImgRow2 = new float[ bufImgExtent ];
private final float[][] bufImgPt = new float[ 2 ][ bufImgExtent ];
private Image image = null;
private Dimension recentSize;
// --- cursor info ---
private ObserverPalette observer = null;
private final MouseMotionListener cursorListener;
private static final String EMPTY_STR = "";
private final String[] cursorInfo = new String[] {
EMPTY_STR, EMPTY_STR, EMPTY_STR, EMPTY_STR, EMPTY_STR
};
private final Object[] msgArgs = new Object[ 2 ];
private static final MessageFormat msgCursorX = new MessageFormat( "X {0,number,0.0000}", Locale.US ); // XXX
private static final MessageFormat msgCursorY = new MessageFormat( "Y {1,number,0.0000}", Locale.US ); // XXX
// --- points and paths ---
private final float[][] trnsBuf = new float[ 2 ][ 1 ];
/*
* elements are GeneralPath objects with the transmitter
* trajectory for the current timeline selection
*/
private final List collTransmitterPath = new ArrayList();
/*
* elements are ReceiverShape objects with the receiver
* anchors and outlines
*/
private final List collReceiverShapes = new ArrayList();
/*
* elements are Image objects with the group's background images
*/
private final List collUserImages = new ArrayList();
// --- tools ---
private final Map tools = new HashMap();
private AbstractTool activeTool = null;
// --- top painter ---
private final List collTopPainters = new ArrayList();
// --- realtime ---
private boolean rt_valid = false; // false if rt shouldn't update transmitter locs
private String[] rt_trnsNames = new String[0]; // names of all transmitters
private float[] rt_trnsLocX = new float[0]; // x locations of all transmitters
private float[] rt_trnsLocY = new float[0]; // y locations of all transmitters FLIPPED
private int[] rt_peak = new int[0]; // maps active transmitter indices to context indices
private long rt_pos;
// --- prefs ---
private boolean prefSnap, prefRcvSense, prefRcvEqP, prefTrnsTraj, prefUserImages;
private static final List grpKeys;
static {
// --- Strokes ---
float[] dash = { 1.0e-2f, 1.0e-2f };
strkDottedLine = new BasicStroke( 4.0e-3f, BasicStroke.CAP_SQUARE, BasicStroke.JOIN_BEVEL, 1.0f, dash, 0.0f );
strkLine = new BasicStroke( 4.0e-3f );
// --- Shapes ---
shpCrossHair = new Area( new Rectangle2D.Float( -2.0e-2f, -2.0e-3f, 4.0e-2f, 4.0e-3f ));
((Area) shpCrossHair).add( new Area( new Rectangle2D.Float( -2.0e-3f, -2.0e-2f, 4.0e-3f, 4.0e-2f )));
grpKeys = Collections.singletonList( SessionGroup.MAP_KEY_USERIMAGE );
}
/**
* Constructs a <code>SurfacePane</code>
* object. Allocates memory for a 8-bit
* grayscale image that displays the
* receivers' sensibilities.
*
* @param root application root
* @param doc session document
*/
public SurfacePane( Main root, final Session doc )
{
super();
this.root = root;
this.doc = doc;
transport = doc.getTransport();
timelinePos = doc.timeline.getPosition();
// timelineRate = doc.timeline.getRate();
timelineLen = doc.timeline.getLength();
setPreferredSize( new Dimension( 480, 640 ));
WritableRaster rast = Raster.createInterleavedRaster(
DataBuffer.TYPE_BYTE, bufImgExtent, bufImgExtent, 1, new Point( 0, 0 ));
ColorSpace colrSpace= ColorSpace.getInstance( ColorSpace.CS_GRAY );
int[] cbits = new int[1];
cbits[0] = 8;
ColorModel cm = new ComponentColorModel( colrSpace, cbits, false, false, Transparency.OPAQUE,
DataBuffer.TYPE_BYTE );
bufImg = new BufferedImage( cm, rast, false, new Hashtable() );
recentSize = getMinimumSize();
// --- Tools ---
tools.put( new Integer( ToolAction.POINTER ), new SurfacePointerTool() );
tools.put( new Integer( ToolAction.LINE ), new SurfaceLineTool( root, doc, this ));
tools.put( new Integer( ToolAction.CURVE ), new SurfaceCurveTool( root, doc, this ));
tools.put( new Integer( ToolAction.ARC ), new SurfaceArcTool( root, doc, this ));
tools.put( new Integer( ToolAction.PENCIL ), new SurfacePencilTool( false ));
tools.put( new Integer( ToolAction.FORK ), new SurfacePencilTool( true ));
// --- Listener ---
new DynamicAncestorAdapter( this ).addTo( this );
new DynamicAncestorAdapter( new DynamicPrefChangeManager(
AbstractApplication.getApplication().getUserPrefs().node( PrefsUtil.NODE_SHARED ),
new String[] { PrefsUtil.KEY_SNAP, PrefsUtil.KEY_VIEWRCVSENSE, PrefsUtil.KEY_VIEWEQPRECEIVER,
PrefsUtil.KEY_VIEWTRNSTRAJ, PrefsUtil.KEY_VIEWUSERIMAGES }, this )).addTo( this );
// this.addMouseListener( new MouseAdapter() {
// public void mousePressed( MouseEvent e )
// {
// requestFocus();
// }
// });
cursorListener = new MouseMotionAdapter() {
public void mouseMoved( MouseEvent e )
{
showCursorInfo( screenToVirtual( e.getPoint() ));
}
public void mouseDragged( MouseEvent e )
{
showCursorInfo( screenToVirtual( e.getPoint() ));
}
};
doc.getActiveReceivers().addListener( new SessionCollection.Listener() {
public void sessionCollectionChanged( SessionCollection.Event e )
{
//System.err.println( "activeReceivers sessionCollectionChanged!" );
update( e );
}
public void sessionObjectChanged( SessionCollection.Event e )
{
if( e.getSource() != SurfacePane.this && e.getSource() != activeTool ) {
switch( e.getModificationType() ) {
case SessionObject.OWNER_RENAMED:
updateReceiverShapes();
redrawImage();
repaint();
break;
case Receiver.OWNER_SENSE:
update( e );
break;
case SessionObject.OWNER_VISUAL:
updateReceiverShapes();
updateSurfacePaneImage( null );
redrawImage();
repaint();
break;
}
}
}
public void sessionObjectMapChanged( SessionCollection.Event e ) {}
// {
// if( e.getSource() != enc_this && e.getSource() != activeTool &&
// e.setContainsAny( rcvKeys )) {
//
// // cannot use 'update( e )' because we would need the previous union rect ;-( XXX
// updateReceiverShapes();
// updateSurfacePaneImage( null );
// redrawImage();
// repaint();
// }
// }
});
doc.getSelectedReceivers().addListener( new SessionCollection.Listener() {
public void sessionCollectionChanged( SessionCollection.Event e )
{
if( e.getSource() != SurfacePane.this && e.getSource() != activeTool ) {
updateReceiverShapes();
redrawImage();
repaint();
}
}
public void sessionObjectChanged( SessionCollection.Event e ) {}
public void sessionObjectMapChanged( SessionCollection.Event e ) {}
});
doc.getActiveTransmitters().addListener( new SessionCollection.Listener() {
public void sessionObjectChanged( SessionCollection.Event e )
{
if( e.getModificationType() == Transmitter.OWNER_RENAMED ) {
checkTrnsNames(); // XXX optimize!
redrawImage();
repaint();
}
}
public void sessionCollectionChanged( SessionCollection.Event e )
{
if( rt_valid ) {
// rt_valid = false;
createRealtime();
// EEE
// transport.removeRealtimeConsumer( SurfacePane.this );
// transport.addRealtimeConsumer( SurfacePane.this );
}
}
public void sessionObjectMapChanged( SessionCollection.Event e ) {}
});
doc.getSelectedTransmitters().addListener( new SessionCollection.Listener() {
public void sessionCollectionChanged( SessionCollection.Event e )
{
if( prefTrnsTraj ) {
checkSchoko( e );
}
}
public void sessionObjectChanged( SessionCollection.Event e )
{
if( prefTrnsTraj && e.getModificationType() == Transmitter.OWNER_TRAJ ) {
checkSchoko( e );
}
}
public void sessionObjectMapChanged( SessionCollection.Event e ) {}
private void checkSchoko( SessionCollection.Event e )
{
try {
doc.bird.waitShared( Session.DOOR_TRNS | Session.DOOR_GRP );
java.util.List coll = e.getCollection();
coll.retainAll( doc.getSelectedTransmitters().getAll() );
if( !coll.isEmpty() ) {
updateTransmitterPath(); // XXX optimize
redrawImage(); // XXX not if frame is hidden
repaint();
}
}
finally {
doc.bird.releaseShared( Session.DOOR_TRNS | Session.DOOR_GRP );
}
}
});
doc.getSelectedGroups().addListener( new SessionCollection.Listener() {
public void sessionCollectionChanged( SessionCollection.Event e )
{
if( prefUserImages ) {
//System.err.println( "B" );
updateUserImages();
redrawImage();
repaint();
}
}
public void sessionObjectChanged( SessionCollection.Event e ) {}
public void sessionObjectMapChanged( SessionCollection.Event e )
{
if( e.setContainsAny( grpKeys )) {
if( prefUserImages ) {
//System.err.println( "A" );
updateUserImages();
redrawImage();
repaint();
}
}
}
});
playTimer = new Timer( 20 /* 33 */, new ActionListener() {
public void actionPerformed( ActionEvent e )
{
timelinePos = transport.getCurrentFrame();
// updatePositionAndRepaint();
// scroll.setPosition( timelinePos, 50, TimelineScroll.TYPE_TRANSPORT );
offhandTick();
}
});
// -------
setOpaque( true );
setFocusable( true ); // required for the tools to hear key presses
updateSurfacePaneImage( null );
// HelpGlassPane.setHelp( this, "Surface" ); // EEE
}
/**
* Registers a new top painter.
* If the top painter wants to paint
* a specific portion of the surface,
* it must make an appropriate repaint call!
*
* @param p the <code>TopPainter</code> to be added to
* the painting queue
*
* @synchronization this method must be called in the event thread
*/
public void addTopPainter( TopPainter p )
{
if( !collTopPainters.contains( p )) {
collTopPainters.add( p );
}
}
/**
* Removes a registered top painter.
*
* @param p the <code>TopPainter</code> to be removed from
* the painting queue
*
* @synchronization this method must be called in the event thread
*/
public void removeTopPainter( TopPainter p )
{
collTopPainters.remove( p );
}
/**
* Recalculates the surface buffered image, e.g. after a receiver movement.
*
* @param clipRect the part of the image that needs update
* or null to update the complete image
*
* @synchronization attemptShared on DOOR_RCV | DOOR_GRP
* @todo increase calculation speed
*/
private void updateSurfacePaneImage( Rectangle2D clipRect )
{
if( !prefRcvSense ) return;
WritableRaster rast = bufImg.getRaster();
float scaleDown = 2.0f / (float) bufImgExtent;
int x, y, i, rcvIdx, x1, x2, y1, y2, numRcv;
float f1;
List collRcv;
if( clipRect == null ) {
x1 = 0;
y1 = 0;
x2 = bufImgExtent;
y2 = bufImgExtent;
} else {
x1 = Math.max( 0, (int) ((clipRect.getMinX() + 1.0) / 2 * bufImgExtent) );
y1 = Math.max( 0, (int) ((clipRect.getMaxY() - 1.0) / 2 * -bufImgExtent) );
x2 = Math.min( bufImgExtent, (int) Math.ceil( (clipRect.getMaxX() + 1.0) / 2 * bufImgExtent ));
y2 = Math.min( bufImgExtent, (int) Math.ceil( (clipRect.getMinY() - 1.0) / 2 * -bufImgExtent ));
}
// we calculate full rows at once, hence
// we can precalc the x coords for all rows
for( x = x1; x < x2; x++ ) {
bufImgPt[ 0 ][ x ] = x * scaleDown - 1.0f;
}
if( !doc.bird.attemptShared( Session.DOOR_RCV | Session.DOOR_GRP, 250 )) return;
try {
collRcv = doc.getActiveReceivers().getAll();
numRcv = collRcv.size();
if( numRcv == 0 ) {
for( i = x2 - x1 - 1; i >= 0; i-- ) {
bufImgRow2[ i ] = 255f;
}
for( y = y1; y < y2; y++ ) {
rast.setPixels( x1, y, x2 - x1, 1, bufImgRow2 );
}
return;
}
if( prefRcvEqP ) { // ----------------------- equal power -----------------------
//System.err.println( "here! x1 "+x1+" x2 "+x2+" y1 "+y1+" y2 "+y2+" numRcv "+numRcv );
//System.err.println( "rcv 1 : anchor "+((Receiver) collRcv.get( 0 )).getAnchor().getX()+","+
// ((Receiver) collRcv.get( 0 )).getAnchor().getY()+" size "+
// ((Receiver) collRcv.get( 0 )).getSize().getWidth()+","+
// ((Receiver) collRcv.get( 0 )).getSize().getHeight() );
for( y = y1; y < y2; y++ ) {
f1 = 1f - (y * scaleDown);
// all points in a row share the same y coord
for( x = x1; x < x2; x++ ) {
bufImgPt[ 1 ][ x ] = f1;
}
((Receiver) collRcv.get( 0 )).getSensitivities(
bufImgPt, bufImgRow, x1, x2, 1 );
for( x = x1, i = 0; x < x2; x++, i++ ) {
bufImgRow2[ i ] = bufImgRow[ x ] * bufImgRow[ x ];
}
for( rcvIdx = 1; rcvIdx < numRcv; rcvIdx++ ) {
((Receiver) collRcv.get( rcvIdx )).getSensitivities(
bufImgPt, bufImgRow, x1, x2, 1 );
for( x = x1, i = 0; x < x2; x++, i++ ) {
bufImgRow2[ i ] += bufImgRow[ x ] * bufImgRow[ x ];
}
}
for( i = x2 - x1 - 1; i >= 0; i-- ) {
f1 = bufImgRow2[ i ];
if( f1 == 0.0f ) {
bufImgRow2[ i ] = 255f;
} else if( f1 >= 1.0f ) {
bufImgRow2[ i ] = 0f;
} else {
bufImgRow2[ i ] = (float) ((1.0 - Math.sqrt( bufImgRow2[ i ])) * 255);
}
}
rast.setPixels( x1, y, x2 - x1, 1, bufImgRow2 );
}
} else { // ----------------------- linear -----------------------
for( y = y1; y < y2; y++ ) {
f1 = 1f - (y * scaleDown);
// all points in a row share the same y coord
for( x = x1; x < x2; x++ ) {
bufImgPt[ 1 ][ x ] = f1;
}
((Receiver) collRcv.get( 0 )).getSensitivities(
bufImgPt, bufImgRow, x1, x2, 1 );
for( x = x1, i = 0; x < x2; x++, i++ ) {
bufImgRow2[ i ] = bufImgRow[ x ];
}
for( rcvIdx = 1; rcvIdx < numRcv; rcvIdx++ ) {
((Receiver) collRcv.get( rcvIdx )).getSensitivities(
bufImgPt, bufImgRow, x1, x2, 1 );
for( x = x1, i = 0; x < x2; x++, i++ ) {
bufImgRow2[ i ] += bufImgRow[ x ];
}
}
for( i = x2 - x1 - 1; i >= 0; i-- ) {
f1 = bufImgRow2[ i ];
bufImgRow2[ i ] = (float) ((1.0 - Math.min( 1.0f, bufImgRow2[ i ])) * 255);
}
rast.setPixels( x1, y, x2 - x1, 1, bufImgRow2 );
}
}
}
finally {
doc.bird.releaseShared( Session.DOOR_RCV | Session.DOOR_GRP );
}
}
/**
* Creates a new default Receiver located at the provided point
*
* @param anchor initial position of the receiver
* @return the new receiver or null when the instantiation failed
*
* @synchronization waitShared on DOOR_RCV + DOOR_GRP
*/
private Receiver createReceiver( Point2D anchor)
{
final List collTypes = Main.getReceiverTypes();
final List collNewRcv;
Receiver rcv = null;
Class c;
try {
// doc.bird.waitExclusive( Session.DOOR_RCV | Session.DOOR_GRP );
// we get a list of known receivers from the main class
// and create a new instance of the first receiver class
// in the list; in the future when there are more types
// apart from SigmaReceiver, we could display a selection
// dialog or the like...
c = Class.forName( (String) ((StringItem) collTypes.get( 0 )).getKey() );
rcv = (Receiver) c.newInstance();
rcv.setAnchor( anchor );
// rcv.setSize( new Dimension2DDouble( 0.5, 0.5 ));
rcv.setName( BasicSessionCollection.createUniqueName( Session.SO_NAME_PTRN,
new Object[] { new Integer( 1 ), Session.RCV_NAME_PREFIX, Session.RCV_NAME_SUFFIX },
doc.getReceivers().getAll() ));
doc.getReceivers().getMap().copyContexts( this, MapManager.Context.FLAG_DYNAMIC,
MapManager.Context.NONE_EXCLUSIVE, rcv.getMap() );
collNewRcv = Collections.singletonList( rcv );
if( doc.getSelectedGroups().isEmpty() ) {
final PerformableEdit edit;
edit = new EditAddSessionObjects( this, doc.getMutableReceivers(), collNewRcv );
doc.getUndoManager().addEdit( edit.perform() );
} else {
final AbstractCompoundEdit edit;
edit = new BasicCompoundEdit();
final List selectedGroups = doc.getSelectedGroups().getAll();
for( int i = 0; i < collNewRcv.size(); i++ ) {
final GroupableSessionObject so = (GroupableSessionObject) collNewRcv.get( i );
edit.addPerform( new EditAddSessionObjects( this, so.getGroups(), selectedGroups ));
}
edit.addPerform( new EditAddSessionObjects( this, doc.getMutableReceivers(), collNewRcv ));
// for( i = 0; i < doc.getSelectedGroups().size(); i++ ) {
// group = (SessionGroup) doc.getSelectedGroups().get( i );
// edit.addPerform( new EditAddSessionObjects( this, group.getReceivers(), collRcv ));
// }
edit.perform();
edit.end();
doc.getUndoManager().addEdit( edit );
}
}
catch( InstantiationException e1 ) {
System.err.println( e1.getLocalizedMessage() );
}
catch( IllegalAccessException e2 ) {
System.err.println( e2.getLocalizedMessage() );
}
catch( LinkageError e3 ) {
System.err.println( e3.getLocalizedMessage() );
}
catch( ClassNotFoundException e4 ) {
System.err.println( e4.getLocalizedMessage() );
}
// finally {
// doc.bird.releaseExclusive( Session.DOOR_RCV | Session.DOOR_GRP );
// }
return rcv;
}
/**
* Custom paint method paints surface image and receiver/transmitter
* anchors and outlines
*/
public void paintComponent( Graphics g )
{
// super.paintComponent( g );
final Dimension d = getSize();
final int diam = Math.min( d.width, d.height ) - 1;
final double diamH = diam * 0.5;
final Graphics2D g2 = (Graphics2D) g;
final AffineTransform trnsOrig = g2.getTransform();
final Stroke strkOrig = g2.getStroke();
final AffineTransform trnsRecent;
if( (recentSize.width != d.width) || (recentSize.height != d.height) ) {
recentSize = d;
recreateImage();
redrawImage();
}
if( image != null ) g.drawImage( image, 0, 0, this );
// g2.scale( diamH, -diamH ); // virtual space has dimension 1.0 x 1.0
// g2.translate( 1.0, -1.0 );
g2.scale( diamH, diamH ); // virtual space has dimension 1.0 x 1.0
g2.translate( 1.0, 1.0 );
g2.setRenderingHint( RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON );
// g2.setRenderingHint( RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC );
// g2.setRenderingHint( RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY );
g2.setFont( fntLabel );
g2.setStroke( strkDottedLine );
trnsRecent = g2.getTransform();
// --- transmitters ---
if( rt_valid ) {
g2.setColor( prefRcvSense ? colrTrnsLight : colrTrnsDark );
for( int i = rt_trnsNames.length - 1; i >= 0; i-- ) {
g2.translate( rt_trnsLocX[ i ], rt_trnsLocY[ i ]);
g2.fill( shpCrossHair );
g2.scale( 5.0e-3f, 5.0e-3f ); // the scaling is necessary because we cannot get font size < 1
if( rt_trnsNames[ i ] != null ) g2.drawString( rt_trnsNames[ i ], 2.0f, -2.0f );
g2.setTransform( trnsRecent ); // undo translation and scaling
}
}
g2.scale( 1.0, -1.0 );
// --- paint tool state ---
if( activeTool != null ) activeTool.paintOnTop( g2 );
// --- invoke top painters ---
for( int i = 0; i < collTopPainters.size(); i++ ) {
((TopPainter) collTopPainters.get( i )).paintOnTop( g2 );
}
// --- restore Graphics2D properties ---
g2.setTransform( trnsOrig );
g2.setStroke( strkOrig );
}
private void recreateImage()
{
if( image != null ) image.flush();
image = createImage( recentSize.width, recentSize.height );
}
// sync : must be called on the event thread
private void redrawImage()
{
if( image == null ) return;
final int diam = Math.min( recentSize.width, recentSize.height ) - 1;
final double diamH = diam * 0.5;
final Graphics2D g2 = (Graphics2D) image.getGraphics();
final AffineTransform trnsRecent;
ReceiverShape rcvShp;
Image userImg;
// --- surface image ---
if( prefRcvSense ) {
g2.drawImage( bufImg, 0, 0, diam, diam, this );
} else {
g2.setColor( Color.white );
g2.fillRect( 0, 0, recentSize.width, recentSize.height );
}
if( prefUserImages ) {
for( int i = 0; i < collUserImages.size(); i++ ) {
userImg = (Image) collUserImages.get( i );
//System.err.println( "userImg w "+userImg.getWidth( this )+" ; h "+userImg.getHeight( this ));
g2.drawImage( userImg, 0, 0, diam, diam, this );
}
}
g2.scale( diamH, -diamH ); // virtual space has dimension 1.0 x 1.0
g2.translate( 1.0, -1.0 );
g2.setRenderingHint( RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON );
// g2.setRenderingHint( RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC );
// g2.setRenderingHint( RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY );
g2.setFont( fntLabel );
trnsRecent = g2.getTransform();
// --- active selection ---
for( int i = 0; i < collReceiverShapes.size(); i++ ) {
rcvShp = (ReceiverShape) collReceiverShapes.get( i );
if( rcvShp.selected ) {
g2.translate( rcvShp.loc.getX(), rcvShp.loc.getY() );
g2.setColor( colrSelection );
g2.fill( rcvShp.outline );
g2.setTransform( trnsRecent ); // undo translation
}
}
// --- receivers ---
g2.setStroke( strkDottedLine );
for( int i = 0; i < collReceiverShapes.size(); i++ ) {
rcvShp = (ReceiverShape) collReceiverShapes.get( i );
g2.setColor( rcvShp.selected ? colrTextSelection : colrReceiver );
g2.translate( rcvShp.loc.getX(), rcvShp.loc.getY() );
g2.draw( rcvShp.outline );
g2.fill( shpCrossHair );
g2.scale( 5.0e-3f, 5.0e-3f ); // the scaling is necessary because we cannot get font size < 1
if( rcvShp.name != null ) {
g2.scale( 1.0, -1.0 );
g2.drawString( rcvShp.name, 1.0f, 1.0f );
}
g2.setTransform( trnsRecent ); // undo translation and scaling
}
// --- transmitter selected trajectories ---
if( prefTrnsTraj ) {
g2.setColor( prefRcvSense ? colrTrnsLight : colrTrnsDark );
for( int i = 0; i < collTransmitterPath.size(); i++ ) {
g2.draw( (Shape) collTransmitterPath.get( i ));
}
}
g2.dispose();
}
/*
* Efficiently recalculates / repaints the surface : if the two clips intersect,
* recalc the union and paint it if its area is smaller than the sum of the separate
* clips. if not, repaint each clip separately
*
* @param clipRect first clipping rectangle in virtual coords
* @param clipRect2 second clipping rectangle in virtual coords
* @param updateSurfacePane whether the surface image needs to be recalculated. if true,
* the offscreen image will be redrawn as well. if true, redrawImage()
* is invoked right after the update
*
* @synchronization waitShared on DOOR_RCV if <code>updateSurfacePane</code> is <code>true</code>
* since in this case <code>updateSurfacePaneImage</code> is called
*/
private void efficientUpdateAndRepaint( Rectangle2D clipRect, Rectangle2D clipRect2, boolean updateSurfacePane )
{
Rectangle2D clipRect3;
if( clipRect != null ) {
if( clipRect2 != null ) {
clipRect3 = clipRect.createUnion( clipRect2 );
if( clipRect3.getWidth() * clipRect3.getHeight() <
(clipRect.getWidth() * clipRect.getHeight() + clipRect2.getWidth() * clipRect2.getHeight()) ) {
if( updateSurfacePane ) {
updateSurfacePaneImage( clipRect3 );
redrawImage();
}
repaint( virtualToScreenClip( clipRect3 ));
} else {
if( updateSurfacePane ) {
updateSurfacePaneImage( clipRect );
updateSurfacePaneImage( clipRect2 );
redrawImage();
}
repaint( virtualToScreenClip( clipRect ));
repaint( virtualToScreenClip( clipRect2 ));
}
} else {
if( updateSurfacePane ) {
updateSurfacePaneImage( clipRect );
redrawImage();
}
repaint( virtualToScreenClip( clipRect ));
}
} else if( clipRect2 != null ) {
if( updateSurfacePane ) {
updateSurfacePaneImage( clipRect2 );
redrawImage();
}
repaint( virtualToScreenClip( clipRect2 ));
}
}
/*
* Calculate the rectangle that contains all the rectangles
* specified by the bounds of Receivers provided as a List.
*
* @param coll Collection of Receivers
* @return a (possibly empty) Rectangle2D describing
* the union of all Receivers' bounds
*
* @throws ClassCastException if one of the collection elements is not a Receiver
*/
private Rectangle2D getUnionRect( java.util.List coll )
{
Rectangle2D clipRect = new Rectangle2D.Double();
Object obj;
int i;
for( i = 0; i < coll.size(); i++ ) {
obj = coll.get( i );
Rectangle.union( clipRect, ((Receiver) obj).getBounds(), clipRect );
}
return clipRect;
}
/*
* Reads all currently selected transmitter paths
* and stores them in collTransmitterPath.
*
* @synchronization waitShared on DOOR_TIMETRNSMTE | DOOR_GRP
*/
private void updateTransmitterPath()
{
int j, reqLen, pathLen, len, lastLen;
Transmitter trns;
Span span;
float[][] frames = null;
float[] x, y;
GeneralPath path = null;
DecimationInfo info;
// AudioTrail at;
DecimatedWaveTrail dwt;
float lx, ly;
List collTrns;
try {
doc.bird.waitShared( Session.DOOR_TIMETRNSMTE | Session.DOOR_GRP );
span = doc.timeline.getSelectionSpan();
collTransmitterPath.clear();
if( span.getLength() < 2 ) return;
collTrns = doc.getActiveTransmitters().getAll();
collTrns.retainAll( doc.getSelectedTransmitters().getAll() );
for( int i = 0; i < collTrns.size(); i++ ) {
trns = (Transmitter) collTrns.get( i );
// at = trns.getTrackEditor();
dwt = trns.getDecimatedWaveTrail();
// performance measures show that this routine is
// vey fast, like one or two millisecs, while the draw method
// of the Graphics2D called in redrawImage() becomes hell
// slow if the GeneralPath contains more than say 250 lines.
// therefore we start with a fairly big subsample to get a
// good resolution; however in the path creation loop, points
// are skipped if they lie very close to each other. therefore,
// right after the creation of the path we know how many lines
// it actually contains, and if these exceed 256 we'll restart
// with a smaller subsample.
for( reqLen = 1024, pathLen = 257, len = -1; pathLen > 256; reqLen >>= 1 ) {
info = dwt.getBestSubsample( span, reqLen );
lastLen = len;
len = (int) info.sublength;
if( lastLen == len ) continue;
if( frames == null || frames[0].length < len ) {
frames = new float[2][len];
}
// dwt.readFrame( sub, pos, ch, data );
// dwt.read( info, frames, 0 );
trns.getAudioTrail().readFrames( frames, 0, new Span( info.span.start, info.span.start + len ));
x = frames[0];
y = frames[1];
path = new GeneralPath( GeneralPath.WIND_EVEN_ODD, len );
lx = x[0];
ly = y[0];
path.moveTo( lx, ly );
for( j = 1, pathLen = 0; j < len; j++ ) {
// only points sufficiently far away from each other are
// added, this speeds up the painting
// the value corresponds to a few pixels when the surface
// frame is viewed at about 1000 x 1000 pixels
if( Point2D.distanceSq( lx, ly, x[j], y[j] ) < 2.0e-5 ) continue;
lx = x[j];
ly = y[j];
path.lineTo( lx, ly );
pathLen++;
}
} // for( ... )
collTransmitterPath.add( path );
}
}
catch( IOException e1 ) {
System.err.println( e1.getLocalizedMessage() );
}
finally {
doc.bird.releaseShared( Session.DOOR_TIMETRNSMTE | Session.DOOR_GRP );
}
}
/*
* Reads all receiver locations and shapes
*
* @synchronization waitShared on DOOR_RCV + DOOR_GRP
*/
private void updateReceiverShapes()
{
Receiver rcv;
final List collRcv;
final List collRcvSel;
collRcvSel = doc.getSelectedReceivers().getAll();
collRcv = doc.getActiveReceivers().getAll();
collReceiverShapes.clear();
for( int i = 0; i < collRcv.size(); i++ ) {
rcv = (Receiver) collRcv.get( i );
collReceiverShapes.add( new ReceiverShape(
rcv.getAnchor(), rcv.getOutline(), rcv.getName(), collRcvSel.contains( rcv )));
}
}
/*
* Reads all group's background images
*
* @synchronization waitShared on DOOR_GRP
*/
private void updateUserImages()
{
int i;
java.util.List collSO;
SessionObject so;
File f;
Toolkit tk = Toolkit.getDefaultToolkit();
MediaTracker mt = new MediaTracker( this );
Image img;
Object[] errors;
try {
doc.bird.waitShared( Session.DOOR_GRP );
collSO = doc.getSelectedGroups().getAll();
disposeUserImages();
for( i = 0; i < collSO.size(); i++ ) {
so = (SessionObject) collSO.get( i );
f = (File) so.getMap().getValue( SessionGroup.MAP_KEY_USERIMAGE );
if( f != null ) {
img = tk.createImage( f.getAbsolutePath() );
collUserImages.add( img );
}
}
}
finally {
doc.bird.releaseShared( Session.DOOR_GRP );
}
try {
mt.waitForAll();
}
catch( InterruptedException e1 ) {}
errors = mt.getErrorsAny();
if( errors != null ) {
for( i = 0; i < errors.length; i++ ) {
System.err.println( "Media error on "+errors[ i ].toString() );
}
}
}
private void disposeUserImages()
{
for( int i = 0; i < collUserImages.size(); i++ ) {
((Image) collUserImages.get( i )).flush();
}
collUserImages.clear();
}
private void update( SessionCollection.Event e )
{
updateReceiverShapes();
updateSurfacePaneImage( getUnionRect( e.getCollection() ));
redrawImage();
repaint();
}
// ---------------- VirtualSurface interface ----------------
/**
* Snaps a point to neighbouring objects
* (transmitters or receivers).
*
* @param freePt 'unsnapped' point
* @param virtual whether freePt is in virtual (true)
* or screen (false) space
* @return the snapped point or the original
* point if snapping deactivated or no
* neighbouring points found. the point
* will be in virtual or screen space
* depending on the value of 'virtual'!
*/
public Point2D snap( Point2D freePt, boolean virtual )
{
if( !prefSnap ) return freePt;
Point2D trnsFreePt = virtual ? virtualToScreen( freePt ) : freePt;
Point2D objectPt, trnsObjectPt;
Point2D snapPt = null;
ReceiverShape rcvShp;
Point2D trnsSnapPt = null;
double snapDistSq = 25.1;
double distSq;
int i;
objectPt = new Point2D.Double();
for( i = 0; i < rt_trnsNames.length; i++ ) {
objectPt.setLocation( rt_trnsLocX[i], rt_trnsLocY[i] );
trnsObjectPt = virtualToScreen( objectPt );
distSq = trnsObjectPt.distanceSq( trnsFreePt );
if( distSq < snapDistSq ) {
snapPt = objectPt;
trnsSnapPt = trnsObjectPt;
snapDistSq = distSq;
}
}
for( i = 0; i < collReceiverShapes.size(); i++ ) {
rcvShp = (ReceiverShape) collReceiverShapes.get( i );
objectPt = rcvShp.loc;
trnsObjectPt = virtualToScreen( objectPt );
distSq = trnsObjectPt.distanceSq( trnsFreePt );
if( distSq < snapDistSq ) {
snapPt = objectPt;
trnsSnapPt = trnsObjectPt;
snapDistSq = distSq;
}
}
return( snapPt == null ? freePt : (virtual ? snapPt : trnsSnapPt) );
}
/**
* Converts a location on the screen
* into a point the virtual space.
* Neither input nor output point need to
* be limited to particular bounds
*
* @param screenPt point in screen space
* @return the input point transformed to virtual space
*/
public Point2D screenToVirtual( Point2D screenPt )
{
final Dimension d = getSize();
final int diam = Math.min( d.width, d.height ) - 1;
final double diamRH = 2.0 / diam;
//System.out.println( "diam " + diam + "; diamRH " + diamRH + "; screenPt " + screenPt + "; --> " +
// (screenPt.getX() * diamRH - 1) + ", " + (1 - screenPt.getY() * diamRH) );
return new Point2D.Double( screenPt.getX() * diamRH - 1, 1 - screenPt.getY() * diamRH );
}
/**
* Converts a point in the virtual space
* into a location on the screen.
* Neither input nor output point need to
* be limited to particular bounds
*
* @param virtualPt point in the virtual space whose
* visible bounds are (0, 0 ... 1, 1)
* @return point in the display space
*/
public Point2D virtualToScreen( Point2D virtualPt )
{
final Dimension d = getSize();
final int diam = Math.min( d.width, d.height ) - 1;
final double diamH = diam * 0.5;
return new Point2D.Double( (virtualPt.getX() + 1) * diamH, (1 - virtualPt.getY()) * diamH );
}
/**
* Converts a rectangle in the virtual space
* into a rectangle suitable for Graphics clipping
*
* @param virtualClip a rectangle in virtual space
* @return the input rectangle transformed to screen space,
* possibly with extra margin padding to ensure
* correct graphics clipping of decorated shapes
*/
public Rectangle virtualToScreenClip( Rectangle2D virtualClip )
{
final Dimension d = getSize();
final int diam = Math.min( d.width, d.height ) - 1;
final double diamH = diam * 0.5;
// die vergroesserung der rechtecks um einige pixel haengt damit zusammen, dass das bufImg
// vergroessert gezeichnet wird und weitere pixel durch antialiasing interpolation
// eingefaerbt werden!
final Rectangle screenClip = new Rectangle( (int) ((virtualClip.getX() + 1) * diamH) - 1, (int) ((virtualClip.getMaxY() - 1) * -diamH) - 1,
(int) (virtualClip.getWidth() * diamH) + 4, (int) (virtualClip.getHeight() * diamH) + 4 );
// System.out.println( "v = " + virtualClip + " -> s " + screenClip );
return screenClip;
}
// final AffineTransform at = AffineTransform.getScaleInstance( diamH, -diamH );
// at.translate( 1.0, -1.0 );
/**
* Converts a shape in the virtual space
* into a shape on the screen.
*
* @param virtualShape arbitrary shape in virtual space
* @return the input shape transformed to screen space
*/
public Shape virtualToScreen( Shape virtualShape )
{
final Dimension d = getSize();
final int diam = Math.min( d.width, d.height ) - 1;
final double diamH = diam * 0.5;
// final AffineTransform at = AffineTransform.getTranslateInstance( 1.0, 1.0 );
// at.scale( diam * 0.5, diam * 0.5 );
final AffineTransform at = AffineTransform.getScaleInstance( diamH, -diamH );
at.translate( 1.0, -1.0 );
// return AffineTransform.getScaleInstance( diam, diam ).createTransformedShape( virtualShape );
return at.createTransformedShape( virtualShape );
}
/**
* Converts a shape in the screen space
* into a shape in the virtual space.
*
* @param screenShape arbitrary shape in screen space
* @return the input shape transformed to virtual space
*/
public Shape screenToVirtual( Shape screenShape )
{
final Dimension d = getSize();
final double diamHR = 2.0 / (double) (Math.min( d.width, d.height ) - 1);
// final AffineTransform at = AffineTransform.getScaleInstance( diamHR, diamHR );
// at.translate( -1.0, -1.0 );
final AffineTransform at = AffineTransform.getTranslateInstance( -1.0, 1.0 );
at.scale( diamHR, -diamHR );
return at.createTransformedShape( screenShape );
}
private void showCursorInfo( Point2D pt )
{
if( observer != null && observer.isVisible() ) {
msgArgs[0] = new Double( pt.getX() );
msgArgs[1] = new Double( pt.getY() );
cursorInfo[0] = msgCursorX.format( msgArgs );
cursorInfo[1] = msgCursorY.format( msgArgs );
observer.showCursorInfo( cursorInfo );
}
}
private void showCursorTab()
{
if( observer == null ) {
observer = (ObserverPalette) root.getComponent( Main.COMP_OBSERVER );
}
if( observer != null && observer.isVisible() ) {
observer.showTab( ObserverPalette.CURSOR_TAB );
}
}
// syncs attemptShared to DOOR_TRNS + DOOR_GRP
private void checkTrnsNames()
{
int trnsIdx, numTrns;
java.util.List collTrns;
if( !doc.bird.attemptShared( Session.DOOR_TRNS | Session.DOOR_GRP, 250 )) return;
try {
collTrns = doc.getActiveTransmitters().getAll();
numTrns = Math.min( rt_trnsNames.length, collTrns.size() );
for( trnsIdx = 0; trnsIdx < numTrns; trnsIdx++ ) {
rt_trnsNames[ trnsIdx ] = ((SessionObject) collTrns.get( trnsIdx )).getName();
}
}
finally {
doc.bird.releaseShared( Session.DOOR_TRNS | Session.DOOR_GRP );
}
}
private String getResourceString( String key )
{
return AbstractApplication.getApplication().getResourceString( key );
}
// ---------------- LaterInvocationManager.Listener interface ----------------
// o instanceof PreferenceChangeEvent
public void preferenceChange( PreferenceChangeEvent pce)
{
String key = pce.getKey();
String value = pce.getNewValue();
if( key.equals( PrefsUtil.KEY_SNAP )) {
prefSnap = Boolean.valueOf( value ).booleanValue();
} else if( key.equals( PrefsUtil.KEY_VIEWRCVSENSE )) {
prefRcvSense= Boolean.valueOf( value ).booleanValue();
if( prefRcvSense ) {
updateSurfacePaneImage( null );
}
redrawImage();
repaint();
} else if( key.equals( PrefsUtil.KEY_VIEWEQPRECEIVER )) {
prefRcvEqP = Boolean.valueOf( value ).booleanValue();
if( prefRcvSense ) {
updateSurfacePaneImage( null );
redrawImage();
repaint();
}
} else if( key.equals( PrefsUtil.KEY_VIEWTRNSTRAJ )) {
prefTrnsTraj = Boolean.valueOf( value ).booleanValue();
if( prefTrnsTraj ) {
updateTransmitterPath();
}
redrawImage();
repaint();
} else if( key.equals( PrefsUtil.KEY_VIEWUSERIMAGES )) {
prefUserImages = Boolean.valueOf( value ).booleanValue();
if( prefUserImages ) {
updateUserImages();
} else {
disposeUserImages();
}
redrawImage();
repaint();
}
}
private void offhandTick()
{
if( !rt_valid ) return;
final SessionCollection collTrns = doc.getActiveTransmitters();
final int numTrns = Math.min( rt_trnsLocX.length, collTrns.size() );
final long pos = Math.min( timelineLen - 1, timelinePos );
if( pos < 0 ) return;
try {
for( int trnsIdx = 0; trnsIdx < numTrns; trnsIdx++ ) {
final Transmitter trns = (Transmitter) collTrns.get( trnsIdx );
final AudioTrail at = trns.getAudioTrail();
at.readFrames( trnsBuf, 0, new Span( pos, pos + 1 ));
rt_trnsLocX[ trnsIdx ] = trnsBuf[ 0 ][ 0 ];
rt_trnsLocY[ trnsIdx ] = -trnsBuf[ 1 ][ 0 ];
}
}
catch( IOException e1 ) {
e1.printStackTrace();
}
repaint();
}
private void createRealtime()
{
final List collTrns;
final int numTrns;
rt_valid = false;
collTrns = doc.getActiveTransmitters().getAll();
numTrns = collTrns.size();
if( rt_trnsNames.length != numTrns ) {
rt_trnsNames= new String[ numTrns ];
rt_trnsLocX = new float[ numTrns ];
rt_trnsLocY = new float[ numTrns ];
// rt_peak = new int[ numTrns ];
}
for( int trnsIdx = 0; trnsIdx < numTrns; trnsIdx++ ) {
rt_trnsNames[ trnsIdx ] = ((Transmitter) collTrns.get( trnsIdx )).getName();
}
rt_valid = true;
if( !doc.getTransport().isRunning() ) {
offhandTick();
}
}
// ---------------- RealtimeConsumer interface ----------------
/**
* Creates a request for realtime consumption.
* The SurfacePane is interested in transmitter trajectory
* data at a visually fluent rate, some 30 fps.
* Delivery notification is requested as offhand and realtime ticks.
*
* @param context the context of the realtime performance
* @return the request of realtime streaming data for the surface
*
* @synchronization this method attempts shared on TRNS + GRP
*/
public RealtimeConsumerRequest createRequest( RealtimeContext context )
{
RealtimeConsumerRequest request;
int trnsIdx, numTrns;
java.util.List collTrns;
rt_valid = false;
request = new RealtimeConsumerRequest( this, context );
if( !doc.bird.attemptShared( Session.DOOR_TRNS | Session.DOOR_GRP, 250 )) return request;
try {
// 30 fps is visually fluent
request.frameStep = RealtimeConsumerRequest.approximateStep( context, 30 );
request.notifyTickStep = request.frameStep;
request.notifyTicks = true;
request.notifyOffhand = true;
// collTrns = context.getTransmitters();
collTrns = doc.getActiveTransmitters().getAll();
numTrns = collTrns.size();
if( rt_peak.length != numTrns ) {
rt_trnsNames= new String[ numTrns ];
rt_trnsLocX = new float[ numTrns ];
rt_trnsLocY = new float[ numTrns ];
rt_peak = new int[ numTrns ];
}
for( trnsIdx = 0; trnsIdx < numTrns; trnsIdx++ ) {
rt_peak[ trnsIdx ] = context.getTransmitters().indexOf( collTrns.get( trnsIdx ));
if( rt_peak[ trnsIdx ] >= 0 ) {
request.trajRequest[ rt_peak[ trnsIdx ]] = true;
rt_trnsNames[ trnsIdx ] = ((Transmitter) collTrns.get( trnsIdx )).getName();
}
}
}
finally {
doc.bird.releaseShared( Session.DOOR_TRNS | Session.DOOR_GRP );
}
// rt_currentClip.setRect( 0.0, 0.0, 1.0, 1.0 ); // repaint whole surface the first time
rt_valid = true;
return request;
}
/**
* Copies realtime generated trajectory
* data to the surface's internal buffers
* and schedules graphics update
*/
public void offhandTick( RealtimeContext context, RealtimeProducer.Source source, long currentPos )
{
int trnsIdx;
if( !rt_valid ) return;
for( trnsIdx = 0; trnsIdx < rt_peak.length; trnsIdx++ ) {
if( rt_peak[ trnsIdx ] < 0 ) continue;
rt_trnsLocX[ trnsIdx ] = source.trajOffhand[ rt_peak[ trnsIdx ]][ 0 ];
rt_trnsLocY[ trnsIdx ] = source.trajOffhand[ rt_peak[ trnsIdx ]][ 1 ];
}
repaint();
}
/**
* Copies realtime generated trajectory
* data to the surface's internal buffers
* and schedules graphics update
*/
public void realtimeTick( RealtimeContext context, RealtimeProducer.Source source, long currentPos )
{
int bufOff, trnsIdx;
if( !rt_valid ) return;
rt_pos = currentPos;
bufOff = (int) (currentPos - source.firstHalf.getStart());
if( bufOff < 0 || bufOff >= source.bufSizeH ) {
bufOff = (int) (currentPos - source.secondHalf.getStart());
if( bufOff < 0 || bufOff >= source.bufSizeH ) return; // currently no valid buffer
bufOff += source.bufSizeH;
}
for( trnsIdx = 0; trnsIdx < rt_peak.length; trnsIdx++ ) {
if( rt_peak[ trnsIdx ] < 0 ) continue;
// x and y have chances to be displayed out of sync but we don't really care
rt_trnsLocX[ trnsIdx ] = source.trajBlockBuf[ rt_peak[ trnsIdx ]][ 0 ][ bufOff ];
rt_trnsLocY[ trnsIdx ] = source.trajBlockBuf[ rt_peak[ trnsIdx ]][ 1 ][ bufOff ];
}
repaint();
}
/**
* We didn't register for block notification
*/
public void realtimeBlock( RealtimeContext context, RealtimeProducer.Source source, boolean even ) {}
// ---------------- DynamicListening interface ----------------
public void startListening()
{
doc.timeline.addTimelineListener( this );
createRealtime();
// doc.getActiveReceivers()
// EEE
// transport.addRealtimeConsumer( this );
transport.addTransportListener( this );
if( prefTrnsTraj ) {
updateTransmitterPath();
}
redrawImage();
repaint();
}
public void stopListening()
{
doc.timeline.removeTimelineListener( this );
playTimer.stop();
// EEE
// transport.removeRealtimeConsumer( this );
transport.removeTransportListener( this );
rt_valid = false;
}
// ---------------- ToolListener interface ----------------
public void toolChanged( ToolActionEvent e )
{
if( activeTool != null ) {
activeTool.toolDismissed( this );
removeMouseMotionListener( cursorListener );
}
activeTool = (AbstractTool) tools.get( new Integer( e.getToolAction().getID() ));
if( activeTool != null ) {
activeTool.toolAcquired( this );
setCursor( e.getToolAction().getDefaultCursor() );
showCursorTab();
addMouseMotionListener( cursorListener );
} else {
setCursor( null );
}
}
// ---------------- TimelineListener interface ----------------
public void timelineSelected( TimelineEvent e )
{
if( prefTrnsTraj ) {
updateTransmitterPath();
redrawImage();
repaint();
}
}
public void timelinePositioned( TimelineEvent e )
{
if( !doc.getTransport().isRunning() ) {
timelinePos = doc.timeline.getPosition();
offhandTick();
}
// updateTransmitterShapes();
// repaint();
}
public void timelineChanged( TimelineEvent e )
{
// timelineRate = doc.timeline.getRate();
timelineLen = doc.timeline.getLength();
// playTimer.setDelay( Math.min( (int) (1000 / (vpScale * timelineRate * playRate)), 33 ));
}
public void timelineScrolled( TimelineEvent e ) {}
// ---------------- TransportListener interface ----------------
public void transportPlay( Transport t, long pos, double rate )
{
// playRate = rate;
// playTimer.setDelay( Math.min( (int) (1000 / (vpScale * timelineRate * playRate)), 33 ));
playTimer.restart();
}
public void transportStop( Transport t, long pos )
{
playTimer.stop();
}
public void transportPosition( Transport t, long pos, double rate ) { /* ignored */ }
public void transportReadjust( Transport t, long pos, double rate ) { /* ignored */ }
public void transportQuit( Transport t )
{
playTimer.stop();
}
// ---------------- Shape information classes ----------------
private class ReceiverShape
{
private Point2D loc;
private Shape outline;
private String name;
private boolean selected;
private ReceiverShape( Point2D loc, Shape outline, String name, boolean selected )
{
this.loc = loc;
this.outline = outline;
this.name = name;
this.selected = selected;
}
}
// ---------------- Pointer Tool ----------------
private class SurfacePointerTool
extends AbstractTool
{
// --- drag-and-drop ---
private MouseEvent dndFirstEvent = null; // event from the initial mouse button press; if null it's not a valid drag gesture
private boolean dndDragging = false; // true when mouse has been moved to a reasonable amount
private Rectangle2D dndRecentRect; // for graphics rendering update
private Hashtable dndAnchorRef = new Hashtable();
private SurfacePointerTool()
{
super();
}
public void toolAcquired( Component c )
{
super.toolAcquired( c );
// essential inits already in finishGesture()
}
public void toolDismissed( Component c )
{
finishGesture();
super.toolDismissed( c );
}
private void finishGesture()
{
// clean up after DnD
if( dndFirstEvent != null ) {
dndFirstEvent = null;
dndDragging = false;
dndAnchorRef.clear(); // deletes references and allows garbage collection
}
}
protected void cancelGesture()
{
finishGesture();
}
public void paintOnTop( Graphics2D g2 ) {}
public void mouseEntered( MouseEvent e ) {}
public void mouseExited( MouseEvent e ) {}
public void mousePressed( MouseEvent e )
{
Point2D ptMouse = e.getPoint();
Point2D ptReceiver;
Point2D ptReceiverTrns;
Receiver rcv;
Rectangle2D clipRect, clipRect2;
double distanceSq;
double hitDistSq = 51.0; // Mouse must be as close at 5 pixels delta-h and delta-v
Receiver hitReceiver = null;
int i;
List coll;
PerformableEdit edit;
List collRcv;
e.getComponent().requestFocus(); // otherwise keyboard shortcuts etc. don't work
if( !doc.bird.attemptShared( Session.DOOR_RCV | Session.DOOR_GRP, 250 )) return;
try {
collRcv = doc.getActiveReceivers().getAll();
for( i = 0; i < collRcv.size(); i++ ) {
rcv = (Receiver) collRcv.get( i );
ptReceiver = rcv.getAnchor();
ptReceiverTrns = virtualToScreen( ptReceiver );
distanceSq = ptReceiverTrns.distanceSq( ptMouse );
if( distanceSq < hitDistSq ) {
hitDistSq = distanceSq;
hitReceiver = rcv;
}
}
// prepare DnD
if( hitReceiver != null ) {
dndFirstEvent = e; // mouseDragged() will detect this
dndDragging = false; // a follow-up mouse move is still required to start the drag
} else {
dndFirstEvent = null;
}
// manage (de)selection
if( e.isShiftDown() || e.isMetaDown() ) { // multi-selection by holding down the shift or cmd key
if( hitReceiver != null ) { // selection changed indeed
coll = doc.getSelectedReceivers().getAll();
if( !coll.contains( hitReceiver )) { // add object to selection
coll.add( hitReceiver );
} else { // remove object from selection
coll.remove( hitReceiver );
}
edit = new EditSetSessionObjects( this, doc.getMutableSelectedReceivers(), coll );
doc.getUndoManager().addEdit( edit.perform() );
updateReceiverShapes();
redrawImage();
repaint( virtualToScreenClip( hitReceiver.getBounds() ));
}
} else { // single-selection
coll = doc.getSelectedReceivers().getAll();
if( (hitReceiver == null && !coll.isEmpty()) ||
(hitReceiver != null && !coll.contains( hitReceiver ))) { // selection changed indeed
clipRect = getUnionRect( coll );
coll.clear();
if( hitReceiver != null ) {
coll.add( hitReceiver );
clipRect2 = hitReceiver.getBounds();
} else {
clipRect2 = null;
}
edit = new EditSetSessionObjects( this, doc.getMutableSelectedReceivers(), coll );
doc.getUndoManager().addEdit( edit.perform() );
updateReceiverShapes();
redrawImage();
efficientUpdateAndRepaint( clipRect, clipRect2, false );
}
}
}
finally {
doc.bird.releaseShared( Session.DOOR_RCV | Session.DOOR_GRP );
}
} // mousePressed( MouseEvent e )
public void mouseReleased( MouseEvent e )
{
finishGesture();
} // mouseReleased( MouseEvent e )
public void mouseClicked( MouseEvent e )
{
Receiver rcv;
Rectangle2D clipRect;
ReceiverEditor rcvEdit;
AbstractWindow rcvEditFrame;
List coll;
PerformableEdit edit;
if( !doc.bird.attemptShared( Session.DOOR_RCV, 250 )) return;
try {
if( doc.getSelectedReceivers().isEmpty() ) {
if( e.getClickCount() == 2 ) { // double click creates new Receiver
rcv = createReceiver( screenToVirtual( e.getPoint() ));
if( rcv != null ) {
coll = doc.getSelectedReceivers().getAll();
coll.add( rcv );
edit = new EditSetSessionObjects( this, doc.getMutableSelectedReceivers(), coll );
doc.getUndoManager().addEdit( edit.perform() );
clipRect = rcv.getBounds();
updateSurfacePaneImage( clipRect );
updateReceiverShapes();
redrawImage();
repaint( virtualToScreenClip( clipRect ));
}
}
} else { // items have been selected
if( e.getClickCount() == 2 && doc.getSelectedReceivers().size() == 1 ) { // double click opens editor
rcv = (Receiver) doc.getSelectedReceivers().get( 0 );
final Class clz = rcv.getDefaultEditor();
final Constructor cons = clz.getConstructor( new Class[] { Session.class });
rcvEdit = (ReceiverEditor) cons.newInstance( new Object[] { doc }); // XXX deligate to SurfacePaneFrame
rcvEdit.init( rcv );
rcvEditFrame = rcvEdit.getView();
rcvEditFrame.setVisible( true );
rcvEditFrame.toFront();
showCursorTab();
}
}
}
catch( InstantiationException e1 ) {
System.err.println( e1.getLocalizedMessage() );
}
catch( IllegalAccessException e2 ) {
System.err.println( e2.getLocalizedMessage() );
}
catch( InvocationTargetException e3 ) {
System.err.println( e3.getLocalizedMessage() );
}
catch( NoSuchMethodException e4 ) {
System.err.println( e4.getLocalizedMessage() );
}
finally {
doc.bird.releaseShared( Session.DOOR_RCV );
}
} // mouseClicked( MouseEvent e )
public void mouseMoved( MouseEvent e ) {}
public void mouseDragged( MouseEvent e )
{
// showCursorInfo( screenToVirtual( e ));
if( dndFirstEvent == null ) return; // not a valid drag gesture
Point2D ptDeltaMouse = new Point2D.Double( e.getPoint().getX() - dndFirstEvent.getPoint().getX(),
e.getPoint().getY() - dndFirstEvent.getPoint().getY() );
Point2D ptMouseTrns, ptAnchor;
Rectangle2D dndCurrentRect;
int i;
Receiver rcv;
List dndColl;
PerformableEdit edit;
if( !dndDragging ) { // test if mouse move was sufficient
if( ptDeltaMouse.getX()*ptDeltaMouse.getX()+ptDeltaMouse.getY()*ptDeltaMouse.getY() <= 25.0 ) return;
}
dndColl = doc.getSelectedReceivers().getAll();
if( !dndDragging ) {
dndDragging = true;
dndRecentRect = getUnionRect( dndColl );
dndAnchorRef.clear();
for( i = 0; i < dndColl.size(); i++ ) {
rcv = (Receiver) dndColl.get( i );
dndAnchorRef.put( rcv, rcv.getAnchor() );
}
}
// ptMouseTrns = screenToVirtual( ptDeltaMouse );
final Point2D ptTrns1 = screenToVirtual( dndFirstEvent.getPoint() );
final Point2D ptTrns2 = screenToVirtual( e.getPoint() );
ptMouseTrns = new Point2D.Double( ptTrns2.getX() - ptTrns1.getX(),
ptTrns2.getY() - ptTrns1.getY() );
for( i = 0; i < dndColl.size(); i++ ) {
rcv = (Receiver) dndColl.get( i );
ptAnchor = (Point2D) dndAnchorRef.get( rcv );
if( ptAnchor != null ) {
// XXX need to exclude our own anchor in the snap method() !
// rcv.setAnchor( snap( new Point2D.Double(
// Math.max( 0.0, Math.min( 1.0, ptAnchor.getX() + ptMouseTrns.getX() )),
// Math.max( 0.0, Math.min( 1.0, ptAnchor.getY() + ptMouseTrns.getY() ))), true ));
ptAnchor = new Point2D.Double(
Math.max( -1.0, Math.min( 1.0, ptAnchor.getX() + ptMouseTrns.getX() )),
Math.max( -1.0, Math.min( 1.0, ptAnchor.getY() + ptMouseTrns.getY() )));
edit = new EditSetReceiverAnchor( this, doc, rcv, ptAnchor );
//System.out.println( "ptDeltaMouse = " + ptDeltaMouse + "; ptMouseTrns = " + ptMouseTrns + "; ptAnchor = " + ptAnchor );
doc.getUndoManager().addEdit( edit.perform() );
}
}
dndCurrentRect = getUnionRect( dndColl );
updateReceiverShapes();
efficientUpdateAndRepaint( dndRecentRect, dndCurrentRect, true );
dndRecentRect = dndCurrentRect;
} // mouseDragged( MouseEvent e )
} // class SurfacePanePointerTool
// ---------------- Line Tool ----------------
private class SurfaceLineTool
extends AbstractSurfaceGeomTool
{
private Line2D lineShape = new Line2D.Double();
// function evaluation
private float f_sx, f_sy, f_dx, f_dy; // start point, delta x, delta y
private SurfaceLineTool( Main root, Session doc, VirtualSurface s )
{
super( root, doc, s, 2 );
}
protected void initControlPoints( Point2D[] ctrlPoints )
{
// no additional control points
}
protected Shape createBasicShape( Point2D[] ctrlPoints )
{
lineShape.setLine( ctrlPoints[0], ctrlPoints[1] );
return lineShape;
}
protected Shape createControlledShape( Point2D[] ctrlPoints )
{
return createBasicShape( ctrlPoints );
}
protected boolean initFunctionEvaluation( Point2D[] ctrlPoints )
{
f_sx = (float) ctrlPoints[0].getX();
f_sy = (float) ctrlPoints[0].getY();
f_dx = (float) (ctrlPoints[1].getX() - ctrlPoints[0].getX());
f_dy = (float) (ctrlPoints[1].getY() - ctrlPoints[0].getY());
return true;
}
// f_x(t) = f_sx + t * f_dx; f_y(t) = f_sy + t * f_dy
protected void evaluateFunction( float[] argBuf, float[][] resultBuf, int len )
{
int i;
float t;
float[] x = resultBuf[0];
float[] y = resultBuf[1];
for( i = 0; i < len; i++ ) {
t = argBuf[i];
x[i] = f_sx + t * f_dx;
y[i] = f_sy + t * f_dy;
}
}
}
// ---------------- Curve Tool ----------------
private class SurfaceCurveTool
extends AbstractSurfaceGeomTool
{
private Line2D lineShape = new Line2D.Double();
private CubicCurve2D curveShape = new CubicCurve2D.Double();
// function evaluation
// = start point, endpoint, ctrl pt1 + 2
private float f_p1x, f_p1y, f_p2x, f_p2y, f_cp1x, f_cp1y, f_cp2x, f_cp2y;
private SurfaceCurveTool( Main root, Session doc, VirtualSurface s )
{
super( root, doc, s, 4 );
}
protected void initControlPoints( Point2D[] ctrlPoints )
{
double r1 = 0.25 / Math.PI;
double r2 = 1.0 - r1;
ctrlPoints[2] = new Point2D.Double( ctrlPoints[0].getX() * r2 + ctrlPoints[1].getX() * r1,
ctrlPoints[0].getY() * r2 + ctrlPoints[1].getY() * r1 );
ctrlPoints[3] = new Point2D.Double( ctrlPoints[0].getX() * r1 + ctrlPoints[1].getX() * r2,
ctrlPoints[0].getY() * r1 + ctrlPoints[1].getY() * r2 );
}
protected Shape createBasicShape( Point2D[] ctrlPoints )
{
lineShape.setLine( ctrlPoints[0], ctrlPoints[1] );
return lineShape;
}
protected Shape createControlledShape( Point2D[] ctrlPoints )
{
// the curve control points (point idx 1 and 2) are
// extrapolated by factor 3.14 because that makes it
// easiert do create big curves without dragging
// the control points off the surface
Point2D ctrlPt1 = new Point2D.Double( ctrlPoints[0].getX() + Math.PI * (ctrlPoints[2].getX() - ctrlPoints[0].getX()),
ctrlPoints[0].getY() + Math.PI * (ctrlPoints[2].getY() - ctrlPoints[0].getY()));
Point2D ctrlPt2 = new Point2D.Double( ctrlPoints[1].getX() + Math.PI * (ctrlPoints[3].getX() - ctrlPoints[1].getX()),
ctrlPoints[1].getY() + Math.PI * (ctrlPoints[3].getY() - ctrlPoints[1].getY()));
// curveShape.setCurve( ctrlPoints[0], ctrlPoints[2], ctrlPoints[3], ctrlPoints[1] );
curveShape.setCurve( ctrlPoints[0], ctrlPt1, ctrlPt2, ctrlPoints[1] );
return curveShape;
}
protected boolean initFunctionEvaluation( Point2D[] ctrlPoints )
{
f_p1x = (float) ctrlPoints[0].getX();
f_p1y = (float) ctrlPoints[0].getY();
f_p2x = (float) ctrlPoints[1].getX();
f_p2y = (float) ctrlPoints[1].getY();
f_cp1x = (float) (ctrlPoints[0].getX() + Math.PI * (ctrlPoints[2].getX() - ctrlPoints[0].getX()));
f_cp1y = (float) (ctrlPoints[0].getY() + Math.PI * (ctrlPoints[2].getY() - ctrlPoints[0].getY()));
f_cp2x = (float) (ctrlPoints[1].getX() + Math.PI * (ctrlPoints[3].getX() - ctrlPoints[1].getX()));
f_cp2y = (float) (ctrlPoints[1].getY() + Math.PI * (ctrlPoints[3].getY() - ctrlPoints[1].getY()));
// f_cp1x = (float) ctrlPoints[2].getX();
// f_cp1y = (float) ctrlPoints[2].getY();
// f_cp2x = (float) ctrlPoints[3].getX();
// f_cp2y = (float) ctrlPoints[3].getY();
return true;
}
// p(t) = (1 - 3t + 3t^2 - t^3) p1 + (3t - 6t^2 + 3t^3) cp1 + (3t^2 - 3t^3) cp2 + t^3 p2
protected void evaluateFunction( float[] argBuf, float[][] resultBuf, int len )
{
int i;
float t, t3, tt, tt3, ttt, ttt3 /*, ttt2 */;
float coeff1, coeff2, coeff3, coeff4;
float[] x = resultBuf[0];
float[] y = resultBuf[1];
for( i = 0; i < len; i++ ) {
t = argBuf[i];
t3 = t*3;
tt = t*t;
tt3 = tt*3;
ttt = tt*t;
ttt3 = ttt*3;
// ttt2 = ttt*2;
coeff1 = 1.0f - t3 + tt3 - ttt;
coeff2 = t3 - 6*tt + ttt3;
coeff3 = tt3 - ttt3;
coeff4 = ttt;
x[i] = coeff1 * f_p1x + coeff2 * f_cp1x + coeff3 * f_cp2x + coeff4 * f_p2x;
y[i] = coeff1 * f_p1y + coeff2 * f_cp1y + coeff3 * f_cp2y + coeff4 * f_p2y;
}
}
}
// ---------------- Arc Tool ----------------
private class SurfaceArcTool
extends AbstractSurfaceGeomTool
{
private Ellipse2D.Double ellipseShape = new Ellipse2D.Double();
private Arc2D arcShape = new Arc2D.Double();
private Rectangle2D frame = new Rectangle2D.Double();
private Rectangle2D recentFrame = new Rectangle2D.Double();
private Point2D[] recentCtrlPt = new Point2D[3];
private double[] ctrlPtAngle = new double[3];
// function evaluation
// = start angle, delta angle, x radius, y radius, center point
private float f_sa, f_da, f_rx, f_ry, f_cx, f_cy;
private SurfaceArcTool( Main root, Session doc, VirtualSurface s )
{
super( root, doc, s, 5 );
}
protected void initControlPoints( Point2D[] ctrlPoints )
{
double cx, cy, rx, ry;
int i;
calcFrame( ctrlPoints );
rx = frame.getWidth()/2;
ry = frame.getHeight()/2;
cx = frame.getX() + rx;
cy = frame.getY() + ry;
arcShape.setFrame( frame );
recentFrame.setFrame( frame );
ctrlPtAngle[0] = Math.PI * 0.75;
ctrlPtAngle[1] = Math.PI * 1.75;
ctrlPtAngle[2] = Math.PI * 1.25;
for( i = 0; i < 3; i++ ) {
ctrlPoints[i+2] = new Point2D.Double( cx + Math.cos( ctrlPtAngle[i] ) * rx,
cy + Math.sin( ctrlPtAngle[i] ) * ry );
recentCtrlPt[i] = ctrlPoints[i+2];
}
arcShape.setAngleStart( Math.toDegrees( -ctrlPtAngle[0] ));
arcShape.setAngleExtent( Math.toDegrees( ctrlPtAngle[1] - ctrlPtAngle[0] ));
}
protected Shape createBasicShape( Point2D[] ctrlPoints )
{
calcFrame( ctrlPoints );
ellipseShape.setFrame( frame );
return ellipseShape;
}
protected Shape createControlledShape( Point2D[] ctrlPoints )
{
double cx, cy, rx, ry;
boolean orient;
int i;
double[] ang = new double[3];
calcFrame( ctrlPoints );
rx = frame.getWidth()/2;
ry = frame.getHeight()/2;
cx = frame.getX() + rx;
cy = frame.getY() + ry;
for( i = 0; i < 3; i++ ) {
ang[i] = Math.atan2( (ctrlPoints[i+2].getY() - cy) / ry, (ctrlPoints[i+2].getX() - cx) / rx );
}
if( ang[1] < ang[0] ) ang[1] += MathUtil.PI2;
if( ang[2] < ang[0] ) ang[2] += MathUtil.PI2;
orient = (ang[1] - ang[0]) / (ang[2] - ang[0]) < 1.0;
if( recentFrame.equals( frame )) {
for( i = 0; i < 3; i++ ) {
if( !ctrlPoints[i+2].equals( recentCtrlPt[i] )) {
ctrlPtAngle[i] = ang[i];
ctrlPoints[i+2].setLocation( cx + Math.cos( ctrlPtAngle[i] ) * rx,
cy + Math.sin( ctrlPtAngle[i] ) * ry );
recentCtrlPt[i] = ctrlPoints[i+2];
}
}
if( orient ) {
arcShape.setAngleStart( Math.toDegrees( -ang[0] ));
arcShape.setAngleExtent( 360.0 - Math.toDegrees( ang[1] - ang[0] ));
} else {
arcShape.setAngleStart( Math.toDegrees( -ang[1] ));
arcShape.setAngleExtent( Math.toDegrees( ang[1] - ang[0] ));
}
} else {
arcShape.setFrame( frame );
recentFrame.setFrame( frame );
for( i = 0; i < 3; i++ ) {
ctrlPoints[i+2].setLocation( cx + Math.cos( ctrlPtAngle[i] ) * rx,
cy + Math.sin( ctrlPtAngle[i] ) * ry );
recentCtrlPt[i] = ctrlPoints[i+2];
}
}
return arcShape;
}
// public boolean canRotate()
// {
// return true;
// }
private void calcFrame( Point2D[] ctrlPoints )
{
frame.setFrame( Math.min( ctrlPoints[0].getX(), ctrlPoints[1].getX() ),
Math.min( ctrlPoints[0].getY(), ctrlPoints[1].getY() ),
Math.abs( ctrlPoints[1].getX() - ctrlPoints[0].getX() ),
Math.abs( ctrlPoints[1].getY() - ctrlPoints[0].getY() ));
}
protected boolean initFunctionEvaluation( Point2D[] ctrlPoints )
{
int i;
double[] ang = new double[3];
boolean orient;
double cx, cy, rx, ry;
rx = Math.abs( ctrlPoints[1].getX() - ctrlPoints[0].getX() ) / 2;
ry = Math.abs( ctrlPoints[1].getY() - ctrlPoints[0].getY() ) / 2;
cx = Math.min( ctrlPoints[0].getX(), ctrlPoints[1].getX() ) + rx;
cy = Math.min( ctrlPoints[0].getY(), ctrlPoints[1].getY() ) + ry;
for( i = 0; i < 3; i++ ) {
ang[i] = Math.atan2( (ctrlPoints[i+2].getY() - cy) / ry, (ctrlPoints[i+2].getX() - cx) / rx );
}
while( ang[1] < ang[0] ) ang[1] += MathUtil.PI2;
while( ang[2] < ang[0] ) ang[2] += MathUtil.PI2;
orient = (ang[1] - ang[0]) / (ang[2] - ang[0]) < 1.0;
//System.err.println( "ang[0] "+(180/Math.PI*ang[0])+" ; ang[1] "+(180/Math.PI*ang[1])+" ; ang[2] "+(180/Math.PI*ang[2])+" ; orient "+orient );
f_rx = (float) rx;
f_ry = (float) ry;
f_cx = (float) cx;
f_cy = (float) cy;
f_sa = (float) ang[1];
if( orient ) {
f_da = (float) (ang[1] - ang[0]);
// don't ask me why this is working
if( f_da < ang[2] - ang[0] ) f_da = (float) (Math.PI * 2 - f_da);
} else {
f_da = (float) (ang[0] - ang[1]);
}
return true;
}
// f_x(t) = f_cx + cos( f_sa + t * f_da ) * f_rx; f_y(t) = f_cy + sin( f_sa + t * f_da ) * f_ry;
protected void evaluateFunction( float[] argBuf, float[][] resultBuf, int len )
{
int i;
float angle;
float[] x = resultBuf[0];
float[] y = resultBuf[1];
for( i = 0; i < len; i++ ) {
angle = f_sa + argBuf[i] * f_da;
x[i] = f_cx + f_rx * (float) Math.cos( angle );
y[i] = f_cy + f_ry * (float) Math.sin( angle );
}
}
}
// ---------------- Pencil Tool ----------------
// XXX @todo : outer class should release mouse motion listener
// since we are using our own when dragging ?
private class SurfacePencilTool
extends AbstractTool
implements ProcessingThread.Client, TrajectoryGenerator
{
// --- drag-and-drop ---
private MouseEvent dndFirstEvent = null; // event from the initial mouse button press; if null it's not a valid drag gesture
private boolean dndDragging = false; // true when mouse has been moved to a reasonable amount
// private boolean dndVelocity = false; // true when adjusting the velocity
private Rectangle2D dndRecentRect; // for graphics rendering update
// private Line2D dndLine = new Line2D.Double();
// private GeneralPath dndFreehand = new GeneralPath();
private List dndFreehandPoints = new ArrayList();
private PointInTime dndLatest;
private int dndLatestIdx;
private ProcessingThread renderThread = null;
// calculation of the velocity area
// private double lineAngle; // of the drawn line
// private GeneralPath vShape = new GeneralPath(); // velocity visualization
private double vStart, vStop; // velocity
// private final double normedRadius = 0.15;
// function evaluation
private float f_scale, f_trans, f_diffx, f_diffy, f_begx, f_begy;
private int f_idx;
private PointInTime f_pit, f_pit2;
private final boolean previewOnly;
// painting
private static final int PNT_SHADES = 8;
private int pntNum = 0;
private double[] pntLocX = new double[ PNT_SHADES ];
private double[] pntLocY = new double[ PNT_SHADES ];
// when the user presses the mouse and transport is not
// running, we'll start it automatically ; in this case
// weStartedTheTransport will be set to 'true' in order
// to remember to stop the transport automatically when
// the mouse button is released
private boolean weStartedTheTransport;
// when the user presses the mouse, a trajectory replacement
// will be installed that overrides the harddisk trajectory
// with the current mouse position, until drag is completed or aborted
private RealtimeProducer.TrajectoryReplacement trajRplc = null;
private Point2D ptCurrentMouse; // always the last recognized drag in virtual space
private final Color[] fadeAway = {
new Color( 0x00, 0x00, 0xFF, 0xFF ), new Color( 0x00, 0x00, 0xFF, 0xE0 ),
new Color( 0x00, 0x00, 0xFF, 0xC0 ), new Color( 0x00, 0x00, 0xFF, 0xA0 ),
new Color( 0x00, 0x00, 0xFF, 0x80 ), new Color( 0x00, 0x00, 0xFF, 0x60 ),
new Color( 0x00, 0x00, 0xFF, 0x40 ), new Color( 0x00, 0x00, 0xFF, 0x20 )
};
private final Object[] msgArgs = new Object[3];
private final MessageFormat msgSingleSense = new MessageFormat( getResourceString(
"forkToolSingleSenseMsg" ), Locale.US ); // XXX
private final MessageFormat msgSumSense = new MessageFormat( getResourceString(
"forkToolSumSenseMsg" ), Locale.US ); // XXX
private SurfacePencilTool( boolean previewOnly )
{
super();
this.previewOnly = previewOnly;
}
public void toolAcquired( Component c )
{
super.toolAcquired( c );
// essential inits already in finishGesture()
}
public void toolDismissed( Component c )
{
finishGesture( false );
// wait for rendering to be completed
while( renderThread != null && renderThread.isRunning() ) {
renderThread.sync();
renderThread = null;
}
super.toolDismissed( c );
}
private void finishGesture( boolean success )
{
if( trajRplc != null ) {
// EEE
// transport.removeTrajectoryReplacement( trajRplc );
doc.getRealtimeProducer().requestRemoveTrajectoryReplacement( trajRplc );
trajRplc = null;
}
cursorInfo[2] = EMPTY_STR;
cursorInfo[3] = EMPTY_STR;
cursorInfo[4] = EMPTY_STR;
// clean up after DnD
dndFirstEvent = null;
if( dndDragging ) {
dndDragging = false;
// dndVelocity = false;
repaint( virtualToScreenClip( dndRecentRect ));
if( success && !previewOnly ) {
if( dndFreehandPoints.size() < 2 ) return;
final List collTrns = doc.getSelectedTransmitters().getAll();
if( collTrns.isEmpty() ) return;
final PointInTime pit, pit2;
pit = (PointInTime) dndFreehandPoints.get( 0 );
pit2 = (PointInTime) dndFreehandPoints.get( dndFreehandPoints.size() - 1 );
final Span span = new Span( pit.getWhen(), pit2.getWhen() );
if( span.getLength() < 2 ) return;
if( !doc.checkProcess( 1000 )) return;
// renderThread = new ProcessingThread( this, root, root, doc,
// AbstractApplication.getApplication().getResourceString( "toolWriteTransmitter" ),
// null, Session.DOOR_TIMETRNSMTE );
final String name = AbstractApplication.getApplication().
getResourceString( "toolWriteTransmitter" );
renderThread = new ProcessingThread( this, root, name );
renderThread.putClientArg( "span", span );
renderThread.putClientArg( "trns", collTrns );
renderThread.putClientArg( "blend", root.getBlending() );
renderThread.putClientArg( "edit",
new CompoundSessionObjEdit( this, collTrns,
Transmitter.OWNER_TRAJ, null, null, "Pencil" ));
renderThread.start();
}
}
}
public void paintOnTop( Graphics2D g2 )
{
if( !dndDragging ) return;
AffineTransform trnsRecent = g2.getTransform();
int i;
Point2D pt;
// if( dndVelocity ) {
// g2.setColor( colrAdjusting );
// g2.fill( vShape );
// }
g2.setStroke( strkLine );
if( pntNum < PNT_SHADES ) pntNum++;
System.arraycopy( pntLocX, 0, pntLocX, 1, pntNum - 1 );
System.arraycopy( pntLocY, 0, pntLocY, 1, pntNum - 1 );
pt = (Point2D) dndFreehandPoints.get( dndLatestIdx );
pntLocX[ 0 ] = pt.getX();
pntLocY[ 0 ] = pt.getY();
for( i = 0; i < pntNum; i++ ) {
g2.setColor( fadeAway[ i ]);
g2.translate( pntLocX[ i ], pntLocY[ i ]);
g2.fill( shpCrossHair );
g2.setTransform( trnsRecent );
}
}
// private void updateVShape()
// {
// double beta1, beta2;
// Rectangle2D dndCurrentRect;
//
// vShape.reset();
// beta1 = lineAngle - Math.PI/2;
// beta2 = beta1 + Math.PI;
// vShape.moveTo( (float) (dndLine.getX1() + normedRadius * vStart * Math.cos( beta1 )),
// (float) (dndLine.getY1() + normedRadius * vStart * Math.sin( beta1 )));
// vShape.lineTo( (float) (dndLine.getX2() + normedRadius * vStop * Math.cos( beta1 )),
// (float) (dndLine.getY2() + normedRadius * vStop * Math.sin( beta1 )));
// vShape.lineTo( (float) (dndLine.getX2() + normedRadius * vStop * Math.cos( beta2 )),
// (float) (dndLine.getY2() + normedRadius * vStop * Math.sin( beta2 )));
// vShape.lineTo( (float) (dndLine.getX1() + normedRadius * vStart * Math.cos( beta2 )),
// (float) (dndLine.getY1() + normedRadius * vStart * Math.sin( beta2 )));
// vShape.closePath();
// dndCurrentRect = vShape.getBounds2D();
// efficientUpdateAndRepaint( dndRecentRect, dndCurrentRect, false );
// dndRecentRect = dndCurrentRect;
// }
// sync: caller must have on RCV, GRP
private void calcCursorInfo()
{
int rcvIdx, i;
int numRcv = doc.getActiveReceivers().size();
float f1 = 0.0f, f2 = 0.0f, f3 = 0.0f, f4;
String loudest1 = null, loudest2 = null;
Receiver rcv;
float[] sense = new float[1];
float[][] points = new float[2][1];
points[0][0] = (float) ptCurrentMouse.getX();
points[1][0] = (float) ptCurrentMouse.getY();
for( rcvIdx = 0; rcvIdx < numRcv; rcvIdx++ ) {
rcv = (Receiver) doc.getActiveReceivers().get( rcvIdx );
rcv.getSensitivities( points, sense, 0, 1, 1 );
f4 = sense[0];
if( f4 > f1 ) {
f2 = f1;
loudest2 = loudest1;
f1 = f4;
loudest1 = rcv.getName();
} else if( f4 > f2 ) {
f2 = f4;
loudest2 = rcv.getName();
}
if( prefRcvEqP ) {
f3 += f4*f4;
} else {
f3 += f4;
}
}
if( prefRcvEqP ) {
f3 = (float) Math.sqrt( f3 );
}
i = 2;
if( loudest1 != null ) {
msgArgs[0] = loudest1;
msgArgs[1] = new Double( f1 );
msgArgs[2] = new Double( MathUtil.linearToDB( f1 ));
cursorInfo[i++] = msgSingleSense.format( msgArgs );
}
if( loudest2 != null ) {
msgArgs[0] = loudest2;
msgArgs[1] = new Double( f2 );
msgArgs[2] = new Double( MathUtil.linearToDB( f2 ));
cursorInfo[i++] = msgSingleSense.format( msgArgs );
}
if( numRcv > 0 ) {
msgArgs[1] = new Double( f3 );
msgArgs[2] = new Double( MathUtil.linearToDB( f3 ));
cursorInfo[i++] = msgSumSense.format( msgArgs );
}
while( i < cursorInfo.length ) cursorInfo[ i++ ] = EMPTY_STR;
showCursorInfo( ptCurrentMouse );
}
public void mouseEntered( MouseEvent e ) {}
public void mouseExited( MouseEvent e ) {}
// sync: this method attempts on time, trns and grp
public void mousePressed( MouseEvent e )
{
if( renderThread != null && renderThread.isRunning() ) {
renderThread.sync();
renderThread = null;
}
long when;
List collTrns;
e.getComponent().requestFocus();
// prepare DnD
dndFirstEvent = e; // mouseDragged() will detect this
dndDragging = false; // a follow-up mouse move is still required to start the drag
dndFreehandPoints.clear();
ptCurrentMouse = screenToVirtual( snap( dndFirstEvent.getPoint(), false ));
// check replacement
if( trajRplc == null ) {
// if( doc.bird.attemptShared( Session.DOOR_TIMETRNSRCV | Session.DOOR_GRP, 250 )) {
// try {
collTrns = doc.getActiveTransmitters().getAll();
collTrns.retainAll( doc.getSelectedTransmitters().getAll() );
trajRplc = new RealtimeProducer.TrajectoryReplacement(
this, new Span( 0, doc.timeline.getLength() ), collTrns );
// EEE
// transport.addTrajectoryReplacement( trajRplc );
doc.getRealtimeProducer().requestAddTrajectoryReplacement( trajRplc );
calcCursorInfo();
// } finally {
// doc.bird.releaseShared( Session.DOOR_TIMETRNSRCV | Session.DOOR_GRP );
// }
// }
}
// check auto-step-mode
weStartedTheTransport = !(transport.isRunning() || previewOnly);
if( transport.isRunning() ) {
rt_pos = transport.getCurrentFrame();
} else {
rt_pos = doc.timeline.getPosition();
}
if( weStartedTheTransport ) {
transport.play( 1.0 );
}
when = rt_pos;
dndFreehandPoints.add( new PointInTime( ptCurrentMouse, when ));
vStart = 1.0;
vStop = 1.0;
pntNum = 0;
} // mousePressed( MouseEvent e )
public void mouseReleased( MouseEvent e )
{
// Point2D pit, pit2;
//
// if( dndDragging && e.isAltDown() ) {
// pit = (Point2D) dndFreehandPoints.firstElement();
// pit2 = (Point2D) dndFreehandPoints.lastElement();
// dndLine.setLine( pit.getX(), pit.getY(), pit2.getX(), pit2.getY() );
// if( GraphicsUtil.getLineLength( dndLine ) > 0.0 ) {
// dndVelocity = true;
// lineAngle = Math.atan2( dndLine.getY2() - dndLine.getY1(), dndLine.getX2() - dndLine.getX1() );
// vShape.reset();
// updateVShape();
// } else {
// finishGesture( true );
// }
// } else {
if( weStartedTheTransport ) transport.stop(); // need to wait so rt_pos is correct
mouseDragged( e ); // add a last point for the current timeline pos!
finishGesture( true );
// }
} // mouseReleased( MouseEvent e )
public void mouseClicked( MouseEvent e )
{
} // mouseClicked( MouseEvent e )
public void mouseMoved( MouseEvent e )
{
// ptCurrentMouse = screenToVirtual( e.getPoint() );
//
// if( dndVelocity ) { // adjusting velocity shape
// Point2D ptProjection = GraphicsUtil.projectPointOntoLine( ptCurrentMouse, dndLine );
//
// if( ptProjection.getX() >= Math.min( dndLine.getX1(), dndLine.getX2() ) &&
// ptProjection.getX() <= Math.max( dndLine.getX1(), dndLine.getX2() )) { // point lines inside line segm.
//
// vStart = Math.min( 2.0, Math.max( 0.0, 2 * ptProjection.distance( dndLine.getX1(), dndLine.getY1() ) /
// GraphicsUtil.getLineLength( dndLine )));
// vStop = 2.0 - vStart;
//
// } else {
// if( Math.abs( ptProjection.getX() - dndLine.getX1() ) < Math.abs( ptProjection.getX() - dndLine.getX2() )) {
// vStart = 0.0;
// vStop = 2.0;
// } else {
// vStart = 2.0;
// vStop = 0.0;
// }
// }
//
// updateVShape();
// }
} // mouseMoved( MouseEvent e )
public void mouseDragged( MouseEvent e )
{
if( dndFirstEvent == null ) return; // not a valid drag gesture
long when, delta, delta2;
PointInTime pit, pit2, recentLatest;
int i;
double d1;
Span span;
if( !dndDragging ) { // test if mouse move was sufficient
dndDragging = true;
dndRecentRect = new Rectangle2D.Double();
dndLatest = (PointInTime) dndFreehandPoints.get( 0 );
dndLatestIdx = 0;
}
ptCurrentMouse = screenToVirtual( e.getPoint() );
// when = rt_pos;
// EEE
when = transport.getCurrentFrame();
recentLatest= dndLatest;
dndLatest = new PointInTime( ptCurrentMouse, when );
pit = (PointInTime) dndFreehandPoints.get( dndFreehandPoints.size() - 1 );
pit2 = (PointInTime) dndFreehandPoints.get( 0 );
if( recentLatest.getWhen() < when ) { // add after recent latest
dndLatestIdx++;
for( i = dndLatestIdx; i < dndFreehandPoints.size(); i++ ) {
if( ((PointInTime) dndFreehandPoints.get( i )).getWhen() > when ) break;
}
while( i > dndLatestIdx ) {
dndFreehandPoints.remove( --i );
}
dndFreehandPoints.add( dndLatestIdx, dndLatest );
} else if( recentLatest.getWhen() == when ) { // replace recent latest
dndFreehandPoints.set( dndLatestIdx, dndLatest );
} else { // loop has restarted
dndLatestIdx = 0;
for( i = 0; i < dndFreehandPoints.size(); i++ ) {
if( ((PointInTime) dndFreehandPoints.get( i )).getWhen() > when ) break;
}
while( i > 0 ) {
dndFreehandPoints.remove( --i );
}
span = doc.timeline.getSelectionSpan();
if( !span.isEmpty() && when > span.getStart() ) {
delta = span.getStop() - pit.getWhen();
delta2 = pit2.getWhen() - span.getStart();
if( delta > 0 && delta2 >= 0 ) {
if( delta2 > 0 && when > span.getStart() ) { // add interpolated point at loop start
d1 = (double) delta / (double) (delta + delta2);
pit = new PointInTime( pit.getX() + (pit2.getX() - pit.getX()) * d1,
pit.getY() + (pit2.getY() - pit.getY()) * d1, span.getStart() );
dndFreehandPoints.add( dndLatestIdx++, pit );
}
if( delta > 1 && when < span.getStop() - 1 ) { // add interpolated point at loop end
d1 = (double) (delta - 1) / (double) (delta + delta2);
pit = new PointInTime( pit.getX() + (pit2.getX() - pit.getX()) * d1,
pit.getY() + (pit2.getY() - pit.getY()) * d1, span.getStop() - 1 );
dndFreehandPoints.add( pit );
}
}
}
dndFreehandPoints.add( dndLatestIdx, dndLatest );
} // if( pit.getWhen() < when )
calcCursorInfo();
} // mouseDragged( MouseEvent e )
protected void cancelGesture()
{
finishGesture( false );
}
/**
* Write the pencil movement
*
* time warp is performed as follows:
* t'(t) = v_start * t + (v_end - v_start)/2T * t^2 , v_start = (0...2)/T, v_end = 2/T - v_start
*/
public int processRun( ProcessingThread context )
{
final CompoundSessionObjEdit edit = (CompoundSessionObjEdit) context.getClientArg( "edit" );
final List collTrns = (List) context.getClientArg( "trns" );
final BlendContext bc = (BlendContext) context.getClientArg( "blend" );
final Span span = (Span) context.getClientArg( "span" );
final float[][] srcBuf = bc == null ? null : new float[ 2 ][ 4096 ];
final double t_norm;
final float[][] interpBuf;
final float[] warpedTime;
final float v_start_norm, dv_norm;
final long interpLen, progressLen;
Transmitter trns;
AudioTrail at;
int len;
long t, tt;
// BlendSpan bs;
// interpLen entspricht 'T' in der Formel (Gesamtzeit), interpOff entspricht 't' (aktueller Zeitpunkt)
long start, interpOff;
long progress = 0;
AudioStake as;
interpLen = span.getLength();
warpedTime = new float[(int) Math.min( interpLen, 4096 )];
interpBuf = new float[2][ warpedTime.length ];
t_norm = 1.0 / (interpLen - 1);
v_start_norm = (float) (vStart * t_norm);
dv_norm = (float) ((vStop - vStart) / 2 * t_norm * t_norm);
// initFunctionEvaluation();
progressLen = interpLen*collTrns.size();
try {
for( int i = 0; i < collTrns.size(); i++ ) {
trns = (Transmitter) collTrns.get( i );
at = trns.getAudioTrail();
// bs = at.beginOverwrite( span, bc, edit );
as = at.alloc( span );
// XXX has to be called for each trns?
initFunctionEvaluation();
for( start = span.getStart(), interpOff = 0; start < span.getStop();
start += len, interpOff += len ) {
len = (int) Math.min( 4096, span.getStop() - start );
t = interpOff;
for( int j = 0; j < len; j++, t++ ) {
tt = t*t;
warpedTime[j] = v_start_norm * t + dv_norm * tt;
// warpedTime[j] = (v_start_norm * t + dv_norm * tt) * 1.5f - 0.25f; // extrap.
}
evaluateFunction( warpedTime, interpBuf, len );
if( bc != null ) {
at.readFrames( srcBuf, 0, new Span( start, start + len ));
if( interpOff < bc.getLen() ) { // EEE getLen?
bc.blend( interpOff, srcBuf, 0, interpBuf, 0, interpBuf, 0, len );
}
if( interpLen - (interpOff + len) < bc.getLen() ) { // EEE getLen?
bc.blend( interpOff - (interpLen - bc.getLen()), interpBuf, 0, srcBuf, 0, interpBuf, 0, len );
}
}
as.writeFrames( interpBuf, 0, new Span( start, start + len ));
// at.continueWrite( bs, interpBuf, 0, len );
progress += len;
context.setProgression( (float) progress / (float) progressLen );
}
at.editBegin( edit );
at.editClear( this, span, edit );
at.editAdd( this, as, edit );
at.editEnd( edit );
// at.finishWrite( bs, edit );
} // for( i = 0; i < collTransmitters.size(); i++ )
// edit.perform();
// edit.end(); // fires doc.tc.modified()
// doc.getUndoManager().addEdit( edit );
return DONE;
}
catch( IOException e1 ) {
edit.cancel();
context.setException( e1 );
return FAILED;
}
} // run()
public void processFinished( ProcessingThread context )
{
final AbstractCompoundEdit edit = (AbstractCompoundEdit) context.getClientArg( "edit" );
if( context.getReturnCode() == ProgressComponent.DONE ) {
edit.perform();
edit.end(); // fires doc.tc.modified()
doc.getUndoManager().addEdit( edit );
} else {
edit.cancel();
}
}
public void processCancel( ProcessingThread context ) {}
private void initFunctionEvaluation()
{
PointInTime pit, pit2;
pit = (PointInTime) dndFreehandPoints.get( 0 );
pit2 = (PointInTime) dndFreehandPoints.get( dndFreehandPoints.size() - 1 );
f_scale = (float) (pit2.getWhen() - pit.getWhen());
f_trans = (float) pit.getWhen();
f_idx = 1;
f_pit = pit;
f_pit2 = (PointInTime) dndFreehandPoints.get( 1 );
f_diffx = (float) ((f_pit2.getX() - f_pit.getX()) / (f_pit2.getWhen() - f_pit.getWhen()));
f_diffy = (float) ((f_pit2.getY() - f_pit.getY()) / (f_pit2.getWhen() - f_pit.getWhen()));
f_begx = (float) f_pit.getX();
f_begy = (float) f_pit.getY();
}
// p(t) = (1 - 3t + 3t^2 - t^3) p1 + (3t - 6t^2 + 3t^3) cp1 + (3t^2 - 3t^3) cp2 + t^3 p2
private void evaluateFunction( float[] argBuf, float[][] resultBuf, int len )
{
float t, off;
float[] x = resultBuf[0];
float[] y = resultBuf[1];
int i;
for( i = 0; i < len; i++ ) {
t = argBuf[i] * f_scale + f_trans;
while( t > f_pit2.getWhen() ) {
f_pit = f_pit2;
f_idx++;
f_pit2 = (PointInTime) dndFreehandPoints.get( f_idx );
f_diffx = (float) ((f_pit2.getX() - f_pit.getX()) / (f_pit2.getWhen() - f_pit.getWhen()));
f_diffy = (float) ((f_pit2.getY() - f_pit.getY()) / (f_pit2.getWhen() - f_pit.getWhen()));
f_begx = (float) f_pit.getX();
f_begy = (float) f_pit.getY();
}
off = t - f_pit.getWhen();
x[i] = f_begx + off * f_diffx;
y[i] = f_begy + off * f_diffy;
}
}
// ---------- TrajectoryGenerator interface ----------
public void read( Span span, float[][] frames, int off )
throws IOException
{
int stop = (int) (span.getLength() + off);
float x = (float) ptCurrentMouse.getX();
float y = (float) ptCurrentMouse.getY();
for( ; off < stop; off++ ) {
frames[ 0 ][ off ] = x;
frames[ 1 ][ off ] = y;
}
}
} // class SurfacePanePencilTool
} // class SurfacePane