/******************************************************************************* * 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 ******************************************************************************/ /* * @(#)BezierBezierLineConnection.java 1.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 java.awt.event.*; import java.awt.geom.*; import java.util.*; import javax.swing.undo.*; import java.io.*; import org.jhotdraw.geom.*; import org.jhotdraw.xml.DOMInput; import org.jhotdraw.xml.DOMOutput; /** * A LineConnection is a standard implementation of the * ConnectionFigure interface. The interface is implemented with BezierFigure. * * * * @author Werner Randelshofer * @version 1.1 2008-07-06 Create BezierOutlineHandle on mouse over. * <br>1.0.2 2007-05-02 Set connector variables directly when reading in * connectors. * <br>1.0.1 2006-02-06 Fixed redo bug. * <br>1.0 23. Januar 2006 Created. */ public class LineConnectionFigure extends LineFigure implements ConnectionFigure { private Connector startConnector; private Connector endConnector; private Liner liner; /** * Handles figure changes in the start and the * end figure. */ private ConnectionHandler connectionHandler = new ConnectionHandler(this); private static class ConnectionHandler extends FigureAdapter implements Serializable { private LineConnectionFigure owner; private ConnectionHandler(LineConnectionFigure owner) { this.owner = owner; } @Override public void figureRemoved(FigureEvent evt) { // The commented lines below must stay commented out. // This is because, we must not set our connectors to null, // in order to support reconnection using redo. /* if (evt.getFigure() == owner.getStartFigure() || evt.getFigure() == owner.getEndFigure()) { owner.setStartConnector(null); owner.setEndConnector(null); }*/ owner.fireFigureRequestRemove(); } @Override public void figureChanged(FigureEvent e) { if (e.getSource() == owner.getStartFigure() || e.getSource() == owner.getEndFigure()) { owner.willChange(); owner.updateConnection(); owner.changed(); } } }; /** Creates a new instance. */ public LineConnectionFigure() { } // DRAWING // SHAPE AND BOUNDS /** * Ensures that a connection is updated if the connection * was moved. */ @Override public void transform(AffineTransform tx) { super.transform(tx); updateConnection(); // make sure that we are still connected } // ATTRIBUTES // EDITING /** * Gets the handles of the figure. It returns the normal * PolylineHandles but adds ChangeConnectionHandles at the * start and end. */ @Override public Collection<Handle> createHandles(int detailLevel) { ArrayList<Handle> handles = new ArrayList<Handle>(getNodeCount()); switch (detailLevel) { case -1: // Mouse hover handles handles.add(new BezierOutlineHandle(this, true)); break; case 0: handles.add(new BezierOutlineHandle(this)); if (getLiner() == null) { for (int i = 1, n = getNodeCount() - 1; i < n; i++) { handles.add(new BezierNodeHandle(this, i)); } } handles.add(new ConnectionStartHandle(this)); handles.add(new ConnectionEndHandle(this)); break; } return handles; } // CONNECTING /** * Tests whether a figure can be a connection target. * ConnectionFigures cannot be connected and return false. */ @Override public boolean canConnect() { return false; } public void updateConnection() { willChange(); if (getStartConnector() != null) { Point2D.Double start = getStartConnector().findStart(this); if (start != null) { setStartPoint(start); } } if (getEndConnector() != null) { Point2D.Double end = getEndConnector().findEnd(this); if (end != null) { setEndPoint(end); } } changed(); } @Override public void validate() { super.validate(); lineout(); } public boolean canConnect(Connector start, Connector end) { return start.getOwner().canConnect() && end.getOwner().canConnect(); } public Connector getEndConnector() { return endConnector; } public Figure getEndFigure() { return (endConnector == null) ? null : endConnector.getOwner(); } public Connector getStartConnector() { return startConnector; } public Figure getStartFigure() { return (startConnector == null) ? null : startConnector.getOwner(); } public void setEndConnector(Connector newEnd) { if (newEnd != endConnector) { if (endConnector != null) { getEndFigure().removeFigureListener(connectionHandler); if (getStartFigure() != null) { if (getDrawing() != null) { handleDisconnect(getStartConnector(), getEndConnector()); } } } endConnector = newEnd; if (endConnector != null) { getEndFigure().addFigureListener(connectionHandler); if (getStartFigure() != null && getEndFigure() != null) { if (getDrawing() != null) { handleConnect(getStartConnector(), getEndConnector()); updateConnection(); } } } } } public void setStartConnector(Connector newStart) { if (newStart != startConnector) { if (startConnector != null) { getStartFigure().removeFigureListener(connectionHandler); if (getEndFigure() != null) { handleDisconnect(getStartConnector(), getEndConnector()); } } startConnector = newStart; if (startConnector != null) { getStartFigure().addFigureListener(connectionHandler); if (getStartFigure() != null && getEndFigure() != null) { handleConnect(getStartConnector(), getEndConnector()); updateConnection(); } } } } // COMPOSITE FIGURES // LAYOUT /* public Liner getBezierPathLayouter() { return (Liner) getAttribute(BEZIER_PATH_LAYOUTER); } public void setBezierPathLayouter(Liner newValue) { setAttribute(BEZIER_PATH_LAYOUTER, newValue); } /** * Lays out the connection. This is called when the connection * itself changes. By default the connection is recalculated * / public void layoutConnection() { if (getStartConnector() != null && getEndConnector() != null) { willChange(); Liner bpl = getBezierPathLayouter(); if (bpl != null) { bpl.lineout(this); } else { if (getStartConnector() != null) { Point2D.Double start = getStartConnector().findStart(this); if(start != null) { basicSetStartPoint(start); } } if (getEndConnector() != null) { Point2D.Double end = getEndConnector().findEnd(this); if(end != null) { basicSetEndPoint(end); } } } changed(); } } */ // CLONING // EVENT HANDLING /** * This method is invoked, when the Figure is being removed from a Drawing. * This method invokes handleConnect, if the Figure is connected. * * @see #handleConnect */ @Override public void addNotify(Drawing drawing) { super.addNotify(drawing); if (getStartConnector() != null && getEndConnector() != null) { handleConnect(getStartConnector(), getEndConnector()); updateConnection(); } } /** * This method is invoked, when the Figure is being removed from a Drawing. * This method invokes handleDisconnect, if the Figure is connected. * * @see #handleDisconnect */ @Override public void removeNotify(Drawing drawing) { if (getStartConnector() != null && getEndConnector() != null) { handleDisconnect(getStartConnector(), getEndConnector()); } // Note: we do not set the connectors to null here, because we // need them when we are added back to a drawing again. For example, // when an undo is performed, after the LineConnection has been // deleted. /* setStartConnector(null); setEndConnector(null); */ super.removeNotify(drawing); } /** * Handles the disconnection of a connection. * Override this method to handle this event. * <p> * Note: This method is only invoked, when the Figure is part of a * Drawing. If the Figure is removed from a Drawing, this method is * invoked on behalf of the removeNotify call to the Figure. * * @see #removeNotify */ protected void handleDisconnect(Connector start, Connector end) { } /** * Handles the connection of a connection. * Override this method to handle this event. * <p> * Note: This method is only invoked, when the Figure is part of a * Drawing. If the Figure is added to a Drawing this method is invoked * on behalf of the addNotify call to the Figure. */ protected void handleConnect(Connector start, Connector end) { } @Override public LineConnectionFigure clone() { LineConnectionFigure that = (LineConnectionFigure) super.clone(); that.connectionHandler = new ConnectionHandler(that); if (this.liner != null) { that.liner = (Liner) this.liner.clone(); } // FIXME - For safety reasons, we clone the connectors, but they would // work, if we continued to use them. Maybe we should state somewhere // whether connectors should be reusable, or not. // To work properly, that must be registered as a figure listener // to the connected figures. if (this.startConnector != null) { that.startConnector = (Connector) this.startConnector.clone(); that.getStartFigure().addFigureListener(that.connectionHandler); } if (this.endConnector != null) { that.endConnector = (Connector) this.endConnector.clone(); that.getEndFigure().addFigureListener(that.connectionHandler); } if (that.startConnector != null && that.endConnector != null) { //that.handleConnect(that.getStartConnector(), that.getEndConnector()); that.updateConnection(); } return that; } @Override public void remap(Map<Figure, Figure> oldToNew, boolean disconnectIfNotInMap) { willChange(); super.remap(oldToNew, disconnectIfNotInMap); Figure newStartFigure = null; Figure newEndFigure = null; if (getStartFigure() != null) { newStartFigure = (Figure) oldToNew.get(getStartFigure()); if (newStartFigure == null && !disconnectIfNotInMap) { newStartFigure = getStartFigure(); } } if (getEndFigure() != null) { newEndFigure = (Figure) oldToNew.get(getEndFigure()); if (newEndFigure == null && !disconnectIfNotInMap) { newEndFigure = getEndFigure(); } } if (newStartFigure != null) { setStartConnector(newStartFigure.findCompatibleConnector(getStartConnector(), true)); } else { if (disconnectIfNotInMap) { setStartConnector(null); } } if (newEndFigure != null) { setEndConnector(newEndFigure.findCompatibleConnector(getEndConnector(), false)); } else { if (disconnectIfNotInMap) { setEndConnector(null); } } updateConnection(); changed(); } public boolean canConnect(Connector start) { return start.getOwner().canConnect(); } /** * Handles a mouse click. */ @Override public boolean handleMouseClick(Point2D.Double p, MouseEvent evt, DrawingView view) { if (getLiner() == null && evt.getClickCount() == 2) { willChange(); final int index = splitSegment(p, (float) (5f / view.getScaleFactor())); if (index != -1) { final BezierPath.Node newNode = getNode(index); fireUndoableEditHappened(new AbstractUndoableEdit() { @Override public void redo() throws CannotRedoException { super.redo(); willChange(); addNode(index, newNode); changed(); } @Override public void undo() throws CannotUndoException { super.undo(); willChange(); removeNode(index); changed(); } }); changed(); return true; } } return false; } // PERSISTENCE @Override protected void readPoints(DOMInput in) throws IOException { super.readPoints(in); in.openElement("startConnector"); setStartConnector((Connector) in.readObject()); in.closeElement(); in.openElement("endConnector"); setEndConnector((Connector) in.readObject()); in.closeElement(); } @Override public void read(DOMInput in) throws IOException { readAttributes(in); readLiner(in); // Note: Points must be read after Liner, because Liner influences // the location of the points. readPoints(in); } protected void readLiner(DOMInput in) throws IOException { if (in.getElementCount("liner") > 0) { in.openElement("liner"); liner = (Liner) in.readObject(); in.closeElement(); } else { liner = null; } } @Override public void write(DOMOutput out) throws IOException { writePoints(out); writeAttributes(out); writeLiner(out); } protected void writeLiner(DOMOutput out) throws IOException { if (liner != null) { out.openElement("liner"); out.writeObject(liner); out.closeElement(); } } @Override protected void writePoints(DOMOutput out) throws IOException { super.writePoints(out); out.openElement("startConnector"); out.writeObject(getStartConnector()); out.closeElement(); out.openElement("endConnector"); out.writeObject(getEndConnector()); out.closeElement(); } public void setLiner(Liner newValue) { this.liner = newValue; } @Override public void setNode(int index, BezierPath.Node p) { if (index != 0 && index != getNodeCount() - 1) { if (getStartConnector() != null) { Point2D.Double start = getStartConnector().findStart(this); if (start != null) { setStartPoint(start); } } if (getEndConnector() != null) { Point2D.Double end = getEndConnector().findEnd(this); if (end != null) { setEndPoint(end); } } } super.setNode(index, p); } /* public void basicSetPoint(int index, Point2D.Double p) { if (index != 0 && index != getNodeCount() - 1) { if (getStartConnector() != null) { Point2D.Double start = getStartConnector().findStart(this); if(start != null) { basicSetStartPoint(start); } } if (getEndConnector() != null) { Point2D.Double end = getEndConnector().findEnd(this); if(end != null) { basicSetEndPoint(end); } } } super.basicSetPoint(index, p); } */ public void lineout() { if (liner != null) { liner.lineout(this); } } /** * FIXME - Liner must work with API of LineConnection! */ @Override public BezierPath getBezierPath() { return path; } public Liner getLiner() { return liner; } @Override public void setStartPoint(Point2D.Double p) { setPoint(0, p); } @Override public void setPoint(int index, Point2D.Double p) { setPoint(index, 0, p); } @Override public void setEndPoint(Point2D.Double p) { setPoint(getNodeCount() - 1, p); } public void reverseConnection() { if (startConnector != null && endConnector != null) { handleDisconnect(startConnector, endConnector); Connector tmpC = startConnector; startConnector = endConnector; endConnector = tmpC; Point2D.Double tmpP = getStartPoint(); setStartPoint(getEndPoint()); setEndPoint(tmpP); handleConnect(startConnector, endConnector); updateConnection(); } } }