/* * OpConnector.java * (FScape) * * Copyright (c) 2001-2016 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.fscape.gui; import java.awt.*; import java.awt.event.FocusEvent; import java.awt.event.MouseEvent; import java.awt.geom.AffineTransform; import java.awt.geom.Path2D; import java.util.Vector; import javax.swing.JPanel; import de.sciss.fscape.op.Operator; import de.sciss.fscape.spect.SpectStreamSlot; import de.sciss.fscape.util.Constants; import de.sciss.fscape.util.Slots; /** * GUI component representing the wire * connection between two spectral operators. */ public class OpConnector extends JPanel // JComponent implements Dragable { // -------- private static variables -------- // private static final String ibName = "images" + File.separator + "arrows.gif"; // IconBitmap private static final int ibWidth = 19; // Breite der Icons private static final int ibHeight = 19; // Hoehe der Icons // private static IconBitmap arrowib; // -------- public variables -------- public static final String OBJ_NAME = "OpConnector"; public static final int ARROW_WIDTH = ibWidth; public static final int ARROW_HEIGHT = ibHeight; public static final int STATE_NORMAL = 0; public static final int STATE_SELECTED = 3; private static final Path2D shpArrow = new Path2D.Float ( Path2D.WIND_NON_ZERO, 5 ); private static final AffineTransform rotateArrow = new AffineTransform(); // -------- static constructor -------- static // Icon-Bitmap laden { // final Image imgArrows = Toolkit.getDefaultToolkit().getImage( // OpConnector.class.getResource( "arrows.gif" )); // arrowib = new IconBitmap( imgArrows, ibWidth, ibHeight ); shpArrow.moveTo(-4, 0); shpArrow.lineTo(0, -8); shpArrow.lineTo(4, 0); shpArrow.closePath (); } // -------- private variables -------- private static final int STATE_UNKNOWN = -1; private int state = STATE_UNKNOWN; // Status wie STATE_NORMAL, selektiert etc. private SpectStreamSlot origin; private OpIcon srcIcon; private OpIcon destIcon; private Point srcLoc, destLoc; // Icon top/left private int anchor; // -1 = src, +1 = dest; 0 = center private Point srcP, destP; // arrow coord private Point thisP; // Mittelpunkt dieser Componente private String labName = null; private int width; // of this component private int height; private FontMetrics fntMetr; // -------- public methods --------- /** * NOTE: origin MUSS VERLINKT SEIN! */ public OpConnector( SpectStreamSlot origin ) { super(); setOpaque( true ); Operator srcOp, destOp; SpectStreamSlot target; String srcName; String destName; Dimension dim; Vector slots; this.origin = origin; target = origin.getLinked(); srcOp = origin.getOwner(); srcIcon = (OpIcon) srcOp.getIcon(); srcName = origin.toString(); if(srcName.equals(Slots.SLOTS_DEFWRITER)) { srcName = ""; } destOp = target.getOwner(); destIcon = (OpIcon) destOp.getIcon(); destName = target.toString(); if(destName.equals(Slots.SLOTS_DEFREADER)) { destName = ""; } // dimension if( (srcName.length() > 0) || (destName.length() > 0) ) { // Slotnamen zeichnen labName = srcName + '>' + destName; newVisualProps(); } else { width = 8; height = 8; } //System.err.println( "width = "+width+"; height = "+height ); // anchor slots = srcOp.getSlots( Slots.SLOTS_WRITER ); anchor = (slots.size() > 1) ? -1 : 0; slots = destOp.getSlots( Slots.SLOTS_READER ); anchor += (slots.size() > 1) ? 1 : 0; srcLoc = srcIcon.getLocation(); dim = srcIcon.getSize(); srcP = new Point( srcLoc.x + (dim.width >> 1), srcLoc.y + (dim.height >> 1) ); destLoc = destIcon.getLocation(); dim = destIcon.getSize(); destP = new Point( destLoc.x + (dim.width >> 1), destLoc.y + (dim.height >> 1) ); thisP = new Point( (destP.x + srcP.x - width) >> 1, (destP.y + srcP.y - height) >> 1 ); setSize( width, height ); setLocation( thisP.x, thisP.y ); setSelected( STATE_NORMAL ); setVisible( labName != null ); // if( labName == null ) setVisible( false ); newVisualProps(); // new DynamicAncestorAdapter( new DynamicPrefChangeManager( // Application.userPrefs, // new String[] { MainPrefs.KEY_ICONFONT }, new LaterInvocationManager.Listener() { // // public void laterInvocation( Object o ) // { // newVisualProps(); // } // })).addTo( this ); // Event handling enableEvents( AWTEvent.FOCUS_EVENT_MASK ); enableEvents( AWTEvent.MOUSE_EVENT_MASK ); setFocusable( false ); } /** * @return returns OBJ_NAME so you can identify it ('==' Operator) */ public String toString() { return OBJ_NAME; } /** * Liefert Ursprungsslot */ public SpectStreamSlot getOrigin() { return origin; } /** * Status veraendern * * @return vorheriger Status */ public int setSelected( int newState ) { final int lastState = state; state = newState; if( lastState != newState ) { if( newState == CurvePanel.CP_STATE_NORMAL ) { setForeground( SystemColor.control ); setBackground( SystemColor.control ); } else { setForeground( OpIcon.selectColor ); setBackground( OpIcon.selectColor ); } repaint(); } return lastState; } public int isSelected() { return state; } public void setLocation( int x, int y ) { super.setLocation( x, y ); if( isVisible() ) { calcArrow( srcIcon, this, srcP, thisP, 2, 0 ); calcArrow( this, destIcon, thisP, destP, 0, 2 ); } else { calcArrow( srcIcon, this, srcP, thisP, 2, 0 ); calcArrow( srcIcon, destIcon, srcP, destP, 2, 2 ); } } // public void setVisible( boolean visible ) // { // Point loc = getLocation(); // // super.setVisible( visible ); // setLocation( loc.x, loc.y ); // repaint(); // } public void adjustLocation() { final Point newThis = getLocation(); final Point newSrc = srcIcon.getLocation(); final Point newDest = destIcon.getLocation(); switch( anchor ) { case -1: newThis.translate( newSrc.x - srcLoc.x, newSrc.y - srcLoc.y ); break; case 1: newThis.translate( newDest.x - destLoc.x, newDest.y - destLoc.y ); break; default: newThis.translate( (newSrc.x - srcLoc.x + newDest.x - destLoc.x) >> 1, (newSrc.y - srcLoc.y + newDest.y - destLoc.y) >> 1 ); break; } if( newThis.x < 0 ) newThis.x = 0; if( newThis.y < 0 ) newThis.y = 0; srcLoc = newSrc; destLoc = newDest; setLocation( newThis.x, newThis.y ); } public void paintComponent(Graphics g) { super.paintComponent(g); // drawArrowCorrect(g2); // XXX TODO final int w = getWidth(); final int h = getHeight(); g.clearRect(0, 0, w, h); g.draw3DRect(1, 1, width - 3, height - 3, true); g.setColor(Color.black); // g.drawRect(0, 0, width - 1, height - 1); if (labName != null) { //System.err.println( "drawing string "+labName ); g.drawString(labName, 2, fntMetr.getAscent()); } } public void drawArrowCorrect(Graphics2D g) { // g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); // g.setColor(Color.black); // g.setColor ( getParent ().getForeground () ); if (isVisible()) { g.drawLine(srcP.x, srcP.y, thisP.x, thisP.y); drawArrow(g, thisP.x, thisP.y, destP.x, destP.y, true); } else { drawArrow(g, srcP.x, srcP.y, destP.x, destP.y, true); } } /** * Zeichnet den Pfeil zwischen den zum Connector * gehoerenden Icons; nimmt dazu getParent().getGraphics(); * * @param mode false, um statt mit schwarz zu zeichnen, den Pfeil zu loeschen */ public void drawArrow(boolean mode) { final Container c = getParent(); Graphics2D g; Point tempP; if (c != null) { g = (Graphics2D) c.getGraphics(); if (g != null) { g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); g.setColor(mode ? Color.black : c.getBackground()); if (isVisible()) { g.drawLine(srcP.x, srcP.y, thisP.x, thisP.y); drawArrow(g, thisP.x, thisP.y, destP.x, destP.y, mode); } else { drawArrow(g, srcP.x, srcP.y, destP.x, destP.y, mode); } g.dispose(); } // loc = srcIcon.getLocation(); g = (Graphics2D) srcIcon.getGraphics(); if (g != null) { g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); tempP = isVisible() ? thisP : destP; g.setColor(mode ? Color.black : c.getBackground()); g.drawLine(srcP.x - srcLoc.x, srcP.y - srcLoc.y, tempP.x - srcLoc.x, tempP.y - srcLoc.y); g.dispose(); } // loc = destIcon.getLocation(); g = (Graphics2D) destIcon.getGraphics(); if (g != null) { g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); tempP = isVisible() ? thisP : srcP; g.setColor(mode ? Color.black : c.getBackground()); drawArrow(g, tempP.x - destLoc.x, tempP.y - destLoc.y, destP.x - destLoc.x, destP.y - destLoc.y, mode); g.dispose(); if (!mode) destIcon.repaint(); // since we may have cleared a part of the icon } } } /** * Zeichnet einen Pfeil mit den exakt angegebenen Koordinaten * * @param mode false, um zu loeschen (akt. Farbe muss Hintergrundfarbe sein!) */ public static void drawArrow( Graphics2D g, int srcX, int srcY, int destX, int destY, boolean mode ) { g.drawLine( srcX, srcY, destX, destY ); if( mode ) { final double angle = Math.atan2( destX - srcX, -destY + srcY ); // int ID = (((int) Math.rint( angle / Math.PI * 24.0 ))) + 48) % 48; // // arrowib.paint( g, ID, destX - (ARROW_WIDTH >> 1), destY - (ARROW_HEIGHT >> 1) ); rotateArrow.setToRotation(angle); // rotateArrow.translate ( destX, destY ); final Shape s = rotateArrow.createTransformedShape ( shpArrow ); g.translate(destX, destY); g.fill(s); g.translate(-destX, -destY); } else { g.fillRect( destX - (ARROW_WIDTH >> 1), destY - (ARROW_HEIGHT >> 1), ARROW_WIDTH, ARROW_HEIGHT ); } } /** * Kalkuliert einen Link Pfeil und speichert seinen Anfangs- und Endpunkt * in den uebergebenen Point-Objekten * * @param src Componente, von dem der Pfeil ausgeht * @param dest Zielcomponente * @param srcLoc dort wird der Anfangspunkt des Pfeils gespeichert * @param destLoc dort wird der Anfangspunkt des Pfeils gespeichert * @param srcDist positive Werte verschieben den Punkt ausserhalb der * Quellcomponente, so dass noch "srcDist" Pixel Abstand * dazwischen frei ist * @param destDist dito fuer Zielpunkt */ public static void calcArrow( Component src, Component dest, Point srcLoc, Point destLoc, int srcDist, int destDist ) { final Rectangle srcB = src.getBounds(); final Rectangle destB = dest.getBounds(); final double arc; final double cos, sin; final Rectangle labB; double dist; String name; srcLoc.setLocation( srcB.x + (srcB.width >> 1), // Mittelpunkt srcB.y + (srcB.height >> 1) ); destLoc.setLocation( destB.x + (destB.width >> 1), destB.y + (destB.height >> 1) ); arc = Math.atan2( destLoc.x - srcLoc.x, -destLoc.y + srcLoc.y ) - Constants.PIH; cos = Math.cos( arc ); sin = Math.sin( arc ); if( srcDist > 0 ) { name = src.toString(); if(name.equals(OpIcon.OBJ_NAME)) { // "runde" Form srcLoc.translate( (int) (((srcB.width >> 1) + srcDist) * cos), (int) (((srcB.height >> 1) + srcDist) * sin) ); } else { // "eckig" if( cos == 0 ) { dist = srcLoc.y - srcB.y; } else if( sin == 0 ) { dist = srcLoc.x - srcB.x; } else { dist = Math.min( ((cos<0) ? (srcB.x-srcLoc.x) : (srcB.x+srcB.width-srcLoc.x)) / cos, ((sin>0) ? (srcLoc.y-srcB.y) : (srcLoc.y-srcB.y-srcB.height)) / sin ); } srcLoc.translate( (int) ((dist + srcDist) * cos), (int) ((dist + srcDist) * sin) ); } } dest: if( destDist > 0 ) { name = dest.toString(); if(name.equals(OpIcon.OBJ_NAME)) { // "runde" Form destLoc.translate( (int) -(((destB.width >> 1) + destDist + (ARROW_WIDTH>>1)) * cos), (int) -(((destB.height >> 1) + destDist + (ARROW_HEIGHT>>1)) * sin) ); if( (arc >= 0.0) || (arc <= -Math.PI )) break dest; // JLabel ueberpruefen labB = ((OpIcon) dest).getLabel().getBounds(); dist = labB.y + (labB.height >> 1) - destLoc.y - (ARROW_HEIGHT>>2)*sin; if( labB.contains( destLoc.x + (int) (dist / Math.tan( arc )), destLoc.y + (int) dist )) { dist += (labB.height >> 1) + (ARROW_HEIGHT >> 1); destLoc.translate( (int) (dist / Math.tan( arc )), (int) dist ); } } else { // "eckig" if( cos == 0 ) { dist = destLoc.y - destB.y; } else if( sin == 0 ) { dist = destLoc.x - destB.x; } else { dist = Math.min( ((cos<0) ? (destB.x-destLoc.x) : (destB.x+destB.width-destLoc.x)) / cos, ((sin>0) ? (destLoc.y-destB.y) : (destLoc.y-destB.y-destB.height)) / sin ); } destLoc.translate( (int) -((dist + destDist) * cos), (int) -((dist + destDist) * sin) ); } } } /** * Ermittelt die Distanz eines Punktes zu einem Link * * @return Distanz in Pixels. Negativer Wert = angegebener Punkt liegt zu weit weg */ public static int getDistance( OpConnector con, int x, int y ) { final Rectangle rect = new Rectangle(); final Point startP[]; final Point endP[]; int x1, y1, x2, y2; double projectX, projectY; int dist = -1; double offset; double arc; if( con.isVisible() ) { startP = new Point[ 2 ]; endP = new Point[ 2 ]; startP[ 0 ] = con.srcP; startP[ 1 ] = con.thisP; endP[ 0 ] = con.thisP; endP[ 1 ] = con.destP; } else { // direkt src/dest verknuepfen startP = new Point[ 1 ]; endP = new Point[ 1 ]; startP[ 0 ] = con.srcP; endP[ 0 ] = con.destP; } for( int i = 0; i < startP.length; i++ ) { x1 = Math.min( startP[ i ].x, endP[ i ].x ); y1 = Math.min( startP[ i ].y, endP[ i ].y ); x2 = Math.max( startP[ i ].x, endP[ i ].x ); y2 = Math.max( startP[ i ].y, endP[ i ].y ); // mind. 8 x 8 wg. z.B. 90 Grad Winkel! rect.setBounds( x1 - 4, y1 - 4, (x2 - x1) + 8, (y2 - y1) + 8 ); if( rect.contains( x, y )) { // erste Abschaetzung offset = Math.sqrt( (x - startP[ i ].x) * (x - startP[ i ].x) + (y - startP[ i ].y) * (y - startP[ i ].y) ); arc = Math.atan2( endP[ i ].x - startP[ i ].x, -endP[ i ].y + startP[ i ].y ) - Math.PI/2; projectX = startP[ i ].x + offset * Math.cos( arc ); projectY = startP[ i ].y + offset * Math.sin( arc ); offset = Math.sqrt( (projectX - x) * (projectX - x) + (projectY - y) * (projectY - y) ); if( (dist == -1) || (offset < dist) ) { dist = (int) offset; } } } return dist; } // -------- Dragable methods -------- /** * Zeichnet ein Schema */ public void paintScheme( Graphics g, int x, int y, boolean mode ) { g.drawRect( x - (width>>1), y - (height>>1), width - 1, height - 1 ); } // -------- private methods -------- private void newVisualProps() { if( labName != null ) { Font fnt = getFont(); fntMetr = getFontMetrics( fnt ); width = fntMetr.stringWidth( labName ) + 4; height = fntMetr.getHeight(); if( isVisible() ) repaint(); } } protected void processMouseEvent( MouseEvent e ) { if( e.getID() == MouseEvent.MOUSE_PRESSED ) { requestFocus(); } super.processMouseEvent( e ); } protected void processFocusEvent( FocusEvent e ) { if( e.getID() == FocusEvent.FOCUS_GAINED ) { setSelected( STATE_SELECTED ); } else if( e.getID() == FocusEvent.FOCUS_LOST ) { setSelected( STATE_NORMAL ); } super.processFocusEvent( e ); } }