/******************************************************************************* * Copyright (c) 2006-2012 * Software Technology Group, Dresden University of Technology * DevBoost GmbH, Berlin, Amtsgericht Charlottenburg, HRB 140026 * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Software Technology Group - TU Dresden, Germany; * DevBoost GmbH - Berlin, Germany * - initial API and implementation ******************************************************************************/ /* * @(#)ConnectionTool.java 4.2.1 2008-07-06 * * Copyright (c) 1996-2008 by the original authors of JHotDraw * and all its contributors. * All rights reserved. * * The copyright of this software is owned by the authors and * contributors of the JHotDraw project ("the copyright holders"). * You may not use, copy or modify this software, except in * accordance with the license agreement you entered into with * the copyright holders. For details see accompanying license terms. */ package org.jhotdraw.draw; import javax.swing.undo.*; import org.jhotdraw.undo.*; import org.jhotdraw.util.*; import java.awt.*; import java.awt.geom.*; import java.awt.event.*; import java.util.*; import java.awt.dnd.*; /** * A tool to create a connection between two figures. * The {@link ConnectionFigure} to be created is specified by a prototype. * The location of the start and end points are controlled by {@link Connector}s. * <p> * To create a connection using the ConnectionTool, the user does the following * mouse gestures on a DrawingView: * <ol> * <li>Press the mouse button inside of a Figure. If the ConnectionTool can * find a Connector at this location, it uses it as the starting point for * the connection.</li> * <li>Drag the mouse while keeping the mouse button pressed, and then release * the mouse button. This defines the end point of the connection. * If the ConnectionTool finds a Connector at this location, it uses it * as the end point of the connection and creates a ConnectionFigure.</li> * </ol> * * @author Werner Randelshofer * @version 4.2.1 2008-07-06 Method mouseReleased allowed to connect to figures * even if ConnectionFigure.canConnect(…,…) returned false. * <br>4.2 2008-03-31 Added tow methods named canConnect() to this tool, * so that subclasses can override the behavior. Made variables startConnector * and endConnector protected instead of private. * <br>4.1 2007-08-22 Added property 'toolDoneAfterCreation'. * <br>4.0 2007-05 Reworked due to changes in ConnectionFigure interface. * Removed split/join functionality for connection points. * <br>3.1 2006-07-15 Added support for prototype class name. * <br>3.0 2006-06-07 Reworked. * <br>2.1 2006-03-15 When user is not pressing the mouse button, we use * the mouse over view as the current view. * <br>2.0.1 2006-02-14 Fixed drawing code. * <br>2.0 2006-01-14 Changed to support double precision coordinates. * <br>1.0 2003-12-01 Derived from JHotDraw 5.4b1. */ public class ConnectionTool extends AbstractTool { /** FIXME - The ANCHOR_WIDTH value must be retrieved from the DrawingEditor */ private final static int ANCHOR_WIDTH = 6; /** * Attributes to be applied to the created ConnectionFigure. * These attributes override the default attributes of the * DrawingEditor. */ private Map<AttributeKey, Object> prototypeAttributes; /** * The Connector at the start point of the connection. */ protected Connector startConnector; /** * The Connector at the end point of the connection. */ protected Connector endConnector; /** * The created figure. */ protected ConnectionFigure createdFigure; /** * the prototypical figure that is used to create new * connections. */ protected ConnectionFigure prototype; /** * The figure for which we enabled drawing of connectors. */ protected Figure targetFigure; protected Collection<Connector> connectors = Collections.emptyList(); /** * A localized name for this tool. The presentationName is displayed by the * UndoableEdit. */ private String presentationName; /** * 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 ConnectionTool(ConnectionFigure prototype) { this(prototype, null, null); } public ConnectionTool(ConnectionFigure prototype, Map<AttributeKey, Object> attributes) { this(prototype, attributes, null); } public ConnectionTool(ConnectionFigure prototype, Map<AttributeKey, Object> attributes, String presentationName) { this.prototype = prototype; this.prototypeAttributes = attributes; if (presentationName == null) { ResourceBundleUtil labels = ResourceBundleUtil.getBundle("org.jhotdraw.draw.Labels"); presentationName = labels.getString("edit.createConnectionFigure.text"); } this.presentationName = presentationName; } public ConnectionTool(String prototypeClassName) { this(prototypeClassName, null, null); } public ConnectionTool(String prototypeClassName, Map<AttributeKey, Object> attributes, String presentationName) { try { this.prototype = (ConnectionFigure) Class.forName(prototypeClassName).newInstance(); } catch (Exception e) { InternalError error = new InternalError("Unable to create ConnectionFigure from " + prototypeClassName); error.initCause(e); throw error; } this.prototypeAttributes = attributes; if (presentationName == null) { ResourceBundleUtil labels = ResourceBundleUtil.getBundle("org.jhotdraw.draw.Labels"); presentationName = labels.getString("edit.createConnectionFigure.text"); } this.presentationName = presentationName; } public ConnectionFigure getPrototype() { return prototype; } protected int getAnchorWidth() { return ANCHOR_WIDTH; } /** * This method is called on the Figure, onto which the user wants * to start a new connection. * * @param f The ConnectionFigure. * @param startConnector The Connector of the start Figure. * @return True, if a connection can be made. */ protected boolean canConnect(ConnectionFigure f, Connector startConnector) { return f.canConnect(startConnector); } /** * This method is called on the Figure, onto which the user wants * to end a new connection. * * @param f The ConnectionFigure. * @param startConnector The Connector of the start Figure. * @param endConnector The Connector of the end Figure. * @return True, if a connection can be made. */ protected boolean canConnect(ConnectionFigure f, Connector startConnector, Connector endConnector) { return f.canConnect(startConnector, endConnector); } public void mouseMoved(MouseEvent evt) { repaintConnectors(evt); } /** * Updates the list of connectors that we draw when the user * moves or drags the mouse over a figure to which can connect. */ public void repaintConnectors(MouseEvent evt) { Rectangle2D.Double invalidArea = null; Point2D.Double targetPoint = viewToDrawing(new Point(evt.getX(), evt.getY())); Figure aFigure = getDrawing().findFigureExcept(targetPoint, createdFigure); if (aFigure != null && !aFigure.canConnect()) { aFigure = null; } if (targetFigure != aFigure) { for (Connector c : connectors) { if (invalidArea == null) { invalidArea = c.getDrawingArea(); } else { invalidArea.add(c.getDrawingArea()); } } targetFigure = aFigure; if (targetFigure != null) { connectors = targetFigure.getConnectors(getPrototype()); for (Connector c : connectors) { if (invalidArea == null) { invalidArea = c.getDrawingArea(); } else { invalidArea.add(c.getDrawingArea()); } } } } if (invalidArea != null) { getView().getComponent().repaint( getView().drawingToView(invalidArea)); } } /** * Manipulates connections in a context dependent way. If the * mouse down hits a figure start a new connection. If the mousedown * hits a connection split a segment or join two segments. */ public void mousePressed(MouseEvent evt) { super.mousePressed(evt); getView().clearSelection(); Point2D.Double startPoint = viewToDrawing(anchor); Figure startFigure = getDrawing().findFigure(startPoint); startConnector = (startFigure == null) ? null : startFigure.findConnector(startPoint, prototype); if (startConnector != null && canConnect(prototype, startConnector)) { Point2D.Double anchor = startConnector.getAnchor(); createdFigure = createFigure(); createdFigure.setStartPoint(anchor); createdFigure.setEndPoint(anchor); getDrawing().add(createdFigure); Rectangle r = new Rectangle(getView().drawingToView(anchor)); r.grow(ANCHOR_WIDTH, ANCHOR_WIDTH); fireAreaInvalidated(r); } else { startConnector = null; createdFigure = null; } endConnector = null; } /** * Adjust the created connection. */ public void mouseDragged(java.awt.event.MouseEvent e) { repaintConnectors(e); if (createdFigure != null) { createdFigure.willChange(); Point2D.Double endPoint = viewToDrawing(new Point(e.getX(), e.getY())); getView().getConstrainer().constrainPoint(endPoint); Figure endFigure = getDrawing().findFigureExcept(endPoint, createdFigure); endConnector = (endFigure == null) ? null : endFigure.findConnector(endPoint, prototype); if (endConnector != null && canConnect(createdFigure, startConnector, endConnector)) { endPoint = endConnector.getAnchor(); } Rectangle r = new Rectangle(getView().drawingToView(createdFigure.getEndPoint())); createdFigure.setEndPoint(endPoint); r.add(getView().drawingToView(endPoint)); r.grow(ANCHOR_WIDTH + 2, ANCHOR_WIDTH + 2); getView().getComponent().repaint(r); createdFigure.changed(); } } /** * Connects the figures if the mouse is released over another * figure. */ @Override public void mouseReleased(MouseEvent e) { if (createdFigure != null && startConnector != null && endConnector != null && createdFigure.canConnect(startConnector, endConnector)) { createdFigure.willChange(); createdFigure.setStartConnector(startConnector); createdFigure.setEndConnector(endConnector); createdFigure.updateConnection(); createdFigure.changed(); final Figure addedFigure = createdFigure; final Drawing addedDrawing = getDrawing(); getDrawing().fireUndoableEditHappened(new AbstractUndoableEdit() { public String getPresentationName() { return presentationName; } public void undo() throws CannotUndoException { super.undo(); addedDrawing.remove(addedFigure); } public void redo() throws CannotRedoException { super.redo(); addedDrawing.add(addedFigure); } }); targetFigure = null; Point2D.Double anchor = startConnector.getAnchor(); Rectangle r = new Rectangle(getView().drawingToView(anchor)); r.grow(ANCHOR_WIDTH, ANCHOR_WIDTH); fireAreaInvalidated(r); anchor = endConnector.getAnchor(); r = new Rectangle(getView().drawingToView(anchor)); r.grow(ANCHOR_WIDTH, ANCHOR_WIDTH); fireAreaInvalidated(r); startConnector = endConnector = null; createdFigure = null; creationFinished(createdFigure); } else { if (isToolDoneAfterCreation()) { fireToolDone(); } } } public void activate(DrawingEditor editor) { super.activate(editor); } @Override public void deactivate(DrawingEditor editor) { if (createdFigure != null) { getDrawing().remove(createdFigure); createdFigure = null; } targetFigure = null; startConnector = endConnector = null; super.deactivate(editor); } /** * Creates the ConnectionFigure. By default the figure prototype is * cloned. */ @SuppressWarnings("unchecked") protected ConnectionFigure createFigure() { ConnectionFigure f = (ConnectionFigure) prototype.clone(); getEditor().applyDefaultAttributesTo(f); if (prototypeAttributes != null) { for (Map.Entry<AttributeKey, Object> entry : prototypeAttributes.entrySet()) { entry.getKey().basicSet(f, entry.getValue()); } } return f; } @Override public void draw(Graphics2D g) { Graphics2D gg = (Graphics2D) g.create(); gg.transform(getView().getDrawingToViewTransform()); if (targetFigure != null) { for (Connector c : targetFigure.getConnectors(getPrototype())) { c.draw(gg); } } if (createdFigure != null) { createdFigure.draw(gg); Point p = getView().drawingToView(createdFigure.getStartPoint()); Ellipse2D.Double e = new Ellipse2D.Double( p.x - ANCHOR_WIDTH / 2, p.y - ANCHOR_WIDTH / 2, ANCHOR_WIDTH, ANCHOR_WIDTH); g.setColor(Color.GREEN); g.fill(e); g.setColor(Color.BLACK); g.draw(e); p = getView().drawingToView(createdFigure.getEndPoint()); e = new Ellipse2D.Double( p.x - ANCHOR_WIDTH / 2, p.y - ANCHOR_WIDTH / 2, ANCHOR_WIDTH, ANCHOR_WIDTH); g.setColor(Color.GREEN); g.fill(e); g.setColor(Color.BLACK); g.draw(e); } gg.dispose(); } /** * 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 (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; } }