blob: eed6250d02e30a5ba4a9d4f41790b3c7da145887 [file] [log] [blame]
/******************************************************************************
* Copyright (c) 2000, 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
****************************************************************************/
package org.eclipse.gmf.runtime.diagram.ui.internal.parts;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import org.eclipse.draw2d.IFigure;
import org.eclipse.draw2d.PositionConstants;
import org.eclipse.draw2d.Viewport;
import org.eclipse.draw2d.geometry.Point;
import org.eclipse.draw2d.geometry.Rectangle;
import org.eclipse.gef.EditPart;
import org.eclipse.gef.GraphicalEditPart;
import org.eclipse.gef.GraphicalViewer;
import org.eclipse.gef.RootEditPart;
import org.eclipse.gef.ui.parts.GraphicalViewerKeyHandler;
import org.eclipse.gmf.runtime.diagram.ui.editparts.DiagramEditPart;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.KeyEvent;
/**
* @author melaasar
* <p>
* Code taken from Eclipse reference bugzilla #98820
*
*/
public class DiagramGraphicalViewerKeyHandler
extends GraphicalViewerKeyHandler {
/**
* @param viewer
*/
public DiagramGraphicalViewerKeyHandler(GraphicalViewer viewer) {
super(viewer);
}
/**
* @see org.eclipse.gef.KeyHandler#keyPressed(org.eclipse.swt.events.KeyEvent)
*/
public boolean keyPressed(KeyEvent event) {
switch (event.keyCode) {
case SWT.HOME :
if ((event.stateMask & SWT.ALT) != 0) {
if (navigateEndSibling(event, PositionConstants.WEST))
return true;
} else {
if (navigateEndSibling(event, PositionConstants.NORTH))
return true;
}
break;
case SWT.END :
if ((event.stateMask & SWT.ALT) != 0) {
if (navigateEndSibling(event, PositionConstants.EAST))
return true;
} else {
if (navigateEndSibling(event, PositionConstants.SOUTH))
return true;
}
break;
case SWT.PAGE_UP :
if ((event.stateMask & SWT.ALT) != 0) {
if (navigatePageSibling(event, PositionConstants.WEST))
return true;
} else {
if (navigatePageSibling(event, PositionConstants.NORTH))
return true;
}
break;
case SWT.PAGE_DOWN :
if ((event.stateMask & SWT.ALT) != 0) {
if (navigatePageSibling(event, PositionConstants.EAST))
return true;
} else {
if (navigatePageSibling(event, PositionConstants.SOUTH))
return true;
}
break;
case SWT.TAB:
if ((event.stateMask & SWT.SHIFT) != 0) {
if (navigateNextHorizontalSibling(isViewerMirrored() ? PositionConstants.EAST
: PositionConstants.WEST)) {
return true;
}
} else {
if (navigateNextHorizontalSibling(isViewerMirrored() ? PositionConstants.WEST
: PositionConstants.EAST)) {
return true;
}
}
break;
}
return super.keyPressed(event);
}
/**
* @return
*/
protected GraphicalEditPart getFocusPart() {
return (GraphicalEditPart) getViewer().getFocusEditPart();
}
/**
* @param part
* @param event
*/
protected void navigateToPart(EditPart part, KeyEvent event) {
if (part == null)
return;
if (!part.isSelectable()) {
getViewer().deselectAll();
getViewer().setFocus(part);
} else 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);
}
/**
* @return
*/
protected List getPartNavigationSiblings() {
EditPart epParent = findParent(getFocusPart());
if (epParent != null)
return epParent.getChildren();
else
return null;
}
/**
* @param figure
* @return
*/
protected Point getFigureInterestingPoint(IFigure figure) {
return figure.getBounds().getCenter();
}
protected Viewport findViewport(GraphicalEditPart part) {
if (part == null)
return null;
IFigure figure = null;
Viewport port = null;
do {
if (figure == null)
figure = part.getContentPane();
else
figure = figure.getParent();
if (figure instanceof Viewport) {
port = (Viewport) figure;
break;
}
} while (figure != null);
return port;
}
/**
* @param event
* @param direction
* @param page
* @param list
* @return
*/
protected boolean navigatePageSibling(KeyEvent event, int direction) {
GraphicalEditPart epStart = getFocusPart();
IFigure figure = epStart.getFigure();
Point pStart = getFigureInterestingPoint(figure);
figure.translateToAbsolute(pStart);
GraphicalEditPart epParent = (GraphicalEditPart) findParent(epStart);
Viewport viewport = findViewport(epParent);
Rectangle bounds =
(viewport != null)
? new Rectangle(viewport.getBounds())
: epParent.getFigure().getClientArea();
int pageDistance = 0;
switch (direction) {
case PositionConstants.NORTH :
case PositionConstants.SOUTH :
pageDistance = bounds.height;
break;
case PositionConstants.EAST :
case PositionConstants.WEST :
pageDistance = bounds.width;
break;
}
if (epStart instanceof DiagramEditPart) {
Point location = viewport.getViewLocation().getCopy();
switch (direction) {
case PositionConstants.NORTH :
location.translate(0, -pageDistance);
break;
case PositionConstants.SOUTH :
location.translate(0, pageDistance);
break;
case PositionConstants.EAST :
location.translate(pageDistance, 0);
break;
case PositionConstants.WEST :
location.translate(-pageDistance, 0);
break;
}
viewport.setViewLocation(location);
}
List<GraphicalEditPart> editParts =
findPageSibling(
getPartNavigationSiblings(),
pStart,
pageDistance,
direction,
epStart);
if (editParts.isEmpty())
return false;
if ((event.stateMask & SWT.SHIFT) != 0) {
Iterator<GraphicalEditPart> parts = editParts.iterator();
while (parts.hasNext())
navigateToPart(parts.next(), event);
} else {
EditPart part = editParts.get(editParts.size() - 1);
navigateToPart(part, event);
}
return true;
}
/**
* @param event
* @param direction
* @param list
* @return
*/
protected boolean navigateEndSibling(KeyEvent event, int direction) {
GraphicalEditPart epStart = getFocusPart();
// go to top-left or bottom-right corner if nothing is selected
if (epStart instanceof DiagramEditPart) {
Viewport viewport = findViewport(epStart);
switch (direction) {
case PositionConstants.NORTH:
case PositionConstants.WEST:
viewport.setViewLocation(viewport.getHorizontalRangeModel()
.getMinimum(), viewport.getVerticalRangeModel()
.getMinimum());
break;
case PositionConstants.SOUTH:
case PositionConstants.EAST:
viewport.setViewLocation(viewport.getHorizontalRangeModel()
.getMaximum(), viewport.getVerticalRangeModel()
.getMaximum());
break;
}
}
IFigure figure = epStart.getFigure();
Point pStart = getFigureInterestingPoint(figure);
figure.translateToAbsolute(pStart);
List<GraphicalEditPart> editParts =
findEndSibling(
getPartNavigationSiblings(),
pStart,
direction,
epStart);
if (editParts.isEmpty())
return false;
if ((event.stateMask & SWT.SHIFT) != 0) {
Iterator<GraphicalEditPart> parts = editParts.iterator();
while (parts.hasNext())
navigateToPart(parts.next(), event);
} else {
EditPart part = editParts.get(editParts.size() - 1);
navigateToPart(part, event);
}
return true;
}
/**
* @param siblings
* @param pStart
* @param minDistance
* @param direction
* @param exclude
* @return
*/
private List<GraphicalEditPart> findPageSibling(
List siblings,
Point pStart,
int pageDistance,
int direction,
EditPart exclude) {
GraphicalEditPart epCurrent;
GraphicalEditPart epFinal = null;
GraphicalEditPart epFurthest = null; // in case there is not a full pageDistance left to scroll
List<GraphicalEditPart> selection = new ArrayList<GraphicalEditPart>();
IFigure figure;
Point pCurrent;
int distance = Integer.MAX_VALUE;
int furthestDistance = 0;
Iterator iter = siblings.iterator();
while (iter.hasNext()) {
epCurrent = (GraphicalEditPart) iter.next();
if (epCurrent == exclude
|| !epCurrent.getFigure().isVisible()
|| epCurrent.getFigure().getBounds().isEmpty())
continue;
figure = epCurrent.getFigure();
pCurrent = getFigureInterestingPoint(figure);
figure.translateToAbsolute(pCurrent);
if (!isInDirection(direction, pStart, pCurrent))
continue;
int d = pCurrent.getDistanceOrthogonal(pStart);
if (d >= pageDistance) {
selection.add(epCurrent);
if (d < distance) {
distance = d;
epFinal = epCurrent;
}
}
if (d > furthestDistance) {
epFurthest = epCurrent;
}
if (epFinal != null) {
selection.remove(epFinal);
selection.add(epFinal);
}
}
if (selection.isEmpty() && epFurthest != null) {
return Collections.singletonList(epFurthest);
}
return selection;
}
/**
* @param siblings
* @param pStart
* @param direction
* @param exclude
* @return
*/
private List<GraphicalEditPart> findEndSibling(
List siblings,
Point pStart,
int direction,
EditPart exclude) {
GraphicalEditPart epCurrent;
GraphicalEditPart epFinal = null;
List<GraphicalEditPart> selection = new ArrayList<GraphicalEditPart>();
IFigure figure;
Point pCurrent;
int distance = 0;
Iterator iter = siblings.iterator();
while (iter.hasNext()) {
epCurrent = (GraphicalEditPart) iter.next();
if (epCurrent == exclude
|| !epCurrent.getFigure().isVisible()
|| epCurrent.getFigure().getBounds().isEmpty())
continue;
figure = epCurrent.getFigure();
pCurrent = getFigureInterestingPoint(figure);
figure.translateToAbsolute(pCurrent);
if (!isInDirection(direction, pStart, pCurrent))
continue;
selection.add(epCurrent);
int d = pCurrent.getDistanceOrthogonal(pStart);
if (d > distance) {
distance = d;
epFinal = epCurrent;
}
}
if (epFinal != null) {
selection.remove(epFinal);
selection.add(epFinal);
}
return selection;
}
/**
* @param child
* @return EditPart
*/
private EditPart findParent(EditPart child) {
//check to see if we are not looking for a parent on RootEditPart,
//as it does not have a parent.
if (child instanceof RootEditPart)
return child;
else
return child.getParent(); //any other EditPart
}
/**
* Traverses to the closest EditPart in the given list that is also in the
* given direction (EAST or WEST). The x-location alone is used to determine
* the closest sibling. If the direction is EAST and there are no EditParts
* to the EAST then the farthest WEST EditPart is returned (and vice versa).
* This allows the user to cycle through all the EditParts using the TAB
* key.
*
* @param direction
* the direction in which to navigate (either
* PositionConstants.WEST or PositionConstants.EAST)
* @return true if a sibling was found to navigate to; false otherwise.
*/
private boolean navigateNextHorizontalSibling(int direction) {
GraphicalEditPart epStart = getFocusEditPart();
EditPart next = null;
if (epStart instanceof DiagramEditPart) {
next = findClosestHorizontalSibling(epStart.getChildren(),
new Point(0, 0), PositionConstants.EAST, null);
} else {
IFigure figure = epStart.getFigure();
Point pStart = figure.getBounds().getCenter();
figure.translateToAbsolute(pStart);
next = findClosestHorizontalSibling(getNavigationSiblings(),
pStart, direction, epStart);
}
if (next == null)
return false;
getViewer().select(next);
getViewer().reveal(next);
return true;
}
/**
* Given an absolute point (pStart) and a list of EditParts, this method
* finds the closest EditPart (except for the one to be excluded) in the
* given direction (EAST or WEST). The x-location is used to determine the
* closest sibling. Also, if going EAST and there is vertically aligned
* figure underneath pStart, then that figure is the result. If going WEST
* and there is a figure above pStartm then that figure is the result.If the
* direction is EAST and there are no EditParts to the EAST and SOUTH then
* the farthest WEST EditPart is returned (and vice versa). This allows the
* user to cycle through all the EditParts using the TAB key.
*
* @param siblings
* List of sibling EditParts
* @param pStart
* The starting point (must be in absolute coordinates) from
* which the next sibling is to be found.
* @param direction
* PositionConstants.EAST or PositionConstants.WEST
* @param exclude
* The EditPart to be excluded from the search
*/
private GraphicalEditPart findClosestHorizontalSibling(List siblings,
Point pStart, int direction, EditPart exclude) {
GraphicalEditPart epCurrent;
GraphicalEditPart epFinal = null;
GraphicalEditPart epCycle = null; // in case there are no more shapes in this direction
GraphicalEditPart epCycleVertical = null; // in case there are no more shapes in EAST or WEST direction
IFigure figure;
Point pCurrent;
int distanceX = Integer.MAX_VALUE;
int distanceY = Integer.MAX_VALUE;
int finalY = 0;
boolean goVertical = false;
int xCycle = direction == PositionConstants.EAST ? Integer.MAX_VALUE : 0;
int yCycle = xCycle;
int yCycleVertical = xCycle;
Iterator iter = siblings.iterator();
while (iter.hasNext()) {
epCurrent = (GraphicalEditPart) iter.next();
if (epCurrent == exclude || !epCurrent.isSelectable())
continue;
figure = epCurrent.getFigure();
pCurrent = figure.getBounds().getCenter();
figure.translateToAbsolute(pCurrent);
int dx = pCurrent.x - pStart.x;
if (!goVertical &&
((direction == PositionConstants.EAST && dx > 0)
|| (direction == PositionConstants.WEST && dx < 0))) {
int abs_dx = Math.abs(dx);
if (abs_dx < distanceX) {
distanceX = abs_dx;
finalY = pCurrent.y;
epFinal = epCurrent;
} else if (abs_dx == distanceX) {
// There are vertically aligned figures that could be horizontally closest to the current figure.
// If we go east, choose the top one, if we go west, choose the bottom one.
// finalY is always assigned in this case
if ((direction == PositionConstants.EAST && pCurrent.y < finalY) ||
(direction == PositionConstants.WEST && pCurrent.y > finalY)) {
finalY = pCurrent.y;
epFinal = epCurrent;
}
}
}
if (dx == 0) {
// Once we find a figure that is vertically aligned with the current, we have to go vertically
// (to this or closest figure vertically aligned) since there will be no other chance to traverse it.
// The exception: we reached the far east end while going east, or far west end while going west.
int dy = pCurrent.y - pStart.y;
if ((direction == PositionConstants.EAST && dy > 0)
|| (direction == PositionConstants.WEST && dy < 0)) {
goVertical = true;
int abs_dy = Math.abs(dy);
if (abs_dy < distanceY) {
distanceY = abs_dy;
epFinal = epCurrent;
}
}
}
// Consider the case when we need to go east (south) or west (north) and the current figure is on the
// far east (south) or west (north) end, respectively.
if (epFinal == null) {
if (dx == 0) {
// Case when the figure is at the end and there is one or more vertically aligned figure.
// This will be the result only if there are no figures to the left or right.
if (direction == PositionConstants.EAST && pCurrent.y < yCycleVertical) {
yCycleVertical = pCurrent.y;
epCycleVertical = epCurrent;
} else if (direction == PositionConstants.WEST && pCurrent.y > yCycleVertical) {
yCycleVertical = pCurrent.y;
epCycleVertical = epCurrent;
}
} else {
boolean assign = false;
if (direction == PositionConstants.EAST) {
if (pCurrent.x < xCycle) {
assign = true;
} else if (pCurrent.x == xCycle) {
// Current figure is vertically aligned with the possible result figure.
// Choose the top figure.
if (pCurrent.y < yCycle) {
assign = true;
}
}
} else if (direction == PositionConstants.WEST) {
if (pCurrent.x > xCycle) {
assign = true;
} else if (pCurrent.x == xCycle) {
// Current figure is vertically aligned with the possible result figure.
// Choose the top figure.
if (pCurrent.y > yCycle) {
assign = true;
}
}
}
if (assign) {
xCycle = pCurrent.x;
yCycle = pCurrent.y;
epCycle = epCurrent;
}
}
}
}
if (epFinal == null) {
if (epCycle == null) {
return epCycleVertical;
} else {
return epCycle;
}
}
return epFinal;
}
/**
* Returns true if the point in question is in the given direction from the
* starting point.
*
* @param direction
* the direction
* @param start
* the starting point
* @param point
* the point in question
* @return true if the point in question is in the given direction from the
* starting point; false otherwise
*/
private boolean isInDirection(int direction, Point start, Point point) {
switch (direction) {
case PositionConstants.WEST:
return start.x > point.x;
case PositionConstants.EAST:
return start.x < point.x;
case PositionConstants.NORTH:
return start.y > point.y;
case PositionConstants.SOUTH:
return start.y < point.y;
}
return false;
}
}