blob: 015b72ba6e6015dba8141b7b1a22b9dadefcd5c5 [file] [log] [blame]
/******************************************************************************
* Copyright (c) 2002, 2009 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
* Nicolas Rouquette (NASA) - Fix for Bug 260812.
****************************************************************************/
package org.eclipse.gmf.runtime.diagram.ui.parts;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.EventObject;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.eclipse.core.commands.ExecutionException;
import org.eclipse.core.commands.operations.IOperationHistory;
import org.eclipse.core.commands.operations.IOperationHistoryListener;
import org.eclipse.core.commands.operations.IUndoContext;
import org.eclipse.core.commands.operations.IUndoableOperation;
import org.eclipse.core.commands.operations.ObjectUndoContext;
import org.eclipse.core.commands.operations.OperationHistoryEvent;
import org.eclipse.core.commands.operations.OperationHistoryFactory;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.gef.commands.Command;
import org.eclipse.gef.commands.CommandStack;
import org.eclipse.gef.commands.CommandStackListener;
import org.eclipse.gef.commands.CompoundCommand;
import org.eclipse.gmf.runtime.common.core.command.CommandResult;
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.command.ICompositeCommand;
import org.eclipse.gmf.runtime.common.core.util.Log;
import org.eclipse.gmf.runtime.common.core.util.Trace;
import org.eclipse.gmf.runtime.diagram.ui.commands.CommandProxy;
import org.eclipse.gmf.runtime.diagram.ui.commands.ICommandProxy;
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.internal.tools.ConnectionHandleTool;
/**
* GEF command stack that delegates to an {@link IOperationHistory}.
*
* @author sshaw
* @author Tauseef A, Israr
* @author ldamus
*/
public class DiagramCommandStack
extends CommandStack {
private Map stackToManager = new HashMap();
private IDiagramEditDomain editDomain;
private Command cmdRecent = null;
private IOperationHistory delegate;
private IUndoContext undoContext;
private final class HistoryEventObject
extends EventObject {
private final OperationHistoryEvent event;
private HistoryEventObject(OperationHistoryEvent event) {
super(event.getHistory());
this.event = event;
}
/**
* Gets my operation history event.
*
* @return my operation history event.
*/
public OperationHistoryEvent getOperationHistoryEvent() {
return event;
}
}
/**
* Initializes me with my diagram edit domain and undo context.
*
* @param editDomain
* the editing domain assoicated with this stack
* @param undoContext
* my undo context
*/
public DiagramCommandStack(IDiagramEditDomain editDomain) {
this.editDomain = editDomain;
}
/**
* Adds a listener to this CommandStack.
*
* @param listener
* The Object listening to this CommandStack.
*/
public void addCommandStackListener(CommandStackListener listener) {
final CommandStackListener csl = listener;
// The removal of the listener here is done to avoid multiple
// commandchangelisteners added to the commandmanager.
// Tauseef Israr
removeCommandStackListener(csl);
IOperationHistoryListener cmcl = new IOperationHistoryListener() {
public void historyNotification(OperationHistoryEvent event) {
if (csl != null) {
csl.commandStackChanged(new HistoryEventObject(event));
}
}
};
stackToManager.put(csl, cmcl);
getOperationHistory().addOperationHistoryListener(cmcl);
}
/**
* Returns <code>true</code> if there is a Command to redo.
*
* @return <code>true</code> if there is a Command to redo.
*/
public boolean canRedo() {
return getOperationHistory().canRedo(getUndoContext());
}
/**
* Returns <code>true</code> if the last Command executed can be undone.
*
* @return <code>true</code> if the last Command executed can be undone.
*/
public boolean canUndo() {
return getOperationHistory().canUndo(getUndoContext());
}
/**
* Executes the given Command if it can execute.
*
* @param command
* The Command to execute.
*/
public void execute(Command command) {
execute(command, null);
}
/**
* Executes the given Command if it can execute.
*
* @param command
* The Command to execute.
* @param progressMonitor
*/
public void execute(Command command, IProgressMonitor progressMonitor) {
if (command == null || !command.canExecute())
return;
execute(getICommand(command), progressMonitor);
}
/**
* exectus a the supplied command
*
* @param command
* the command to execute
*/
protected void execute(ICommand command) {
execute(command, null);
}
/**
* executes the supplied command
*
* @param command
* the command to exectue
* @param progressMonitor
*/
protected void execute(ICommand command, IProgressMonitor progressMonitor) {
if (progressMonitor != null) {
try {
command.addContext(getUndoContext());
getOperationHistory().execute(command, progressMonitor, null);
} catch (ExecutionException e) {
Trace.catching(DiagramUIPlugin.getInstance(),
DiagramUIDebugOptions.EXCEPTIONS_CATCHING,
getClass(), "execute", e); //$NON-NLS-1$
Log.error(DiagramUIPlugin.getInstance(),
DiagramUIStatusCodes.COMMAND_FAILURE, "execute", e); //$NON-NLS-1$
}
} else {
try {
command.addContext(getUndoContext());
getOperationHistory().execute(command,
new NullProgressMonitor(), null);
} catch (ExecutionException e) {
Trace.catching(DiagramUIPlugin.getInstance(),
DiagramUIDebugOptions.EXCEPTIONS_CATCHING,
getClass(), "execute", e); //$NON-NLS-1$
Log.error(DiagramUIPlugin.getInstance(),
DiagramUIStatusCodes.COMMAND_FAILURE, "execute", e); //$NON-NLS-1$
}
}
}
/**
* Converts a GEF {@link Command} into a GMF {@link ICommand}
*
* @param command
* the GEF command
* @return the GMF command
*/
public static ICommand getICommand(Command command) {
if (command instanceof CompoundCommand) {
CompositeCommand composite = new CompositeCommand(
command.getLabel());
Object[] subCommands = ((CompoundCommand) command).getChildren();
for (int i = 0; i < subCommands.length; i++) {
composite.compose(getICommand((Command) subCommands[i]));
}
return composite.reduce();
}
if (command instanceof ICommandProxy) {
return getICommand(((ICommandProxy) command).getICommand());
}
if (null != command) {
return new CommandProxy(command);
} else {
return null;
}
}
/**
* Removes redundancies from <code>command</code> by stripping out layers
* of command wrappers used to accomodate the use of GEF commands on an
* {@link IOperationHistory} and {@link ICommand}s on the GEF
* {@link CommandStack}.
*
* @param command
* the command to be processed
* @return a command representing the simplified form of the input command.
* May be a new command.
*/
public static ICommand getICommand(ICommand command) {
ICommand result = command;
if (command instanceof ICompositeCommand) {
// process composite command
List processedCommands = new ArrayList();
ICompositeCommand composite = (ICompositeCommand) command;
if (!composite.isEmpty()) {
for (Iterator i = composite.iterator(); i.hasNext();) {
IUndoableOperation nextOperation = (IUndoableOperation) i
.next();
// remove the next child from the composite
i.remove();
// convert any GEF commands to GMF commands
if (nextOperation instanceof ICommand) {
ICommand nextCommand = (ICommand) nextOperation;
processedCommands.add(getICommand(nextCommand));
} else {
processedCommands.add(nextOperation);
}
}
// add all the children back
for (Iterator i = processedCommands.iterator(); i.hasNext();) {
composite.add((IUndoableOperation) i.next());
}
// reduce to the simplest equivalent form
result = composite.reduce();
}
} else if (command instanceof CommandProxy) {
// process GEF command proxy
return getICommand(((CommandProxy) command).getCommand());
}
return result;
}
/**
* Returns the most recently executed command.
*
* @return The most recently executed command.
*/
public Command getMostRecentCommand() {
return cmdRecent;
}
/**
* getRedoCommand Returns the command at the top of the redo stack.
*
* @see org.eclipse.gef.commands.CommandStack#getRedoCommand()
*/
public Command getRedoCommand() {
if (getOperationHistory().canRedo(getUndoContext())) {
Command emptyCmd = new Command() {
// empty
};
IUndoableOperation redo = getOperationHistory().getRedoOperation(
getUndoContext());
emptyCmd.setLabel(redo.getLabel());
return emptyCmd;
}
return null;
}
/**
* getUndoCommand() Returns the next command to be undone.
*
* @see org.eclipse.gef.commands.CommandStack#getUndoCommand()
*/
public Command getUndoCommand() {
if (getOperationHistory().canUndo(getUndoContext())) {
Command emptyCmd = new Command() {
// empty
};
IUndoableOperation undo = getOperationHistory().getUndoOperation(
getUndoContext());
emptyCmd.setLabel(undo.getLabel());
return emptyCmd;
}
return null;
}
/**
* Executes the last undone Command.
*/
public void redo() {
cmdRecent = getRedoCommand();
try {
getOperationHistory().redo(getUndoContext(),
new NullProgressMonitor(), null);
} catch (ExecutionException e) {
Trace.catching(DiagramUIPlugin.getInstance(),
DiagramUIDebugOptions.EXCEPTIONS_CATCHING,
ConnectionHandleTool.class, "redo", e); //$NON-NLS-1$
Log.error(DiagramUIPlugin.getInstance(),
DiagramUIStatusCodes.COMMAND_FAILURE, "redo", e); //$NON-NLS-1$
}
}
/**
* Removes the given CommandStackListener.
*
* @param listener
* The object to be removed from the list of listeners.
*/
public void removeCommandStackListener(CommandStackListener listener) {
final CommandStackListener csl = listener;
if (csl != null) {
IOperationHistoryListener historyListener = (IOperationHistoryListener) stackToManager
.get(csl);
if (historyListener != null) {
getOperationHistory().removeOperationHistoryListener(
historyListener);
}
// mgoyal: removing from stack manager
stackToManager.remove(csl);
}
}
/**
* Undoes the last executed Command.
*/
public void undo() {
cmdRecent = getUndoCommand();
try {
getOperationHistory().undo(getUndoContext(),
new NullProgressMonitor(), null);
} catch (ExecutionException e) {
Trace.catching(DiagramUIPlugin.getInstance(),
DiagramUIDebugOptions.EXCEPTIONS_CATCHING,
ConnectionHandleTool.class, "undo", e); //$NON-NLS-1$
Log.error(DiagramUIPlugin.getInstance(),
DiagramUIStatusCodes.COMMAND_FAILURE, "undo", e); //$NON-NLS-1$
}
}
/**
* Returns the editDomain.
*
* @return IDiagramEditDomain
*/
protected IDiagramEditDomain getDiagramEditDomain() {
return editDomain;
}
/**
* Gets my operation history delegate.
*
* @return my operation history delegate
*/
protected IOperationHistory getOperationHistory() {
if (delegate == null) {
delegate = OperationHistoryFactory.getOperationHistory();
}
return delegate;
}
/**
* Sets my operation history delegate.
*
* @param operationHistory
* my operation history delegate
*/
public void setOperationHistory(IOperationHistory operationHistory) {
this.delegate = operationHistory;
}
/**
* Gets the return values of the given executed command
*
* @param c
* The command
* @return A collection of values returned by the given command
*/
public static Collection getReturnValues(Command c) {
if (c instanceof CompoundCommand) {
CompoundCommand cc = (CompoundCommand) c;
List l = new ArrayList(cc.size());
for (Iterator i = cc.getCommands().iterator(); i.hasNext();)
l.addAll(getReturnValues((Command) i.next()));
return l;
} else if (c instanceof ICommandProxy) {
return getReturnValues((ICommandProxy) c);
}
return Collections.EMPTY_LIST;
}
/**
* gets the return the values for the supplied command.
*
* @param cmd
* command to use
* @return a collection of return values
*/
public static Collection getReturnValues(ICommandProxy cmd) {
return getReturnValues(cmd.getICommand());
}
/**
* gets the return the values for the supplied command.
* @param cmd command to use
* @return a collection of return values
*/
public static Collection getReturnValues( CommandProxy cmd ) {
return getReturnValues( cmd.getCommand() );
}
/**
* gets the return the values for the supplied command.
*
* @param cmd
* command to use
* @return a collection of return values
*/
public static Collection getReturnValues(ICommand cmd) {
if (cmd instanceof ICompositeCommand) {
ICompositeCommand cc = (ICompositeCommand) cmd;
List l = new ArrayList();
for (Iterator i = cc.iterator(); i.hasNext();) {
IUndoableOperation child = (IUndoableOperation) i.next();
if (child instanceof ICommand) {
l.addAll(getReturnValues((ICommand) child));
}
}
return l;
} else if ( cmd instanceof CommandProxy ) { //
// Need to recurse into the proxy command(s) since they
// will not have set the CommandProxy result
// This Could be moved into CommandProxy but
// #getCommandResult() can no longer be final.
return getReturnValues((CommandProxy)cmd);
} else {
CommandResult r = cmd.getCommandResult();
Object o = r != null ? r.getReturnValue()
: null;
if (o instanceof Collection) {
return (Collection) o;
} else if (o != null) {
return Collections.singletonList(o);
}
}
return Collections.EMPTY_LIST;
}
/**
* Gets my undo context. I add my context to all commands executed through
* me.
*
* @return my undo context
*/
public IUndoContext getUndoContext() {
if (undoContext == null) {
undoContext = new ObjectUndoContext(this);
}
return undoContext;
}
/**
* Sets my undo context.
*
* @param undoContext
* my undo context
*/
public void setUndoContext(IUndoContext undoContext) {
this.undoContext = undoContext;
}
public void dispose() {
// clean up the known listeners (if there is any remaining)
// this will prevent clients from causing memory leaks
Set entries = stackToManager.entrySet();
for (Iterator iter = entries.iterator(); iter.hasNext();) {
Map.Entry element = (Map.Entry) iter.next();
IOperationHistoryListener historyListener = (IOperationHistoryListener) element.getValue();
if (historyListener != null) {
getOperationHistory().removeOperationHistoryListener(
historyListener);
}
}
super.dispose();
}
}