Notice: This Wiki is now read only and edits are no longer possible. Please see: https://gitlab.eclipse.org/eclipsefdn/helpdesk/-/wikis/Wiki-shutdown-plan for the plan.
Papyrus/Papyrus Developer Guide/Editing Domains and Commands
Contents
Initialize a new command with an editing domain
The same editing domain must always be used. Otherwise, you can result in actions which are stored in different domains and different stacks, which leads to several operation history which manage the same diagram but different operations.
The best way to achieve this is to always recover the editing domain using the method
org.eclipse.gmf.runtime.diagram.ui.editparts.GraphicalEditPart.getEditingDomain()
when you have an edit part at hand.
You can also use a method from one of the following classes, in order of preference:
If you have the serviceRegistry:
import org.eclipse.papyrus.core.utils.ServiceUtils; ServiceUtils.getTransactionalEditingDomain(serviceRegistry);
If you have an EditPart:
import org.eclipse.papyrus.diagram.common.util.ServiceUtilsForGMF; ServiceUtilsForGMF.getTransactionalEditingDomain( editpart.getDiagramEditDomain() );
If you have an EditPolicy:
import org.eclipse.papyrus.diagram.common.util.ServiceUtilsForGMF; ServiceUtilsForGMF.getTransactionalEditingDomain( policy.getHost().getDiagramEditDomain() );
If you are in an ActionHandler (to be used with care !)
import org.eclipse.papyrus.core.utils.ServiceUtilsForActionHandlers; ServiceUtilsForActionHandlers.getTransactionalEditingDomain();
Do not use anymore the old deprecated method:
org.eclipse.papyrus.core.utils.EditorUtils.getTransactionalEditingDomain()
You can find more about this classes and methods in http://git.eclipse.org/c/papyrus/org.eclipse.papyrus.git/tree/doc/DevelopperDocuments/cookbook/PapyrusCookBook.odt
EMF Transaction framework
Always use a transactional command and domain when you can. The avantages of the EMF Transaction framework are :
- Operations will be validated (emf validation is called by gmf framework on the editing domain). This validation can check a model or eventually correct it or reject modifications (using rules defined with the
org.eclipse.emf.validation.constraintBindings
extension point).
- Operations in a same transaction, will be rejected (in case validation fails), or undone, or will fail (in case an exception occurs) all at the same time. This avoids having unconsistent models with half-executed actions.
Implementing new commands
- Note: Avoid to use GMF packages if your plugin doesn't need to depend on GMF. Use EMF transactional commands instead.
- For model changes commands, try using transactionnal commands, by implementing
org.eclipse.gmf.runtime.emf.commands.core.command.AbstractTransactionalCommandnote: Avoid to use the GMF packages as often as possible. Use the EMF transactional commands instead.
They may seem painfull to implement, but it ensures a better model consistency and its the only way to respect the EMF_Transaction_framework. In most cases, extending
org.eclipse.gmf.runtime.common.core.command.CompositeCommandnote: Avoid to use the GMF packages as often as possible. Use the EMF transactional commands instead.
to add classic commands (AddCommand, SetPropertyCommand, ...) to make the job works fine and avoids any coding error.
- You may also use GEF commands for graphical changes, inheriting
org.eclipse.gef.commands.Command note: Avoid to use the GMF packages as often as possible. Use the EMF transactional commands instead.
- GMF non transactional commands extending
org.eclipse.gmf.runtime.common.core.command.AbstractCommandnote: Avoid to use the GMF packages as often as possible. Use the EMF transactional commands instead.
are rarely used. Generally, they act like a proxy for GEF or transactional EMF commands, or may extend
org.eclipse.gmf.runtime.common.core.command.CompositeCommandnote: Avoid to use the GMF packages as often as possible. Use the EMF transactional commands instead.
to enclose other GMF commands (which themselves follow the same rules). Otherwise, they must have no impact on the resource, for example, opening a popup.
Call a command's execution
Classic way
- Changes which impact the model should get the CommandStack from the transactionalEditingDomain:
transactionalEditingDomain.getCommandStack().execute(cmd);
- It is also possible to execute against the IOperationHistory:
OperationHistoryFactory.getOperationHistory().execute(cmd)
- Changes which impact a GMF diagram should call the method
org.eclipse.gmf.runtime.diagram.ui.parts.DiagramCommandStack.execute(ICommand, IProgressMonitor)
(this method encloses the IOperationHistory.execute method, but catches raised exceptions to avoid the diagram exploding in front of the user).
- Make a single call per action, by constructing a compound (or composite) command.
- You may use proxies or other execute methods of the same class to call any kind of commands (GEF, GMF, transactional EMF). The java compiler will avoid your getting lost in the commands hierarchies.
The diagram command stack can be obtained from an edit part with
getDiagramEditDomain().getDiagramCommandStack();
You can also use, in case there is no edit part at your disposition :
CommandStack stack = (CommandStack)EditorUtils.getMultiDiagramEditor().getAdapter(CommandStack.class); if(stack != null) { stack.execute(org.eclipse.gef.commands.Command); }
This call is in fact hiding a call to the same method by inheritance (Papyrus editor has a diagram command stack). But this call is less satisfying, because it can be confused with
org.eclipse.emf.edit.domain.EditingDomain.getCommandStack().execute(...)
which MUST NOT be used (often called with the wrong editing domain, Initialize a new command with an editing domain).
Commands executed in a Post-Commit context (for advanced users)
Direct command
In a post-commit context, you may call directly
org.eclipse.gmf.runtime.diagram.core.util.ViewUtil.setStructuralFeatureValue(View, EStructuralFeature, Object)
(for graphical changes), or use
org.eclipse.emf.common.command.CompoundCommand.execute()
only in case your operation must not be undone nor logged in the history.
There are very few examples of such a case. In fact, I have met only two cases so far :
- When the figure is being updated in an edit part thanks to a listener on the model (generally through the usage of
org.eclipse.papyrus.diagram.common.helper.NotificationHelper
). In such a case, the graphic change is only a consequence of the model change, and undoing the model change will modify the figure again through the same listener.
- In a validator registered with
org.eclipse.emf.validation.constraintProviders
extension point. You can make a corrector, which will correct the model at each modification instead of simply validate and reject an unvalid modification. Any other way will raise an exception since the operation being validated/corrected is in post-commit state. This corrector approach works only in case you have redundant information in the model. For examples, a call behavior action's pins are heavily synchronized with the behavior's parameters ; when parameters are modified, you can deduce that the same modifications must occur on the corresponding pin (as implemented in org.eclipse.papyrus.diagram.activity.helper.PinAndParameterSynchronizer).
Editing Domain Listener
Thanks to an extension point you can listen the editing domain in order to interact with it. This extension point is org.eclipse.emf.transaction.listeners. To register on the Papyrus Editing Domain use the id org.eclipse.papyrus.SharedEditingDomainID. For example:
<extension point="org.eclipse.emf.transaction.listeners"> <listener class="org.eclipse.papyrus.diagram.activity.listeners.InterruptibleEdgeListener"> <editingDomain id="org.eclipse.papyrus.SharedEditingDomainID"> </editingDomain> </listener> </extension>
Now their is a convenient Abstract Class to create quick listener that you can find at org.eclipse.papyrus.diagram.common.listeners.AbstractModifcationTriggerListener<T>. This listener inherit from TriggerListener which allow to concatenate command before post commit state. The abstract class will ask you to implement methods such as isCorrectStructuralfeature which return true if the feature of the event concern you.
You can use the following example to implement it:
public class InterruptibleEdgeListener extends AbstractModifcationTriggerListener<ActivityEdge> { @Override protected boolean isCorrectStructuralfeature(EStructuralFeature eStructuralFeature) { if(UMLPackage.Literals.ACTIVITY_EDGE__INTERRUPTS.equals(eStructuralFeature)) { return true; } return false; } @Override protected ICommand getModificationCommand(Notification notif) { if(Notification.SET == notif.getEventType()) { IGraphicalEditPart edgeEditPart = getChildByEObject((EObject)notif.getNotifier(), getDiagramEditPart(), true); if(edgeEditPart != null && edgeEditPart instanceof InterruptibleEdge) { InterruptibleEdgeRequest request = new InterruptibleEdgeRequest(); if(notif.getNewValue() != null) { request.setType(InterruptibleEdgeRequest.SET_INTERRUPTIBLE_EDGE); } else { request.setType(InterruptibleEdgeRequest.UNSET_INTERRUPTIBLE_EDGE); } Command command = edgeEditPart.getCommand(request); if(command != null && command.canExecute()) { return new CommandProxy(command); } } } return null; } @Override protected ActivityEdge getElement(Notification notif) { Object element = notif.getNotifier(); if(element instanceof ActivityEdge) { return (ActivityEdge)element; } return null; } }