/*
* File : BoxConector.java
* Created : 14-dec-2000 10:40
* By : fbusquets
*
* JClic - Authoring and playing system for educational activities
*
* Copyright (C) 2000 - 2005 Francesc Busquets & Departament
* d'Educacio de la Generalitat de Catalunya
*
* This program 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 of the License, or
* (at your option) any later version.
*
* This program 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 (see the LICENSE file).
*/
package edu.xtec.jclic.boxes;
import edu.xtec.util.Options;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Cursor;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.Stroke;
import java.awt.geom.Point2D;
import java.awt.image.ImageObserver;
import javax.swing.JComponent;
/**
* <CODE>BoxConnector</CODE> allows users to visually connect two {@link edu.xtec.jclic.boxes.ActiveBox}
* objects in a {@link edu.xtec.jclic.Activity.Panel}. There are two modes of operation:
* drawing a line between an origin point (usually the point where the user clicks on)
* and a destination point, or dragging the box from one location to another. The lines can
* have arrows at its ending.
* @author Francesc Busquets (fbusquets@xtec.cat)
* @version 1.0
*/
public class BoxConnector extends Object {
public static final float LINE_WIDTH=1.5F;
public static final BasicStroke BASIC_STROKE=new BasicStroke(LINE_WIDTH);
public static final Color DEFAULT_LINE_COLOR=Color.black;
public static final Color DEFAULT_XOR_COLOR=Color.white;
public static final double ARROW_ANGLE=Math.PI/6;
public static final double ARROW_L=10.0;
public static boolean USE_XOR=true;
public static boolean GROW_BUG=false;
public Point2D origin;
public Point2D dest;
public boolean arrow;
public boolean active=false;
public boolean linePainted=false;
public double arrow_l=ARROW_L;
public double arrow_angle=ARROW_ANGLE;
public Color lineColor=DEFAULT_LINE_COLOR;
public Color xorColor=DEFAULT_XOR_COLOR;
Point2D relativePos;
ActiveBox bx;
JComponent parent;
public float line_width=LINE_WIDTH;
/** Creates new BoxConnector */
public BoxConnector(JComponent setParent) {
parent=setParent;
origin=new Point2D.Double();
dest=new Point2D.Double();
arrow=false;
active=false;
linePainted=false;
relativePos=new Point2D.Double();
}
public boolean update(Graphics2D g2, Rectangle dirtyRegion, ImageObserver io){
if(!active) return false;
if(bx!=null){
bx.setTemporaryHidden(false);
bx.update(g2, dirtyRegion, io);
bx.setTemporaryHidden(true);
}
else{
drawLine(g2);
linePainted=true;
}
return true;
}
public void drawLine(Graphics2D g2){
if(active)
drawLine(g2, origin, dest, arrow, lineColor, xorColor,
arrow_l, arrow_angle, line_width);
}
public void moveBy(double dx, double dy){
moveTo(new Point2D.Double(dest.getX()+dx, dest.getY()+dy));
}
public void moveTo(Point2D p){
moveTo(p, false);
}
public void moveTo(Point2D p, boolean forcePaint){
Rectangle clipRect;
if(!active || (forcePaint==false && dest.equals(p)))
return;
if(bx!=null){
clipRect=new Rectangle(
(int)(p.getX()-relativePos.getX()),
(int)(p.getY()-relativePos.getY()),
(int)bx.width,
(int)bx.height);
clipRect.add(bx);
bx.setLocation(new Point2D.Double(p.getX()-relativePos.getX(),
p.getY()-relativePos.getY()));
}
else{
if(forcePaint || !USE_XOR){
clipRect=new Rectangle((int)origin.getX(), (int)origin.getY(), 0, 0);
clipRect.add(p);
clipRect.add(dest);
dest.setLocation(p);
}
else{
Graphics2D g2=(Graphics2D)parent.getGraphics();
if(linePainted){
drawLine(g2);
}
dest.setLocation(p);
drawLine(g2);
linePainted=true;
return;
}
}
growRect(clipRect, arrow ? (int)arrow_l : 1, arrow ? (int)arrow_l : 1);
parent.repaint(clipRect);
}
public void begin(Point2D p){
if(active)
end();
origin.setLocation(p);
dest.setLocation(p);
linePainted=false;
active=true;
parent.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
}
public void begin(Point2D p, ActiveBox setBox){
begin(p);
bx=setBox;
relativePos.setLocation(p.getX()-bx.x, p.getY()-bx.y);
bx.setTemporaryHidden(true);
Rectangle r = new Rectangle(bx.getBounds());
growRect(r, 1, 1);
linePainted=false;
parent.repaint(r);
}
public ActiveBox getBox(){
return bx;
}
public void end(){
if(!active) return;
if(bx!=null){
Rectangle r = new Rectangle(bx.getBounds());
growRect(r, 1, 1);
parent.repaint(r);
bx.setLocation(origin.getX()-relativePos.getX(),
origin.getY()-relativePos.getY());
bx.setTemporaryHidden(false);
r.setBounds(bx.getBounds());
growRect(r, 1, 1);
parent.repaint(r);
bx=null;
relativePos.setLocation(0, 0);
}
else {
moveTo(dest, true);
}
active=false;
linePainted=false;
parent.setCursor(null);
}
public static void drawLine(Graphics2D g2, Point2D origin, Point2D dest, boolean arrow){
drawLine(g2, origin, dest, arrow, DEFAULT_LINE_COLOR, DEFAULT_XOR_COLOR,
ARROW_L, ARROW_ANGLE, LINE_WIDTH);
}
public static void drawLine(Graphics2D g2, Point2D origin, Point2D dest,
boolean arrow, Color color, Color xorColor,
double arrow_l, double arrowAngle, float strokeWidth){
Stroke oldStroke=g2.getStroke();
Object oldStrokeHint=g2.getRenderingHint(RenderingHints.KEY_STROKE_CONTROL);
g2.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_DEFAULT);
g2.setColor(color);
if(USE_XOR && xorColor!=null)
g2.setXORMode(xorColor);
g2.setStroke(strokeWidth==LINE_WIDTH ? BASIC_STROKE : new BasicStroke(strokeWidth));
g2.drawLine((int)origin.getX(), (int)origin.getY(), (int)dest.getX(), (int)dest.getY());
if(arrow){
double beta= Math.atan2(origin.getY()-dest.getY(), dest.getX()-origin.getX());
Point2D arp=new Point2D.Double(dest.getX()-arrow_l*Math.cos(beta+arrowAngle),
dest.getY()+arrow_l*Math.sin(beta+arrowAngle));
g2.drawLine((int)dest.getX(), (int)dest.getY(), (int)arp.getX(), (int)arp.getY());
arp.setLocation(dest.getX()-arrow_l*Math.cos(beta-arrowAngle),
dest.getY()+arrow_l*Math.sin(beta-arrowAngle));
g2.drawLine((int)dest.getX(), (int)dest.getY(), (int)arp.getX(), (int)arp.getY());
}
if(USE_XOR && xorColor!=null)
g2.setPaintMode();
g2.setStroke(oldStroke);
g2.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, oldStrokeHint);
}
public static void checkOptions(Options options){
if(options.getBoolean(Options.JAVA141) && options.getBoolean(Options.MAC)){
USE_XOR=false;
}
}
public static void growRect(Rectangle r, int h, int w){
if(GROW_BUG){
r.x-=w;
r.width+=2*w;
r.y-=h;
r.height+=2*h;
}
else{
r.grow(h, w);
}
}
public static Color getXORColor(Color src){
return getXORColor(src, Color.white);
}
public static Color getXORColor(Color src, Color against){
return new Color(src.getRGB()^against.getRGB());
}
}