blob: e12e5317996a4bc589bb10f9902023f083ee02c9 [file] [log] [blame]
/******************************************************************************
* 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.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Set;
import java.util.WeakHashMap;
import org.eclipse.core.commands.ExecutionException;
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.Status;
import org.eclipse.emf.common.notify.Notification;
import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.common.util.UniqueEList;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EReference;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.emf.transaction.Transaction;
import org.eclipse.emf.transaction.TransactionalEditingDomain;
import org.eclipse.emf.transaction.util.TransactionUtil;
import org.eclipse.emf.workspace.AbstractEMFOperation;
import org.eclipse.gef.EditPart;
import org.eclipse.gef.EditPartViewer;
import org.eclipse.gef.commands.Command;
import org.eclipse.gef.commands.CompoundCommand;
import org.eclipse.gef.editpolicies.AbstractEditPolicy;
import org.eclipse.gef.requests.CreateRequest;
import org.eclipse.gmf.runtime.common.core.command.CompositeCommand;
import org.eclipse.gmf.runtime.common.core.command.ICommand;
import org.eclipse.gmf.runtime.common.core.util.Log;
import org.eclipse.gmf.runtime.common.core.util.StringStatics;
import org.eclipse.gmf.runtime.common.core.util.Trace;
import org.eclipse.gmf.runtime.common.ui.util.DisplayUtils;
import org.eclipse.gmf.runtime.diagram.core.commands.DeleteCommand;
import org.eclipse.gmf.runtime.diagram.core.listener.DiagramEventBroker;
import org.eclipse.gmf.runtime.diagram.core.listener.NotificationListener;
import org.eclipse.gmf.runtime.diagram.core.listener.NotificationUtil;
import org.eclipse.gmf.runtime.diagram.core.util.ViewType;
import org.eclipse.gmf.runtime.diagram.core.util.ViewUtil;
import org.eclipse.gmf.runtime.diagram.ui.commands.CommandProxy;
import org.eclipse.gmf.runtime.diagram.ui.commands.CreateCommand;
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.IGraphicalEditPart;
import org.eclipse.gmf.runtime.diagram.ui.internal.DiagramUIDebugOptions;
import org.eclipse.gmf.runtime.diagram.ui.internal.DiagramUIPlugin;
import org.eclipse.gmf.runtime.diagram.ui.internal.DiagramUIStatusCodes;
import org.eclipse.gmf.runtime.diagram.ui.l10n.DiagramUIMessages;
import org.eclipse.gmf.runtime.diagram.ui.parts.DiagramGraphicalViewer;
import org.eclipse.gmf.runtime.diagram.ui.requests.CreateViewRequest;
import org.eclipse.gmf.runtime.diagram.ui.requests.CreateViewRequest.ViewDescriptor;
import org.eclipse.gmf.runtime.diagram.ui.util.EditPartUtil;
import org.eclipse.gmf.runtime.emf.core.util.EMFCoreUtil;
import org.eclipse.gmf.runtime.emf.core.util.EObjectAdapter;
import org.eclipse.gmf.runtime.notation.CanonicalStyle;
import org.eclipse.gmf.runtime.notation.DrawerStyle;
import org.eclipse.gmf.runtime.notation.Node;
import org.eclipse.gmf.runtime.notation.NotationPackage;
import org.eclipse.gmf.runtime.notation.Style;
import org.eclipse.gmf.runtime.notation.View;
import org.eclipse.osgi.util.NLS;
/**
* The base canonical editpolicy class.
* This edit policy will register itself with the model server to receive
* semantic events fired to its host editpart. It will create, if necessary,
* notation elements for all semantic elements inserted into the host
* element or delete the notation element for the semantic element removed
* from the host element.
* <P>
* This editpolicy will create the necessary notation element by simply
* returning a {@link org.eclipse.gmf.runtime.diagram.ui.commands.CreateCommand}.
*
* @see #refreshSemanticChildren()
* @see #handleSemanticEvent(NotificationEvent) will create or delete notation elements
* as required.
* @see #getCreateViewCommand(CreateRequest)
* @see #shouldDeleteView(View)
* @author mhanner, mmostafa
*/
public abstract class CanonicalEditPolicy extends AbstractEditPolicy
implements NotificationListener {
/** Runs the supplied commands asyncronously. */
private static class AsyncCommand extends Command {
private final CompoundCommand _cc;
/**
* constructor
* @param label this command label
*/
public AsyncCommand(String label) {
super(label);
_cc = new CompoundCommand(label);
}
/**
* constructor
* @param cmd the command
*/
public AsyncCommand( Command cmd ) {
this( cmd.getLabel() );
add( cmd );
}
/**
* Executes the command asynchonously.
* Calls {@link #doExecute}.
*/
public final void execute() {
// do not use Display.getCurrent() this mthod could be invoked
// on a non ui thread
DisplayUtils.getDisplay().asyncExec(new Runnable() {
public void run() {
AsyncCommand.this.doExecute();
}
} );
}
/**
* Return the command to be executed asynchronously.
* @return the command
*/
protected final CompoundCommand getCommand() {
return _cc;
}
/** Executes the command. */
protected void doExecute() {
getCommand().execute();
}
/**
* Add supplied command to the list of commands to be executed.
* @param cmd the command to add
*/
public void add( Command cmd ) {
_cc.add( cmd );
}
}
/** [semantic element, canonical editpolicy] registry map. */
static Map<EObject, Set<CanonicalEditPolicy>> _registry = new WeakHashMap<EObject, Set<CanonicalEditPolicy>>();
/** ModelServer Listener Identifiers. */
private static final String SEMANTIC_FILTER_ID = "SemanticFilterID";//$NON-NLS-1$
/** enable refresh flag. */
private boolean _enabled = true;
/** flag signaling a refresh request made while the editpolicy was disabled. */
private boolean _deferredRefresh = false;
/** semantic listener. */
private Map<String, Object[]> _listenerFilters;
/** Adds <code>String.class</tt> adaptablity to return a factory hint. */
protected static final class CanonicalElementAdapter extends EObjectAdapter {
private String _hint;
/**
* constructor
* @param element
* @param hint
*/
public CanonicalElementAdapter( EObject element, String hint ) {
super(element);
_hint = hint;
}
/** Adds <code>String.class</tt> adaptablity. */
public Object getAdapter(Class adapter) {
if ( adapter.equals(String.class) ) {
return _hint;
}
return super.getAdapter(adapter);
}
}
/** Register this editpolicy against its semantic host. */
private void RegisterEditPolicy() {
EObject semanticHost = getSemanticHost();
Set<CanonicalEditPolicy> set = _registry.get(semanticHost);
if ( set == null ) {
set = new HashSet<CanonicalEditPolicy>();
_registry.put( semanticHost, set );
}
set.add(this);
}
/** Unregisters this editpolicy from the cache. */
private void UnregisterEditPolicy() {
EObject semanticHost = null;
// 1st - delete unspecified refs
Set<CanonicalEditPolicy> set = _registry.get(null);
if ( set != null ) {
set.remove(this);
if ( set.isEmpty() ) {
_registry.remove(semanticHost);
}
}
// reverse key lookup since the unregistering an editpolicy
// typically means that the semantic element has been deleted.
for(EObject key : _registry.keySet()) {
if(_registry.get(key).contains(this)) {
semanticHost = key;
break;
}
}
set = _registry.get(semanticHost);
if ( set != null ) {
set.remove(this);
if ( set.isEmpty() ) {
_registry.remove(semanticHost);
}
}
}
/**
* Returns the <code>Canonical EditPolicies</code> mapped to the supplied <i>element</i>.
* Canonical EditPolicies are mapped to their {@link #getSemanticHost()} as
* the key. A single key may have multiple editpolicies registered against it.
* @param element a semantic element
* @return a unmodifiable list of semantic editpolicies listening to the supplied element
*/
public static List<CanonicalEditPolicy> getRegisteredEditPolicies( EObject element ) {
List<CanonicalEditPolicy> policies = new ArrayList<CanonicalEditPolicy>();
Collection<CanonicalEditPolicy> policiesWithSemanticElements = _registry.get(element);
if (policiesWithSemanticElements != null) {
policies.addAll(policiesWithSemanticElements);
}
Collection<CanonicalEditPolicy> policiesWithNullSemanticElements = _registry.get(null);
if (policiesWithNullSemanticElements != null) {
policies.addAll(policiesWithNullSemanticElements);
}
return Collections.unmodifiableList(policies);
}
/**
* Returns the <b>enabled </b> <code>Canonical EditPolicies</code> mapped
* to the supplied <i>element </i> that are an instance of the supplied
* <tt>clazz</tt>. Canonical EditPolicies are mapped to their
* {@link #getSemanticHost()}as the key. A single key may have multiple
* editpolicies registered against it.
*
* @param element
* a semantic element
* @param clazz
* a class type
* @return an unmodifiable list of semantic editpolicies listening to the
* supplied element
*/
public static List<CanonicalEditPolicy> getRegisteredEditPolicies( EObject element, Class clazz ) {
List<CanonicalEditPolicy> registeredPolicies = new ArrayList<CanonicalEditPolicy>();
for(CanonicalEditPolicy cep : getRegisteredEditPolicies(element)) {
if ( cep.isEnabled() && clazz.isInstance(cep) ) {
registeredPolicies.add(cep);
}
}
return Collections.unmodifiableList(registeredPolicies);
}
/** Asserts that the supplied host is an {@link IGraphicalEditPart} instance. */
public void setHost(EditPart host) {
if ( !(host instanceof IGraphicalEditPart) ) {
throw new IllegalArgumentException();
}
super.setHost(host);
}
/**
* @return <code>(IGraphicalEditPart)host()</code>.
*/
protected final IGraphicalEditPart host() {
return (IGraphicalEditPart)getHost();
}
/**
* Return the host's semantic children. <BR>
* @return a list of semantic children.
*/
/*
* Changing the return type to List<EObject> breaks API for clients
*/
abstract protected List getSemanticChildrenList();
/**
* Returns the default factory hint.
* @return <code>host().getView().getSemanticType()</code>
*/
protected String getDefaultFactoryHint() {
return ((View)host().getModel()).getType();
}
/**
* Return a factory hint to assign to this element. The supplied
* default hint is used if no hint can be found.
* @see #getFactoryHint(IAdaptable)
* @param elementAdapter adapter that adapts to {@link EObject}.
* @param defaultHint a default factory hint (typically the host's factory hint).
* @return a factory hint.
*/
protected String getFactoryHint(
IAdaptable elementAdapter,
final String defaultHint) {
String fh = getFactoryHint(elementAdapter);
return fh == null ? defaultHint : fh;
}
/**
* Clients may override this method to return an appropriate factory
* hint for the supplied semantic element. Returning <tt>null</tt> will
* set the factory hint to the host editpart's factory hint.
* @see #getFactoryHint(IAdaptable, String)
* @param elementAdapter adapter that adapts to {@link EObject}.
* @return <tt>null</tt>.
*/
protected String getFactoryHint(IAdaptable elementAdapter) {
return null;
}
/**
* Deletes a list of views. The views will be deleted <tt>iff</tt> their semantic
* element has also been deleted.
* @param views an iterator on a list of views.
* @return <tt>true</tt> if the host editpart should be refreshed; either one one of the supplied
* views was deleted or has been reparented.
*/
protected final boolean deleteViews( Iterator<View> views ) {
if ( !isEnabled() ) {
return false;
}
final CompoundCommand cc = new CompoundCommand(DiagramUIMessages.DeleteCommand_Label);
while (views.hasNext()) {
View view = (View)views.next();
if ( shouldDeleteView(view) ) {
cc.add(getDeleteViewCommand(view));
}
}
boolean doDelete = !cc.isEmpty() && cc.canExecute();
if ( doDelete ) {
executeCommand(cc);
}
return doDelete;
}
/**
* gets a {@link Command} to delete the supplied {@link View}.
* @param view view to use
* @return command
*/
protected Command getDeleteViewCommand(View view) {
TransactionalEditingDomain editingDomain = ((IGraphicalEditPart) getHost()).getEditingDomain();
return new ICommandProxy(new DeleteCommand(editingDomain, view));
}
/**
* returns <tt>true</tt> to always delete a view if required.
* @param view to consider
* @return true or false
*/
protected boolean shouldDeleteView(View view) {
return true;
}
/**
* Return a list of all the notation elements mapped to the supplied semantic element.
* @param element to use
* @return list of <code>View</code>s
*/
protected List<View> getViewReferers(EObject element) {
List<View> views = new ArrayList<View>();
if (element != null) {
EReference[] features = {NotationPackage.eINSTANCE
.getView_Element()};
views.addAll(EMFCoreUtil.getReferencers(element, features));
}
return views;
}
/**
* This method tries to locate the position that the view will be
* inserted into it's parent. The position is determined by the position
* of the semantic element. If the semantic element is not found the view
* will be appended to it's parent.
*
* @param semanticChild
* @return position where the view should be inserted
*/
protected int getViewIndexFor(EObject semanticChild) {
// The default implementation returns APPEND
return ViewUtil.APPEND;
}
/**
* Creates a <code>View</code> element for each of the supplied semantic elements.
* @param eObjects list of semantic element
* @return a list of {@link IAdaptable} that adapt to {@link View}.
*/
protected final List<IAdaptable> createViews(List<EObject> eObjects) {
List<ViewDescriptor> descriptors = new ArrayList<ViewDescriptor>();
for(EObject element : eObjects) {
if ( element != null ) {
descriptors.add(getViewDescriptor(element));
}
}
if ( !descriptors.isEmpty() ) {
// create the request
CreateViewRequest request = getCreateViewRequest(descriptors);
// get the command and execute it.
Command cmd = getCreateViewCommand(request);
if ( cmd != null && cmd.canExecute() ) {
SetViewMutabilityCommand.makeMutable(new EObjectAdapter(host().getNotationView())).execute();
executeCommand(cmd);
List<IAdaptable> adapters = (List<IAdaptable>)request.getNewObject();
return adapters;
}
}
return Collections.emptyList();
}
/**
* Executes the supplied command inside an <code>unchecked action</code>
* @param cmd command that can be executed (i.e., cmd.canExecute() == true)
*/
protected void executeCommand( final Command cmd ) {
Map<String, Boolean> options = null;
EditPart ep = getHost();
boolean isActivating = true;
// use the viewer to determine if we are still initializing the diagram
// do not use the DiagramEditPart.isActivating since ConnectionEditPart's
// parent will not be a diagram edit part
EditPartViewer viewer = ep.getViewer();
if (viewer instanceof DiagramGraphicalViewer){
isActivating = ((DiagramGraphicalViewer)viewer).isInitializing();
}
if (isActivating||
!EditPartUtil.isWriteTransactionInProgress((IGraphicalEditPart)getHost(), false, false))
options = Collections.singletonMap(Transaction.OPTION_UNPROTECTED,
Boolean.TRUE);
AbstractEMFOperation operation = new AbstractEMFOperation(
((IGraphicalEditPart) getHost()).getEditingDomain(),
StringStatics.BLANK, options) {
protected IStatus doExecute(IProgressMonitor monitor,
IAdaptable info)
throws ExecutionException {
cmd.execute();
return Status.OK_STATUS;
}
};
try {
operation.execute(new NullProgressMonitor(), null);
} catch (ExecutionException e) {
Trace.catching(DiagramUIPlugin.getInstance(),
DiagramUIDebugOptions.EXCEPTIONS_CATCHING, getClass(),
"executeCommand", e); //$NON-NLS-1$
Log.warning(DiagramUIPlugin.getInstance(),
DiagramUIStatusCodes.IGNORED_EXCEPTION_WARNING,
"executeCommand", e); //$NON-NLS-1$
}
}
/**
* Returns a {@link CreateCommand} for each view descriptor contained
* in the supplied request without forwarding create requests to the
* host editpart.
* @param request a create request
* @return command create view command(s)
*/
protected Command getCreateViewCommand(CreateRequest request) {
assert request instanceof CreateViewRequest;
CompositeCommand cc = new CompositeCommand(DiagramUIMessages.AddCommand_Label);
Command cmd = host().getCommand(request);
if (cmd == null) {
for(ViewDescriptor descriptor : ((CreateViewRequest)request).getViewDescriptors()) {
ICommand createCommand = getCreateViewCommand(descriptor);
cc.compose(createCommand);
}
} else {
cc.compose(new CommandProxy(cmd));
for(ViewDescriptor descriptor : ((CreateViewRequest)request).getViewDescriptors()) {
cc.compose(new CommandProxy(SetViewMutabilityCommand.makeMutable(descriptor)));
}
}
return new ICommandProxy(cc.reduce());
}
/**
* @param descriptor
* @return ICommand to create a view given a descriptor
*/
protected ICommand getCreateViewCommand(CreateViewRequest.ViewDescriptor descriptor) {
TransactionalEditingDomain editingDomain = ((IGraphicalEditPart) getHost()).getEditingDomain();
CreateCommand createCommand =
new CreateCommand(editingDomain,
descriptor,
(View)getHost().getModel());
CompositeCommand cmd = new CompositeCommand(DiagramUIMessages.AddCommand_Label);
cmd.compose(createCommand);
cmd.compose(new CommandProxy(SetViewMutabilityCommand.makeMutable(descriptor)));
return cmd;
}
/**
* Return a create view request.
* @param descriptors a {@link CreateViewRequest.ViewDescriptor} list.
* @return a create request
*/
protected CreateViewRequest getCreateViewRequest( List<ViewDescriptor> descriptors ) {
return new CreateViewRequest( descriptors );
}
/**
* Return a view descriptor.
* @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 descriptor
*/
protected CreateViewRequest.ViewDescriptor getViewDescriptor(
IAdaptable elementAdapter,
Class viewKind,
String hint,
int index) {
return new CreateViewRequest.ViewDescriptor(
elementAdapter,
viewKind,
hint,
index,
false,
host().getDiagramPreferencesHint());
}
/**
* Convenience method to create a view descriptor. Will call
* {@link #getViewDescriptor(IAdaptable, Class, String, int)}
* @param element semantic element.
* @return view descriptor
*/
protected CreateViewRequest.ViewDescriptor getViewDescriptor( EObject element ) {
//
// create the view descriptor
String factoryHint = getDefaultFactoryHint();
IAdaptable elementAdapter =
new CanonicalElementAdapter(element, factoryHint);
int pos = getViewIndexFor(element);
CreateViewRequest.ViewDescriptor descriptor =
getViewDescriptor(
elementAdapter,
Node.class,
getFactoryHint(elementAdapter, factoryHint),
pos);
return descriptor;
}
/**
* Registers with the model server to receive semantic events targeted to
* the host editpart. By default, this editpolicy will receive events fired
* to the semantic element mapped to the host editpart. Clients wanting to
* listen to another semantic element should override {@link #getSemanticHost()}
* @see #deactivate()
*/
public void activate() {
EObject semanticHost = getSemanticHost();
if ( semanticHost != null && !isActive() ) {
addListenerFilter(SEMANTIC_FILTER_ID, this, semanticHost);
// add listener to host view (handle case when user changes "visibility" property)
addListenerFilter("NotationListener_Visibility", //$NON-NLS-1$
this,
(View)getHost().getModel(),
NotationPackage.eINSTANCE.getView_Visible());
Style style = ((View)host().getModel()).getStyle(NotationPackage.eINSTANCE.getDrawerStyle());
if ( style != null ) {
addListenerFilter("NotationListener_DrawerStyle", this, style); //$NON-NLS-1$
}
style = ((View)host().getModel()).getStyle(NotationPackage.eINSTANCE.getCanonicalStyle());
if ( style != null ) {
addListenerFilter("NotationListener_CanonicalStyle", this, style); //$NON-NLS-1$
}
refreshOnActivate();
}
RegisterEditPolicy();
}
/**
* Refresh that is called on activate of the editpolicy to ensure that all relevant editparts
* can receive canonically created connections.
*/
protected void refreshOnActivate() {
refresh();
}
/**
* Return <tt>true</tt> if the editpolicy is enabled and its host
* is visible; otherwise <tt>false</tt>.
* @return <tt>true</tt>
*/
public boolean isEnabled() {
// if the editing domain is null then there is no point in enabling the edit policy
// the editing domain could be null because the view is detached or if the host is detached
if ( TransactionUtil.getEditingDomain((EObject)getHost().getModel())==null){
return false;
}
DrawerStyle dstyle = (DrawerStyle) ((View)host().getModel()).getStyle(NotationPackage.eINSTANCE.getDrawerStyle());
boolean isCollapsed = dstyle == null ? false : dstyle.isCollapsed();
if ( isCollapsed ) {
return false;
}
CanonicalStyle style = getCanonicalStyle();
boolean enabled = _enabled && ((View)host().getModel()).isVisible();
return style == null
? enabled
: style.isCanonical() && enabled;
}
/**
* Disables the editpolicy. While disabled, the editpolicy
* will not perform any refreshes.
* @param enable
*/
public void enableRefresh( boolean enable ) {
_enabled = enable;
if ( _enabled && _deferredRefresh ) {
_deferredRefresh = false;
refresh();
}
}
/**
* Sets enable(aBoolean) on all the edit policies of the semantic host.
* @param enable
*/
public void setEnable( boolean enable ) {
EObject sHost = getSemanticHost();
List<CanonicalEditPolicy> registeredPolicies = getRegisteredEditPolicies(sHost);
CanonicalEditPolicy[] policies = new CanonicalEditPolicy[registeredPolicies.size()];
registeredPolicies.toArray(policies);
for ( int i = 0; i < policies.length; i++ ) {
policies[i].enableRefresh(enable);
}
}
/**
* check is this edit policy is active or not
* @return <tt>true</tt> if the this editpart has already been activated;
* otherwise <tt>false</tt>.
*/
public final boolean isActive() {
return _listenerFilters == null
? false
: _listenerFilters.containsKey(SEMANTIC_FILTER_ID);
}
/**
* Return the semantic element to be <i>listened</i> to by this editpolicy.
* @return <code>host().getView().resolveSemanticElement()</code> by default.
*/
public EObject getSemanticHost() {
return ViewUtil.resolveSemanticElement((View)host().getModel());
}
/**
* Unregisters all registered model server listeners.
* @see #activate()
*/
public void deactivate() {
if (_listenerFilters != null) {
Map<String, Object[]> listeners = new HashMap<String, Object[]>(_listenerFilters);
for(String id: listeners.keySet()) {
removeListenerFilter(id);
}
}
UnregisterEditPolicy();
}
/**
* Adds a listener filter by adding the given listener to a passed notifier.
* The supplied <tt>listener</tt> will not be added to there is already a listener
* registered against the supplied <tt>filterId</tt>
*
* @param filterId A unique filter id (within the same editpart instance)
* @param listener A listener instance
* @param notifier An element notifer to add the listener to
* @return <tt>true</tt> if the listener was added, otherwise <tt>false</tt>
* @throws NullPointerException if either <tt>filterId</tt> or <tt>listner</tt> parameters are <tt>null</tt>.
*/
protected boolean addListenerFilter(
String filterId,
NotificationListener listener,
EObject element) {
if ( filterId == null || listener == null ) {
throw new NullPointerException();
}
if (element != null) {
if (_listenerFilters == null)
_listenerFilters = new HashMap<String, Object[]>();
if ( !_listenerFilters.containsKey(filterId)) {
getDiagramEventBroker().addNotificationListener(element,listener);
_listenerFilters.put(filterId, new Object[] { element, listener });
return true;
}
}
return false;
}
/**
* Adds a listener filter by adding the given listener to a passed notifier.
* The supplied <tt>listener</tt> will not be added to there is already a listener
* registered against the supplied <tt>filterId</tt>
*
* @param filterId A unique filter id (within the same editpart instance)
* @param listener A listener instance
* @param notifier An element notifer to add the listener to
* @return <tt>true</tt> if the listener was added, otherwise <tt>false</tt>
* @throws NullPointerException if either <tt>filterId</tt> or <tt>listner</tt> parameters are <tt>null</tt>.
*/
protected boolean addListenerFilter(
String filterId,
NotificationListener listener,
EObject element,
EStructuralFeature feature) {
if ( filterId == null || listener == null ) {
throw new NullPointerException();
}
if (element != null) {
if (_listenerFilters == null)
_listenerFilters = new HashMap<String, Object[]>();
if ( !_listenerFilters.containsKey(filterId)) {
getDiagramEventBroker().addNotificationListener(element,feature,listener);
_listenerFilters.put(filterId, new Object[] { element,feature, listener });
return true;
}
}
return false;
}
/**
* Removes a listener previously added with the given id
* @param filterId the filter id
*/
protected void removeListenerFilter(String filterId) {
if (_listenerFilters == null) {
return;
}
Object[] objects = (Object[]) _listenerFilters.remove(filterId);
if (objects == null) {
return;
}
if (objects.length > 2) {
getDiagramEventBroker().removeNotificationListener(
(EObject) objects[0], (EStructuralFeature) objects[1],
(NotificationListener) objects[2]);
} else {
getDiagramEventBroker().removeNotificationListener(
(EObject) objects[0], (NotificationListener) objects[1]);
}
}
/**
* Event callback: filters out non IElementEvent events.
* @param event an event fired from the model server.
*/
public final void notifyChanged(Notification notification) {
if ( isHostStillValid()) {
Object element = notification.getNotifier();
if ( element == null ) {
return;
}
handleNotificationEvent(notification);
}
}
/**
* Return <tt>true</tt> if the host is active and its view has not
* been deleted; otherwise <tt>false</tt>
* @return true or false
*/
protected final boolean isHostStillValid() {
if (!host().isActive()) {
return false;
}
// is it detached?
EObject eObject = (EObject) host().getModel();
if (eObject != null && eObject.eResource() == null
&& !eObject.eIsProxy()) {
return false;
}
return true;
}
/**
* Handles <code>NotificationEvent</code> and resynchronizes the canonical
* container if the event should be handled.
*
* @param event <code>NotificationEvent</code> to handle.
*/
protected void handleNotificationEvent(Notification event) {
boolean shouldRefresh = false;
if ( shouldHandleNotificationEvent(event) ) {
if ( NotationPackage.eINSTANCE.getCanonicalStyle_Canonical() == event.getFeature() ) {
CanonicalStyle style = (CanonicalStyle) ((View)host().getModel()).getStyle(NotationPackage.eINSTANCE.getCanonicalStyle());
if (style != null) {
setEnable(style.isCanonical());
}
}
shouldRefresh = true;
}
if (shouldRefresh)
refresh();
}
/**
* Determines if the the <code>NotificationEvent</code> should be handled / processed
* by the editpolicy.
*
* @param event <code>NotificationEvent</code> to check
* @return <code>true</code> if event should be handled, <code>false</code> otherwise.
*/
protected boolean shouldHandleNotificationEvent(Notification event) {
if ( NotationPackage.eINSTANCE.getDrawerStyle_Collapsed() == event.getFeature() ||
NotationPackage.eINSTANCE.getCanonicalStyle_Canonical() == event.getFeature() ||
NotationPackage.eINSTANCE.getView_Visible() == event.getFeature() ||
NotationPackage.eINSTANCE.getView_PersistedChildren() == event.getFeature()) {
return true;
}
Object element = event.getNotifier();
if (element instanceof EObject && !(element instanceof View)){
boolean addOrDelete = (NotificationUtil.isElementAddedToSlot(event)
|| NotificationUtil.isElementRemovedFromSlot(event));
EStructuralFeature feature = getFeatureToSynchronize();
if (feature!=null){
if (feature.equals(event.getFeature()) &&
(addOrDelete||NotificationUtil.isSlotModified(event))){
return true;
}
return false;
}
Set<EStructuralFeature> features = getFeaturesToSynchronize();
if (features!=null && !features.isEmpty()){
if (features.contains(event.getFeature())&&
(addOrDelete||NotificationUtil.isSlotModified(event))){
return true;
}
return false;
}
// just for backward compatibility will not be needed when all clients migrate
if (addOrDelete){
return true;
}
}
return false;
}
/**
* Return the host's model children.
* @return list of <code>View</Code>s
*/
protected List<View> getViewChildren() {
return getViewChildren((View) host().getModel());
}
/**
* Return the host's model children. This is a recursive method that handles
* groups.
*
* @param view
* the view to find the children for
* @return list of children views with groups removed.
*/
private List<View> getViewChildren(View view) {
ArrayList<View> list = new ArrayList<View>();
for(View child : (EList<View>)view.getChildren()) {
if (child instanceof Node
&& ViewType.GROUP.equals(((Node) child).getType())) {
list.addAll(getViewChildren(child));
} else {
list.add(child);
}
}
return list;
}
/**
* Resynchronize the canonical container.
*/
public final void refresh() {
try {
if ( isEnabled() ) {
// avoid re-entry
boolean defRefresh = _deferredRefresh;
_deferredRefresh = false;
refreshSemantic();
_deferredRefresh = defRefresh;
}
else {
_deferredRefresh = true;
}
}
catch ( Throwable t ) {
String eMsg = DiagramUIMessages.CanonicalEditPolicy_refresh_failed_ERROR_;
Log.error(DiagramUIPlugin.getInstance(), IStatus.WARNING,
eMsg, t);
}
}
/**
* Redirects the call to {@link #refreshSemanticChildren()}.
*/
protected void refreshSemantic() {
List<IAdaptable> createdViews = refreshSemanticChildren();
makeViewsImmutable(createdViews);
}
/**
* Sets state on views to allow for modification without changing their
* non-persisted status.
*
* @param createdViews <code<>List</code> of view adapters that were created during the
* {@link CanonicalEditPolicy#refreshSemantic()} operation
*/
final protected void makeViewsMutable(List<IAdaptable> createdViews) {
if (createdViews != null && !createdViews.isEmpty()) {
List<IAdaptable> viewAdapters = prepareAdapterList(createdViews);
executeCommand(SetViewMutabilityCommand.makeMutable(viewAdapters));
}
}
/**
* Sets views as being immutable, meaning that they are unmodifiable as
* non-persisted views. Any subsequent change to an immutable view will force
* the view to be persisted.
*
* @param createdViews <code<>List</code> of view adapters that were created during the
* {@link CanonicalEditPolicy#refreshSemantic()} operation
*/
final protected void makeViewsImmutable(List<IAdaptable> createdViews) {
if (createdViews != null && !createdViews.isEmpty()) {
addListenersToContainers(createdViews);
List<IAdaptable> viewAdapters = prepareAdapterList(createdViews);
Command immutable = SetViewMutabilityCommand.makeImmutable(viewAdapters);
AsyncCommand ac = new AsyncCommand(immutable);
ac.execute();
}
}
private void addListenersToContainers(List<IAdaptable> createdViews) {
UniqueEList<View> list = new UniqueEList<View>();
for(IAdaptable obj : createdViews) {
View view = (View)obj.getAdapter(View.class);
if (view != null) {
list.add((View)view.eContainer());
}
}
for(View containerView : list) {
addListenerFilter("NotationListener_Container_" + containerView.getClass().getName() + '@' + Integer.toHexString(containerView.hashCode()), //$NON-NLS-1$
this,
containerView,
NotationPackage.eINSTANCE.getView_PersistedChildren());
}
}
private List<IAdaptable> prepareAdapterList(List<IAdaptable> createdViews) {
List<IAdaptable> viewAdapters = new ArrayList<IAdaptable>();
View hostView = host().getNotationView();
if (hostView != null) {
viewAdapters.add(new EObjectAdapter(hostView));
}
ListIterator<IAdaptable> li = createdViews.listIterator();
while (li.hasNext()) {
IAdaptable adapter = li.next();
if (adapter != null) {
if (!(adapter instanceof IAdaptable) && adapter instanceof EObject) {
viewAdapters.add(new EObjectAdapter((EObject)adapter));
} else {
viewAdapters.add(adapter);
}
}
}
return viewAdapters;
}
/**
* Updates the set of children views so that it
* is in sync with the semantic children. This method is called
* in response to notification from the model.
* <P>
* The update is performed by comparing the exising views with the set of
* semantic children returned from {@link #getViewChildren()}. Views whose
* semantic element no longer exists are {@link #deleteViews(Iterator) removed}.
* New semantic children have their View {@link #createViews(List)
* created}. Subclasses must override <code>getSemanticChildren()</code>.
* <P>
* Unlike <code>AbstractEditPart#refreshChildren()</code>, this refresh 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 edit policy will handle
* a specifc subset of the host's views.
* <P>
* The host is refreshed if a view has created or deleted as a result of this
* refresh.
*
* @return <code>List</code> of new <code>View</code> objects that were created as a result of
* the synchronization
*/
protected final List<IAdaptable> refreshSemanticChildren() {
// Don't try to refresh children if the semantic element
// cannot be resolved.
if (resolveSemanticElement() == null) {
return Collections.emptyList();
}
// current views
List<View> viewChildren = getViewChildren();
List<EObject> semanticChildren = new ArrayList<EObject>(getSemanticChildrenList());
List<View> orphaned = cleanCanonicalSemanticChildren(viewChildren, semanticChildren);
boolean changed = false;
//
// delete all the remaining orphaned views
if ( !orphaned.isEmpty() ) {
changed = deleteViews(orphaned.iterator());
}
//
// create a view for each remaining semantic element.
List<IAdaptable> createdViews = Collections.emptyList();
if ( !semanticChildren.isEmpty() ) {
createdViews = createViews( semanticChildren );
for ( int i = 0; i < createdViews.size(); i++ ) {
View createdView = (View)((IAdaptable)createdViews.get(i)).getAdapter(View.class);
if (createdView == null) {
String eMsg =
NLS
.bind(
DiagramUIMessages.CanonicalEditPolicy_create_view_failed_ERROR_,
semanticChildren.get(i));
IllegalStateException ise =
new IllegalStateException(eMsg);
Log.error(
DiagramUIPlugin.getInstance(),
IStatus.ERROR,
eMsg,
ise);
throw ise;
}
}
}
if (changed || createdViews.size() > 0) {
postProcessRefreshSemantic(createdViews);
}
return createdViews;
}
/**
* Synchronizes the semanticChildren the viewChildren to discover if any of the semanticChildren
* don't have a corresponding view. Any <code>semanticChildren</code> that do have a view are
* removed from the list.
*
* @param viewChildren <code>List</code> of <code>View</code> elements that already exist in the container.
* @param semanticChildren <code>List</code> of semantic elements that are candidates for synchronization
* @return <code>List</code> of orphans views that should be deleted from the container.
*/
final protected List<View> cleanCanonicalSemanticChildren(Collection<? extends View> viewChildren, Collection<EObject> semanticChildren) {
EObject semanticChild;
List<View> orphaned = new ArrayList<View>();
Map<EObject, View> viewToSemanticMap = new HashMap<EObject, View>();
for(View viewChild : viewChildren) {
semanticChild = viewChild.getElement();
if (!isOrphaned(semanticChildren, viewChild)) {
semanticChildren.remove(semanticChild);
viewToSemanticMap.put(semanticChild, viewChild);
}
else {
orphaned.add(viewChild);
}
View viewInMap = viewToSemanticMap.get(semanticChild);
if (viewInMap != null && !viewChild.equals(viewInMap)) {
if (viewInMap.isMutable()) {
orphaned.remove(viewChild);
orphaned.add(viewInMap);
viewToSemanticMap.put(semanticChild, viewChild);
}
}
}
return orphaned;
}
/**
* Decide if the passed view is orphaned or not
*
* @param semanticChildren
* semantic children to check against
* @param view
* the view that should be checked
* @return true if orphaned other wise false
*/
protected boolean isOrphaned(Collection<EObject> semanticChildren, View view) {
return !semanticChildren.contains(view.getElement());
}
/**
* Allow for post processing of the refresh semantic to set the view mutable
* state and allow subclasses to add functionality.
*
* @param viewDescriptors <code>List</code> of IAdaptable that adapt to <code>View</code>
*/
protected void postProcessRefreshSemantic(List<IAdaptable> viewDescriptors) {
// need to refresh host to create editparts so that dependent canonical editpolicies can synchronize as well.
getHost().refresh();
}
/**
* Convenience method to return the host's semantic element.
* Same as calling <code>host().getView().resolveSemanticElement();</code>
* @return an {@link EObject}
*/
protected final EObject resolveSemanticElement() {
return ViewUtil.resolveSemanticElement((View)host().getModel());
}
/**
* gets the canonical style that may be installed on the host shape compartment view.
* @return <code>CanonicalStyle</code>
*/
protected CanonicalStyle getCanonicalStyle() {
return (CanonicalStyle) ((View)host().getModel()).getStyle(NotationPackage.eINSTANCE.getCanonicalStyle());
}
/**
* Gets the diagram event broker from the editing domain.
*
* @return the diagram event broker
*/
private DiagramEventBroker getDiagramEventBroker() {
TransactionalEditingDomain theEditingDomain = ((IGraphicalEditPart) getHost())
.getEditingDomain();
if (theEditingDomain != null) {
return DiagramEventBroker.getInstance(theEditingDomain);
}
return null;
}
/*
* (non-Javadoc)
* @see org.eclipse.gmf.runtime.diagram.ui.internal.parts.NotificationForEditPartsListener#getViewer()
*/
final public EditPartViewer getViewer() {
return getHost().getViewer();
}
/**
* This method should be overridden by sub classes to provide the features the canonical edit policy
* will use to synchronize the views with the semantic element
* This method should be overridden only if the edit policy synchronizes more than one EStructuralFeature
* @return Set of EStructuralFeature features
*/
protected Set<EStructuralFeature> getFeaturesToSynchronize(){
return Collections.emptySet();
}
/**
* This method should be overridden by sub classes to provide the feature the canonical edit policy
* will use to synchronize the views with the semantic element
* This method should be overridden only if the edit policy synchronizes only one EStructuralFeature
* @return EStructuralFeature
*/
protected EStructuralFeature getFeatureToSynchronize(){
return null;
}
/**
* 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 #getSemanticChildrenList()}.
* @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 eObject == null
? false
: getSemanticChildrenList().contains(eObject);
}
}