/******************************************************************************* * Copyright (c) 2000, 2008 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.beans.IntrospectionException; import java.beans.Introspector; import java.beans.PropertyDescriptor; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Date; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; import org.eclipse.swt.SWT; import org.eclipse.swt.dnd.DragSourceEvent; import org.eclipse.swt.events.FocusEvent; import org.eclipse.swt.events.KeyEvent; import org.eclipse.swt.events.MouseEvent; import org.eclipse.swt.events.TraverseEvent; import org.eclipse.swt.graphics.Cursor; import org.eclipse.swt.graphics.Rectangle; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Event; import org.eclipse.swt.widgets.Scrollable; import org.eclipse.draw2d.IFigure; import org.eclipse.draw2d.geometry.Dimension; import org.eclipse.draw2d.geometry.Point; import org.eclipse.gef.DragTracker; import org.eclipse.gef.EditDomain; import org.eclipse.gef.EditPartViewer; import org.eclipse.gef.LayerConstants; import org.eclipse.gef.MouseWheelHandler; import org.eclipse.gef.RequestConstants; import org.eclipse.gef.Tool; import org.eclipse.gef.commands.Command; import org.eclipse.gef.commands.CommandStackEvent; import org.eclipse.gef.commands.CommandStackEventListener; import org.eclipse.gef.editparts.LayerManager; /** * The base implementation for {@link Tool}s. The base implementation provides a * framework for a <EM>state machine</EM> which processes mouse and keyboard input. The * state machine consists of a series of states identified by <code>int</code>s. Each * mouse or keyboard event results in a transition, sometimes to the same state in which * the input was received. The interesting transitions have corresponding actions * assigned to them, such as {@link #handleDragStarted()}. * <P> * The base implementation performs <EM>no</em> state transitions by default, but does * route events to different method handlers based on state. It is up to subclasses to * set the appropriate states. * <P> * There are two broad "categories" of methods on AbstractTool. There are the methods * defined on the {@link Tool} interface which handle the job of receiving raw user input. * For example, {@link #mouseDrag(MouseEvent, EditPartViewer)}. Then, there are the * methods which correspond to higher-level interpretation of these events, such as {@link * #handleDragInProgress()}, which is called from <code>mouseMove(...)</code>, but * <em>only</em> when the drag threshold has been passed. These methods are generally * more subclass-friendly. Subclasses should <em>not</em> override the methods which * receive raw input. */ public abstract class AbstractTool extends org.eclipse.gef.util.FlagSupport implements Tool, RequestConstants { /** * The property to be used in {@link #setProperties(Map)} for * {@link #setUnloadWhenFinished(boolean)} */ public static final Object PROPERTY_UNLOAD_WHEN_FINISHED = "unloadWhenFinished"; //$NON-NLS-1$ private static final int DRAG_THRESHOLD = 5; private static final int FLAG_ACTIVE = 8; private static final int FLAG_HOVER = 2; private static final int FLAG_PAST_THRESHOLD = 1; private static final int FLAG_UNLOAD = 4; { setFlag(FLAG_UNLOAD, true); } /** * The highest-bit flag being used. */ protected static final int MAX_FLAG = 8; /** * The maximum state flag defined by this class */ protected static final int MAX_STATE = 32; /** * constant used for mouse button 1. * @deprecated Use {@link SWT#BUTTON1} instead. */ protected static final int MOUSE_BUTTON1 = SWT.BUTTON1; /** * constant used for mouse button 2. * @deprecated Use {@link SWT#BUTTON2} instead. */ protected static final int MOUSE_BUTTON2 = SWT.BUTTON2; /** * constant used for mouse button 3. * @deprecated Use {@link SWT#BUTTON3} instead. */ protected static final int MOUSE_BUTTON3 = SWT.BUTTON3; /** * constant used to indicate any of the mouse buttons. * @deprecated Use {@link SWT#BUTTON_MASK} instead. */ protected static final int MOUSE_BUTTON_ANY = SWT.BUTTON_MASK; /** * The state indicating that the keyboard is being used to perform a drag that is normally * done using the mouse. */ protected static final int STATE_ACCESSIBLE_DRAG = 16; /** * The state indicating that a keyboard drag is in progress. The threshold for keyboard * drags is non-existent, so this state would be entered very quickly. */ protected static final int STATE_ACCESSIBLE_DRAG_IN_PROGRESS = 32; /** * The state indicating that one or more buttons are pressed, but the user has not moved * past the drag threshold. Many tools will do nothing during this state but wait * until {@link #STATE_DRAG_IN_PROGRESS} is entered. */ protected static final int STATE_DRAG = 2; /** * The state indicating that the drag detection theshold has been passed, and a drag is in * progress. */ protected static final int STATE_DRAG_IN_PROGRESS = 4; /** * The first state that a tool is in. The tool will generally be in this state * immediately following {@link #activate()}. */ protected static final int STATE_INITIAL = 1; /** * The state indicating that an input event has invalidated the interaction. For example, * during a mouse drag, pressing additional mouse button might invalidate the drag. */ protected static final int STATE_INVALID = 8; /** * The final state for a tool to be in. Once a tool reaches this state, it will not * change states until it is activated() again. */ protected static final int STATE_TERMINAL = 1 << 30; /** * Key modifier for ignoring snap while dragging. It's CTRL on Mac, and ALT on all * other platforms. */ static final int MODIFIER_NO_SNAPPING; private long accessibleBegin; private int accessibleStep; private Command command; private CommandStackEventListener commandStackListener = new CommandStackEventListener() { public void stackChanged(CommandStackEvent event) { if (event.isPreChangeEvent()) handleCommandStackChanged(); } }; private Input current; private EditPartViewer currentViewer; private Cursor defaultCursor, disabledCursor; private EditDomain domain; private List operationSet; private int startX, startY, state; static { if (SWT.getPlatform().equals("carbon"))//$NON-NLS-1$ MODIFIER_NO_SNAPPING = SWT.CTRL; else MODIFIER_NO_SNAPPING = SWT.ALT; } boolean acceptAbort(KeyEvent e) { return e.character == SWT.ESC; } /** * Returns true if the event corresponds to an arrow key with the * appropriate modifiers and if the system is in a state where the arrow key * should be accepted. * * @param e * the key event * @return true if the arrow key should be accepted by this tool * @since 3.4 */ protected boolean acceptArrowKey(KeyEvent e) { int key = e.keyCode; if (!(isInState(STATE_INITIAL | STATE_ACCESSIBLE_DRAG | STATE_ACCESSIBLE_DRAG_IN_PROGRESS))) return false; return (key == SWT.ARROW_UP) || (key == SWT.ARROW_RIGHT) || (key == SWT.ARROW_DOWN) || (key == SWT.ARROW_LEFT); } boolean acceptDragCommit(KeyEvent e) { return isInState(STATE_ACCESSIBLE_DRAG_IN_PROGRESS) && e.character == 13; } int accGetStep() { return accessibleStep; } void accStepIncrement() { if (accessibleBegin == -1) { accessibleBegin = new Date().getTime(); accessibleStep = 1; } else { accessibleStep = 4; long elapsed = new Date().getTime() - accessibleBegin; if (elapsed > 1000) accessibleStep = Math.min(16, (int) (elapsed / 150)); } } void accStepReset() { accessibleBegin = -1; } /** * Activates the tool. Any initialization should be performed here. * This method is called when a tool is selected. * * @see #deactivate() */ public void activate() { resetFlags(); accessibleBegin = -1; getCurrentInput().verifyMouseButtons = true; setState(STATE_INITIAL); setFlag(FLAG_ACTIVE, true); getDomain().getCommandStack().addCommandStackEventListener(commandStackListener); } /** * Convenience method to add the given figure to the feedback layer. * @param figure the feedback being added */ protected void addFeedback(IFigure figure) { LayerManager lm = (LayerManager)getCurrentViewer(). getEditPartRegistry().get(LayerManager.ID); if (lm == null) return; lm.getLayer(LayerConstants.FEEDBACK_LAYER).add(figure); } /** * This method is invoked from {@link #setProperties(Map)}. Sub-classes can override to * add support for more properties. This method should fail silently in case of any * error. * <p> * AbstractTool uses introspection to match any keys with properties. For instance, the * key "defaultCursor" would lead to the invocation of {@link #setDefaultCursor(Cursor)} * with the provided value. * @param key the key; may be <code>null</code> * @param value the new value * @since 3.1 * @see #setProperties(Map) */ protected void applyProperty(Object key, Object value) { if (PROPERTY_UNLOAD_WHEN_FINISHED.equals(key)) { if (value instanceof Boolean) setUnloadWhenFinished(((Boolean)value).booleanValue()); return; } if (!(key instanceof String)) return; try { PropertyDescriptor[] descriptors = Introspector .getBeanInfo(getClass(), Introspector.IGNORE_ALL_BEANINFO) .getPropertyDescriptors(); PropertyDescriptor property = null; for (int i = 0; i < descriptors.length; i++) { if (descriptors[i].getName().equals(key)) { property = descriptors[i]; break; } } if (property != null) { Method setter = property.getWriteMethod(); // setter.setAccessible(true); setter.invoke(this, new Object[] {value}); } } catch (IntrospectionException ie) { } catch (IllegalAccessException iae) { } catch (InvocationTargetException ite) { } catch (SecurityException se) { } } /** * Returns the appropriate cursor for the tools current state. If the tool is in its * terminal state, <code>null</code> is returned. Otherwise, either the default or * disabled cursor is returned, based on the existence of a current command, and whether * that current command is executable. * <P> * Subclasses may override or extend this method to calculate the appropriate cursor based * on other conditions. * @see #getDefaultCursor() * @see #getDisabledCursor() * @see #getCurrentCommand() * @return <code>null</code> or a cursor to be displayed. */ protected Cursor calculateCursor() { if (isInState(STATE_TERMINAL)) return null; Command command = getCurrentCommand(); if (command == null || !command.canExecute()) return getDisabledCursor(); return getDefaultCursor(); } /** * Added for compatibility. {@link DragTracker#commitDrag()} was added for accessibility * reasons. Since all tool implementations must inherit from this base class, then * implementing this method here avoids breaking subclasses that implemented the {@link * DragTracker} interface. */ public void commitDrag() { } /** * Returns a new List of editparts that this tool is operating on. This method is called * once during {@link #getOperationSet()}, and its result is cached. * <P> * By default, the operations set is the current viewer's entire selection. Subclasses * may override this method to filter or alter the operation set as necessary. * @return a list of editparts being operated on */ protected List createOperationSet() { return new ArrayList(getCurrentViewer().getSelectedEditParts()); } /** * 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. The abstract tool allows * cursors for viewers to be changed. When the tool is deactivated it must revert to * normal the cursor of the last tool it changed. * @see #activate() */ public void deactivate() { setFlag(FLAG_ACTIVE, false); setViewer(null); setCurrentCommand(null); setState(STATE_TERMINAL); operationSet = null; current = null; getDomain().getCommandStack().removeCommandStackEventListener(commandStackListener); } /** * Prints a string in the GEF Debug console if the Tools debug option is selected. * @param message a message for the debug trace tool * @deprecated */ protected void debug(String message) { } /** * Executes the given command on the command stack. * @since 3.1 * @param command the command to execute */ protected void executeCommand(Command command) { getDomain().getCommandStack().removeCommandStackEventListener(commandStackListener); try { getDomain().getCommandStack() .execute(command); } finally { getDomain().getCommandStack().addCommandStackEventListener(commandStackListener); } } /** * Execute the currently active command. */ protected void executeCurrentCommand() { Command curCommand = getCurrentCommand(); if (curCommand != null && curCommand.canExecute()) executeCommand(curCommand); setCurrentCommand(null); } /** * Called when a viewer that the editor controls gains focus. * * @param event The SWT focus event * @param viewer The viewer that the focus event is over. */ public void focusGained(FocusEvent event, EditPartViewer viewer) { setViewer(viewer); handleFocusGained(); } /** * Called when a viewer that the editor controls loses focus. * * @param event The SWT focus event * @param viewer The viewer that the focus event is over. */ public void focusLost(FocusEvent event, EditPartViewer viewer) { setViewer(viewer); handleFocusLost(); } /** * Returns a new, updated command based on the tool's current properties. The default * implementation returns an unexecutable command. Some tools do not work commands and * the model, but simply change the viewer's state in some way. * @return a newly obtained command */ protected Command getCommand() { return org.eclipse.gef.commands.UnexecutableCommand.INSTANCE; } /** * Returns the identifier of the command that is being sought. This name is also the * named that will be logged in the debug view. * @return the identifier for the command */ protected abstract String getCommandName(); /** * Returns the currently cached command. * @return the current command * @see #setCurrentCommand(Command) */ protected Command getCurrentCommand() { return command; } /** * Returns the input object encapsulating the current mouse and keyboard state. * @return the current input */ protected Input getCurrentInput() { if (current == null) current = new Input(); return current; } /** * Return the viewer that the tool is currently receiving input from, or * <code>null</code>. The last viewer to dispatch an event is defined as the current * viewer. Current viewer is automatically updated as events are received, and is set to * <code>null</code> on <code>deactivate()</code>. * @return the current viewer */ protected EditPartViewer getCurrentViewer() { return currentViewer; } /** * Returns the debug name for this tool. * @return the debug name */ protected String getDebugName() { return getClass().getName(); } /** * Returns a String representation of the given state for debug purposes. * @param state the state * @return the string for the given state */ protected String getDebugNameForState(int state) { switch (state) { case STATE_INITIAL: return "Initial State";//$NON-NLS-1$ case STATE_DRAG: return "Drag State";//$NON-NLS-1$ case STATE_DRAG_IN_PROGRESS: return "Drag In Progress State";//$NON-NLS-1$ case STATE_INVALID: return "Invalid State"; //$NON-NLS-1$ case STATE_TERMINAL: return "Terminal State"; //$NON-NLS-1$ case STATE_ACCESSIBLE_DRAG: return "Accessible Drag"; //$NON-NLS-1$ case STATE_ACCESSIBLE_DRAG_IN_PROGRESS: return "Accessible Drag In Progress"; //$NON-NLS-1$ } return "Unknown state:";//$NON-NLS-1$ } /** * Returns the cursor used under normal conditions. * @see #setDefaultCursor(Cursor) * @return the default cursor */ protected Cursor getDefaultCursor() { return defaultCursor; } /** * Returns the cursor used under abnormal conditions. * @see #calculateCursor() * @see #setDisabledCursor(Cursor) * @return the disabled cursor */ protected Cursor getDisabledCursor() { if (disabledCursor != null) return disabledCursor; return getDefaultCursor(); } /** * Returns the EditDomain. A tool is told its EditDomain when it becomes active. A tool * may need to know its edit domain prior to receiving any events from any of that * domain's viewers. * @return the editdomain */ protected EditDomain getDomain() { return domain; } /** * Return the number of pixels that the mouse has been moved since that drag was started. * The drag start is determined by where the mouse button was first pressed. * @see #getStartLocation() * @return the drag delta */ protected Dimension getDragMoveDelta() { return getLocation().getDifference(getStartLocation()); } /** * Returns the current x, y position of the mouse cursor. * @return the mouse location */ protected Point getLocation() { return new Point(getCurrentInput().getMouseLocation()); } /** * Lazily creates and returns the list of editparts on which the tool operates. The list * is initially <code>null</code>, in which case {@link #createOperationSet()} is called, * and its results cached until the tool is deactivated. * @return the operation set. */ protected List getOperationSet() { if (operationSet == null) operationSet = createOperationSet(); return operationSet; } /** * Returns the starting mouse location for the current tool operation. This is typically * the mouse location where the user first pressed a mouse button. This is important for * tools that interpret mouse drags. * @return the start location */ protected Point getStartLocation() { return new Point(startX, startY); } /** * Returns the tool's current state. * @return the current state */ protected int getState() { return state; } /** * Called when the mouse button has been pressed. By default, nothing happens * and <code>false</code> is returned. Subclasses may override this method to interpret * the meaning of a mouse down. Returning <code>true</code> indicates that the button down * was handled in some way. * @param button which button went down * @return <code>true</code> if the buttonDown was handled */ protected boolean handleButtonDown(int button) { return false; } /** * Called when the mouse button has been released. By default, nothing happens and * <code>false</code> is returned. Subclasses may override this method to interpret the * mouse up. Returning <code>true</code> indicates that the mouse up was handled in some * way. * @see #mouseUp(MouseEvent, EditPartViewer) * @param button the button being released * @return <code>true</code> if the button up was handled */ protected boolean handleButtonUp(int button) { return false; } /** * Called when the command stack has changed, for instance, when a delete or undo command * has been executed. By default, state is set to <code>STATE_INVALID</code> and * handleInvalidInput is called. Subclasses may override this method to change what * happens when the command stack changes. Returning <code>true</code> indicates that the * change was handled in some way. * @return <code>true</code> if the change was handled in some way */ protected boolean handleCommandStackChanged() { if (!isInState(STATE_INITIAL | STATE_INVALID)) { setState(STATE_INVALID); handleInvalidInput(); return true; } return false; } /** * Called when a mouse double-click occurs. By default, nothing happens and * <code>false</code> is returned. Subclasses may override this method to interpret * double-clicks. Returning <code>true</code> indicates that the event was handled in * some way. * @param button which button was double-clicked * @return <code>true</code> if the event was handled * @see #mouseDoubleClick(MouseEvent, EditPartViewer) */ protected boolean handleDoubleClick(int button) { return false; } /** * Called whenever the mouse is being dragged. This method continues to be called * even once {@link #handleDragInProgress()} starts getting called. By default, nothing * happens, and <code>false</code> is returned. Subclasses may override this method to * interpret a drag. Returning <code>true</code> indicates that the drag was handled in * some way. * @return <code>true</code> if the drag is handled * @see #mouseDrag(MouseEvent, EditPartViewer) */ protected boolean handleDrag() { return false; } /** * Called whenever a mouse is being dragged and the drag threshold has been exceeded. * Prior to the drag threshold being exceeded, only {@link #handleDrag()} is called. This * method gets called repeatedly for every mouse move during the drag. By default, nothing * happens and <code>false</code> is returned. Subclasses may override this method to * interpret the drag. Returning <code>true</code> indicates that the drag was handled. * @see #movedPastThreshold() * @see #mouseDrag(MouseEvent, EditPartViewer) * @return <code>true</code> if the drag was handled */ protected boolean handleDragInProgress() { return false; } /** * Called only one time during a drag when the drag threshold has been exceeded. By * default, nothing happens and <code>false</code> is returned. Subclasses may override * to interpret the drag starting. Returning <code>true</code> indicates that the event * was handled. * @see #movedPastThreshold() * @see #mouseDrag(MouseEvent, EditPartViewer) * @return true if the drag starting was handled */ protected boolean handleDragStarted() { return false; } /** * Called when the current tool operation is to be completed. In other words, the "state * machine" and has accepted the sequence of input (i.e. the mouse gesture). By default, * the tool will either reactivate itself, or ask the edit domain to load the default * tool. * <P> * Subclasses should extend this method to first do whatever it is that the tool does, and * then call <code>super</code>. * @see #unloadWhenFinished() */ protected void handleFinished() { if (unloadWhenFinished()) getDomain().loadDefaultTool(); else reactivate(); } /** * Handles high-level processing of a focus gained event. By default, nothing happens and * <code>false</code> is returned. Subclasses may override this method to interpret the * focus gained event. Return <code>true</code> to indicate that the event was processed. * @see #focusGained(FocusEvent, EditPartViewer) * @return <code>true</code> if the event was handled */ protected boolean handleFocusGained() { return false; } /** * Handles high-level processing of a focus lost event. By default, nothing happens and * <code>false</code> is returned. Subclasses may override this method to interpret the * focus lost event. Return <code>true</code> to indicate that the event was processed. * @see #focusLost(FocusEvent, EditPartViewer) * @return <code>true</code> if the event was handled */ protected boolean handleFocusLost() { return false; } /** * Handles high-level processing of a mouse hover event. By default, nothing happens and * <code>false</code> is returned. Subclasses may override this method to interpret the * hover. Return <code>true</code> to indicate that the hover was handled. * @see #mouseHover(MouseEvent, EditPartViewer) * @return <code>true</code> if the hover was handled */ protected boolean handleHover() { return false; } /** * Called when invalid input is encountered. The state does not change, so the * caller must set the state to {@link AbstractTool#STATE_INVALID}. * @return <code>true</code> */ protected boolean handleInvalidInput() { return false; } /** * Handles high-level processing of a key down event. By default, the KeyEvent is * checked to see if it is the ESCAPE key. If so, the domain's default tool is reloaded, * and <code>true</code> is returned. Subclasses may extend this method to interpret * additional key down events. Returns <code>true</code> if the given key down was * handled. * @see #keyDown(KeyEvent, EditPartViewer) * @param e the key event * @return <code>true</code> if the key down was handled. */ protected boolean handleKeyDown(KeyEvent e) { if (acceptAbort(e)) { getDomain().loadDefaultTool(); return true; } return false; } /** * Override to process a traverse event. If the event's {@link KeyEvent#doit doit} field * is set to <code>false</code>, the traversal will be prevented from occurring. * Otherwise, a traverse will occur. * @param event the SWT traverse event * @since 3.1 */ protected void handleKeyTraversed(TraverseEvent event) { } /** * Handles high-level processing of a key up event. By default, does nothing and returns * <code>false</code>. Subclasses may extend this method to process key up events. * Returns <code>true</code> if the key up was processed in some way. * @see #keyUp(KeyEvent, EditPartViewer) * @param e the key event * @return <code>true</code> if the event was handled */ protected boolean handleKeyUp(KeyEvent e) { return false; } /** * Handles high-level processing of a mouse move. By default, does nothing and returns * <code>false</code>. Subclasses may extend this method to process mouse moves. * Returns <code>true</code> if the mouse move was processed. * @see #mouseMove(MouseEvent, EditPartViewer) * @return <code>true</code> if the mouse move was handled */ protected boolean handleMove() { return false; } /** * Handles when a native drag has ended. By default, does nothing and returns * <code>false</code>. Subclasses may extend this method to process native drags ending. * @param event the drag event * @return <code>true</code> if the native drag finished was handled */ protected boolean handleNativeDragFinished(DragSourceEvent event) { return false; } /** * Handles when a native drag has started. By default, does nothing and returns * <code>false</code>. Subclasses may extend this method to process native drag starts. * <P> * When a native drag starts, all subsequent mouse events will not be received, including * the mouseUp event. The only event that will be received is the drag finished event. * @param event the drag event * @return <code>true</code> if the native drag start was handled */ protected boolean handleNativeDragStarted(DragSourceEvent event) { return false; } /** * Called when the mouse enters an EditPartViewer. By default, does nothing and returns * <code>false</code>. Subclasses may extend this method to process the viewer enter. * Returns <code>true</code> to indicate if the viewer entered was process in some way. * @return <code>true</code> if the viewer entered was handled */ protected boolean handleViewerEntered() { return false; } /** * Called when the mouse exits an EditPartViewer. By default, does nothing and returns * <code>false</code>. Subclasses may extend this method to process viewer exits. Returns * <code>true</code> to indicate if the viewer exited was process in some way. * @return <code>true</code> if the viewer exited was handled */ protected boolean handleViewerExited() { return false; } /** * Returns <code>true</code> if the tool is active. * @return <code>true</code> if active */ protected boolean isActive() { return getFlag(FLAG_ACTIVE); } boolean isCurrentViewerMirrored() { return (getCurrentViewer().getControl().getStyle() & SWT.MIRRORED) != 0; } /** * Returns <code>true</code> if the tool is hovering. * @return <code>true</code> if hovering */ protected boolean isHoverActive() { return getFlag(FLAG_HOVER); } boolean isInDragInProgress() { return isInState(STATE_DRAG_IN_PROGRESS | STATE_ACCESSIBLE_DRAG_IN_PROGRESS); } /* * Returns <code>true</code> if the current {@link Input} is * synchronized with the current MouseEvent. */ private boolean isInputSynched(MouseEvent event) { Input input = getCurrentInput(); return input.isMouseButtonDown(1) == ((event.stateMask & SWT.BUTTON1) != 0) && input.isMouseButtonDown(2) == ((event.stateMask & SWT.BUTTON2) != 0) && input.isMouseButtonDown(3) == ((event.stateMask & SWT.BUTTON3) != 0) && input.isMouseButtonDown(4) == ((event.stateMask & SWT.BUTTON4) != 0) && input.isMouseButtonDown(5) == ((event.stateMask & SWT.BUTTON5) != 0); } /** * Returns <code>true</code> if the tool is in the given state. * @param state the state being queried * @return <code>true</code> if the tool is in the given state */ protected boolean isInState(int state) { return ((getState() & state) != 0); } /** * Default implementation always returns <code>true</code>. Sub-classes may override. * @param viewer the viewer where the event occured * @return <code>true</code> if this tool is interested in events occuring in the given * viewer; <code>false</code> otherwise * @since 3.1 */ protected boolean isViewerImportant(EditPartViewer viewer) { return true; } /** * Receives a KeyDown event for the given viewer. Subclasses wanting to handle this * event should override {@link #handleKeyDown(KeyEvent)}. * @param evt the key event * @param viewer the originating viewer */ public void keyDown(KeyEvent evt, EditPartViewer viewer) { if (!isViewerImportant(viewer)) return; setViewer(viewer); getCurrentInput().setInput(evt); handleKeyDown(evt); } /** * Receives a traversal event for the given viewer. Subclasses wanting to handle this * event should override {@link #handleKeyTraversed(TraverseEvent)}. * @param event the traverse event * @param viewer the originating viewer */ public void keyTraversed(TraverseEvent event, EditPartViewer viewer) { if (!isViewerImportant(viewer)) return; setViewer(viewer); getCurrentInput().setInput(event); handleKeyTraversed(event); } /** * Receives a KeyUp event for the given viewer. Subclasses wanting to handle this event * should override {@link #handleKeyUp(KeyEvent)}. * @param evt the key event * @param viewer the originating viewer */ public void keyUp(KeyEvent evt, EditPartViewer viewer) { if (!isViewerImportant(viewer)) return; setViewer(viewer); getCurrentInput().setInput(evt); handleKeyUp(evt); } /** * Handles mouse double click events within a viewer. Subclasses wanting to handle this * event should override {@link #handleDoubleClick(int)}. * @param me the mouse event * @param viewer the originating viewer */ public void mouseDoubleClick(MouseEvent me, EditPartViewer viewer) { if (me.button > 5 || !isViewerImportant(viewer)) return; setViewer(viewer); getCurrentInput().setInput(me); handleDoubleClick(me.button); } /** * Handles mouse down events within a viewer. Subclasses wanting to handle this event * should override {@link #handleButtonDown(int)}. * @param me the mouse event * @param viewer the originating viewer */ public void mouseDown(MouseEvent me, EditPartViewer viewer) { if (!isViewerImportant(viewer)) return; setViewer(viewer); getCurrentInput().setInput(me); getCurrentInput().setMouseButton(me.button, true); startX = me.x; startY = me.y; handleButtonDown(me.button); } /** * Handles mouse drag events within a viewer. Subclasses wanting to handle this event * should override {@link #handleDrag()} and/or {@link #handleDragInProgress()}. * @param me the mouse event * @param viewer the originating viewer */ public void mouseDrag(MouseEvent me, EditPartViewer viewer) { if (!isViewerImportant(viewer)) return; setViewer(viewer); boolean wasDragging = movedPastThreshold(); getCurrentInput().setInput(me); handleDrag(); if (movedPastThreshold()) { if (!wasDragging) handleDragStarted(); handleDragInProgress(); } } /** * Handles mouse hover event. within a viewer. Subclasses wanting to handle this event * should override {@link #handleHover()}. * @param me the mouse event * @param viewer the originating viewer * */ public void mouseHover(MouseEvent me, EditPartViewer viewer) { if (!isViewerImportant(viewer)) return; setViewer(viewer); getCurrentInput().setInput(me); handleHover(); } /** * Handles mouse moves (if the mouse button is up) within a viewer. Subclasses wanting to * handle this event should override {@link #handleMove()}. * @param me the mouse event * @param viewer the originating viewer */ public void mouseMove(MouseEvent me, EditPartViewer viewer) { if (!isViewerImportant(viewer)) return; setViewer(viewer); if (!isInputSynched(me)) { boolean b1 = getCurrentInput().isMouseButtonDown(1); boolean b2 = getCurrentInput().isMouseButtonDown(2); boolean b3 = getCurrentInput().isMouseButtonDown(3); boolean b4 = getCurrentInput().isMouseButtonDown(4); boolean b5 = getCurrentInput().isMouseButtonDown(5); getCurrentInput().verifyMouseButtons = true; getCurrentInput().setInput(me); if (b1) handleButtonUp(1); if (b2) handleButtonUp(2); if (b3) handleButtonUp(3); if (b4) handleButtonUp(4); if (b5) handleButtonUp(5); if (getDomain().getActiveTool() != this) return; /* * processing one of the buttonUps may have caused the tool to reactivate itself, * which causes the viewer to get nulled-out. If we are going to call another * handleXxx method below, we must set the viewer again to be paranoid. */ setViewer(viewer); } else getCurrentInput().setInput(me); if (isInState(STATE_ACCESSIBLE_DRAG_IN_PROGRESS)) handleDragInProgress(); else handleMove(); } /** * Handles mouse up within a viewer. Subclasses wanting to handle this event should * override {@link #handleButtonUp(int)}. * @param me the mouse event * @param viewer the originating viewer */ public void mouseUp(MouseEvent me, EditPartViewer viewer) { if (!isViewerImportant(viewer)) return; setViewer(viewer); getCurrentInput().setInput(me); getCurrentInput().setMouseButton(me.button, false); handleButtonUp(me.button); } /** * Handles mouse-wheel scrolling for a viewer. Sub-classes may override as needed. The * default implementation delegates to * {@link #performViewerMouseWheel(Event, EditPartViewer)} IFF the tool is in the initial * state. Mouse-wheel events generated at other times are ignored. * @param event the SWT scroll event * @param viewer the originating viewer * @see #performViewerMouseWheel(Event, EditPartViewer) */ public void mouseWheelScrolled(Event event, EditPartViewer viewer) { if (isInState(STATE_INITIAL)) performViewerMouseWheel(event, viewer); } /** * Returns <code>true</code> if the threshold has been exceeded during a mouse drag. * @return <code>true</code> if the threshold has been exceeded */ protected boolean movedPastThreshold() { if (getFlag(FLAG_PAST_THRESHOLD)) return true; Point start = getStartLocation(), end = getLocation(); if (Math.abs(start.x - end.x) > DRAG_THRESHOLD || Math.abs(start.y - end.y) > DRAG_THRESHOLD) { setFlag(FLAG_PAST_THRESHOLD, true); return true; } return false; } /** * @see org.eclipse.gef.Tool#nativeDragFinished(DragSourceEvent, EditPartViewer) */ public void nativeDragFinished(DragSourceEvent event, EditPartViewer viewer) { if (!isViewerImportant(viewer)) return; setViewer(viewer); handleNativeDragFinished(event); } /** * @see org.eclipse.gef.Tool#nativeDragStarted(DragSourceEvent, EditPartViewer) */ public void nativeDragStarted(DragSourceEvent event, EditPartViewer viewer) { if (!isViewerImportant(viewer)) return; setViewer(viewer); handleNativeDragStarted(event); } /** * Delegates mouse-wheel event handling to registered * {@link MouseWheelHandler MouseWheelHandlers} based on the given Event's statemask. * Does nothing if there are no matching handlers found. * * @param event the SWT scroll event * @param viewer the originating viewer * @since 3.1 */ protected void performViewerMouseWheel(Event event, EditPartViewer viewer) { MouseWheelHandler handler = (MouseWheelHandler)viewer .getProperty(MouseWheelHandler.KeyGenerator.getKey(event.stateMask)); if (handler != null) handler.handleMouseWheel(event, viewer); } /** * Places the mouse in the viewer based on the point given. If the point * given is outside the viewer, then the mouse is placed in the location * nearest the given point but within the viewer. * * @param p * the point * @since 3.4 */ protected void placeMouseInViewer(Point p) { if (getCurrentViewer() == null) return; Control c = getCurrentViewer().getControl(); Rectangle rect; if (c instanceof Scrollable) rect = ((Scrollable)c).getClientArea(); else rect = c.getBounds(); if (p.x > rect.x + rect.width - 1) p.x = rect.x + rect.width - 1; else if (p.x < rect.x) p.x = rect.x; if (p.y > rect.y + rect.height - 1) p.y = rect.y + rect.height - 1; else if (p.y < rect.y) p.y = rect.y; org.eclipse.swt.graphics.Point swt = new org.eclipse.swt.graphics.Point(p.x, p.y); swt = c.toDisplay(swt); c.getDisplay().setCursorLocation(swt); } /** * Calls <code>deactivate()</code> and then <code>activate()</code>. */ protected void reactivate() { // Fix for Bug# 91448 EditPartViewer viewer = getCurrentViewer(); deactivate(); activate(); if (viewer != null) { Control c = viewer.getControl(); if (c != null && !c.isDisposed() && c.isFocusControl()) setViewer(viewer); } } /** * Sets the cursor being displayed to the appropriate cursor. If the tool is active, the * current cursor being displayed is updated by calling {@link #calculateCursor()}. */ protected void refreshCursor() { if (isActive()) setCursor(calculateCursor()); } /** * Releases tool capture. * @see #setToolCapture() */ protected void releaseToolCapture() { getCurrentViewer().setRouteEventsToEditDomain(false); } /** * Convenience method to removes a figure from the feedback layer. * @param figure the figure being removed */ protected void removeFeedback(IFigure figure) { LayerManager lm = (LayerManager)getCurrentViewer(). getEditPartRegistry().get(LayerManager.ID); if (lm == null) return; lm.getLayer(LayerConstants.FEEDBACK_LAYER).remove(figure); } /** * Resets all stateful flags to their initial values. Subclasses should extend this method * to reset their own custom flags. */ protected void resetFlags() { setFlag(FLAG_PAST_THRESHOLD, false); setFlag(FLAG_HOVER, false); } /** * Used to cache a command obtained from {@link #getCommand()}. * @param c the command * @see #getCurrentCommand() */ protected void setCurrentCommand(Command c) { command = c; refreshCursor(); } /** * Shows the given cursor on the current viewer. * @param cursor the cursor to display */ protected void setCursor(Cursor cursor) { if (getCurrentViewer() != null) getCurrentViewer().setCursor(cursor); } /** * Sets the default cursor. * @param cursor the cursor * @see #getDefaultCursor() */ public void setDefaultCursor(Cursor cursor) { if (defaultCursor == cursor) return; defaultCursor = cursor; refreshCursor(); } /** * Sets the disabled cursor. * @param cursor the cursor * @see #getDisabledCursor() */ public void setDisabledCursor(Cursor cursor) { if (disabledCursor == cursor) return; disabledCursor = cursor; refreshCursor(); } /** * Sets the EditDomain. * @param domain the edit domain * @see #getDomain() */ public void setEditDomain(EditDomain domain) { this.domain = domain; } /** * Sets whether the hover flag is true or false. Subclasses which do something on hover * can use this flag to track whether they have received a hover or not. * @param value whether hover is active */ protected void setHoverActive(boolean value) { setFlag(FLAG_HOVER, value); } void setMouseCapture(boolean value) { if (getCurrentViewer() != null && getCurrentViewer().getControl() != null && !getCurrentViewer().getControl().isDisposed()) getCurrentViewer().getControl().setCapture(value); } /** * An example is {@link #PROPERTY_UNLOAD_WHEN_FINISHED} -> Boolean. AbstractTool uses * introspection to set properties that are not explicitly specified. For instance, * the key "defaultCursor" will cause {@link #setDefaultCursor(Cursor)} to be invoked * with the given value. * @see org.eclipse.gef.Tool#setProperties(java.util.Map) */ public void setProperties(Map properties) { if (properties == null) return; Iterator entries = properties.entrySet().iterator(); while (entries.hasNext()) { Entry entry = (Entry)entries.next(); applyProperty(entry.getKey(), entry.getValue()); } } /** * Sets the start mouse location, typically for a drag operation. * @param p the start location */ protected void setStartLocation(Point p) { startX = p.x; startY = p.y; } /** * Sets the tools state. * @param state the new state */ protected void setState(int state) { this.state = state; } /** * Sets tool capture. When a tool has capture, viewers will make every effort to send * events through the editdomain to the tool. Therefore, the default handling of some * events is bypassed. */ protected void setToolCapture() { getCurrentViewer().setRouteEventsToEditDomain(true); } /** * Setting this to <code>true</code> will cause the tool to be unloaded after one * operation has completed. The default value is <code>true</code>. The tool is * unloaded, and the edit domains default tool will be activated. * @param value whether the tool should be unloaded on completion */ public void setUnloadWhenFinished(boolean value) { setFlag(FLAG_UNLOAD, value); } /** * Sets the active EditPartViewer. The active viewer is the viewer from which the last * event was received. * @param viewer the viewer */ public void setViewer(EditPartViewer viewer) { if (viewer == currentViewer) return; setCursor(null); currentViewer = viewer; if (currentViewer != null) { org.eclipse.swt.graphics.Point p = currentViewer.getControl().toControl( Display.getCurrent().getCursorLocation()); getCurrentInput().setMouseLocation(p.x, p.y); } refreshCursor(); } /** * Returns <code>true</code> if the give state transition succeeds. This is a "test and * set" operation, where the tool is tested to be in the specified start state, and if so, * is set to the given end state. The method returns the result of the first test. * @param start the start state being tested * @param end the end state * @return <code>true</code> if the state transition is successful */ protected boolean stateTransition(int start, int end) { if ((getState() & start) != 0) { setState(end); return true; } else return false; } /** * Returns <code>true</code> if the tool is set to unload when its current operation is * complete. * @return <code>true</code> if the tool should be unloaded when finished */ protected final boolean unloadWhenFinished() { return getFlag(FLAG_UNLOAD); } /** * Receives the mouse entered event. Subclasses wanting to handle this event should * override {@link #handleViewerEntered()}. * <p> * FEATURE in SWT: mouseExit comes after mouseEntered on the new control. Therefore, if * the current viewer is not <code>null</code>, it means the exit has not been sent yet by * SWT. To maintain proper ordering, GEF fakes the exit and calls {@link * #handleViewerExited()}. The real exit will then be ignored. * @param me the mouse event * @param viewer the originating viewer */ public void viewerEntered(MouseEvent me, EditPartViewer viewer) { if (!isViewerImportant(viewer)) return; getCurrentInput().setInput(me); if (getCurrentViewer() != null && getCurrentViewer() != viewer) handleViewerExited(); setViewer(viewer); handleViewerEntered(); } /** * Handles the mouse exited event. Subclasses wanting to handle this event should * override {@link #handleViewerExited()}. * @param me the mouse event * @param viewer the originating viewer */ public void viewerExited(MouseEvent me, EditPartViewer viewer) { /* * FEATURE in SWT. mouseExited comes after mouseEntered. So only call handle exit if * we didn't previously fake it on viewer entered. */ if (viewer == getCurrentViewer()) { getCurrentInput().setInput(me); handleViewerExited(); setViewer(null); } } /** * Allows the user to access mouse and keyboard input. */ public static class Input extends org.eclipse.gef.util.FlagSupport { int modifiers; Point mouse = new Point(); boolean verifyMouseButtons; /** * Returns the event modifiers. Modifiers are defined in {@link MouseEvent#stateMask}, * and include things like the mouse buttons and keyboard modifier keys. * @return the event modifiers */ protected int getModifiers() { return modifiers; } /** * Returns the current location of the mouse. * @return the mouse location */ public Point getMouseLocation() { return mouse; } /** * Returns <code>true</code> if the ALT key is pressed. * @return <code>true</code> if the ALT key is pressed */ public boolean isAltKeyDown() { return (modifiers & SWT.ALT) != 0; } /** * Returns <code>true</code> if any of the mouse buttons are pressed. * @return <code>true</code> if any of the mouse buttons are pressed */ public boolean isAnyButtonDown() { return getFlag(2 | 4 | 8 | 16 | 32); } /** * Returns <code>true</code> if the CTRL key is pressed. * @return <code>true</code> of CTRL pressed */ public boolean isControlKeyDown() { return (modifiers & SWT.CONTROL) != 0; } /** * Returns <code>true</code> if any of the given mod keys are pressed. * @param mod SWT.MOD1, SWT.MOD2, SWT.MOD3, SWT.MOD4 or any combination thereof * @return <code>true</code> if the given mod key is pressed * @since 3.1 */ public boolean isModKeyDown(int mod) { return (modifiers & mod) != 0; } /** * Returns <code>true</code> if the specified button is down. * @param which which button * @return <code>true</code> if the button is down */ public boolean isMouseButtonDown(int which) { return getFlag(1 << which); } /** * Returns <code>true</code> if the SHIFT key is pressed. * @return <code>true</code> if SHIFT pressed */ public boolean isShiftKeyDown() { return (modifiers & SWT.SHIFT) != 0; } /** * Sets the keyboard input based on the KeyEvent. * @param ke the key event providing the input */ public void setInput(KeyEvent ke) { modifiers = ke.stateMask; } /** * Sets the mouse and keyboard input based on the MouseEvent. * @param me the mouse event providing the input */ public void setInput(MouseEvent me) { setMouseLocation(me.x, me.y); modifiers = me.stateMask; if (verifyMouseButtons) { setMouseButton(1, (modifiers & SWT.BUTTON1) != 0); setMouseButton(2, (modifiers & SWT.BUTTON2) != 0); setMouseButton(3, (modifiers & SWT.BUTTON3) != 0); setMouseButton(4, (modifiers & SWT.BUTTON4) != 0); setMouseButton(5, (modifiers & SWT.BUTTON5) != 0); verifyMouseButtons = false; } } /** * Sets mouse button # <code>which</code> to be pressed if <code>state</code> is true. * @param which which button * @param state <code>true</code> if button down */ public void setMouseButton(int which, boolean state) { setFlag(1 << which, state); } /** * Sets the current location of the mouse * @param x x location * @param y y location * @since 3.4 */ public void setMouseLocation(int x, int y) { mouse.setLocation(x, y); } } }