/*
* @(#)DefaultDrawingEditor.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;
import edu.umd.cs.findbugs.annotations.Nullable;
import org.jhotdraw.app.action.edit.PasteAction;
import org.jhotdraw.app.action.edit.CutAction;
import org.jhotdraw.app.action.edit.DeleteAction;
import org.jhotdraw.app.action.edit.CopyAction;
import org.jhotdraw.app.action.edit.SelectAllAction;
import org.jhotdraw.draw.tool.Tool;
import org.jhotdraw.draw.event.ToolEvent;
import javax.swing.ActionMap;
import javax.swing.InputMap;
import org.jhotdraw.beans.*;
import java.awt.*;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
import java.util.*;
import javax.swing.JComponent;
import javax.swing.KeyStroke;
import org.jhotdraw.draw.action.*;
import org.jhotdraw.draw.event.ToolAdapter;
import static org.jhotdraw.draw.AttributeKeys.*;
/**
* A default implementation of {@link DrawingEditor}.
*
* XXX - DefaultDrawingEditor should not publicly implement ToolListener.
*
* @author Werner Randelshofer
* @version $Id$
*/
public class DefaultDrawingEditor extends AbstractBean implements DrawingEditor {
private static final long serialVersionUID = 1L;
private HashMap<AttributeKey<?>, Object> defaultAttributes = new HashMap<AttributeKey<?>, Object>();
private HashMap<AttributeKey<?>, Object> handleAttributes = new HashMap<AttributeKey<?>, Object>();
private Tool tool;
private HashSet<DrawingView> views;
private DrawingView activeView;
private boolean isEnabled = true;
private ToolHandler toolHandler;
private class ToolHandler extends ToolAdapter {
@Override
public void areaInvalidated(ToolEvent evt) {
Rectangle r = evt.getInvalidatedArea();
evt.getView().getComponent().repaint(r.x, r.y, r.width, r.height);
}
@Override
public void toolStarted(ToolEvent evt) {
setActiveView(evt.getView());
}
@Override
public void boundsInvalidated(ToolEvent evt) {
Drawing d = evt.getView().getDrawing();
for (DrawingView v : views) {
if (v.getDrawing() == d) {
JComponent c = v.getComponent();
c.revalidate();
}
}
}
}
/**
* The input map of the drawing editor.
*/
private InputMap inputMap;
/**
* The action map of the drawing editor.
*/
private ActionMap actionMap;
/**
* The focus handler.
*/
private FocusListener focusHandler = new FocusListener() {
@Override
public void focusGained(FocusEvent e) {
setActiveView(findView((Container) e.getSource()));
}
@Override
public void focusLost(FocusEvent e) {
/*
if (! e.isTemporary()) {
setFocusedView(null);
}*/
}
};
/** Creates a new instance. */
public DefaultDrawingEditor() {
toolHandler = new ToolHandler();
setDefaultAttribute(FILL_COLOR, Color.white);
setDefaultAttribute(STROKE_COLOR, Color.black);
setDefaultAttribute(TEXT_COLOR, Color.black);
views = new HashSet<DrawingView>();
inputMap = createInputMap();
actionMap = createActionMap();
}
@Override
public void setTool(Tool newValue) {
Tool oldValue = tool;
if (newValue == tool) {
return;
}
if (tool != null) {
for (DrawingView v : views) {
v.removeMouseListener(tool);
v.removeMouseMotionListener(tool);
v.removeKeyListener(tool);
}
tool.deactivate(this);
tool.removeToolListener(toolHandler);
}
tool = newValue;
if (tool != null) {
tool.activate(this);
for (DrawingView v : views) {
v.addMouseListener(tool);
v.addMouseMotionListener(tool);
v.addKeyListener(tool);
}
tool.addToolListener(toolHandler);
}
firePropertyChange(TOOL_PROPERTY, oldValue, newValue);
}
@Override
public void setActiveView(DrawingView newValue) {
DrawingView oldValue = activeView;
activeView = newValue;
firePropertyChange(ACTIVE_VIEW_PROPERTY, oldValue, newValue);
}
@Override
public Tool getTool() {
return tool;
}
@Override
public DrawingView getActiveView() {
return activeView;
}
private void updateActiveView() {
DrawingView aView = null;
for (DrawingView v : views) {
if (v.getComponent().isFocusOwner()) {
setActiveView(v);
return;
}
aView = v;
}
setActiveView(aView);
}
@SuppressWarnings("unchecked")
@Override
public void applyDefaultAttributesTo(Figure f) {
for (Map.Entry<AttributeKey<?>, Object> entry : defaultAttributes.entrySet()) {
f.set((AttributeKey<Object>)entry.getKey(), entry.getValue());
}
}
@Override
public <T> T getDefaultAttribute(AttributeKey<T> key) {
if (defaultAttributes.containsKey(key)) {
return key.get(defaultAttributes);
} else {
return key.getDefaultValue();
}
}
@Override
public <T> void setDefaultAttribute(AttributeKey<T> key, @Nullable T newValue) {
Object oldValue = defaultAttributes.put(key, newValue);
firePropertyChange(DEFAULT_ATTRIBUTE_PROPERTY_PREFIX+key.getKey(), oldValue, newValue);
}
@Override
public void remove(DrawingView view) {
view.getComponent().removeFocusListener(focusHandler);
views.remove(view);
if (tool != null) {
view.removeMouseListener(tool);
view.removeMouseMotionListener(tool);
view.removeKeyListener(tool);
}
view.removeNotify(this);
if (activeView == view) {
view = (views.size() > 0) ? views.iterator().next() : null;
}
updateActiveView();
}
@Override
public void add(DrawingView view) {
views.add(view);
view.addNotify(this);
view.getComponent().addFocusListener(focusHandler);
if (tool != null) {
view.addMouseListener(tool);
view.addMouseMotionListener(tool);
view.addKeyListener(tool);
}
updateActiveView();
}
@Override
public void setCursor(Cursor c) {
}
@Override
public Collection<DrawingView> getDrawingViews() {
return Collections.unmodifiableCollection(views);
}
@Override
public DrawingView findView(Container c) {
for (DrawingView v : views) {
if (v.getComponent() == c) {
return v;
}
}
return null;
}
@Override
public void setEnabled(boolean newValue) {
if (newValue != isEnabled) {
boolean oldValue = isEnabled;
isEnabled = newValue;
firePropertyChange("enabled", oldValue, newValue);
}
}
@Override
public boolean isEnabled() {
return isEnabled;
}
@Override
public Map<AttributeKey<?>, Object> getDefaultAttributes() {
@SuppressWarnings("cast")
Map<AttributeKey<?>, Object> m = (Map<AttributeKey<?>, Object>) Collections.unmodifiableMap(defaultAttributes);
return m;
}
@Override
public <T> void setHandleAttribute(AttributeKey<T> key, T value) {
handleAttributes.put(key, value);
}
@Override
public <T> T getHandleAttribute(AttributeKey<T> key) {
if (handleAttributes.containsKey(key)) {
return key.get(handleAttributes);
} else {
return key.getDefaultValue();
}
}
@Override
public void setInputMap(InputMap newValue) {
InputMap oldValue = inputMap;
inputMap = newValue;
firePropertyChange(INPUT_MAP_PROPERTY, oldValue, newValue);
}
@Override
public InputMap getInputMap() {
return inputMap;
}
@Override
public void setActionMap(ActionMap newValue) {
ActionMap oldValue = actionMap;
actionMap = newValue;
firePropertyChange(ACTION_MAP_PROPERTY, oldValue, newValue);
}
@Override
public ActionMap getActionMap() {
return actionMap;
}
/** Override this method to create a tool-specific input map, which
* overrides the input map of the drawing editor.
* <p>
* The implementation of this class creates an input map for the following
* action ID's:
* <ul>
* <li>DeleteAction</li>
* <li>SelectAllAction</li>
* <li>IncreaseHandleDetailLevelAction</li>
* <li>MoveConstrainedAction.West, .East, .North, .South</li>
* <li>MoveAction.West, .East, .North, .South</li>
* <li>CutAction</li>
* <li>CopyAction</li>
* <li>PasteAction</li>
* </ul>
*/
protected InputMap createInputMap() {
InputMap m = new InputMap();
m.put(KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, 0), DeleteAction.ID);
m.put(KeyStroke.getKeyStroke(KeyEvent.VK_BACK_SPACE, 0), DeleteAction.ID);
m.put(KeyStroke.getKeyStroke(KeyEvent.VK_A, 0), SelectAllAction.ID);
m.put(KeyStroke.getKeyStroke(KeyEvent.VK_A, InputEvent.CTRL_DOWN_MASK), SelectAllAction.ID);
m.put(KeyStroke.getKeyStroke(KeyEvent.VK_A, InputEvent.META_DOWN_MASK), SelectAllAction.ID);
m.put(KeyStroke.getKeyStroke(KeyEvent.VK_SPACE, 0), IncreaseHandleDetailLevelAction.ID);
m.put(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0), MoveConstrainedAction.West.ID);
m.put(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0), MoveConstrainedAction.East.ID);
m.put(KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0), MoveConstrainedAction.North.ID);
m.put(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0), MoveConstrainedAction.South.ID);
m.put(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, InputEvent.ALT_DOWN_MASK), MoveAction.West.ID);
m.put(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, InputEvent.ALT_DOWN_MASK), MoveAction.East.ID);
m.put(KeyStroke.getKeyStroke(KeyEvent.VK_UP, InputEvent.ALT_DOWN_MASK), MoveAction.North.ID);
m.put(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, InputEvent.ALT_DOWN_MASK), MoveAction.South.ID);
m.put(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, InputEvent.SHIFT_DOWN_MASK), MoveAction.West.ID);
m.put(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, InputEvent.SHIFT_DOWN_MASK), MoveAction.East.ID);
m.put(KeyStroke.getKeyStroke(KeyEvent.VK_UP, InputEvent.SHIFT_DOWN_MASK), MoveAction.North.ID);
m.put(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, InputEvent.SHIFT_DOWN_MASK), MoveAction.South.ID);
m.put(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, InputEvent.CTRL_DOWN_MASK), MoveAction.West.ID);
m.put(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, InputEvent.CTRL_DOWN_MASK), MoveAction.East.ID);
m.put(KeyStroke.getKeyStroke(KeyEvent.VK_UP, InputEvent.CTRL_DOWN_MASK), MoveAction.North.ID);
m.put(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, InputEvent.CTRL_DOWN_MASK), MoveAction.South.ID);
m.put(KeyStroke.getKeyStroke(KeyEvent.VK_C, InputEvent.CTRL_DOWN_MASK), CopyAction.ID);
m.put(KeyStroke.getKeyStroke(KeyEvent.VK_C, InputEvent.META_DOWN_MASK), CopyAction.ID);
m.put(KeyStroke.getKeyStroke(KeyEvent.VK_V, InputEvent.CTRL_DOWN_MASK), PasteAction.ID);
m.put(KeyStroke.getKeyStroke(KeyEvent.VK_V, InputEvent.META_DOWN_MASK), PasteAction.ID);
m.put(KeyStroke.getKeyStroke(KeyEvent.VK_X, InputEvent.CTRL_DOWN_MASK), CutAction.ID);
m.put(KeyStroke.getKeyStroke(KeyEvent.VK_X, InputEvent.META_DOWN_MASK), CutAction.ID);
return m;
}
/** Override this method to create a tool-specific action map, which
* overrides the action map of the drawing editor.
* <p>
* The implementation of this class creates an action map which maps
* the following action ID's to the classes which define them:
* <ul>
* <li>DeleteAction</li>
* <li>SelectAllAction</li>
* <li>IncreaseHandleDetailLevelAction</li>
* <li>MoveConstrainedAction.West, .East, .North, .South</li>
* <li>MoveAction.West, .East, .North, .South</li>
* <li>CutAction</li>
* <li>CopyAction</li>
* <li>PasteAction</li>
* </ul>
*/
protected ActionMap createActionMap() {
ActionMap m = new ActionMap();
m.put(DeleteAction.ID, new DeleteAction());
m.put(SelectAllAction.ID, new SelectAllAction());
m.put(IncreaseHandleDetailLevelAction.ID, new IncreaseHandleDetailLevelAction(this));
m.put(MoveAction.East.ID, new MoveAction.East(this));
m.put(MoveAction.West.ID, new MoveAction.West(this));
m.put(MoveAction.North.ID, new MoveAction.North(this));
m.put(MoveAction.South.ID, new MoveAction.South(this));
m.put(MoveConstrainedAction.East.ID, new MoveConstrainedAction.East(this));
m.put(MoveConstrainedAction.West.ID, new MoveConstrainedAction.West(this));
m.put(MoveConstrainedAction.North.ID, new MoveConstrainedAction.North(this));
m.put(MoveConstrainedAction.South.ID, new MoveConstrainedAction.South(this));
m.put(CutAction.ID, new CutAction());
m.put(CopyAction.ID, new CopyAction());
m.put(PasteAction.ID, new PasteAction());
return m;
}
}