/* $Id: FigCompartment.java 17741 2010-01-10 03:50:08Z 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: FigCompartment.java 17741 2010-01-10 03:50:08Z 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.util.ArrayList;
import java.util.Collection;
import java.util.List;
import org.apache.log4j.Logger;
import org.argouml.model.InvalidElementException;
import org.argouml.notation.NotationProvider;
import org.argouml.uml.diagram.DiagramSettings;
import org.tigris.gef.presentation.Fig;
import org.tigris.gef.presentation.FigRect;
/**
* Presentation logic for a UML List Compartment. <p>
*
* The UML defines a Name Compartment, and a List Compartment.
* This class implements the latter.<p>
*
* A List Compartment is a boxed compartment,
* containing vertically stacked figs,
* which is common to e.g. an operations
* compartment and an attributes compartment.<p>
*
* The bigPort is filled with a border. All other figs contained
* in this group may not be filled.<p>
*
* The size calculation done here supports vertically
* stacked sub-figs of this group and supports all
* compartment specializations.
*
* @author Bob Tarling
*/
public abstract class FigCompartment extends ArgoFigGroup {
private static final Logger LOG = Logger.getLogger(FigCompartment.class);
private Fig bigPort;
private static final int MIN_HEIGHT = FigNodeModelElement.NAME_FIG_HEIGHT;
/**
* A separator line that may be wider than the compartment.
*/
private Fig externalSeparatorFig = new FigSeparator(X0, Y0, 11, LINE_WIDTH);
private void constructFigs(int x, int y, int w, int h) {
bigPort = new FigPort(X0, Y0, w, h);
bigPort.setFilled(false);
bigPort.setLineWidth(0);
addFig(bigPort);
}
/**
* Construct a new FigCompartment.
*
* @param owner owning UML element
* @param bounds rectangle describing bounds of compartment
* @param settings render settings
*/
public FigCompartment(Object owner, Rectangle bounds,
DiagramSettings settings) {
super(owner, settings);
constructFigs(bounds.x, bounds.y, bounds.width, bounds.height);
}
/**
* If a boxed compartment is set to invisible then remove all its
* children.
* This is to save on resources and increase efficiency as multiple
* figs need not exist and be resized, moved etc if they are not visible.
* If a compartment is later made visible then its child figs are rebuilt
* from the model.
* {@inheritDoc}
*/
@Override
public void setVisible(boolean visible) {
if (isVisible() == visible) {
return;
}
super.setVisible(visible);
if (externalSeparatorFig != null) {
externalSeparatorFig.setVisible(visible);
}
if (visible) {
populate();
} else {
for (int i = getFigs().size() - 1; i >= 0; --i) {
Fig f = getFigAt(i);
if (f instanceof CompartmentFigText) {
removeFig(f);
}
}
}
}
@Override
public void addFig(Fig fig) {
if (fig != getBigPort()
&& !(fig instanceof CompartmentFigText)
&& !(fig instanceof FigSeparator)) {
LOG.error("Illegal Fig added to a FigEditableCompartment");
throw new IllegalArgumentException(
"A FigEditableCompartment can only "
+ "contain CompartmentFigTexts, "
+ "received a " + fig.getClass().getName());
}
super.addFig(fig);
}
/**
* @return the bigPort
*/
public Fig getBigPort() {
return bigPort;
}
/**
* The minimum width is the minimum width of the child with the widest
* minimum width.
* The minimum height is the total minimum height of all child figs.
* @return the minimum width
*/
@Override
public Dimension getMinimumSize() {
int minWidth = 0;
int minHeight = 0;
for (Fig fig : (Collection<Fig>) getFigs()) {
if (fig.isVisible() && fig != getBigPort()) {
int fw = fig.getMinimumSize().width;
if (fw > minWidth) {
minWidth = fw;
}
minHeight += fig.getMinimumSize().height;
}
}
minHeight += 2; // 2 Pixel padding after compartment
minHeight = Math.max(minHeight, MIN_HEIGHT);
return new Dimension(minWidth, minHeight);
}
@Override
protected void setBoundsImpl(int x, int y, int w, int h) {
Rectangle oldBounds = getBounds();
Dimension minimumSize = getMinimumSize();
int newW = Math.max(w, minimumSize.width);
int newH = Math.max(h, minimumSize.height);
int currentHeight = 0;
for (Fig fig : (List<Fig>) getFigs()) {
if (fig.isVisible() && fig != getBigPort()) {
int fh = fig.getMinimumSize().height;
fig.setBounds(x, y + currentHeight, newW, fh);
currentHeight += fh;
}
}
getBigPort().setBounds(x, y, newW, newH);
calcBounds();
firePropChange("bounds", oldBounds, getBounds());
}
/**
* Create a new model element for the compartment.
*/
protected abstract void createModelElement();
@Override
public void setFilled(boolean f) {
// Only the bigPort may be filled
super.setFilled(false);
// bigPort.setFilled(f);
}
@Deprecated //see parent
@Override
public boolean getFilled() {
return isFilled();
}
@Override
public boolean isFilled() {
return bigPort.isFilled();
}
/**
* This operation shall return a name unique for this type of
* compartment. Potential use: show at the top in the compartment
* as described in the UML, or as an identification string for
* the compartment type. <p>
* See UML 1.4.2 OMG, chapter 5.24.1.2: Compartment name.
*
* @return the name of the compartment
*/
public abstract String getName();
/**
* Implemented in the subclass to indicate the primary type of model element
* the compartment is designed to hold.
* @return a model element type
*/
public abstract Object getCompartmentType();
/**
* @return the collection of UML objects
* on which this compartment is based
*/
protected abstract Collection getUmlCollection();
/**
* @return the type of the notationProvider
* used to handle the text in the compartment
*/
protected abstract int getNotationType();
/**
* Fills the Fig by adding all figs within.
*/
public void populate() {
if (!isVisible()) {
return;
}
int xpos = bigPort.getX();
int ypos = bigPort.getY();
List<CompartmentFigText> figs = getElementFigs();
// We remove all of them:
for (Fig f : figs) {
removeFig(f);
}
// We are going to add the ones still valid & new ones
// in the right sequence:
FigSingleLineTextWithNotation comp = null;
try {
int acounter = -1;
for (Object umlObject : getUmlCollection()) {
comp = findCompartmentFig(figs, umlObject);
acounter++;
// TODO: Some of these magic numbers probably assume a line
// width of 1. Replace with appropriate constants/variables.
// If we don't have a fig for this UML object, we'll need to add
// one. We set the bounds, but they will be reset later.
if (comp == null) {
comp = createFigText(umlObject, new Rectangle(
xpos + 1 /*?LINE_WIDTH?*/,
ypos + 1 /*?LINE_WIDTH?*/ + acounter
* ROWHEIGHT,
0,
ROWHEIGHT - 2 /*? 2*LINE_WIDTH? */),
getSettings());
} else {
/* This one is still usable, so let's retain it, */
/* but its position may have been changed: */
Rectangle b = comp.getBounds();
b.y = ypos + 1 /*?LINE_WIDTH?*/ + acounter * ROWHEIGHT;
// bounds not relevant here, but I am perfectionist...
comp.setBounds(b);
}
/* We need to set a new notationprovider, since
* the Notation language may have been changed: */
comp.initNotationProviders();
addFig(comp); // add it again (but now in the right sequence)
// Now put the text in
// We must handle the case where the text is null
String ftText = comp.getNotationProvider().toString(umlObject,
comp.getNotationSettings());
if (ftText == null) {
ftText = "";
}
comp.setText(ftText);
comp.setBotMargin(0);
}
} catch (InvalidElementException e) {
// TODO: It would be better here to continue the loop and try to
// build the rest of the compartment. Hence try/catch should be
// internal to the loop.
LOG.debug("Attempted to populate a FigEditableCompartment"
+ " using a deleted model element - aborting", e);
}
if (comp != null) {
comp.setBotMargin(6); // the last one needs extra space below it
}
}
/**
* @return null
* @deprecated for 0.27.3 by tfmorris. Subclasses must implement
* {@link #createFigText(Object, Rectangle, DiagramSettings,
* NotationProvider)}
* which will become abstract in the future when this deprecated method is
* removed.
*/
@Deprecated
protected FigSingleLineTextWithNotation createFigText(
int x, int y, int w, int h, Fig aFig, NotationProvider np) {
// No longer abstract to allow subclasses to remove, so we provide a
// null default implementation
return null;
}
/**
* Factory method to create a FigSingleLineTextWithNotation
* which must be implemented by all subclasses.
* It will become abstract after the release of 0.28 to
* enforce this requirement.
*
* @param owner owning UML element
* @param bounds position and size
* @param settings render settings
* @param np notation provider
* @return a FigSingleLineText which can be used to display the text.
*/
@SuppressWarnings("deprecation")
protected FigSingleLineTextWithNotation createFigText(Object owner,
Rectangle bounds,
@SuppressWarnings("unused") DiagramSettings settings,
NotationProvider np) {
// If this is not overridden it will revert to the old behavior
// All internal subclasses have been updated, but this if for
// compatibility of non-ArgoUML extensions.
FigSingleLineTextWithNotation comp = createFigText(
bounds.x,
bounds.y,
bounds.width,
bounds.height,
this.getBigPort(),
np);
comp.setOwner(owner);
return comp;
}
/**
* @param owner owning UML element
* @param bounds position and size
* @param settings the render settings
* @return a FigSingleLineText with notation provider
* which can be used to display the text
*/
abstract FigSingleLineTextWithNotation createFigText(Object owner,
Rectangle bounds,
DiagramSettings settings);
/**
* Returns the new size of the FigGroup (e.g. attributes or
* operations) after calculation new bounds for all sub-figs,
* considering their minimal sizes; FigGroup need not be
* displayed; no update event is fired.<p>
*
* This method has side effects that are sometimes used.
*
* @param x x
* @param y y
* @param w w
* @param h h
* @return the new dimension
*/
@SuppressWarnings("unused")
public Dimension updateFigGroupSize(
int x,
int y,
int w,
int h,
boolean checkSize,
int rowHeight) {
return getMinimumSize();
}
/* Find the compartment fig for this umlObject: */
private CompartmentFigText findCompartmentFig(List<CompartmentFigText> figs,
Object umlObject) {
for (CompartmentFigText fig : figs) {
if (fig.getOwner() == umlObject) {
return fig;
}
}
return null;
}
private List<CompartmentFigText> getElementFigs() {
final List<CompartmentFigText> figs =
new ArrayList<CompartmentFigText>(getFigs().size());
for (Object f : getFigs()) {
if (f instanceof CompartmentFigText) {
figs.add((CompartmentFigText) f);
}
}
return figs;
}
@Override
public void setLineColor(Color col) {
super.setLineColor(col);
externalSeparatorFig.setFillColor(col);
}
@Override
public void setLineWidth(int w) {
super.setLineWidth(0);
bigPort.setLineWidth(0);
externalSeparatorFig.setHeight(w);
}
@Override
public void setFillColor(Color col) {
super.setFillColor(col);
externalSeparatorFig.setFillColor(getLineColor());
}
/**
* Set new bounds for the external separator line (if it exists).
*
* @param r the new bounds
*/
public void setExternalSeparatorFigBounds(Rectangle r) {
externalSeparatorFig.setBounds(r);
}
/**
* @return separator figure
*/
public Fig getSeparatorFig() {
return externalSeparatorFig;
}
/**
* Fig representing a horizontal line separator for compartment. <p>
*
* This is a horizontal line, but implemented as a rectangle
* filled with the line color, since using a FigLine would draw the line
* around the start and end coordinates with a line width > 1.
*/
private static class FigSeparator extends FigRect {
/**
* Constructor.
*
* @param x coordinate
* @param y coordinate
* @param len length of the line
*/
FigSeparator(int x, int y, int len, int lineWidth) {
super(x, y, len, lineWidth);
setLineWidth(0);
super.setFilled(true);
}
@Override
public void setFilled(boolean filled) {
// Override superclass to do nothing.
// Fill property cannot be changed.
}
@Override
public void setLineWidth(int width) {
// Override superclass to do nothing.
// Line width cannot be changed.
}
}
/**
* Fig representing a horizontal line separator for compartment. <p>
*
* This is a horizontal line, but implemented as a rectangle
* filled with the line color, since using a FigLine would draw the line
* around the start and end coordinates with a line width > 1.
*/
private static class FigPort extends FigRect {
/**
* Constructor.
*
* @param x coordinate
* @param y coordinate
* @param len length of the line
*/
FigPort(int x, int y, int len, int lineWidth) {
super(x, y, len, lineWidth);
super.setLineWidth(0);
super.setFillColor(null);
super.setFilled(false);
}
@Override
public void setFilled(boolean filled) {
// Override superclass to do nothing.
// Fill property cannot be changed.
}
@Override
public void setFillColor(Color color) {
// Override superclass to do nothing.
// Fill property cannot be changed.
}
@Override
public void setLineWidth(int width) {
// Override superclass to do nothing.
// Line width property cannot be changed.
}
/**
* The hit method in GEF {Fig.hit(Rectangle)} does not register a hit
* inside the Fig if the FIg is not filled. When not filled only a hit
* on the border is registered.
*
* This override is a workaround until GEF is fixed.
*
* We require GEF to all a Fig to be set so that filled = true but
* have fill color as null (transparent). That way the base
* functionality will work for us.
*
* @param r
* the rectangular hit area
* @return true if the hit rectangle strikes this fig
*/
public boolean hit(Rectangle r) {
if (!isVisible() || !isSelectable())
return false;
final int cornersHit =
countCornersContained(r.x, r.y, r.width, r.height);
return cornersHit > 0;
}
}
}