blob: 009832917547aa417f3997c9c8976fb41f1bc9c4 [file] [log] [blame]
/******************************************************************************
* Copyright (c) 2002, 2008 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
****************************************************************************/
package org.eclipse.gmf.runtime.diagram.ui.providers;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import org.eclipse.core.commands.ExecutionException;
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.emf.edit.domain.EditingDomain;
import org.eclipse.emf.edit.domain.IEditingDomainProvider;
import org.eclipse.emf.transaction.TransactionalEditingDomain;
import org.eclipse.emf.transaction.util.TransactionUtil;
import org.eclipse.gef.EditPart;
import org.eclipse.gef.EditPartViewer;
import org.eclipse.gef.Request;
import org.eclipse.gef.RequestConstants;
import org.eclipse.gef.RootEditPart;
import org.eclipse.gef.commands.Command;
import org.eclipse.gef.commands.CommandStack;
import org.eclipse.gef.commands.CompoundCommand;
import org.eclipse.gef.requests.GroupRequest;
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.util.Trace;
import org.eclipse.gmf.runtime.common.ui.action.actions.global.ClipboardContentsHelper;
import org.eclipse.gmf.runtime.common.ui.action.actions.global.ClipboardManager;
import org.eclipse.gmf.runtime.common.ui.action.global.GlobalActionId;
import org.eclipse.gmf.runtime.common.ui.services.action.global.AbstractGlobalActionHandler;
import org.eclipse.gmf.runtime.common.ui.services.action.global.IGlobalActionContext;
import org.eclipse.gmf.runtime.common.ui.util.ICustomData;
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.editparts.DiagramRootEditPart;
import org.eclipse.gmf.runtime.diagram.ui.editparts.INodeEditPart;
import org.eclipse.gmf.runtime.diagram.ui.editparts.ShapeEditPart;
import org.eclipse.gmf.runtime.diagram.ui.internal.commands.ClipboardCommand;
import org.eclipse.gmf.runtime.diagram.ui.internal.commands.CopyCommand;
import org.eclipse.gmf.runtime.diagram.ui.parts.DiagramCommandStack;
import org.eclipse.gmf.runtime.diagram.ui.parts.IDiagramWorkbenchPart;
import org.eclipse.gmf.runtime.diagram.ui.providers.internal.DiagramProvidersDebugOptions;
import org.eclipse.gmf.runtime.diagram.ui.providers.internal.DiagramProvidersPlugin;
import org.eclipse.gmf.runtime.diagram.ui.requests.PasteViewRequest;
import org.eclipse.gmf.runtime.draw2d.ui.mapmode.IMapMode;
import org.eclipse.gmf.runtime.draw2d.ui.mapmode.MapModeUtil;
import org.eclipse.gmf.runtime.emf.commands.core.command.CompositeTransactionalCommand;
import org.eclipse.gmf.runtime.emf.ui.properties.actions.PropertyPageViewAction;
import org.eclipse.gmf.runtime.notation.Edge;
import org.eclipse.gmf.runtime.notation.View;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IWorkbenchPart;
/**
* Class that implements the <code>IGlobalActionHandler</code> interface.
* Contains behaviour common to GMF diagrams.
*
* @author Vishy Ramaswamy
*/
public class DiagramGlobalActionHandler
extends AbstractGlobalActionHandler {
/** Remember the "open" commands associated with the selected edit parts. */
private ICommand openCommand = null;
/**
* Constructor for DiagramGlobalActionHandler.
*/
public DiagramGlobalActionHandler() {
super();
}
/*
* (non-Javadoc)
*
* @see org.eclipse.gmf.runtime.common.ui.services.action.global.IGlobalActionHandler#getCommand(org.eclipse.gmf.runtime.common.ui.services.action.global.IGlobalActionContext)
*/
public ICommand getCommand(IGlobalActionContext cntxt) {
/* Check if the active part is a IDiagramWorkbenchPart */
IWorkbenchPart part = cntxt.getActivePart();
if (!(part instanceof IDiagramWorkbenchPart)) {
return null;
}
/* Get the model operation context */
IDiagramWorkbenchPart diagramPart = (IDiagramWorkbenchPart) part;
/* Create a command */
ICommand command = null;
/* Check the action id */
String actionId = cntxt.getActionId();
if (actionId.equals(GlobalActionId.DELETE)) {
CompoundCommand deleteCC = getDeleteCommand(diagramPart, cntxt);
/* Set the command */
if (deleteCC != null && deleteCC.canExecute())
command = new CommandProxy(deleteCC);
} 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)) {
// Open command: use the previously cached command.
command = openCommand;
} else if (actionId.equals(GlobalActionId.PASTE)) {
PasteViewRequest pasteReq = createPasteViewRequest();
/* Get the selected edit parts */
Object[] objects = ((IStructuredSelection) cntxt.getSelection())
.toArray();
if (objects.length == 1) {
/* Send the request to the target edit part of the paste command for the currently selected part */
EditPart targetEP = ((EditPart) objects[0]).getTargetEditPart(pasteReq);
Command paste = targetEP.getCommand(pasteReq);
if (paste != null) {
/* Set the command */
CommandStack cs = diagramPart.getDiagramEditDomain()
.getDiagramCommandStack();
cs.execute(paste);
diagramPart.getDiagramEditPart().getFigure().invalidate();
diagramPart.getDiagramEditPart().getFigure().validate();
selectAddedObject(diagramPart.getDiagramGraphicalViewer(),
DiagramCommandStack.getReturnValues(paste));
return null;
}
}
} else if (actionId.equals(GlobalActionId.SAVE)) {
part.getSite().getPage().saveEditor((IEditorPart) diagramPart,
false);
} else if (actionId.equals(GlobalActionId.PROPERTIES)) {
new PropertyPageViewAction().run();
}
return command;
}
/**
* Returns a command to copy the context's selection to the clipboard.
*
* @param cntxt
* the <code>IGlobalActionContext</code> from which the label
* and selection are retrieved.
* @param diagramPart
* the <code>IDiagramWorkbenchPart</code> from which the
* diagram is retrieved.
* @param isUndoable
* true if this command should be undoable/redoable; false
* otherwise
* @return the copy command
*/
protected ICommand getCopyCommand(IGlobalActionContext cntxt,
IDiagramWorkbenchPart diagramPart, final boolean isUndoable) {
TransactionalEditingDomain editingDomain = getEditingDomain(diagramPart);
if (editingDomain == null) {
return null;
}
return new CopyCommand(editingDomain, cntxt.getLabel(), diagramPart.getDiagram(),
getSelectedViews(cntxt.getSelection())) {
public boolean canUndo() {
return isUndoable;
}
public boolean canRedo() {
return isUndoable;
}
protected IStatus doUndo(IProgressMonitor monitor, IAdaptable info)
throws ExecutionException {
if (isUndoable) {
return Status.OK_STATUS;
}
return super.doUndo(monitor, info);
}
protected IStatus doRedo(IProgressMonitor monitor, IAdaptable info)
throws ExecutionException {
if (isUndoable) {
return Status.OK_STATUS;
}
return super.doRedo(monitor, info);
}
};
}
/**
* Returns a command to copy the context's selection to the clipboard and to
* delete it.
*
* @param cntxt
* the <code>IGlobalActionContext</code> from which the label
* and selection are retrieved.
* @param diagramPart
* the <code>IDiagramWorkbenchPart</code> from which the
* diagram is retrieved.
* @return the cut command
*/
protected ICommand getCutCommand(IGlobalActionContext cntxt,
IDiagramWorkbenchPart diagramPart) {
TransactionalEditingDomain editingDomain = getEditingDomain(diagramPart);
if (editingDomain == null) {
return null;
}
CompositeTransactionalCommand cut = new CompositeTransactionalCommand(editingDomain, cntxt
.getLabel());
// Add a copy command - the cut must be undoable/redoable
cut.compose(getCopyCommand(cntxt, diagramPart, true));
/* Get the selected edit parts */
Object[] objects = ((IStructuredSelection) cntxt.getSelection())
.toArray();
for (int i = 0; i < objects.length; i++) {
/* Get the next part */
EditPart editPart = (EditPart) objects[i];
/* Create the delete request */
GroupRequest deleteReq = new GroupRequest(
RequestConstants.REQ_DELETE);
/* Send the request to the edit part */
Command deleteCommand = editPart.getCommand(deleteReq);
/* Add to the compound command */
if (deleteCommand != null) {
cut.compose(new CommandProxy(deleteCommand));
}
}
if (!cut.isEmpty() && cut.canExecute())
return cut;
return null;
}
/**
* Creates and returns the appropriate <code>PasteViewRequest</code> that
* is to be used to get the appropriate paste <code>Command</code> from
* the <code>EditPart</code>. The returned <code>PasteViewRequest</code>
* contains data from the clipboard
*
* @return PasteViewRequest
*/
protected PasteViewRequest createPasteViewRequest() {
PasteViewRequest pasteReq;
ICustomData[] data = ClipboardManager.getInstance().getClipboardData(
ClipboardCommand.DRAWING_SURFACE,
ClipboardContentsHelper.getInstance());
if (data == null) {
data = ClipboardManager.getInstance().getClipboardData(
ClipboardManager.COMMON_FORMAT,
ClipboardContentsHelper.getInstance());
}
/* Create the paste request */
pasteReq = new PasteViewRequest(data);
return pasteReq;
}
/**
* Returns appropriate delete command for this context.
*
* @param part the workbench part
* @param cntxt
* the <code>IGlobalActionContext</code> holding the necessary
* information needed by this action handler
* @return CompoundCommand command
*/
private CompoundCommand getDeleteCommand(IDiagramWorkbenchPart part,
IGlobalActionContext cntxt) {
/* Create the delete request */
GroupRequest deleteReq = new GroupRequest(RequestConstants.REQ_DELETE);
CompoundCommand deleteCC = new CompoundCommand(cntxt.getLabel());
TransactionalEditingDomain editingDomain = getEditingDomain(part);
if (editingDomain == null) {
return deleteCC;
}
CompositeTransactionalCommand compositeCommand = new CompositeTransactionalCommand(editingDomain,
cntxt.getLabel());
/* Get the selected edit parts */
Object[] objects = ((IStructuredSelection) cntxt.getSelection())
.toArray();
for (int i = 0; i < objects.length; i++) {
/* Get the next part */
EditPart editPart = (EditPart) objects[i];
/* Send the request to the edit part */
Command command = editPart.getCommand(deleteReq);
if (command != null)
compositeCommand.compose(new CommandProxy(command));
// deleteCC.add(editPart.getCommand(deleteReq));
}
if (!compositeCommand.isEmpty()) {
deleteCC.add(new ICommandProxy(compositeCommand));
}
return deleteCC;
}
private boolean isContainedInViews(List views, View view) {
while (view != null) {
if (views.contains(view)) {
return true;
}
if (view.eContainer() instanceof View)
view = (View) view.eContainer();
else
break;
}
return false;
}
/**
* Returns the selected <code>View</code> objects, only if the selection
* is an <code>IStructuredSelection</code>. and only the
* <code>View</code> object of an <code>INodeEditPart</code> or a
* <code>ShapeEditPart</code>
*
* @param sel
* the selection from which to extract the View objects
* @return List the selected View. Could be empty if the selection doesn't
* contain proper edit parts, or, could be the original if the
* selection is not an <code>IStructuredSelection</code>
*/
protected List getSelectedViews(ISelection sel) {
final ArrayList views = new ArrayList();
final ArrayList editParts = new ArrayList();
/* Check if the selection is a structured selection */
if (!(sel instanceof IStructuredSelection)) {
return views;
}
/* Get the relevant Views */
for (Iterator i = ((IStructuredSelection) sel).iterator(); i.hasNext();) {
Object object = i.next();
if (!((object instanceof INodeEditPart) || (object instanceof ShapeEditPart))) {
continue;
}
View view = (object instanceof IAdaptable) ? (View) ((IAdaptable) object)
.getAdapter(View.class)
: null;
/* Make sure that view is not deleted */
if (view != null && view.eResource() != null) {
views.add(view);
editParts.add(object);
}
}
/* Get rid of dangling connections */
if (!views.isEmpty()) {
try {
TransactionUtil.getEditingDomain(views.get(0)).runExclusive(
new Runnable() {
public void run() {
ArrayList objects = (ArrayList) views.clone();
for (Iterator i = objects.iterator(); i.hasNext();) {
Object object = i.next();
if (object instanceof Edge) {
Edge view = (Edge) object;
View fromView = view.getSource();
View toView = view.getTarget();
if (fromView == null || toView == null
|| !isContainedInViews(views, fromView)
|| !isContainedInViews(views, toView)) {
views.remove(view);
}
}
}
}
});
} catch (Exception e) {
Trace.catching(DiagramProvidersPlugin.getInstance(),
DiagramProvidersDebugOptions.EXCEPTIONS_CATCHING, getClass(),
"getSelectedViews()", //$NON-NLS-1$
e);
}
}
/* Make sure that the selection contains atleast one IShapeView */
boolean doesSelectionContainAShapeView = false;
for (Iterator i = editParts.iterator(); i.hasNext();) {
if (i.next() instanceof ShapeEditPart) {
doesSelectionContainAShapeView = true;
break;
}
}
/* Clear the selection if no shape views are present */
if (!doesSelectionContainAShapeView) {
views.clear();
}
return views;
}
/**
* Checks to determine if the selected edit part can be opened.
* <p>
* In order to truly verify that the edit part can be opened, the
* corresponding "open" command must be obtained and tested for execution.
* This command can then be cached for the getCommand() method.
* <p>
*
* @param cntxt
* the <code>IGlobalActionContext</code> holding the necessary
* information needed by this action handler
* @return boolean - indicates an "open" command exists and can be executed
*/
private boolean canOpen(IGlobalActionContext cntxt) {
// Reset any information about the elements to be opened.
boolean canOpenAll = true;
openCommand = new CompositeCommand(cntxt.getLabel());
// Since all edit parts are associated with the smae edit domain,
// get the model operation context from the first selected edit part.
Object[] objects = ((IStructuredSelection) cntxt.getSelection())
.toArray();
// For all of the selected objects until one cannot be opened
for (int i = 0; canOpenAll && i < objects.length; i++) {
if (objects[i] instanceof EditPart) {
// Get the edit part's command for the "open" request.
EditPart editPart = (EditPart) objects[i];
Request request = new Request(RequestConstants.REQ_OPEN);
Command cmd = editPart.getCommand(request);
// If a command is not available or cannot be executed,
if (cmd == null || !cmd.canExecute()) {
// Do not allow any of the selected parts to be opened.
canOpenAll = false;
}
// else add the command to the composite command.
else {
openCommand.compose(new CommandProxy(cmd));
}
}
}
if (!canOpenAll) {
openCommand = null;
}
return canOpenAll;
}
/**
* Checks if the selected IViews can be deleted.
*
* @param cntxt
* the <code>IGlobalActionContext</code> holding the necessary
* information needed by this action handler
* @return boolean indicating the enablement of the delete action
*/
private boolean canDelete(IGlobalActionContext cntxt) {
// The selectedObjects are not in a container
// that is set to canonical, so do a regular check that the
// command is not null.
return getCommand(cntxt) != null;
}
/**
* Checks if the selected IViews can be copied to the clipboard
*
* @param cntxt
* the <code>IGlobalActionContext</code> holding the necessary
* information needed by this action handler
* @return boolean indicating the enablement of the copy action
*/
protected boolean canCopy(IGlobalActionContext cntxt) {
List elements = getSelectedViews(cntxt.getSelection());
/* Make sure the selection is not empty */
if (elements.isEmpty()) {
return false;
}
return true;
}
/**
* Checks if the selected IViews can be cut
*
* @param cntxt
* the <code>IGlobalActionContext</code> holding the necessary
* information needed by this action handler
* @return boolean indicating the enablement of the cut action
*/
protected boolean canCut(IGlobalActionContext cntxt) {
String actionId = cntxt.getActionId();
if (actionId.equals(GlobalActionId.CUT)) {
ICommand command = getCommand(cntxt);
if (command != null && command.canExecute()) {
return canCopy(cntxt);
}
}
return false;
}
/**
* Checks if the paste can occur
*
* @param cntxt
* the <code>IGlobalActionContext</code> holding the necessary
* information needed by this action handler
* @return boolean indicating the enablement of the paste action
*/
protected boolean canPaste(IGlobalActionContext cntxt) {
/* Check if the clipboard has data for the drawing surface */
return ClipboardManager.getInstance().doesClipboardHaveData(
ClipboardCommand.DRAWING_SURFACE,
ClipboardContentsHelper.getInstance())
|| (ClipboardManager.getInstance().doesClipboardHaveData(
ClipboardManager.COMMON_FORMAT, ClipboardContentsHelper
.getInstance()));
}
/**
* Checks if the selected IElements will allow a print
*
* @param cntxt
* the <code>IGlobalActionContext</code> holding the necessary
* information needed by this action handler
* @return boolean indicating the enablement of the print action
*/
private boolean canPrint() {
return true;
}
/**
* Checks if the selected IElements will allow a save. Save should only be
* enabled when no shapes are selected to avoid clutter on the context menu
* and if the editor is dirty.
*
* @param cntxt
* the <code>IGlobalActionContext</code> holding the necessary
* information needed by this action handler
* @return boolean indicating the enablement of the save action
*/
protected boolean canSave(IGlobalActionContext cntxt) {
IWorkbenchPart part = cntxt.getActivePart();
if (part instanceof IDiagramWorkbenchPart
&& part instanceof IEditorPart && ((IEditorPart) part).isDirty()) {
return true;
}
return false;
}
/*
* (non-Javadoc)
*
* @see org.eclipse.gmf.runtime.common.ui.services.action.global.IGlobalActionHandler#canHandle(org.eclipse.gmf.runtime.common.ui.services.action.global.IGlobalActionContext)
*/
public boolean canHandle(final IGlobalActionContext cntxt) {
boolean result = false;
/* Check if the active part is a IDiagramWorkbenchPart */
IWorkbenchPart part = cntxt.getActivePart();
if (!(part instanceof IDiagramWorkbenchPart)) {
return false;
}
/* Check if the selection is a structured selection */
if (!(cntxt.getSelection() instanceof IStructuredSelection)) {
return result;
}
/* Check the action id */
String actionId = cntxt.getActionId();
if (actionId.equals(GlobalActionId.DELETE)) {
result = canDelete(cntxt);
} else if (actionId.equals(GlobalActionId.COPY)) {
result = canCopy(cntxt);
} else if (actionId.equals(GlobalActionId.CUT)) {
result = canCut(cntxt);
} else if (actionId.equals(GlobalActionId.OPEN)) {
result = canOpen(cntxt);
} else if (actionId.equals(GlobalActionId.PASTE)) {
result = canPaste(cntxt);
} else if (actionId.equals(GlobalActionId.PRINT)) {
result = canPrint();
} else if (actionId.equals(GlobalActionId.SAVE)) {
result = canSave(cntxt);
} else if (actionId.equals(GlobalActionId.PROPERTIES)) {
result = true;
}
return result;
}
/**
* Select the newly added shape view by default.
*
* @param viewer
* the viewer owning the edit parts to be selected
* @param objects
* the collection of object from which to extract the
* <code>EditPart</code> to select
*/
protected void selectAddedObject(EditPartViewer viewer, Collection objects) {
final List editparts = new ArrayList();
for (Iterator i = objects.iterator(); i.hasNext();) {
EditPart editPart = getEditPart(viewer, i.next());
if (editPart != null && editPart.isSelectable())
editparts.add(editPart);
}
if (!editparts.isEmpty()) {
viewer.setSelection(new StructuredSelection(editparts));
viewer.reveal((EditPart) editparts.get(0));
}
}
private EditPart getEditPart(EditPartViewer viewer, Object object) {
if (object instanceof View) {
return (EditPart) viewer.getEditPartRegistry().get(object);
} else if (object instanceof IAdaptable) {
return (EditPart) viewer.getEditPartRegistry().get(
((IAdaptable) object).getAdapter(View.class));
}
return null;
}
/**
* Retrieve the <code>IMapMode</code> object from the
* <code>DiagramRootEditPart</code>
*
* @param cntxt
* the <code>IGlobalActionContext</code> holding the necessary
* information needed by this action handler
* @return <code>IMapMode</code> object that performs coordinate mapping
* from device to logical. Returns null if the context isn't valid.
*/
protected IMapMode getMapMode(IGlobalActionContext cntxt) {
IWorkbenchPart part = cntxt.getActivePart();
if (!(part instanceof IDiagramWorkbenchPart)) {
RootEditPart rootEP = ((IDiagramWorkbenchPart) part)
.getDiagramGraphicalViewer().getRootEditPart();
if (rootEP instanceof DiagramRootEditPart) {
return ((DiagramRootEditPart) part).getMapMode();
}
}
return MapModeUtil.getMapMode();
}
/**
* Gets the transactional editing domain associated with the workbench
* <code>part</code>.
*
* @param part
* the diagram workbench part
* @return the editing domain, or <code>null</code> if there is none.
*/
private TransactionalEditingDomain getEditingDomain(
IDiagramWorkbenchPart part) {
TransactionalEditingDomain result = null;
IEditingDomainProvider provider = (IEditingDomainProvider) part
.getAdapter(IEditingDomainProvider.class);
if (provider != null) {
EditingDomain domain = provider.getEditingDomain();
if (domain != null && domain instanceof TransactionalEditingDomain) {
result = (TransactionalEditingDomain) domain;
} }
return result;
}
}