/* $Id: FigClassifierBox.java 17739 2010-01-09 08:39:10Z bobtarling $
*******************************************************************************
* Copyright (c) 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
* Michiel van der Wulp
*******************************************************************************
*
* Some portions of this file were previously release using the BSD License:
*/
// $Id: FigClassifierBox.java 17739 2010-01-09 08:39:10Z 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.static_structure.ui;
import java.awt.Rectangle;
import java.awt.event.MouseEvent;
import java.beans.PropertyChangeEvent;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.Vector;
import javax.swing.Action;
import org.argouml.model.AddAssociationEvent;
import org.argouml.model.AssociationChangeEvent;
import org.argouml.model.AttributeChangeEvent;
import org.argouml.model.Model;
import org.argouml.model.RemoveAssociationEvent;
import org.argouml.model.UmlChangeEvent;
import org.argouml.ui.ActionCreateContainedModelElement;
import org.argouml.ui.ArgoJMenu;
import org.argouml.uml.diagram.DiagramSettings;
import org.argouml.uml.diagram.OperationsCompartmentContainer;
import org.argouml.uml.diagram.ui.ActionAddNote;
import org.argouml.uml.diagram.ui.ActionCompartmentDisplay;
import org.argouml.uml.diagram.ui.ActionEdgesDisplay;
import org.argouml.uml.diagram.ui.FigAttributesCompartment;
import org.argouml.uml.diagram.ui.FigCompartment;
import org.argouml.uml.diagram.ui.FigCompartmentBox;
import org.argouml.uml.diagram.ui.FigOperationsCompartment;
import org.tigris.gef.base.Editor;
import org.tigris.gef.base.Globals;
import org.tigris.gef.base.Selection;
import org.tigris.gef.presentation.Fig;
/**
* Class to display graphics for any UML Classifier in a diagram.<p>
*
* This abstract Fig adds an Operations compartment.
*/
public abstract class FigClassifierBox extends FigCompartmentBox
implements OperationsCompartmentContainer {
/**
* The Fig for the operations compartment (if any).
*/
private FigOperationsCompartment operationsFigCompartment;
private Rectangle getDefaultBounds() {
// this rectangle marks the operation section; all operations
// are inside it
Rectangle bounds = new Rectangle(DEFAULT_COMPARTMENT_BOUNDS);
// 2nd compartment, so adjust Y appropriately
bounds.y = DEFAULT_COMPARTMENT_BOUNDS.y + ROWHEIGHT + 1;
return bounds;
}
/**
* 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 FigClassifierBox(Object owner, Rectangle bounds,
DiagramSettings settings) {
super(owner, bounds, settings);
operationsFigCompartment = new FigOperationsCompartment(
owner,
getDefaultBounds(),
getSettings());
}
/*
* @see java.lang.Object#clone()
*/
public Object clone() {
FigClassifierBox figClone = (FigClassifierBox) super.clone();
Iterator thisIter = this.getFigs().iterator();
while (thisIter.hasNext()) {
Fig thisFig = (Fig) thisIter.next();
if (thisFig == operationsFigCompartment) {
figClone.operationsFigCompartment = (FigOperationsCompartment) thisFig;
return figClone;
}
}
return figClone;
}
/**
* @deprecated by Bob Tarling in 0.29.3 use
* updateCompartment(Model.getMetaTypes().getAttribute())
*/
protected void updateOperations() {
if (!isOperationsVisible()) {
return;
}
operationsFigCompartment.populate();
setBounds(getBounds());
damage();
}
/*
* @see org.argouml.uml.diagram.ui.FigNodeModelElement#renderingChanged()
*/
public void renderingChanged() {
super.renderingChanged();
// TODO: We should be able to just call renderingChanged on the child
// figs here instead of doing an updateOperations...
updateOperations();
// TODO: Taken from FigClassifierBoxWithAttribute to handle events
// on an attribute. All this event handling should eventually be moved
// to the compartment Fig for attributes
if (isAttributesVisible()) {
// TODO: We shouldn't actually have to do all this work
updateCompartment(Model.getMetaTypes().getAttribute());
}
}
/**
* We are getting events we don't want. Filter them out.
* TODO: Can we instruct the model event pump not to send these in the
* first place? See defect 5095.
* @param event the event
*/
public void propertyChange(PropertyChangeEvent event) {
if (event.getPropertyName().equals("generalization")
&& Model.getFacade().isAGeneralization(event.getOldValue())) {
return;
} else if (event.getPropertyName().equals("association")
&& Model.getFacade().isAAssociationEnd(event.getOldValue())) {
return;
} else if (event.getPropertyName().equals("supplierDependency")
&& Model.getFacade().isAUsage(event.getOldValue())) {
return;
} else if (event.getPropertyName().equals("clientDependency")
&& Model.getFacade().isAAbstraction(event.getOldValue())) {
return;
}
super.propertyChange(event);
}
protected void updateLayout(UmlChangeEvent event) {
super.updateLayout(event);
if (event instanceof AssociationChangeEvent
&& getOwner().equals(event.getSource())) {
Object o = null;
if (event instanceof AddAssociationEvent) {
o = event.getNewValue();
} else if (event instanceof RemoveAssociationEvent) {
o = event.getOldValue();
}
if (Model.getFacade().isAOperation(o)
|| Model.getFacade().isAReception(o)) {
updateOperations();
}
}
// TODO: Taken from FigClassifierBoxWithAttribute to handle events
// on an attribute. All this event handling should eventually be moved
// to the compartment Fig for attributes
if (Model.getFacade().isAAttribute(getOwner())) {
if (event instanceof AttributeChangeEvent) {
Object source = event.getSource();
if (Model.getFacade().isAAttribute(source)) {
// TODO: We just need to get someone to re-render a single
// line of text which represents the element here, but I'm
// not sure how to do that. - tfm
// TODO: Bob replies - we shouldn't be interested in this
// event here. The FigFeature (or its notation) should be
// listen for change and the FigFeature should be update
// from that.
updateCompartment(Model.getMetaTypes().getAttribute());
}
} else if (event instanceof AssociationChangeEvent
&& getOwner().equals(event.getSource())) {
Object o = null;
if (event instanceof AddAssociationEvent) {
o = event.getNewValue();
} else if (event instanceof RemoveAssociationEvent) {
o = event.getOldValue();
}
if (Model.getFacade().isAAttribute(o)) {
// TODO: Bob says - we should not be listening here for
// addition and removal of attributes. This should be done in
// FigAttributesCompartment.
updateCompartment(Model.getMetaTypes().getAttribute());
}
}
}
}
@Override
protected void updateListeners(Object oldOwner, Object newOwner) {
if (isAttributesVisible()) {
Set<Object[]> listeners = new HashSet<Object[]>();
// Collect the set of model elements that we want to listen to
if (newOwner != null) {
// TODO: Because we get called on each and every change event, when
// the model is in a state of flux, we'll often get an
// InvalidElementException before we finish this collection. The
// only saving grace is that we're called SO many times that on the
// last time, things should be stable again and we'll get a good set
// of elements for the final update. We need a better mechanism.
// add the listeners to the newOwner
listeners.add(new Object[] {newOwner, null});
// and its stereotypes
// TODO: Aren't stereotypes handled elsewhere?
for (Object stereotype
: Model.getFacade().getStereotypes(newOwner)) {
listeners.add(new Object[] {stereotype, null});
}
// and its features
for (Object feat : Model.getFacade().getFeatures(newOwner)) {
listeners.add(new Object[] {feat, null});
// and the stereotypes of its features
for (Object stereotype
: Model.getFacade().getStereotypes(feat)) {
listeners.add(new Object[] {stereotype, null});
}
// and the parameter of its operations
if (Model.getFacade().isAOperation(feat)) {
for (Object param : Model.getFacade().getParameters(feat)) {
listeners.add(new Object[] {param, null});
}
}
}
}
// Update the listeners to match the desired set using the minimal
// update facility
updateElementListeners(listeners);
} else {
super.updateListeners(oldOwner, newOwner);
}
}
/**
* Updates a compartment box. Called from updateLayout if there is
* a model event effecting the attributes/operations and from
* renderingChanged in all cases.
* TODO: The above statement means that the entire contents of the
* compartments are being rebuilt whenever an add/remove
* of an attribute, operation or a reception is detected. It would be
* better to have compartments listen for add and remove events
* and make minimum change rather than entirely rebuild.
* Remark MVW: This is a bit exaggerated, since the populate()
* method is already heavily optimized.
*/
protected void updateCompartment(Object metaType) {
FigCompartment fc = getCompartment(metaType);
if (!fc.isVisible()) {
return;
}
fc.populate();
// TODO: make setBounds, calcBounds and updateBounds consistent
setBounds(getBounds());
}
/**
* @return The graphics for the UML operations (if any).
* @deprecated in 0.29.3 use
* getCompartment(Model.getUmlFactory(Model.getMetaTypes(),getOperation()))
* to determine if an operation compartment exists and return it.
* The operationsCompartment should be created by the concrete class
*/
protected FigOperationsCompartment getOperationsFig() {
return operationsFigCompartment;
}
/**
* @deprecated by Bob Tarling in 0.29.3 use
* getCompartment(Model.getMetaTypes().getOperation()).getBounds()
* @return the bounds
*/
public Rectangle getOperationsBounds() {
return operationsFigCompartment.getBounds();
}
/**
* @deprecated by Bob Tarling in 0.29.2 use
* isCompartmentVisible(Model.getMetaTypes().getOperation())
* @return the visibility
*/
public boolean isOperationsVisible() {
return operationsFigCompartment != null && operationsFigCompartment.isVisible();
}
/**
* TODO: Should not be on this class as we don't know if we'll have
* operations
* @param isVisible true if the operation compartment is visible
* @deprecated by Bob Tarling in 0.29.2 use setCompartmentVisible
*/
public void setOperationsVisible(boolean isVisible) {
setCompartmentVisible(operationsFigCompartment, isVisible);
}
/**
* @return The graphics for the UML attributes (if any).
* @deprecated in 0.29.1 use
* getCompartment(Model.getUmlFactory(Model.getMetaTypes(),getAttribute()))
* to determine if an attribute compartment exists and return it.
* The attributesCompartment should be created by the concrete class
*/
protected FigAttributesCompartment getAttributesFig() {
FigCompartment fc = getCompartment(Model.getMetaTypes().getAttribute());
return (FigAttributesCompartment) fc;
}
/**
* @deprecated by Bob Tarling in 0.29.2 use
* getCompartment(Model.getMetaTypes().getAttribute()).getBounds()
* @return the bounds
*/
@Deprecated
public Rectangle getAttributesBounds() {
return getAttributesFig().getBounds();
}
/**
* @deprecated by Bob Tarling in 0.29.2 use
* isCompartmentVisible(Model.getMetaTypes().getAttribute())
* @return the visibility
*/
@Deprecated
public boolean isAttributesVisible() {
FigCompartment fc = getCompartment(Model.getMetaTypes().getAttribute());
return fc != null && fc.isVisible();
}
/**
* TODO: Should not be on this class as we don't know if we'll have
* attributes
* @param isVisible true if the attribute compartment is visible
* @deprecated by Bob Tarling in 0.29.2 use setCompartmentVisible
*/
public void setAttributesVisible(boolean isVisible) {
final FigCompartment afc =
getCompartment(Model.getMetaTypes().getAttribute());
setCompartmentVisible(afc, isVisible);
}
/*
* @see org.tigris.gef.presentation.Fig#translate(int, int)
*/
public void translate(int dx, int dy) {
super.translate(dx, dy);
Editor ce = Globals.curEditor();
if (ce != null) {
Selection sel = ce.getSelectionManager().findSelectionFor(this);
// TODO" What is the purpose of this? Why do we hide buttons here?
// Presumably if so we should not assume SelectionClass
if (sel instanceof SelectionClass) {
((SelectionClass) sel).hideButtons();
}
}
}
/**
* Build a collection of menu items relevant for a right-click
* popup menu on an Interface.
*
* @param me a mouse event
* @return a collection of menu items
*/
public Vector getPopUpActions(MouseEvent me) {
Vector popUpActions = super.getPopUpActions(me);
// Add ...
ArgoJMenu addMenu = buildAddMenu();
popUpActions.add(
popUpActions.size() - getPopupAddOffset(),
addMenu);
// Modifier ...
popUpActions.add(
popUpActions.size() - getPopupAddOffset(),
buildModifierPopUp());
// Visibility ...
popUpActions.add(
popUpActions.size() - getPopupAddOffset(),
buildVisibilityPopUp());
return popUpActions;
}
protected ArgoJMenu buildShowPopUp() {
ArgoJMenu showMenu = super.buildShowPopUp();
Iterator i = ActionCompartmentDisplay.getActions().iterator();
while (i.hasNext()) {
showMenu.add((Action) i.next());
}
return showMenu;
}
protected ArgoJMenu buildAddMenu() {
ArgoJMenu addMenu = new ArgoJMenu("menu.popup.add");
List<FigCompartment> comps = getCompartments();
for (FigCompartment comp : comps) {
final Object metaType = comp.getCompartmentType();
Action addAction = new ActionCreateContainedModelElement(metaType, getOwner());
addAction.setEnabled(isSingleTarget());
addMenu.insert(addAction, 0);
}
addMenu.add(new ActionAddNote());
addMenu.add(ActionEdgesDisplay.getShowEdges());
addMenu.add(ActionEdgesDisplay.getHideEdges());
return addMenu;
}
/**
* USED BY PGML.tee.
* TODO We should loop round the compartments to build this string. That
* way we have no attribute/operation knowledge at this level.
* @return the class name and bounds together with compartment
* visibility.
*/
public String classNameAndBounds() {
String classNameAndBounds = super.classNameAndBounds()
+ "operationsVisible=" + isOperationsVisible() + ";";
FigCompartment fc = getCompartment(Model.getMetaTypes().getAttribute());
if (fc != null) {
classNameAndBounds +=
"attributesVisible=" + fc.isVisible() + ";";
}
return classNameAndBounds;
}
protected Object buildModifierPopUp() {
return buildModifierPopUp(ABSTRACT | LEAF | ROOT);
}
}