// Copyright (c) 2006 - 2008, Markus Strauch. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // // * Redistributions of source code must retain the above copyright notice, // this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above copyright notice, // this list of conditions and the following disclaimer in the documentation // and/or other materials provided with the distribution. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF // THE POSSIBILITY OF SUCH DAMAGE. package net.sf.sdedit.diagram; import java.awt.Color; import java.awt.Font; import java.io.IOException; import java.io.OutputStream; import java.lang.reflect.Field; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.NoSuchElementException; import java.util.Set; import net.sf.sdedit.drawable.Drawable; import net.sf.sdedit.drawable.ExtensibleDrawable; import net.sf.sdedit.drawable.Fragment; import net.sf.sdedit.drawable.Line; import net.sf.sdedit.drawable.SequenceElement; import net.sf.sdedit.util.Direction; /** * A PaintDevice is an intelligent container of {@linkplain Drawable} objects, * computing their layout and displaying them in a way that is to be made * concrete by a subclass. * * @author Markus Strauch */ public abstract class PaintDevice implements Iterable<Drawable> { private static Map<String,Color> colorMap; static { colorMap = new HashMap<String,Color>(); Field [] fields = Color.class.getFields(); for (int i = 0; i < fields.length; i++) { if (fields[i].getName().equals(fields[i].getName().toUpperCase()) && fields [i].getType() == Color.class) { try { colorMap.put(fields[i].getName(), (Color) fields[i].get(null)); } catch (IllegalAccessException e) { e.printStackTrace(); } } } } private int height; /** * The i-th entry of this list is the set of sequence elements that appear * in the space between the i-th and the (i+1)-th lifeline, where n+1 * corresponds to rightBound */ private final ArrayList<Set<SequenceElement>> leftOf; private final List<Drawable> other; private Diagram diagram; private final Line rightBound; private Font plainFont; private Font boldFont; protected PaintDevice() { height = 0; leftOf = new ArrayList<Set<SequenceElement>>(); other = new LinkedList<Drawable>(); rightBound = new Line(1, null); } public void setDiagram (Diagram diagram) { this.diagram = diagram; plainFont = diagram.getConfiguration().getFont(); boldFont = new Font(plainFont.getName(), Font.BOLD, plainFont.getSize() + 1); } public Color getColor (String name) { return colorMap.get(name.toUpperCase()); } /** * This method is called by the diagram when all objects/lifelines are * known. */ public void reinitialize() { leftOf.clear(); other.clear(); rightBound.setLeft(0); height = 0; for (int i = 0; i < diagram.getNumberOfLifelines(); i++) { leftOf.add(new HashSet<SequenceElement>()); } } public Font getFont(boolean bold) { return bold ? boldFont : plainFont; } public Line getRightBound() { return rightBound; } public void addOtherDrawable(Drawable drawable) { other.add(drawable); if (drawable.getRight() > rightBound.getLeft()) { rightBound.setLeft(drawable.getRight()); } if (drawable.getBottom() > height) { height = drawable.getBottom(); } } public void addSequenceElement(SequenceElement elem) { int index; ExtensibleDrawable left, right; if (elem.getAlign() == Direction.RIGHT) { index = elem.getLeftEndpoint().getLifeline().getPosition(); left = elem.getLeftEndpoint().getLifeline().getRightmost() .getView(); if (index == diagram.getNumberOfLifelines() - 1) { right = rightBound; } else { right = diagram.getLifelineAt(index + 1).getLeftmost() .getView(); } } else { // Direction.LEFT index = elem.getRightEndpoint().getLifeline().getPosition() - 1; right = elem.getRightEndpoint().getLifeline().getLeftmost() .getView(); left = diagram.getLifelineAt(index).getRightmost().getView(); } leftOf.get(index).add(elem); elem.setLeftLimit(left); elem.setRightLimit(right); diagram.getFragmentManager().addSequenceElement(elem); } public boolean isEmpty() { return diagram == null || diagram.getNumberOfLifelines() == 0; } public void computeAxes(int leftAxis) { int n = diagram.getNumberOfLifelines(); int axis = leftAxis; int mainWidth = diagram.getConfiguration().getMainLifelineWidth(); int subWidth = diagram.getConfiguration().getSubLifelineWidth(); for (int i = 0; i < n; i++) { if (i > 0) { axis += diagram.getConfiguration().getGlue(); } for (ExtensibleDrawable view : diagram.getLifelineAt(i) .getAllViews()) { if (view instanceof Line) { view.setLeft(axis + mainWidth / 2); } else { switch (view.getLifeline().getDirection()) { case CENTER: view.setLeft(axis); break; case LEFT: view.setLeft(axis - view.getLifeline().getSideLevel() * subWidth); break; case RIGHT: view.setLeft(axis + mainWidth + (view.getLifeline().getSideLevel() - 1) * subWidth); } } } Lifeline lifeline = diagram.getLifelineAt(i); Drawable head = lifeline.getHead(); int offset = mainWidth / 2; // int offset = lifeline.getView().getWidth() / 2; head.setLeft(axis - head.getWidth() / 2 + offset); if (lifeline.getCross() != null) { lifeline.getCross().setLeft( axis - diagram.getConfiguration().getDestructorWidth() / 2 + mainWidth / 2); } if (i < diagram.getNumberOfLifelines() - 1) { axis += head.getWidth() / 2 + diagram.getLifelineAt(i + 1).getHead().getWidth() / 2; } else { axis += head.getWidth() / 2 + mainWidth / 2; } // iterate over the sequence elements with their contents (text) // to the left of the (i+1)-th lifeline for (SequenceElement arrow : leftOf.get(i)) { int left = arrow.getLeftLimit().getRight(); int level; if (arrow.getRightLimit() == rightBound) { level = 0; } else { level = arrow.getRightLimit().getLifeline().getSideLevel(); } axis = Math.max(axis, left + arrow.getSpace() + arrow.getWidth() + level * subWidth); } } rightBound.setLeft(axis); } public abstract int getTextWidth(String text, boolean bold); public int getTextWidth(String text) { return getTextWidth(text, false); } public abstract int getTextHeight(boolean bold); public int getTextHeight() { return getTextHeight(false); } public int getWidth() { return diagram == null ? 0 : rightBound.getLeft() + 6 + diagram.getConfiguration().getRightMargin(); } public int getHeight() { return height; } public void clear() { // as a result of this, iterator() will return an empty iterator diagram = null; } /** * Computes the width and height of this PaintDevice (this is necessary * before a frame and a descriptive text can be set). */ public void computeBounds() { for (int i = 0; i < diagram.getNumberOfLifelines(); i++) { for (Drawable view : diagram.getLifelineAt(i).getAllViews()) { processDrawable(view); } } for (int i = 0; i < leftOf.size(); i++) { for (SequenceElement arrow : leftOf.get(i)) { processDrawable(arrow); } } for (Drawable d : other) { processDrawable(d); } height += diagram.getConfiguration().getLowerMargin(); } /** * This method is called once when no {@linkplain Drawable} object will be * added anymore. Its default implementation is empty. */ public void close() { /* empty */ } private void processDrawable(Drawable drawable) { height = Math.max(height, drawable.getTop() + drawable.getHeight()); drawable.computeLayoutInformation(); if (drawable instanceof Fragment) { int r = rightBound.getLeft(); rightBound.setLeft(Math.max(r, drawable.getLeft() + drawable.getWidth())); } } public void announce (int height) { /* empty */ } public Diagram getDiagram() { return diagram; } public void writeToStream(OutputStream stream) throws IOException { throw new UnsupportedOperationException("writeToStream not supported"); } /** * Returns an iterator over the <i>visible</i> drawable elements that are * displayed by this <tt>PanelPaintDevice</tt>. * * @return an iterator over the <i>visible</i> drawable elements that are * displayed by this <tt>PanelPaintDevice</tt> */ public Iterator<Drawable> iterator() { return new Iter(); } private class Iter implements Iterator<Drawable> { // counts through indices of lifelines and sets in the leftOf list private int counter; // the current iterator, suitable for the counter value private Iterator<? extends Drawable> iterator; private Drawable next; private final Diagram diagram; private void nextIterator() { if (diagram == null || !diagram.isFinished()) { iterator = null; return; } counter++; if (counter < diagram.getNumberOfLifelines()) { iterator = diagram.getLifelineAt(counter).getAllViews() .iterator(); } else if (counter < 2 * diagram.getNumberOfLifelines()) { iterator = leftOf.get(counter - diagram.getNumberOfLifelines()) .iterator(); } else if (counter == 2 * diagram.getNumberOfLifelines()) { iterator = other.iterator(); } else { iterator = null; } } Iter() { this.diagram = PaintDevice.this.diagram; counter = -1; nextIterator(); } public boolean hasNext() { if (diagram != null && diagram != PaintDevice.this.diagram) { return false; } if (next == null) { findNext(); } return next != null; } /** * Sets <tt>next</tt> to the next element, if there is one. As a * precondition, next must be null. */ private void findNext() { while (iterator != null) { while (iterator.hasNext()) { next = iterator.next(); if (next.isVisible()) { return; } } nextIterator(); } if (next != null && !next.isVisible()) { next = null; } } public Drawable next() { if (next == null) { findNext(); } if (next != null) { Drawable ret = next; next = null; return ret; } throw new NoSuchElementException(); } public void remove() { throw new UnsupportedOperationException(); } } }