| /******************************************************************************* |
| * <copyright> |
| * |
| * Copyright (c) 2005, 2018 SAP AG. |
| * 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: |
| * SAP AG - initial API, implementation and documentation |
| * mwenz - Bug 324859 - Need Undo/Redo support for Non-EMF based domain objects |
| * mwenz - Bug 340627 - Features should be able to indicate cancellation |
| * mwenz - Bug 351053 - Remove the need for WorkspaceCommandStackImpl |
| * mwenz - Bug 486902 - Cancelling model changes done in a CustomFeature logs an error stack trace |
| * |
| * </copyright> |
| * |
| *******************************************************************************/ |
| package org.eclipse.graphiti.ui.internal.editor; |
| |
| import java.util.ArrayList; |
| import java.util.EventObject; |
| import java.util.HashMap; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| |
| import org.eclipse.core.commands.operations.IOperationHistory; |
| import org.eclipse.core.commands.operations.IUndoContext; |
| import org.eclipse.core.commands.operations.IUndoableOperation; |
| import org.eclipse.core.runtime.IStatus; |
| import org.eclipse.emf.common.command.CommandStackListener; |
| import org.eclipse.emf.transaction.RollbackException; |
| import org.eclipse.emf.transaction.TransactionalCommandStack; |
| import org.eclipse.emf.transaction.TransactionalEditingDomain; |
| import org.eclipse.emf.workspace.IWorkspaceCommandStack; |
| import org.eclipse.gef.commands.Command; |
| import org.eclipse.gef.commands.CommandStack; |
| import org.eclipse.graphiti.features.IFeature; |
| import org.eclipse.graphiti.features.IFeatureAndContext; |
| import org.eclipse.graphiti.internal.command.CommandContainer; |
| import org.eclipse.graphiti.internal.command.DefaultExecutionInfo; |
| import org.eclipse.graphiti.internal.command.FeatureCommand; |
| import org.eclipse.graphiti.internal.command.GFPreparableCommand2; |
| import org.eclipse.graphiti.internal.command.ICommand; |
| import org.eclipse.graphiti.tb.IToolBehaviorProvider; |
| import org.eclipse.graphiti.ui.internal.T; |
| import org.eclipse.graphiti.ui.internal.command.AddModelObjectCommand; |
| import org.eclipse.graphiti.ui.internal.command.ContextEntryCommand; |
| import org.eclipse.graphiti.ui.internal.command.CreateConnectionCommand; |
| import org.eclipse.graphiti.ui.internal.command.GFCommand; |
| import org.eclipse.graphiti.ui.internal.command.GefCommandWrapper; |
| import org.eclipse.graphiti.ui.internal.command.ReconnectCommand; |
| import org.eclipse.graphiti.ui.internal.services.GraphitiUiInternal; |
| import org.eclipse.graphiti.ui.platform.IConfigurationProvider; |
| |
| /** |
| * The Class GFCommandStack. |
| * |
| * @noinstantiate This class is not intended to be instantiated by clients. |
| * @noextend This class is not intended to be subclassed by clients. |
| */ |
| public class GFCommandStack extends CommandStack implements CommandStackListener { |
| |
| static final String OPTION_EXECUTION_INFO = "org.eclipse.graphiti.execution.info"; |
| |
| private TransactionalCommandStack emfCommandStack; |
| |
| private IConfigurationProvider configurationProvider; |
| |
| private TransactionalEditingDomain editingDomain; |
| |
| public GFCommandStack(IConfigurationProvider configurationProvider, TransactionalEditingDomain editingDomain) { |
| org.eclipse.emf.common.command.CommandStack commandStack = editingDomain.getCommandStack(); |
| if (!(commandStack instanceof TransactionalCommandStack)) { |
| IllegalArgumentException e = new IllegalArgumentException( |
| "The command stack of the passed editing domain must be a TransactionalCommandStack"); |
| e.fillInStackTrace(); |
| throw e; |
| } |
| emfCommandStack = (TransactionalCommandStack) commandStack; |
| emfCommandStack.addCommandStackListener(this); |
| setConfigurationProvider(configurationProvider); |
| this.editingDomain = editingDomain; |
| } |
| |
| @Override |
| public boolean canRedo() { |
| return getEmfCommandStack().canRedo(); |
| } |
| |
| @Override |
| public boolean canUndo() { |
| return getEmfCommandStack().canUndo(); |
| } |
| |
| @Override |
| public void dispose() { |
| super.dispose(); |
| emfCommandStack.removeCommandStackListener(this); |
| emfCommandStack = null; |
| configurationProvider = null; |
| editingDomain = null; |
| } |
| |
| @Override |
| public void execute(Command gefCommand) { |
| if (gefCommand == null) { |
| return; |
| } |
| |
| org.eclipse.emf.common.command.Command emfCommand = GraphitiUiInternal.getCommandService().transformFromGefToEmfCommand(gefCommand); |
| org.eclipse.emf.common.command.Command gfPreparableCommand = new GFPreparableCommand2(getTransactionalEditingDomain(), emfCommand); |
| |
| IToolBehaviorProvider tbp = getConfigurationProvider().getDiagramTypeProvider().getCurrentToolBehaviorProvider(); |
| |
| DefaultExecutionInfo executionInfo = new DefaultExecutionInfo(); |
| GraphitiUiInternal.getCommandService().completeExecutionInfo(executionInfo, gefCommand); |
| // Put the execution info object into the options map for consumption within the execution |
| Map<String, DefaultExecutionInfo> options = new HashMap<String, DefaultExecutionInfo>(2); |
| options.put(OPTION_EXECUTION_INFO, executionInfo); |
| |
| // Store contexts of redo operations for restoring after a feature that |
| // has done no changes has been executed to re-enable redo. Executing a |
| // feature will remove the contexts from the operations - see Bug 405710 |
| IUndoContext[][] contexts = new IUndoContext[0][]; |
| if (emfCommandStack instanceof IWorkspaceCommandStack) { |
| IUndoableOperation[] originalRedoOperations = ((IWorkspaceCommandStack) emfCommandStack) |
| .getOperationHistory().getRedoHistory(IOperationHistory.GLOBAL_UNDO_CONTEXT); |
| contexts = new IUndoContext[originalRedoOperations.length][]; |
| for (int i = 0; i < originalRedoOperations.length; i++) { |
| contexts[i] = originalRedoOperations[i].getContexts(); |
| } |
| } |
| |
| tbp.preExecute(executionInfo); |
| try { |
| getEmfCommandStack().execute(gfPreparableCommand, options); |
| } catch (RollbackException e) { |
| if (e.getStatus().getSeverity() == IStatus.CANCEL) { |
| // Just log it as info (operation was cancelled on purpose) |
| T.racer().log(IStatus.INFO, "Command execution was cancelled: " + e.getMessage()); //$NON-NLS-1$ |
| } else { |
| T.racer().error("GFCommandStack.execute(Command) " + e, e); //$NON-NLS-1$ |
| } |
| } catch (Exception e) { |
| T.racer().error("GFCommandStack.execute(Command) " + e, e); //$NON-NLS-1$ |
| } |
| tbp.postExecute(executionInfo); |
| |
| // Check if the executed feature has really done changes (indicated by |
| // IFeature.hasDoneChanges). If not, remove the operation of the command |
| // used to execute the feature from the command stack. It will then also |
| // no longer appear as an entry in the undo stack. This is especially |
| // necessary for direct editing in a pattern environment when e.g. a new |
| // object is created and directly offered for direct editing (handled |
| // internally as two different commands because of EMF restrictions). |
| // This has been reworked for bug 327756 to enable the call to |
| // hasDoneChanges also for other types of features (especially |
| // CustomFeatures and CreateFeatures). |
| List<IFeature> features = new ArrayList<IFeature>(2); |
| if (gefCommand instanceof GefCommandWrapper) { |
| GefCommandWrapper gefCommandWrapper = (GefCommandWrapper) gefCommand; |
| ICommand command = gefCommandWrapper.getCommand(); |
| extractFeatures(features, command); |
| } else { |
| extractFeatures(features, gefCommand); |
| } |
| |
| boolean changesDone = false; |
| // Basic assumption is that no changes were done, only if no features |
| // were found we cannot judge if any changes happened, in that case we |
| // need to assume that changes happened. |
| if (features.size() == 0) { |
| changesDone = true; |
| } |
| |
| // Check if any of the involved features has done changes |
| for (Iterator<IFeature> iterator = features.iterator(); iterator.hasNext();) { |
| IFeature feature = iterator.next(); |
| if (feature.hasDoneChanges()) { |
| // First change is enough |
| changesDone = true; |
| break; |
| } |
| } |
| |
| // If no changes were done revert the undo stack entry |
| if (!changesDone && emfCommandStack instanceof IWorkspaceCommandStack) { |
| // Retrieve the last operation using the default context |
| IWorkspaceCommandStack workspaceCommandStackImpl = (IWorkspaceCommandStack) getEmfCommandStack(); |
| IUndoContext context = workspaceCommandStackImpl.getDefaultUndoContext(); |
| IUndoableOperation operation = workspaceCommandStackImpl.getOperationHistory().getUndoOperation(context); |
| |
| // Replace the found operation with an empty set |
| workspaceCommandStackImpl.getOperationHistory().replaceOperation(operation, new IUndoableOperation[0]); |
| |
| // Restore the original contexts of the redo operations to re-enable |
| // undo as there is no new entry on the undo stack - see Bug 405710 |
| IUndoableOperation[] newRedoOperations = workspaceCommandStackImpl.getOperationHistory().getRedoHistory( |
| IOperationHistory.GLOBAL_UNDO_CONTEXT); |
| for (int i = 0; i < newRedoOperations.length; i++) { |
| for (int j = 0; j < contexts[i].length; j++) { |
| if (!newRedoOperations[i].hasContext(contexts[i][j])) { |
| newRedoOperations[i].addContext(contexts[i][j]); |
| } |
| } |
| } |
| |
| // Update the editor actions bars, especially Edit --> Undo |
| notifyListeners(gefCommand, CommandStack.POST_MASK); |
| } |
| } |
| |
| /** |
| * Extracts from the given command (java.lang.Object is used because the |
| * potentially passed objects do not have another common super class) the |
| * list of all involved (executed) features. The features are added to the |
| * passed list. |
| * |
| * @param features |
| * The list to which the features will be added. |
| * @param command |
| * Any kind of command object that is executed on this command |
| * stack. |
| */ |
| private void extractFeatures(List<IFeature> features, Object command) { |
| if (command instanceof FeatureCommand) { |
| // Used in direct editing |
| features.add(((FeatureCommand) command).getFeature()); |
| } else if (command instanceof ContextEntryCommand) { |
| // Used for wrapping any feature in a context button |
| features.add(((ContextEntryCommand) command).getContextEntry().getFeature()); |
| } else if (command instanceof AddModelObjectCommand) { |
| // Used for add features |
| IFeatureAndContext[] innerFeatures = ((AddModelObjectCommand) command).getFeaturesAndContexts(); |
| for (int i = 0; i < innerFeatures.length; i++) { |
| features.add(innerFeatures[i].getFeature()); |
| } |
| } else if (command instanceof CreateConnectionCommand) { |
| // Used for connection creation |
| IFeatureAndContext[] innerFeatures = ((CreateConnectionCommand) command).getFeaturesAndContexts(); |
| for (int i = 0; i < innerFeatures.length; i++) { |
| features.add(innerFeatures[i].getFeature()); |
| } |
| } else if (command instanceof GFCommand) { |
| // Used for object creation |
| features.add(((GFCommand) command).getFeature()); |
| } else if (command instanceof ReconnectCommand) { |
| // Used for reconnection features |
| features.add(((ReconnectCommand) command).getFeature()); |
| } else if (command instanceof CommandContainer) { |
| // Used for custom features |
| ICommand[] innerCommands = ((CommandContainer) command).getCommands(); |
| for (int i = 0; i < innerCommands.length; i++) { |
| extractFeatures(features, innerCommands[i]); |
| } |
| } |
| } |
| |
| @Override |
| public void flush() { |
| super.flush(); |
| } |
| |
| @Override |
| public Object[] getCommands() { |
| return super.getCommands(); |
| } |
| |
| @Override |
| public Command getRedoCommand() { |
| org.eclipse.emf.common.command.Command emfCommand = getEmfCommandStack().getRedoCommand(); |
| return emfCommand != null ? GraphitiUiInternal.getCommandService().transformFromEmfToGefCommand(emfCommand) : null; |
| } |
| |
| @Override |
| public Command getUndoCommand() { |
| org.eclipse.emf.common.command.Command emfCommand = getEmfCommandStack().getUndoCommand(); |
| return emfCommand != null ? GraphitiUiInternal.getCommandService().transformFromEmfToGefCommand(emfCommand) : null; |
| } |
| |
| @Override |
| public boolean isDirty() { |
| return super.isDirty(); |
| } |
| |
| @Override |
| public void redo() { |
| getEmfCommandStack().redo(); |
| } |
| |
| @Override |
| public void undo() { |
| getEmfCommandStack().undo(); |
| } |
| |
| private TransactionalCommandStack getEmfCommandStack() { |
| return emfCommandStack; |
| } |
| |
| private TransactionalEditingDomain getTransactionalEditingDomain() { |
| return editingDomain; |
| } |
| |
| @SuppressWarnings("deprecation") |
| public void commandStackChanged(EventObject event) { |
| notifyListeners(); |
| notifyListeners(null, 0); |
| } |
| |
| private IConfigurationProvider getConfigurationProvider() { |
| return configurationProvider; |
| } |
| |
| private void setConfigurationProvider(IConfigurationProvider configurationProvider) { |
| this.configurationProvider = configurationProvider; |
| } |
| |
| } |