blob: 88a95625f7dc106fb3baf61b7a7b8c6d5c66b8d9 [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.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;
}
}