blob: b007d9ec301b9c7baa19b3edb329dfc5ff77ee26 [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 org.eclipse.swt.graphics.Cursor;
import org.eclipse.draw2d.IFigure;
import org.eclipse.draw2d.PositionConstants;
import org.eclipse.draw2d.geometry.Dimension;
import org.eclipse.draw2d.geometry.Point;
import org.eclipse.draw2d.geometry.PrecisionRectangle;
import org.eclipse.draw2d.geometry.Rectangle;
import org.eclipse.gef.EditPart;
import org.eclipse.gef.EditPartViewer;
import org.eclipse.gef.GraphicalEditPart;
import org.eclipse.gef.Request;
import org.eclipse.gef.SharedCursors;
import org.eclipse.gef.SnapToHelper;
import org.eclipse.gef.requests.CreateRequest;
import org.eclipse.gef.requests.CreationFactory;
/**
* The CreationTool creates new {@link EditPart EditParts} via a
* {@link CreationFactory}. If the user simply clicks on the viewer, the default
* sized EditPart will be created at that point. If the user clicks and drags,
* the created EditPart will be sized based on where the user clicked and
* dragged.
*/
public class CreationTool extends TargetingTool {
/**
* Property to be used in {@link AbstractTool#setProperties(java.util.Map)}
* for {@link #setFactory(CreationFactory)}.
*/
public static final Object PROPERTY_CREATION_FACTORY = "factory"; //$NON-NLS-1$
private CreationFactory factory;
private SnapToHelper helper;
/**
* Default constructor. Sets the default and disabled cursors.
*/
public CreationTool() {
setDefaultCursor(SharedCursors.CURSOR_TREE_ADD);
setDisabledCursor(SharedCursors.NO);
}
/**
* Constructs a new CreationTool with the given factory.
*
* @param aFactory
* the creation factory
*/
public CreationTool(CreationFactory aFactory) {
this();
setFactory(aFactory);
}
/**
* @see org.eclipse.gef.tools.AbstractTool#applyProperty(java.lang.Object,
* java.lang.Object)
*/
protected void applyProperty(Object key, Object value) {
if (PROPERTY_CREATION_FACTORY.equals(key)) {
if (value instanceof CreationFactory)
setFactory((CreationFactory) value);
return;
}
super.applyProperty(key, value);
}
/**
* @see org.eclipse.gef.tools.AbstractTool#calculateCursor()
*/
protected Cursor calculateCursor() {
/*
* Fix for Bug# 66010 The following two lines of code were added for the
* case where a tool is activated via the keyboard (that code hasn't
* been released yet). However, they were causing a problem as described
* in 66010. Since the keyboard activation code is not being released
* for 3.0, the following lines are being commented out.
*/
// if (isInState(STATE_INITIAL))
// return getDefaultCursor();
return super.calculateCursor();
}
/**
* Creates a {@link CreateRequest} and sets this tool's factory on the
* request.
*
* @see org.eclipse.gef.tools.TargetingTool#createTargetRequest()
*/
protected Request createTargetRequest() {
CreateRequest request = new CreateRequest();
request.setFactory(getFactory());
return request;
}
/**
* @see org.eclipse.gef.Tool#deactivate()
*/
public void deactivate() {
super.deactivate();
helper = null;
}
/**
* @see org.eclipse.gef.tools.AbstractTool#getCommandName()
*/
protected String getCommandName() {
return REQ_CREATE;
}
/**
* Cast the target request to a CreateRequest and returns it.
*
* @return the target request as a CreateRequest
* @see TargetingTool#getTargetRequest()
*/
protected CreateRequest getCreateRequest() {
return (CreateRequest) getTargetRequest();
}
/**
* @see org.eclipse.gef.tools.AbstractTool#getDebugName()
*/
protected String getDebugName() {
return "Creation Tool";//$NON-NLS-1$
}
/**
* Returns the creation factory used to create the new EditParts.
*
* @return the creation factory
*/
protected CreationFactory getFactory() {
return factory;
}
/**
* The creation tool only works by clicking mouse button 1 (the left mouse
* button in a right-handed world). If any other button is pressed, the tool
* goes into an invalid state. Otherwise, it goes into the drag state,
* updates the request's location and calls
* {@link TargetingTool#lockTargetEditPart(EditPart)} with the edit part
* that was just clicked on.
*
* @see org.eclipse.gef.tools.AbstractTool#handleButtonDown(int)
*/
protected boolean handleButtonDown(int button) {
if (button != 1) {
setState(STATE_INVALID);
handleInvalidInput();
return true;
}
if (stateTransition(STATE_INITIAL, STATE_DRAG)) {
getCreateRequest().setLocation(getLocation());
lockTargetEditPart(getTargetEditPart());
// Snap only when size on drop is employed
if (getTargetEditPart() != null)
helper = (SnapToHelper) getTargetEditPart().getAdapter(
SnapToHelper.class);
}
return true;
}
/**
* If the tool is currently in a drag or drag-in-progress state, it goes
* into the terminal state, performs some cleanup (erasing feedback,
* unlocking target edit part), and then calls {@link #performCreation(int)}
* .
*
* @see org.eclipse.gef.tools.AbstractTool#handleButtonUp(int)
*/
protected boolean handleButtonUp(int button) {
if (stateTransition(STATE_DRAG | STATE_DRAG_IN_PROGRESS, STATE_TERMINAL)) {
// HACK for DND in RAP
handleMove();
eraseTargetFeedback();
unlockTargetEditPart();
performCreation(button);
}
setState(STATE_TERMINAL);
handleFinished();
return true;
}
/**
* Updates the request, sets the current command, and asks to show feedback.
*
* @see org.eclipse.gef.tools.AbstractTool#handleDragInProgress()
*/
protected boolean handleDragInProgress() {
if (isInState(STATE_DRAG_IN_PROGRESS)) {
updateTargetRequest();
setCurrentCommand(getCommand());
showTargetFeedback();
}
return true;
}
/**
* @see org.eclipse.gef.tools.AbstractTool#handleDragStarted()
*/
protected boolean handleDragStarted() {
return stateTransition(STATE_DRAG, STATE_DRAG_IN_PROGRESS);
}
/**
* If the user is in the middle of creating a new edit part, the tool erases
* feedback and goes into the invalid state when focus is lost.
*
* @see org.eclipse.gef.tools.AbstractTool#handleFocusLost()
*/
protected boolean handleFocusLost() {
if (isInState(STATE_DRAG | STATE_DRAG_IN_PROGRESS)) {
eraseTargetFeedback();
setState(STATE_INVALID);
handleFinished();
return true;
}
return false;
}
/**
* @see org.eclipse.gef.tools.TargetingTool#handleHover()
*/
protected boolean handleHover() {
if (isInState(STATE_INITIAL))
updateAutoexposeHelper();
return true;
}
/**
* Updates the request and mouse target, gets the current command and asks
* to show feedback.
*
* @see org.eclipse.gef.tools.AbstractTool#handleMove()
*/
protected boolean handleMove() {
updateTargetRequest();
updateTargetUnderMouse();
setCurrentCommand(getCommand());
showTargetFeedback();
return true;
}
/**
* Executes the current command and selects the newly created object. The
* button that was released to cause this creation is passed in, but since
* {@link #handleButtonDown(int)} goes into the invalid state if the button
* pressed is not button 1, this will always be button 1.
*
* @param button
* the button that was pressed
*/
protected void performCreation(int button) {
EditPartViewer viewer = getCurrentViewer();
executeCurrentCommand();
selectAddedObject(viewer);
}
/*
* Add the newly created object to the viewer's selected objects.
*/
private void selectAddedObject(EditPartViewer viewer) {
final Object model = getCreateRequest().getNewObject();
if (model == null || viewer == null)
return;
Object editpart = viewer.getEditPartRegistry().get(model);
if (editpart instanceof EditPart) {
// Force the new object to get positioned in the viewer.
viewer.flush();
viewer.select((EditPart) editpart);
}
}
/**
* Sets the creation factory used to create the new edit parts.
*
* @param factory
* the factory
*/
public void setFactory(CreationFactory factory) {
this.factory = factory;
}
/**
* Sets the location (and size if the user is performing size-on-drop) of
* the request.
*
* @see org.eclipse.gef.tools.TargetingTool#updateTargetRequest()
*/
protected void updateTargetRequest() {
CreateRequest createRequest = getCreateRequest();
if (isInState(STATE_DRAG_IN_PROGRESS)) {
Point loq = getStartLocation();
Rectangle bounds = new Rectangle(loq, loq);
bounds.union(loq.getTranslated(getDragMoveDelta()));
createRequest.setSize(bounds.getSize());
createRequest.setLocation(bounds.getLocation());
createRequest.getExtendedData().clear();
createRequest.setSnapToEnabled(!getCurrentInput().isModKeyDown(
MODIFIER_NO_SNAPPING));
if (helper != null && createRequest.isSnapToEnabled()) {
PrecisionRectangle baseRect = new PrecisionRectangle(bounds);
PrecisionRectangle result = baseRect.getPreciseCopy();
helper.snapRectangle(createRequest, PositionConstants.NSEW,
baseRect, result);
createRequest.setLocation(result.getLocation());
createRequest.setSize(result.getSize());
}
enforceConstraintsForSizeOnDropCreate(createRequest);
} else {
createRequest.setSize(null);
createRequest.setLocation(getLocation());
createRequest.setSnapToEnabled(false);
}
}
/**
* Ensures size constraints (by default minimum and maximum) are respected
* by the given request. May be overwritten by clients to enforce additional
* constraints.
*
* @since 3.7
*/
protected void enforceConstraintsForSizeOnDropCreate(CreateRequest request) {
CreateRequest createRequest = (CreateRequest) getTargetRequest();
if (createRequest.getSize() != null) {
// ensure create request respects minimum and maximum size
// constraints
PrecisionRectangle constraint = new PrecisionRectangle(
createRequest.getLocation(), createRequest.getSize());
((GraphicalEditPart) getTargetEditPart()).getContentPane()
.translateToRelative(constraint);
constraint.setSize(Dimension.max(constraint.getSize(),
getMinimumSizeFor(createRequest)));
constraint.setSize(Dimension.min(constraint.getSize(),
getMaximumSizeFor(createRequest)));
((GraphicalEditPart) getTargetEditPart()).getContentPane()
.translateToRelative(constraint);
createRequest.setSize(constraint.getSize());
}
}
/**
* Determines the <em>maximum</em> size for CreateRequest's size on drop. It
* is called from {@link #enforceConstraintsForSizeOnDropCreate(CreateRequest)}
* during creation. By default, a large <code>Dimension</code> is returned.
*
* @param request
* the request.
* @return the minimum size
* @since 3.7
*/
protected Dimension getMaximumSizeFor(CreateRequest request) {
return IFigure.MAX_DIMENSION;
}
/**
* Determines the <em>minimum</em> size for CreateRequest's size on drop. It
* is called from {@link #enforceConstraintsForSizeOnDropCreate(CreateRequest)}
* during creation. By default, a small <code>Dimension</code> is returned.
*
* @param request
* the request.
* @return the minimum size
* @since 3.7
*/
protected Dimension getMinimumSizeFor(CreateRequest request) {
return IFigure.MIN_DIMENSION;
}
}