/*
* PanoramaPanel.java
* Eisenkraut
*
* Copyright (c) 2004-2014 Hanns Holger Rutz. All rights reserved.
*
* This software is published under the GNU General Public License v3+
*
*
* For further information, please contact Hanns Holger Rutz at
* contact@sciss.de
*/
package de.sciss.eisenkraut.gui;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Insets;
import java.awt.Paint;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.geom.AffineTransform;
import java.awt.geom.Arc2D;
import java.awt.geom.Area;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Line2D;
import java.awt.geom.Point2D;
import java.util.ArrayList;
import java.util.List;
import javax.swing.BorderFactory;
import javax.swing.JComponent;
import javax.swing.event.MouseInputAdapter;
public class PanoramaPanel
extends JComponent
{
private final AffineTransform at = new AffineTransform();
protected static final Insets insets = new Insets( 1, 1, 1, 1 );
private static final Shape shpCtrlIn = new Ellipse2D.Double( -2, -2, 5, 5 );
private static final Area shpCtrlOut;
private static final Paint pntCtrlIn = new Color( 0x00, 0x00, 0x00, 0x7F );
private static final Paint pntCtrlOut = new Color( 0x00, 0x00, 0x00, 0x3F );
private static final Paint pntCtrlOutS = new Color( 0x00, 0x00, 0xFF, 0x7F );
private final List outlines = new ArrayList();
private final List tOutlines = new ArrayList();
private final List outlinePaints = new ArrayList();
private final List areas = new ArrayList();
private final List tAreas = new ArrayList();
private final List areaPaints = new ArrayList();
private final Point2D ctrlPt = new Point2D.Double( 0.0, 0.75 );
protected double recentRadius = -1;
private boolean recalc = true;
protected boolean isDragging = false;
protected final double startAngle, deltaAngle;
private final int numSpots;
protected double azi, spread;
private static final List collListeners = new ArrayList();
static {
shpCtrlOut = new Area( new Ellipse2D.Double( -7, -7, 15, 15 ));
shpCtrlOut.subtract( new Area( new Ellipse2D.Double( -4, -4, 9, 9 )));
}
public PanoramaPanel( final int numSpots, double startAng )
{
super();
if( startAng < 0 ) {
startAng = 360 - ((-startAng) % 360);
}
startAngle = startAng % 360;
deltaAngle = 360.0 / numSpots;
this.numSpots = numSpots;
outlines.add( new Ellipse2D.Double( -1.0, -1.0, 2.0, 2.0 ));
outlinePaints.add( Color.black );
outlines.add( new Ellipse2D.Double( -0.75, -0.75, 1.5, 1.5 ));
outlinePaints.add( pntCtrlOut );
double angDeg, angRad, dx, dy;
double speakerWidth = Math.max( 4, Math.min( 10, deltaAngle / 2 ));
Arc2D arc1 = new Arc2D.Double( -1.15, -1.15, 2.3, 2.3, 0, speakerWidth, Arc2D.PIE );
Arc2D arc2 = new Arc2D.Double( -1.05, -1.05, 2.1, 2.1, 0, speakerWidth, Arc2D.PIE );
// GeneralPath gen;
Area area;
for( int i = 0; i < numSpots; i++ ) {
angDeg = startAngle + i * deltaAngle;
angRad = (-angDeg + 90) * Math.PI / 180;
dx = Math.cos( angRad );
dy = Math.sin( angRad );
outlines.add( new Line2D.Double( 0.0, 0.0, dx, dy ));
outlinePaints.add( pntCtrlOut );
arc1.setAngleStart( angDeg - 90 - speakerWidth/2 );
arc2.setAngleStart( angDeg - 90 - speakerWidth/2 );
area = new Area( arc1 );
area.subtract( new Area( arc2 ));
areas.add( area );
areaPaints.add( pntCtrlIn );
}
setMinimumSize( new Dimension( 64, 64 ));
setPreferredSize( new Dimension( 128, 128 ));
setBorder( BorderFactory.createEmptyBorder( insets.left, insets.top, insets.bottom, insets.right ));
// setBorder( BorderFactory.createMatteBorder( insets.left, insets.top, insets.bottom, insets.right, Color.green ));
MouseInputAdapter mia = new MouseInputAdapter() {
public void mousePressed( MouseEvent e )
{
final Point2D mousePt = getVirtualMousePos( e );
isDragging = true;
processDrag( mousePt, !e.isControlDown() );
}
public void mouseReleased( MouseEvent e )
{
isDragging = false;
repaint();
}
// x ^= radius, y ^= angleRad
private Point2D getVirtualMousePos( MouseEvent e )
{
final double x = ((e.getX() - insets.left) / recentRadius) - 1.15;
final double y = 1.15 - ((e.getY() - insets.top) / recentRadius);
return new Point2D.Double( Math.min( 1.0, Math.sqrt( x * x + y * y )),
Math.atan2( y, x ));
}
public void mouseDragged( MouseEvent e )
{
if( isDragging ) processDrag( getVirtualMousePos( e ), !e.isControlDown() );
}
public void mouseMoved( MouseEvent e )
{
mouseDragged( e );
}
private void processDrag( Point2D mousePt, boolean snap )
{
double dragAngDeg, temp, temp2; // , spreadDegH;
if( snap ) {
if( mousePt.getX() < 0.1 ) {
mousePt.setLocation( 0.0, mousePt.getY() );
} else if( Math.abs( mousePt.getX() - 0.75 ) < 0.1 ) {
mousePt.setLocation( 0.75, mousePt.getY() );
} else if( 1.0 - mousePt.getX() < 0.1 ) {
mousePt.setLocation( 1.0, mousePt.getY() );
}
dragAngDeg = -(mousePt.getY() * 180 / Math.PI) + 90;
if( dragAngDeg < 0 ) dragAngDeg += 360;
for( int i = 0; i < numSpots * 2; i++ ) {
temp = (startAngle + (deltaAngle * i) / 2) % 360;
temp2 = Math.abs( temp - dragAngDeg );
if( temp2 > 180 ) temp2 = 360 - temp2;
if( temp2 < 5 ) {
mousePt.setLocation( mousePt.getX(), (-temp + 90) * Math.PI / 180 );
break;
}
}
}
// azi + spread
if( mousePt.getX() <= 0.75 ) {
spread = 1.0 - (mousePt.getX() / 0.75);
} else {
spread = (0.75 - mousePt.getX()) / 0.25;
}
dragAngDeg = -(mousePt.getY() * 180 / Math.PI) + 90;
setAzimuthAndSpread( dragAngDeg, spread );
dispatchAction();
}
};
addMouseListener( mia );
addMouseMotionListener( mia );
//setCursor( new Cursor( Cursor.CROSSHAIR_CURSOR ));
}
public void beginDragging()
{
isDragging = true;
}
protected void dispatchAction()
{
final ActionEvent e = new ActionEvent( this, ActionEvent.ACTION_PERFORMED, null );
synchronized( collListeners ) {
for( int i = 0; i < collListeners.size(); i++ ) {
((ActionListener) collListeners.get( i )).actionPerformed( e );
}
}
}
public void addActionListener( ActionListener l )
{
synchronized( collListeners ) {
collListeners.add( l );
}
}
public void removeActionListener( ActionListener l )
{
synchronized( collListeners ) {
collListeners.remove( l );
}
}
private void recalcColours()
{
double temp, spreadDegH;
if( spread >= 0.0 ) {
spreadDegH = spread * (1.0 - 2.0 / numSpots) + 2.0 / numSpots;
} else {
spreadDegH = (2.0 + spread) / numSpots;
}
spreadDegH *= 180;
for( int i = 0; i < numSpots; i++ ) {
temp = (startAngle + deltaAngle * i) % 360;
temp = Math.abs( temp - azi );
if( temp > 180 ) temp = 360 - temp;
temp = Math.max( 0.0, 1.0 - temp / spreadDegH );
areaPaints.set( i, new Color( (int) (temp * 0xC0), 0, 0, (int) ((temp + 1.0) * 0x7F) ));
}
}
public void setAzimuthAndSpread( double azi, double spread )
{
this.spread = Math.max( -1.0, Math.min( 1.0, spread ));
this.azi = azi < 0 ? 360 - ((-azi) % 360) : azi % 360;
ctrlPt.setLocation( PanoramaPanel.aziAndSpreadToCtrlPoint( this.azi, this.spread ));
recalcColours();
if( isVisible() ) repaint();
}
public static Point2D aziAndSpreadToCtrlPoint( double azi, double spread )
{
final double r = spread >= 0.0 ? (1.0 - spread) * 0.75 : 0.75 - spread * 0.25;
final double angRad = (-azi + 90) * Math.PI / 180;
return new Point2D.Double( r * Math.cos( angRad ), r * Math.sin( angRad ));
}
public double getAzimuth()
{
return azi;
}
public double getSpread()
{
return spread;
}
public void paintComponent( Graphics g )
{
super.paintComponent( g );
final Graphics2D g2 = (Graphics2D) g;
final int currentWidth = getWidth() - insets.left - insets.right;
final int currentHeight = getHeight() - insets.top - insets.bottom;
final AffineTransform oldAT = g2.getTransform();
final double radius = Math.min( currentWidth, currentHeight ) / 2.3;
double trnsX, trnsY;
if( (radius != recentRadius) || recalc ) {
recentRadius = radius;
at.setToScale( radius, -radius );
at.translate( 1.15, -1.15 );
recalcTransforms();
}
g2.setRenderingHint( RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON );
g2.translate( insets.left, insets.top );
for( int i = 0; i < tOutlines.size(); i++ ) {
g2.setPaint( (Paint) outlinePaints.get( i ));
g2.draw( (Shape) tOutlines.get( i ));
}
for( int i = 0; i < tAreas.size(); i++ ) {
g2.setPaint( (Paint) areaPaints.get( i ));
g2.fill( (Shape) tAreas.get( i ));
}
trnsX = (ctrlPt.getX() + 1.15) * radius;
trnsY = (1.15 - ctrlPt.getY()) * radius;
g2.translate( trnsX, trnsY );
g2.setPaint( pntCtrlIn );
g2.fill( shpCtrlIn );
if( isDragging ) {
g2.setPaint( pntCtrlOutS );
} else {
g2.setPaint( pntCtrlOut );
}
g2.fill( shpCtrlOut );
g2.translate( -trnsX, -trnsY );
g2.setTransform( oldAT );
}
private void recalcTransforms()
{
tOutlines.clear();
for( int i = 0; i < outlines.size(); i++ ) {
tOutlines.add( at.createTransformedShape( (Shape) outlines.get( i )));
}
tAreas.clear();
for( int i = 0; i < areas.size(); i++ ) {
tAreas.add( at.createTransformedShape( (Shape) areas.get( i )));
}
recalc = false;
}
}