package bd.amazed.docscissors.view;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Stroke;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import bd.amazed.docscissors.model.RectChangeListener;
public class Rect implements Cloneable {
protected Rectangle bounds;
protected boolean isSelected;
public static final int CORNERBOX_SIZE = 6;
public static final int CORNER_NONE = -1;
public static final int CORNER_TOP_LEFT = 0;
public static final int CORNER_BOTTOM_LEFT = 1;
public static final int CORNER_BUTTOM_RIGHT = 2;
public static final int CORNER_TOP_RIGHT = 3;
protected static final Color COLOR_SELECTED_RECT = new Color(0x55000077, true);
protected static final Color COLOR_RECT = new Color(0x55555555, true);
public static Stroke STROKE_DASHED = new BasicStroke(1, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL, 0, new float[] { 9 }, 0);
public static Stroke STROKE_SOLID = new BasicStroke();
private static Font font;
protected List<RectChangeListener> listeners = Collections.<RectChangeListener>synchronizedList(new ArrayList<RectChangeListener>());
private UIHandler uiHandler;
/**
* Initial width, height is zero.
*
* @param start starting point
*/
public Rect(Point start, UIHandler uiHandler) {
bounds = new Rectangle(start);
this.uiHandler = uiHandler;
}
public void addListener(RectChangeListener listener) {
if (!listeners.contains(listener)) {
listeners.add(listener);
}
}
protected void setBounds(Rectangle newBounds) {
Rectangle oldBounds = bounds;
bounds = newBounds;
updateCanvas(oldBounds.union(bounds));
}
public void resize(Point anchor, Point end, int containerWidth, int containerHeight) {
Rectangle newRect = new Rectangle(anchor);
newRect.add(end); // creates smallest rectange which includes both anchor & end
if (isRectInsideContainer(newRect, containerWidth, containerHeight)) {
setBounds(newRect);
}
}
public void translate(int dx, int dy, int containerWidth, int containerHeight) {
Rectangle newRect = new Rectangle(bounds);
newRect.translate(dx, dy);
if (isRectInsideContainer(newRect, containerWidth, containerHeight)) {
setBounds(newRect);
}
}
private boolean isRectInsideContainer(Rectangle newRect, int containerWidth, int containerHeight) {
return newRect.x >= 0 && newRect.x + newRect.width <= containerWidth && newRect.y >= 0 && newRect.y + newRect.height <= containerHeight;
}
public void setSelected(boolean newState) {
isSelected = newState;
updateCanvas(bounds, true); // need to erase/add cornerboxs including extent of extended bounds
}
protected void updateCanvas(Rectangle areaOfChange, boolean enlargeForCornerboxes) {
Rectangle toRedraw = new Rectangle(areaOfChange);
if (enlargeForCornerboxes)
toRedraw.grow(CORNERBOX_SIZE / 2, CORNERBOX_SIZE / 2);
fireEvent(toRedraw);
}
/**
*
* @param repaintArea can be null to indicate repaint whole area
*/
void fireEvent(Rectangle repaintArea) {
for (RectChangeListener listener : listeners) {
listener.rectUpdated(this, repaintArea);
}
}
protected void updateCanvas(Rectangle areaOfChange) {
updateCanvas(areaOfChange, isSelected);
}
public void draw(Graphics g, Rectangle clipRect) {
draw(g, 1, STROKE_DASHED, true);
}
public void draw(Graphics g, float scale, Stroke rectBorderStroke, boolean drawIndex) {
if (isSelected) {
g.setColor(COLOR_SELECTED_RECT);
} else {
g.setColor(COLOR_RECT);
}
g.fillRect( (int) (bounds.x * scale), (int)( bounds.y * scale), (int)( bounds.width * scale), (int)( bounds.height * scale));
// draw order number
if (drawIndex) {
if (font == null) {
font = new Font(g.getFont().getFamily(), g.getFont().getStyle(), g.getFont().getSize() * 2);
}
if (isSelected) {
g.setColor(Color.black);
} else {
g.setColor(Color.gray);
}
int boxSize = (int) (font.getSize() * 1.5 * scale);
g.fillRect((int) (bounds.x * scale), (int) (bounds.y * scale), boxSize, boxSize);
if (isSelected) {
g.setColor(Color.RED);
} else {
g.setColor(Color.GREEN);
}
g.setFont(font);
g.drawString(String.valueOf(uiHandler.getIndexOf(this) + 1), bounds.x + 5, bounds.y + g.getFont().getSize() + 2);
}
// draw dashed border
g.setColor(Color.BLACK);
Graphics2D g2d = (Graphics2D) g;
Stroke oldStroke = g2d.getStroke();
g2d.setStroke(rectBorderStroke);
g.drawRect((int) (bounds.x * scale), (int)( bounds.y * scale), (int)( bounds.width * scale), (int)( bounds.height * scale));
if (scale != 1)
// System.out.println("BOX " + (int) (bounds.x * scale) + "," + (int)( bounds.y * scale) + "," + (int)( bounds.width * scale) + "," + (int)( bounds.height * scale));
g2d.setStroke(oldStroke);
if (isSelected) {
Rectangle[] cornerboxs = getCornerboxRects();
for (int i = 0; i < cornerboxs.length; i++)
g.fillRect( (int) (cornerboxs[i].x * scale), (int)( cornerboxs[i].y * scale),
(int) (cornerboxs[i].width * scale), (int)(cornerboxs[i].height * scale));
}
}
public boolean inside(Point pt) {
return bounds.contains(pt);
}
protected Rectangle[] getCornerboxRects() {
Rectangle[] cornerboxs = new Rectangle[4];
cornerboxs[CORNER_TOP_LEFT] = new Rectangle(bounds.x - CORNERBOX_SIZE / 2, bounds.y - CORNERBOX_SIZE / 2, CORNERBOX_SIZE, CORNERBOX_SIZE);
cornerboxs[CORNER_BOTTOM_LEFT] = new Rectangle(bounds.x - CORNERBOX_SIZE / 2, bounds.y + bounds.height - CORNERBOX_SIZE / 2, CORNERBOX_SIZE, CORNERBOX_SIZE);
cornerboxs[CORNER_BUTTOM_RIGHT] = new Rectangle(bounds.x + bounds.width - CORNERBOX_SIZE / 2, bounds.y + bounds.height - CORNERBOX_SIZE / 2, CORNERBOX_SIZE, CORNERBOX_SIZE);
cornerboxs[CORNER_TOP_RIGHT] = new Rectangle(bounds.x + bounds.width - CORNERBOX_SIZE / 2, bounds.y - CORNERBOX_SIZE / 2, CORNERBOX_SIZE, CORNERBOX_SIZE);
return cornerboxs;
}
/**
* Helper method to determine if a point is within one of the resize corner cornerboxs. If not selected, we have no
* resize cornerboxs, so it can't have been a click on one. Otherwise, we calculate the cornerbox rects and then
* check whether the point falls in one of them. The return value is one of NW, NE, SW, SE constants depending on
* which cornerbox is found, or NONE if the click doesn't fall within any cornerbox.
*/
public int getCornerboxContainingPoint(Point pt) {
if (!isSelected) // if we aren't selected, the cornerboxs aren't showing and
// thus there are no cornerboxs to check
return CORNER_NONE;
Rectangle[] cornerboxs = getCornerboxRects();
for (int i = 0; i < cornerboxs.length; i++)
if (cornerboxs[i].contains(pt))
return i;
return CORNER_NONE;
}
/**
* Method used by PdfPanel to determine if a mouse click is starting a resize event. In order for it to be a resize,
* the click must have been within one of the cornerbox rects (checked by the helper method
* getCornerboxContainingPoint) and if so, we return the "anchor" ie the cornerbox opposite this corner that will
* remain fixed as the user drags the resizing cornerbox of the other corner around. During the drag actions of a
* resize, that fixed anchor point and the current mouse point will be passed to the resize method, which will reset
* the bounds in response to the movement. If the mouseLocation wasn't a click in a cornerbox and thus not the
* beginning of a resize event, null is returned.
*/
public Point getAnchorForResize(Point mouseLocation) {
int whichCornerbox = getCornerboxContainingPoint(mouseLocation);
if (whichCornerbox == CORNER_NONE) // no resize cornerbox is at this location
return null;
switch (whichCornerbox) {
case CORNER_TOP_LEFT:
return new Point(bounds.x + bounds.width, bounds.y + bounds.height);
case CORNER_TOP_RIGHT:
return new Point(bounds.x, bounds.y + bounds.height);
case CORNER_BOTTOM_LEFT:
return new Point(bounds.x + bounds.width, bounds.y);
case CORNER_BUTTOM_RIGHT:
return new Point(bounds.x, bounds.y);
}
return null;
}
public Rectangle getRectangleBound() {
return bounds;
}
@Override
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
}