| /******************************************************************************* |
| * 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.util.Collection; |
| import java.util.Collections; |
| |
| import org.eclipse.swt.widgets.Display; |
| |
| import org.eclipse.draw2d.geometry.Point; |
| |
| import org.eclipse.gef.AutoexposeHelper; |
| import org.eclipse.gef.EditPart; |
| import org.eclipse.gef.EditPartViewer; |
| import org.eclipse.gef.Request; |
| import org.eclipse.gef.commands.Command; |
| import org.eclipse.gef.commands.UnexecutableCommand; |
| import org.eclipse.gef.requests.TargetRequest; |
| |
| /** |
| * The base implementation for tools which perform targeting of editparts. |
| * Targeting tools may operate using either mouse drags or just mouse moves. |
| * Targeting tools work with a <i>target</i> request. This request is used along |
| * with the mouse location to obtain an active target from the current |
| * EditPartViewer. This target is then asked for the <code>Command</code> that |
| * performs the given request. The target is also asked to show target feedback. |
| * <P> |
| * TargetingTool also provides support for auto-expose (a.k.a. auto-scrolling). |
| * Subclasses that wish to commence auto-expose can do so by calling |
| * {@link #updateAutoexposeHelper()}. An an AutoExposeHelper is found, |
| * auto-scrolling begins. Whenever that helper scrolls the diagram of performs |
| * any other change, <code>handleMove</code> will be called as if the mouse had |
| * moved. This is because the target has probably moved, but there is no input |
| * event to trigger an update of the operation. |
| */ |
| public abstract class TargetingTool extends AbstractTool { |
| |
| private static final int FLAG_LOCK_TARGET = AbstractTool.MAX_FLAG << 1; |
| private static final int FLAG_TARGET_FEEDBACK = AbstractTool.MAX_FLAG << 2; |
| /** |
| * The max flag. |
| */ |
| protected static final int MAX_FLAG = FLAG_TARGET_FEEDBACK; |
| |
| private Request targetRequest; |
| private EditPart targetEditPart; |
| private AutoexposeHelper exposeHelper; |
| |
| /** |
| * Creates the target request that will be used with the target editpart. |
| * This request will be cached and updated as needed. |
| * |
| * @see #getTargetRequest() |
| * @return the new target request |
| */ |
| protected Request createTargetRequest() { |
| Request request = new Request(); |
| request.setType(getCommandName()); |
| return request; |
| } |
| |
| /** |
| * @see org.eclipse.gef.Tool#deactivate() |
| */ |
| public void deactivate() { |
| if (isHoverActive()) |
| resetHover(); |
| eraseTargetFeedback(); |
| targetEditPart = null; |
| targetRequest = null; |
| setAutoexposeHelper(null); |
| super.deactivate(); |
| } |
| |
| /** |
| * Called to perform an iteration of the autoexpose process. If the expose |
| * helper is set, it will be asked to step at the current mouse location. If |
| * it returns true, another expose iteration will be queued. There is no |
| * delay between autoexpose events, other than the time required to perform |
| * the step(). |
| */ |
| protected void doAutoexpose() { |
| if (exposeHelper == null) |
| return; |
| if (exposeHelper.step(getLocation())) { |
| handleAutoexpose(); |
| Display.getCurrent().asyncExec(new QueuedAutoexpose()); |
| } else |
| setAutoexposeHelper(null); |
| } |
| |
| /** |
| * Asks the current target editpart to erase target feedback using the |
| * target request. If target feedback is not being shown, this method does |
| * nothing and returns. Otherwise, the target feedback flag is reset to |
| * false, and the target editpart is asked to erase target feedback. This |
| * methods should rarely be overridden. |
| */ |
| protected void eraseTargetFeedback() { |
| if (!isShowingTargetFeedback()) |
| return; |
| setFlag(FLAG_TARGET_FEEDBACK, false); |
| if (getTargetEditPart() != null) |
| getTargetEditPart().eraseTargetFeedback(getTargetRequest()); |
| } |
| |
| /** |
| * Queries the target editpart for a command. |
| * |
| * @see org.eclipse.gef.tools.AbstractTool#getCommand() |
| */ |
| protected Command getCommand() { |
| if (getTargetEditPart() == null) |
| return null; |
| return getTargetEditPart().getCommand(getTargetRequest()); |
| } |
| |
| /** |
| * Returns a List of objects that should be excluded as potential targets |
| * for the operation. |
| * |
| * @return the list of objects to be excluded as targets |
| */ |
| protected Collection getExclusionSet() { |
| return Collections.EMPTY_LIST; |
| } |
| |
| /** |
| * Returns the conditional object used for obtaining the target editpart |
| * from the current viewer. By default, a conditional is returned that tests |
| * whether an editpart at the current mouse location indicates a target for |
| * the operation's request, using |
| * {@link EditPart#getTargetEditPart(Request)}. If <code>null</code> is |
| * returned, then the conditional fails, and the search continues. |
| * |
| * @see EditPartViewer#findObjectAtExcluding(Point, Collection, |
| * EditPartViewer.Conditional) |
| * @return the targeting conditional |
| */ |
| protected EditPartViewer.Conditional getTargetingConditional() { |
| return new EditPartViewer.Conditional() { |
| public boolean evaluate(EditPart editpart) { |
| return editpart.getTargetEditPart(getTargetRequest()) != null; |
| } |
| }; |
| } |
| |
| /** |
| * Returns <code>null</code> or the current target editpart. |
| * |
| * @return <code>null</code> or a target part |
| */ |
| protected EditPart getTargetEditPart() { |
| return targetEditPart; |
| } |
| |
| /** |
| * Lazily creates and returns the request used when communicating with the |
| * target editpart. |
| * |
| * @return the target request |
| */ |
| protected Request getTargetRequest() { |
| if (targetRequest == null) |
| setTargetRequest(createTargetRequest()); |
| return targetRequest; |
| } |
| |
| /** |
| * This method is called whenever an autoexpose occurs. When an autoexpose |
| * occurs, it is possible that everything in the viewer has moved a little. |
| * Therefore, by default, {@link AbstractTool#handleMove() handleMove()} is |
| * called to simulate the mouse moving even though it didn't. |
| */ |
| protected void handleAutoexpose() { |
| handleMove(); |
| } |
| |
| /** |
| * Called whenever the target editpart has changed. By default, the target |
| * request is updated, and the new target is asked to show feedback. |
| * Subclasses may extend this method if needed. |
| * |
| * @return <code>true</code> |
| */ |
| protected boolean handleEnteredEditPart() { |
| updateTargetRequest(); |
| showTargetFeedback(); |
| return true; |
| } |
| |
| /** |
| * Called whenever the target editpart is about to change. By default, hover |
| * is reset, in the case that a hover was showing something, and the target |
| * being exited is asked to erase its feedback. |
| * |
| * @return <code>true</code> |
| */ |
| protected boolean handleExitingEditPart() { |
| resetHover(); |
| eraseTargetFeedback(); |
| return true; |
| } |
| |
| /** |
| * Called from resetHover() iff hover is active. Subclasses may extend this |
| * method to handle the hover stop event. Returns <code>true</code> if |
| * something was done in response to the call. |
| * |
| * @see AbstractTool#isHoverActive() |
| * @return <code>true</code> if the hover stop is processed in some way |
| */ |
| protected boolean handleHoverStop() { |
| return false; |
| } |
| |
| /** |
| * Called when invalid input is encountered. By default, feedback is erased, |
| * and the current command is set to the unexecutable command. The state |
| * does not change, so the caller must set the state to |
| * {@link AbstractTool#STATE_INVALID}. |
| * |
| * @return <code>true</code> |
| */ |
| protected boolean handleInvalidInput() { |
| eraseTargetFeedback(); |
| setCurrentCommand(UnexecutableCommand.INSTANCE); |
| return true; |
| } |
| |
| /** |
| * An archaic method name that has been left here to force use of the new |
| * name. |
| * |
| * @throws Exception |
| * exc |
| */ |
| protected final void handleLeavingEditPart() throws Exception { |
| } |
| |
| /** |
| * Sets the target to <code>null</code>. |
| * |
| * @see org.eclipse.gef.tools.AbstractTool#handleViewerExited() |
| */ |
| protected boolean handleViewerExited() { |
| setTargetEditPart(null); |
| return true; |
| } |
| |
| /** |
| * Returns <code>true</code> if target feedback is being shown. |
| * |
| * @return <code>true</code> if showing target feedback |
| */ |
| protected boolean isShowingTargetFeedback() { |
| return getFlag(FLAG_TARGET_FEEDBACK); |
| } |
| |
| /** |
| * Return <code>true</code> if the current target is locked. |
| * |
| * @see #lockTargetEditPart(EditPart) |
| * @return <code>true</code> if the target is locked |
| */ |
| protected boolean isTargetLocked() { |
| return getFlag(FLAG_LOCK_TARGET); |
| } |
| |
| /** |
| * Locks-in the given editpart as the target. Updating of the target will |
| * not occur until {@link #unlockTargetEditPart()} is called. |
| * |
| * @param editpart |
| * the target to be locked-in |
| */ |
| protected void lockTargetEditPart(EditPart editpart) { |
| if (editpart == null) { |
| unlockTargetEditPart(); |
| return; |
| } |
| setFlag(FLAG_LOCK_TARGET, true); |
| setTargetEditPart(editpart); |
| } |
| |
| /** |
| * Extended to reset the target lock flag. |
| * |
| * @see org.eclipse.gef.tools.AbstractTool#resetFlags() |
| * @see #lockTargetEditPart(EditPart) |
| */ |
| protected void resetFlags() { |
| setFlag(FLAG_LOCK_TARGET, false); |
| super.resetFlags(); |
| } |
| |
| /** |
| * Resets hovering to inactive. |
| * |
| * @since 3.4 |
| */ |
| protected void resetHover() { |
| if (isHoverActive()) |
| handleHoverStop(); |
| setHoverActive(false); |
| } |
| |
| class QueuedAutoexpose implements Runnable { |
| public void run() { |
| if (exposeHelper != null) |
| doAutoexpose(); |
| } |
| } |
| |
| /** |
| * Sets the active autoexpose helper to the given helper, or |
| * <code>null</code>. If the helper is not <code>null</code>, a runnable is |
| * queued on the event thread that will trigger a subsequent |
| * {@link #doAutoexpose()}. The helper is typically updated only on a hover |
| * event. |
| * |
| * @param helper |
| * the new autoexpose helper or <code>null</code> |
| */ |
| protected void setAutoexposeHelper(AutoexposeHelper helper) { |
| exposeHelper = helper; |
| if (exposeHelper == null) |
| return; |
| Display.getCurrent().asyncExec(new QueuedAutoexpose()); |
| } |
| |
| /** |
| * Sets the target editpart. If the target editpart is changing, this method |
| * will call {@link #handleExitingEditPart()} for the previous target if not |
| * <code>null</code>, and {@link #handleEnteredEditPart()} for the new |
| * target, if not <code>null</code>. |
| * |
| * @param editpart |
| * the new target |
| */ |
| protected void setTargetEditPart(EditPart editpart) { |
| if (editpart != targetEditPart) { |
| if (targetEditPart != null) |
| handleExitingEditPart(); |
| targetEditPart = editpart; |
| if (getTargetRequest() instanceof TargetRequest) |
| ((TargetRequest) getTargetRequest()) |
| .setTargetEditPart(targetEditPart); |
| handleEnteredEditPart(); |
| } |
| } |
| |
| /** |
| * Sets the target request. This method is typically not called; subclasses |
| * normally override {@link #createTargetRequest()}. |
| * |
| * @param req |
| * the target request |
| */ |
| protected void setTargetRequest(Request req) { |
| targetRequest = req; |
| } |
| |
| /** |
| * Asks the target editpart to show target feedback and sets the target |
| * feedback flag. |
| */ |
| protected void showTargetFeedback() { |
| if (getTargetEditPart() != null) |
| getTargetEditPart().showTargetFeedback(getTargetRequest()); |
| setFlag(FLAG_TARGET_FEEDBACK, true); |
| } |
| |
| /** |
| * Releases the targeting lock, and updates the target in case the mouse is |
| * already over a new target. |
| */ |
| protected void unlockTargetEditPart() { |
| setFlag(FLAG_LOCK_TARGET, false); |
| updateTargetUnderMouse(); |
| } |
| |
| /** |
| * Updates the active {@link AutoexposeHelper}. Does nothing if there is |
| * still an active helper. Otherwise, obtains a new helper (possible |
| * <code>null</code>) at the current mouse location and calls |
| * {@link #setAutoexposeHelper(AutoexposeHelper)}. |
| */ |
| protected void updateAutoexposeHelper() { |
| if (exposeHelper != null) |
| return; |
| AutoexposeHelper.Search search; |
| search = new AutoexposeHelper.Search(getLocation()); |
| getCurrentViewer().findObjectAtExcluding(getLocation(), |
| Collections.EMPTY_LIST, search); |
| setAutoexposeHelper(search.result); |
| } |
| |
| /** |
| * Subclasses should override to update the target request. |
| */ |
| protected void updateTargetRequest() { |
| } |
| |
| /** |
| * Updates the target editpart and returns <code>true</code> if the target |
| * changes. The target is updated by using the target conditional and the |
| * target request. If the target has been locked, this method does nothing |
| * and returns <code>false</code>. |
| * |
| * @return <code>true</code> if the target was changed |
| */ |
| protected boolean updateTargetUnderMouse() { |
| if (!isTargetLocked()) { |
| EditPart editPart = getCurrentViewer() |
| .findObjectAtExcluding(getLocation(), getExclusionSet(), |
| getTargetingConditional()); |
| if (editPart != null) |
| editPart = editPart.getTargetEditPart(getTargetRequest()); |
| boolean changed = getTargetEditPart() != editPart; |
| setTargetEditPart(editPart); |
| return changed; |
| } else |
| return false; |
| } |
| |
| /** |
| * Returns <code>null</code> or the current autoexpose helper. |
| * |
| * @return null or a helper |
| */ |
| protected AutoexposeHelper getAutoexposeHelper() { |
| return exposeHelper; |
| } |
| |
| } |