/******************************************************************************* * Copyright (c) 2000, 2010, 2012 IBM Corporation, Gerhardt Informatics Kft. 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 * Gerhardt Informatics Kft. - GEFGWT port *******************************************************************************/ package org.eclipse.gef.tools; 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.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; 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.Event; import org.eclipse.swt.widgets.Scrollable; /** * 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; static { // if (Platform.OS_MACOSX.equals(Platform.getOS())) { // MODIFIER_NO_SNAPPING = SWT.CTRL; // } else { MODIFIER_NO_SNAPPING = SWT.ALT; // } } 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; 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); setStartLocation(new Point(me.x, 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; // place the mouse cursor at the calculated position within the viewer org.eclipse.swt.graphics.Point cursorLocation = new org.eclipse.swt.graphics.Point( p.x, p.y); cursorLocation = c.toDisplay(cursorLocation); // calling Display#setCursorLocation(Point) will cause an SWT.MouseMove // event to be dispatched as a result, so that mouseMove(MouseEvent, // EditPartViewer) will be triggered as a result, which will react to // the movement by delegating to handleMove() or handleDragInProgress(). c.getDisplay().setCursorLocation(cursorLocation); } /** * 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); // Log.info("stateTransition(" + start + "," + 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); } } }