/*
* (c) 2005-2009 Carlos G�mez Rodr�guez, todos los derechos reservados / all rights reserved.
* Licencia en license/bsd.txt / License in license/bsd.txt
*
* Created at regulus on 20-jul-2005 10:42:50
* as file StructuralArrow.java on package org.f2o.absurdum.puck.gui.graph
*/
package org.f2o.absurdum.puck.gui.graph;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.geom.Line2D;
import org.f2o.absurdum.puck.gui.config.PuckConfiguration;
import org.f2o.absurdum.puck.gui.panels.ArrowPanel;
import org.f2o.absurdum.puck.gui.panels.CharHasItemPanel;
import org.f2o.absurdum.puck.gui.panels.CharHasSpellPanel;
import org.f2o.absurdum.puck.gui.panels.EntityPanel;
import org.f2o.absurdum.puck.gui.panels.GenericRelationshipPanel;
import org.f2o.absurdum.puck.gui.panels.GraphElementPanel;
import org.f2o.absurdum.puck.gui.panels.ItemHasItemPanel;
import org.f2o.absurdum.puck.gui.panels.PathPanel;
import org.f2o.absurdum.puck.gui.panels.RoomHasCharPanel;
import org.f2o.absurdum.puck.gui.panels.RoomHasItemPanel;
import org.f2o.absurdum.puck.gui.panels.RoomPanel;
import org.f2o.absurdum.puck.gui.panels.SpellHasEffectPanel;
import org.f2o.absurdum.puck.i18n.UIMessages;
/**
* @author carlos
*
* Created at regulus, 20-jul-2005 10:42:50
*/
public class StructuralArrow extends Arrow
{
private Node source;
private Node destination;
private GraphElementPanel associatedPanel;
public GraphElementPanel getAssociatedPanel()
{
if ( associatedPanel == null )
{
if ( source instanceof RoomNode && destination instanceof RoomNode )
associatedPanel = new PathPanel(this);
else if ( source instanceof RoomNode && destination instanceof ItemNode )
associatedPanel = new RoomHasItemPanel(this);
else if ( source instanceof RoomNode && destination instanceof CharacterNode )
associatedPanel = new RoomHasCharPanel(this);
else if ( source instanceof CharacterNode && destination instanceof ItemNode )
associatedPanel = new CharHasItemPanel(this);
else if ( source instanceof CharacterNode && destination instanceof SpellNode )
associatedPanel = new CharHasSpellPanel(this);
else if ( source instanceof ItemNode && destination instanceof ItemNode )
associatedPanel = new ItemHasItemPanel(this);
else if ( source instanceof SpellNode && destination instanceof AbstractEntityNode )
associatedPanel = new SpellHasEffectPanel(this);
else
//associatedPanel = new GraphElementPanel();
associatedPanel = new GenericRelationshipPanel(this,source.getClass(),destination.getClass());
}
return associatedPanel;
}
public Node getSource()
{
return source;
}
public Node getDestination()
{
return destination;
}
public void setSource(Node n)
{
source = n;
}
public void setDestination(Node n)
{
destination = n;
}
private static final int NORTH = 0;
private static final int SOUTH = 1;
private static final int EAST = 2;
private static final int WEST = 3;
//norte, noroeste, etc.
public String getMostLikelyDirection()
{
int[] pCoords = getPaintingCoords();
double angle = calcAngle( (float)pCoords[0],(float)pCoords[1],(float)pCoords[2],(float)pCoords[3] );
while ( angle > 2*Math.PI )
angle -= 2*Math.PI;
while ( angle < 0 )
angle += 2*Math.PI;
if ( angle >= 0.0 && angle <= Math.PI/4 )
return UIMessages.getInstance().getMessage("dir.e");
if ( angle >= Math.PI/4 && angle <= 3*Math.PI/4 )
return UIMessages.getInstance().getMessage("dir.s");
if ( angle >= 3*Math.PI/4 && angle <= 5*Math.PI/4 )
return UIMessages.getInstance().getMessage("dir.w");
if ( angle >= 5*Math.PI/4 && angle <= 7*Math.PI/4 )
return UIMessages.getInstance().getMessage("dir.n");
if ( angle >= 7*Math.PI/4 && angle <= 8*Math.PI/4 )
return UIMessages.getInstance().getMessage("dir.e");
else
return UIMessages.getInstance().getMessage("dir.u");
}
/*devuelve las coordenadas (x1,y1,x2,y2) en que se pinta la flecha.*/
public int[] getPaintingCoords()
{
if ( destination == null )
{
System.out.println(this);
System.out.println("Source: " + this.getSource());
System.out.println("Destination: " + this.getDestination());
System.out.println("Name: " + this.getName());
System.out.println("Panel: " + this.getAssociatedPanel());
System.out.println("Panel class: " + this.getAssociatedPanel().getClass());
}
double srcCenterX = source.getBounds().getCenterX();
double srcCenterY = source.getBounds().getCenterY();
double dstCenterX = destination.getBounds().getCenterX();
double dstCenterY = destination.getBounds().getCenterY();
//dest's center position vector from source's center
double dstRelPosX = dstCenterX - srcCenterX;
double dstRelPosY = dstCenterY - srcCenterY;
//dest's qualitative relative position from source
int dstPosition = NORTH;
//casu�stica...
if ( dstRelPosX >= 0 && dstRelPosY >= 0 )
{
if ( dstRelPosX > dstRelPosY )
dstPosition = EAST;
else
dstPosition = SOUTH;
}
else if ( dstRelPosX >= 0 && dstRelPosY <= 0 )
{
if ( dstRelPosX > -dstRelPosY )
dstPosition = EAST;
else
dstPosition = NORTH;
}
else if ( dstRelPosX <= 0 && dstRelPosY <= 0 )
{
if ( -dstRelPosX > -dstRelPosY )
dstPosition = WEST;
else
dstPosition = NORTH;
}
else if ( dstRelPosX <= 0 && dstRelPosY >= 0 )
{
if ( -dstRelPosX > dstRelPosY )
dstPosition = WEST;
else
dstPosition = SOUTH;
}
double srcX=0.0,srcY=0.0;
double dstX=0.0,dstY=0.0;
if ( dstPosition == NORTH )
{
srcX = source.getBounds().getMinX();
srcY = source.getBounds().getMinY();
dstX = destination.getBounds().getMinX();
dstY = destination.getBounds().getMaxY();
}
if ( dstPosition == EAST )
{
srcX = source.getBounds().getMaxX();
srcY = source.getBounds().getMinY();
dstX = destination.getBounds().getMinX();
dstY = destination.getBounds().getMinY();
}
if ( dstPosition == SOUTH )
{
srcX = source.getBounds().getMaxX();
srcY = source.getBounds().getMaxY();
dstX = destination.getBounds().getMaxX();
dstY = destination.getBounds().getMinY();
}
if ( dstPosition == WEST )
{
srcX = source.getBounds().getMinX();
srcY = source.getBounds().getMaxY();
dstX = destination.getBounds().getMaxX();
dstY = destination.getBounds().getMaxY();
}
return new int[] {(int)srcX,(int)srcY,(int)dstX,(int)dstY};
}
/* (non-Javadoc)
* @see org.f2o.absurdum.puck.gui.graph.Arrow#paint(java.awt.Graphics)
*/
public static double calcAngle(float x1, float y1, float x2, float y2)
{
float dx = x2-x1;
float dy = y2-y1;
double angle=0.0d;
// Calculate angle
if (dx == 0.0)
{
if (dy == 0.0)
angle = 0.0;
else if (dy > 0.0)
angle = Math.PI / 2.0;
else
angle = Math.PI * 3.0 / 2.0;
}
else if (dy == 0.0)
{
if (dx > 0.0)
angle = 0.0;
else
angle = Math.PI;
}
else
{
if (dx < 0.0)
angle = Math.atan(dy/dx) + Math.PI;
else if (dy < 0.0)
angle = Math.atan(dy/dx) + (2*Math.PI);
else
angle = Math.atan(dy/dx);
}
// Convert to degrees -> nah!
//angle = angle * 180 / Math.PI;
// Return
return angle;
}
public void paintLinkToDoorIfAny ( Graphics g , int srcX , int srcY , int dstX , int dstY , double viewZoom , double viewXOffset , double viewYOffset )
{
if ( getAssociatedPanel() instanceof PathPanel )
{
PathPanel pp = (PathPanel) getAssociatedPanel();
ItemNode door = pp.getDoor();
if ( door != null )
{
int centerX = ( srcX + dstX ) / 2;
int centerY = ( srcY + dstY ) / 2;
int doorX = (int) door.getBounds().getCenterX();
int doorY = (int) door.getBounds().getCenterY();
int viewDoorX = (int)(( (int) doorX - viewXOffset ) * viewZoom);
int viewDoorY = (int)(( (int) doorY - viewYOffset ) * viewZoom);
//transform coords
Color oldColor = g.getColor();
g.setColor(Color.LIGHT_GRAY);
g.drawLine(centerX,centerY,viewDoorX,viewDoorY);
g.setColor(oldColor);
}
}
}
public void paintToView ( Graphics g , double viewZoom , double viewXOffset , double viewYOffset )
{
int [] coords = getPaintingCoords();
//transform coords
coords[0] = (int)(( (int) coords[0] - viewXOffset ) * viewZoom);
coords[1] = (int)(( (int) coords[1] - viewYOffset ) * viewZoom);
coords[2] = (int)(( (int) coords[2] - viewXOffset ) * viewZoom);
coords[3] = (int)(( (int) coords[3] - viewYOffset ) * viewZoom);
//paint the arrow
paint(g,(int)coords[0],(int)coords[1],(int)coords[2],(int)coords[3]);
//paint link to door, if any
paintLinkToDoorIfAny(g,(int)coords[0],(int)coords[1],(int)coords[2],(int)coords[3],viewZoom,viewXOffset,viewYOffset);
}
/**
* Draws a single arrow.
* Note that StructuralArrow's paint() method may do more than one call to this method, since, if cut mode is used
* used, the structural arrow is replaced by an arrow from the source to a placeholder and an arrow from the other
* placeholder to the target.
* @param g
* @param srcX
* @param srcY
* @param dstX
* @param dstY
* @param text
*/
private void paintSingleArrow ( Graphics g , int srcX , int srcY , int dstX , int dstY , String text , Color arrowColor , Color textColor )
{
//set arrow color
g.setColor(arrowColor);
if ( PuckConfiguration.getInstance().getProperty("showArrows").equals("true") || isHighlighted() )
{
//draw the arrow line
g.drawLine((int)srcX,(int)srcY,(int)dstX,(int)dstY);
//calculate the arrowheads
Line2D l2d = new Line2D.Double ( dstX , dstY , srcX , srcY );
double angle = calcAngle(dstX,dstY,srcX,srcY);
double ang1 = angle+0.4;
double ang2 = angle-0.4;
double dest1X = dstX + 10*Math.cos(ang1);
double dest1Y = dstY + 10*Math.sin(ang1);
double dest2X = dstX + 10*Math.cos(ang2);
double dest2Y = dstY + 10*Math.sin(ang2);
//draw the arrowheads
g.drawLine((int)dstX,(int)dstY,(int)dest1X,(int)dest1Y);
g.drawLine((int)dstX,(int)dstY,(int)dest2X,(int)dest2Y);
}
//draw arrow text
if ( PuckConfiguration.getInstance().getProperty("showArrowNames").equals("true") || isHighlighted() )
{
//set text color
g.setColor(textColor);
Font oldFont = g.getFont();
Font newFont = oldFont.deriveFont(getNameFontSize());
g.setFont(newFont);
int swidth = g.getFontMetrics().stringWidth(getName());
int textXCoord = (int)(srcX+dstX)/2+5 - swidth/2;
int textYCoord = (int)(srcY+dstY)/2+5;
if ( dstY > srcY ) textYCoord -= 5; //correction so that two corresponding north-south paths' texts do not clash
else textYCoord += 5;
g.drawString(text,textXCoord,textYCoord);
g.setFont(oldFont);
}
}
private double MAX_DISTANCE_UNTIL_CUT_MODE = 300.0;
private double DISTANCE_TO_SOURCE_CUT = 50.0;
private double DISTANCE_TO_TARGET_CUT = 50.0;
public void paint ( Graphics g , int srcX , int srcY , int dstX , int dstY )
{
//store old graphics color
Color oldColor = g.getColor();
//init arrow color
Color arrowColor;
if ( isSelected() )
arrowColor = GraphColorSettings.getInstance().getColorSetting("highArrow");
else
arrowColor = GraphColorSettings.getInstance().getColorSetting("arrow");
//choose modes by calculating length
double arrowLength = Math.sqrt( (srcX-dstX)*(srcX-dstX)+(srcY-dstY)*(srcY-dstY) );
if ( isSelected() || arrowLength <= MAX_DISTANCE_UNTIL_CUT_MODE )
{
//normal (continuous) drawing mode
//draw the arrow
paintSingleArrow(g,srcX,srcY,dstX,dstY,getName(),arrowColor,GraphColorSettings.getInstance().getColorSetting("text"));
}
else
{
//calculate position of placeholders
double piece1RelLen = DISTANCE_TO_SOURCE_CUT / arrowLength;
double piece2RelLen = DISTANCE_TO_TARGET_CUT / arrowLength;
int piece1DstX = (int)((double)srcX + ((double)(dstX-srcX))*piece1RelLen);
int piece1DstY = (int)((double)srcY + ((double)(dstY-srcY))*piece1RelLen);
int piece2SrcX = (int)((double)dstX + ((double)(srcX-dstX))*piece2RelLen);
int piece2SrcY = (int)((double)dstY + ((double)(srcY-dstY))*piece2RelLen);
//draw placeholders
if ( PuckConfiguration.getInstance().getProperty("showArrows").equals("true") || isHighlighted() )
{
g.drawOval(piece1DstX-2,piece1DstY-2,5,5);
g.drawOval(piece2SrcX-2,piece2SrcY-2,5,5);
}
//prepare font to draw placeholder texts
Font oldFont = g.getFont();
//Font newFont = oldFont.deriveFont(Font.ITALIC,(float)10.0);
Font newFont = oldFont.deriveFont(getNameFontSize());
g.setColor(GraphColorSettings.getInstance().getColorSetting("auxText"));
g.setFont(newFont);
//draw text for 1st placeholder
if ( PuckConfiguration.getInstance().getProperty("showArrowNames").equals("true") || isHighlighted() )
{
String placeHolder1Text = "[a " + destination.getName()+"]";
int placeHolder1StringWidth = g.getFontMetrics().stringWidth(placeHolder1Text);
int placeHolder1NameX = piece1DstX-placeHolder1StringWidth/2;
int placeHolder1NameY = piece1DstY;
if ( piece1DstY > srcY + 20 ) placeHolder1NameY += 15;
else if ( piece1DstY < srcY - 20 ) placeHolder1NameY -= 15;
g.drawString(placeHolder1Text,placeHolder1NameX,placeHolder1NameY);
}
//draw text for 2nd placeholder
if ( PuckConfiguration.getInstance().getProperty("showArrowNames").equals("true") || isHighlighted() )
{
String placeHolder2Text = "[de " + source.getName()+"]";
int placeHolder2StringWidth = g.getFontMetrics().stringWidth(placeHolder2Text);
int placeHolder2NameX = piece2SrcX-placeHolder2StringWidth/2;
int placeHolder2NameY = piece2SrcY;
if ( piece2SrcY + 20 < dstY ) placeHolder2NameY -= 15;
else if ( piece2SrcY - 20 > dstY ) placeHolder2NameY += 15;
g.drawString(placeHolder2Text,placeHolder2NameX,placeHolder2NameY);
g.setFont(oldFont);
}
//draw arrows
paintSingleArrow(g,srcX,srcY,piece1DstX,piece1DstY,getName(),arrowColor,oldColor);
paintSingleArrow(g,piece2SrcX,piece2SrcY,dstX,dstY,getName(),arrowColor,oldColor);
}
//reset color to default
g.setColor(oldColor);
}
/* (non-Javadoc)
* @see org.f2o.absurdum.puck.gui.graph.Arrow#paintTo(java.awt.Graphics, int, int)
*/
//UNUSED
public void paintTo(Graphics g, int x, int y)
{
//paint an arrow from the source to point (x,y)
double srcX = source.getBounds().getCenterX();
double srcY = source.getBounds().getCenterY();
paint(g,(int)srcX,(int)srcY,x,y);
}
/* (non-Javadoc)
* @see org.f2o.absurdum.puck.gui.graph.Arrow#clone()
*/
public Object clone()
{
StructuralArrow sa = new StructuralArrow();
sa.setSource(source);
sa.setDestination(destination);
return sa;
}
public String getName()
{
if ( associatedPanel != null /* && associatedPanel instanceof PathPanel*/ )
{
return associatedPanel.getNameForElement();
}
else return "";
}
}