/******************************************************************************* * Copyright (c) 2000, 2005 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) { return editpart.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); } }