/*
* @(#)CreationTool.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 edu.umd.cs.findbugs.annotations.Nullable;
import org.jhotdraw.draw.*;
import javax.swing.undo.*;
import java.awt.*;
import java.awt.geom.*;
import java.awt.event.*;
import java.util.*;
import org.jhotdraw.util.*;
/**
* A {@link Tool} to create a new figure by drawing its bounds.
* The figure to be created is specified by a prototype.
* <p>
* To create a figure using the {@code CreationTool}, the user does the
* following mouse gestures on a DrawingView:
* <ol>
* <li>Press the mouse button over the DrawingView. This defines the
* start point of the Figure bounds.</li>
* <li>Drag the mouse while keeping the mouse button pressed, and then release
* the mouse button. This defines the end point of the Figure bounds.</li>
* </ol>
* The CreationTool works well with most figures that fit into a rectangular
* shape or that concist of a single straight line. For figures that need
* additional editing after these mouse gestures, the use of a specialized
* creation tool is recommended. For example the TextTool allows to enter the
* text into a TextFigure after the user has performed the mouse gestures.
* <p>
* Alltough the mouse gestures might be fitting for the creation of a connection,
* the CreationTool is not suited for the creation of a ConnectionFigure. Use
* the ConnectionTool for this type of figures instead.
* <p>
*
* <hr>
* <b>Design Patterns</b>
*
* <p><em>Prototype</em><br>
* The creation tool creates new figures by cloning a prototype figure object.
* That's the reason why {@code Figure} extends the {@code Cloneable} interface.
* <br>
* Prototype: {@link Figure}; Client: {@link CreationTool}.
* <hr>
*
* @author Werner Randelshofer
* @version $Id$
*/
public class CreationTool extends AbstractTool {
private static final long serialVersionUID = 1L;
/**
* Attributes to be applied to the created ConnectionFigure.
* These attributes override the default attributes of the
* DrawingEditor.
*/
@Nullable protected Map<AttributeKey<?>, Object> prototypeAttributes;
/**
* A localized name for this tool. The presentationName is displayed by the
* UndoableEdit.
*/
@Nullable protected String presentationName;
/**
* Treshold for which we create a larger shape of a minimal size.
*/
protected Dimension minimalSizeTreshold = new Dimension(2, 2);
/**
* We set the figure to this minimal size, if it is smaller than the
* minimal size treshold.
*/
protected Dimension minimalSize = new Dimension(40, 40);
/**
* The prototype for new figures.
*/
protected Figure prototype;
/**
* The created figure.
*/
@Nullable protected Figure createdFigure;
/**
* If this is set to false, the CreationTool does not fire toolDone
* after a new Figure has been created. This allows to create multiple
* figures consecutively.
*/
private boolean isToolDoneAfterCreation = true;
/** Creates a new instance. */
public CreationTool(String prototypeClassName) {
this(prototypeClassName, null, null);
}
public CreationTool(String prototypeClassName, @Nullable Map<AttributeKey<?>, Object> attributes) {
this(prototypeClassName, attributes, null);
}
public CreationTool(String prototypeClassName, @Nullable Map<AttributeKey<?>, Object> attributes, @Nullable String name) {
try {
this.prototype = (Figure) Class.forName(prototypeClassName).newInstance();
} catch (Exception e) {
InternalError error = new InternalError("Unable to create Figure from " + prototypeClassName);
error.initCause(e);
throw error;
}
this.prototypeAttributes = attributes;
if (name == null) {
ResourceBundleUtil labels = ResourceBundleUtil.getBundle("org.jhotdraw.draw.Labels");
name = labels.getString("edit.createFigure.text");
}
this.presentationName = name;
}
/** Creates a new instance with the specified prototype but without an
* attribute set. The CreationTool clones this prototype each time a new
* Figure needs to be created. When a new Figure is created, the
* CreationTool applies the default attributes from the DrawingEditor to it.
*
* @param prototype The prototype used to create a new Figure.
*/
public CreationTool(Figure prototype) {
this(prototype, null, null);
}
/** Creates a new instance with the specified prototype but without an
* attribute set. The CreationTool clones this prototype each time a new
* Figure needs to be created. When a new Figure is created, the
* CreationTool applies the default attributes from the DrawingEditor to it,
* and then it applies the attributes to it, that have been supplied in
* this constructor.
*
* @param prototype The prototype used to create a new Figure.
* @param attributes The CreationTool applies these attributes to the
* prototype after having applied the default attributes from the DrawingEditor.
*/
public CreationTool(Figure prototype, @Nullable Map<AttributeKey<?>, Object> attributes) {
this(prototype, attributes, null);
}
/**
* Creates a new instance with the specified prototype and attribute set.
*
* @param prototype The prototype used to create a new Figure.
* @param attributes The CreationTool applies these attributes to the
* prototype after having applied the default attributes from the DrawingEditor.
* @param name The name parameter is currently not used.
* @deprecated This constructor might go away, because the name parameter
* is not used.
*/
@Deprecated
public CreationTool(Figure prototype, @Nullable Map<AttributeKey<?>, Object> attributes, @Nullable String name) {
this.prototype = prototype;
this.prototypeAttributes = attributes;
if (name == null) {
ResourceBundleUtil labels = ResourceBundleUtil.getBundle("org.jhotdraw.draw.Labels");
name = labels.getString("edit.createFigure.text");
}
this.presentationName = name;
}
public Figure getPrototype() {
return prototype;
}
@Override
public void activate(DrawingEditor editor) {
super.activate(editor);
if (getView() != null) {
getView().setCursor(Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR));
}
}
@Override
public void deactivate(DrawingEditor editor) {
super.deactivate(editor);
if (getView() != null) {
getView().setCursor(Cursor.getDefaultCursor());
}
if (createdFigure != null) {
if (createdFigure instanceof CompositeFigure) {
((CompositeFigure) createdFigure).layout();
}
createdFigure = null;
}
}
@Override
public void mousePressed(MouseEvent evt) {
super.mousePressed(evt);
getView().clearSelection();
createdFigure = createFigure();
Point2D.Double p = constrainPoint(viewToDrawing(anchor));
anchor.x = evt.getX();
anchor.y = evt.getY();
createdFigure.setBounds(p, p);
getDrawing().add(createdFigure);
}
@Override
public void mouseDragged(MouseEvent evt) {
if (createdFigure != null) {
Point2D.Double p = constrainPoint(new Point(evt.getX(), evt.getY()));
createdFigure.willChange();
createdFigure.setBounds(
constrainPoint(new Point(anchor.x, anchor.y)),
p);
createdFigure.changed();
}
}
@Override
public void mouseReleased(MouseEvent evt) {
if (createdFigure != null) {
Rectangle2D.Double bounds = createdFigure.getBounds();
if (bounds.width == 0 && bounds.height == 0) {
getDrawing().remove(createdFigure);
if (isToolDoneAfterCreation()) {
fireToolDone();
}
} else {
if (Math.abs(anchor.x - evt.getX()) < minimalSizeTreshold.width &&
Math.abs(anchor.y - evt.getY()) < minimalSizeTreshold.height) {
createdFigure.willChange();
createdFigure.setBounds(
constrainPoint(new Point(anchor.x, anchor.y)),
constrainPoint(new Point(
anchor.x + (int) Math.max(bounds.width, minimalSize.width),
anchor.y + (int) Math.max(bounds.height, minimalSize.height))));
createdFigure.changed();
}
if (createdFigure instanceof CompositeFigure) {
((CompositeFigure) createdFigure).layout();
}
final Figure addedFigure = createdFigure;
final Drawing addedDrawing = getDrawing();
getDrawing().fireUndoableEditHappened(new AbstractUndoableEdit() {
private static final long serialVersionUID = 1L;
@Override
public String getPresentationName() {
return presentationName;
}
@Override
public void undo() throws CannotUndoException {
super.undo();
addedDrawing.remove(addedFigure);
}
@Override
public void redo() throws CannotRedoException {
super.redo();
addedDrawing.add(addedFigure);
}
});
Rectangle r = new Rectangle(anchor.x, anchor.y, 0, 0);
r.add(evt.getX(), evt.getY());
maybeFireBoundsInvalidated(r);
creationFinished(createdFigure);
createdFigure = null;
}
} else {
if (isToolDoneAfterCreation()) {
fireToolDone();
}
}
}
@SuppressWarnings("unchecked")
protected Figure createFigure() {
Figure f = prototype.clone();
getEditor().applyDefaultAttributesTo(f);
if (prototypeAttributes != null) {
for (Map.Entry<AttributeKey<?>, Object> entry : prototypeAttributes.entrySet()) {
f.set((AttributeKey<Object>)entry.getKey(), entry.getValue());
}
}
return f;
}
protected Figure getCreatedFigure() {
return createdFigure;
}
protected Figure getAddedFigure() {
return createdFigure;
}
/**
* This method allows subclasses to do perform additonal user interactions
* after the new figure has been created.
* The implementation of this class just invokes fireToolDone.
*/
protected void creationFinished(Figure createdFigure) {
if (createdFigure.isSelectable()) {
getView().addToSelection(createdFigure);
}
if (isToolDoneAfterCreation()) {
fireToolDone();
}
}
/**
* If this is set to false, the CreationTool does not fire toolDone
* after a new Figure has been created. This allows to create multiple
* figures consecutively.
*/
public void setToolDoneAfterCreation(boolean newValue) {
boolean oldValue = isToolDoneAfterCreation;
isToolDoneAfterCreation = newValue;
}
/**
* Returns true, if this tool fires toolDone immediately after a new
* figure has been created.
*/
public boolean isToolDoneAfterCreation() {
return isToolDoneAfterCreation;
}
@Override
public void updateCursor(DrawingView view, Point p) {
if (view.isEnabled()) {
view.setCursor(Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR));
} else {
view.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
}
}
}