/******************************************************************************* * 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 ******************************************************************************/ /* * @(#)LabeledLineConnection.java 1.1 2006-02-14 * * Copyright (c) 1996-2006 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 org.jhotdraw.util.*; import java.util.*; import java.awt.*; import java.awt.geom.*; import javax.swing.event.*; import javax.swing.undo.*; /** * A LineConnection with labels. * <p> * Usage: * <pre> * LineConnectionFigure lcf = new LineConnectionFigure(); * lcf.setLayouter(new LocatorLayouter()); * TextFigure label = new TextFigure(); * label.setText("Hello"); * LocatorLayouter.LAYOUT_LOCATOR.set(label, new BezierLabelLocator(0, -Math.PI / 4, 8)); * lcf.add(label); * </pre> * * @author Werner Randelshofer * @version 1.1 2006-02-14 Do not include labels in logical bounds. * <br>1.0 23. Januar 2006 Created. */ public class LabeledLineConnectionFigure extends LineConnectionFigure implements CompositeFigure { private Layouter layouter; private ArrayList<Figure> children = new ArrayList<Figure>(); private transient Rectangle2D.Double cachedDrawingArea; /** * Handles figure changes in the children. */ private ChildHandler childHandler = new ChildHandler(this); private static class ChildHandler extends FigureAdapter implements UndoableEditListener { private LabeledLineConnectionFigure owner; private ChildHandler(LabeledLineConnectionFigure owner) { this.owner = owner; } @Override public void figureRequestRemove(FigureEvent e) { owner.remove(e.getFigure()); } @Override public void figureChanged(FigureEvent e) { if (! owner.isChanging()) { owner.willChange(); owner.fireFigureChanged(e); owner.changed(); } } @Override public void areaInvalidated(FigureEvent e) { if (! owner.isChanging()) { owner.fireAreaInvalidated(e.getInvalidatedArea()); } } public void undoableEditHappened(UndoableEditEvent e) { owner.fireUndoableEditHappened(e.getEdit()); } }; /** Creates a new instance. */ public LabeledLineConnectionFigure() { } // DRAWING /** * Draw the figure. This method is delegated to the encapsulated presentation figure. */ public void draw(Graphics2D g) { super.draw(g); for (Figure child : children) { if (child.isVisible()) { child.draw(g); } } } // SHAPE AND BOUNDS /** * Transforms the figure. */ public void transform(AffineTransform tx) { super.transform(tx); for (Figure f : children) { f.transform(tx); } invalidate(); } public Rectangle2D.Double getDrawingArea() { if (cachedDrawingArea == null) { cachedDrawingArea = super.getDrawingArea(); for (Figure child : getChildrenFrontToBack()) { if (child.isVisible()) { Rectangle2D.Double childBounds = child.getDrawingArea(); if (! childBounds.isEmpty()) { cachedDrawingArea.add(childBounds); } } } } return (Rectangle2D.Double) cachedDrawingArea.clone(); } public boolean contains(Point2D.Double p) { if (getDrawingArea().contains(p)) { for (Figure child : getChildrenFrontToBack()) { if (child.isVisible() && child.contains(p)) return true; } return super.contains(p); } return false; } // ATTRIBUTES /** * Sets an attribute of the figure. * AttributeKey name and semantics are defined by the class implementing * the figure interface. */ public <T> void setAttribute(AttributeKey<T> key, T newValue) { super.setAttribute(key, newValue); if (isAttributeEnabled(key)) { if (children != null) { for (Figure child : children) { key.basicSet(child, newValue); } } } } // EDITING public Figure findFigureInside(Point2D.Double p) { if (getDrawingArea().contains(p)) { Figure found = null; for (Figure child : getChildrenFrontToBack()) { if (child.isVisible()) { found = child.findFigureInside(p); if (found != null) { return found; } } } } return null; } // CONNECTING public void updateConnection() { super.updateConnection(); layout(); } // COMPOSITE FIGURES public java.util.List<Figure> getChildren() { return Collections.unmodifiableList(children); } public int getChildCount() { return children.size(); } public Figure getChild(int index) { return children.get(index); } /** * Returns an iterator to iterate in * Z-order front to back over the children. */ public java.util.List<Figure> getChildrenFrontToBack() { return children == null ? new LinkedList<Figure>() : new ReversedList<Figure>(children); } public boolean add(Figure figure) { basicAdd(figure); if (getDrawing() != null) { figure.addNotify(getDrawing()); } return true; } public void add(int index, Figure figure) { basicAdd(index, figure); if (getDrawing() != null) { figure.addNotify(getDrawing()); } } public void basicAdd(Figure figure) { basicAdd(children.size(), figure); } public void basicAdd(int index, Figure figure) { children.add(index, figure); figure.addFigureListener(childHandler); invalidate(); } public boolean remove(final Figure figure) { int index = children.indexOf(figure); if (index == -1) { return false; } else { willChange(); basicRemoveChild(index); if (getDrawing() != null) { figure.removeNotify(getDrawing()); } changed(); return true; } } public Figure removeChild(int index) { willChange(); Figure figure = basicRemoveChild(index); if (getDrawing() != null) { figure.removeNotify(getDrawing()); } changed(); return figure; } public int basicRemove(final Figure figure) { int index = children.indexOf(figure); if (index != -1) { basicRemoveChild(index); } return index; } public Figure basicRemoveChild(int index) { Figure figure = children.remove(index); figure.removeFigureListener(childHandler); return figure; } public void removeAllChildren() { willChange(); while (children.size() > 0) { Figure figure = basicRemoveChild(children.size() - 1); if (getDrawing() != null) { figure.removeNotify(getDrawing()); } } changed(); } public void basicRemoveAllChildren() { while (children.size() > 0) { basicRemoveChild(children.size() - 1); } } // LAYOUT /** * Get a Layouter object which encapsulated a layout * algorithm for this figure. Typically, a Layouter * accesses the child components of this figure and arranges * their graphical presentation. * * * @return layout strategy used by this figure */ public Layouter getLayouter() { return layouter; } public void setLayouter(Layouter newLayouter) { this.layouter = newLayouter; } /** * A layout algorithm is used to define how the child components * should be laid out in relation to each other. The task for * layouting the child components for presentation is delegated * to a Layouter which can be plugged in at runtime. */ public void layout() { if (getLayouter() != null) { Rectangle2D.Double bounds = getBounds(); Point2D.Double p = new Point2D.Double(bounds.x, bounds.y); Rectangle2D.Double r = getLayouter().layout( this, p, p ); invalidate(); } } // EVENT HANDLING public void invalidate() { super.invalidate(); cachedDrawingArea = null; } public void validate() { super.validate(); layout(); } public void addNotify(Drawing drawing) { for (Figure child : new LinkedList<Figure>(children)) { child.addNotify(drawing); } super.addNotify(drawing); } public void removeNotify(Drawing drawing) { for (Figure child : new LinkedList<Figure>(children)) { child.removeNotify(drawing); } super.removeNotify(drawing); } public void removeCompositeFigureListener(CompositeFigureListener listener) { listenerList.remove(CompositeFigureListener.class, listener); } public void addCompositeFigureListener(CompositeFigureListener listener) { listenerList.add(CompositeFigureListener.class, listener); } /** * Notify all listenerList that have registered interest for * notification on this event type. */ protected void fireFigureAdded(Figure f, int zIndex) { CompositeFigureEvent event = null; // Notify all listeners that have registered interest for // Guaranteed to return a non-null array Object[] listeners = listenerList.getListenerList(); // Process the listeners last to first, notifying // those that are interested in this event for (int i = listeners.length-2; i>=0; i-=2) { if (listeners[i] == CompositeFigureListener.class) { // Lazily create the event: if (event == null) event = new CompositeFigureEvent(this, f, f.getDrawingArea(), zIndex); ((CompositeFigureListener)listeners[i+1]).figureAdded(event); } } } /** * Notify all listenerList that have registered interest for * notification on this event type. */ protected void fireFigureRemoved(Figure f, int zIndex) { CompositeFigureEvent event = null; // Notify all listeners that have registered interest for // Guaranteed to return a non-null array Object[] listeners = listenerList.getListenerList(); // Process the listeners last to first, notifying // those that are interested in this event for (int i = listeners.length-2; i>=0; i-=2) { if (listeners[i] == CompositeFigureListener.class) { // Lazily create the event: if (event == null) event = new CompositeFigureEvent(this, f, f.getDrawingArea(), zIndex); ((CompositeFigureListener)listeners[i+1]).figureRemoved(event); } } } // CLONING public LabeledLineConnectionFigure clone() { LabeledLineConnectionFigure that = (LabeledLineConnectionFigure) super.clone(); that.childHandler = new ChildHandler(that); that.children = new ArrayList<Figure>(); for (Figure thisChild : this.children) { Figure thatChild = (Figure) thisChild.clone(); that.children.add(thatChild); thatChild.addFigureListener(that.childHandler); } return that; } public void remap(Map<Figure,Figure> oldToNew, boolean disconnectIfNotInMap) { super.remap(oldToNew, disconnectIfNotInMap); for (Figure child : children) { child.remap(oldToNew, disconnectIfNotInMap); } } public boolean contains(Figure f) { return children.contains(f); } public int indexOf(Figure child) { return children.indexOf(child); } }