/******************************************************************************* * 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); viewer.flush(); if (editpart != null && editpart instanceof EditPart && ((EditPart) editpart).isSelectable()) { // Force the new object to get positioned in the viewer. 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() .translateToAbsolute(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; } }