/* * @(#)AbstractTool.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.tool; import javax.annotation.Nullable; import org.jhotdraw.draw.*; import org.jhotdraw.draw.handle.Handle; import org.jhotdraw.draw.event.ToolEvent; import org.jhotdraw.draw.event.ToolListener; import javax.swing.*; import org.jhotdraw.beans.AbstractBean; import java.awt.*; import java.awt.geom.*; import java.awt.event.*; import javax.swing.event.*; import static org.jhotdraw.draw.AttributeKeys.*; /** * This abstract class can be extended to implement a {@link Tool}. * * <hr> * <b>Design Patterns</b> * * <p> * <em>Proxy</em><br> * To remove the need for null-handling, {@code AbstractTool} makes use of a proxy for * {@code DrawingEditor}. Subject: {@link DrawingEditor}; Proxy: {@link DrawingEditorProxy}; Client: * {@link AbstractTool}. * <hr> * * @author Werner Randelshofer * @version $Id$ */ public abstract class AbstractTool extends AbstractBean implements Tool { private static final long serialVersionUID = 1L; /** * This is set to true, if this is the active tool of the editor. */ private boolean isActive; /** * This is set to true, while the tool is doing some work. This prevents the currentView from * being changed when a mouseEnter event is received. */ protected boolean isWorking; protected DrawingEditor editor; protected Point anchor = new Point(); protected EventListenerList listenerList = new EventListenerList(); private DrawingEditorProxy editorProxy; /* private PropertyChangeListener editorHandler; private PropertyChangeListener viewHandler; */ /** * The input map of the tool. */ private InputMap inputMap; /** * The action map of the tool. */ private ActionMap actionMap; /** * Creates a new instance. */ public AbstractTool() { editorProxy = new DrawingEditorProxy(); setInputMap(createInputMap()); setActionMap(createActionMap()); } public void addUndoableEditListener(UndoableEditListener l) { listenerList.add(UndoableEditListener.class, l); } public void removeUndoableEditListener(UndoableEditListener l) { listenerList.remove(UndoableEditListener.class, l); } @Override public void activate(DrawingEditor editor) { this.editor = editor; editorProxy.setTarget(editor); isActive = true; // Repaint all handles for (DrawingView v : editor.getDrawingViews()) { v.repaintHandles(); } } @Override public void deactivate(DrawingEditor editor) { this.editor = editor; editorProxy.setTarget(null); isActive = false; } public boolean isActive() { return isActive; } @Nullable protected DrawingView getView() { return editor.getActiveView(); } protected DrawingEditor getEditor() { return editor; } protected Drawing getDrawing() { return getView().getDrawing(); } protected Point2D.Double viewToDrawing(Point p) { return constrainPoint(getView().viewToDrawing(p)); } protected Point2D.Double constrainPoint(Point p, Figure... figure) { return constrainPoint(getView().viewToDrawing(p), figure); } protected Point2D.Double constrainPoint(Point2D.Double p, Figure... figure) { if (getView() == null) { return p; } return getView().getConstrainer() == null ? p : getView().getConstrainer().constrainPoint(p, figure); } /** * Sets the InputMap for the Tool. * * @see #keyPressed * @see #setActionMap */ public void setInputMap(InputMap newValue) { inputMap = newValue; } /** * Gets the input map of the Tool */ public InputMap getInputMap() { return inputMap; } /** * Sets the ActionMap for the Tool. * * @see #keyPressed */ public void setActionMap(ActionMap newValue) { actionMap = newValue; } /** * Gets the action map of the Tool */ public ActionMap getActionMap() { return actionMap; } /** * Deletes the selection. Depending on the tool, this could be selected figures, selected points * or selected text. */ @Override public void editDelete() { getView().getDrawing().removeAll(getView().getSelectedFigures()); } /** * Cuts the selection into the clipboard. Depending on the tool, this could be selected figures, * selected points or selected text. */ @Override public void editCut() { } /** * Copies the selection into the clipboard. Depending on the tool, this could be selected * figures, selected points or selected text. */ @Override public void editCopy() { } /** * Duplicates the selection. Depending on the tool, this could be selected figures, selected * points or selected text. */ @Override public void editDuplicate() { } /** * Pastes the contents of the clipboard. Depending on the tool, this could be selected figures, * selected points or selected text. */ @Override public void editPaste() { } @Override public void keyReleased(KeyEvent evt) { fireToolDone(); } @Override public void keyTyped(KeyEvent evt) { } /** * The Tool uses the InputMap to determine what to do, when a key is pressed. If the * corresponding value of the InputMap is a String, the ActionMap of the tool is used, to find * the action to be performed. If the corresponding value of the InputMap is a ActionListener, * the actionPerformed method of the ActionListener is performed. */ @Override public void keyPressed(KeyEvent evt) { if (!evt.isConsumed()) { if (evt.getSource() instanceof Container) { editor.setActiveView(editor.findView((Container) evt.getSource())); } Object obj = null; if (inputMap != null) { // Lookup the input map of the tool obj = inputMap.get(KeyStroke.getKeyStroke(evt.getKeyCode(), evt.getModifiers(), false)); } if (obj == null) { // Fall back to the input map of the drawing editor InputMap im = editor.getInputMap(); if (im != null) { obj = im.get(KeyStroke.getKeyStroke(evt.getKeyCode(), evt.getModifiers(), false)); } } ActionListener al = null; if (obj instanceof ActionListener) { al = (ActionListener) obj; } else if (obj != null) { // Lookup the action map of the tool if (actionMap != null) { al = actionMap.get(obj); } if (al == null) { // Fall back to the action map of the drawing editor al = editor.getActionMap().get(obj); } } if (al != null) { evt.consume(); al.actionPerformed(new ActionEvent(this, ActionEvent.ACTION_PERFORMED, "tool", evt.getWhen(), evt.getModifiers())); fireToolDone(); } } } /** * Override this method to create a tool-specific input map, which overrides the input map of * the drawing edtior. * <p> * The implementation of this class returns null. */ @Nullable protected InputMap createInputMap() { return null; } /** * Override this method to create a tool-specific action map, which overrides the action map of * the drawing edtior. * <p> * The implementation of this class returns null. */ @Nullable protected ActionMap createActionMap() { return null; } @Override public void mouseClicked(MouseEvent evt) { } @Override public void mouseEntered(MouseEvent evt) { /*if (! isWorking) { editor.setActiveView(editor.findView((Container) evt.getSource())); }*/ } @Override public void mouseExited(MouseEvent evt) { } @Override public void mouseMoved(MouseEvent evt) { } @Override public void mousePressed(MouseEvent evt) { DrawingView view = editor.findView((Container) evt.getSource()); view.requestFocus(); anchor = new Point(evt.getX(), evt.getY()); isWorking = true; fireToolStarted(view); } @Override public void mouseReleased(MouseEvent evt) { isWorking = false; } @Override public void addToolListener(ToolListener l) { listenerList.add(ToolListener.class, l); } @Override public void removeToolListener(ToolListener l) { listenerList.remove(ToolListener.class, l); } /** * Notify all listenerList that have registered interest for notification on this event type. */ protected void fireToolStarted(DrawingView view) { ToolEvent 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] == ToolListener.class) { // Lazily create the event: if (event == null) { event = new ToolEvent(this, view, new Rectangle(0, 0, -1, -1)); } ((ToolListener) listeners[i + 1]).toolStarted(event); } } } /** * Notify all listenerList that have registered interest for notification on this event type. */ protected void fireToolDone() { ToolEvent 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] == ToolListener.class) { // Lazily create the event: if (event == null) { event = new ToolEvent(this, getView(), new Rectangle(0, 0, -1, -1)); } ((ToolListener) listeners[i + 1]).toolDone(event); } } } /** * Notify all listenerList that have registered interest for notification on this event type. */ protected void fireAreaInvalidated(Rectangle2D.Double r) { Point p1 = getView().drawingToView(new Point2D.Double(r.x, r.y)); Point p2 = getView().drawingToView(new Point2D.Double(r.x + r.width, r.y + r.height)); fireAreaInvalidated( new Rectangle(p1.x, p1.y, p2.x - p1.x, p2.y - p1.y)); } /** * Notify all listenerList that have registered interest for notification on this event type. */ protected void fireAreaInvalidated(Rectangle invalidatedArea) { ToolEvent 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] == ToolListener.class) { // Lazily create the event: if (event == null) { event = new ToolEvent(this, getView(), invalidatedArea); } ((ToolListener) listeners[i + 1]).areaInvalidated(event); } } } /** * Notify all listenerList that have registered interest for notification on this event type. * * Note: This method only fires an event, if the invalidated area is outside of the canvas * bounds. */ protected void maybeFireBoundsInvalidated(Rectangle invalidatedArea) { Drawing d = getDrawing(); Rectangle2D.Double canvasBounds = new Rectangle2D.Double(0, 0, 0, 0); if (d.get(CANVAS_WIDTH) != null) { canvasBounds.width += d.get(CANVAS_WIDTH); } if (d.get(CANVAS_HEIGHT) != null) { canvasBounds.height += d.get(CANVAS_HEIGHT); } if (!canvasBounds.contains(invalidatedArea)) { fireBoundsInvalidated(invalidatedArea); } } /** * Notify all listenerList that have registered interest for notification on this event type. */ protected void fireBoundsInvalidated(Rectangle invalidatedArea) { ToolEvent 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] == ToolListener.class) { // Lazily create the event: if (event == null) { event = new ToolEvent(this, getView(), invalidatedArea); } ((ToolListener) listeners[i + 1]).boundsInvalidated(event); } } } @Override public void draw(Graphics2D g) { } public void updateCursor(DrawingView view, Point p) { if (view.isEnabled()) { Handle handle = view.findHandle(p); if (handle != null) { view.setCursor(handle.getCursor()); } else { Figure figure = view.findFigure(p); Point2D.Double point = view.viewToDrawing(p); Drawing drawing = view.getDrawing(); while (figure != null && !figure.isSelectable()) { figure = drawing.findFigureBehind(point, figure); } if (figure != null) { view.setCursor(figure.getCursor(view.viewToDrawing(p))); } else { view.setCursor(Cursor.getDefaultCursor()); } } } else { view.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); } } @Override public String getToolTipText(DrawingView view, MouseEvent evt) { return null; } /** * Returns true, if this tool lets the user interact with handles. * <p> * Handles may draw differently, if interaction is not possible. * * @return True, if this tool supports interaction with the handles. */ @Override public boolean supportsHandleInteraction() { return false; } }