/*
* @(#)AbstractHandle.java
*
* Copyright (c) 1996-2010 The authors and contributors of JHotDraw.
* You may not use, copy or modify this file, except in compliance with the
* accompanying license terms.
*/
package org.jhotdraw.draw.handle;
import javax.annotation.Nullable;
import org.jhotdraw.draw.*;
import org.jhotdraw.draw.event.HandleListener;
import org.jhotdraw.draw.event.HandleEvent;
import org.jhotdraw.draw.event.FigureListener;
import org.jhotdraw.draw.event.FigureEvent;
import javax.swing.event.*;
import java.awt.*;
import java.awt.event.*;
import java.awt.geom.*;
import javax.swing.undo.*;
import java.util.*;
/**
* This abstract class can be extended to implement a {@link Handle}.
*
* @author Werner Randelshofer
* @version $Id$
*/
public abstract class AbstractHandle implements Handle, FigureListener {
final private Figure owner;
@Nullable protected DrawingView view;
/** Holds the tool tip text. By default a handle has no tool tip text. */
private String toolTipText;
protected EventListenerList listenerList = new EventListenerList();
/**
* The bounds of the abstract handle.
*/
@Nullable private Rectangle bounds;
/** Creates a new instance. */
public AbstractHandle(Figure owner) {
if (owner == null) {
throw new IllegalArgumentException("owner must not be null");
}
this.owner = owner;
owner.addFigureListener(this);
}
protected int getHandlesize() {
return getEditor().getHandleAttribute(HandleAttributeKeys.HANDLE_SIZE);
}
/**
* Adds a listener for this handle.
*/
@Override
public void addHandleListener(HandleListener l) {
listenerList.add(HandleListener.class, l);
}
/**
* Removes a listener for this handle.
*/
@Override
public void removeHandleListener(HandleListener l) {
listenerList.remove(HandleListener.class, l);
}
@Override
public Figure getOwner() {
return owner;
}
@Override
public void setView(@Nullable DrawingView view) {
this.view = view;
}
@Nullable public DrawingView getView() {
return view;
}
public DrawingEditor getEditor() {
return view.getEditor();
}
/**
* Notify all listenerList that have registered interest for
* notification on this event type.
*/
protected void fireAreaInvalidated(Rectangle invalidatedArea) {
HandleEvent event = null;
// Notify all listeners that have registered interest for
// Guaranteed to return a non-null array
Object[] listeners = listenerList.getListenerList();
// Process the listeners last to first, notifying
// those that are interested in this event
for (int i = listeners.length - 2; i >= 0; i -= 2) {
if (listeners[i] == HandleListener.class) {
// Lazily create the event:
if (event == null) {
event = new HandleEvent(this, invalidatedArea);
}
((HandleListener) listeners[i + 1]).areaInvalidated(event);
}
}
}
/**
* Notify all listenerList that have registered interest for
* notification on this event type.
*/
protected void fireUndoableEditHappened(UndoableEdit edit) {
view.getDrawing().fireUndoableEditHappened(edit);
}
/**
* Notify all listenerList that have registered interest for
* notification on this event type.
*/
protected void fireHandleRequestRemove(Rectangle invalidatedArea) {
HandleEvent event = null;
// Notify all listeners that have registered interest for
// Guaranteed to return a non-null array
Object[] listeners = listenerList.getListenerList();
// Process the listeners last to first, notifying
// those that are interested in this event
for (int i = listeners.length - 2; i >= 0; i -= 2) {
if (listeners[i] == HandleListener.class) {
// Lazily create the event:
if (event == null) {
event = new HandleEvent(this, invalidatedArea);
}
((HandleListener) listeners[i + 1]).handleRequestRemove(event);
}
}
}
/**
* Notify all listenerList that have registered interest for
* notification on this event type.
*/
protected void fireHandleRequestSecondaryHandles() {
HandleEvent event = null;
// Notify all listeners that have registered interest for
// Guaranteed to return a non-null array
Object[] listeners = listenerList.getListenerList();
// Process the listeners last to first, notifying
// those that are interested in this event
for (int i = listeners.length - 2; i >= 0; i -= 2) {
if (listeners[i] == HandleListener.class) {
// Lazily create the event:
if (event == null) {
event = new HandleEvent(this, null);
}
((HandleListener) listeners[i + 1]).handleRequestSecondaryHandles(event);
}
}
}
/**
* Draws this handle.
*/
@Override
public void draw(Graphics2D g) {
drawCircle(g,
getEditor().getHandleAttribute(HandleAttributeKeys.HANDLE_FILL_COLOR),
getEditor().getHandleAttribute(HandleAttributeKeys.HANDLE_STROKE_COLOR));
}
protected void drawCircle(Graphics2D g, Color fill, Color stroke) {
Rectangle r = getBounds();
if (fill != null) {
g.setColor(fill);
g.fillOval(r.x + 1, r.y + 1, r.width - 2, r.height - 2);
}
if (stroke != null) {
g.setStroke(new BasicStroke());
g.setColor(stroke);
g.drawOval(r.x, r.y, r.width - 1, r.height - 1);
if (getView().getActiveHandle() == this) {
g.fillOval(r.x + 2, r.y + 2, r.width - 4, r.height - 4);
}
}
}
protected void drawRectangle(Graphics2D g, Color fill, Color stroke) {
if (fill != null) {
Rectangle r = getBounds();
g.setColor(fill);
r.x += 1;
r.y += 1;
r.width -= 2;
r.height -= 2;
g.fill(r);
}
g.setStroke(new BasicStroke());
if (stroke != null) {
Rectangle r = getBounds();
r.width -= 1;
r.height -= 1;
g.setColor(stroke);
g.draw(r);
if (getView().getActiveHandle() == this) {
r.x += 2;
r.y += 2;
r.width -= 3;
r.height -= 3;
g.fill(r);
}
}
}
protected void drawDiamond(Graphics2D g, Color fill, Color stroke) {
if (stroke != null) {
Rectangle r = getBounds();
r.grow(1, 1);
Path2D.Double p = new Path2D.Double();
p.moveTo(r.x + r.width / 2f, r.y);
p.lineTo(r.x + r.width, r.y + r.height / 2f);
p.lineTo(r.x + r.width / 2f, r.y + r.height);
p.lineTo(r.x, r.y + r.height / 2f);
p.closePath();
g.setColor(stroke);
g.fill(p);
}
if (fill != null) {
Rectangle r = getBounds();
Path2D.Double p = new Path2D.Double();
p.moveTo(r.x + r.width / 2f, r.y);
p.lineTo(r.x + r.width, r.y + r.height / 2f);
p.lineTo(r.x + r.width / 2f, r.y + r.height);
p.lineTo(r.x, r.y + r.height / 2f);
p.closePath();
g.setColor(fill);
g.fill(p);
}
if (stroke != null && getView().getActiveHandle() == this) {
Rectangle r = getBounds();
r.grow(-1, -1);
Path2D.Double p = new Path2D.Double();
p.moveTo(r.x + r.width / 2f, r.y);
p.lineTo(r.x + r.width, r.y + r.height / 2f);
p.lineTo(r.x + r.width / 2f, r.y + r.height);
p.lineTo(r.x, r.y + r.height / 2f);
p.closePath();
g.setColor(stroke);
g.fill(p);
}
}
@Override
public boolean contains(Point p) {
return getBounds().contains(p);
}
@Override
public void invalidate() {
bounds = null;
}
@Override
public void dispose() {
owner.removeFigureListener(this);
//owner = null;
}
/**
* Sent when a region used by the figure needs to be repainted.
* The implementation of this method assumes that the handle
* is located on the bounds of the figure or inside the figure.
* If the handle is located elsewhere this method must be reimpleted
* by the subclass.
*/
@Override
public void areaInvalidated(FigureEvent evt) {
updateBounds();
}
/**
* Sent when a figure was added.
*/
@Override
public void figureAdded(FigureEvent e) {
// Empty
}
/**
* Sent when a figure was removed.
*/
@Override
public void figureRemoved(FigureEvent e) {
// Empty
}
/**
* Sent when a figure requests to be removed.
*/
@Override
public void figureRequestRemove(FigureEvent e) {
// Empty
}
/**
* Sent when the bounds or shape of a figure has changed.
*/
@Override
public void figureChanged(FigureEvent evt) {
updateBounds();
}
/**
* Returns a cursor for the handle.
*/
@Override
public Cursor getCursor() {
return Cursor.getPredefinedCursor(Cursor.MOVE_CURSOR);
}
/**
* Returns true, if the given handle is an instance of the same
* class or of a subclass of this handle,.
*/
@Override
public boolean isCombinableWith(Handle handle) {
return getClass().isAssignableFrom(handle.getClass());
}
@Override
public void keyTyped(KeyEvent evt) {
}
@Override
public void keyReleased(KeyEvent evt) {
}
@Override
public void keyPressed(KeyEvent evt) {
}
@Override
public final Rectangle getBounds() {
if (bounds == null) {
bounds = basicGetBounds();
}
return (Rectangle) bounds.clone();
}
@Override
public Rectangle getDrawingArea() {
Rectangle r = getBounds();
r.grow(2, 2); // grow by two pixels to take antialiasing into account
return r;
}
protected abstract Rectangle basicGetBounds();
protected void updateBounds() {
Rectangle newBounds = basicGetBounds();
if (bounds == null || !newBounds.equals(bounds)) {
if (bounds != null) {
fireAreaInvalidated(getDrawingArea());
}
bounds = newBounds;
fireAreaInvalidated(getDrawingArea());
}
}
/**
* Tracks a double click.
*/
@Override
public void trackDoubleClick(Point p, int modifiersEx) {
}
@Override
public void attributeChanged(FigureEvent e) {
}
@Override
public void viewTransformChanged() {
invalidate();
}
@Override
public Collection<Handle> createSecondaryHandles() {
return Collections.emptyList();
}
/**
* Returns a tooltip for the specified location.
* By default, AbstractHandle returns null.
*/
@Override
public String getToolTipText(Point p) {
return toolTipText;
}
/**
* Changes the default tool tip text returned by AbstractHandle.
*/
public void setToolTipText(String newValue) {
toolTipText = newValue;
}
@Override
public void figureHandlesChanged(FigureEvent e) {
}
}