/* $Id: ExplorerPopup.java 18629 2010-08-05 11:57:42Z 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 * 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.ui.explorer; import java.awt.event.ActionEvent; import java.awt.event.MouseEvent; import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; import java.util.List; import java.util.Set; import java.util.TreeSet; import javax.swing.AbstractAction; import javax.swing.Action; import javax.swing.JMenu; import javax.swing.JMenuItem; import javax.swing.JPopupMenu; import javax.swing.MenuElement; import org.apache.log4j.Logger; import org.argouml.i18n.Translator; import org.argouml.kernel.ActionList; import org.argouml.kernel.ProfileConfiguration; import org.argouml.kernel.Project; import org.argouml.kernel.ProjectManager; import org.argouml.model.IllegalModelElementConnectionException; import org.argouml.model.Model; import org.argouml.profile.Profile; import org.argouml.ui.ActionCreateContainedModelElement; import org.argouml.ui.ActionCreateEdgeModelElement; import org.argouml.ui.ContextActionFactoryManager; import org.argouml.ui.UndoableAction; import org.argouml.ui.targetmanager.TargetManager; import org.argouml.uml.diagram.ArgoDiagram; import org.argouml.uml.diagram.DiagramUtils; import org.argouml.uml.diagram.SequenceDiagram; import org.argouml.uml.diagram.activity.ui.UMLActivityDiagram; import org.argouml.uml.diagram.state.ui.UMLStateDiagram; import org.argouml.uml.diagram.static_structure.ui.UMLClassDiagram; import org.argouml.uml.diagram.ui.ActionAddAllClassesFromModel; import org.argouml.uml.diagram.ui.ActionAddExistingEdge; import org.argouml.uml.diagram.ui.ActionAddExistingNode; import org.argouml.uml.diagram.ui.ActionAddExistingNodes; import org.argouml.uml.diagram.ui.ActionSaveDiagramToClipboard; import org.argouml.uml.diagram.ui.ModeAddToDiagram; import org.argouml.uml.ui.ActionActivityDiagram; import org.argouml.uml.ui.ActionClassDiagram; import org.argouml.uml.ui.ActionCollaborationDiagram; import org.argouml.uml.ui.ActionDeleteModelElements; import org.argouml.uml.ui.ActionDeploymentDiagram; import org.argouml.uml.ui.ActionSequenceDiagram; import org.argouml.uml.ui.ActionSetSourcePath; import org.argouml.uml.ui.ActionStateDiagram; import org.argouml.uml.ui.ActionUseCaseDiagram; import org.tigris.gef.base.Diagram; import org.tigris.gef.base.Editor; import org.tigris.gef.base.Globals; import org.tigris.gef.graph.GraphModel; import org.tigris.gef.graph.MutableGraphModel; /** * PopUp for extra functionality for the Explorer. * * @author alexb * @since 0.15.2 */ public class ExplorerPopup extends JPopupMenu { private JMenu createDiagrams = new JMenu(menuLocalize("menu.popup.create-diagram")); private static final Logger LOG = Logger.getLogger(ExplorerPopup.class); /** * Array of model elements and corresponding il8n tag * that are presented on the Create Model Elements menu. */ private static final Object[] MODEL_ELEMENT_MENUITEMS = new Object[] { Model.getMetaTypes().getProfile(), // needs to be the first entry "button.new-profile-package", // because of UML1/UML2 differences Model.getMetaTypes().getParameter(), "button.new-parameter", Model.getMetaTypes().getTemplateParameter(), "button.new-template-parameter", Model.getMetaTypes().getPackage(), "button.new-package", Model.getMetaTypes().getActor(), "button.new-actor", Model.getMetaTypes().getUseCase(), "button.new-usecase", Model.getMetaTypes().getExtensionPoint(), "button.new-extension-point", Model.getMetaTypes().getUMLClass(), "button.new-class", Model.getMetaTypes().getInterface(), "button.new-interface", Model.getMetaTypes().getAttribute(), "button.new-attribute", Model.getMetaTypes().getOperation(), "button.new-operation", Model.getMetaTypes().getDataType(), "button.new-datatype", Model.getMetaTypes().getEnumeration(), "button.new-enumeration", Model.getMetaTypes().getEnumerationLiteral(), "button.new-enumeration-literal", Model.getMetaTypes().getSignal(), "button.new-signal", Model.getMetaTypes().getException(), "button.new-exception", Model.getMetaTypes().getComponent(), "button.new-component", Model.getMetaTypes().getComponentInstance(), "button.new-componentinstance", Model.getMetaTypes().getNode(), "button.new-node", Model.getMetaTypes().getNodeInstance(), "button.new-nodeinstance", Model.getMetaTypes().getReception(), "button.new-reception", Model.getMetaTypes().getStereotype(), "button.new-stereotype" }; /** * Creates a new instance of ExplorerPopup. * * @param selectedItem * is the item that we are pointing at. * @param me * is the event. */ public ExplorerPopup(Object selectedItem, MouseEvent me) { super("Explorer popup menu"); /* Check if multiple items are selected. */ boolean multiSelect = TargetManager.getInstance().getTargets().size() > 1; boolean mutableModelElementsOnly = true; for (Object element : TargetManager.getInstance().getTargets()) { if (!Model.getFacade().isAUMLElement(element) || Model.getModelManagementHelper().isReadOnly(element)) { mutableModelElementsOnly = false; break; } } final ArgoDiagram activeDiagram = DiagramUtils.getActiveDiagram(); // TODO: I've made some attempt to rationalize the conditions here // and make them more readable. However I'd suggest that the // conditions should move to each diagram. // Break up one complex method into a few simple ones and // give the diagrams more knowledge of themselves // (although the diagrams may in fact delegate this in // turn to the Model component). // Bob Tarling 31 Jan 2004 // eg the code here should be something like - // if (activeDiagram.canAdd(selectedItem)) { // UMLAction action = // new ActionAddExistingNode( // menuLocalize("menu.popup.add-to-diagram"), // selectedItem); // action.setEnabled(action.shouldBeEnabled()); // this.add(action); // } // profile section: dealing with profiles in different states // 1. a noneditable applied profile try { if (!multiSelect && selectedItem instanceof Profile && !((Profile) selectedItem).getProfilePackages().isEmpty()) { this.add(new ActionExportProfileXMI((Profile) selectedItem)); } } catch (Exception e) { // just no item added in this case } // 2. the profile configuration, holding noneditable applied profiles if (!multiSelect && selectedItem instanceof ProfileConfiguration) { this.add(new ActionManageProfiles()); } // 3. the editable unapplied profile if (Model.getFacade().isAProfile(selectedItem)) { this.add(new ActionDeployProfile(selectedItem)); } // end of profile section if (!multiSelect && mutableModelElementsOnly) { initMenuCreateDiagrams(); this.add(createDiagrams); } initMenuCreateModuleActions(); if (mutableModelElementsOnly) { initMenuCreateModelElements(); } final boolean modelElementSelected = Model.getFacade().isAUMLElement(selectedItem); if (modelElementSelected) { final boolean nAryAssociationSelected = Model.getFacade().isANaryAssociation(selectedItem); final boolean classifierSelected = Model.getFacade().isAClassifier(selectedItem); final boolean packageSelected = Model.getFacade().isAPackage(selectedItem); final boolean commentSelected = Model.getFacade().isAComment(selectedItem); final boolean stateVertexSelected = Model.getFacade().isAStateVertex(selectedItem); final boolean instanceSelected = Model.getFacade().isAInstance(selectedItem); final boolean dataValueSelected = Model.getFacade().isADataValue(selectedItem); final boolean relationshipSelected = Model.getFacade().isARelationship(selectedItem); final boolean flowSelected = Model.getFacade().isAFlow(selectedItem); final boolean linkSelected = Model.getFacade().isALink(selectedItem); final boolean transitionSelected = Model.getFacade().isATransition(selectedItem); final boolean activityDiagramActive = activeDiagram instanceof UMLActivityDiagram; final boolean sequenceDiagramActive = activeDiagram instanceof SequenceDiagram; final boolean stateDiagramActive = activeDiagram instanceof UMLStateDiagram; final Object selectedStateMachine = (stateVertexSelected) ? Model .getStateMachinesHelper().getStateMachine(selectedItem) : null; final Object diagramStateMachine = (stateDiagramActive) ? ((UMLStateDiagram) activeDiagram) .getStateMachine() : null; final Object diagramActivity = (activityDiagramActive) ? ((UMLActivityDiagram) activeDiagram).getStateMachine() : null; Collection projectModels = ProjectManager.getManager().getCurrentProject().getModels(); if (!multiSelect) { if ((classifierSelected && !relationshipSelected) || (packageSelected // TODO: Allow adding models to a diagram - issue 4172. && !projectModels.contains(selectedItem)) || (stateVertexSelected && activityDiagramActive && diagramActivity == selectedStateMachine) || (stateVertexSelected && stateDiagramActive && diagramStateMachine == selectedStateMachine) || (instanceSelected && !dataValueSelected && !sequenceDiagramActive) || nAryAssociationSelected || commentSelected) { // TODO: Why can't we use ActionAddExistingNodes here? Bob. Action action = new ActionAddExistingNode( menuLocalize("menu.popup.add-to-diagram"), selectedItem); this.add(action); } if ((relationshipSelected && !flowSelected && !nAryAssociationSelected) || (linkSelected && !sequenceDiagramActive) || transitionSelected) { Action action = new ActionAddExistingEdge( menuLocalize("menu.popup.add-to-diagram"), selectedItem); this.add(action); addMenuItemForBothEndsOf(selectedItem); } if (classifierSelected || packageSelected) { this.add(new ActionSetSourcePath()); } } if (mutableModelElementsOnly // Can't delete last top level model && !(projectModels.size() == 1 && projectModels.contains(selectedItem))) { // TODO: Shouldn't be creating a new instance here. We should // hold the delete action in some central place. this.add(new ActionDeleteModelElements()); } } // TODO: Make sure this shouldn't go into a previous // condition -tml if (!multiSelect) { if (selectedItem instanceof UMLClassDiagram) { Action action = new ActionAddAllClassesFromModel( menuLocalize("menu.popup.add-all-classes-to-diagram"), selectedItem); this.add(action); } } if (multiSelect) { List<Object> classifiers = new ArrayList<Object>(); for (Object o : TargetManager.getInstance().getTargets()) { // TODO: Should be anything allowed by current diagram if (Model.getFacade().isAClassifier(o) && !Model.getFacade().isARelationship(o)) { classifiers.add(o); } } if (!classifiers.isEmpty()) { Action action = new ActionAddExistingNodes( menuLocalize("menu.popup.add-to-diagram"), classifiers); this.add(action); } } else if (selectedItem instanceof Diagram) { this.add(new ActionSaveDiagramToClipboard()); // TODO: Delete should be available on any combination of model // elements and diagrams. // TODO: Shouldn't be creating a new instance here. We should // hold the delete action in some central place. ActionDeleteModelElements ad = new ActionDeleteModelElements(); ad.setEnabled(ad.shouldBeEnabled()); this.add(ad); } } /** * initialize the menu for diagram construction in the explorer popup menu. * */ private void initMenuCreateDiagrams() { createDiagrams.add(new ActionUseCaseDiagram()); createDiagrams.add(new ActionClassDiagram()); createDiagrams.add(new ActionSequenceDiagram()); createDiagrams.add(new ActionCollaborationDiagram()); createDiagrams.add(new ActionStateDiagram()); createDiagrams.add(new ActionActivityDiagram()); createDiagrams.add(new ActionDeploymentDiagram()); } /** * initialize the menu for diagram construction in the explorer popup menu. * */ private void initMenuCreateModelElements() { List targets = TargetManager.getInstance().getTargets(); Set<JMenuItem> menuItems = new TreeSet<JMenuItem>(); if (targets.size() >= 2) { // Check to see if all targets are classifiers // before adding an option to create an association between // them all boolean classifierRoleFound = false; boolean classifierRolesOnly = true; for (Iterator it = targets.iterator(); it.hasNext() && classifierRolesOnly; ) { if (Model.getFacade().isAClassifierRole(it.next())) { classifierRoleFound = true; } else { classifierRolesOnly = false; } } if (classifierRolesOnly) { menuItems.add(new OrderedMenuItem( new ActionCreateAssociationRole( Model.getMetaTypes().getAssociationRole(), targets))); } else if (!classifierRoleFound) { boolean classifiersOnly = true; for (Iterator it = targets.iterator(); it.hasNext() && classifiersOnly; ) { if (!Model.getFacade().isAClassifier(it.next())) { classifiersOnly = false; } } if (classifiersOnly) { menuItems.add(new OrderedMenuItem( new ActionCreateAssociation( Model.getMetaTypes().getAssociation(), targets))); } } } if (targets.size() == 2) { addCreateModelElementAction( menuItems, Model.getMetaTypes().getDependency(), " " + menuLocalize("menu.popup.depends-on") + " "); addCreateModelElementAction( menuItems, Model.getMetaTypes().getGeneralization(), " " + menuLocalize("menu.popup.generalizes") + " "); addCreateModelElementAction( menuItems, Model.getMetaTypes().getInclude(), " " + menuLocalize("menu.popup.includes") + " "); addCreateModelElementAction( menuItems, Model.getMetaTypes().getExtend(), " " + menuLocalize("menu.popup.extends") + " "); addCreateModelElementAction( menuItems, Model.getMetaTypes().getPackageImport(), " " + menuLocalize("menu.popup.has-permission-on") + " "); addCreateModelElementAction( menuItems, Model.getMetaTypes().getUsage(), " " + menuLocalize("menu.popup.uses") + " "); addCreateModelElementAction( menuItems, Model.getMetaTypes().getAbstraction(), " " + menuLocalize("menu.popup.realizes") + " "); } else if (targets.size() == 1) { Object target = targets.get(0); // iterate through all possible model elements to determine which // are valid to be contained by the selected target int start = 0; if (Model.getFacade().getUmlVersion().charAt(0) == '1') { // skip profile item for UML1 start = 2; } for (int iter = start; iter < MODEL_ELEMENT_MENUITEMS.length; iter += 2) { Object me = MODEL_ELEMENT_MENUITEMS[iter]; String md = (String) MODEL_ELEMENT_MENUITEMS[iter + 1]; // test if this element can be contained by the target if (Model.getUmlFactory().isContainmentValid(me, target)) { // this element can be contained add a menu item // that allows the user to take that action menuItems.add(new OrderedMenuItem( new ActionCreateContainedModelElement( me, target, md))); } } } if (menuItems.size() == 1) { add(menuItems.iterator().next()); } else if (menuItems.size() > 1) { JMenu menu = new JMenu(menuLocalize("menu.popup.create-model-element")); add(menu); for (JMenuItem item : menuItems) { menu.add(item); } } } private void initMenuCreateModuleActions() { final List<Action> contextActions = ContextActionFactoryManager.getContextPopupActions(); LOG.info(contextActions); if (contextActions instanceof ActionList) { recursiveAdd(this, (Action) contextActions); } else { for (Action a : contextActions) { recursiveAdd(this, a); } } } private void recursiveAdd(MenuElement menu, Action a) { if (a instanceof List<?>) { JMenu m = new JMenu(a); if (menu instanceof JPopupMenu) { ((JPopupMenu) menu).add(m); } else if (menu instanceof JMenu) { ((JMenu) menu).add(m); } for (Action subAction : (List<Action>) a) { recursiveAdd(m, subAction); } } else { if (menu instanceof JPopupMenu) { ((JPopupMenu) menu).add(a); } else if (menu instanceof JMenu) { ((JMenu) menu).add(a); } } } private void addCreateModelElementAction( Set<JMenuItem> menuItems, Object metaType, String relationshipDescr) { List targets = TargetManager.getInstance().getTargets(); Object source = targets.get(0); Object dest = targets.get(1); JMenu subMenu = new OrderedMenu( menuLocalize("menu.popup.create") + " " + Model.getMetaTypes().getName(metaType)); buildDirectionalCreateMenuItem( metaType, dest, source, relationshipDescr, subMenu); buildDirectionalCreateMenuItem( metaType, source, dest, relationshipDescr, subMenu); if (subMenu.getMenuComponents().length > 0) { menuItems.add(subMenu); } } /** * Attempt to build a menu item to create the given model element type * as a relation betwen two existing model elements. * If succesful then the menu item is added to the given menu * @param metaType The type of model element the menu item should create * @param source The source model element * @param dest The destination model element * @param relationshipDescr A textual description that describes how * source relates to dest * @param menu The menu to which the menu item should be added */ private void buildDirectionalCreateMenuItem( Object metaType, Object source, Object dest, String relationshipDescr, JMenu menu) { if (Model.getUmlFactory().isConnectionValid( metaType, source, dest, true)) { JMenuItem menuItem = new JMenuItem( new ActionCreateEdgeModelElement( metaType, source, dest, relationshipDescr)); if (menuItem != null) { menu.add(menuItem); } } } /** * Localize a popup menu item in the navigator pane. * * @param key * The key for the string to localize. * @return The localized string. */ private String menuLocalize(String key) { return Translator.localize(key); } /** * Add popup menu items for adding to diagram both edge ends. * * @param edge * The edge for which the menu item will be added. */ private void addMenuItemForBothEndsOf(Object edge) { Collection coll = null; if (Model.getFacade().isAAssociation(edge) || Model.getFacade().isALink(edge)) { coll = Model.getFacade().getConnections(edge); } else if (Model.getFacade().isAAbstraction(edge) || Model.getFacade().isADependency(edge)) { coll = new ArrayList(); coll.addAll(Model.getFacade().getClients(edge)); coll.addAll(Model.getFacade().getSuppliers(edge)); } else if (Model.getFacade().isAGeneralization(edge)) { coll = new ArrayList(); Object parent = Model.getFacade().getGeneral(edge); coll.add(parent); coll.addAll(Model.getFacade().getChildren(parent)); } if (coll == null) { return; } Iterator iter = coll.iterator(); while (iter.hasNext()) { Object me = iter.next(); if (me != null) { if (Model.getFacade().isAAssociationEnd(me)) { me = Model.getFacade().getType(me); } if (me != null) { String name = Model.getFacade().getName(me); if (name == null || name.length() == 0) { name = "(anon element)"; } Action action = new ActionAddExistingRelatedNode( menuLocalize("menu.popup.add-to-diagram") + ": " + name, me); this.add(action); } } } } /** * The UID. */ private static final long serialVersionUID = -5663884871599931780L; private class ActionAddExistingRelatedNode extends UndoableAction { /** * The UML object to be added to the diagram. */ private Object object; /** * The Constructor. * * @param name the localized name of the action * @param o the node UML object to be added */ public ActionAddExistingRelatedNode(String name, Object o) { super(name); object = o; } /** * @see javax.swing.Action#isEnabled() */ public boolean isEnabled() { ArgoDiagram dia = DiagramUtils.getActiveDiagram(); if (dia == null) { return false; } MutableGraphModel gm = (MutableGraphModel) dia.getGraphModel(); return gm.canAddNode(object); } public void actionPerformed(ActionEvent ae) { super.actionPerformed(ae); Editor ce = Globals.curEditor(); GraphModel gm = ce.getGraphModel(); if (!(gm instanceof MutableGraphModel)) { return; } String instructions = null; if (object != null) { instructions = Translator.localize( "misc.message.click-on-diagram-to-add", new Object[] { Model.getFacade().toString(object), }); Globals.showStatus(instructions); } ArrayList<Object> elementsToAdd = new ArrayList<Object>(1); elementsToAdd.add(object); final ModeAddToDiagram placeMode = new ModeAddToDiagram(elementsToAdd, instructions); Globals.mode(placeMode, false); } } /* end class ActionAddExistingRelatedNode */ /** * A JMenuItem that will order by display name * * @author Bob Tarling */ private class OrderedMenuItem extends JMenuItem implements Comparable { /** * Instantiate OrderedMenuItem * @param action */ public OrderedMenuItem(Action action) { super(action); } /** * Instantiate OrderedMenuItem * @param name */ public OrderedMenuItem(String name) { super(name); setName(name); } public int compareTo(Object o) { JMenuItem other = (JMenuItem) o; return toString().compareTo(other.toString()); } } /** * A JMenuItem that will order by display name * * @author Bob Tarling */ private class OrderedMenu extends JMenu implements Comparable { /** * Instantiate OrderedMenu * @param name */ public OrderedMenu(String name) { super(name); } public int compareTo(Object o) { JMenuItem other = (JMenuItem) o; return toString().compareTo(other.toString()); } } /** * An action to create an association between 2 or more model elements. * * @author Bob Tarling */ private class ActionCreateAssociation extends AbstractAction { private Object metaType; private List classifiers; public ActionCreateAssociation( Object theMetaType, List classifiersList) { super(menuLocalize("menu.popup.create") + " " + Model.getMetaTypes().getName(theMetaType)); this.metaType = theMetaType; this.classifiers = classifiersList; } public void actionPerformed(ActionEvent e) { try { Object newElement = Model.getUmlFactory().buildConnection( metaType, classifiers.get(0), null, classifiers.get(1), null, null, null); for (int i = 2; i < classifiers.size(); ++i) { Model.getUmlFactory().buildConnection( Model.getMetaTypes().getAssociationEnd(), newElement, null, classifiers.get(i), null, null, null); } } catch (IllegalModelElementConnectionException e1) { LOG.error("Exception", e1); } } } /** * An action to create an association between 2 or more model elements. * * @author Bob Tarling */ private class ActionCreateAssociationRole extends AbstractAction { private Object metaType; private List classifierRoles; public ActionCreateAssociationRole( Object theMetaType, List classifierRolesList) { super(menuLocalize("menu.popup.create") + " " + Model.getMetaTypes().getName(theMetaType)); this.metaType = theMetaType; this.classifierRoles = classifierRolesList; } public void actionPerformed(ActionEvent e) { try { Object newElement = Model.getUmlFactory().buildConnection( metaType, classifierRoles.get(0), null, classifierRoles.get(1), null, null, null); for (int i = 2; i < classifierRoles.size(); ++i) { Model.getUmlFactory().buildConnection( Model.getMetaTypes().getAssociationEndRole(), newElement, null, classifierRoles.get(i), null, null, null); } } catch (IllegalModelElementConnectionException e1) { LOG.error("Exception", e1); } } } }