/****************************************************************************** * Copyright (c) 2004, 2010 IBM Corporation and others. * 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: * IBM Corporation - initial API and implementation ****************************************************************************/ package org.eclipse.gmf.runtime.diagram.ui.editpolicies; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.ListIterator; import java.util.Set; import org.eclipse.core.runtime.IAdaptable; import org.eclipse.core.runtime.IStatus; import org.eclipse.draw2d.geometry.Point; import org.eclipse.emf.ecore.EObject; import org.eclipse.gef.EditPart; import org.eclipse.gef.EditPartViewer; import org.eclipse.gef.Request; import org.eclipse.gef.commands.Command; import org.eclipse.gef.commands.UnexecutableCommand; import org.eclipse.gef.requests.CreateRequest; import org.eclipse.gmf.runtime.common.core.util.Log; import org.eclipse.gmf.runtime.diagram.core.util.ViewUtil; import org.eclipse.gmf.runtime.diagram.ui.commands.DeferredLayoutCommand; import org.eclipse.gmf.runtime.diagram.ui.commands.ICommandProxy; import org.eclipse.gmf.runtime.diagram.ui.commands.SetViewMutabilityCommand; import org.eclipse.gmf.runtime.diagram.ui.editparts.GroupEditPart; import org.eclipse.gmf.runtime.diagram.ui.editparts.IGraphicalEditPart; import org.eclipse.gmf.runtime.diagram.ui.editparts.INodeEditPart; import org.eclipse.gmf.runtime.diagram.ui.figures.ICanonicalShapeCompartmentLayout; import org.eclipse.gmf.runtime.diagram.ui.internal.DiagramUIPlugin; import org.eclipse.gmf.runtime.diagram.ui.l10n.DiagramUIMessages; import org.eclipse.gmf.runtime.diagram.ui.parts.IDiagramGraphicalViewer; import org.eclipse.gmf.runtime.diagram.ui.requests.CreateConnectionViewRequest; import org.eclipse.gmf.runtime.diagram.ui.requests.CreateViewRequest; import org.eclipse.gmf.runtime.diagram.ui.requests.DropObjectsRequest; import org.eclipse.gmf.runtime.diagram.ui.requests.RequestConstants; import org.eclipse.gmf.runtime.diagram.ui.requests.CreateConnectionViewRequest.ConnectionViewDescriptor; import org.eclipse.gmf.runtime.diagram.ui.requests.CreateViewRequest.ViewDescriptor; import org.eclipse.gmf.runtime.emf.core.util.EMFCoreUtil; import org.eclipse.gmf.runtime.emf.core.util.EObjectAdapter; import org.eclipse.gmf.runtime.emf.core.util.PackageUtil; import org.eclipse.gmf.runtime.notation.Diagram; import org.eclipse.gmf.runtime.notation.Edge; import org.eclipse.gmf.runtime.notation.Node; import org.eclipse.gmf.runtime.notation.View; import org.eclipse.osgi.util.NLS; /** * A specialized implementation of <code>CanonicalEditPolicy</code>. * This implementation will manage connections owned by the semantic host. * * @author mhanner / sshaw */ public abstract class CanonicalConnectionEditPolicy extends CanonicalEditPolicy { /** * Return a list of semantic relationships contained inside this * compartment. * @return EObject list * */ abstract protected List<EObject> getSemanticConnectionsList(); /** * Return the supplied relationship's source element * @param relationship semantic connection * @return EObject the source EObject */ abstract protected EObject getSourceElement(EObject relationship); /** * Return the supplied relationship's target element. * * @param relationship semantic connection * @return EObject the target EObject */ abstract protected EObject getTargetElement(EObject relationship); /** Return an empty list. */ protected List<EObject> getSemanticChildrenList() { return Collections.emptyList(); } /* * Override to ensure that all owned editparts are activated before the editpolicy refresh * is invoked that will try to canonically create connections and shapes. */ protected void refreshOnActivate() { // need to activate editpart children before invoking the canonical refresh List<EditPart> c = getHost().getChildren(); for (int i = 0; i < c.size(); i++) { c.get(i).activate(); } refresh(); } /** * Return <tt>true</tt> if the connection should be drawn between the * supplied endpoints; otherwise return <tt>false</tt>. * * @param a * connection's source element * @param a * connection's target element * @return <tt>true</tt> if both parameters are not <tt>null</tt>; * otherwise <tt>false</tt> */ protected boolean canCreateConnection(EditPart sep, EditPart tep, EObject connection) { if (sep != null && sep.isActive() && tep != null && tep.isActive()) { View src = (View) sep.getAdapter(View.class); View tgt = (View) tep.getAdapter(View.class); if (src != null && tgt != null) { EditPart sourceParent = sep.getParent(); while (sourceParent instanceof GroupEditPart) { sourceParent = sourceParent.getParent(); } EditPart targetParent = tep.getParent(); while (targetParent instanceof GroupEditPart) { targetParent = targetParent.getParent(); } return sourceParent.getEditPolicy( EditPolicyRoles.CANONICAL_ROLE) != null && targetParent.getEditPolicy( EditPolicyRoles.CANONICAL_ROLE) != null; } } return false; } /** * Return the editpart mapped to the object. The editpart is retrieved from * a [view,semantic] mapping. {@link #getView(IElement)}is called if the * mapping cannot be found inside this manager. * * @param element * an <tt>View</tt> or <tt>EObject</tt> instance. * @param context * an <code>EObject</code> that is the context for the element. Typically, * this will be either <code>null</code> or it will the <code>Edge</code> * that is connected to the <code>element</code> to find the <code>EditPart</code> of. * @return an editpart; <tt>null</tt> if non could be found. */ private EditPart getEditPartFor(EObject element, EObject context) { if (element != null && !(element instanceof View)) { EditPartViewer viewer = getHost().getViewer(); if (viewer instanceof IDiagramGraphicalViewer) { List<EditPart> parts = ((IDiagramGraphicalViewer) viewer) .findEditPartsForElement(EMFCoreUtil.getProxyID(element), INodeEditPart.class); if (parts.isEmpty()) { // reach for the container's editpart instead and force it // to refresh EObject container = element.eContainer(); EditPart containerEP = getEditPartFor(container, null); if (containerEP != null) { containerEP.refresh(); parts = ((IDiagramGraphicalViewer) viewer) .findEditPartsForElement(EMFCoreUtil.getProxyID(element), INodeEditPart.class); } } // Check if the part is contained with-in the host EditPart // since we are canonically updated the host. return findEditPartForElement(element, context, parts); } } return (EditPart) host().getViewer().getEditPartRegistry().get(element); } /** * Finds the specific <code>EditPart</code> from a <code>List</code> of editparts * that is the exact representation of the given <code>element</code> in the * host context. * * @param element * an <tt>View</tt> or <tt>EObject</tt> instance. * @param context * an <code>EObject</code> that is the context for the element. Typically, * this will be either <code>null</code> or it will the <code>Edge</code> * that is connected to the <code>element</code> to find the <code>EditPart</code> of. * @param parts * a <code>List</code> of <code>EditPart</codes> to search for a specific * instance that is the exact representation of <code>element</code> * in the host context. * @return an editpart; <tt>null</tt> if non could be found. */ protected EditPart findEditPartForElement(EObject element, EObject context, List<EditPart> parts) { EditPart ancestor = getHost(); while (ancestor != null) { EditPart ep = reachForEditPartWithAncestor(parts, ancestor); if (ep != null) { return ep; } ancestor = ancestor.getParent(); } return null; } /** * Walks up the container tree and tries to find the EditPart that has the * given <code>EditPart</code> as an ancestor. * * @param results * <code>List</code> of <code>EditPart</code> objects * @param ancestor * <code>EditPart</code> to check against. * @return <code>EditPart</code> that contains the <code>ancestor</code> * in it's containment hierarchy */ private EditPart reachForEditPartWithAncestor(List<EditPart> results, EditPart ancestor) { ListIterator<EditPart> li = results.listIterator(); while (li.hasNext()) { EditPart ep = (EditPart) li.next(); EditPart walker = ep.getParent(); while (walker != null) { if (walker.equals(ancestor)) return ep; walker = walker.getParent(); } } return null; } /** * Returns the default factory hint. * * @return an empty string */ protected String getDefaultFactoryHint() { return "";//$NON-NLS-1$ } /** * Creates a connection view facde element for the supplied semantic element. * An empty string is used as the default factory hint. * * @param element * the semantic element * @param the * connections source editpart * @param the * connections target editpart * @param index * semantic elements position */ protected final Edge createConnectionView(EObject connection, int index) { EditPart sep = getSourceEditPartFor(connection); EditPart tep = getTargetEditPartFor(connection); if (!canCreateConnection(sep, tep, connection)) { return null; } View sView = (View) sep.getModel(); View tView = (View) tep.getModel(); Edge model = null; String factoryHint = getDefaultFactoryHint(); IAdaptable elementAdapter = new CanonicalElementAdapter(connection, factoryHint); CreateConnectionViewRequest ccr = getCreateConnectionViewRequest( elementAdapter, getFactoryHint(elementAdapter, factoryHint), index); ccr.setType(RequestConstants.REQ_CONNECTION_START); ccr.setSourceEditPart(sep); getCreateViewCommand(ccr); // sep.getCommand(ccr); //prime the command ccr.setTargetEditPart(tep); ccr.setType(RequestConstants.REQ_CONNECTION_END); Command cmd = getCreateViewCommand(ccr); // tep.getCommand(ccr); if (cmd != null && cmd.canExecute()) { List<EObjectAdapter> viewAdapters = new ArrayList<EObjectAdapter>(); viewAdapters.add(new EObjectAdapter(((View) host().getModel()) .getDiagram())); viewAdapters.add(new EObjectAdapter(sView)); viewAdapters.add(new EObjectAdapter(tView)); SetViewMutabilityCommand.makeMutable(viewAdapters).execute(); executeCommand(cmd); IAdaptable adapter = (IAdaptable) ccr.getNewObject(); SetViewMutabilityCommand.makeMutable(adapter).execute(); model = (Edge) adapter.getAdapter(Edge.class); if (model == null) { String eMsg = NLS .bind( DiagramUIMessages.CanonicalEditPolicy_create_view_failed_ERROR_, connection); IllegalStateException ise = new IllegalStateException(eMsg); Log.error(DiagramUIPlugin.getInstance(), IStatus.ERROR, eMsg, ise); throw ise; } } return model; } /** * Calculates the <code>EditPart</code> that this connection element is * connected to at it's target. * * @param connection * the <code>EObject</code> element that we are canonical * trying to create a view for. * @return the <code>EditPart</code> that is the source of the * <code>View</code> we want to create */ protected EditPart getTargetEditPartFor(EObject connection) { EObject tel; EditPart tep; tel = getTargetElement(connection); tep = getEditPartFor(tel, connection); return tep; } /** * Calculates the <code>EditPart</code> that this connection element is * connected to at it's source. * * @param connection * the <code>EObject</code> element that we are canonical * trying to create a view for. * @return the <code>EditPart</code> that is the target of the * <code>View</code> we want to create */ protected EditPart getSourceEditPartFor(EObject connection) { EObject sel; EditPart sep; sel = getSourceElement(connection); sep = getEditPartFor(sel, connection); return sep; } /** * Forwards the supplied request to its source if the target is * <tt>null</tt>; otherwise it is forwarded to the target. Forwards the * supplied request to the editpart's <code>host</code>. * * @param request * a <code>CreareConnecgtorViewRequest</code> * @return Command to create the views in the request */ protected Command getCreateViewCommand(CreateRequest request) { if (request instanceof CreateConnectionViewRequest) { CreateConnectionViewRequest ccr = (CreateConnectionViewRequest) request; EditPart ep = ccr.getTargetEditPart() == null ? ccr .getSourceEditPart() : ccr.getTargetEditPart(); return ep.getCommand(request); } return super.getCreateViewCommand(request); } /** * Return a create view request. * * @param descriptor * a {@link CreateViewRequest.ViewDescriptor}. * @return a create request */ protected CreateViewRequest getCreateViewRequest( CreateViewRequest.ViewDescriptor descriptor) { return getCreateViewRequest(Collections.singletonList(descriptor)); } /** * Return a create view request. The request's location is set to * {@link ICanonicalShapeCompartmentLayout#UNDEFINED}. * * @param descriptors * a {@link CreateViewRequest.ViewDescriptor} list. * @return a create request */ protected CreateViewRequest getCreateViewRequest(List<ViewDescriptor> descriptors) { CreateViewRequest cvr = super.getCreateViewRequest(descriptors); Point loc = ICanonicalShapeCompartmentLayout.UNDEFINED.getLocation(); cvr.setLocation(loc); return cvr; } /** * Return a create connection view request. * * @param elementAdapter * semantic element * @param viewKind * type of view to create * @param hint * factory hint * @param index * index * @return a create <i>non-persisted </i> view request */ private CreateConnectionViewRequest getCreateConnectionViewRequest( IAdaptable elementAdapter, String hint, int index) { return new CreateConnectionViewRequest(getConnectionViewDescriptor( elementAdapter, hint, index)); } /** * Return a connection view descriptor. * * @param elementAdapter * semantic element * @param hint * factory hint * @param index * index * @return a create <i>non-persisted </i> connection view descriptor */ private ConnectionViewDescriptor getConnectionViewDescriptor( IAdaptable elementAdapter, String hint, int index) { return new ConnectionViewDescriptor(elementAdapter, hint, index, false, ((IGraphicalEditPart) getHost()).getDiagramPreferencesHint()); } /** * Updates the set of connection views so that it is in sync with the * semantic connections. This method is called in response to notification * from the model. * <P> * The update is performed by comparing the existing connection views with the * set of semantic connections returned from {@link #getSemanticConnections()}. * Views whose semantic connection no longer exists or whose semantic * connection ends are <tt>null</tt> are * {@link org.eclipse.gmf.runtime.diagram.ui.editpolicies.CanonicalEditPolicy#deleteViews(Iterator) removed}. * New semantic children have their View * {@link #createEdge(IElement, EditPart, EditPart, int, String) * created}. Subclasses must override <code>getSemanticConnections()</code>. * <P> * This refresh routine will not reorder the view list to ensure both it and * the semantic children are in the same order since it is possible that * this editpolicy will only handle a specific subset of the host's views. * * This method should <em>not</em> be overridden. * * @return <code>List</code> of new <code>IAdaptable</code> objects that * adapt to <code>View</code> objects that were created as a * result of the synchronization */ protected List<IAdaptable> refreshSemanticConnections() { Edge viewChild; // current connection views Collection<Edge> viewChildren = getConnectionViews(); Collection<EObject> semanticChildren = new HashSet<EObject>(); semanticChildren.addAll(getSemanticConnectionsList()); List<View> orphaned = cleanCanonicalSemanticChildren(viewChildren, semanticChildren); // delete all the remaining views deleteViews(orphaned.iterator()); // create a view for each remaining semantic element. List<IAdaptable> viewDescriptors = new ArrayList<IAdaptable>(); for(EObject semanticChild : semanticChildren) { viewChild = createConnectionView(semanticChild, ViewUtil.APPEND); if (viewChild != null) { viewDescriptors.add(new EObjectAdapter(viewChild)); } } makeViewsMutable(viewDescriptors); // now refresh all the connection containers to update the editparts HashSet<EditPart> ends = new HashSet<EditPart>(); ListIterator<IAdaptable> li = viewDescriptors.listIterator(); while (li.hasNext()) { IAdaptable adaptable = li.next(); Edge edge = (Edge)adaptable.getAdapter(Edge.class); EditPart sourceEP = getEditPartFor(edge.getSource(), edge); if (sourceEP != null) { ends.add(sourceEP); } EditPart targetEP = getEditPartFor(edge.getTarget(), edge); if (targetEP != null) { ends.add(targetEP); } } for(EditPart end : ends) { end.refresh(); } return viewDescriptors; } @Override protected boolean isOrphaned(Collection<EObject> semanticChildren, View view) { EObject element = view.getElement(); if (semanticChildren.contains(element)) { if (view instanceof Edge) { Edge edge = (Edge) view; if ((edge.getSource() == null || (edge.getSource().getElement() != getSourceElement(element))) || (edge.getTarget() == null || (edge.getTarget() .getElement() != getTargetElement(element)))) { return true; } } } else { return true; } return false; } /* * (non-Javadoc) * * @see org.eclipse.gmf.runtime.diagram.ui.editpolicies.CanonicalEditPolicy#refreshSemantic() */ protected void refreshSemantic() { List<IAdaptable> createdViews = super.refreshSemanticChildren(); List<IAdaptable> createdConnectionViews = refreshSemanticConnections(); // perform a layout of the container DeferredLayoutCommand layoutCmd = new DeferredLayoutCommand(host() .getEditingDomain(), createdViews, host()); if (!createdViews.isEmpty() && layoutCmd.canExecute()) { executeCommand(new ICommandProxy(layoutCmd)); } List<IAdaptable> allViews = new ArrayList<IAdaptable>( createdConnectionViews.size() + createdViews.size()); allViews.addAll(createdViews); allViews.addAll(createdConnectionViews); makeViewsImmutable(allViews); } /** * Return <tt>true</tt> if this editpolicy should try and delete the * supplied view; otherwise <tt>false<tt>. * The default behaviour is to return <tt>true</tt> if the view's semantic element is <tt>null</tt>. * <P> * Subclasses should override this method to ensure the correct behaviour. * @return <code>view.resolveSemanticElement() == null</code> */ protected boolean shouldDeleteView(View view) { return ViewUtil.resolveSemanticElement(view) == null; } /* * (non-Javadoc) * * @see org.eclipse.gmf.runtime.diagram.ui.editpolicies.CanonicalEditPolicy#postProcessRefreshSemantic(java.util.List) */ protected void postProcessRefreshSemantic(List<IAdaptable> viewDescriptors) { makeViewsMutable(viewDescriptors); super.postProcessRefreshSemantic(viewDescriptors); } /** * Return the list of connections between elements contained within the host * compartment. * * @return list of <code>Edge</code>s. */ protected Collection<Edge> getConnectionViews() { Collection<View> children = getViewChildren(); Set<Edge> connections = new HashSet<Edge>(); if (getHost() instanceof IGraphicalEditPart) { IGraphicalEditPart gep = (IGraphicalEditPart)getHost(); getConnectionViews(connections, gep.getNotationView(), children); } return connections; } /** * Add all connections that are attached to the given node and any of it's * children. * * @param connections * @param node */ private void getConnectionViews(Set<Edge> connections, View view, Collection<View> viewChildren ) { IGraphicalEditPart gep = (IGraphicalEditPart)getHost(); View hostView = gep.getNotationView(); if (hostView != view) { if (!shouldCheckForConnections(view, viewChildren)) { return; } } for (Edge sourceEdge : (List<Edge>) ViewUtil.getSourceConnections(view)) { if (shouldIncludeConnection(sourceEdge, viewChildren)) { connections.add(sourceEdge); } } for (Edge targetEdge : (List<Edge>) ViewUtil.getTargetConnections(view)) { if (shouldIncludeConnection(targetEdge, viewChildren)) { connections.add(targetEdge); } } for(View viewChild : (List<View>)view.getChildren()){ if (viewChild instanceof Node) { getConnectionViews(connections, viewChild, viewChildren ); } } } /** * Determines if a given view should be checked to see if any attached connections should be considered * by the canonical synchronization routine. By default it will consider views that are 2 levels deep from the * container in order to allow for connections that are attached to border items on children views in the * container. * * @param view a <code>View</code> to check to see if attached connections should be considered. * @param viewChildren a <code>Collection</code> of view children of the host notation view, that can be used * as a context to determine if the given view's attached connections should be considered. * @return a <code>boolean</code> <code>true</code> if connections on the view are used as part of the * canonical synchronization. <code>false</code> if the view's attached connections are to be ignored. */ protected boolean shouldCheckForConnections(View view, Collection<View> viewChildren) { return (view != null && (viewChildren.contains(view) || viewChildren.contains(view.eContainer()))); } /** * Called by {@link #getConnectionViews()} to determine if the underlying * shape compartment is responsible for the supplied connection. By default, * the following condition must be met for the connection to be accepted: * <UL> * <LI> its source must not be null. * <LI> its target must not be null. * <LI> the shape compartment contains the source (or the source's container * view). * <LI> the shape compartment contains the target (or the target's container * view). </LI> * * @param connection * the connection view * @param children * underlying shape compartment's children. * @return <tt>false</tt> if supplied connection should be ignored; * otherwise <tt>true</tt>. */ protected boolean shouldIncludeConnection(Edge connection, Collection<View> children) { return shouldCheckForConnections(connection.getSource(), children) || shouldCheckForConnections(connection.getTarget(), children); } /** * Return {@link UnexecutableCommand} if the editpolicy is enabled and a * {@link DropObjectsRequest} is passed as an argument and its objects are * contained in the list of semantic children. */ public Command getCommand(Request request) { if (understandsRequest(request)) { if (isEnabled() && request instanceof DropObjectsRequest) { return getDropCommand((DropObjectsRequest) request); } } return super.getCommand(request); } /** * gets an <code>UnexecutableCommand</code> if the droprequest cannot be * supported; the semantic host cannot contain the element being dropped or * this editpolicy is enabled and it already contains of view for the * elements being dropped. * * @param request * the request to use * @return <code>Command</code> */ protected Command getDropCommand(DropObjectsRequest request) { boolean enabled = isEnabled(); List children = getSemanticChildrenList(); for(Object dropElement : request.getObjects()) { // Allow diagram links on Canonical shapes compartments if (allowDropElement(dropElement)) { continue; } if (dropElement instanceof EObject && preventDropElement(dropElement)) { return UnexecutableCommand.INSTANCE; } boolean containsElement = children.contains(dropElement); if (enabled) { if (containsElement || preventDropElement(dropElement)) { return UnexecutableCommand.INSTANCE; } } } return null; } /** * Return <tt>false</tt> if the supplied element should be prevented from * being dropped into this editpolicy's host; otherwise <tt>true</tt>. * This method is called by {@link #getDropCommand(DropObjectsRequest)} if * this editpolicy is enabled. * * @param dropElement * object being dropped. * @return <code>PackageUtil.canContain(getSemanticHost().eClass(), ((EObject)dropElement).eClass(), false)</code> * if the supplied element is an <code>EObject</code>; otherwise * <tt>false</tt> */ protected boolean preventDropElement(Object dropElement) { return dropElement instanceof EObject ? !PackageUtil.canContain( getSemanticHost().eClass(), ((EObject) dropElement).eClass(), false) : false; } /** * Return <tt>true</tt> if the supplied element should be able to be * dropped into this editpolicy's host; otherwise <tt>false</tt>. This * method is called by {@link #getDropCommand(DropObjectsRequest)} if this * editpolicy is enabled. Returning false will necessarily prevent the * element from being dropped; the <code>getDropCommand</code> method will * also invoke <code>preventDropElement</code>. * * @param dropElement * object being dropped. * @return true if dropping the supplied element is supported, false * otherwise. */ protected boolean allowDropElement(Object dropElement) { return dropElement instanceof Diagram; } /** * Understands the following: * <UL> * <LI>{@link DropObjectsRequest} * <LI>{@link RequestConstants#REQ_DROP_OBJECTS} * <LI>{@link org.eclipse.gef.RequestConstants#REQ_CREATE} * </UL> */ public boolean understandsRequest(Request req) { return (RequestConstants.REQ_DROP_OBJECTS.equals(req.getType()) || req instanceof DropObjectsRequest || RequestConstants.REQ_CREATE .equals(req.getType())) ? true : super.understandsRequest(req); } /** * Determines if this editpolicy would create a view for the supplied * semantic element. The default implementation will return <tt>true</tt> * if the supplied <tt>eObject</tt> is contained in {@link #getSemanticConnectionsList()}. * @param eObject a semantic element * @return <tt>true</tt> if this policy would create a view; * <tt>false</tt> otherwise. */ public boolean canCreate( EObject eObject ) { return super.canCreate(eObject) || getSemanticConnectionsList().contains(eObject); } }