/******************************************************************************* * 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); } } }