001: /*******************************************************************************
002: * Copyright (c) 2000, 2007 IBM Corporation and others.
003: * All rights reserved. This program and the accompanying materials
004: * are made available under the terms of the Eclipse Public License v1.0
005: * which accompanies this distribution, and is available at
006: * http://www.eclipse.org/legal/epl-v10.html
007: *
008: * Contributors:
009: * IBM Corporation - initial API and implementation
010: *******************************************************************************/package org.eclipse.jface.viewers;
011:
012: import org.eclipse.swt.dnd.DND;
013: import org.eclipse.swt.dnd.DropTargetAdapter;
014: import org.eclipse.swt.dnd.DropTargetEvent;
015: import org.eclipse.swt.dnd.TransferData;
016: import org.eclipse.swt.graphics.Point;
017: import org.eclipse.swt.graphics.Rectangle;
018: import org.eclipse.swt.widgets.Item;
019: import org.eclipse.swt.widgets.TableItem;
020: import org.eclipse.swt.widgets.TreeItem;
021:
022: /**
023: * This adapter class provides generic drag-and-drop support for a viewer.
024: * <p>
025: * Subclasses must implement the following methods:
026: * <ul>
027: * <li><code>validateDrop</code> - identifies valid drop targets in viewer</li>
028: * <li><code>performDrop</code> - carries out a drop into a viewer</li>
029: * </ul>
030: * The <code>setFeedbackEnabled</code> method can be called to turn on and off
031: * visual insertion feedback (on by default).
032: * </p>
033: */
034: public abstract class ViewerDropAdapter extends DropTargetAdapter {
035:
036: /**
037: * Constant describing the position of the cursor relative
038: * to the target object. This means the mouse is positioned
039: * slightly before the target.
040: * @see #getCurrentLocation()
041: */
042: public static final int LOCATION_BEFORE = 1;
043:
044: /**
045: * Constant describing the position of the cursor relative
046: * to the target object. This means the mouse is positioned
047: * slightly after the target.
048: * @see #getCurrentLocation()
049: */
050: public static final int LOCATION_AFTER = 2;
051:
052: /**
053: * Constant describing the position of the cursor relative
054: * to the target object. This means the mouse is positioned
055: * directly on the target.
056: * @see #getCurrentLocation()
057: */
058: public static final int LOCATION_ON = 3;
059:
060: /**
061: * Constant describing the position of the cursor relative
062: * to the target object. This means the mouse is not positioned
063: * over or near any valid target.
064: * @see #getCurrentLocation()
065: */
066: public static final int LOCATION_NONE = 4;
067:
068: /**
069: * The viewer to which this drop support has been added.
070: */
071: private Viewer viewer;
072:
073: /**
074: * The current operation.
075: */
076: private int currentOperation = DND.DROP_NONE;
077:
078: /**
079: * The last valid operation.
080: */
081: private int lastValidOperation = DND.DROP_NONE;
082:
083: /**
084: * The data item currently under the mouse.
085: */
086: private Object currentTarget;
087:
088: /**
089: * Information about the position of the mouse relative to the
090: * target (before, on, or after the target. Location is one of
091: * the <code>LOCATION_* </code> constants defined in this type.
092: */
093: private int currentLocation;
094:
095: /**
096: * A flag that allows adapter users to turn the insertion
097: * feedback on or off. Default is <code>true</code>.
098: */
099: private boolean feedbackEnabled = true;
100:
101: /**
102: * A flag that allows adapter users to turn auto scrolling
103: * on or off. Default is <code>true</code>.
104: */
105: private boolean scrollEnabled = true;
106:
107: /**
108: * A flag that allows adapter users to turn auto
109: * expanding on or off. Default is <code>true</code>.
110: */
111: private boolean expandEnabled = true;
112:
113: /**
114: * A flag that allows adapter users to turn selection feedback
115: * on or off. Default is <code>true</code>.
116: */
117: private boolean selectFeedbackEnabled = true;
118:
119: /**
120: * Creates a new drop adapter for the given viewer.
121: *
122: * @param viewer the viewer
123: */
124: protected ViewerDropAdapter(Viewer viewer) {
125: this .viewer = viewer;
126: }
127:
128: /**
129: * Returns the position of the given event's coordinates relative to its target.
130: * The position is determined to be before, after, or on the item, based on
131: * some threshold value.
132: *
133: * @param event the event
134: * @return one of the <code>LOCATION_* </code>constants defined in this class
135: */
136: protected int determineLocation(DropTargetEvent event) {
137: if (!(event.item instanceof Item)) {
138: return LOCATION_NONE;
139: }
140: Item item = (Item) event.item;
141: Point coordinates = new Point(event.x, event.y);
142: coordinates = viewer.getControl().toControl(coordinates);
143: if (item != null) {
144: Rectangle bounds = getBounds(item);
145: if (bounds == null) {
146: return LOCATION_NONE;
147: }
148: if ((coordinates.y - bounds.y) < 5) {
149: return LOCATION_BEFORE;
150: }
151: if ((bounds.y + bounds.height - coordinates.y) < 5) {
152: return LOCATION_AFTER;
153: }
154: }
155: return LOCATION_ON;
156: }
157:
158: /**
159: * Returns the target item of the given drop event.
160: *
161: * @param event the event
162: * @return The target of the drop, may be <code>null</code>.
163: */
164: protected Object determineTarget(DropTargetEvent event) {
165: return event.item == null ? null : event.item.getData();
166: }
167:
168: /* (non-Javadoc)
169: * Method declared on DropTargetAdapter.
170: * The mouse has moved over the drop target. If the
171: * target item has changed, notify the action and check
172: * that it is still enabled.
173: */
174: private void doDropValidation(DropTargetEvent event) {
175: //update last valid operation
176: if (event.detail != DND.DROP_NONE) {
177: lastValidOperation = event.detail;
178: }
179: //valid drop and set event detail accordingly
180: if (validateDrop(currentTarget, event.detail,
181: event.currentDataType)) {
182: currentOperation = lastValidOperation;
183: } else {
184: currentOperation = DND.DROP_NONE;
185: }
186: event.detail = currentOperation;
187: }
188:
189: /* (non-Javadoc)
190: * Method declared on DropTargetAdapter.
191: * The drag has entered this widget's region. See
192: * if the drop should be allowed.
193: */
194: public void dragEnter(DropTargetEvent event) {
195: currentTarget = determineTarget(event);
196: doDropValidation(event);
197: }
198:
199: /* (non-Javadoc)
200: * Method declared on DropTargetAdapter.
201: * The drop operation has changed, see if the action
202: * should still be enabled.
203: */
204: public void dragOperationChanged(DropTargetEvent event) {
205: currentTarget = determineTarget(event);
206: doDropValidation(event);
207: }
208:
209: /* (non-Javadoc)
210: * Method declared on DropTargetAdapter.
211: * The mouse has moved over the drop target. If the
212: * target item has changed, notify the action and check
213: * that it is still enabled.
214: */
215: public void dragOver(DropTargetEvent event) {
216: //use newly revealed item as target if scrolling occurs
217: Object target = determineTarget(event);
218:
219: //set the location feedback
220: int oldLocation = currentLocation;
221: currentLocation = determineLocation(event);
222: setFeedback(event, currentLocation);
223:
224: //see if anything has really changed before doing validation.
225: if (target != currentTarget || currentLocation != oldLocation) {
226: currentTarget = target;
227: doDropValidation(event);
228: }
229: }
230:
231: /* (non-Javadoc)
232: * Method declared on DropTargetAdapter.
233: * The user has dropped something on the desktop viewer.
234: */
235: public void drop(DropTargetEvent event) {
236: currentLocation = determineLocation(event);
237:
238: //perform the drop behavior
239: if (!performDrop(event.data)) {
240: event.detail = DND.DROP_NONE;
241: }
242: currentOperation = event.detail;
243: }
244:
245: /* (non-Javadoc)
246: * Method declared on DropTargetAdapter.
247: * Last chance for the action to disable itself
248: */
249: public void dropAccept(DropTargetEvent event) {
250: if (!validateDrop(currentTarget, event.detail,
251: event.currentDataType)) {
252: event.detail = DND.DROP_NONE;
253: }
254: }
255:
256: /**
257: * Returns the bounds of the given SWT tree or table item.
258: *
259: * @param item the SWT Item
260: * @return the bounds, or <code>null</code> if it is not a known type of item
261: */
262: protected Rectangle getBounds(Item item) {
263: if (item instanceof TreeItem) {
264: return ((TreeItem) item).getBounds();
265: }
266: if (item instanceof TableItem) {
267: return ((TableItem) item).getBounds(0);
268: }
269: return null;
270: }
271:
272: /**
273: * Returns a constant describing the position of the mouse relative to the
274: * target (before, on, or after the target.
275: *
276: * @return one of the <code>LOCATION_* </code> constants defined in this type
277: */
278: protected int getCurrentLocation() {
279: return currentLocation;
280: }
281:
282: /**
283: * Returns the current operation.
284: *
285: * @return a <code>DROP_*</code> constant from class <code>DND</code>
286: *
287: * @see DND#DROP_COPY
288: * @see DND#DROP_MOVE
289: * @see DND#DROP_LINK
290: * @see DND#DROP_NONE
291: */
292: protected int getCurrentOperation() {
293: return currentOperation;
294: }
295:
296: /**
297: * Returns the target object currently under the mouse.
298: *
299: * @return the current target object
300: */
301: protected Object getCurrentTarget() {
302: return currentTarget;
303: }
304:
305: /**
306: * Returns whether visible insertion feedback should be presented to the user.
307: * <p>
308: * Typical insertion feedback is the horizontal insertion bars that appear
309: * between adjacent items while dragging.
310: * </p>
311: *
312: * @return <code>true</code> if visual feedback is desired, and <code>false</code> if not
313: */
314: public boolean getFeedbackEnabled() {
315: return feedbackEnabled;
316: }
317:
318: /**
319: * Returns the object currently selected by the viewer.
320: *
321: * @return the selected object, or <code>null</code> if either no object or
322: * multiple objects are selected
323: */
324: protected Object getSelectedObject() {
325: ISelection selection = viewer.getSelection();
326: if (selection instanceof IStructuredSelection
327: && !selection.isEmpty()) {
328: IStructuredSelection structured = (IStructuredSelection) selection;
329: return structured.getFirstElement();
330: }
331: return null;
332: }
333:
334: /**
335: * @return the viewer to which this drop support has been added.
336: */
337: protected Viewer getViewer() {
338: return viewer;
339: }
340:
341: /**
342: * @deprecated this method should not be used. Exception handling has been
343: * removed from DropTargetAdapter methods overridden by this class.
344: * Handles any exception that occurs during callback, including
345: * rethrowing behavior.
346: * <p>
347: * [Issue: Implementation prints stack trace and eats exception to avoid
348: * crashing VA/J.
349: * Consider conditionalizing the implementation to do one thing in VAJ
350: * and something more reasonable in other operating environments.
351: * ]
352: * </p>
353: *
354: * @param exception the exception
355: * @param event the event
356: */
357: protected void handleException(Throwable exception,
358: DropTargetEvent event) {
359: // Currently we never rethrow because VA/Java crashes if an SWT
360: // callback throws anything. Generally catching Throwable is bad, but in
361: // this cases it's better than hanging the image.
362: exception.printStackTrace();
363: event.detail = DND.DROP_NONE;
364: }
365:
366: /**
367: * Performs any work associated with the drop.
368: * <p>
369: * Subclasses must implement this method to provide drop behavior.
370: * </p>
371: *
372: * @param data the drop data
373: * @return <code>true</code> if the drop was successful, and
374: * <code>false</code> otherwise
375: */
376: public abstract boolean performDrop(Object data);
377:
378: /* (non-Javadoc)
379: * Method declared on DropTargetAdapter.
380: * The mouse has moved over the drop target. If the
381: * target item has changed, notify the action and check
382: * that it is still enabled.
383: */
384: private void setFeedback(DropTargetEvent event, int location) {
385: if (feedbackEnabled) {
386: switch (location) {
387: case LOCATION_BEFORE:
388: event.feedback = DND.FEEDBACK_INSERT_BEFORE;
389: break;
390: case LOCATION_AFTER:
391: event.feedback = DND.FEEDBACK_INSERT_AFTER;
392: break;
393: case LOCATION_ON:
394: default:
395: event.feedback = DND.FEEDBACK_SELECT;
396: break;
397: }
398: }
399:
400: // Explicitly inhibit SELECT feedback if desired
401: if (!selectFeedbackEnabled) {
402: event.feedback &= ~DND.FEEDBACK_SELECT;
403: }
404:
405: if (expandEnabled) {
406: event.feedback |= DND.FEEDBACK_EXPAND;
407: }
408: if (scrollEnabled) {
409: event.feedback |= DND.FEEDBACK_SCROLL;
410: }
411: }
412:
413: /**
414: * Sets whether visible insertion feedback should be presented to the user.
415: * <p>
416: * Typical insertion feedback is the horizontal insertion bars that appear
417: * between adjacent items while dragging.
418: * </p>
419: *
420: * @param value
421: * <code>true</code> if visual feedback is desired, and
422: * <code>false</code> if not
423: */
424: public void setFeedbackEnabled(boolean value) {
425: feedbackEnabled = value;
426: }
427:
428: /**
429: * Sets whether selection feedback should be provided during dragging.
430: *
431: * @param value <code>true</code> if selection feedback is desired, and
432: * <code>false</code> if not
433: *
434: * @since 3.2
435: */
436: public void setSelectionFeedbackEnabled(boolean value) {
437: selectFeedbackEnabled = value;
438: }
439:
440: /**
441: * Sets whether auto scrolling and expanding should be provided during dragging.
442: *
443: * @param value <code>true</code> if scrolling and expanding is desired, and
444: * <code>false</code> if not
445: * @since 2.0
446: */
447: public void setScrollExpandEnabled(boolean value) {
448: expandEnabled = value;
449: scrollEnabled = value;
450: }
451:
452: /**
453: * Sets whether auto expanding should be provided during dragging.
454: *
455: * @param value <code>true</code> if expanding is desired, and
456: * <code>false</code> if not
457: * @since 3.4
458: */
459: public void setExpandEnabled(boolean value) {
460: expandEnabled = value;
461: }
462:
463: /**
464: * Sets whether auto scrolling should be provided during dragging.
465: *
466: * @param value <code>true</code> if scrolling is desired, and
467: * <code>false</code> if not
468: * @since 3.4
469: */
470: public void setScrollEnabled(boolean value) {
471: scrollEnabled = value;
472: }
473:
474: /**
475: * Validates dropping on the given object. This method is called whenever some
476: * aspect of the drop operation changes.
477: * <p>
478: * Subclasses must implement this method to define which drops make sense.
479: * </p>
480: *
481: * @param target the object that the mouse is currently hovering over, or
482: * <code>null</code> if the mouse is hovering over empty space
483: * @param operation the current drag operation (copy, move, etc.)
484: * @param transferType the current transfer type
485: * @return <code>true</code> if the drop is valid, and <code>false</code>
486: * otherwise
487: */
488: public abstract boolean validateDrop(Object target, int operation,
489: TransferData transferType);
490: }
|