package jas.plot;
import jas.util.ColorMenu;
import java.awt.Color;
import java.awt.Component;
import java.awt.Cursor;
import java.awt.Graphics;
import java.awt.Insets;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import javax.swing.BorderFactory;
import javax.swing.JMenu;
import javax.swing.JMenuItem;
import javax.swing.JPopupMenu;
import javax.swing.JRadioButtonMenuItem;
import javax.swing.border.AbstractBorder;
import javax.swing.border.Border;
import javax.swing.border.CompoundBorder;
import javax.swing.border.LineBorder;
import javax.swing.colorchooser.ColorSelectionModel;
import javax.swing.colorchooser.DefaultColorSelectionModel;
/**
* Base class for any object that can be moved around using handles
*/
public abstract class MovableObject extends PlotComponent implements HasPopupItems, JASPlotMouseListener
{
public MovableObject(String prefix)
{
this.prefix = prefix;
super.setBorder(border);
}
protected String getPrefix()
{
return prefix;
}
public void setMovableObjectBounds(int p1, int p2, int p3, int p4)
{
super.setBounds(p1, p2, p3, p4);
hasBeenMoved = true;
hasBeenResized = true;
}
public void setMovableObjectBounds(Rectangle r)
{
super.setBounds(r);
hasBeenMoved = true;
hasBeenResized = true;
}
public void setBorder(Border newBorder)
{
Border oldBorder = super.getBorder();
if (oldBorder instanceof CompoundBorder) oldBorder = ((CompoundBorder) oldBorder).getOutsideBorder();
super.setBorder(newBorder == null ? oldBorder : new CompoundBorder(oldBorder,newBorder));
}
public int getBorderType()
{
return getBorderType(getInsideBorder());
}
public Border getInsideBorder()
{
Border oldBorder = super.getBorder();
if (oldBorder instanceof CompoundBorder) return ((CompoundBorder) oldBorder).getInsideBorder();
if (oldBorder instanceof HandleBorder) return null;
return oldBorder;
}
public void modifyPopupMenu(JPopupMenu menu, Component source)
{
menu.add(new BorderMenu(prefix));
ColorSelectionModel cm = new DefaultColorSelectionModel()
{
public Color getSelectedColor()
{
if (!isPaintingBackground()) return null;
return getBackground();
}
public void setSelectedColor(Color c)
{
setBackground(c);
}
};
menu.add(new BackgroundColorMenu(prefix,cm));
}
final void showHandles()
{
handlesVisible = true;
paintBorder(getGraphics());
saveCursor = getCursor();
currentCursor = Cursor.DEFAULT_CURSOR;
}
final void hideHandles()
{
handlesVisible = false;
setCursor(saveCursor);
Component p = getParent(); // Parent can be null when window is closing
if (p != null) p.repaint(); // Since we may overlap our siblings
}
public final void mouseEventNotify(final MouseEvent me)
{
if (handlesVisible)
{
if (me.getID() == MouseEvent.MOUSE_ENTERED)
{
saveCursor = getCursor();
}
else if (me.getID() == MouseEvent.MOUSE_EXITED)
{
setCursor(saveCursor);
}
else if (me.getID() == MouseEvent.MOUSE_PRESSED)
{
// What happens when the mouse is pressed depends on where it is
// It either starts some sort of resize or a move
if (currentCursor == Cursor.MOVE_CURSOR)
{
dragOffset = me.getPoint();
}
else
{
}
}
else if (me.getID() == MouseEvent.MOUSE_RELEASED)
{
dragOffset = null;
}
}
}
final void mouseMotionEventNotify(final MouseEvent me)
{
if (!handlesVisible) return;
if (me.getID() == MouseEvent.MOUSE_DRAGGED)
{
if (currentCursor == Cursor.MOVE_CURSOR)
{
Point p = me.getPoint();
Point x = getLocation();
p.translate(x.x,x.y);
if (dragOffset != null) p.translate(-dragOffset.x,-dragOffset.y);
setLocation(p);
hasBeenMoved = true;
}
else
{
final Rectangle b = getBounds();
final Point p = me.getPoint();
if (currentCursor == Cursor.E_RESIZE_CURSOR)
{
b.width = p.x;
}
else if (currentCursor == Cursor.W_RESIZE_CURSOR)
{
b.width -= p.x;
b.x += p.x;
}
else if (currentCursor == Cursor.N_RESIZE_CURSOR)
{
b.height -= p.y;
b.y += p.y;
}
else if (currentCursor == Cursor.S_RESIZE_CURSOR)
{
b.height = p.y;
}
else if (currentCursor == Cursor.NE_RESIZE_CURSOR)
{
// code from N:
b.height -= p.y;
b.y += p.y;
// code from E:
b.width = p.x;
}
else if (currentCursor == Cursor.NW_RESIZE_CURSOR)
{
// code from N:
b.height -= p.y;
b.y += p.y;
// code from W:
b.width -= p.x;
b.x += p.x;
}
else if (currentCursor == Cursor.SE_RESIZE_CURSOR)
{
// code from S:
b.height = p.y;
// code from E:
b.width = p.x;
}
else if (currentCursor == Cursor.SW_RESIZE_CURSOR)
{
// code from S:
b.height = p.y;
// code from W:
b.width -= p.x;
b.x += p.x;
}
else return;
hasBeenResized = true;
setBounds(b);
validate();
}
}
else if (me.getID() == MouseEvent.MOUSE_MOVED)
{
currentCursor = border.getCursor(getBounds(),me.getPoint());
((Component) me.getSource()).setCursor(Cursor.getPredefinedCursor(currentCursor));
}
}
public boolean hasBeenResized() {
return hasBeenResized;
}
public boolean hasBeenMoved() {
return hasBeenMoved;
}
public void restoreDefaultLayout() {
hasBeenResized = false;
hasBeenMoved = false;
}
public boolean hasDefaultLayout() {
return ! hasBeenMoved() && ! hasBeenResized();
}
// Are these methods really needed?
public void resizeMovableObject(int w, int h) {
hasBeenResized = true;
this.setSize(w,h);
}
public void moveMovableObject(int x, int y) {
hasBeenMoved = true;
this.setLocation(x,y);
}
/**
* Clicking on a movable object will by default cause its handles to appear.
*/
private String prefix; // Prefix for popup menu items
private boolean hasBeenResized = false;
private boolean hasBeenMoved = false;
private int currentCursor;
private Point dragOffset;
private Cursor saveCursor;
private boolean handlesVisible;
private static final HandleBorder border = new HandleBorder(Color.blue);
final private static class HandleBorder extends AbstractBorder
{
private Color lineColor;
/**
* Creates a handle border with the specified color and a
* @param color the color for the border
*/
HandleBorder(Color color)
{
lineColor = color;
}
/**
* Paints the border for the specified component with the
* specified position and size.
* @param c the component for which this border is being painted
* @param g the paint graphics
* @param x the x position of the painted border
* @param y the y position of the painted border
* @param width the width of the painted border
* @param height the height of the painted border
*/
public void paintBorder(Component c, Graphics g, int x, int y, int width, int height)
{
if (((MovableObject) c).handlesVisible && !PrintHelper.isPrinting())
{
Color oldColor = g.getColor();
int xx = x+thick2;
int yy = y+thick2;
int ww = width-thickness;
int hh = height-thickness;
Rectangle[] h = getHandles(xx,yy,ww,hh);
g.setColor(lineColor);
g.drawRect(xx, yy, ww,hh);
for (int i=0; i<h.length; i++) g.fillRect(h[i].x,h[i].y,h[i].width,h[i].height);
g.setColor(oldColor);
}
}
private Handle[] getHandles(int xx, int yy, int ww, int hh)
{
handles[0].set(Cursor.NW_RESIZE_CURSOR, xx, yy);
handles[1].set(Cursor.NE_RESIZE_CURSOR, xx+ww,yy);
handles[2].set(Cursor.SE_RESIZE_CURSOR, xx+ww,yy+hh);
handles[3].set(Cursor.SW_RESIZE_CURSOR, xx,yy+hh);
handles[4].set(Cursor.N_RESIZE_CURSOR, xx+ww/2, yy);
handles[5].set(Cursor.S_RESIZE_CURSOR, xx+ww/2, yy+hh);
handles[6].set(Cursor.W_RESIZE_CURSOR, xx, yy+hh/2);
handles[7].set(Cursor.E_RESIZE_CURSOR, xx+ww, yy+hh/2);
return handles;
}
int getCursor(final Rectangle b, final Point p)
{
int xx = thick2;
int yy = thick2;
int ww = b.width-thickness;
int hh = b.height-thickness;
Handle[] h = getHandles(xx,yy,ww,hh);
for (int i=0; i<h.length; i++) if (h[i].contains(p)) return h[i].getCursor();
final int extra = thick2 + 1;
if (p.x <= thick2 + extra && p.x >= thick2 - extra ||
p.y <= thick2 + extra && p.y >= thick2 - extra ||
p.x >= b.width - thick2 - extra && p.x <= b.width - thick2 + extra ||
p.y >= b.height - thick2 - extra && p.y <= b.height - thick2 + extra)
{
return Cursor.MOVE_CURSOR;
}
return Cursor.DEFAULT_CURSOR;
}
/**
* Returns the insets of the border.
* @param c the component for which this border insets value applies
*/
public Insets getBorderInsets(Component c)
{
return insets;
}
/**
* Returns the color of the border.
*/
public Color getLineColor()
{
return lineColor;
}
/**
* Returns whether or not the border is opaque.
*/
public boolean isBorderOpaque() { return false; }
private final static int thickness = 5;
private final static Insets insets = new Insets(thickness, thickness, thickness, thickness);
private final static int thick2 = thickness/2;
private static Handle[] handles;
static
{
handles = new Handle[8];
for (int i=0; i<handles.length; i++) handles[i] = new Handle(thickness);
}
final private static class Handle extends Rectangle
{
Handle(int size)
{
this.setSize(size-1,size-1);
thick2 = size/2;
}
void set(int type, int x, int y)
{
this.setLocation(x - thick2, y - thick2);
this.type = type;
}
int getCursor()
{
return type;
}
private int thick2;
private int type;
}
}
public void print(Graphics g)
{
// Note, print is called when printing, but so is paint called directly,
// so there is no point in also printing here??
// Not true under JDK 1.3, better work around this
try
{
if (System.getProperty("java.version").compareTo("1.3") >= 0) super.print(g);
}
catch (SecurityException x) {}
}
/**
* An array containing an expanded selection of colors.
*/
public final static Color[] bgcolors =
{
null, Color.pink,
Color.orange, Color.yellow,
Color.green, Color.cyan,
new Color(164, 207, 255), new Color(225, 170, 255),
new Color(255, 170, 210)
};
/**
* An array containing names of the expanded selection of colors.
*
* @see ColorMenu#EXTENDED_COLORS
*/
public final static String[] bgnames =
{
"default", "pink",
"light orange", "yellow",
"light green", "cyan",
"sky blue", "violet",
"light magenta"
};
private final class BackgroundColorMenu extends ColorMenu
{
BackgroundColorMenu(String prefix, ColorSelectionModel cm)
{
super(prefix+" Background",cm,bgcolors,bgnames);
}
}
private final class BorderMenu extends JMenu
{
BorderMenu(String prefix)
{
super(prefix+" Border");
addItem("None", NONE);
addItem("Bevel In", BEVEL_IN);
addItem("Bevel Out", BEVEL_OUT);
addItem("Ethched", ETCHED);
addItem("Shadow", SHADOW);
ColorSelectionModel cm = new DefaultColorSelectionModel()
{
public Color getSelectedColor()
{
Border oldBorder = MovableObject.this.getBorder();
if (oldBorder instanceof CompoundBorder)
{
oldBorder = ((CompoundBorder) oldBorder).getInsideBorder();
}
if (oldBorder instanceof LineBorder)
{
return ((LineBorder) oldBorder).getLineColor();
}
return null;
}
public void setSelectedColor(Color c)
{
MovableObject.this.setBorder(BorderFactory.createLineBorder(c));
}
};
this.add(new ColorMenu("Line",cm,false));
}
private void addItem(String name, final int btype)
{
JMenuItem i = new JRadioButtonMenuItem(name);
add(i);
i.addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent e)
{
setBorderType(btype);
}
});
i.setSelected(getBorderType() == btype);
}
}
}