/* $Id: FigCompartmentBox.java 17926 2010-01-28 10:36:20Z bobtarling $ ******************************************************************************* * Copyright (c) 2009-2010 Contributors - see below * 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: * Bob Tarling ******************************************************************************* * * Some portions of this file was previously release using the BSD License: */ // $Id: FigCompartmentBox.java 17926 2010-01-28 10:36:20Z bobtarling $ // Copyright (c) 1996-2009 The Regents of the University of California. All // Rights Reserved. Permission to use, copy, modify, and distribute this // software and its documentation without fee, and without a written // agreement is hereby granted, provided that the above copyright notice // and this paragraph appear in all copies. This software program and // documentation are copyrighted by The Regents of the University of // California. The software program and documentation are supplied "AS // IS", without any accompanying services from The Regents. The Regents // does not warrant that the operation of the program will be // uninterrupted or error-free. The end-user understands that the program // was developed for research purposes and is advised not to rely // exclusively on the program for any reason. IN NO EVENT SHALL THE // UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT, // SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING LOST PROFITS, // ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF // THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF // SUCH DAMAGE. THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY // WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF // MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE // PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF // CALIFORNIA HAS NO OBLIGATIONS TO PROVIDE MAINTENANCE, SUPPORT, // UPDATES, ENHANCEMENTS, OR MODIFICATIONS. package org.argouml.uml.diagram.ui; import java.awt.Color; import java.awt.Dimension; import java.awt.Rectangle; import java.awt.event.InputEvent; import java.awt.event.MouseEvent; import java.beans.PropertyChangeEvent; import java.util.ArrayList; import java.util.List; import javax.swing.SwingUtilities; import org.apache.log4j.Logger; import org.argouml.model.AssociationChangeEvent; import org.argouml.model.AttributeChangeEvent; import org.argouml.model.InvalidElementException; import org.argouml.ui.targetmanager.TargetManager; import org.argouml.uml.diagram.DiagramSettings; import org.tigris.gef.base.Editor; import org.tigris.gef.base.Globals; import org.tigris.gef.base.Selection; import org.tigris.gef.base.SelectionButtons; import org.tigris.gef.presentation.Fig; import org.tigris.gef.presentation.FigGroup; import org.tigris.gef.presentation.FigRect; /** * Class to display graphics for a node with compartments in a diagram. * <p> * * The leaf descendants of this class shall add the compartments in top to * bottom order. * <p> * * It deals with highlighting editable compartments. * <p> * * All descendants of this class have the bigPort filled with the main fig fill * color, with line border. * <p> * * The name, keyword and stereotype are shown in transparent figs without * border, but their size is reduced so that they fit within the border of the * borderFig. */ public abstract class FigCompartmentBox extends FigNodeModelElement { private static final Logger LOG = Logger.getLogger( FigCompartmentBox.class); /** * Default bounds for a compartment. */ protected static final Rectangle DEFAULT_COMPARTMENT_BOUNDS = new Rectangle( X0, Y0 + 20 /* 20 = height of name fig ?*/, WIDTH, ROWHEIGHT + 2 /* 2*LINE_WIDTH? or extra padding? */ ); /** * Text highlighted by mouse actions on the diagram.<p> */ private static CompartmentFigText highlightedFigText = null; private List<FigCompartment> compartments = new ArrayList<FigCompartment>(); /** * Buffer the calculated dimensions of the compartments for later use. */ protected Dimension containerBox; /** * Initialization shared by all constructors. */ private void initialize() { // Set properties of the stereotype box. getStereotypeFig().setHeight(STEREOHEIGHT + LINE_WIDTH); /* * The nameFig is transparent, since this is a box and the fill color is * drawn by the bigPort. */ getNameFig().setFillColor(null); } /** * Overrule this if a rectangle is not usable. * * @return the Fig to be used as bigPort */ protected Fig createBigPortFig() { Fig b = new FigRect(X0, Y0, 0, 0, LINE_COLOR, FILL_COLOR); return b; } /** * Construct a Fig with owner, bounds, and settings. * * @param owner the model element that owns this fig * @param bounds the rectangle defining the bounds * @param settings the rendering settings */ public FigCompartmentBox(Object owner, Rectangle bounds, DiagramSettings settings) { super(owner, bounds, settings); initialize(); } /** * Get the compartment that lists model elements of the given type. * This can also be used to test if a Fig supports a particular type * of compartment by checking for a null return. * @param metaType the model element type for which the compartment is * required. * @return a compartment fig or null if the compartment is not contained. */ public FigCompartment getCompartment(Object metaType) { for (FigCompartment f : compartments) { if (f.getCompartmentType() == metaType) { return f; } } return null; } /** * Return true of a compartment exists and is visible * @param metaType the model element type for which the compartment is * required. * @return true if the compartment exists and is visible */ public boolean isCompartmentVisible(Object metaType) { FigCompartment f = getCompartment(metaType); if (f == null) { return false; } return f.isVisible(); } protected List<FigCompartment> getCompartments() { return compartments; } @Override public void addFig(Fig fig) { if (fig instanceof FigCompartment) { addCompartment((FigCompartment) fig); } super.addFig(fig); } private void addCompartment(FigCompartment c) { assert !compartments.contains(c); addFig(c.getSeparatorFig()); compartments.add(c); } protected int getVisibleCompartmentCount() { int result = 0; for (int i = 0; i < compartments.size(); i++) { result += compartments.get(i).isVisible() ? 1 : 0; } return result; } @Override public Dimension getMinimumSize() { // Use "aSize" to build up the minimum size. Start with the size of the // name compartment and build up. Dimension aSize = getNameFig().getMinimumSize(); /* * Only take into account the stereotype width, not the height, since * the height is included in the name fig: */ aSize = ArgoFigUtil.addChildWidth(aSize, getStereotypeFig()); /* Add the height of all the compartments (if there are any), * and check their minimum width: */ for (FigCompartment c : compartments) { aSize = ArgoFigUtil.addChildDimensions(aSize, c); } /* We want to maintain a minimum width for the fig. Also, add the border * dimensions to the minimum space required for its contents: */ aSize.width = Math.max(WIDTH, aSize.width); aSize = addCompartmentBoxSurroundings(aSize); return aSize; } /** * Increase the size of the given box with the area around the * compartments. * * @param box the minimum box size needed for the compartments * @return the dimensions of the complete fig */ protected Dimension addCompartmentBoxSurroundings(Dimension box) { containerBox = new Dimension(box); box.width += 2 * getLineWidth(); box.height += 2 * getLineWidth(); return box; } /** * Given the outside dimensions and location of the Fig, calculate * the position and size of the box for the compartments. * The compartments are located inside the complete fig. For a * rectangle (i.e. the default implementation), only the line-width * of the outside box needs to be added. * Other Figs may have other shapes, e.g. a Use Case has the * box located inside an ellipse. So, they need to overrule this method. * * @param x outside top left * @param y outside top left * @param w outside dimension, including line-width * @param h outside dimension, including line-width * @return the location and area to be used by the compartments */ protected Rectangle calculateCompartmentBoxDimensions( final int x, final int y, final int w, final int h) { return new Rectangle( x + getLineWidth(), y + getLineWidth(), w - 2 * getLineWidth(), h - 2 * getLineWidth()); } /** * Sets the bounds, but the size will be at least the one returned by * {@link #getMinimumSize()}, unless checking of size is disabled. * <p> * * If the required height is bigger, then the additional height is equally * distributed among all compartments, such that the accumulated height of * all visible figs equals the demanded height. * * @param x Desired X coordinate of upper left corner * * @param y Desired Y coordinate of upper left corner * * @param width Desired width of the Fig * * @param height Desired height of the Fig * * @see org.tigris.gef.presentation.Fig#setBoundsImpl(int, int, int, int) */ @Override protected void setStandardBounds(final int x, final int y, final int w, final int h) { // Save our old boundaries so it can be used in property message later Rectangle oldBounds = getBounds(); // Make sure we don't try to set things smaller than the minimum Dimension minimumSize = getMinimumSize(); int newW = Math.max(w, minimumSize.width); int newH = Math.max(h, minimumSize.height); /* The box for the compartments is somewhere * inside the outside bounds: */ Rectangle box = calculateCompartmentBoxDimensions( x, y, newW, newH); int currentHeight = 0; if (getStereotypeFig().isVisible()) { int stereotypeHeight = getStereotypeFig().getMinimumSize().height; getNameFig().setTopMargin(stereotypeHeight); getStereotypeFig().setBounds( box.x, box.y, box.width, stereotypeHeight); } else { getNameFig().setTopMargin(0); } /* Now the new nameFig height will include the stereotype height: */ Dimension nameMin = getNameFig().getMinimumSize(); int minNameHeight = Math.max(nameMin.height, NAME_FIG_HEIGHT); getNameFig().setBounds(box.x, box.y, box.width, minNameHeight); /* The new height can not be less than the name height: */ /* * TODO: Is this needed/correct? * For when all compartments are hidden? */ newH = Math.max(minNameHeight + 2 * getLineWidth(), newH); currentHeight += minNameHeight; int requestedHeight = box.height - currentHeight; int neededHeight = 0; /* Calculate the minimum needed height for all the compartments:*/ for (FigCompartment c : compartments) { if (c.isVisible()) { neededHeight += c.getMinimumSize().height; } } for (FigCompartment c : compartments) { if (c.isVisible()) { int compartmentHeight = c.getMinimumSize().height; if (requestedHeight > neededHeight) { /* * Distribute the extra height over the visible * compartments: */ compartmentHeight += (requestedHeight - neededHeight) / getVisibleCompartmentCount(); } setCompartmentBounds(c, new Rectangle( box.x, box.y + currentHeight, box.width, compartmentHeight), new Rectangle(x, y, newW, newH)); currentHeight += compartmentHeight; } } if (requestedHeight < neededHeight) { /* Increase the height of the fig: */ newH += neededHeight - requestedHeight; } /* * If requested height equals needed height then do nothing; it fits * exactly. */ /* Finally set the bounds of the big box and the border fig: */ getBigPort().setBounds(x, y, newW, newH); // Now force calculation of the bounds of the figure, update the edges // and trigger anyone who's listening to see if the "bounds" property // has changed. calcBounds(); updateEdges(); LOG.debug("Bounds change : old - " + oldBounds + ", new - " + getBounds()); firePropChange("bounds", oldBounds, getBounds()); } /** * Set the bounds of the compartment. * * @param c the compartment * @param cb the new compartment bounds * @param ob the new outside fig bounds */ protected void setCompartmentBounds(FigCompartment c, Rectangle cb, Rectangle ob) { Rectangle r = new Rectangle(); r.y = cb.y; r.height = getLineWidth(); r.width = ob.width; r.x = ob.x; c.setExternalSeparatorFigBounds(r); c.setBounds(cb.x, cb.y + 1, cb.width, cb.height - 1); } /* * @see org.tigris.gef.presentation.Fig#translate(int, int) */ @Override public void translate(int dx, int dy) { super.translate(dx, dy); Editor ce = Globals.curEditor(); if (ce != null) { Selection sel = ce.getSelectionManager().findSelectionFor(this); if (sel instanceof SelectionButtons) { ((SelectionButtons) sel).hideButtons(); } } } @Override protected void modelChanged(PropertyChangeEvent mee) { super.modelChanged(mee); if (mee instanceof AssociationChangeEvent || mee instanceof AttributeChangeEvent) { Runnable doWorkRunnable = new Runnable() { public void run() { try { renderingChanged(); updateListeners(getOwner(), getOwner()); } catch (InvalidElementException e) { if (LOG.isDebugEnabled()) { LOG.debug("method accessed " + "deleted element", e); } } } }; SwingUtilities.invokeLater(doWorkRunnable); } } /* * @see java.awt.event.MouseListener#mouseClicked(java.awt.event.MouseEvent) */ @Override public void mouseClicked(MouseEvent mouseEvent) { if (mouseEvent.isConsumed()) { return; } super.mouseClicked(mouseEvent); if (mouseEvent.isShiftDown() && TargetManager.getInstance().getTargets().size() > 0) { return; } Editor ce = Globals.curEditor(); if (ce != null) { Selection sel = ce.getSelectionManager().findSelectionFor(this); if (sel instanceof SelectionButtons) { ((SelectionButtons) sel).hideButtons(); } } unhighlight(); Rectangle r = new Rectangle( mouseEvent.getX() - 1, mouseEvent.getY() - 1, 2, 2); Fig f = hitFig(r); if (f instanceof FigCompartment) { FigCompartment figCompartment = (FigCompartment) f; f = figCompartment.hitFig(r); if (f instanceof CompartmentFigText) { if (highlightedFigText != null && highlightedFigText != f) { highlightedFigText.setHighlighted(false); if (highlightedFigText.getGroup() != null) { /* Preventing NullPointerException. */ highlightedFigText.getGroup().damage(); } } ((CompartmentFigText) f).setHighlighted(true); highlightedFigText = (CompartmentFigText) f; TargetManager.getInstance().setTarget(f); } } } /** * Remove the highlight from the currently highlit FigText. * * @return the FigText that had highlight removed */ protected CompartmentFigText unhighlight() { Fig fc; // Search all feature compartments for a text fig to unhighlight for (int i = 1; i < getFigs().size(); i++) { fc = getFigAt(i); if (fc instanceof FigCompartment) { CompartmentFigText ft = unhighlight((FigCompartment) fc); if (ft != null) { return ft; } } } return null; } /** * Search the given compartment for a highlighted CompartmentFigText * and unhighlight it. * * @param fc compartment to search for highlight item * @return item that was unhighlighted or null if no action was taken */ protected final CompartmentFigText unhighlight( FigCompartment fc) { Fig ft; for (int i = 1; i < fc.getFigs().size(); i++) { ft = fc.getFigAt(i); if (ft instanceof CompartmentFigText && ((CompartmentFigText) ft).isHighlighted()) { ((CompartmentFigText) ft).setHighlighted(false); ft.getGroup().damage(); return ((CompartmentFigText) ft); } } return null; } protected void createContainedModelElement(FigGroup fg, InputEvent ie) { if (!(fg instanceof FigCompartment)) { return; } ((FigCompartment) fg).createModelElement(); /* Populate the compartment now, * so that we can put the last one in edit mode: * This fixes issue 5439. */ ((FigCompartment) fg).populate(); // TODO: The above populate works but seems rather heavy here. // I can see something like this is needed though as events // won't manage this quick enough. Could we make // FigEditableCompartment.createModelElement() create // the new child Fig instance? It may also be useful // for it to return the new model element rather than // the current void return - Bob. List figList = fg.getFigs(); if (figList.size() > 0) { Fig fig = (Fig) figList.get(figList.size() - 1); if (fig != null && fig instanceof CompartmentFigText) { if (highlightedFigText != null) { highlightedFigText.setHighlighted(false); if (highlightedFigText.getGroup() != null) { /* Preventing NullPointerException. */ highlightedFigText.getGroup().damage(); } } CompartmentFigText ft = (CompartmentFigText) fig; ft.startTextEditor(ie); ft.setHighlighted(true); highlightedFigText = ft; } } ie.consume(); } /** * Show or hide a compartment based on the meta-type of its contents. * * @param metaType the compartment type to be shown * @param visible true if the compartment should be visible */ public void showCompartment(Object metaType, boolean visible) { FigCompartment fc = getCompartment(metaType); if (fc == null) return; assert fc != null; setCompartmentVisible(fc, visible); } /** * TODO: This functionality is in the worn place. We should be able to * call setVisible on the compartment itself and then this class should * react to that event. * Improvements will follow in later releases. * Hence this method should not be considered stable for module developers. * @param compartment the compartment to be changed * @param isVisible true if the attribute compartment is visible */ public void setCompartmentVisible(FigCompartment compartment, boolean isVisible) { Rectangle rect = getBounds(); if (compartment.isVisible()) { if (!isVisible) { // hide compartment damage(); for (Object f : compartment.getFigs()) { ((Fig) f).setVisible(false); } compartment.setVisible(false); /* * Hiding one compartment means that the Fig returns to minimal * dimensions: */ Dimension aSize = getMinimumSize(); setBounds(rect.x, rect.y, (int) aSize.getWidth(), (int) aSize .getHeight()); /* * Alternatively, we could reduce the height of the Fig by the * height of this one hidden compartment. But that would not be * possible for the width - so we better return to minimal * dimensions overall. */ } } else { if (isVisible) { // show compartment for (Object f : compartment.getFigs()) { ((Fig) f).setVisible(true); } compartment.setVisible(true); Dimension aSize = this.getMinimumSize(); setBounds(rect.x, rect.y, (int) aSize.getWidth(), (int) aSize.getHeight()); damage(); } } } @Override public void setLineWidth(int w) { /* This sets the lineWidth of all in the group: */ super.setLineWidth(w); /* NameFig and StereotypeFig are handled by parent. */ } @Override public int getLineWidth() { return getBigPort().getLineWidth(); } @Override public void setFillColor(Color col) { super.setFillColor(col); getStereotypeFig().setFillColor(null); getNameFig().setFillColor(null); } @Override public Color getFillColor() { return getBigPort().getFillColor(); } @Override public void setFilled(boolean f) { super.setFilled(f); getBigPort().setFilled(f); getNameFig().setFilled(false); getStereotypeFig().setFilled(false); } @Override protected void updateStereotypeText() { if (getOwner() == null) { return; } getStereotypeFig().setVisible( getStereotypeFig().getStereotypeCount() > 0); super.updateStereotypeText(); if (getStereotypeFig().isVisible()) { getNameFig().setTopMargin( getStereotypeFig().getMinimumSize().height); } else { getNameFig().setTopMargin(0); } /* TODO: Is this needed? */ // forceRepaintShadow(); } }