| /******************************************************************************* |
| * Copyright (c) 2000, 2010 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.gef.tools; |
| |
| import java.lang.ref.WeakReference; |
| import java.util.List; |
| |
| import org.eclipse.swt.SWT; |
| import org.eclipse.swt.dnd.DragSourceEvent; |
| import org.eclipse.swt.events.KeyEvent; |
| import org.eclipse.swt.events.MouseEvent; |
| import org.eclipse.swt.widgets.Display; |
| import org.eclipse.swt.widgets.Event; |
| |
| import org.eclipse.draw2d.geometry.Point; |
| |
| import org.eclipse.gef.AccessibleHandleProvider; |
| import org.eclipse.gef.DragTracker; |
| import org.eclipse.gef.EditDomain; |
| import org.eclipse.gef.EditPart; |
| import org.eclipse.gef.EditPartViewer; |
| import org.eclipse.gef.GraphicalViewer; |
| import org.eclipse.gef.Handle; |
| import org.eclipse.gef.KeyHandler; |
| import org.eclipse.gef.Request; |
| import org.eclipse.gef.RequestConstants; |
| import org.eclipse.gef.requests.LocationRequest; |
| import org.eclipse.gef.requests.SelectionRequest; |
| |
| /** |
| * Tool to select and manipulate figures. A selection tool is in one of three |
| * states, e.g., background selection, figure selection, handle manipulation. |
| * The different states are handled by different child tools. |
| */ |
| public class SelectionTool extends TargetingTool { |
| |
| private static final int FLAG_HOVER_FEEDBACK = TargetingTool.MAX_FLAG << 1; |
| /** Max flag */ |
| protected static final int MAX_FLAG = FLAG_HOVER_FEEDBACK; |
| |
| /** Traverse handle state */ |
| protected static final int STATE_TRAVERSE_HANDLE = TargetingTool.MAX_STATE << 1; |
| /** Max state */ |
| protected static final int MAX_STATE = STATE_TRAVERSE_HANDLE; |
| |
| private int handleIndex; |
| private DragTracker dragTracker; |
| private LocationRequest hoverRequest; |
| |
| private WeakReference cachedHandlePart; |
| |
| /** |
| * Default constructor. |
| */ |
| public SelectionTool() { |
| } |
| |
| private boolean acceptTraverseHandle(KeyEvent e) { |
| return (e.character == '.' || e.character == '>') |
| && isInState(STATE_INITIAL | STATE_ACCESSIBLE_DRAG |
| | STATE_ACCESSIBLE_DRAG_IN_PROGRESS) |
| && ((e.stateMask & (SWT.ALT | SWT.CONTROL)) == 0); |
| } |
| |
| /** |
| * Creates the hover request (a {@link LocationRequest}) and sets its type |
| * to {@link RequestConstants#REQ_SELECTION_HOVER}. |
| */ |
| protected void createHoverRequest() { |
| hoverRequest = new LocationRequest(); |
| hoverRequest.setType(RequestConstants.REQ_SELECTION_HOVER); |
| } |
| |
| /** |
| * Creates a {@link SelectionRequest} for the target request. |
| * |
| * @see TargetingTool#createTargetRequest() |
| */ |
| protected Request createTargetRequest() { |
| SelectionRequest request = new SelectionRequest(); |
| request.setType(getCommandName()); |
| return request; |
| } |
| |
| /** |
| * Deactivates the tool. This method is called whenever the user switches to |
| * another tool. Use this method to do some clean-up when the tool is |
| * switched. Sets the drag tracker to <code>null</code>. |
| */ |
| public void deactivate() { |
| setDragTracker(null); // deactivates the current drag tracker |
| super.deactivate(); |
| } |
| |
| /** |
| * Erases the hover feedback by calling |
| * {@link EditPart#eraseTargetFeedback(Request)}. |
| */ |
| protected void eraseHoverFeedback() { |
| if (getTargetEditPart() == null) |
| return; |
| if (getTargetHoverRequest() == null) |
| return; |
| getTargetEditPart().eraseTargetFeedback(getTargetHoverRequest()); |
| } |
| |
| /** |
| * @see AbstractTool#getCommandName() |
| */ |
| protected String getCommandName() { |
| return REQ_SELECTION; |
| } |
| |
| /** |
| * @see AbstractTool#getDebugName() |
| */ |
| protected String getDebugName() { |
| return "Selection Tool";//$NON-NLS-1$ |
| } |
| |
| /** |
| * Returns the current drag tracker. |
| * |
| * @return the drag tracker |
| */ |
| protected DragTracker getDragTracker() { |
| return dragTracker; |
| } |
| |
| private EditPart getLastHandleProvider() { |
| if (cachedHandlePart == null) |
| return null; |
| EditPart part = (EditPart) cachedHandlePart.get(); |
| if (cachedHandlePart.isEnqueued()) |
| return null; |
| return part; |
| } |
| |
| /** |
| * Returns a new Conditional that evaluates to <code>true</code> if the |
| * queried edit part's {@link EditPart#isSelectable()} method returns |
| * <code>true</code>. |
| * |
| * @see TargetingTool#getTargetingConditional() |
| */ |
| protected EditPartViewer.Conditional getTargetingConditional() { |
| return new EditPartViewer.Conditional() { |
| public boolean evaluate(EditPart editpart) { |
| EditPart targetEditPart = editpart |
| .getTargetEditPart(getTargetRequest()); |
| return targetEditPart != null && targetEditPart.isSelectable(); |
| } |
| }; |
| } |
| |
| /** |
| * Returns the target hover request. If <code>null</code>, it will be |
| * created via {@link #createHoverRequest()}. |
| * |
| * @return the hover request |
| */ |
| protected Request getTargetHoverRequest() { |
| if (hoverRequest == null) |
| createHoverRequest(); |
| return hoverRequest; |
| } |
| |
| /** |
| * If there is a {@link Handle} under the mouse, this method sets the drag |
| * tracker returned from the handle. If there's an {@link EditPart} under |
| * the mouse, this method sets the drag tracker returned from the edit part. |
| * |
| * @see AbstractTool#handleButtonDown(int) |
| */ |
| protected boolean handleButtonDown(int button) { |
| if (!stateTransition(STATE_INITIAL, STATE_DRAG)) { |
| resetHover(); |
| return true; |
| } |
| resetHover(); |
| EditPartViewer viewer = getCurrentViewer(); |
| Point p = getLocation(); |
| |
| if (getDragTracker() != null) |
| getDragTracker().deactivate(); |
| |
| if (viewer instanceof GraphicalViewer) { |
| Handle handle = ((GraphicalViewer) viewer).findHandleAt(p); |
| if (handle != null) { |
| setDragTracker(handle.getDragTracker()); |
| return true; |
| } |
| } |
| updateTargetRequest(); |
| ((SelectionRequest) getTargetRequest()).setLastButtonPressed(button); |
| updateTargetUnderMouse(); |
| EditPart editpart = getTargetEditPart(); |
| if (editpart != null) { |
| setDragTracker(editpart.getDragTracker(getTargetRequest())); |
| lockTargetEditPart(editpart); |
| return true; |
| } |
| return false; |
| } |
| |
| /** |
| * Resets this tool when the last button is released. |
| * |
| * @see AbstractTool#handleButtonUp(int) |
| */ |
| protected boolean handleButtonUp(int button) { |
| if (getCurrentInput().isAnyButtonDown()) |
| return false; |
| ((SelectionRequest) getTargetRequest()).setLastButtonPressed(0); |
| setDragTracker(null); |
| setState(STATE_INITIAL); |
| unlockTargetEditPart(); |
| return true; |
| } |
| |
| /** |
| * @see AbstractTool#handleCommandStackChanged() |
| */ |
| protected boolean handleCommandStackChanged() { |
| if (getDragTracker() == null) |
| return super.handleCommandStackChanged(); |
| return false; |
| } |
| |
| /** |
| * Sets the drag tracker to <code>null</code> and goes into the initial |
| * state when focus is lost. |
| * |
| * @see AbstractTool#handleFocusLost() |
| */ |
| protected boolean handleFocusLost() { |
| if (isInState(STATE_ACCESSIBLE_DRAG | STATE_ACCESSIBLE_DRAG_IN_PROGRESS |
| | STATE_DRAG | STATE_DRAG_IN_PROGRESS)) { |
| if (getDragTracker() != null) |
| setDragTracker(null); |
| setState(STATE_INITIAL); |
| return true; |
| } |
| return false; |
| } |
| |
| /** |
| * Called when the mouse hovers. Calls {@link #showHoverFeedback()}. |
| * |
| * @see AbstractTool#handleHover() |
| */ |
| protected boolean handleHover() { |
| setHoverActive(true); |
| showHoverFeedback(); |
| return true; |
| } |
| |
| /** |
| * Called when the mouse hover stops (i.e. the mouse moves or a button is |
| * clicked). Calls {@link #eraseHoverFeedback()}. |
| * |
| * @see TargetingTool#handleHoverStop() |
| */ |
| protected boolean handleHoverStop() { |
| eraseHoverFeedback(); |
| return true; |
| } |
| |
| /** |
| * Processes key down events. Specifically, arrow keys for moving edit |
| * parts, the ESC key for aborting a drag, the period '.' key for traversing |
| * handles, and the ENTER key for committing a drag. If none of these keys |
| * were pressed and the current viewer has a {@link KeyHandler}, it calls |
| * {@link KeyHandler#keyPressed(KeyEvent)}. |
| * |
| * @see AbstractTool#handleKeyDown(KeyEvent) |
| */ |
| protected boolean handleKeyDown(KeyEvent e) { |
| resetHover(); |
| |
| if (acceptArrowKey(e)) |
| if (stateTransition(STATE_ACCESSIBLE_DRAG, |
| STATE_ACCESSIBLE_DRAG_IN_PROGRESS)) |
| return true; |
| |
| if (acceptAbort(e)) { |
| if (getDragTracker() != null) |
| setDragTracker(null); |
| if (isInState(STATE_TRAVERSE_HANDLE | STATE_ACCESSIBLE_DRAG |
| | STATE_ACCESSIBLE_DRAG_IN_PROGRESS)) |
| placeMouseInViewer(getStartLocation().getTranslated(6, 6)); |
| setState(STATE_INITIAL); |
| setLastHandleProvider(null); |
| return true; |
| } |
| |
| if (acceptTraverseHandle(e)) { |
| if (isInState(STATE_ACCESSIBLE_DRAG_IN_PROGRESS)) |
| if (getDragTracker() != null) |
| getDragTracker().commitDrag(); |
| if (isInState(STATE_ACCESSIBLE_DRAG |
| | STATE_ACCESSIBLE_DRAG_IN_PROGRESS)) { |
| setDragTracker(null); |
| getCurrentViewer().flush(); |
| } |
| if (!handleTraverseHandle(e)) |
| setState(STATE_INITIAL); |
| return true; |
| } |
| |
| if (acceptDragCommit(e)) { |
| if (getDragTracker() != null) |
| getDragTracker().commitDrag(); |
| setDragTracker(null); |
| setState(STATE_INITIAL); |
| handleIndex--; |
| placeMouseInViewer(getLocation().getTranslated(6, 6)); |
| return true; |
| } |
| |
| if (isInState(STATE_INITIAL)) { |
| if (getCurrentViewer().getKeyHandler() != null) |
| return getCurrentViewer().getKeyHandler().keyPressed(e); |
| } |
| |
| return false; |
| } |
| |
| /** |
| * If in the initial state and the viewer has a {@link KeyHandler}, calls |
| * {@link KeyHandler#keyReleased(KeyEvent)} sending it the given key event. |
| * |
| * @see AbstractTool#handleKeyUp(KeyEvent) |
| */ |
| protected boolean handleKeyUp(KeyEvent e) { |
| if (isInState(STATE_INITIAL) |
| && getCurrentViewer().getKeyHandler() != null |
| && getCurrentViewer().getKeyHandler().keyReleased(e)) |
| return true; |
| |
| return false; |
| } |
| |
| /** |
| * If in the initial state, updates the request and the mouse target and |
| * asks to show target feedback. If in the traverse handle state, finds the |
| * next handle, moves the mouse cursor to that handle, and gets a drag |
| * tracker from the handle. |
| * |
| * @see AbstractTool#handleMove() |
| */ |
| protected boolean handleMove() { |
| if (stateTransition(STATE_ACCESSIBLE_DRAG, STATE_INITIAL)) |
| setDragTracker(null); |
| if (isInState(STATE_INITIAL)) { |
| updateTargetRequest(); |
| updateTargetUnderMouse(); |
| showTargetFeedback(); |
| return true; |
| } else if (isInState(STATE_TRAVERSE_HANDLE)) { |
| EditPartViewer viewer = getCurrentViewer(); |
| if (viewer instanceof GraphicalViewer) { |
| Handle handle = ((GraphicalViewer) viewer) |
| .findHandleAt(getLocation()); |
| if (handle != null) { |
| setState(STATE_ACCESSIBLE_DRAG); |
| setStartLocation(getLocation()); |
| setDragTracker(handle.getDragTracker()); |
| return true; |
| } else { |
| setState(STATE_INITIAL); |
| } |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * If there's a drag tracker, calls handleNativeDragFinished() on the drag |
| * tracker and then sets the drag tracker to <code>null</code>. |
| * |
| * @see AbstractTool#handleNativeDragFinished(DragSourceEvent) |
| */ |
| public boolean handleNativeDragFinished(DragSourceEvent event) { |
| if (getDragTracker() != null) |
| getDragTracker().nativeDragFinished(event, getCurrentViewer()); |
| setDragTracker(null); |
| unlockTargetEditPart(); |
| return true; |
| } |
| |
| /** |
| * If there's a drag tracker, calls nativeDragStarted() on the drag tracker. |
| * |
| * @see AbstractTool#handleNativeDragStarted(DragSourceEvent) |
| */ |
| public boolean handleNativeDragStarted(DragSourceEvent event) { |
| if (getDragTracker() != null) |
| getDragTracker().nativeDragStarted(event, getCurrentViewer()); |
| setState(STATE_INITIAL); |
| return true; |
| } |
| |
| private boolean handleTraverseHandle(KeyEvent e) { |
| EditPart focus = getCurrentViewer().getFocusEditPart(); |
| if (focus.getSelected() == EditPart.SELECTED_NONE) |
| return false; |
| getCurrentViewer().reveal(focus); |
| |
| AccessibleHandleProvider provider; |
| provider = (AccessibleHandleProvider) focus |
| .getAdapter(AccessibleHandleProvider.class); |
| if (provider == null |
| || provider.getAccessibleHandleLocations().isEmpty()) |
| return false; |
| |
| /* |
| * At this point, a handle provider with 1 or more handles has been |
| * obtained |
| */ |
| setState(STATE_TRAVERSE_HANDLE); |
| List locations = provider.getAccessibleHandleLocations(); |
| // Goto next index, wrapping if necessary |
| if (e.character == '.') |
| handleIndex = (++handleIndex) % locations.size(); |
| else |
| handleIndex = (--handleIndex + locations.size()) % locations.size(); |
| if (getLastHandleProvider() != focus) { |
| handleIndex = 0; |
| setLastHandleProvider(focus); |
| } |
| |
| Point loc = (Point) locations.get(handleIndex); |
| Point current = new Point(getCurrentViewer().getControl().toControl( |
| Display.getCurrent().getCursorLocation())); |
| |
| if (current.equals(loc)) { |
| // The cursor is already at the location that it is to be moved to. |
| // So, we |
| // move to the next handle instead. If there are no more handles, |
| // then we |
| // cancel the drag. |
| if (locations.size() > 1) |
| if (e.character == '.') |
| handleIndex = (++handleIndex) % locations.size(); |
| else |
| handleIndex = (--handleIndex + locations.size()) |
| % locations.size(); |
| else { |
| placeMouseInViewer(loc.getTranslated(6, 6)); |
| return false; |
| } |
| } |
| placeMouseInViewer((Point) locations.get(handleIndex)); |
| return true; |
| } |
| |
| /** |
| * If there's a drag tracker, sets it to <code>null</code> and then sets |
| * this tool's state to the initial state. |
| * |
| * @see AbstractTool#handleViewerExited() |
| */ |
| protected boolean handleViewerExited() { |
| if (isInState(STATE_ACCESSIBLE_DRAG | STATE_ACCESSIBLE_DRAG_IN_PROGRESS |
| | STATE_TRAVERSE_HANDLE | STATE_DRAG | STATE_DRAG_IN_PROGRESS)) { |
| if (getDragTracker() != null) |
| setDragTracker(null); |
| setState(STATE_INITIAL); |
| } |
| return super.handleViewerExited(); |
| } |
| |
| /** |
| * Forwards the key down event to the drag tracker, if one exists. |
| * |
| * @see org.eclipse.gef.Tool#keyDown(KeyEvent, |
| * org.eclipse.gef.EditPartViewer) |
| */ |
| public void keyDown(KeyEvent evt, EditPartViewer viewer) { |
| if (getDragTracker() != null) |
| getDragTracker().keyDown(evt, viewer); |
| super.keyDown(evt, viewer); |
| } |
| |
| /** |
| * Forwards the key up event to the drag tracker, if one exists. |
| * |
| * @see org.eclipse.gef.Tool#keyUp(KeyEvent, org.eclipse.gef.EditPartViewer) |
| */ |
| public void keyUp(KeyEvent evt, EditPartViewer viewer) { |
| if (getDragTracker() != null) |
| getDragTracker().keyUp(evt, viewer); |
| super.keyUp(evt, viewer); |
| } |
| |
| /** |
| * Forwards the mouse down event to the drag tracker, if one exists. |
| * |
| * @see org.eclipse.gef.Tool#mouseDown(MouseEvent, |
| * org.eclipse.gef.EditPartViewer) |
| */ |
| public void mouseDown(MouseEvent e, EditPartViewer viewer) { |
| super.mouseDown(e, viewer); |
| if (getDragTracker() != null) |
| getDragTracker().mouseDown(e, viewer); |
| } |
| |
| /** |
| * Forwards the mouse double clicked event to the drag tracker, if one |
| * exists. |
| * |
| * @see org.eclipse.gef.Tool#mouseDoubleClick(MouseEvent, |
| * org.eclipse.gef.EditPartViewer) |
| */ |
| public void mouseDoubleClick(MouseEvent e, EditPartViewer viewer) { |
| super.mouseDoubleClick(e, viewer); |
| if (getDragTracker() != null) |
| getDragTracker().mouseDoubleClick(e, viewer); |
| } |
| |
| /** |
| * Forwards the mouse drag event to the drag tracker, if one exists. |
| * |
| * @see org.eclipse.gef.Tool#mouseDrag(MouseEvent, |
| * org.eclipse.gef.EditPartViewer) |
| */ |
| public void mouseDrag(MouseEvent e, EditPartViewer viewer) { |
| if (getDragTracker() != null) |
| getDragTracker().mouseDrag(e, viewer); |
| super.mouseDrag(e, viewer); |
| } |
| |
| /** |
| * Forwards the mouse hover event to the drag tracker, if one exists. |
| * |
| * @see org.eclipse.gef.Tool#mouseHover(MouseEvent, |
| * org.eclipse.gef.EditPartViewer) |
| */ |
| public void mouseHover(MouseEvent me, EditPartViewer viewer) { |
| if (getDragTracker() != null) |
| getDragTracker().mouseHover(me, viewer); |
| super.mouseHover(me, viewer); |
| } |
| |
| /** |
| * Forwards the mouse move event to the drag tracker, if one exists. |
| * |
| * @see org.eclipse.gef.Tool#mouseMove(MouseEvent, |
| * org.eclipse.gef.EditPartViewer) |
| */ |
| public void mouseMove(MouseEvent me, EditPartViewer viewer) { |
| if (getDragTracker() != null) |
| getDragTracker().mouseMove(me, viewer); |
| super.mouseMove(me, viewer); |
| } |
| |
| /** |
| * Forwards the mouse up event to the drag tracker, if one exists. |
| * |
| * @see org.eclipse.gef.Tool#mouseUp(MouseEvent, |
| * org.eclipse.gef.EditPartViewer) |
| */ |
| public void mouseUp(MouseEvent e, EditPartViewer viewer) { |
| if (getDragTracker() != null) |
| getDragTracker().mouseUp(e, viewer); |
| super.mouseUp(e, viewer); |
| } |
| |
| /** |
| * Delegates the scrolling to the DragTracker (if there is one). If not, |
| * invokes the super method. |
| * |
| * @see org.eclipse.gef.Tool#mouseWheelScrolled(org.eclipse.swt.widgets.Event, |
| * org.eclipse.gef.EditPartViewer) |
| */ |
| public void mouseWheelScrolled(Event event, EditPartViewer viewer) { |
| if (getDragTracker() != null) { |
| getDragTracker().mouseWheelScrolled(event, viewer); |
| event.doit = false; |
| } else |
| super.mouseWheelScrolled(event, viewer); |
| } |
| |
| /** |
| * If there is a drag tracker, this method does nothing so that the drag |
| * tracker can take care of the cursor. Otherwise, calls <code>super</code>. |
| * |
| * @see AbstractTool#refreshCursor() |
| */ |
| protected void refreshCursor() { |
| // If we have a DragTracker, let it control the Cursor |
| if (getDragTracker() == null) |
| super.refreshCursor(); |
| } |
| |
| /** |
| * Sets the drag tracker for this SelectionTool. If the current drag tracker |
| * is not <code>null</code>, this method deactivates it. If the new drag |
| * tracker is not <code>null</code>, this method will activate it and set |
| * the {@link EditDomain} and {@link EditPartViewer}. |
| * |
| * @param newDragTracker |
| * the new drag tracker |
| */ |
| public void setDragTracker(DragTracker newDragTracker) { |
| if (newDragTracker == dragTracker) |
| return; |
| if (dragTracker != null) |
| dragTracker.deactivate(); |
| dragTracker = newDragTracker; |
| |
| // if (!getCurrentInput().isMouseButtonDown(3)) |
| // setMouseCapture(dragTracker != null); |
| if (newDragTracker != null) { |
| newDragTracker.setEditDomain(getDomain()); |
| newDragTracker.activate(); |
| newDragTracker.setViewer(getCurrentViewer()); |
| } |
| |
| refreshCursor(); |
| } |
| |
| private void setLastHandleProvider(EditPart part) { |
| if (part == null) |
| cachedHandlePart = null; |
| else |
| cachedHandlePart = new WeakReference(part); |
| } |
| |
| /** |
| * Asks the target edit part (if there is one) to show hover feedback via |
| * {@link EditPart#showTargetFeedback(Request)} with a hover request. |
| */ |
| protected void showHoverFeedback() { |
| if (getTargetEditPart() == null) |
| return; |
| if (getTargetHoverRequest() == null) |
| return; |
| getTargetEditPart().showTargetFeedback(getTargetHoverRequest()); |
| } |
| |
| /** |
| * Updates the location of the hover request. |
| */ |
| protected void updateHoverRequest() { |
| LocationRequest request = (LocationRequest) getTargetHoverRequest(); |
| request.setLocation(getLocation()); |
| } |
| |
| /** |
| * Sets the modifiers , type and location of the target request (which is a |
| * {@link SelectionRequest}) and then calls {@link #updateHoverRequest()}. |
| * |
| * @see TargetingTool#updateTargetRequest() |
| */ |
| protected void updateTargetRequest() { |
| SelectionRequest request = (SelectionRequest) getTargetRequest(); |
| request.setModifiers(getCurrentInput().getModifiers()); |
| request.setType(getCommandName()); |
| request.setLocation(getLocation()); |
| updateHoverRequest(); |
| } |
| |
| /** |
| * @see AbstractTool#getDebugNameForState(int) |
| */ |
| protected String getDebugNameForState(int state) { |
| if (state == STATE_TRAVERSE_HANDLE) |
| return "Traverse Handle"; //$NON-NLS-1$ |
| return super.getDebugNameForState(state); |
| } |
| |
| } |