/* $Id: FigNodeModelElement.java 18859 2010-11-29 21:37:03Z mvw $ ***************************************************************************** * 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: * Thomas Neustupny * Bob Tarling * Michiel van der Wulp ***************************************************************************** * * Some portions of this file was previously release using the BSD License: */ // 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.Dimension; import java.awt.Font; import java.awt.Graphics; import java.awt.Image; import java.awt.Rectangle; import java.awt.event.InputEvent; import java.awt.event.KeyEvent; import java.awt.event.KeyListener; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.beans.PropertyVetoException; import java.beans.VetoableChangeListener; import java.util.ArrayList; import java.util.Collection; 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 javax.swing.Icon; import javax.swing.JMenu; import javax.swing.JSeparator; import javax.swing.SwingUtilities; import org.apache.log4j.Logger; import org.argouml.application.events.ArgoDiagramAppearanceEvent; import org.argouml.application.events.ArgoDiagramAppearanceEventListener; import org.argouml.application.events.ArgoEventPump; import org.argouml.application.events.ArgoEventTypes; import org.argouml.application.events.ArgoHelpEvent; import org.argouml.application.events.ArgoNotationEvent; import org.argouml.application.events.ArgoNotationEventListener; import org.argouml.cognitive.Designer; import org.argouml.cognitive.Highlightable; import org.argouml.cognitive.ToDoItem; import org.argouml.cognitive.ToDoList; import org.argouml.cognitive.ui.ActionGoToCritique; import org.argouml.i18n.Translator; import org.argouml.kernel.DelayedChangeNotify; import org.argouml.kernel.DelayedVChangeListener; import org.argouml.kernel.Owned; import org.argouml.kernel.Project; import org.argouml.model.AssociationChangeEvent; import org.argouml.model.AttributeChangeEvent; import org.argouml.model.DeleteInstanceEvent; import org.argouml.model.DiElement; import org.argouml.model.InvalidElementException; import org.argouml.model.Model; import org.argouml.model.UmlChangeEvent; import org.argouml.notation.Notation; import org.argouml.notation.NotationName; import org.argouml.notation.NotationProvider; import org.argouml.notation.NotationProviderFactory2; import org.argouml.notation.NotationRenderer; import org.argouml.notation.NotationSettings; import org.argouml.profile.FigNodeStrategy; import org.argouml.ui.ArgoJMenu; import org.argouml.ui.Clarifier; import org.argouml.ui.ContextActionFactoryManager; import org.argouml.ui.ProjectActions; import org.argouml.ui.UndoableAction; import org.argouml.ui.targetmanager.TargetManager; import org.argouml.uml.StereotypeUtility; import org.argouml.uml.diagram.ArgoDiagram; import org.argouml.uml.diagram.DiagramAppearance; import org.argouml.uml.diagram.DiagramElement; import org.argouml.uml.diagram.DiagramSettings; import org.argouml.uml.diagram.DiagramSettings.StereotypeStyle; import org.argouml.uml.diagram.PathContainer; import org.argouml.uml.ui.ActionDeleteModelElements; import org.argouml.util.IItemUID; import org.argouml.util.ItemUID; import org.tigris.gef.base.Diagram; import org.tigris.gef.base.Globals; import org.tigris.gef.base.Layer; import org.tigris.gef.base.LayerPerspective; import org.tigris.gef.base.Selection; import org.tigris.gef.graph.MutableGraphSupport; import org.tigris.gef.presentation.Fig; import org.tigris.gef.presentation.FigGroup; import org.tigris.gef.presentation.FigImage; import org.tigris.gef.presentation.FigNode; import org.tigris.gef.presentation.FigRect; import org.tigris.gef.presentation.FigText; /** * Abstract class to display diagram icons for UML ModelElements that * look like nodes and that have editable names and can be * resized. * <p> * NOTE: This will drop the ArgoNotationEventListener and * ArgoDiagramAppearanceEventListener interfaces in the next release. * The corresponding methods have been marked as deprecated. * * @author abonner */ public abstract class FigNodeModelElement extends FigNode implements VetoableChangeListener, DelayedVChangeListener, MouseListener, KeyListener, PropertyChangeListener, PathContainer, ArgoDiagramAppearanceEventListener, ArgoNotationEventListener, NotationRenderer, Highlightable, IItemUID, Clarifiable, ArgoFig, StereotypeStyled, DiagramElement, Owned { private static final Logger LOG = Logger.getLogger(FigNodeModelElement.class); // TODO: There are lots and LOTS of magic numbers used in calculating // positions and sizes. Any time you see Figs being placed at 10,10 use // these constants instead. If you can reliably interpret calculations, // you can factor them out of there as well. Add additional constants // as needed to express other common factors - tfm 20081201 /** * Default width for a node fig. * Used to be 60 (up to V0.20), later (from V0.22) it was 90. * Now 64 to align to grid better. */ protected static final int WIDTH = 64; /** * The default minimum height of the name fig, computed to allow room for * the Critics "clarifiers" (red squiggly line) with the default font. This * should really go away and be managed internally to the name figs and * fetched through getMinimumSize(). The final height can change based on * the font selected. */ protected static final int NAME_FIG_HEIGHT = 21; /** * Padding to be used above and below the name. */ protected static final int NAME_V_PADDING = 2; private DiElement diElement; private NotationProvider notationProviderName; /** * True if an instance is allowed to be * invisible. This is currently only set true by FigEdgePort. * TODO: FigEdgePort should be removed from the FigNodeModelElement * hierarchy and so the need for this removed. */ protected boolean invisibleAllowed = false; /** * Needed for loading. Warning: if false, a too small size might look bad! */ private boolean checkSize = true; /** * Offset from the end of the set of popup actions at which new items * should be inserted by concrete figures. * @See {@link #getPopUpActions(MouseEvent)} */ private static int popupAddOffset; /** * Used for #buildModifierPopUp(). */ protected static final int ROOT = 1; /** * Used for #buildModifierPopUp(). */ protected static final int ABSTRACT = 2; /** * Used for #buildModifierPopUp(). */ protected static final int LEAF = 4; /** * Used for #buildModifierPopUp(). */ protected static final int ACTIVE = 8; private Fig bigPort; /** * use getNameFig() and setNameFig() to access the Figs. * Use getName() and setName() to just change the text. */ private FigText nameFig; /** * use getter/setter * getStereotypeFig() and setStereoTypeFig() to access the Figs. * Use getStereotype() and setStereotype() to change stereotype * text. */ private FigStereotypesGroup stereotypeFig; /** * The <code>FigProfileIcon</code> being currently displayed */ private FigProfileIcon stereotypeFigProfileIcon; /** * Contains the figs of the floating stereotypes when viewed in * <code>SmallIcon</code> mode. */ private List<Fig> floatingStereotypes = new ArrayList<Fig>(); /** * The current stereotype view, defaults to "textual". * * @see DiagramAppearance#STEREOTYPE_VIEW_TEXTUAL * @see DiagramAppearance#STEREOTYPE_VIEW_SMALL_ICON * @see DiagramAppearance#STEREOTYPE_VIEW_BIG_ICON */ private DiagramSettings.StereotypeStyle stereotypeStyle = DiagramSettings.StereotypeStyle.TEXTUAL; /** * The width of the profile icons when viewed at the small icon mode. * The icon width is resized to <code>ICON_WIDTH</code> and the height is * set to a value that attempts to keep the original width/height * proportion. */ private static final int ICON_WIDTH = 16; /** * When stereotypes are shown in <code>BigIcon</code> mode the * <code>nameFig</code> is replaced by the one provided by the * <code>FigProfileIcon</code> * * @see FigProfileIcon */ private FigText originalNameFig; /** * EnclosedFigs are the Figs that are enclosed by this figure. Say that * it is a Package then these are the Classes, Interfaces, Packages etc * that are on this figure. This is not the same as the figures in the * FigGroup that this FigNodeModelElement "is", since these are the * figures that make up this high-level primitive figure. */ private Vector<Fig> enclosedFigs = new Vector<Fig>(); /** * The figure enclosing this figure such as a package surrounding a class. */ private Fig encloser; // TODO: Bobs says - what is the purpose of this flag? Please document. private boolean readyToEdit = true; private boolean suppressCalcBounds; private static boolean showBoldName; private ItemUID itemUid; /** * Set the removeFromDiagram to false if this node may not * be removed from the diagram. */ private boolean removeFromDiagram = true; /** * If the contains text to be edited by the user. */ private boolean editable = true; // TODO: A more strongly typed data structure could be used here. private Set<Object[]> listeners = new HashSet<Object[]>(); /** * Settings which affect rendering (color, font, line width, etc); */ private DiagramSettings settings; /** * The notation settings for this specific fig. We manage it separately * from DiagramSettings because it is more likely to change. */ private NotationSettings notationSettings; /** * Construct an unplaced Fig with no owner using the given * rendering settings. */ private void constructFigs() { // TODO: Why isn't this stuff managed by the nameFig itself? nameFig.setFilled(true); nameFig.setText(placeString()); nameFig.setBotMargin(7); // make space for the clarifier nameFig.setRightMargin(4); // margin between text and border nameFig.setLeftMargin(4); readyToEdit = false; setShadowSize(getSettings().getDefaultShadowWidth()); /* TODO: how to handle changes in shadowsize * from the project properties? */ stereotypeStyle = getSettings().getDefaultStereotypeView(); } /** * Construct a figure at a specific position for a given model element * with the given settings. This is the constructor used by the PGML * parser when loading a diagram from a file.<p> * * Beware: the width and height in the given Rectangle are currently ignored. * According issue 5604 this is a bug. * * @param element ModelElement associated with figure * @param bounds x & y are used to set position, width & height are ignored * @param renderSettings the rendering settings to use for the Fig */ protected FigNodeModelElement(Object element, Rectangle bounds, DiagramSettings renderSettings) { super(); super.setOwner(element); // TODO: We currently don't support per-fig settings for most stuff, so // we can just use the defaults that we were given. // settings = new DiagramSettings(renderSettings); settings = renderSettings; // Be careful here since subclasses could have overridden this with // the assumption that it wouldn't be called before the constructors // finished super.setFillColor(FILL_COLOR); super.setLineColor(LINE_COLOR); super.setLineWidth(LINE_WIDTH); super.setTextColor(TEXT_COLOR); // Some subclasses will try to use this /* * Notation settings are different since, we know that, at a minimum, * the isShowPath() setting can change because with implement * PathContainer, so we make sure that we have a private copy of the * notation settings. */ notationSettings = new NotationSettings(settings.getNotationSettings()); // this rectangle marks the whole modelelement figure; everything // is inside it: bigPort = createBigPortFig(); nameFig = new FigNameWithAbstractAndBold(element, new Rectangle(X0, Y0, WIDTH, NAME_FIG_HEIGHT), getSettings(), true); stereotypeFig = createStereotypeFig(); constructFigs(); // TODO: For a FigPool the element will be null. // When issue 5031 is resolved this constraint can be reinstated // if (element == null) { // throw new IllegalArgumentException("An owner must be supplied"); // } if (element != null && !Model.getFacade().isAUMLElement(element)) { throw new IllegalArgumentException( "The owner must be a model element - got a " + element.getClass().getName()); } nameFig.setText(placeString()); if (element != null) { NotationName nn = Notation.findNotation(notationSettings.getNotationLanguage()); notationProviderName = NotationProviderFactory2.getInstance().getNotationProvider( getNotationProviderType(), element, this, nn); /* This next line presumes that the 1st fig with this owner * is the previous port - and consequently nullifies the owner * of this 1st fig. */ bindPort(element, bigPort); // Add a listener for changes to any property addElementListener(element); } if (bounds != null) { setLocation(bounds.x, bounds.y); } // TODO: The following is carried over from setOwner, but probably // isn't needed // renderingChanged(); // It does the following (add as needed): // updateNameText(); // updateStereotypeText(); // updateStereotypeIcon(); // updateBounds(); // damage(); readyToEdit = true; } /** * Overrule this if a rectangle is not usable. * * @return the Fig to be used as bigPort */ protected Fig createBigPortFig() { return new FigRect(X0, Y0, 0, 0, DEBUG_COLOR, DEBUG_COLOR); } protected FigStereotypesGroup createStereotypeFig() { return new FigStereotypesGroup(getOwner(), new Rectangle(X0, Y0, WIDTH, STEREOHEIGHT), settings); } /** * This is the final call at creation time of the Fig, i.e. here * it is put on a Diagram. * * @param lay the Layer (which has a 1..1 relation to the Diagram) * @see org.tigris.gef.presentation.Fig#setLayer(org.tigris.gef.base.Layer) */ @Override public void setLayer(Layer lay) { super.setLayer(lay); determineDefaultPathVisible(); } /** * Clone this figure. After the base clone method has been called determine * which child figs of the clone represent the name, stereotype and port. * <p> * TODO: enclosedFigs, encloser and eventSenders may also need to be cloned. * * @see java.lang.Object#clone() * @return the cloned figure */ @Override public Object clone() { final FigNodeModelElement clone = (FigNodeModelElement) super.clone(); final Iterator cloneIter = clone.getFigs().iterator(); for (Object thisFig : getFigs()) { final Fig cloneFig = (Fig) cloneIter.next(); if (thisFig == getBigPort()) { clone.setBigPort(cloneFig); } if (thisFig == nameFig) { clone.nameFig = (FigSingleLineText) thisFig; /* TODO: MVW: I think this has to be: * clone.nameFig = (FigSingleLineText) cloneFig; * but have not the means to investigate, * since this code is not yet used. * Enable the menu-items for Copy/Paste to test... * BTW: In some other FigNodeModelElement * classes I see the same mistake. */ } if (thisFig == getStereotypeFig()) { clone.stereotypeFig = (FigStereotypesGroup) thisFig; /* Idem here: * clone.stereotypeFig = (FigStereotypesGroup) cloneFig; */ } } return clone; } /** * Default Reply text to be shown while placing node in diagram. * Overrule this when the text is not "new [UMLClassName]". * * @return the text to be shown while placing node in diagram */ public String placeString() { if (Model.getFacade().isAModelElement(getOwner())) { String placeString = Model.getFacade().getName(getOwner()); if (placeString == null) { placeString = // TODO: I18N "new " + Model.getFacade().getUMLClassName(getOwner()); } return placeString; } return ""; } /** * @param id UID */ public void setItemUID(ItemUID id) { itemUid = id; } /** * @return UID */ public ItemUID getItemUID() { return itemUid; } /** * Get the Fig that displays the model element name. * * @return the name Fig */ protected FigText getNameFig() { return nameFig; } /** * Get the Rectangle in which the model elements name is displayed * @return bounding box for name */ public Rectangle getNameBounds() { return nameFig.getBounds(); } /** * Set the Fig that displays the model element name. * * @param fig the name Fig */ protected void setNameFig(FigText fig) { nameFig = fig; if (nameFig != null) { updateFont(); } } /** * Get the name of the model element this Fig represents. * * @return the name of the model element */ public String getName() { return nameFig.getText(); } /** * Change the name of the model element this Fig represents. * * @param n the name of the model element */ public void setName(String n) { nameFig.setText(n); } /** * This method returns a Vector of one of these 4 types: * AbstractAction, JMenu, JMenuItem, JSeparator. * {@inheritDoc} */ @Override public Vector getPopUpActions(MouseEvent me) { ActionList popUpActions = new ActionList(super.getPopUpActions(me), isReadOnly()); final List<Action> modulesActions = ContextActionFactoryManager.getContextPopupActions(); for (Action a : modulesActions) { if (a instanceof List) { JMenu m = new JMenu((Action) a); popUpActions.add(m); for (Action subAction : (List<Action>) a) { m.add(subAction); } } else { popUpActions.add(a); } } // Show ... ArgoJMenu show = buildShowPopUp(); if (show.getMenuComponentCount() > 0) { popUpActions.add(show); } // popupAddOffset should be equal to the number of items added here: popUpActions.add(new JSeparator()); popupAddOffset = 1; if (removeFromDiagram) { popUpActions.add( ProjectActions.getInstance().getRemoveFromDiagramAction()); popupAddOffset++; } if (!isReadOnly()) { popUpActions.add(new ActionDeleteModelElements()); popupAddOffset++; } /* Check if multiple items are selected: */ if (TargetManager.getInstance().getTargets().size() == 1) { // TODO: Having Critics actions here introduces an unnecessary // dependency on the Critics subsystem. Have it register its // desired actions using an extension mechanism - tfm ToDoList tdList = Designer.theDesigner().getToDoList(); List<ToDoItem> items = tdList.elementListForOffender(getOwner()); if (items != null && items.size() > 0) { // TODO: This creates a dependency on the Critics subsystem. // We need a generic way for modules (including our internal // subsystems) to request addition of actions to the popup // menu. - tfm 20080430 ArgoJMenu critiques = new ArgoJMenu("menu.popup.critiques"); ToDoItem itemUnderMouse = hitClarifier(me.getX(), me.getY()); if (itemUnderMouse != null) { critiques.add(new ActionGoToCritique(itemUnderMouse)); critiques.addSeparator(); } for (ToDoItem item : items) { if (item != itemUnderMouse) { critiques.add(new ActionGoToCritique(item)); } } popUpActions.add(0, new JSeparator()); popUpActions.add(0, critiques); } } // Add stereotypes submenu Collection<Object> elements = new ArrayList<Object>(); Object owner = getOwner(); if (owner != null) { elements.add(owner); } for (Object o : TargetManager.getInstance().getTargets()) { Object element = null; if (Model.getFacade().isAUMLElement(o)) { element = o; } else if (o instanceof Fig) { element = ((Fig) o).getOwner(); } if (element != null && element != owner) { elements.add(element); } } final Action[] stereoActions = StereotypeUtility.getApplyStereotypeActions(elements); if (stereoActions != null) { popUpActions.add(0, new JSeparator()); final ArgoJMenu stereotypes = new ArgoJMenu("menu.popup.apply-stereotypes"); for (Action action : stereoActions) { stereotypes.addCheckItem(action); } popUpActions.add(0, stereotypes); } if (TargetManager.getInstance().getTargets().size() == 1) { // add stereotype view submenu ArgoJMenu stereotypesView = new ArgoJMenu("menu.popup.stereotype-view"); // TODO: There are cyclic dependencies between ActionStereotypeView* // and FigNodeModelElement. Register these actions opaquely since // we don't what they are. - tfm stereotypesView.addRadioItem(new ActionStereotypeViewTextual(this)); stereotypesView.addRadioItem(new ActionStereotypeViewBigIcon(this)); stereotypesView.addRadioItem( new ActionStereotypeViewSmallIcon(this)); popUpActions.add(0, stereotypesView); } return popUpActions; } protected ArgoJMenu buildShowPopUp() { ArgoJMenu showMenu = new ArgoJMenu("menu.popup.show"); for (UndoableAction ua : ActionSetPath.getActions()) { showMenu.add(ua); } return showMenu; } /** * @return the pop-up menu item for Visibility */ protected Object buildVisibilityPopUp() { ArgoJMenu visibilityMenu = new ArgoJMenu("menu.popup.visibility"); visibilityMenu.addRadioItem(new ActionVisibilityPublic(getOwner())); visibilityMenu.addRadioItem(new ActionVisibilityPrivate(getOwner())); visibilityMenu.addRadioItem(new ActionVisibilityProtected(getOwner())); visibilityMenu.addRadioItem(new ActionVisibilityPackage(getOwner())); return visibilityMenu; } /** * Build a pop-up menu item for the various modifiers.<p> * * This function is designed to be easily extendable with new items. * * @param items bitwise OR of the items: ROOT, ABSTRACT, LEAF, ACTIVE. * @return the menu item */ protected Object buildModifierPopUp(int items) { ArgoJMenu modifierMenu = new ArgoJMenu("menu.popup.modifiers"); if ((items & ABSTRACT) > 0) { modifierMenu.addCheckItem(new ActionModifierAbstract(getOwner())); } if ((items & LEAF) > 0) { modifierMenu.addCheckItem(new ActionModifierLeaf(getOwner())); } if ((items & ROOT) > 0) { modifierMenu.addCheckItem(new ActionModifierRoot(getOwner())); } if ((items & ACTIVE) > 0) { modifierMenu.addCheckItem(new ActionModifierActive(getOwner())); } return modifierMenu; } /* * @see org.tigris.gef.presentation.Fig#getEnclosingFig() */ @Override public Fig getEnclosingFig() { return encloser; } /* * Updates the modelelement container if the fig is moved in or * out another fig. If this fig doesn't have an enclosing fig * anymore, the namespace of the diagram will be the owning * modelelement. If this fig is moved inside another * FigNodeModelElement the owner of that fignodemodelelement will * be the owning modelelement. * * @see org.tigris.gef.presentation.FigNode#setEnclosingFig(org.tigris.gef.presentation.Fig) */ @Override public void setEnclosingFig(Fig newEncloser) { Fig oldEncloser = encloser; LayerPerspective layer = (LayerPerspective) getLayer(); if (layer != null) { ArgoDiagram diagram = (ArgoDiagram) layer.getDiagram(); diagram.encloserChanged( this, (FigNode) oldEncloser, (FigNode) newEncloser); } super.setEnclosingFig(newEncloser); if (layer != null && newEncloser != oldEncloser) { Diagram diagram = layer.getDiagram(); if (diagram instanceof ArgoDiagram) { ArgoDiagram umlDiagram = (ArgoDiagram) diagram; // Set the namespace of the enclosed model element to the // namespace of the encloser. Object namespace = null; if (newEncloser == null) { // The node's been placed on the diagram umlDiagram.setModelElementNamespace(getOwner(), null); } else { // The node's been placed within some Fig namespace = newEncloser.getOwner(); if (Model.getFacade().isANamespace(namespace)) { umlDiagram.setModelElementNamespace( getOwner(), namespace); } } } if (encloser instanceof FigNodeModelElement) { ((FigNodeModelElement) encloser).removeEnclosedFig(this); } if (newEncloser instanceof FigNodeModelElement) { ((FigNodeModelElement) newEncloser).addEnclosedFig(this); } } encloser = newEncloser; } /** * Handle the case where this fig is moved into a Component. * * @param newEncloser the new encloser for this Fig */ protected void moveIntoComponent(Fig newEncloser) { final Object component = newEncloser.getOwner(); final Object owner = getOwner(); assert Model.getFacade().isAComponent(component); assert Model.getFacade().isAUMLElement(owner); final Collection er1 = Model.getFacade().getElementResidences(owner); final Collection er2 = Model.getFacade().getResidentElements(component); boolean found = false; // Find all ElementResidences between the class and the component: final Collection<Object> common = new ArrayList<Object>(er1); common.retainAll(er2); for (Object elementResidence : common) { if (!found) { found = true; // There is already a correct ElementResidence } else { // There were 2 ElementResidences .. strange case. Model.getUmlFactory().delete(elementResidence); } } if (!found) { // There was no ElementResidence yet, so let's create one: Model.getCoreFactory().buildElementResidence( owner, component); } } /** * Add a Fig that is enclosed by this figure. * * @param fig The fig to be added */ public void addEnclosedFig(Fig fig) { enclosedFigs.add(fig); } /** * Removes the given Fig from the list of enclosed Figs. * * @param fig The Fig to be removed */ public void removeEnclosedFig(Fig fig) { enclosedFigs.remove(fig); } /* * @see org.tigris.gef.presentation.Fig#getEnclosedFigs() */ @Override public Vector<Fig> getEnclosedFigs() { return enclosedFigs; } /* * @see org.tigris.gef.presentation.Fig#makeSelection() */ @Override public Selection makeSelection() { return new SelectionDefaultClarifiers(this); } /** * Displays visual indications of pending ToDoItems. * Please note that the list of advices (ToDoList) is not the same * as the list of element known by the FigNode (_figs). Therefore, * it is necessary to check if the graphic item exists before drawing * on it. See ClAttributeCompartment for an example. * @param g the graphics device * @see org.argouml.uml.cognitive.critics.ClAttributeCompartment */ public void paintClarifiers(Graphics g) { // TODO: Generalize extension and remove critic specific stuff int iconX = getX(); int iconY = getY() - 10; ToDoList tdList = Designer.theDesigner().getToDoList(); List<ToDoItem> items = tdList.elementListForOffender(getOwner()); for (ToDoItem item : items) { Icon icon = item.getClarifier(); if (icon instanceof Clarifier) { ((Clarifier) icon).setFig(this); ((Clarifier) icon).setToDoItem(item); } if (icon != null) { icon.paintIcon(null, g, iconX, iconY); iconX += icon.getIconWidth(); } } items = tdList.elementListForOffender(this); for (ToDoItem item : items) { Icon icon = item.getClarifier(); if (icon instanceof Clarifier) { ((Clarifier) icon).setFig(this); ((Clarifier) icon).setToDoItem(item); } if (icon != null) { icon.paintIcon(null, g, iconX, iconY); iconX += icon.getIconWidth(); } } } /** * @param x the x of the hit * @param y the y of the hit * @return the todo item of which the clarifier has been hit */ protected ToDoItem hitClarifier(int x, int y) { // TODO: ToDoItem stuff should be made an opaque extension int iconX = getX(); ToDoList tdList = Designer.theDesigner().getToDoList(); List<ToDoItem> items = tdList.elementListForOffender(getOwner()); for (ToDoItem item : items) { Icon icon = item.getClarifier(); int width = icon.getIconWidth(); if (y >= getY() - 15 && y <= getY() + 10 && x >= iconX && x <= iconX + width) { return item; } iconX += width; } for (ToDoItem item : items) { Icon icon = item.getClarifier(); if (icon instanceof Clarifier) { ((Clarifier) icon).setFig(this); ((Clarifier) icon).setToDoItem(item); if (((Clarifier) icon).hit(x, y)) { return item; } } } items = tdList.elementListForOffender(this); for (ToDoItem item : items) { Icon icon = item.getClarifier(); int width = icon.getIconWidth(); if (y >= getY() - 15 && y <= getY() + 10 && x >= iconX && x <= iconX + width) { return item; } iconX += width; } for (ToDoItem item : items) { Icon icon = item.getClarifier(); if (icon instanceof Clarifier) { ((Clarifier) icon).setFig(this); ((Clarifier) icon).setToDoItem(item); if (((Clarifier) icon).hit(x, y)) { return item; } } } return null; } /* * @see org.tigris.gef.presentation.Fig#getTipString(java.awt.event.MouseEvent) */ @Override public String getTipString(MouseEvent me) { // TODO: Generalize extension and remove critic specific code ToDoItem item = hitClarifier(me.getX(), me.getY()); String tip = ""; if (item != null && Globals.curEditor().getSelectionManager().containsFig(this)) { tip = item.getHeadline() + " "; } else if (getOwner() != null) { tip = Model.getFacade().getTipString(getOwner()); } else { tip = toString(); } if (tip != null && tip.length() > 0 && !tip.endsWith(" ")) { tip += " "; } return tip; } //////////////////////////////////////////////////////////////// // event handlers /* * @see java.beans.VetoableChangeListener#vetoableChange(java.beans.PropertyChangeEvent) */ public void vetoableChange(PropertyChangeEvent pce) { LOG.debug("in vetoableChange"); Object src = pce.getSource(); if (src == getOwner()) { DelayedChangeNotify delayedNotify = new DelayedChangeNotify(this, pce); SwingUtilities.invokeLater(delayedNotify); } else { LOG.debug("FigNodeModelElement got vetoableChange" + " from non-owner:" + src); } } /* * @see org.argouml.kernel.DelayedVChangeListener#delayedVetoableChange(java.beans.PropertyChangeEvent) */ public void delayedVetoableChange(PropertyChangeEvent pce) { LOG.debug("in delayedVetoableChange"); // update any text, colors, fonts, etc. renderingChanged(); endTrans(); } /** * Determine new bounds. <p> * * This algorithm makes the box grow * (if the calculated minimum size grows), * but then it can never shrink again * (not even if the calculated minimum size is smaller).<p> * * If the user can not resize the fig, e.g. like the FigActor or FigFinalState, * then we return the original size. */ protected void updateBounds() { if (!checkSize) { return; } Rectangle bbox = getBounds(); Dimension minSize = getMinimumSize(); if (isResizable()) { bbox.width = Math.max(bbox.width, minSize.width); bbox.height = Math.max(bbox.height, minSize.height); } /* Only update the bounds if they change: */ if (bbox.x != getX() || bbox.y != getY() || bbox.width != getWidth() || bbox.height != getHeight()) { setBounds(bbox.x, bbox.y, bbox.width, bbox.height); } } /* * @see java.beans.PropertyChangeListener#propertyChange(java.beans.PropertyChangeEvent) */ public void propertyChange(final PropertyChangeEvent pve) { final Object src = pve.getSource(); final String pName = pve.getPropertyName(); if (pve instanceof DeleteInstanceEvent && src == getOwner()) { removeFromDiagram(); return; } // We are getting events we don't want. Filter them out. if (pve.getPropertyName().equals("supplierDependency") && Model.getFacade().isADependency(pve.getOldValue())) { // TODO: Can we instruct the model event pump not to send these in // the first place? See defect 5095. return; } // We handle and consume editing events if (pName.equals("editing") && Boolean.FALSE.equals(pve.getNewValue())) { try { //parse the text that was edited textEdited((FigText) src); // resize the FigNode to accommodate the new text final Rectangle bbox = getBounds(); final Dimension minSize = getMinimumSize(); bbox.width = Math.max(bbox.width, minSize.width); bbox.height = Math.max(bbox.height, minSize.height); setBounds(bbox.x, bbox.y, bbox.width, bbox.height); endTrans(); } catch (PropertyVetoException ex) { LOG.error("could not parse the text entered. " + "PropertyVetoException", ex); } } else if (pName.equals("editing") && Boolean.TRUE.equals(pve.getNewValue())) { if (!isReadOnly()) { textEditStarted((FigText) src); } } else { super.propertyChange(pve); } if (Model.getFacade().isAUMLElement(src)) { final UmlChangeEvent event = (UmlChangeEvent) pve; /* If the source of the event is an UML object, * e.g. the owner of this Fig (but not always only the owner * is shown, e.g. for a class, also its attributes are shown), * then the UML model has been changed. */ final Object owner = getOwner(); if (owner == null) { // TODO: Should this not be an assert? return; } try { modelChanged(event); } catch (InvalidElementException e) { if (LOG.isDebugEnabled()) { LOG.debug("modelChanged method accessed deleted element" + formatEvent(event), e); } } if (event.getSource() == owner && "stereotype".equals(event.getPropertyName())) { stereotypeChanged(event); } Runnable doWorkRunnable = new Runnable() { public void run() { try { updateLayout(event); } catch (InvalidElementException e) { if (LOG.isDebugEnabled()) { LOG.debug("updateLayout method accessed " + "deleted element " + formatEvent(event), e); } } } }; SwingUtilities.invokeLater(doWorkRunnable); } } private String formatEvent(PropertyChangeEvent event) { return "\n\t event = " + event.getClass().getName() + "\n\t source = " + event.getSource() + "\n\t old = " + event.getOldValue() + "\n\t name = " + event.getPropertyName(); } /** * Return true if the model element that this Fig represents is read only * @return The model element is read only. */ private boolean isReadOnly() { try { return Model.getModelManagementHelper().isReadOnly(getOwner()); } catch (InvalidElementException e) { return true; } } /** * Called by propertyChanged when it detects that a stereotype * has been added or removed. On removal the FigNode removes its * listener to that stereotype. When a new stereotype is detected * we add a listener. * TODO: Bob says: In my opinion we shouldn't be doing this here. * FigStereotype should always be listening to change of its * owners name. * FigStereotypesCompartment should always be listening for add * or remove of Stereotypes to its owner. * Those classes will need to pass some event to the FigNode on * the AWT thread only if a change results in a change of size * that requires a redraw. * <p>NOTE: Runs at the Model (MDR) Thread </p> * @param event the UmlChangeEvent that caused the change */ private void stereotypeChanged(final UmlChangeEvent event) { final Object owner = getOwner(); assert owner != null; try { if (event.getOldValue() != null) { removeElementListener(event.getOldValue()); } if (event.getNewValue() != null) { addElementListener(event.getNewValue(), "name"); } } catch (InvalidElementException e) { LOG.debug("stereotypeChanged method accessed deleted element ", e); } } /** * This method is called when the user doubleclicked on the text field, * and starts editing. Subclasses should overrule this field to e.g. * supply help to the user about the used format. <p> * * It is also possible to alter the text to be edited * already here, e.g. by adding the stereotype in front of the name, * by using setFullyHandleStereotypes(true) in the NotationSettings * argument of the NotationProvider.toString() function, * but that seems not user-friendly. See issue 3838. * * @param ft the FigText that will be edited and contains the start-text */ protected void textEditStarted(FigText ft) { if (ft == getNameFig()) { showHelp(notationProviderName.getParsingHelp()); ft.setText(notationProviderName.toString(getOwner(), getNotationSettings())); } if (ft instanceof CompartmentFigText) { final CompartmentFigText figText = (CompartmentFigText) ft; showHelp(figText.getNotationProvider().getParsingHelp()); figText.setText(figText.getNotationProvider().toString( figText.getOwner(), getNotationSettings())); } } /** * Utility function to localize the given string with help text, and show it * in the status bar of the ArgoUML window. This function is used in favour * of the inline call to enable later improvements; e.g. it would be * possible to show a help-balloon. * <p> * TODO: Work this out. One matter to possibly improve: show multiple lines. * * @param s the given string to be localized and shown */ protected void showHelp(String s) { if (s == null) { // Convert null to empty string and clear help message ArgoEventPump.fireEvent(new ArgoHelpEvent( ArgoEventTypes.HELP_CHANGED, this, "")); } else { ArgoEventPump.fireEvent(new ArgoHelpEvent( ArgoEventTypes.HELP_CHANGED, this, Translator.localize(s))); } } /** * This method is called after the user finishes editing a text * field that is in the FigNodeModelElement. Determine which * field and update the model. This class handles the name, * the stereotype, and any CompartmentFigTexts. * Subclasses should override to handle other text elements. * * @param ft the FigText that has been edited and contains the new text * @throws PropertyVetoException thrown when new text represents * an unacceptable value */ protected void textEdited(FigText ft) throws PropertyVetoException { if (ft == nameFig) { // TODO: Can we delegate this to a specialist FigName class? if (getOwner() == null) { return; } notationProviderName.parse(getOwner(), ft.getText()); ft.setText(notationProviderName.toString(getOwner(), getNotationSettings())); } if (ft instanceof CompartmentFigText) { final CompartmentFigText figText = (CompartmentFigText) ft; figText.getNotationProvider().parse(ft.getOwner(), ft.getText()); ft.setText(figText.getNotationProvider().toString( ft.getOwner(), getNotationSettings())); } } //////////////////////////////////////////////////////////////// // event handlers - MouseListener implementation /* * If the user double clicks on any part of this FigNode, pass it * down to one of the internal Figs. This allows the user to * initiate direct text editing. * * @see java.awt.event.MouseListener#mouseClicked(java.awt.event.MouseEvent) */ @Override public void mouseClicked(MouseEvent me) { if (!readyToEdit) { if (Model.getFacade().isAModelElement(getOwner())) { // TODO: Why is this clearing the name?!?! - tfm Model.getCoreHelper().setName(getOwner(), ""); readyToEdit = true; } else { LOG.debug("not ready to edit name"); return; } } if (me.isConsumed()) { return; } if (me.getClickCount() >= 2 && !(me.isPopupTrigger() || me.getModifiers() == InputEvent.BUTTON3_MASK) && getOwner() != null && !isReadOnly()) { Rectangle r = new Rectangle(me.getX() - 2, me.getY() - 2, 4, 4); Fig f = hitFig(r); if (f instanceof MouseListener && f.isVisible()) { ((MouseListener) f).mouseClicked(me); } else if (f instanceof FigGroup && f.isVisible()) { //this enables direct text editing for sub figs of a //FigGroup object: Fig f2 = ((FigGroup) f).hitFig(r); if (f2 instanceof MouseListener) { ((MouseListener) f2).mouseClicked(me); } else { createContainedModelElement((FigGroup) f, me); } } } } /* * @see java.awt.event.KeyListener#keyPressed(java.awt.event.KeyEvent) */ public void keyPressed(KeyEvent ke) { if (ke.isConsumed() || getOwner() == null) { return; } nameFig.keyPressed(ke); } /* * @see java.awt.event.KeyListener#keyReleased(java.awt.event.KeyEvent) */ public void keyReleased(KeyEvent ke) { if (ke.isConsumed() || getOwner() == null) { return; } nameFig.keyReleased(ke); } /* * @see java.awt.event.KeyListener#keyTyped(java.awt.event.KeyEvent) */ public void keyTyped(KeyEvent ke) { if (!editable || isReadOnly()) { return; } if (!readyToEdit) { if (Model.getFacade().isAModelElement(getOwner())) { Model.getCoreHelper().setName(getOwner(), ""); readyToEdit = true; } else { LOG.debug("not ready to edit name"); return; } } if (ke.isConsumed() || getOwner() == null) { return; } nameFig.keyTyped(ke); } /** * This is a template method called by the ArgoUML framework as the result * of a change to a model element. Do not call this method directly * yourself. * <p>Override this in any subclasses in order to change what model * elements the FigNode is listening to as a result of change to the model. * </p> * <p>This method is guaranteed by the framework to be running on the same * thread as the model subsystem.</p> * TODO: Lets refactor this at some time to take UmlChangeEvent argument * * @param event the UmlChangeEvent that caused the change */ protected void modelChanged(PropertyChangeEvent event) { if (event instanceof AssociationChangeEvent || event instanceof AttributeChangeEvent) { // TODO: This brute force approach of updating listeners on each // and every event, without checking the event type or any other // information is going to cause lots of InvalidElementExceptions // in subclasses implementations of updateListeners (and they // won't have the event information to make their own decisions) updateListeners(getOwner(), getOwner()); } } //////////////////////////////////////////////////////////////// // internal methods /** * This is a template method called by the ArgoUML framework as the result * of a change to a model element. Do not call this method directly * yourself. * <p>Override this in any subclasses in order to restructure the FigNode * due to change of any model element that this FigNode is listening to.</p> * <p>This method automatically updates the stereotype rendering.</p> * <p>The default behavior is to update the name and stereotype text.</p> * <p>For e.g. a Package, if the visibility is changed * via the properties panel, then * the display of it on the diagram has to follow the change. * This is not handled here, but by the notationProviderName.</p> * <p>This method is guaranteed by the framework to be running on the * Swing/AWT thread.</p> * * @param event the UmlChangeEvent that caused the change */ protected void updateLayout(UmlChangeEvent event) { assert event != null; final Object owner = getOwner(); assert owner != null; if (owner == null) { return; } boolean needDamage = false; if (event instanceof AssociationChangeEvent || event instanceof AttributeChangeEvent) { if (notationProviderName != null) { updateNameText(); } needDamage = true; } if (event.getSource() == owner && "stereotype".equals(event.getPropertyName())) { updateStereotypeText(); updateStereotypeIcon(); needDamage = true; } if (needDamage) { damage(); } } /** * Create a new model element contained in the fig owner. * Used by subclasses to, for example, create an attribute * within a class. * * @param fg The fig group to which this applies * @param me The input event that triggered us. In the current * implementation a mouse double click. */ protected void createContainedModelElement(FigGroup fg, InputEvent me) { // must be overridden to make sense // (I didn't want to make it abstract because it might not be required) } /** * @param o the given object * @return true if one of my figs has the given object as owner */ protected boolean isPartlyOwner(Object o) { if (o == null || o == getOwner()) { return true; } for (Object fig : getFigs()) { if (isPartlyOwner((Fig) fig, o)) { return true; } } return false; } /** * @param fig the given fig (may be a group) * @param o the given object * @return true if one of the given figs has the given object as owner */ protected boolean isPartlyOwner(Fig fig, Object o) { if (o == null) { return false; } if (o == fig.getOwner()) { return true; } if (fig instanceof FigGroup) { for (Object fig2 : ((FigGroup) fig).getFigs()) { if (isPartlyOwner((Fig) fig2, o)) { return true; } } } return false; } /* * @see org.tigris.gef.presentation.Fig#deleteFromModel() */ @Override public void deleteFromModel() { Object own = getOwner(); if (own != null) { getProject().moveToTrash(own); } for (Object fig : getFigs()) { ((Fig) fig).deleteFromModel(); } super.deleteFromModel(); } /** * Replace the NotationProvider(s). <p> * * This method shall not be used for the initial creation of * notation providers, but only for replacing them when required. * Initialization must be done in the * constructor using methods which * can't be overridden. <p> * NotationProviders can not be updated - they * are lightweight throw-away objects. * Hence this method creates a (new) NotationProvider whenever * needed. E.g. when the notation language is * changed by the user, then the NPs are to be re-created. * So, this method shall not be * called from a Fig constructor.<p> * * After the removal of the deprecated method setOwner(), * this method shall contain the following statement: * assert notationProviderName != null * * @param own owning UML element */ protected void initNotationProviders(Object own) { if (notationProviderName != null) { notationProviderName.cleanListener(); } if (Model.getFacade().isAUMLElement(own)) { NotationName notation = Notation.findNotation( getNotationSettings().getNotationLanguage()); notationProviderName = NotationProviderFactory2.getInstance().getNotationProvider( getNotationProviderType(), own, this, notation); } } /** * Overrule this for subclasses * that need a different NotationProvider. * * @return the type of the notation provider */ protected int getNotationProviderType() { return NotationProviderFactory2.TYPE_NAME; } /** * Updates the text of the stereotype FigText. Override in subclasses to get * wanted behaviour. */ protected void updateStereotypeText() { if (getOwner() == null) { LOG.warn("Null owner for [" + this.toString() + "/" + this.getClass()); return; } if (getStereotypeFig() != null) { getStereotypeFig().populate(); } } /** * Updates the text of the name FigText. * This includes text changes, * but also changes in rendering like bold. */ protected void updateNameText() { if (readyToEdit) { if (getOwner() == null) { return; } if (notationProviderName != null) { nameFig.setText(notationProviderName.toString( getOwner(), getNotationSettings())); updateFont(); updateBounds(); } } } /* * @see org.argouml.uml.diagram.ui.PathContainer#isPathVisible() */ public boolean isPathVisible() { return getNotationSettings().isShowPaths(); } /* * @see org.argouml.uml.diagram.ui.PathContainer#setPathVisible(boolean) */ public void setPathVisible(boolean visible) { NotationSettings ns = getNotationSettings(); if (ns.isShowPaths() == visible) { return; } MutableGraphSupport.enableSaveAction(); // TODO: Use this event mechanism to update // the checkmark on the Presentation Tab: firePropChange("pathVisible", !visible, visible); ns.setShowPaths(visible); if (readyToEdit) { renderingChanged(); damage(); } } /** * At creation time of the Fig, we determine * if the path should be visible by default. <p> * * The path is a concatenation of the names of all packages by which * this modelelement is contained, * seperated by "::" (for UML at least). <p> * * If the default namespace of the diagram corresponds * to the namespace of the modelelement, * then we do NOT show the path. Otherwise, we do. <p> * * RRose uses the same heuristic algorithm, * but shows "(from <path>)" below the name, * while we follow the UML syntax. */ protected void determineDefaultPathVisible() { Object modelElement = getOwner(); LayerPerspective layer = (LayerPerspective) getLayer(); if ((layer != null) && Model.getFacade().isAModelElement(modelElement)) { ArgoDiagram diagram = (ArgoDiagram) layer.getDiagram(); Object elementNs = Model.getFacade().getNamespace(modelElement); Object diagramNs = diagram.getNamespace(); if (elementNs != null) { boolean visible = (elementNs != diagramNs); getNotationSettings().setShowPaths(visible); updateNameText(); damage(); } // it is done } // either layer or owner was null } /* * @see org.tigris.gef.presentation.Fig#classNameAndBounds() */ @Deprecated @Override public String classNameAndBounds() { return getClass().getName() + "[" + getX() + ", " + getY() + ", " + getWidth() + ", " + getHeight() + "]" + "pathVisible=" + isPathVisible() + ";" + "stereotypeView=" + getStereotypeView() + ";"; } /** * Implementations of this method should register/unregister the fig for all * (model)events. For FigNodeModelElement only the fig itself is registered * as listening to (all) events fired by the owner itself. * But for, for example, * FigClass the fig must also register for events fired by the operations * and attributes of the owner. <p> * * An explanation of the original * purpose of this method is given in issue 1321.<p> * * This function is used by the modelChanged() * function.<p> * * In certain cases, it is imperative that indeed ALL listeners are * updated, since they are ALL removed * by a call to removeElementListener. <p> * * IF this method is called with both the oldOwner and the * newOwner equal and not null, * AND we listen only to the owner itself, * THEN we can safely ignore the call, but * ELSE we need to update the listeners of the related elements, * since the related elements may have been replaced. * * @param newOwner null, or the owner of this. * The former means that all listeners have to be removed. * @param oldOwner null, or the previous owner * The former means that all listeners have to be set. */ protected void updateListeners(Object oldOwner, Object newOwner) { if (oldOwner == newOwner) { return; } if (oldOwner != null) { removeElementListener(oldOwner); } if (newOwner != null) { addElementListener(newOwner); } } /** * @see org.argouml.application.events.ArgoNotationEventListener#notationChanged(org.argouml.application.events.ArgoNotationEvent) * @deprecated for 0.27.2 by tfmorris. Changes to notatation provider are * now handled by the owning diagram. */ @Deprecated public void notationChanged(ArgoNotationEvent event) { if (getOwner() == null) { return; } try { renderingChanged(); } catch (Exception e) { LOG.error("Exception", e); } } /** * @see org.argouml.application.events.ArgoNotationEventListener#notationAdded(org.argouml.application.events.ArgoNotationEvent) * @deprecated for 0.27.2 by tfmorris. */ @Deprecated public void notationAdded(ArgoNotationEvent event) { // Default is to do nothing } /** * @see org.argouml.application.events.ArgoNotationEventListener#notationRemoved(org.argouml.application.events.ArgoNotationEvent) * @deprecated for 0.27.2 by tfmorris. */ @Deprecated public void notationRemoved(ArgoNotationEvent event) { // Default is to do nothing } /** * @see org.argouml.application.events.ArgoNotationEventListener#notationProviderAdded(org.argouml.application.events.ArgoNotationEvent) * @deprecated for 0.27.2 by tfmorris. */ @Deprecated public void notationProviderAdded(ArgoNotationEvent event) { // Default is to do nothing } /** * @see org.argouml.application.events.ArgoNotationEventListener#notationProviderRemoved(org.argouml.application.events.ArgoNotationEvent) * @deprecated for 0.27.2 by tfmorris. */ @Deprecated public void notationProviderRemoved(ArgoNotationEvent event) { // Default is to do nothing } /** * Rerender the entire fig. * <p> * This is may be an expensive operation for subclasses which are complex, * so should be used sparingly. This functionality was originally the * functionality of modelChanged but modelChanged takes the event now into * account. */ public void renderingChanged() { initNotationProviders(getOwner()); updateNameText(); updateStereotypeText(); updateStereotypeIcon(); updateBounds(); damage(); } protected void updateStereotypeIcon() { if (getOwner() == null) { LOG.warn("Owner of [" + this.toString() + "/" + this.getClass() + "] is null."); LOG.warn("I return..."); return; } if (stereotypeFigProfileIcon != null) { for (Object fig : getFigs()) { ((Fig) fig).setVisible(fig != stereotypeFigProfileIcon); } this.removeFig(stereotypeFigProfileIcon); stereotypeFigProfileIcon = null; } if (originalNameFig != null) { this.setNameFig(originalNameFig); originalNameFig = null; } for (Fig icon : floatingStereotypes) { this.removeFig(icon); } floatingStereotypes.clear(); int practicalView = getPracticalView(); Object modelElement = getOwner(); Collection stereos = Model.getFacade().getStereotypes(modelElement); boolean hiding = practicalView == DiagramAppearance.STEREOTYPE_VIEW_SMALL_ICON; if (getStereotypeFig() != null) { getStereotypeFig().setHidingStereotypesWithIcon(hiding); } if (practicalView == DiagramAppearance.STEREOTYPE_VIEW_BIG_ICON) { Image replaceIcon = null; if (stereos.size() == 1) { Object stereo = stereos.iterator().next(); // TODO: Find a way to replace this dependency on Project replaceIcon = getProject() .getProfileConfiguration().getFigNodeStrategy() .getIconForStereotype(stereo); } if (replaceIcon != null) { stereotypeFigProfileIcon = new FigProfileIcon(settings, replaceIcon, getName()); stereotypeFigProfileIcon.setOwner(getOwner()); stereotypeFigProfileIcon.setLocation(getBigPort() .getLocation()); addFig(stereotypeFigProfileIcon); originalNameFig = this.getNameFig(); final FigText labelFig = stereotypeFigProfileIcon.getLabelFig(); setNameFig(labelFig); labelFig.addPropertyChangeListener(this); getBigPort().setBounds(stereotypeFigProfileIcon.getBounds()); for (Object fig : getFigs()) { ((Fig) fig).setVisible(fig == stereotypeFigProfileIcon); } } } else if (practicalView == DiagramAppearance.STEREOTYPE_VIEW_SMALL_ICON) { int i = this.getX() + this.getWidth() - ICON_WIDTH - 2; for (Object stereo : stereos) { // TODO: Find a way to replace this dependency on Project Image icon = getProject() .getProfileConfiguration().getFigNodeStrategy() .getIconForStereotype(stereo); if (icon != null) { FigImage fimg = new FigImage(i, this.getBigPort().getY() + 2, icon); fimg.setSize(ICON_WIDTH, (icon.getHeight(null) * ICON_WIDTH) / icon.getWidth(null)); addFig(fimg); floatingStereotypes.add(fimg); i -= ICON_WIDTH - 2; } } updateSmallIcons(this.getWidth()); } // TODO: This is a redundant invocation updateStereotypeText(); damage(); calcBounds(); updateEdges(); updateBounds(); redraw(); } /* * @see org.tigris.gef.presentation.Fig#calcBounds() */ public void calcBounds() { if (suppressCalcBounds) { return; } super.calcBounds(); } /** * The setter for checkSize. * * @param flag the new value */ public void enableSizeChecking(boolean flag) { checkSize = flag; } /* * Necessary since GEF contains some errors regarding the hit subject. * * @see org.tigris.gef.presentation.Fig#hit(Rectangle) */ @Override public boolean hit(Rectangle r) { int cornersHit = countCornersContained(r.x, r.y, r.width, r.height); if (_filled) { return cornersHit > 0 || intersects(r); } return (cornersHit > 0 && cornersHit < 4) || intersects(r); } /* * @see org.tigris.gef.presentation.Fig#removeFromDiagram() */ @Override public final void removeFromDiagram() { Fig delegate = getRemoveDelegate(); if (delegate instanceof FigNodeModelElement) { ((FigNodeModelElement) delegate).removeFromDiagramImpl(); } else if (delegate instanceof FigEdgeModelElement) { ((FigEdgeModelElement) delegate).removeFromDiagramImpl(); } else if (delegate != null) { removeFromDiagramImpl(); } } /** * Subclasses should override this to redirect a remove request from * one Fig to another. * e.g. FigClassAssociationClass uses this to delegate the remove to * its attached FigAssociationClass. * @return the fig that handles the remove request */ protected Fig getRemoveDelegate() { return this; } /** * If you override this method, make sure to remove all listeners: * If you don't, objects in a deleted project will still receive events.<p> * * Also important for remove from diagram! */ protected void removeFromDiagramImpl() { if (notationProviderName != null) { //This test needed for a FigPool notationProviderName.cleanListener(); } removeAllElementListeners(); setShadowSize(0); super.removeFromDiagram(); // Get model listeners removed: if (getStereotypeFig() != null) { getStereotypeFig().removeFromDiagram(); } } /** * Get the Fig containing the stereotype(s). * * @return the stereotype FigGroup */ protected FigStereotypesGroup getStereotypeFig() { return stereotypeFig; } /** * @param bp the bigPort, which is the port where edges * connect to this node * @deprecated by MVW since V0.28.1. Use {@link #createBigPortFig} * instead, to guarantee correct initialization. */ protected void setBigPort(Fig bp) { this.bigPort = bp; bindPort(getOwner(), bigPort); } /** * @return the fig which is the port where edges connect to this node */ public Fig getBigPort() { return bigPort; } /** * @return Returns the checkSize. */ protected boolean isCheckSize() { return checkSize; } /* * @see org.tigris.gef.presentation.FigNode#isDragConnectable() */ public boolean isDragConnectable() { return false; } /** * @param e The _encloser to set. */ protected void setEncloser(Fig e) { this.encloser = e; } /** * @return Returns the _encloser. */ protected Fig getEncloser() { return encloser; } /** * @return Returns the ReadyToEdit. */ protected boolean isReadyToEdit() { return readyToEdit; } /** * @param v if ready to edit */ protected void setReadyToEdit(boolean v) { readyToEdit = v; } /** * TODO: Move this in FigGroup (in GEF). * * @param scb The suppressCalcBounds to set. */ protected void setSuppressCalcBounds(boolean scb) { this.suppressCalcBounds = scb; } /** * Set visibility of figure. If the field {@link #invisibleAllowed} is not * <code>true</code> and this method is passed a parameter of * <code>false</code> it will throw an IllegalArgumentException. * * @param visible * new visibility - <code>true</code> = visible. * * @see org.tigris.gef.presentation.Fig#setVisible(boolean) */ public void setVisible(boolean visible) { if (!visible && !invisibleAllowed) { throw new IllegalArgumentException( "Visibility of a FigNode should never be false"); } } /** * To redraw each element correctly when changing its location * with X and Y additions. Also manages relocation of enclosed * Figs. * * @param xInc the increment in the x direction * @param yInc the increment in the y direction */ public void displace (int xInc, int yInc) { List<Fig> figsVector; Rectangle rFig = getBounds(); setLocation(rFig.x + xInc, rFig.y + yInc); figsVector = ((List<Fig>) getEnclosedFigs().clone()); if (!figsVector.isEmpty()) { for (int i = 0; i < figsVector.size(); i++) { ((FigNodeModelElement) figsVector.get(i)) .displace(xInc, yInc); } } } /** * @param allowed true if the function RemoveFromDiagram is allowed */ protected void allowRemoveFromDiagram(boolean allowed) { this.removeFromDiagram = allowed; } public void setDiElement(DiElement element) { this.diElement = element; } public DiElement getDiElement() { return diElement; } /** * @return Returns the popupAddOffset. */ protected static int getPopupAddOffset() { return popupAddOffset; } /** * Determine if this node can be edited. * @return editable state */ public boolean isEditable() { return editable; } /** * By default a node is directly editable by simply selecting * that node and starting to type. * Should a subclass of FigNodeModelElement not desire this behaviour * then it should call setEditable(false) in its constructor. * * @param canEdit new state, false = editing disabled. */ protected void setEditable(boolean canEdit) { this.editable = canEdit; } /** * Add an element listener and remember the registration. * * @param element * element to listen for changes on * @see org.argouml.model.ModelEventPump#addModelEventListener(PropertyChangeListener, Object, String) */ protected void addElementListener(Object element) { listeners.add(new Object[] {element, null}); Model.getPump().addModelEventListener(this, element); } /** * Add a listener for a given property name and remember the registration. * * @param element * element to listen for changes on * @param property * name of property to listen for changes of * @see org.argouml.model.ModelEventPump#addModelEventListener(PropertyChangeListener, * Object, String) */ protected void addElementListener(Object element, String property) { listeners.add(new Object[] {element, property}); Model.getPump().addModelEventListener(this, element, property); } /** * Add a listener for an array of property names and remember the * registration. * * @param element * element to listen for changes on * @param property * array of property names (Strings) to listen for changes of * @see org.argouml.model.ModelEventPump#addModelEventListener(PropertyChangeListener, * Object, String) */ protected void addElementListener(Object element, String[] property) { listeners.add(new Object[] {element, property}); Model.getPump().addModelEventListener(this, element, property); } /** * Remove an element listener and remembered registration. * * @param element * element to listen for changes on * @see org.argouml.model.ModelEventPump#addModelEventListener(PropertyChangeListener, Object, String) */ protected void removeElementListener(Object element) { listeners.remove(new Object[] {element, null}); Model.getPump().removeModelEventListener(this, element); } /** * Unregister all listeners registered through addElementListener * @see #addElementListener(Object, String) */ protected void removeAllElementListeners() { removeElementListeners(listeners); } private void removeElementListeners(Set<Object[]> listenerSet) { for (Object[] listener : listenerSet) { Object property = listener[1]; if (property == null) { Model.getPump().removeModelEventListener(this, listener[0]); } else if (property instanceof String[]) { Model.getPump().removeModelEventListener(this, listener[0], (String[]) property); } else if (property instanceof String) { Model.getPump().removeModelEventListener(this, listener[0], (String) property); } else { throw new RuntimeException( "Internal error in removeAllElementListeners"); } } listeners.removeAll(listenerSet); } private void addElementListeners(Set<Object[]> listenerSet) { for (Object[] listener : listenerSet) { Object property = listener[1]; if (property == null) { addElementListener(listener[0]); } else if (property instanceof String[]) { addElementListener(listener[0], (String[]) property); } else if (property instanceof String) { addElementListener(listener[0], (String) property); } else { throw new RuntimeException( "Internal error in addElementListeners"); } } } /** * Update the set of registered listeners to match the given set using * a minimal update strategy to remove unneeded listeners and add new * listeners. * * @param listenerSet a set of arrays containing a tuple of a UML element * to be listened to and a set of property to be listened for. */ protected void updateElementListeners(Set<Object[]> listenerSet) { Set<Object[]> removes = new HashSet<Object[]>(listeners); removes.removeAll(listenerSet); removeElementListeners(removes); Set<Object[]> adds = new HashSet<Object[]>(listenerSet); adds.removeAll(listeners); addElementListeners(adds); } /** * This optional method is not implemented. It will throw an * {@link UnsupportedOperationException} if used. Figs are * added to a GraphModel which is, in turn, owned by a project.<p> * * @param project the project * @deprecated */ @SuppressWarnings("deprecation") @Deprecated public void setProject(Project project) { throw new UnsupportedOperationException(); } /** * @deprecated for 0.27.2 by tfmorris. Implementations should have all * the information that they require in the DiagramSettings object. * * @return the owning project * @see org.argouml.uml.diagram.ui.ArgoFig#getProject() */ @Deprecated public Project getProject() { return ArgoFigUtil.getProject(this); } /** * Determine if this Fig is the sole selected target in * the TargetManager * @return true if this is the sole target. */ protected boolean isSingleTarget() { return TargetManager.getInstance().getSingleModelTarget() == getOwner(); } /** * @return current stereotype view * @deprecated for 0.27.2 by tfmorris. Use {@link #getStereotypeStyle()}. */ public int getStereotypeView() { return stereotypeStyle.ordinal(); } /** * @return * @see org.argouml.uml.diagram.ui.StereotypeStyled#getStereotypeStyle() */ public StereotypeStyle getStereotypeStyle() { return stereotypeStyle; } /** * Return a stereotype view which is most practical for the current * conditions. If the current mode is set to <code>BigIcon</code> mode and * the model element has zero or more than one stereotype the practical view * should be the textual view. * * @return current practical stereotype view */ private int getPracticalView() { // TODO assert modelElement != null??? int practicalView = getStereotypeView(); Object modelElement = getOwner(); if (modelElement != null) { int stereotypeCount = getStereotypeCount(); if (getStereotypeView() == DiagramAppearance.STEREOTYPE_VIEW_BIG_ICON) { // TODO: Find a way to replace this dependency on Project FigNodeStrategy figNodeStrategy = getProject() .getProfileConfiguration().getFigNodeStrategy(); Iterator<FigStereotype> figsIterator = getStereotypeFig() .getStereotypeFigs().iterator(); Object owner = figsIterator.hasNext() ? figsIterator.next().getOwner() : null; if (stereotypeCount != 1 || figNodeStrategy == null || owner == null || (stereotypeCount == 1 && figNodeStrategy.getIconForStereotype(owner) == null)) { practicalView = DiagramAppearance.STEREOTYPE_VIEW_TEXTUAL; } } } return practicalView; } /** * Get the number of stereotypes contained in this FigNode * @return the number of stereotypes contained in the FigNode */ public int getStereotypeCount() { if (getStereotypeFig() == null) { return 0; } return getStereotypeFig().getStereotypeCount(); } /** * Sets the stereotype view. * * @param s the stereotype view to be set * @deprecated for 0.27.2 by tfmorris. Use * {@link #setStereotypeStyle(StereotypeStyle)}. */ public void setStereotypeView(int s) { setStereotypeStyle(StereotypeStyle.getEnum(s)); } /** * Set the stereotype style to be used for rendering this fig. * * @param style the stereotype style to be set */ public void setStereotypeStyle(StereotypeStyle style) { stereotypeStyle = style; renderingChanged(); } /** * Sets the bounds of this node taking the stereotype view into * consideration.<br> * * Do not override this method, override * {@link #setStandardBounds(int, int, int, int)} instead. * * {@inheritDoc} */ @Override protected void setBoundsImpl(final int x, final int y, final int w, final int h) { if (getPracticalView() == DiagramAppearance.STEREOTYPE_VIEW_BIG_ICON) { if (stereotypeFigProfileIcon != null) { stereotypeFigProfileIcon.setBounds(stereotypeFigProfileIcon .getX(), stereotypeFigProfileIcon.getY(), w, h); // FigClass calls setBoundsImpl before we set // the stereotypeFigProfileIcon } } else { setStandardBounds(x, y, w, h); if (getStereotypeView() == DiagramAppearance.STEREOTYPE_VIEW_SMALL_ICON) { updateSmallIcons(w); } } } private void updateSmallIcons(int wid) { int i = this.getX() + wid - ICON_WIDTH - 2; for (Fig ficon : floatingStereotypes) { ficon.setLocation(i, this.getBigPort().getY() + 2); i -= ICON_WIDTH - 2; } getNameFig().setRightMargin( floatingStereotypes.size() * (ICON_WIDTH + 5)); } /** * Returns the minimum size of the Fig. This is the smallest size that the * user can make this Fig by dragging. <p> * * Do not call this function if the Fig is not resizable! * In ArgoUML we decided that it is not needed to implement * suitable getMinimumSize() methods * for Figs that are not resizable. */ @Override public Dimension getMinimumSize() { assert isResizable(); return super.getMinimumSize(); } /** * Replaces {@link #setBoundsImpl(int, int, int, int)}. * * @param x Desired X coordinate of upper left corner * @param y Desired Y coordinate of upper left corner * @param w Desired width of the FigClass * @param h Desired height of the FigClass * @see org.tigris.gef.presentation.Fig#setBoundsImpl(int, int, int, int) */ protected void setStandardBounds(final int x, final int y, final int w, final int h) { } /** * Handles diagram font changing. * @param e the event or null * @see org.argouml.application.events.ArgoDiagramAppearanceEventListener#diagramFontChanged(org.argouml.application.events.ArgoDiagramAppearanceEvent) * @deprecated for 0.27.2 by tfmorris. The owning diagram manages global * changes to rendering defaults. */ public void diagramFontChanged(ArgoDiagramAppearanceEvent e) { updateFont(); updateBounds(); damage(); } /** * This function should, for all FigTexts, * recalculate the font-style (plain, bold, italic, bold/italic), * and apply it by calling FigText.setFont(). <p> * * If the "deepUpdateFont" function does not * work for a subclass, then override this method. */ protected void updateFont() { int style = getNameFigFontStyle(); Font f = getSettings().getFont(style); nameFig.setFont(f); deepUpdateFont(this); } /** * Determines the font style based on the UML model. * Overrule this in Figs that have to show bold or italic based on the * UML model they represent. * E.g. abstract classes show their name in italic. * * @return the font style for the nameFig. */ protected int getNameFigFontStyle() { // TODO: Why do we need this when we can just change the font and // achieve the same effect? showBoldName = getSettings().isShowBoldNames(); return showBoldName ? Font.BOLD : Font.PLAIN; } /** * Changes the font for all Figs contained in the given FigGroup. <p> * * TODO: In fact, there is a design error in this method: * E.g. for a class, if the name is Italic since the class is abstract, * then the classes features should be in Plain font. * This problem can be fixed by implementing * the updateFont() method in all subclasses. * * @param fg the FigGroup to change the font of. */ private void deepUpdateFont(FigGroup fg) { // TODO: Fonts shouldn't be handled any differently than other // rendering attributes boolean changed = false; List<Fig> figs = fg.getFigs(); for (Fig f : figs) { if (f instanceof ArgoFigText) { ((ArgoFigText) f).renderingChanged(); changed = true; } if (f instanceof FigGroup) { deepUpdateFont((FigGroup) f); } } if (changed) { fg.calcBounds(); } } public DiagramSettings getSettings() { // TODO: This is a temporary crutch to use until all Figs are updated // to use the constructor that accepts a DiagramSettings object if (settings == null) { LOG.debug("Falling back to project-wide settings"); Project p = getProject(); if (p != null) { return p.getProjectSettings().getDefaultDiagramSettings(); } } return settings; } public void setSettings(DiagramSettings renderSettings) { settings = renderSettings; renderingChanged(); } protected NotationSettings getNotationSettings() { return notationSettings; } public void setLineWidth(int w) { super.setLineWidth(w); // Default for name and stereotype is no border getNameFig().setLineWidth(0); if (getStereotypeFig() != null) { getStereotypeFig().setLineWidth(0); } } /** * A default "clarifier" to be used for selection if the subclass doesn't * override makeSelection and provide its own. */ class SelectionDefaultClarifiers extends SelectionNodeClarifiers2 { /** Construct a new SelectionNodeClarifiers for the given Fig * * @param f the given Fig */ private SelectionDefaultClarifiers(Fig f) { super(f); } @Override protected Icon[] getIcons() { return null; } @Override protected String getInstructions(int index) { return null; } @Override protected Object getNewNodeType(int index) { return null; } @Override protected Object getNewEdgeType(int index) { return null; } @Override protected boolean isReverseEdge(int index) { return false; } } /** * Setting the owner of the Fig must be done in the constructor and not * changed afterwards for all ArgoUML figs. * * @param owner owning UML element * @see org.tigris.gef.presentation.Fig#setOwner(java.lang.Object) * @throws UnsupportedOperationException * @deprecated for 0.27.3 by tfmorris. Set owner in constructor. This method * is implemented in GEF, so we'll leave this implementation * here to block any attempts to use it within ArgoUML. */ @SuppressWarnings("deprecation") @Deprecated public void setOwner(Object owner) { if (owner != getOwner()) { throw new UnsupportedOperationException( "Owner must be set in constructor and left unchanged"); } } /* * Override FigNode implementation to keep setOwner from getting called. */ @Override public void bindPort(Object port, Fig f) { if (f.getOwner() != port) { f.setOwner(port); } } public void notationRenderingChanged(NotationProvider np, String rendering) { if (notationProviderName == np) { nameFig.setText(rendering); updateBounds(); damage(); } } public NotationSettings getNotationSettings(NotationProvider np) { if (notationProviderName == np) { return getNotationSettings(); } return null; } public Object getOwner(NotationProvider np) { if (notationProviderName == np) { return getOwner(); } return null; } }