Let’s solve once for all the Eclipse GMF copy-paste problem and then forget about it

Hello,

This post is only meant for  people working on GMF-based projects (www.eclipse.org/gmf) and having problems with the copy-paste functionality.

In order to implement my copy-paste solution I found this document very useful http://www.moskitt.org/fileadmin/conselleria/documentacion/Presentaciones/MOSKitt-CopyPasteSupport.pdf

and also the code of the open-source project bonita soft

I needed to implement this solution for a project developed by employer enterprise ActiveEon, for the  WorkflowStudio, part of the open source project proactive  proactive

Here’s an screenshot of the WorkflowStudio

ProActive WorkflowStudio


First of all: the copy-paste functionality in the GMF generated code does not work: it is a copy-by-reference: the figures on your diagram are duplicated, but they are linked to the source emf objects. So this is why you have a strange behavior  when editting figures after paste or when pasting figures in another editor.

What do we need in order to make the copy-paste work?

We should first plug-in our own Copy and Paste Commands. Then, the copy command should remember the user selection and the paste command should re-create new objects equal to the ones selected. One thing we should do is to create connectors between the newly created objects and make sure we do not have any connectors from the newly created objects to the initial (selected) objects.  Once the new objects created we might want to position the new figures to a given location, the mouse cursor for instance, and also be sure we keep the same distances between the new figures as in the initial selection. The last thing we might want to do is to restore, from the initial gmf generated – copy-paste functionality a nice feature we have lost by implementing our own copy-paste: during the copy command, put into the clipboard an image representing the selected figures so that the user could directly paste it into a document or email.

Here’s how to do it, step-by-step

Step1.

First we need to plug-in our own implementations for Copy and Paste commands

Open  the plugin.xml file of your diagram plugin project.  Go to the extension point org.eclipse.gmf.runtime.common.ui.services.action.globalActionHandlerProviders and identify the GlobalActionHandlerProvider which provides the cut, copy and paste command.

Change the default class to a class of your own. Let’s call it MyEditorGlobalActionHandlerProvider

the code in this class remains pretty the same as the code in the initial class, we only need to specify our own Global Action Handler

The method getGlobalActionHandler() becomes:

 

public IGlobalActionHandler getGlobalActionHandler(

final IGlobalActionHandlerContext context) {

/* Create the handler */

if (!getHandlerList().containsKey(context.getActivePart())) {

getHandlerList().put(context.getActivePart(),

new MyEditorClipboardSupportGlobalActionHandler());


* Register as a part listener so that the cache can be cleared when
* the part is disposed
*/

context.getActivePart().getSite().getPage().addPartListener( new IPartListener() {
   private IWorkbenchPart localPart = context.getActivePart();
    public void partActivated(IWorkbenchPart part) { }

public void partBroughtToTop(IWorkbenchPart part) {// Do nothing}

public void partClosed(IWorkbenchPart part) {
/* Remove the cache associated with the part */
if (part != null && part == localPart
&& getHandlerList().containsKey(part)) {
getHandlerList().remove(part);
localPart.getSite().getPage().removePartListener(this);
localPart = null; }
}

public void partDeactivated(IWorkbenchPart part) {// Do nothing}

public void partOpened(IWorkbenchPart part) {// Do nothing}
});
}
return (MyEditorClipboardSupportGlobalActionHandler) getHandlerList().get( context.getActivePart());}

 

The rest of the class stays the same as the initial gmf class.
Now we need to create the class MyEditorClipboardSupportGlobalActionHandler. It extends DiagramGlobalActionHandler.
we need to provide the methods public ICommand getCommand(IGlobalActionContext cntxt), getCopyCommand and getPasteCommand.
Here they are:
 

public ICommand getCommand(IGlobalActionContext cntxt) {
IWorkbenchPart part = cntxt.getActivePart();
if (!(part instanceof IDiagramWorkbenchPart)) {
return null; }
IDiagramWorkbenchPart diagramPart = (IDiagramWorkbenchPart) part;
org.eclipse.gmf.runtime.common.core.command.ICommand command = null;
String actionId = cntxt.getActionId();
if (actionId.equals(GlobalActionId.DELETE)) {
super.getCommand(cntxt);
} else if (actionId.equals(GlobalActionId.COPY)) {
command = getCopyCommand(cntxt, diagramPart, false);
} else if (actionId.equals(GlobalActionId.CUT)) {
command = getCutCommand(cntxt, diagramPart);
} else if (actionId.equals(GlobalActionId.OPEN)) {
super.getCommand(cntxt);
} else if (actionId.equals(GlobalActionId.PASTE)) {
command = getPasteCommand(cntxt, diagramPart);
} else if (actionId.equals(GlobalActionId.SAVE)) {
super.getCommand(cntxt);
} else if (actionId.equals(GlobalActionId.PROPERTIES)) {
super.getCommand(cntxt);
}
return command;
}

protected ICommand getCopyCommand(IGlobalActionContext cntxt,
IDiagramWorkbenchPart diagramPart, final boolean isUndoable) {
List toCopyElements = this.getSelectedElements(cntxt.getSelection());
List toCopyEditParts = this.getSelectedEditParts(cntxt.getSelection());
MyEditorCopyCommand copyCmd =  new MyEditorCopyCommand("Copy",toCopyElements, toCopyEditParts);
return copyCmd;
}

private ICommand getPasteCommand(IGlobalActionContext cntxt,
IDiagramWorkbenchPart diagramPart) {
return new MyEditorPasteCommand("Paste",(IGraphicalEditPart) ((StructuredSelection)cntxt.getSelection()).getFirstElement());
}

// These are 2 utilitary methods:
protected List getSelectedElements(ISelection selection){
List results = new LinkedList();
if (selection==null || selection.isEmpty())
return results;
Iterator iterator = ((IStructuredSelection) selection).iterator();
while (iterator.hasNext())
{
Object selectedElement = iterator.next();
EObject element =  (EObject) ( (EditPart) selectedElement).getAdapter(EObject.class) ;
results.add(element);
}
return results;
}

private List getSelectedEditParts(ISelection selection)

{
List results = new LinkedList();
Iterator iterator = ((IStructuredSelection) selection).iterator();
while (iterator.hasNext())
{
Object selectedElement = iterator.next();
results.add((EditPart)selectedElement);
}
return results;
}

Step2. Implement the class for the copy command

You need to implement a class MyEditorCopyCommand which extends org.eclipse.gmf.runtime.common.core.command.AbstractCommand
In the constructor you receive the EObjects to be copied and their edit parts. store these in some local variables.
then implement the method


protected CommandResult doExecuteWithResult( IProgressMonitor progressMonitor, IAdaptable info)

I will not provide you the code for this method as this is dependent on your own application.
You should keep a static variable (accessible via a static getter) containing the list of the object you will need to paste.
Basically, what you need to do in this method is:
Copy the list of EObject you received in the constructor into a new list of objects.
Use something like :

List myObjectsToCopy = new LinkedList (EcoreUtil.copyAll(mySelectedEObjectsList));

Then loop over your new list of objects and:
– check if it is consistent: are these object supposed to be copied together ? (are they on the same level, are they the children of the same type of object, etc … )
– remove dependencies to objects outside the selection. For instance, if one object a in the selection has an attribute b which point to an object B which was not selected, I should do something like a.setB(null);
– you might need to keep track of the containment feature of the selected objects .
Imagine i have an object of Type “Classroom” which contains 2 child elements of type “human”. Like classroom.teacher and classroom.student. if I selected the teacherr and the student, and not the classroom object, once I copied the list of the selected objects, I will have 2 objects of type “human” and do not know that one of them had the role of teacher and the other one the role of student. So you might want to keep track of this in some static variable of type Map.
– if you want to remember, during the paste command, some graphical attributes of the objects selected during the copy command, we can find that information in the list of edit parts you received in the constructor.

Step3. Implement the class for the paste command

We’re almost done. We only need to implement the MyEditorPasteCommand class (extends AbstractCommand) for the paste command and create the new objects equals to the ones stored in the static field of the MyEditorCopyCommand class.
The constructor of my class MyEditorPasteCommand looks like:


public WorkflowStudioPasteCommand(String label, IGraphicalEditPart target) {
		super(label);
		this.targetEditPart = target;
		this.targetElement = target.resolveSemanticElement();
	}

Then I need to implement the doExecuteWithResult method.
I have all I need:
– the selected targetEditPart – which needs to receive the new objects
– the objects copied, in the MyEditorCopyCommand’s static field.

First of all, I make another copy of the objects (If I modify them, I would’n like to modify the ones in the MyEditorCopyCommand):
List newObjects = new LinkedList( EcoreUtil.copyAll(MyEditorCopyCommand.copiedElements));

What I will do now, is to create and execute a gmf command which updates my data structure by adding to it the new objects.
I will write a class calleed PasteTransactionalCommand which will add, to my target edit part, the objects in my list.
We’ll see this class in a few moments.
In the WorkflowStudioPasteCommand class, I keep a private field


private PasteTransactionalCommand pasteCmd;

then, in the method doExecuteWithResult I do something like:

 TransactionalEditingDomain domain = (TransactionalEditingDomain) AdapterFactoryEditingDomain.getEditingDomainFor(targetElement);
 pasteCmd = new PasteTransactionalCommand(domain, newObjects, targetElement, this);
 pasteCmd.execute(progressMonitor, info);

Once the pasteCmd command executed, the edit parts and figures have already been created.
I might want to change some graphical properties and position of the newly created objects.
I can loop over the list newObjects
To find the edit part of an EObject “eo” in my list I write:

EditPart eoEditPart = targetEditPart.findEditPart(targetEditPart,eObject);

then I can change graphical properties using the eoEditPart reference.

One thing I would like to do is a refresh in order to show the newly created connectors:

 EditPart mainEditPart = (EditPart)(targetEditPart.getRoot().getChildren().get(0));
 CanonicalEditPolicy cep = (CanonicalEditPolicy)mainEditPart.getEditPolicy(EditPolicyRoles.CANONICAL_ROLE);
 if (cep!=null)
	 cep.refresh();

The last thing we need is the class PasteTransactionalCommand. It extends org.eclipse.gmf.runtime.emf.commands.core.command.AbstractTransactionalCommand

Here’s my constructor:

public PasteTransactionalCommand(TransactionalEditingDomain domain,
			List elemToPaste, EObject targetElement,
			WorkflowStudioPasteCommand pasteCmd) {

		super(domain, PasteTransactionalCommand.class.getName(),
		getWorkspaceFiles(elemToPaste));
		this.targetElement = targetElement;
		this.elementsToPaste = elemToPaste;
		this.targetElement = targetElement;
		this.enclosingPasteCmd = pasteCmd;
		this.elementsCreated = new LinkedList();
	}

Then the doExecuteWithResult method is up to you to write, it is completly dependent on your application.
You have a “targetElement” and a list “elementsToPaste”. It is up to you to call the right settters on the targetElement, like:
targetElement.setMyPropertyA(elemToPaste.get(0));
You might also need to change some properties of the objects in the elementsToPaste list, like the object name for instance in order not to have duplicates.

This is it.

Here’s a tip. it took me 2 hours to find this. add this method in the PasteTransactionalCommand class:

public void addContext(IUndoContext context) {
		super.addContext(context);
		this.enclosingPasteCmd.addContext(context);
	}

.. and all your paste operations will be well integrated in the undo/redo stack.

Second tip (this was straight forward). If you want to keep, for windows, the possibility to paste your diagram picture within a document or in an email:
In the MyEditorClipboardSupportGlobalActionHandler#getCopyCommand() you can return, instead of your custom copy command, a compond command containing your custom copy command and a command of type org.eclipse.gmf.runtime.diagram.ui.render.internal.commands.CopyImageCommand (see the initial gmf code).

Well this is it.
If this post helped you implement your copy-paste functionality, do not hesitate to leave a cooment and sayy “hi”.
If you have a stack like “Could not perform operation outside a transaction”, or anny other java stack trace, do not ask me to debug you, just concentrate a litle bit on what you are doing 🙂

cheers,
emil

Advertisements
This entry was posted in Eclipse RCP. Bookmark the permalink.

17 Responses to Let’s solve once for all the Eclipse GMF copy-paste problem and then forget about it

  1. Hi says:

    Hi, can i kiss you?
    After painstakingly debugging not working copy paste code in generated diagram code
    this implementation copied in an hour or two works like a dream.
    Thank you very much!

    Kind regards,

    Hans de Kluiver

  2. Jan says:

    Dear esalagea,

    Thanks for writing this up.
    My problem is that the paste command is never enabled. And during debugging I find that the copy command is hit every time a selection is made.
    Any advice?

    • esalagea says:

      Hi,
      You should run it in debug mode and watch who’s calling the doExecute() method of your copy command and see why canExecute() method of your Paste command returns false.

      • Jan says:

        Dear Emil,

        I found out that the canPaste always returned false. So I have overridden the function to look at the copy command static field that contains the items to copy. Then it still did not work, because when I select a node, copy it and try to paste it on the canvas, the contents of the static field had become the canvas itself! This was caused by the “cut” command’s canCut function. It fires after every selection change and compounds the copy command. I made getCommand return null for the getCutCommand() and then it started to work. Thanks again.

        In the PasteTransactionalCommand I “pasted” some code from the DuplicateAnythingCommand because the references were lost. The function now has this:

        @Override
        protected CommandResult doExecuteWithResult(IProgressMonitor monitor, IAdaptable info) throws ExecutionException {
        // Code taken from the duplicate command

        // Remove elements whose container is getting copied.
        ClipboardSupportUtil.getCopyElements(BuilderEditorCopyCommand.mySelectedEObjectsList);

        // Perform the copy and update the references.
        EcoreUtil.Copier copier = new EcoreUtil.Copier();
        copier.copyAll(BuilderEditorCopyCommand.mySelectedEObjectsList);
        copier.copyReferences();

        // Add the duplicates to the original's container.
        for (Iterator i = BuilderEditorCopyCommand.mySelectedEObjectsList.iterator(); i.hasNext();) {
        EObject original = (EObject) i.next();
        EObject duplicate = copier.get(original);

        EReference reference = original.eContainmentFeature();
        if (reference != null && FeatureMapUtil.isMany(this.targetElement, reference)
        && ClipboardSupportUtil.isOkToAppendEObjectAt(this.targetElement, reference, duplicate)) {

        ClipboardSupportUtil.appendEObjectAt(this.targetElement, reference, duplicate);
        }
        }
        return CommandResult.newOKCommandResult();
        }

  3. Lu says:

    Thank you very much Emil, a lot of us will be grateful from now on for the time you saved us with this post !! 😉

    I am currently trying to implement your approach, however I am still new with EMF/GMF and I miss some basic knowledge. For example, at the current point, I can create the new objects to be pasted with the same properties as the ones copied, but I don’t know how to attach their graphical representations to the canvas. Would it be possible for you to publish a short part of your MyEditorCopyCommand and PasteTransactionalCommand? I would be sincerely grateful!

    Best, Lu

  4. Lu says:

    Excuse me, I meant some code only from the PasteTransactionalCommand class, not MyEditorCopyCommand… Thanks!

  5. Lu says:

    Emil, problem has been solved! Please delete my previous 2 comments! Thank you once more for your time to write such a helpful post!

  6. Fatla says:

    Hi,
    Thanks it was extremely useful.
    I was wondering if you can help me if you know how to paste the element with the same size!
    I was able to adjust copied element location using the following:

    protected CommandResult doExecuteWithResult(IProgressMonitor progressMonitor, IAdaptable info) throws ExecutionException {
    Point originalLocation = ((ShapeEditPart) originalEditPart).getLocation();
    Point copyTargetLocation = originalLocation.getTranslated(50, 50);
    ShapeEditPart duplicateEditPart = (ShapeEditPart) targetEditPart.findEditPart(targetEditPart, entry.getValue());
    createdEditParts.add(duplicateEditPart);
    Point copyCurrentLocation = duplicateEditPart.getLocation();
    ChangeBoundsRequest request = new ChangeBoundsRequest(RequestConstants.REQ_MOVE);
    request.setEditParts(duplicateEditPart);
    Dimension d = copyTargetLocation.getDifference(copyCurrentLocation);
    request.setMoveDelta(new Point(d.width, d.height));

    Command cmd = duplicateEditPart.getCommand(request);
    if (cmd != null && cmd.canExecute()) {
    cmd.execute();
    }
    }

    • esalagea says:

      Sorry I didn’t see your comment. It’s been a long time since I wrote this post, I’m working on totally different things now, I don’t think I can help. You are surely better than me in GMF now 🙂

  7. mohammed ahmed says:

    i am looking for implementation of undo-redo the copy-paste command …. can you help

  8. Paul Brown says:

    Thank you very much for your posting – you have saved me days of work. I have a question about the copy command: why do you copy the objects at all in the copy command? You make another copy in the paste command anyway. Why doesn’t the copy command just save the lists of elements and edit parts in a static variable and the paste command simply use those lists? Am I missing something?

  9. Mohamed Lemine says:

    Hi,
    I want to make the copy/paste from my project explorer and save the changes into my domain model, do you have an idea how to use the DragDropPolicy to allow saving changes in domain model and not only int the diagram model ?

  10. Hi, I was wondering if I can reuse the first image from your post (adding a link to this article, of course). I am writing a guide to DSLs and I would like to include in the list of tools also GMF, so I was looking for a screenshot. The article would appear on my personal blog (https://tomassetti.me)

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s