Bug 507203 - Offer commands which allow progress reporting
* Paste commands which uses progress compound commands to pass a
progress monitor.
* commands which report progress and read from the event queue
* ReportSetHelpers allows to pass in an AdapterFactoryEditingDomain, so
that user may override createCommand and still use the current methods
in a convinient way
Change-Id: I2a38a18dbb5c9929a036d0bc24abbc3acb6f41e0
Signed-off-by: Johannes Faltermeier <jfaltermeier@eclipsesource.com>
diff --git a/bundles/org.eclipse.emf.ecp.edit.swt/META-INF/MANIFEST.MF b/bundles/org.eclipse.emf.ecp.edit.swt/META-INF/MANIFEST.MF
index 1f13dc3..1499ee9 100644
--- a/bundles/org.eclipse.emf.ecp.edit.swt/META-INF/MANIFEST.MF
+++ b/bundles/org.eclipse.emf.ecp.edit.swt/META-INF/MANIFEST.MF
@@ -26,6 +26,7 @@
org.eclipse.emf.ecp.view.treemasterdetail.ui.swt,
org.eclipse.emf.ecp.view.core.swt",
org.eclipse.emf.ecp.edit.spi.swt.actions;version="1.11.0",
+ org.eclipse.emf.ecp.edit.spi.swt.commands;version="1.11.0",
org.eclipse.emf.ecp.edit.spi.swt.reference;version="1.11.0",
org.eclipse.emf.ecp.edit.spi.swt.table;version="1.11.0",
org.eclipse.emf.ecp.edit.spi.swt.util;version="1.11.0"
@@ -50,6 +51,7 @@
org.eclipse.jface.dialogs;version="0.0.0",
org.eclipse.jface.fieldassist;version="0.0.0",
org.eclipse.jface.layout;version="0.0.0",
+ org.eclipse.jface.operation;version="0.0.0",
org.eclipse.jface.resource;version="0.0.0",
org.eclipse.jface.viewers;version="0.0.0",
org.eclipse.jface.window;version="0.0.0",
diff --git a/bundles/org.eclipse.emf.ecp.edit.swt/src/org/eclipse/emf/ecp/edit/spi/swt/commands/IProgressMonitorConsumer.java b/bundles/org.eclipse.emf.ecp.edit.swt/src/org/eclipse/emf/ecp/edit/spi/swt/commands/IProgressMonitorConsumer.java
new file mode 100644
index 0000000..8b64ce5
--- /dev/null
+++ b/bundles/org.eclipse.emf.ecp.edit.swt/src/org/eclipse/emf/ecp/edit/spi/swt/commands/IProgressMonitorConsumer.java
@@ -0,0 +1,28 @@
+/*******************************************************************************
+ * Copyright (c) 2011-2016 EclipseSource Muenchen GmbH 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:
+ * Johannes Faltermeier - initial API and implementation
+ ******************************************************************************/
+package org.eclipse.emf.ecp.edit.spi.swt.commands;
+
+/**
+ * A Marker interface for classes which may consume an {@link IProgressMonitorProvider}.
+ *
+ * @author Johannes Faltermeier
+ * @since 1.11
+ *
+ */
+public interface IProgressMonitorConsumer {
+
+ /**
+ * @param monitor the {@link IProgressMonitorProvider}
+ */
+ void setIProgressMonitorAccessor(IProgressMonitorProvider monitor);
+
+}
diff --git a/bundles/org.eclipse.emf.ecp.edit.swt/src/org/eclipse/emf/ecp/edit/spi/swt/commands/IProgressMonitorProvider.java b/bundles/org.eclipse.emf.ecp.edit.swt/src/org/eclipse/emf/ecp/edit/spi/swt/commands/IProgressMonitorProvider.java
new file mode 100644
index 0000000..cdee0c6
--- /dev/null
+++ b/bundles/org.eclipse.emf.ecp.edit.swt/src/org/eclipse/emf/ecp/edit/spi/swt/commands/IProgressMonitorProvider.java
@@ -0,0 +1,29 @@
+/*******************************************************************************
+ * Copyright (c) 2011-2016 EclipseSource Muenchen GmbH 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:
+ * Johannes Faltermeier - initial API and implementation
+ ******************************************************************************/
+package org.eclipse.emf.ecp.edit.spi.swt.commands;
+
+import org.eclipse.core.runtime.IProgressMonitor;
+
+/**
+ * Interface which marks classes which provide an {@link IProgressMonitor}.
+ *
+ * @author Johannes Faltermeier
+ * @since 1.11
+ *
+ */
+public interface IProgressMonitorProvider {
+
+ /**
+ * @return the {@link IProgressMonitor}
+ */
+ IProgressMonitor getProgressMonitor();
+}
diff --git a/bundles/org.eclipse.emf.ecp.edit.swt/src/org/eclipse/emf/ecp/edit/spi/swt/commands/ProgressAddCommand.java b/bundles/org.eclipse.emf.ecp.edit.swt/src/org/eclipse/emf/ecp/edit/spi/swt/commands/ProgressAddCommand.java
new file mode 100644
index 0000000..b30f186
--- /dev/null
+++ b/bundles/org.eclipse.emf.ecp.edit.swt/src/org/eclipse/emf/ecp/edit/spi/swt/commands/ProgressAddCommand.java
@@ -0,0 +1,90 @@
+/*******************************************************************************
+ * Copyright (c) 2011-2016 EclipseSource Muenchen GmbH 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:
+ * Johannes Faltermeier - initial API and implementation
+ ******************************************************************************/
+package org.eclipse.emf.ecp.edit.spi.swt.commands;
+
+import java.util.Collection;
+
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.emf.ecore.EObject;
+import org.eclipse.emf.ecore.EStructuralFeature;
+import org.eclipse.emf.edit.command.AddCommand;
+import org.eclipse.emf.edit.command.CommandParameter;
+import org.eclipse.emf.edit.domain.EditingDomain;
+import org.eclipse.swt.widgets.Display;
+
+/**
+ * {@link AddCommand} which is able to report progress.
+ *
+ * @author Johannes Faltermeier
+ * @since 1.11
+ *
+ */
+public class ProgressAddCommand extends AddCommand implements IProgressMonitorConsumer {
+
+ private IProgressMonitorProvider monitor;
+
+ /**
+ * Constructs a {@link ProgressAddCommand}.
+ *
+ * @param domain the {@link EditingDomain}
+ * @param owner the parent
+ * @param feature the feature
+ * @param collection the children to add
+ * @param index the start index
+ */
+ public ProgressAddCommand(EditingDomain domain, EObject owner, EStructuralFeature feature, Collection<?> collection,
+ int index) {
+ super(domain, owner, feature, collection, index);
+ }
+
+ @Override
+ public void setIProgressMonitorAccessor(IProgressMonitorProvider monitor) {
+ this.monitor = monitor;
+ }
+
+ @Override
+ public void doExecute() {
+ // Simply add the collection to the list.
+ //
+ if (index == CommandParameter.NO_INDEX) {
+ for (final Object object : collection) {
+ ownerList.add(object);
+ worked();
+ }
+ } else {
+ int i = index;
+ for (final Object object : collection) {
+ ownerList.add(i++, object);
+ worked();
+ }
+ }
+
+ // Update the containing map, if necessary.
+ //
+ updateEMap(owner, feature);
+
+ // We'd like the collection of things added to be selected after this command completes.
+ //
+ affectedObjects = collection;
+ }
+
+ private void worked() {
+ final IProgressMonitor progressMonitor = monitor.getProgressMonitor();
+ if (progressMonitor == null) {
+ return;
+ }
+ progressMonitor.worked(1);
+ while (Display.getDefault().readAndDispatch()) {
+ }
+ }
+
+}
diff --git a/bundles/org.eclipse.emf.ecp.edit.swt/src/org/eclipse/emf/ecp/edit/spi/swt/commands/ProgressCompoundCommand.java b/bundles/org.eclipse.emf.ecp.edit.swt/src/org/eclipse/emf/ecp/edit/spi/swt/commands/ProgressCompoundCommand.java
new file mode 100644
index 0000000..3ad8c25
--- /dev/null
+++ b/bundles/org.eclipse.emf.ecp.edit.swt/src/org/eclipse/emf/ecp/edit/spi/swt/commands/ProgressCompoundCommand.java
@@ -0,0 +1,552 @@
+/*******************************************************************************
+ * Copyright (c) 2011-2016 EclipseSource Muenchen GmbH 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:
+ * Johannes Faltermeier - initial API and implementation
+ ******************************************************************************/
+package org.eclipse.emf.ecp.edit.spi.swt.commands;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.ListIterator;
+
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.emf.common.CommonPlugin;
+import org.eclipse.emf.common.command.Command;
+import org.eclipse.emf.common.command.CompoundCommand;
+import org.eclipse.emf.common.command.UnexecutableCommand;
+import org.eclipse.emf.common.util.WrappedException;
+import org.eclipse.swt.widgets.Display;
+
+/**
+ * Extension of the {@link CompoundCommand} which reports progress.
+ *
+ * @author Johannes Faltermeier
+ * @since 1.11
+ *
+ */
+public class ProgressCompoundCommand extends CompoundCommand {
+
+ private final IProgressMonitorProvider monitor;
+
+ /**
+ * Creates an empty instance with the given result index.
+ *
+ * @param resultIndex the {@link #resultIndex}.
+ * @param monitor the progress monitor
+ */
+ public ProgressCompoundCommand(int resultIndex, IProgressMonitorProvider monitor) {
+ super(resultIndex);
+ this.monitor = monitor;
+ }
+
+ // REUSED CLASS
+
+ /**
+ * Returns whether there are commands in the list.
+ *
+ * @return whether there are commands in the list.
+ */
+ @Override
+ public boolean isEmpty() {
+ return commandList.isEmpty();
+ }
+
+ /**
+ * Returns an unmodifiable view of the commands in the list.
+ *
+ * @return an unmodifiable view of the commands in the list.
+ */
+ @Override
+ public List<Command> getCommandList() {
+ return Collections.unmodifiableList(commandList);
+ }
+
+ /**
+ * Returns the index of the command whose result and affected objects are forwarded.
+ * Negative values have special meaning, as defined by the static constants.
+ *
+ * @return the index of the command whose result and affected objects are forwarded.
+ * @see #LAST_COMMAND_ALL
+ * @see #MERGE_COMMAND_ALL
+ */
+ @Override
+ public int getResultIndex() {
+ return resultIndex;
+ }
+
+ /**
+ * Returns whether all the commands can execute so that {@link #isExecutable} can be cached.
+ * An empty command list causes <code>false</code> to be returned.
+ *
+ * @return whether all the commands can execute.
+ */
+ @Override
+ protected boolean prepare() {
+ if (commandList.isEmpty()) {
+ return false;
+ }
+ for (final Command command : commandList) {
+ if (!command.canExecute()) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Calls {@link Command#execute} for each command in the list.
+ */
+ @Override
+ public void execute() {
+ for (final ListIterator<Command> commands = commandList.listIterator(); commands.hasNext();) {
+ try {
+ final Command command = commands.next();
+ command.execute();
+ worked();
+ } catch (final RuntimeException exception) {
+ // Skip over the command that threw the exception.
+ //
+ commands.previous();
+
+ try {
+ // Iterate back over the executed commands to undo them.
+ //
+ while (commands.hasPrevious()) {
+ final Command command = commands.previous();
+ if (command.canUndo()) {
+ command.undo();
+ worked();
+ } else {
+ break;
+ }
+ }
+ } catch (final RuntimeException nestedException) {
+ CommonPlugin.INSTANCE
+ .log(new WrappedException(CommonPlugin.INSTANCE.getString("_UI_IgnoreException_exception"), //$NON-NLS-1$
+ nestedException).fillInStackTrace());
+ }
+
+ throw exception;
+ }
+ }
+ }
+
+ /**
+ * Returns <code>false</code> if any of the commands return <code>false</code> for {@link Command#canUndo}.
+ *
+ * @return <code>false</code> if any of the commands return <code>false</code> for <code>canUndo</code>.
+ */
+ @Override
+ public boolean canUndo() {
+ for (final Command command : commandList) {
+ if (!command.canUndo()) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Calls {@link Command#undo} for each command in the list, in reverse order.
+ */
+ @Override
+ public void undo() {
+ for (final ListIterator<Command> commands = commandList.listIterator(commandList.size()); commands
+ .hasPrevious();) {
+ try {
+ final Command command = commands.previous();
+ command.undo();
+ worked();
+ } catch (final RuntimeException exception) {
+ // Skip over the command that threw the exception.
+ //
+ commands.next();
+
+ try {
+ // Iterate forward over the undone commands to redo them.
+ //
+ while (commands.hasNext()) {
+ final Command command = commands.next();
+ command.redo();
+ worked();
+ }
+ } catch (final RuntimeException nestedException) {
+ CommonPlugin.INSTANCE
+ .log(new WrappedException(CommonPlugin.INSTANCE.getString("_UI_IgnoreException_exception"), //$NON-NLS-1$
+ nestedException).fillInStackTrace());
+ }
+
+ throw exception;
+ }
+ }
+ }
+
+ /**
+ * Calls {@link Command#redo} for each command in the list.
+ */
+ @Override
+ public void redo() {
+ for (final ListIterator<Command> commands = commandList.listIterator(); commands.hasNext();) {
+ try {
+ final Command command = commands.next();
+ command.redo();
+ worked();
+ } catch (final RuntimeException exception) {
+ // Skip over the command that threw the exception.
+ //
+ commands.previous();
+
+ try {
+ // Iterate back over the executed commands to undo them.
+ //
+ while (commands.hasPrevious()) {
+ final Command command = commands.previous();
+ command.undo();
+ worked();
+ }
+ } catch (final RuntimeException nestedException) {
+ CommonPlugin.INSTANCE
+ .log(new WrappedException(CommonPlugin.INSTANCE.getString("_UI_IgnoreException_exception"), //$NON-NLS-1$
+ nestedException).fillInStackTrace());
+ }
+
+ throw exception;
+ }
+ }
+ }
+
+ /**
+ * Determines the result by composing the results of the commands in the list;
+ * this is affected by the setting of {@link #resultIndex}.
+ *
+ * @return the result.
+ */
+ @Override
+ public Collection<?> getResult() {
+ if (commandList.isEmpty()) {
+ return Collections.EMPTY_LIST;
+ } else if (resultIndex == LAST_COMMAND_ALL) {
+ return commandList.get(commandList.size() - 1).getResult();
+ } else if (resultIndex == MERGE_COMMAND_ALL) {
+ return getMergedResultCollection();
+ } else if (resultIndex < commandList.size()) {
+ return commandList.get(resultIndex).getResult();
+ } else {
+ return Collections.EMPTY_LIST;
+ }
+ }
+
+ /**
+ * Returns the merged collection of all command results.
+ *
+ * @return the merged collection of all command results.
+ */
+ @Override
+ protected Collection<?> getMergedResultCollection() {
+ final Collection<Object> result = new ArrayList<Object>();
+
+ for (final Command command : commandList) {
+ result.addAll(command.getResult());
+ }
+
+ return result;
+ }
+
+ /**
+ * Determines the affected objects by composing the affected objects of the commands in the list;
+ * this is affected by the setting of {@link #resultIndex}.
+ *
+ * @return the affected objects.
+ */
+ @Override
+ public Collection<?> getAffectedObjects() {
+ if (commandList.isEmpty()) {
+ return Collections.EMPTY_LIST;
+ } else if (resultIndex == LAST_COMMAND_ALL) {
+ return commandList.get(commandList.size() - 1).getAffectedObjects();
+ } else if (resultIndex == MERGE_COMMAND_ALL) {
+ return getMergedAffectedObjectsCollection();
+ } else if (resultIndex < commandList.size()) {
+ return commandList.get(resultIndex).getAffectedObjects();
+ } else {
+ return Collections.EMPTY_LIST;
+ }
+ }
+
+ /**
+ * Returns the merged collection of all command affected objects.
+ *
+ * @return the merged collection of all command affected objects.
+ */
+ @Override
+ protected Collection<?> getMergedAffectedObjectsCollection() {
+ final Collection<Object> result = new ArrayList<Object>();
+
+ for (final Command command : commandList) {
+ result.addAll(command.getAffectedObjects());
+ }
+
+ return result;
+ }
+
+ /**
+ * Determines the label by composing the labels of the commands in the list;
+ * this is affected by the setting of {@link #resultIndex}.
+ *
+ * @return the label.
+ */
+ @Override
+ public String getLabel() {
+ if (label != null) {
+ return label;
+ } else if (commandList.isEmpty()) {
+ return CommonPlugin.INSTANCE.getString("_UI_CompoundCommand_label"); //$NON-NLS-1$
+ } else if (resultIndex == LAST_COMMAND_ALL || resultIndex == MERGE_COMMAND_ALL) {
+ return commandList.get(commandList.size() - 1).getLabel();
+ } else if (resultIndex < commandList.size()) {
+ return commandList.get(resultIndex).getLabel();
+ } else {
+ return CommonPlugin.INSTANCE.getString("_UI_CompoundCommand_label"); //$NON-NLS-1$
+ }
+ }
+
+ /**
+ * Determines the description by composing the descriptions of the commands in the list;
+ * this is affected by the setting of {@link #resultIndex}.
+ *
+ * @return the description.
+ */
+ @Override
+ public String getDescription() {
+ if (description != null) {
+ return description;
+ } else if (commandList.isEmpty()) {
+ return CommonPlugin.INSTANCE.getString("_UI_CompoundCommand_description"); //$NON-NLS-1$
+ } else if (resultIndex == LAST_COMMAND_ALL || resultIndex == MERGE_COMMAND_ALL) {
+ return commandList.get(commandList.size() - 1).getDescription();
+ } else if (resultIndex < commandList.size()) {
+ return commandList.get(resultIndex).getDescription();
+ } else {
+ return CommonPlugin.INSTANCE.getString("_UI_CompoundCommand_description"); //$NON-NLS-1$
+ }
+ }
+
+ /**
+ * Adds a command to this compound command's list of commands.
+ *
+ * @param command the command to append.
+ */
+ @Override
+ public void append(Command command) {
+ if (isPrepared) {
+ throw new IllegalStateException("The command is already prepared"); //$NON-NLS-1$
+ }
+
+ if (command != null) {
+ commandList.add(command);
+ }
+ }
+
+ /**
+ * Checks if the command can execute;
+ * if so, it is executed, appended to the list, and true is returned,
+ * if not, it is just disposed and false is returned.
+ * A typical use for this is to execute commands created during the execution of another command, e.g.,
+ *
+ * <pre>
+ * class MyCommand extends CommandBase
+ * {
+ * protected Command subcommand;
+ *
+ * //...
+ *
+ * public void execute()
+ * {
+ * // ...
+ * Compound subcommands = new CompoundCommand();
+ * subcommands.appendAndExecute(new AddCommand(...));
+ * if (condition) subcommands.appendAndExecute(new AddCommand(...));
+ * subcommand = subcommands.unwrap();
+ * }
+ *
+ * public void undo()
+ * {
+ * // ...
+ * subcommand.undo();
+ * }
+ *
+ * public void redo()
+ * {
+ * // ...
+ * subcommand.redo();
+ * }
+ *
+ * public void dispose()
+ * {
+ * // ...
+ * if (subcommand != null)
+ * {
+ * subcommand.dispose();
+ * }
+ * }
+ * }
+ * </pre>
+ *
+ * Another use is in an execute override of compound command itself:
+ *
+ * <pre>
+ * class MyCommand extends CompoundCommand
+ * {
+ * public void execute()
+ * {
+ * // ...
+ * appendAndExecute(new AddCommand(...));
+ * if (condition) appendAndExecute(new AddCommand(...));
+ * }
+ * }
+ * </pre>
+ *
+ * Note that appending commands will modify what getResult and getAffectedObjects return,
+ * so you may want to set the resultIndex flag.
+ *
+ * @param command the command.
+ * @return whether the command was successfully executed and appended.
+ */
+ @Override
+ public boolean appendAndExecute(Command command) {
+ if (command != null) {
+ if (!isPrepared) {
+ if (commandList.isEmpty()) {
+ isPrepared = true;
+ isExecutable = true;
+ } else {
+ isExecutable = prepare();
+ isPrepared = true;
+ if (isExecutable) {
+ execute();
+ }
+ }
+ }
+
+ if (command.canExecute()) {
+ try {
+ command.execute();
+ worked();
+ commandList.add(command);
+ return true;
+ } catch (final RuntimeException exception) {
+ CommonPlugin.INSTANCE
+ .log(new WrappedException(CommonPlugin.INSTANCE.getString("_UI_IgnoreException_exception"), //$NON-NLS-1$
+ exception).fillInStackTrace());
+ }
+ }
+
+ command.dispose();
+ }
+
+ return false;
+ }
+
+ /**
+ * Adds a command to this compound command's the list of commands and returns <code>true</code>,
+ * if <code>command.{@link org.eclipse.emf.common.command.Command#canExecute() canExecute()}</code> returns true;
+ * otherwise, it simply calls
+ * <code>command.{@link org.eclipse.emf.common.command.Command#dispose() dispose()}</code>
+ * and returns <code>false</code>.
+ *
+ * @param command the command.
+ * @return whether the command was executed and appended.
+ */
+ @Override
+ public boolean appendIfCanExecute(Command command) {
+ if (command == null) {
+ return false;
+ } else if (command.canExecute()) {
+ commandList.add(command);
+ return true;
+ } else {
+ command.dispose();
+ return false;
+ }
+ }
+
+ /**
+ * Calls {@link Command#dispose} for each command in the list.
+ */
+ @Override
+ public void dispose() {
+ for (final Command command : commandList) {
+ command.dispose();
+ }
+ }
+
+ /**
+ * Returns one of three things:
+ * {@link org.eclipse.emf.common.command.UnexecutableCommand#INSTANCE}, if there are no commands,
+ * the one command, if there is exactly one command,
+ * or <code>this</code>, if there are multiple commands;
+ * this command is {@link #dispose}d in the first two cases.
+ * You should only unwrap a compound command if you created it for that purpose, e.g.,
+ *
+ * <pre>
+ * CompoundCommand subcommands = new CompoundCommand();
+ * subcommands.append(x);
+ * if (condition)
+ * subcommands.append(y);
+ * Command result = subcommands.unwrap();
+ * </pre>
+ *
+ * is a good way to create an efficient accumulated result.
+ *
+ * @return the unwrapped command.
+ */
+ @Override
+ public Command unwrap() {
+ switch (commandList.size()) {
+ case 0: {
+ dispose();
+ return UnexecutableCommand.INSTANCE;
+ }
+ case 1: {
+ final Command result = commandList.remove(0);
+ dispose();
+ return result;
+ }
+ default: {
+ return this;
+ }
+ }
+ }
+
+ @Override
+ public String toString() {
+ final StringBuffer result = new StringBuffer(super.toString());
+ result.append(" (commandList: #" + commandList.size() + ")"); //$NON-NLS-1$ //$NON-NLS-2$
+ result.append(" (resultIndex: " + resultIndex + ")"); //$NON-NLS-1$ //$NON-NLS-2$
+
+ return result.toString();
+ }
+
+ // END REUSED CLASS
+
+ private void worked() {
+ final IProgressMonitor progressMonitor = monitor.getProgressMonitor();
+ if (progressMonitor == null) {
+ return;
+ }
+ progressMonitor.worked(1);
+ while (Display.getDefault().readAndDispatch()) {
+ }
+ }
+
+}
diff --git a/bundles/org.eclipse.emf.ecp.edit.swt/src/org/eclipse/emf/ecp/edit/spi/swt/commands/ProgressCopyCommandFactory.java b/bundles/org.eclipse.emf.ecp.edit.swt/src/org/eclipse/emf/ecp/edit/spi/swt/commands/ProgressCopyCommandFactory.java
new file mode 100644
index 0000000..b689490
--- /dev/null
+++ b/bundles/org.eclipse.emf.ecp.edit.swt/src/org/eclipse/emf/ecp/edit/spi/swt/commands/ProgressCopyCommandFactory.java
@@ -0,0 +1,58 @@
+/*******************************************************************************
+ * Copyright (c) 2011-2016 EclipseSource Muenchen GmbH 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:
+ * Johannes Faltermeier - initial API and implementation
+ ******************************************************************************/
+package org.eclipse.emf.ecp.edit.spi.swt.commands;
+
+import java.util.Collection;
+
+import org.eclipse.emf.common.command.Command;
+import org.eclipse.emf.common.command.CompoundCommand;
+import org.eclipse.emf.common.command.UnexecutableCommand;
+import org.eclipse.emf.edit.command.CommandParameter;
+import org.eclipse.emf.edit.command.CopyCommand;
+import org.eclipse.emf.edit.command.CopyCommand.Helper;
+import org.eclipse.emf.edit.domain.EditingDomain;
+
+/**
+ * Factory to create copy commands enabling progress reposrting.
+ *
+ * @since 1.11
+ */
+public final class ProgressCopyCommandFactory {
+
+ private ProgressCopyCommandFactory() {
+ }
+
+ /**
+ * This creates a command that copies the given collection of objects. If the collection contains more than one
+ * object,
+ * then a compound command will be created containing individual copy commands for each object.
+ *
+ * @param domain {@link EditingDomain}
+ * @param collection objects to copy
+ * @param monitor the monitor.
+ * @return the copy command.
+ */
+ public static Command create(final EditingDomain domain, final Collection<?> collection,
+ IProgressMonitorProvider monitor) {
+ if (collection == null || collection.isEmpty()) {
+ return UnexecutableCommand.INSTANCE;
+ }
+
+ final Helper copyHelper = new Helper();
+ final CompoundCommand copyCommand = new ProgressCompoundCommand(CompoundCommand.MERGE_COMMAND_ALL, monitor);
+ for (final Object object : collection) {
+ copyCommand.append(domain.createCommand(CopyCommand.class, new CommandParameter(object, null, copyHelper)));
+ }
+
+ return copyCommand.unwrap();
+ }
+}
diff --git a/bundles/org.eclipse.emf.ecp.edit.swt/src/org/eclipse/emf/ecp/edit/spi/swt/commands/ProgressPasteFromClipboardCommand.java b/bundles/org.eclipse.emf.ecp.edit.swt/src/org/eclipse/emf/ecp/edit/spi/swt/commands/ProgressPasteFromClipboardCommand.java
new file mode 100644
index 0000000..ed9d25c
--- /dev/null
+++ b/bundles/org.eclipse.emf.ecp.edit.swt/src/org/eclipse/emf/ecp/edit/spi/swt/commands/ProgressPasteFromClipboardCommand.java
@@ -0,0 +1,272 @@
+/*******************************************************************************
+ * Copyright (c) 2011-2016 EclipseSource Muenchen GmbH 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:
+ * Johannes Faltermeier - initial API and implementation
+ ******************************************************************************/
+package org.eclipse.emf.ecp.edit.spi.swt.commands;
+
+import java.lang.reflect.InvocationTargetException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.emf.common.command.Command;
+import org.eclipse.emf.common.command.CommandWrapper;
+import org.eclipse.emf.edit.command.AddCommand;
+import org.eclipse.emf.edit.command.PasteFromClipboardCommand;
+import org.eclipse.emf.edit.domain.EditingDomain;
+import org.eclipse.jface.dialogs.ProgressMonitorDialog;
+import org.eclipse.jface.operation.IRunnableWithProgress;
+import org.eclipse.swt.widgets.Display;
+
+/**
+ * Extension of the {@link PasteFromClipboardCommand} which enabled progress reporting.
+ *
+ * @author Johannes Faltermeier
+ * @since 1.11
+ *
+ */
+public class ProgressPasteFromClipboardCommand extends PasteFromClipboardCommand implements IProgressMonitorProvider {
+
+ private IProgressMonitor monitor;
+
+ /**
+ * Constructs a new {@link ProgressPasteFromClipboardCommand}.
+ *
+ * @param domain the {@link EditingDomain}
+ * @param owner the owner object
+ * @param feature the feature
+ * @param index the index
+ */
+ public ProgressPasteFromClipboardCommand(EditingDomain domain, Object owner, Object feature, int index) {
+ super(domain, owner, feature, index);
+ }
+
+ /**
+ * Constructs a new {@link ProgressPasteFromClipboardCommand}.
+ *
+ * @param domain the {@link EditingDomain}
+ * @param owner the owner object
+ * @param feature the feature
+ * @param index the index
+ * @param optimize whether to call the optimized can execute method
+ */
+ public ProgressPasteFromClipboardCommand(EditingDomain domain, Object owner, Object feature, int index,
+ boolean optimize) {
+ super(domain, owner, feature, index, optimize);
+ }
+
+ @Override
+ protected boolean prepare() {
+ {
+ // Create a strict compound command to do a copy and then add the result
+ //
+ command = new ProgressStrictCompoundCommand(this);
+
+ // Create a command to copy the clipboard.
+ //
+ final Command copyCommand = ProgressCopyCommandFactory.create(domain, domain.getClipboard(), this);
+
+ // REUSED CLASS
+ command.append(copyCommand);
+
+ // Create a proxy that will create an add command.
+ //
+ command.append(new CommandWrapper() {
+ protected Collection<Object> original;
+ protected Collection<Object> copy;
+
+ @Override
+ protected Command createCommand() {
+ original = domain.getClipboard();
+ copy = new ArrayList<Object>(copyCommand.getResult());
+
+ // Use the original to do the add, but only if it's of the same type as the copy.
+ // This ensures that if there is conversion being done as part of the copy,
+ // as would be the case for a cross domain copy in the mapping framework,
+ // that we do actually use the converted instance.
+ //
+ if (original.size() == copy.size()) {
+ for (Iterator<Object> i = original.iterator(), j = copy.iterator(); i.hasNext();) {
+ final Object originalObject = i.next();
+ final Object copyObject = j.next();
+ if (originalObject.getClass() != copyObject.getClass()) {
+ original = null;
+ break;
+ }
+ }
+ }
+
+ final Command addCommand = AddCommand.create(domain, owner, feature,
+ original == null ? copy : original, index);
+ if (IProgressMonitorConsumer.class.isInstance(addCommand)) {
+ IProgressMonitorConsumer.class.cast(addCommand)
+ .setIProgressMonitorAccessor(ProgressPasteFromClipboardCommand.this);
+ }
+ return addCommand;
+ }
+
+ @Override
+ public void execute() {
+ if (original != null) {
+ domain.setClipboard(copy);
+ }
+ super.execute();
+ }
+
+ @Override
+ public void undo() {
+ super.undo();
+ if (original != null) {
+ domain.setClipboard(original);
+ }
+ }
+
+ @Override
+ public void redo() {
+ if (original != null) {
+ domain.setClipboard(copy);
+ }
+ super.redo();
+ }
+ });
+
+ boolean result;
+ if (optimize) {
+ // This will determine canExecute as efficiently as possible.
+ //
+ result = optimizedCanExecute();
+ } else {
+ // This will actually execute the copy command in order to check if the add can execute.
+ //
+ result = command.canExecute();
+ }
+
+ return result;
+ }
+ }
+ // END REUSED CLASS
+
+ @Override
+ public void doExecute() {
+ doExecuteWithProgress("Pasting elements..."); //$NON-NLS-1$
+ }
+
+ /**
+ * Does the same as {@link #doExecute()} but also displays a progress dialog.
+ *
+ * @param task the name of the task
+ */
+ protected void doExecuteWithProgress(final String task) {
+ try {
+ final ProgressMonitorDialog progressMonitorDialog = new ProgressMonitorDialog(
+ Display.getDefault().getActiveShell());
+ progressMonitorDialog.run(
+ false, /* if we fork auto UI updates of EMF.edit.ui do not work, so stay on UI thread */
+ false,
+ new IRunnableWithProgress() {
+ @Override
+ public void run(IProgressMonitor monitor) throws InvocationTargetException, InterruptedException {
+ ProgressPasteFromClipboardCommand.this.monitor = monitor;
+ monitor.beginTask(task, getTotalWorkExecute());
+ ProgressPasteFromClipboardCommand.super.doExecute();
+ ProgressPasteFromClipboardCommand.this.monitor = null;
+ }
+ });
+ } catch (final InvocationTargetException ex) {
+ } catch (final InterruptedException ex) {
+ }
+ }
+
+ /**
+ * @return the total work required to execute the paste operation
+ */
+ protected int getTotalWorkExecute() {
+ int work = domain.getClipboard().size();
+ final List<Command> commandList = command.getCommandList();
+ work += commandList.size();
+ for (final Command subCommand : commandList) {
+ if (!ProgressCompoundCommand.class.isInstance(subCommand)) {
+ continue;
+ }
+ work += ProgressCompoundCommand.class.cast(subCommand).getCommandList().size();
+ }
+ return work;
+ }
+
+ @Override
+ public void doUndo() {
+ doUndoWithProgress("Undoing paste operation"); //$NON-NLS-1$
+ }
+
+ /**
+ * Does the same as {@link #doUndo()} but also displays a progress dialog.
+ *
+ * @param task the name of the task
+ */
+ protected void doUndoWithProgress(final String task) {
+ try {
+ final ProgressMonitorDialog progressMonitorDialog = new ProgressMonitorDialog(
+ Display.getDefault().getActiveShell());
+ progressMonitorDialog.run(
+ false, /* if we fork auto UI updates of EMF.edit.ui do not work, so stay on UI thread */
+ false,
+ new IRunnableWithProgress() {
+ @Override
+ public void run(IProgressMonitor monitor) throws InvocationTargetException, InterruptedException {
+ ProgressPasteFromClipboardCommand.this.monitor = monitor;
+ monitor.beginTask(task, 1);
+ ProgressPasteFromClipboardCommand.super.doUndo();
+ ProgressPasteFromClipboardCommand.this.monitor = null;
+ }
+ });
+ } catch (final InvocationTargetException ex) {
+ } catch (final InterruptedException ex) {
+ }
+ }
+
+ @Override
+ public void doRedo() {
+ doRedoWithProgress("Redoing paste operation."); //$NON-NLS-1$
+ }
+
+ /**
+ * Does the same as {@link #doRedo()} but also displays a progress dialog.
+ *
+ * @param task the name of the task
+ */
+ protected void doRedoWithProgress(final String task) {
+ try {
+ final ProgressMonitorDialog progressMonitorDialog = new ProgressMonitorDialog(
+ Display.getDefault().getActiveShell());
+ progressMonitorDialog.run(
+ false, /* if we fork auto UI updates of EMF.edit.ui do not work, so stay on UI thread */
+ false,
+ new IRunnableWithProgress() {
+ @Override
+ public void run(IProgressMonitor monitor) throws InvocationTargetException, InterruptedException {
+ ProgressPasteFromClipboardCommand.this.monitor = monitor;
+ monitor.beginTask(task, 1);
+ ProgressPasteFromClipboardCommand.super.doRedo();
+ ProgressPasteFromClipboardCommand.this.monitor = null;
+ }
+ });
+ } catch (final InvocationTargetException ex) {
+ } catch (final InterruptedException ex) {
+ }
+ }
+
+ @Override
+ public IProgressMonitor getProgressMonitor() {
+ return monitor;
+ }
+
+}
diff --git a/bundles/org.eclipse.emf.ecp.edit.swt/src/org/eclipse/emf/ecp/edit/spi/swt/commands/ProgressStrictCompoundCommand.java b/bundles/org.eclipse.emf.ecp.edit.swt/src/org/eclipse/emf/ecp/edit/spi/swt/commands/ProgressStrictCompoundCommand.java
new file mode 100644
index 0000000..3fe6cde
--- /dev/null
+++ b/bundles/org.eclipse.emf.ecp.edit.swt/src/org/eclipse/emf/ecp/edit/spi/swt/commands/ProgressStrictCompoundCommand.java
@@ -0,0 +1,249 @@
+/*******************************************************************************
+ * Copyright (c) 2011-2016 EclipseSource Muenchen GmbH 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:
+ * Johannes Faltermeier - initial API and implementation
+ ******************************************************************************/
+package org.eclipse.emf.ecp.edit.spi.swt.commands;
+
+import java.util.ListIterator;
+
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.emf.common.CommonPlugin;
+import org.eclipse.emf.common.command.Command;
+import org.eclipse.emf.common.command.StrictCompoundCommand;
+import org.eclipse.emf.common.util.WrappedException;
+import org.eclipse.swt.widgets.Display;
+
+/**
+ * Extension of the {@link StrictCompoundCommand} which reports the progress.
+ *
+ * @author Johannes Faltermeier
+ * @since 1.11
+ *
+ */
+public class ProgressStrictCompoundCommand extends StrictCompoundCommand {
+
+ private final IProgressMonitorProvider monitor;
+
+ /**
+ * Creates an empty instance.
+ *
+ * @param monitor the monitor
+ */
+ public ProgressStrictCompoundCommand(IProgressMonitorProvider monitor) {
+ super();
+ this.monitor = monitor;
+ }
+
+ // REUSED CLASS
+
+ @Override
+ protected boolean prepare() {
+ // Go through the commands of the list.
+ //
+ final ListIterator<Command> commands = commandList.listIterator();
+
+ // If there are some...
+ //
+ if (commands.hasNext()) {
+ boolean result = true;
+
+ // The termination guard is in the body.
+ //
+ for (;;) {
+ final Command command = commands.next();
+ if (command.canExecute()) {
+ if (commands.hasNext()) {
+ if (command.canUndo()) {
+ try {
+ if (commands.previousIndex() <= rightMostExecutedCommandIndex) {
+ command.redo();
+ worked();
+ } else {
+ ++rightMostExecutedCommandIndex;
+ command.execute();
+ worked();
+ }
+ } catch (final RuntimeException exception) {
+ CommonPlugin.INSTANCE.log(new WrappedException(
+ CommonPlugin.INSTANCE.getString("_UI_IgnoreException_exception"), exception) //$NON-NLS-1$
+ .fillInStackTrace());
+
+ result = false;
+ break;
+ }
+ } else {
+ // We can't undo it, so we'd better give up.
+ //
+ result = false;
+ break;
+ }
+ } else {
+ // Now is the best time to record isUndoable because later we would have to do all the executes
+ // again!
+ // This makes canUndo very simple!
+ //
+ isUndoable = command.canUndo();
+ break;
+ }
+ } else {
+ // If we can't execute this one, we just can't do it at all.
+ //
+ result = false;
+ break;
+ }
+ }
+
+ // If we are pessimistic, then we need to undo all the commands that we have executed so far.
+ //
+ if (isPessimistic) {
+ // The most recently processed command will never have been executed.
+ //
+ commands.previous();
+
+ // We want to unroll all the effects of the previous commands.
+ //
+ while (commands.hasPrevious()) {
+ final Command command = commands.previous();
+ command.undo();
+ worked();
+ }
+ }
+
+ return result;
+ }
+ isUndoable = false;
+ return false;
+ }
+
+ @Override
+ public void execute() {
+ if (isPessimistic) {
+ for (final ListIterator<Command> commands = commandList.listIterator(); commands.hasNext();) {
+ try {
+ // Either execute or redo the command, as appropriate.
+ //
+ final Command command = commands.next();
+ if (commands.previousIndex() <= rightMostExecutedCommandIndex) {
+ command.redo();
+ worked();
+ } else {
+ command.execute();
+ worked();
+ }
+ } catch (final RuntimeException exception) {
+ // Skip over the command that threw the exception.
+ //
+ commands.previous();
+
+ // Iterate back over the executed commands to undo them.
+ //
+ while (commands.hasPrevious()) {
+ commands.previous();
+ final Command command = commands.previous();
+ if (command.canUndo()) {
+ command.undo();
+ worked();
+ } else {
+ break;
+ }
+ }
+
+ throw exception;
+ }
+ }
+ } else if (!commandList.isEmpty()) {
+ final Command command = commandList.get(commandList.size() - 1);
+ command.execute();
+ worked();
+ }
+ }
+
+ @Override
+ public void undo() {
+ if (isPessimistic) {
+ super.undo();
+ } else if (!commandList.isEmpty()) {
+ final Command command = commandList.get(commandList.size() - 1);
+ command.undo();
+ worked();
+ }
+ }
+
+ @Override
+ public void redo() {
+ if (isPessimistic) {
+ super.redo();
+ } else if (!commandList.isEmpty()) {
+ final Command command = commandList.get(commandList.size() - 1);
+ command.redo();
+ worked();
+ }
+ }
+
+ @Override
+ public boolean appendAndExecute(Command command) {
+ if (command != null) {
+ if (!isPrepared) {
+ if (commandList.isEmpty()) {
+ isPrepared = true;
+ isExecutable = true;
+ } else {
+ isExecutable = prepare();
+ isPrepared = true;
+ isPessimistic = true;
+ if (isExecutable) {
+ execute();
+ }
+ }
+ }
+
+ if (command.canExecute()) {
+ try {
+ command.execute();
+ worked();
+ commandList.add(command);
+ ++rightMostExecutedCommandIndex;
+ isUndoable = command.canUndo();
+ return true;
+ } catch (final RuntimeException exception) {
+ CommonPlugin.INSTANCE
+ .log(new WrappedException(CommonPlugin.INSTANCE.getString("_UI_IgnoreException_exception"), //$NON-NLS-1$
+ exception).fillInStackTrace());
+ }
+ }
+
+ command.dispose();
+ }
+
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ final StringBuffer result = new StringBuffer(super.toString());
+ result.append(" (isUndoable: " + isUndoable + ")"); //$NON-NLS-1$ //$NON-NLS-2$
+ result.append(" (isPessimistic: " + isPessimistic + ")"); //$NON-NLS-1$ //$NON-NLS-2$
+ result.append(" (rightMostExecutedCommandIndex: " + rightMostExecutedCommandIndex + ")"); //$NON-NLS-1$ //$NON-NLS-2$
+
+ return result.toString();
+ }
+
+ // END REUSED CLASS
+
+ private void worked() {
+ final IProgressMonitor progressMonitor = monitor.getProgressMonitor();
+ if (progressMonitor == null) {
+ return;
+ }
+ progressMonitor.worked(1);
+ while (Display.getDefault().readAndDispatch()) {
+ }
+ }
+}
diff --git a/bundles/org.eclipse.emfforms.editor/src/org/eclipse/emfforms/spi/editor/helpers/ResourceSetHelpers.java b/bundles/org.eclipse.emfforms.editor/src/org/eclipse/emfforms/spi/editor/helpers/ResourceSetHelpers.java
index dec5e78..c13fc71 100644
--- a/bundles/org.eclipse.emfforms.editor/src/org/eclipse/emfforms/spi/editor/helpers/ResourceSetHelpers.java
+++ b/bundles/org.eclipse.emfforms.editor/src/org/eclipse/emfforms/spi/editor/helpers/ResourceSetHelpers.java
@@ -109,6 +109,17 @@
new ComposedAdapterFactory(
ComposedAdapterFactory.Descriptor.Registry.INSTANCE) }),
commandStack);
+ return createResourceSet(domain);
+ }
+
+ /**
+ * Creates a ResourceSet with the given EditingDomain.
+ *
+ * @param domain the editing domain
+ * @return the resource set
+ * @since 1.11
+ */
+ public static ResourceSet createResourceSet(final AdapterFactoryEditingDomain domain) {
final ResourceSet resourceSet = domain.getResourceSet();
resourceSet.eAdapters().add(
new AdapterFactoryEditingDomain.EditingDomainProvider(domain));