/******************************************************************************* | |
* Copyright (c) 2005, 2012 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.bpel.ui; | |
import java.lang.ref.WeakReference; | |
import java.util.ArrayList; | |
import java.util.Iterator; | |
import java.util.List; | |
import java.util.Vector; | |
import org.eclipse.bpel.ui.editparts.CollapsableEditPart; | |
import org.eclipse.bpel.ui.editparts.ProcessEditPart; | |
import org.eclipse.draw2d.FigureCanvas; | |
import org.eclipse.draw2d.IFigure; | |
import org.eclipse.draw2d.PositionConstants; | |
import org.eclipse.draw2d.geometry.Point; | |
import org.eclipse.draw2d.geometry.Rectangle; | |
import org.eclipse.gef.ConnectionEditPart; | |
import org.eclipse.gef.EditPart; | |
import org.eclipse.gef.GraphicalEditPart; | |
import org.eclipse.gef.GraphicalViewer; | |
import org.eclipse.gef.KeyHandler; | |
import org.eclipse.gef.requests.DirectEditRequest; | |
import org.eclipse.swt.SWT; | |
import org.eclipse.swt.events.KeyEvent; | |
public class BPELGraphicalKeyHandler extends KeyHandler { | |
int counter; | |
/** | |
* When navigating through connections, a "Node" EditPart is used as a | |
* reference. | |
*/ | |
private WeakReference<GraphicalEditPart> cachedNode; | |
private GraphicalViewer viewer; | |
public BPELGraphicalKeyHandler(GraphicalViewer viewer) { | |
this.viewer = viewer; | |
} | |
private boolean acceptConnection(KeyEvent event) { | |
return event.character == '/' | |
|| event.character == '?' | |
|| event.character == '\\' | |
|| event.character == '\u001c' | |
|| event.character == '|'; | |
} | |
private boolean acceptIntoContainer(KeyEvent event) { | |
return ((event.stateMask & SWT.ALT) != 0) | |
&& (event.keyCode == SWT.ARROW_DOWN); | |
} | |
private boolean acceptLeaveConnection(KeyEvent event) { | |
int key = event.keyCode; | |
if (getFocus() instanceof ConnectionEditPart) | |
if ((key == SWT.ARROW_UP) | |
|| (key == SWT.ARROW_RIGHT) | |
|| (key == SWT.ARROW_DOWN) | |
|| (key == SWT.ARROW_LEFT)) | |
return true; | |
return false; | |
} | |
private boolean acceptLeaveContents(KeyEvent event) { | |
int key = event.keyCode; | |
return getFocus() == getViewer().getContents() | |
&& ((key == SWT.ARROW_UP) | |
|| (key == SWT.ARROW_RIGHT) | |
|| (key == SWT.ARROW_DOWN) | |
|| (key == SWT.ARROW_LEFT)); | |
} | |
private boolean acceptOutOf(KeyEvent event) { | |
return ((event.stateMask & SWT.ALT) != 0) | |
&& (event.keyCode == SWT.ARROW_UP); | |
} | |
private ConnectionEditPart findConnection( | |
GraphicalEditPart node, | |
ConnectionEditPart current, | |
boolean forward) { | |
List<ConnectionEditPart> connections = new ArrayList<ConnectionEditPart>(node.getSourceConnections()); | |
connections.addAll(node.getTargetConnections()); | |
if (connections.isEmpty()) | |
return null; | |
if (forward) | |
counter++; | |
else | |
counter--; | |
while (counter < 0) | |
counter += connections.size(); | |
counter %= connections.size(); | |
return connections.get( | |
counter % connections.size()); | |
} | |
/* | |
* pStart is a point in absolute coordinates. | |
*/ | |
private GraphicalEditPart findSibling( | |
List<GraphicalEditPart> siblings, | |
Point pStart, | |
int direction, | |
EditPart exclude) { | |
GraphicalEditPart epCurrent; | |
GraphicalEditPart epFinal = null; | |
IFigure figure; | |
Point pCurrent; | |
int distance = Integer.MAX_VALUE; | |
Iterator<GraphicalEditPart> iter = siblings.iterator(); | |
while (iter.hasNext()) { | |
epCurrent = iter.next(); | |
if (epCurrent == exclude || !epCurrent.isSelectable()) | |
continue; | |
figure = epCurrent.getFigure(); | |
pCurrent = getInterestingPoint(figure); | |
figure.translateToAbsolute(pCurrent); | |
if (pStart.getPosition(pCurrent) != direction) | |
continue; | |
int d = pCurrent.getDistanceOrthogonal(pStart); | |
if (d < distance) { | |
distance = d; | |
epFinal = epCurrent; | |
} | |
} | |
return epFinal; | |
} | |
Point getInterestingPoint(IFigure figure) { | |
return figure.getBounds().getCenter(); | |
} | |
/** | |
* Returns the cached node. It is possible that the node is not longer in | |
* the viewer but has not been garbage collected yet. | |
*/ | |
private GraphicalEditPart getCachedNode() { | |
if (cachedNode == null) | |
return null; | |
if (cachedNode.isEnqueued()) | |
return null; | |
return cachedNode.get(); | |
} | |
protected GraphicalEditPart getFocus() { | |
return (GraphicalEditPart) getViewer().getFocusEditPart(); | |
} | |
/** | |
* Returns the viewer on which this key handler was created. | |
* | |
* @return the viewer | |
*/ | |
protected GraphicalViewer getViewer() { | |
return viewer; | |
} | |
/** | |
* Extended to process key events described above. | |
* | |
* @see org.eclipse.gef.KeyHandler#keyPressed(org.eclipse.swt.events.KeyEvent) | |
*/ | |
@Override | |
public boolean keyPressed(KeyEvent event) { | |
if (event.character == ' ') { | |
processSelect(event); | |
return true; | |
} else if (acceptIntoContainer(event)) { | |
navigateIntoContainer(event); | |
return true; | |
} else if (acceptOutOf(event)) { | |
navigateOut(event); | |
return true; | |
} else if (acceptConnection(event)) { | |
navigateConnections(event); | |
return true; | |
} else if (acceptScroll(event)) { | |
scrollViewer(event); | |
return true; | |
} else if (acceptLeaveConnection(event)) { | |
navigateOutOfConnection(event); | |
return true; | |
} else if (acceptLeaveContents(event)) { | |
navigateIntoContainer(event); | |
return true; | |
} else if (event.character == '[') { | |
int found = -1; | |
List<GraphicalEditPart> list = getProcessNavigationList(); | |
for (int i = 0; i < list.size(); i++) { | |
if (getFocus() == list.get(i)) { | |
found = i; | |
break; | |
} | |
} | |
if (found >= 0) { | |
found--; | |
} | |
if (found >=0) { | |
EditPart navigateTo = list.get(found); | |
navigateTo(navigateTo, event); | |
} | |
} else if (event.character == ']') { | |
int found = -1; | |
List<GraphicalEditPart> list = getProcessNavigationList(); | |
for (int i = 0; i < list.size(); i++) { | |
if (getFocus() == list.get(i)) { | |
found = i; | |
break; | |
} | |
} | |
if (found >= 0) { | |
found++; | |
} | |
if (found < list.size()) { | |
EditPart navigateTo = list.get(found); | |
navigateTo(navigateTo, event); | |
} | |
} else if (event.character == '+' || event.character == '=') { | |
expand(event); | |
} else if (event.character == '-') { | |
collapse(event); | |
} | |
switch (event.keyCode) { | |
case SWT.ARROW_LEFT : | |
if (navigateNextSibling(event, PositionConstants.WEST)) | |
return true; | |
break; | |
case SWT.ARROW_RIGHT : | |
if (navigateNextSibling(event, PositionConstants.EAST)) | |
return true; | |
break; | |
case SWT.ARROW_UP : | |
if (navigateNextSibling(event, PositionConstants.NORTH)) | |
return true; | |
break; | |
case SWT.ARROW_DOWN : | |
if (navigateNextSibling(event, PositionConstants.SOUTH)) | |
return true; | |
break; | |
case SWT.HOME : | |
if (navigateJumpSibling(event, PositionConstants.WEST)) | |
return true; | |
break; | |
case SWT.END : | |
if (navigateJumpSibling(event, PositionConstants.EAST)) | |
return true; | |
break; | |
case SWT.PAGE_DOWN : | |
if (navigateJumpSibling(event, PositionConstants.SOUTH)) | |
return true; | |
break; | |
case SWT.PAGE_UP : | |
if (navigateJumpSibling(event, PositionConstants.NORTH)) | |
return true; | |
break; | |
case SWT.TAB : | |
return true; | |
case SWT.F2: | |
activateDirectEdit(event); | |
break; | |
} | |
return super.keyPressed(event); | |
} | |
private void navigateConnections(KeyEvent event) { | |
GraphicalEditPart focus = getFocus(); | |
ConnectionEditPart current = null; | |
GraphicalEditPart node = getCachedNode(); | |
if (focus instanceof ConnectionEditPart) { | |
current = (ConnectionEditPart) focus; | |
if (node == null | |
|| (node != current.getSource() && node != current.getTarget())) { | |
node = (GraphicalEditPart) current.getSource(); | |
counter = 0; | |
} | |
} else { | |
node = focus; | |
} | |
setCachedNode(node); | |
boolean forward = event.character == '/' || event.character == '?'; | |
ConnectionEditPart next = findConnection(node, current, forward); | |
navigateTo(next, event); | |
} | |
private void navigateIntoContainer(KeyEvent event) { | |
GraphicalEditPart focus = getFocus(); | |
List<GraphicalEditPart> childList = getNavigationChildren(focus); | |
Point tl = focus.getContentPane().getBounds().getTopLeft(); | |
int minimum = Integer.MAX_VALUE; | |
int current; | |
GraphicalEditPart closestPart = null; | |
for (int i = 0; i < childList.size(); i++) { | |
GraphicalEditPart ged = childList.get(i); | |
if (!ged.isSelectable()) | |
continue; | |
Rectangle childBounds = ged.getFigure().getBounds(); | |
current = (childBounds.x - tl.x) + (childBounds.y - tl.y); | |
if (current < minimum) { | |
minimum = current; | |
closestPart = ged; | |
} | |
} | |
if (closestPart != null) | |
navigateTo(closestPart, event); | |
} | |
private boolean navigateJumpSibling(KeyEvent event, int direction) { | |
// TODO: Implement navigateJumpSibling() (for PGUP, PGDN, HOME and END | |
// key events) | |
return false; | |
} | |
private boolean navigateNextSibling(KeyEvent event, int direction) { | |
return navigateNextSibling(event, direction, getNavigationSiblings()); | |
} | |
boolean navigateNextSibling(KeyEvent event, int direction, List<GraphicalEditPart> list) { | |
GraphicalEditPart epStart = getFocus(); | |
IFigure figure = epStart.getFigure(); | |
Point pStart = getInterestingPoint(figure); | |
figure.translateToAbsolute(pStart); | |
EditPart next = findSibling(list, pStart, direction, epStart); | |
if (next == null) | |
return false; | |
navigateTo(next, event); | |
return true; | |
} | |
private void navigateOut(KeyEvent event) { | |
EditPart parent = getNavigationParent(getFocus()); | |
if (getFocus() == null || getFocus() == getViewer().getContents() || getFocus().getParent() == getViewer().getContents()) | |
return; | |
navigateTo(parent, event); | |
} | |
private void navigateOutOfConnection(KeyEvent event) { | |
GraphicalEditPart cached = getCachedNode(); | |
ConnectionEditPart conn = (ConnectionEditPart) getFocus(); | |
if (cached != null && (cached == conn.getSource() || cached == conn.getTarget())) | |
navigateTo(cached, event); | |
else | |
navigateTo(conn.getSource(), event); | |
} | |
void navigateTo(EditPart part, KeyEvent event) { | |
if (part == null) | |
return; | |
if ((event.stateMask & SWT.SHIFT) != 0) { | |
getViewer().appendSelection(part); | |
getViewer().setFocus(part); | |
} else if ((event.stateMask & SWT.CONTROL) != 0) | |
getViewer().setFocus(part); | |
else | |
getViewer().select(part); | |
getViewer().reveal(part); | |
} | |
private void processSelect(KeyEvent event) { | |
EditPart part = getViewer().getFocusEditPart(); | |
if ((event.stateMask & SWT.CONTROL) != 0 | |
&& part.getSelected() != EditPart.SELECTED_NONE) | |
getViewer().deselect(part); | |
else | |
getViewer().appendSelection(part); | |
getViewer().setFocus(part); | |
} | |
private void expand(KeyEvent event) { | |
EditPart part = getViewer().getFocusEditPart(); | |
if (part instanceof CollapsableEditPart) { | |
((CollapsableEditPart)part).setCollapsed(false); | |
} | |
} | |
private void collapse(KeyEvent event) { | |
EditPart part = getViewer().getFocusEditPart(); | |
if (part instanceof CollapsableEditPart) { | |
((CollapsableEditPart)part).setCollapsed(true); | |
} | |
} | |
private void setCachedNode(GraphicalEditPart node) { | |
if (node == null) | |
cachedNode = null; | |
else | |
cachedNode = new WeakReference<GraphicalEditPart>(node); | |
} | |
/** | |
* handles StartNodeEditPart specific behavior | |
*/ | |
protected List<GraphicalEditPart> getNavigationChildren(EditPart part) { | |
// if (part instanceof StartNodeEditPart) { | |
// // return the process's children | |
// ProcessEditPart proc = (ProcessEditPart) part.getParent(); | |
// List list = proc.getChildren(); | |
// list.remove(part); | |
// return list; | |
// } | |
// else | |
return part.getChildren(); | |
} | |
protected List<GraphicalEditPart> getNavigationSiblings() { | |
EditPart focus = getFocus().getParent(); | |
return focus.getChildren(); | |
} | |
protected EditPart getNavigationParent(EditPart part) { | |
EditPart parent = part.getParent(); | |
if (parent instanceof ProcessEditPart) { | |
return null; | |
} | |
return parent; | |
} | |
protected void addChildren(EditPart part, List<GraphicalEditPart> list) { | |
List<GraphicalEditPart> children = part.getChildren(); | |
for (int i = 0; i < children.size(); i++) { | |
list.add(children.get(i)); | |
addChildren((children.get(i)), list); | |
} | |
return; | |
} | |
/* | |
* as a usability enhancement, we want to navigate to the next activity using the SHIFT left and right arrow, this | |
* allows the user to navigate to every element on the canvas easily | |
*/ | |
protected List<GraphicalEditPart> getProcessNavigationList() { | |
ProcessEditPart process = (ProcessEditPart)viewer.getContents(); | |
Vector<GraphicalEditPart> all = new Vector<GraphicalEditPart>(); | |
addChildren(process,all); | |
return all; | |
} | |
protected boolean activateDirectEdit(KeyEvent event) { | |
GraphicalEditPart currentPart = (GraphicalEditPart)viewer.getFocusEditPart(); | |
currentPart.performRequest(new DirectEditRequest()); | |
return true; | |
} | |
boolean acceptScroll(KeyEvent event) { | |
return ((event.stateMask & SWT.CTRL) != 0 && (event.stateMask & SWT.SHIFT) != 0 | |
&& (event.keyCode == SWT.ARROW_DOWN || event.keyCode == SWT.ARROW_LEFT | |
|| event.keyCode == SWT.ARROW_RIGHT || event.keyCode == SWT.ARROW_UP)); | |
} | |
void scrollViewer(KeyEvent event) { | |
if (!(getViewer().getControl() instanceof FigureCanvas)) | |
return; | |
FigureCanvas figCanvas = (FigureCanvas)getViewer().getControl(); | |
Point loc = figCanvas.getViewport().getViewLocation(); | |
Rectangle area = figCanvas.getViewport().getClientArea(Rectangle.SINGLETON).scale(.1); | |
switch (event.keyCode) { | |
case SWT.ARROW_DOWN: | |
figCanvas.scrollToY(loc.y + area.height); | |
break; | |
case SWT.ARROW_UP: | |
figCanvas.scrollToY(loc.y - area.height); | |
break; | |
case SWT.ARROW_LEFT: | |
figCanvas.scrollToX(loc.x - area.width); | |
break; | |
case SWT.ARROW_RIGHT: | |
figCanvas.scrollToX(loc.x + area.width); | |
} | |
} | |
} |