blob: 3e8be00592c571a864dc3513fc737e913285847d [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2010 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.gef.tools;
import java.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.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.core.runtime.Platform;
import org.eclipse.draw2d.IFigure;
import org.eclipse.draw2d.geometry.Dimension;
import org.eclipse.draw2d.geometry.Point;
import org.eclipse.draw2d.rap.swt.SWT;
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;
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;
org.eclipse.swt.graphics.Point swt = new org.eclipse.swt.graphics.Point(
p.x, p.y);
swt = c.toDisplay(swt);
// UNSUPPORTED - api not supported in RAP
// 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) {
// UNSUPPORTED - capture api not implemented in RAP
// 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);
}
}
}