/* * @(#)BezierTool.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 org.jhotdraw.util.*; import java.awt.*; import java.awt.geom.*; import java.awt.event.*; import java.util.*; import org.jhotdraw.geom.*; /** * A {@link Tool} which allows to create a new {@link BezierFigure} by * drawing its path. * <p> * To creation of the BezierFigure can be finished by adding a segment * which closes the path, or by double clicking on the drawing area, or by * selecting a different tool in the DrawingEditor. * @author Werner Randelshofer * @version $Id$ */ public class BezierTool extends AbstractTool { private static final long serialVersionUID = 1L; /** * Set this to true to turn on debugging output on System.out. */ private static final boolean DEBUG = false; @Nullable private Boolean finishWhenMouseReleased; @Nullable protected Map<AttributeKey<?>, Object> attributes; private boolean isToolDoneAfterCreation; /** * The prototype for new figures. */ private BezierFigure prototype; /** * The created figure. */ @Nullable protected BezierFigure createdFigure; private int nodeCountBeforeDrag; /** * A localized name for this tool. The presentationName is displayed by the * UndoableEdit. */ @Nullable private String presentationName; private Point mouseLocation; /** Holds the view on which we are currently creating a figure. */ private DrawingView creationView; /** Creates a new instance. */ public BezierTool(BezierFigure prototype) { this(prototype, null); } /** Creates a new instance. */ public BezierTool(BezierFigure prototype, @Nullable Map<AttributeKey<?>, Object> attributes) { this(prototype, attributes, null); } public BezierTool(BezierFigure prototype, @Nullable Map<AttributeKey<?>, Object> attributes, @Nullable String name) { this.prototype = prototype; this.attributes = attributes; if (name == null) { ResourceBundleUtil labels = ResourceBundleUtil.getBundle("org.jhotdraw.draw.Labels"); name = labels.getString("edit.createFigure.text"); } this.presentationName = name; } public String getPresentationName() { return presentationName; } @Override public void activate(DrawingEditor editor) { super.activate(editor); getView().setCursor(Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR)); } @Override public void deactivate(DrawingEditor editor) { super.deactivate(editor); getView().setCursor(Cursor.getDefaultCursor()); if (createdFigure != null) { if (anchor != null && mouseLocation != null) { Rectangle r = new Rectangle(anchor); r.add(mouseLocation); if (createdFigure.getNodeCount() > 0 && createdFigure.isClosed()) { r.add(getView().drawingToView(createdFigure.getStartPoint())); } fireAreaInvalidated(r); } finishCreation(createdFigure, creationView); createdFigure = null; } } @Override public void mousePressed(MouseEvent evt) { if (DEBUG) { System.out.println("BezierTool.mousePressed " + evt); } if (mouseLocation != null) { Rectangle r = new Rectangle(mouseLocation); r.add(evt.getPoint()); r.grow(1, 1); fireAreaInvalidated(r); } mouseLocation = evt.getPoint(); super.mousePressed(evt); if (createdFigure != null && creationView != getView()) { finishCreation(createdFigure, creationView); createdFigure = null; } if (createdFigure == null) { creationView = getView(); creationView.clearSelection(); finishWhenMouseReleased = null; createdFigure = createFigure(); createdFigure.addNode(new BezierPath.Node( creationView.getConstrainer().constrainPoint( creationView.viewToDrawing(anchor)))); getDrawing().add(createdFigure); } else { if (evt.getClickCount() == 1) { addPointToFigure(creationView.getConstrainer().constrainPoint( creationView.viewToDrawing(anchor))); } } nodeCountBeforeDrag = createdFigure.getNodeCount(); } @SuppressWarnings("unchecked") protected BezierFigure createFigure() { BezierFigure f = prototype.clone(); getEditor().applyDefaultAttributesTo(f); if (attributes != null) { for (Map.Entry<AttributeKey<?>, Object> entry : attributes.entrySet()) { f.set((AttributeKey<Object>)entry.getKey(), entry.getValue()); } } return f; } protected Figure getCreatedFigure() { return createdFigure; } protected Figure getAddedFigure() { return createdFigure; } protected void addPointToFigure(Point2D.Double newPoint) { int pointCount = createdFigure.getNodeCount(); createdFigure.willChange(); if (pointCount < 2) { createdFigure.addNode(new BezierPath.Node(newPoint)); } else { Point2D.Double endPoint = createdFigure.getEndPoint(); Point2D.Double secondLastPoint = (pointCount <= 1) ? endPoint : createdFigure.getPoint(pointCount - 2, 0); if (newPoint.equals(endPoint)) { // nothing to do } else if (pointCount > 1 && Geom.lineContainsPoint(newPoint.x, newPoint.y, secondLastPoint.x, secondLastPoint.y, endPoint.x, endPoint.y, 0.9f / getView().getScaleFactor())) { createdFigure.setPoint(pointCount - 1, 0, newPoint); } else { createdFigure.addNode(new BezierPath.Node(newPoint)); } } createdFigure.changed(); } @Override public void mouseClicked(MouseEvent evt) { if (createdFigure != null) { switch (evt.getClickCount()) { case 1: if (createdFigure.getNodeCount() > 2) { Rectangle r = new Rectangle(getView().drawingToView(createdFigure.getStartPoint())); r.grow(2, 2); if (r.contains(evt.getX(), evt.getY())) { createdFigure.setClosed(true); finishCreation(createdFigure, creationView); createdFigure = null; if (isToolDoneAfterCreation) { fireToolDone(); } } } break; case 2: finishWhenMouseReleased = null; finishCreation(createdFigure, creationView); createdFigure = null; break; } } } protected void fireUndoEvent(Figure createdFigure, DrawingView creationView) { final Figure addedFigure = createdFigure; final Drawing addedDrawing = creationView.getDrawing(); final DrawingView addedView = creationView; 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(); addedView.clearSelection(); addedDrawing.add(addedFigure); addedView.addToSelection(addedFigure); } }); } @Override public void mouseReleased(MouseEvent evt) { if (DEBUG) { System.out.println("BezierTool.mouseReleased " + evt); } isWorking = false; if (createdFigure.getNodeCount() > nodeCountBeforeDrag + 1) { createdFigure.willChange(); BezierPath figurePath = createdFigure.getBezierPath(); BezierPath digitizedPath = new BezierPath(); for (int i = nodeCountBeforeDrag - 1, n = figurePath.size(); i < n; i++) { digitizedPath.add(figurePath.get(nodeCountBeforeDrag - 1)); figurePath.remove(nodeCountBeforeDrag - 1); } BezierPath fittedPath = calculateFittedCurve(digitizedPath); //figurePath.addPolyline(digitizedPath); figurePath.addAll(fittedPath); createdFigure.setBezierPath(figurePath); createdFigure.changed(); nodeCountBeforeDrag = createdFigure.getNodeCount(); } if (finishWhenMouseReleased == Boolean.TRUE) { if (createdFigure.getNodeCount() > 1) { Rectangle r = new Rectangle(anchor.x, anchor.y, 0, 0); r.add(evt.getX(), evt.getY()); maybeFireBoundsInvalidated(r); finishCreation(createdFigure, creationView); createdFigure = null; finishWhenMouseReleased = null; return; } } else if (finishWhenMouseReleased == null) { finishWhenMouseReleased = Boolean.FALSE; } // repaint dotted line Rectangle r = new Rectangle(anchor); r.add(mouseLocation); r.add(evt.getPoint()); r.grow(1, 1); fireAreaInvalidated(r); anchor.x = evt.getX(); anchor.y = evt.getY(); mouseLocation = evt.getPoint(); } protected void finishCreation(BezierFigure createdFigure, DrawingView creationView) { fireUndoEvent(createdFigure, creationView); creationView.addToSelection(createdFigure); if (isToolDoneAfterCreation) { fireToolDone(); } } @Override public void mouseDragged(MouseEvent evt) { if (finishWhenMouseReleased == null) { finishWhenMouseReleased = Boolean.TRUE; } int x = evt.getX(); int y = evt.getY(); addPointToFigure(getView().viewToDrawing(new Point(x, y))); } @Override public void draw(Graphics2D g) { if (createdFigure != null && // anchor != null && // mouseLocation != null &&// getView() == creationView) { g.setColor(Color.BLACK); g.setStroke(new BasicStroke(1f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL, 0f, new float[]{1f, 5f}, 0f)); g.drawLine(anchor.x, anchor.y, mouseLocation.x, mouseLocation.y); if (!isWorking && createdFigure.isClosed() && createdFigure.getNodeCount() > 1) { Point p = creationView.drawingToView(createdFigure.getStartPoint()); g.drawLine(mouseLocation.x, mouseLocation.y, p.x, p.y); } } } @Override public void mouseMoved(MouseEvent evt) { if (createdFigure != null && anchor != null && mouseLocation != null) { if (evt.getSource() == creationView) { Rectangle r = new Rectangle(anchor); r.add(mouseLocation); r.add(evt.getPoint()); if (createdFigure.isClosed() && createdFigure.getNodeCount() > 0) { r.add(creationView.drawingToView(createdFigure.getStartPoint())); } r.grow(1, 1); fireAreaInvalidated(r); mouseLocation = evt.getPoint(); } } } protected BezierPath calculateFittedCurve(BezierPath path) { return Bezier.fitBezierPath(path, 1.5d / getView().getScaleFactor()); } public void setToolDoneAfterCreation(boolean b) { isToolDoneAfterCreation = b; } public boolean isToolDoneAfterCreation() { return isToolDoneAfterCreation; } }