/******************************************************************************* * Copyright (c) 2007, 2011 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.jdt.internal.ui.dnd; import org.eclipse.swt.dnd.DND; import org.eclipse.swt.dnd.DropTargetAdapter; import org.eclipse.swt.dnd.DropTargetEvent; import org.eclipse.swt.dnd.TransferData; import org.eclipse.swt.graphics.Point; import org.eclipse.swt.graphics.Rectangle; import org.eclipse.swt.widgets.Item; import org.eclipse.swt.widgets.TableItem; import org.eclipse.swt.widgets.TreeItem; import org.eclipse.jface.viewers.ISelection; import org.eclipse.jface.viewers.IStructuredSelection; import org.eclipse.jface.viewers.Viewer; /** * This adapter class provides generic drag-and-drop support for a viewer. * <p> * Subclasses must implement the following methods: * <ul> * <li><code>validateDrop</code> - identifies valid drop targets in viewer</li> * <li><code>performDrop</code> - carries out a drop into a viewer</li> * </ul> * The <code>setFeedbackEnabled</code> method can be called to turn on and off * visual insertion feedback (on by default). * </p> * <p> * THIS IS (ALMOST) A ONE-TO-ONE COPY OF PLATFORMS ViewerDropAdapter * REFERE THERE FIRST IF YOU NEED TO FIX SOMETHING. * </p> */ public abstract class JdtViewerDropAdapter extends DropTargetAdapter { /** * Constant describing the position of the cursor relative * to the target object. This means the mouse is positioned * slightly before the target. * @see #getCurrentLocation() */ public static final int LOCATION_BEFORE = 1; /** * Constant describing the position of the cursor relative * to the target object. This means the mouse is positioned * slightly after the target. * @see #getCurrentLocation() */ public static final int LOCATION_AFTER = 2; /** * Constant describing the position of the cursor relative * to the target object. This means the mouse is positioned * directly on the target. * @see #getCurrentLocation() */ public static final int LOCATION_ON = 3; /** * Constant describing the position of the cursor relative * to the target object. This means the mouse is not positioned * over or near any valid target. * @see #getCurrentLocation() */ public static final int LOCATION_NONE = 4; /** * The viewer to which this drop support has been added. */ private Viewer viewer; /** * The current operation. */ private int currentOperation = DND.DROP_NONE; /** * The last valid operation. */ private int lastValidOperation = DND.DROP_NONE; /** * The data item currently under the mouse. */ private Object currentTarget; /** * Information about the position of the mouse relative to the * target (before, on, or after the target. Location is one of * the <code>LOCATION_* </code> constants defined in this type. */ private int currentLocation; /** * A flag that allows adapter users to turn the insertion * feedback on or off. Default is <code>true</code>. */ private boolean feedbackEnabled = true; /** * A flag that allows adapter users to turn auto scrolling * on or off. Default is <code>true</code>. */ private boolean scrollEnabled = true; /** * A flag that allows adapter users to turn auto * expanding on or off. Default is <code>true</code>. */ private boolean expandEnabled = true; /** * A flag that allows adapter users to turn selection feedback * on or off. Default is <code>true</code>. */ private boolean selectFeedbackEnabled = true; /** * Creates a new drop adapter for the given viewer. * * @param viewer the viewer */ protected JdtViewerDropAdapter(Viewer viewer) { this.viewer = viewer; } /** * Returns the position of the given event's coordinates relative to its target. * The position is determined to be before, after, or on the item, based on * some threshold value. * * @param event the event * @return one of the <code>LOCATION_* </code>constants defined in this class */ protected int determineLocation(DropTargetEvent event) { if (!(event.item instanceof Item)) { return LOCATION_NONE; } Item item = (Item) event.item; Point coordinates = new Point(event.x, event.y); coordinates = viewer.getControl().toControl(coordinates); if (item != null) { Rectangle bounds = getBounds(item); if (bounds == null) { return LOCATION_NONE; } if ((coordinates.y - bounds.y) < 5) { return LOCATION_BEFORE; } if ((bounds.y + bounds.height - coordinates.y) < 5) { return LOCATION_AFTER; } } return LOCATION_ON; } /** * Returns the target item of the given drop event. * * @param event the event * @return The target of the drop, may be <code>null</code>. */ protected Object determineTarget(DropTargetEvent event) { return event.item == null ? null : event.item.getData(); } /* (non-Javadoc) * Method declared on DropTargetAdapter. * The mouse has moved over the drop target. If the * target item has changed, notify the action and check * that it is still enabled. */ private void doDropValidation(DropTargetEvent event) { currentOperation= determineOperation(currentTarget, lastValidOperation, event.currentDataType, event.operations); event.detail = currentOperation; setFeedback(event, currentLocation); } /* (non-Javadoc) * Method declared on DropTargetAdapter. * The drag has entered this widget's region. See * if the drop should be allowed. */ @Override public void dragEnter(DropTargetEvent event) { currentTarget = determineTarget(event); currentLocation = determineLocation(event); lastValidOperation = event.detail; doDropValidation(event); } /* (non-Javadoc) * Method declared on DropTargetAdapter. * The drop operation has changed, see if the action * should still be enabled. */ @Override public void dragOperationChanged(DropTargetEvent event) { currentTarget = determineTarget(event); lastValidOperation = event.detail; doDropValidation(event); } /* (non-Javadoc) * Method declared on DropTargetAdapter. * The mouse has moved over the drop target. If the * target item has changed, notify the action and check * that it is still enabled. */ @Override public void dragOver(DropTargetEvent event) { //use newly revealed item as target if scrolling occurs Object target = determineTarget(event); //set the location feedback int oldLocation = currentLocation; currentLocation = determineLocation(event); setFeedback(event, currentLocation); //see if anything has really changed before doing validation. if (target != currentTarget || currentLocation != oldLocation) { currentTarget = target; doDropValidation(event); } } /* (non-Javadoc) * Method declared on DropTargetAdapter. * The user has dropped something on the desktop viewer. */ @Override public void drop(DropTargetEvent event) { currentLocation = determineLocation(event); //perform the drop behavior if (!performDrop(event.data)) { event.detail = DND.DROP_NONE; } currentOperation = event.detail; } /* (non-Javadoc) * Method declared on DropTargetAdapter. * Last chance for the action to disable itself */ @Override public void dropAccept(DropTargetEvent event) { event.detail= determineOperation(currentTarget, event.detail, event.currentDataType, event.operations); } /** * Returns the bounds of the given SWT tree or table item. * * @param item the SWT Item * @return the bounds, or <code>null</code> if it is not a known type of item */ protected Rectangle getBounds(Item item) { if (item instanceof TreeItem) { return ((TreeItem) item).getBounds(); } if (item instanceof TableItem) { return ((TableItem) item).getBounds(0); } return null; } /** * Returns a constant describing the position of the mouse relative to the * target (before, on, or after the target. * * @return one of the <code>LOCATION_* </code> constants defined in this type */ protected int getCurrentLocation() { return currentLocation; } /** * Returns the current operation. * * @return a <code>DROP_*</code> constant from class <code>DND</code> * * @see DND#DROP_COPY * @see DND#DROP_MOVE * @see DND#DROP_LINK * @see DND#DROP_NONE */ protected int getCurrentOperation() { return currentOperation; } /** * Returns the target object currently under the mouse. * * @return the current target object */ protected Object getCurrentTarget() { return currentTarget; } /** * Returns whether visible insertion feedback should be presented to the user. * <p> * Typical insertion feedback is the horizontal insertion bars that appear * between adjacent items while dragging. * </p> * * @return <code>true</code> if visual feedback is desired, and <code>false</code> if not */ public boolean getFeedbackEnabled() { return feedbackEnabled; } /** * Returns the object currently selected by the viewer. * * @return the selected object, or <code>null</code> if either no object or * multiple objects are selected */ protected Object getSelectedObject() { ISelection selection = viewer.getSelection(); if (selection instanceof IStructuredSelection && !selection.isEmpty()) { IStructuredSelection structured = (IStructuredSelection) selection; return structured.getFirstElement(); } return null; } /** * @return the viewer to which this drop support has been added. */ protected Viewer getViewer() { return viewer; } /** * @deprecated this method should not be used. Exception handling has been * removed from DropTargetAdapter methods overridden by this class. * Handles any exception that occurs during callback, including * rethrowing behavior. * <p> * [Issue: Implementation prints stack trace and eats exception to avoid * crashing VA/J. * Consider conditionalizing the implementation to do one thing in VAJ * and something more reasonable in other operating environments. * ] * </p> * * @param exception the exception * @param event the event */ protected void handleException(Throwable exception, DropTargetEvent event) { // Currently we never rethrow because VA/Java crashes if an SWT // callback throws anything. Generally catching Throwable is bad, but in // this cases it's better than hanging the image. exception.printStackTrace(); event.detail = DND.DROP_NONE; } /** * Performs any work associated with the drop. * <p> * Subclasses must implement this method to provide drop behavior. * </p> * * @param data the drop data * @return <code>true</code> if the drop was successful, and * <code>false</code> otherwise */ public abstract boolean performDrop(Object data); /* (non-Javadoc) * Method declared on DropTargetAdapter. * The mouse has moved over the drop target. If the * target item has changed, notify the action and check * that it is still enabled. */ private void setFeedback(DropTargetEvent event, int location) { if (feedbackEnabled) { switch (location) { case LOCATION_BEFORE: event.feedback = DND.FEEDBACK_INSERT_BEFORE; break; case LOCATION_AFTER: event.feedback = DND.FEEDBACK_INSERT_AFTER; break; case LOCATION_ON: default: event.feedback = DND.FEEDBACK_SELECT; break; } } // Explicitly inhibit SELECT feedback if desired if (!selectFeedbackEnabled) { event.feedback &= ~DND.FEEDBACK_SELECT; } if (expandEnabled) { event.feedback |= DND.FEEDBACK_EXPAND; } if (scrollEnabled) { event.feedback |= DND.FEEDBACK_SCROLL; } } /** * Sets whether visible insertion feedback should be presented to the user. * <p> * Typical insertion feedback is the horizontal insertion bars that appear * between adjacent items while dragging. * </p> * * @param value * <code>true</code> if visual feedback is desired, and * <code>false</code> if not */ public void setFeedbackEnabled(boolean value) { feedbackEnabled = value; } /** * Sets whether selection feedback should be provided during dragging. * * @param value <code>true</code> if selection feedback is desired, and * <code>false</code> if not * * @since 3.2 */ public void setSelectionFeedbackEnabled(boolean value) { selectFeedbackEnabled = value; } /** * Sets whether auto scrolling and expanding should be provided during dragging. * * @param value <code>true</code> if scrolling and expanding is desired, and * <code>false</code> if not * @since 2.0 */ public void setScrollExpandEnabled(boolean value) { expandEnabled = value; scrollEnabled = value; } /** * Sets whether auto expanding should be provided during dragging. * * @param value <code>true</code> if expanding is desired, and * <code>false</code> if not * @since 3.4 */ public void setExpandEnabled(boolean value) { expandEnabled = value; } /** * Sets whether auto scrolling should be provided during dragging. * * @param value <code>true</code> if scrolling is desired, and * <code>false</code> if not * @since 3.4 */ public void setScrollEnabled(boolean value) { scrollEnabled = value; } /** * Validates dropping on the given object. This method is called whenever some * aspect of the drop operation changes. * <p> * Subclasses must implement this method to define which drops make sense. * </p> * * @param target the object that the mouse is currently hovering over, or * <code>null</code> if the mouse is hovering over empty space * @param operation the current drag operation (copy, move, etc.) * @param transferType the current transfer type * @return <code>true</code> if the drop is valid, and <code>false</code> * otherwise */ public abstract boolean validateDrop(Object target, int operation, TransferData transferType); /** * Determine the operation which should be executed given the target and the operation * requested by the user. This method is called whenever some aspect of the drop operation * changes. The operation is one of DND#DROP_DEFAULT, DND#DROP_COPY, DND#DROP_MOVE, DND#DROP_LINK. * <p> * The method returns the operation valid in the given context. The result is one of * DND#DROP_NONE, DND#DROP_COPY, DND#DROP_MOVE, DND#DROP_LINK, DND#DROP_DEFAULT. * </p> * <p> * Subclasses can overwrite this method to define which operation does make * sense in the given context. * </p> * * @param target the object that the mouse is currently hovering over, or * <code>null</code> if the mouse is hovering over empty space * @param operation the current drag operation (copy, move, etc.) * @param transferType the current transfer type * @param operations a bitwise OR'ing of the operations that the DragSource can support * @return the operation which will be executed if no modifier key is pressed * by the user * * @see DND#DROP_NONE * @see DND#DROP_MOVE * @see DND#DROP_COPY * @see DND#DROP_LINK */ protected int determineOperation(Object target, int operation, TransferData transferType, int operations) { if (!validateDrop(target, operation, transferType)) { return DND.DROP_NONE; } return operation; } }