/* * @(#)LabeledLineConnection.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; import javax.annotation.Nullable; import org.jhotdraw.draw.layouter.Layouter; import org.jhotdraw.draw.event.FigureAdapter; import org.jhotdraw.draw.event.FigureEvent; import org.jhotdraw.draw.event.CompositeFigureEvent; import org.jhotdraw.draw.event.CompositeFigureListener; import org.jhotdraw.util.*; import java.util.*; import java.awt.*; import java.awt.geom.*; import java.io.Serializable; import javax.swing.event.*; /** * 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 $Id$ */ public class LabeledLineConnectionFigure extends LineConnectionFigure implements CompositeFigure { private static final long serialVersionUID = 1L; private Layouter layouter; private ArrayList<Figure> children = new ArrayList<>(); @Nullable 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, Serializable { private static final long serialVersionUID = 1L; 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()); } } @Override 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. */ @Override public void draw(Graphics2D g) { super.draw(g); for (Figure child : children) { if (child.isVisible()) { child.draw(g); } } } // SHAPE AND BOUNDS /** * Transforms the figure. */ @Override public void transform(AffineTransform tx) { super.transform(tx); for (Figure f : children) { f.transform(tx); } invalidate(); } @Override 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(); } @Override 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. */ @Override public <T> void set(AttributeKey<T> key, T newValue) { super.set(key, newValue); if (isAttributeEnabled(key)) { if (children != null) { for (Figure child : children) { child.set(key, newValue); } } } } // EDITING @Override 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 @Override public void updateConnection() { super.updateConnection(); layout(); } // COMPOSITE FIGURES @Override public java.util.List<Figure> getChildren() { return Collections.unmodifiableList(children); } @Override public int getChildCount() { return children.size(); } @Override 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<>() : new ReversedList<>(children); } @Override public boolean add(Figure figure) { basicAdd(figure); if (getDrawing() != null) { figure.addNotify(getDrawing()); } return true; } @Override public void add(int index, Figure figure) { basicAdd(index, figure); if (getDrawing() != null) { figure.addNotify(getDrawing()); } } @Override public void basicAdd(Figure figure) { basicAdd(children.size(), figure); } @Override public void basicAdd(int index, Figure figure) { children.add(index, figure); figure.addFigureListener(childHandler); invalidate(); } @Override 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; } } @Override public Figure removeChild(int index) { willChange(); Figure figure = basicRemoveChild(index); if (getDrawing() != null) { figure.removeNotify(getDrawing()); } changed(); return figure; } @Override public int basicRemove(final Figure figure) { int index = children.indexOf(figure); if (index != -1) { basicRemoveChild(index); } return index; } @Override public Figure basicRemoveChild(int index) { Figure figure = children.remove(index); figure.removeFigureListener(childHandler); return figure; } @Override public void removeAllChildren() { willChange(); while (children.size() > 0) { Figure figure = basicRemoveChild(children.size() - 1); if (getDrawing() != null) { figure.removeNotify(getDrawing()); } } changed(); } @Override 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 */ @Override public Layouter getLayouter() { return layouter; } @Override 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. */ @Override public void layout() { if (getLayouter() != null) { Rectangle2D.Double bounds = getBounds(); Point2D.Double p = new Point2D.Double(bounds.x, bounds.y); getLayouter().layout(this, p, p); invalidate(); } } // EVENT HANDLING @Override public void invalidate() { super.invalidate(); cachedDrawingArea = null; } @Override public void validate() { super.validate(); layout(); } @Override public void addNotify(Drawing drawing) { for (Figure child : new LinkedList<>(children)) { child.addNotify(drawing); } super.addNotify(drawing); } @Override public void removeNotify(Drawing drawing) { for (Figure child : new LinkedList<>(children)) { child.removeNotify(drawing); } super.removeNotify(drawing); } @Override public void removeCompositeFigureListener(CompositeFigureListener listener) { listenerList.remove(CompositeFigureListener.class, listener); } @Override 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 @Override public LabeledLineConnectionFigure clone() { LabeledLineConnectionFigure that = (LabeledLineConnectionFigure) super.clone(); that.childHandler = new ChildHandler(that); that.children = new ArrayList<>(); for (Figure thisChild : this.children) { Figure thatChild = thisChild.clone(); that.children.add(thatChild); thatChild.addFigureListener(that.childHandler); } return that; } @Override public void remap(Map<Figure,Figure> oldToNew, boolean disconnectIfNotInMap) { super.remap(oldToNew, disconnectIfNotInMap); for (Figure child : children) { child.remap(oldToNew, disconnectIfNotInMap); } } @Override public boolean contains(Figure f) { return children.contains(f); } @Override public int indexOf(Figure child) { return children.indexOf(child); } }