Project

General

Profile

Download (16.3 KB) Statistics
| Branch: | Tag: | Revision:
1
/*******************************************************************************
2
 * Copyright (c) 2000, 2010 IBM Corporation and others.
3
 * All rights reserved. This program and the accompanying materials
4
 * are made available under the terms of the Eclipse Public License v1.0
5
 * which accompanies this distribution, and is available at
6
 * http://www.eclipse.org/legal/epl-v10.html
7
 *
8
 * Contributors:
9
 *     IBM Corporation - initial API and implementation
10
 *******************************************************************************/
11
package org.eclipse.gef.dnd;
12

    
13
import java.util.Collection;
14
import java.util.Collections;
15

    
16
import org.eclipse.swt.dnd.DND;
17
import org.eclipse.swt.dnd.DropTarget;
18
import org.eclipse.swt.dnd.DropTargetEvent;
19
import org.eclipse.swt.dnd.DropTargetListener;
20
import org.eclipse.swt.dnd.Transfer;
21
import org.eclipse.swt.dnd.TransferData;
22

    
23
import org.eclipse.draw2d.geometry.Point;
24

    
25
import org.eclipse.gef.AutoexposeHelper;
26
import org.eclipse.gef.EditPart;
27
import org.eclipse.gef.EditPartViewer;
28
import org.eclipse.gef.Request;
29
import org.eclipse.gef.commands.Command;
30

    
31
/**
32
 * An abstract implementation of TransferDropTargetListener for use with
33
 * {@link EditPartViewer}s. The Viewer's <code>Control</code> should be the Drop
34
 * target. In order to communicate with EditParts in a consistent way,
35
 * DropTargetEvents are processed into {@link Request Requests}.
36
 * <P>
37
 * Dropping is inherently a targeting interaction. This class handles
38
 * calculating the <i>target</i> EditPart. It also handles common targeting
39
 * behavior, such as interacting with the target EditPart or its ancestors to
40
 * achieve things like auto-scroll/auto-expose.
41
 */
42
public abstract class AbstractTransferDropTargetListener implements
43
		TransferDropTargetListener {
44

    
45
	private DropTargetEvent currentEvent;
46
	private AutoexposeHelper exposeHelper;
47
	private boolean hovering = false;
48
	private boolean askForCommand;
49
	private long hoverStartTime = -1;
50
	private Point prevMouseLoc;
51
	private Request request;
52
	private boolean showingFeedback;
53
	private EditPart target;
54
	private Transfer transfer;
55
	private EditPartViewer viewer;
56

    
57
	/**
58
	 * Constructs a new AbstractTransferDropTargetListener and sets the
59
	 * EditPartViewer. If you use this constructor, you must set the Transfer
60
	 * yourself using {@link #setTransfer(Transfer)}.
61
	 * 
62
	 * @param viewer
63
	 *            the EditPartViewer
64
	 */
65
	public AbstractTransferDropTargetListener(EditPartViewer viewer) {
66
		setViewer(viewer);
67
	}
68

    
69
	/**
70
	 * Constructs a new AbstractTransferDropTargetListener and sets the
71
	 * EditPartViewer and Transfer. The Viewer's Control should be the Drop
72
	 * target.
73
	 * 
74
	 * @param viewer
75
	 *            the EditPartViewer
76
	 * @param xfer
77
	 *            the Transfer
78
	 */
79
	public AbstractTransferDropTargetListener(EditPartViewer viewer,
80
			Transfer xfer) {
81
		setViewer(viewer);
82
		setTransfer(xfer);
83
	}
84

    
85
	private EditPart calculateTargetEditPart() {
86
		EditPart ep = getViewer().findObjectAtExcluding(getDropLocation(),
87
				getExclusionSet(), new EditPartViewer.Conditional() {
88
					public boolean evaluate(EditPart editpart) {
89
						return editpart.getTargetEditPart(getTargetRequest()) != null;
90
					}
91
				});
92
		if (ep != null)
93
			ep = ep.getTargetEditPart(getTargetRequest());
94
		return ep;
95
	}
96

    
97
	/**
98
	 * Creates and returns the <code>Request</code> that will be sent to the
99
	 * targeted EditPart. Subclasses can override to create specialized
100
	 * requests.
101
	 * 
102
	 * @return the <code>Request</code> to be used with the <i>target</i>
103
	 *         EditPart
104
	 */
105
	protected Request createTargetRequest() {
106
		return new Request();
107
	}
108

    
109
	/**
110
	 * Stores the information about the current DropTargetEvent. This method may
111
	 * not be called on the listener, because the listener may not be made
112
	 * active until after the mouse has entered the drop target.
113
	 * 
114
	 * @see DropTargetListener#dragEnter(DropTargetEvent)
115
	 */
116
	public void dragEnter(DropTargetEvent event) {
117
		resetHover();
118
		setCurrentEvent(event);
119
	}
120

    
121
	/**
122
	 * Stores the information about the current DropTargetEvent and then calls
123
	 * <code>unload()</code>. Subclasses should override {@link #unload()} to
124
	 * perform actions for this event. For some reason, SWT also calls
125
	 * <code>dragLeave()</code> when the actual drop is performed, even though
126
	 * the mouse has not left the drop target.
127
	 * 
128
	 * @see DropTargetListener#dragLeave(DropTargetEvent)
129
	 */
130
	public void dragLeave(DropTargetEvent event) {
131
		setCurrentEvent(event);
132
		unload();
133
	}
134

    
135
	/**
136
	 * Stores the information about the current DropTargetEvent and then calls
137
	 * <code>handleDragOperationChanged()</code>. Subclasses should override
138
	 * {@link #handleDragOperationChanged()} to perform actions for this event.
139
	 * 
140
	 * @see DropTargetListener#dragOperationChanged(DropTargetEvent)
141
	 */
142
	public void dragOperationChanged(DropTargetEvent event) {
143
		resetHover();
144
		setCurrentEvent(event);
145
		handleDragOperationChanged();
146
	}
147

    
148
	/**
149
	 * Stores the information about the current DropTargetEvent and then calls
150
	 * <code>handleDragOver()</code>. Subclasses should override
151
	 * {@link #handleDragOver()} to perform actions for this event.
152
	 * 
153
	 * @see DropTargetListener#dragOver(org.eclipse.swt.dnd.DropTargetEvent)
154
	 */
155
	public void dragOver(DropTargetEvent event) {
156
		setCurrentEvent(event);
157
		handleDragOver();
158
		if (testAndSet(event)) {
159
			resetHover();
160
		} else {
161
			if (hovering)
162
				return;
163
			long currentTime = event.time;
164
			if (hoverStartTime == -1) {
165
				hoverStartTime = currentTime;
166
			} else if (currentTime - hoverStartTime > 400) {
167
				handleHover();
168
				hovering = true;
169
			}
170
		}
171
	}
172

    
173
	/**
174
	 * Stores the information about the current DropTargetEvent and then calls
175
	 * {@link #handleDrop()}, followed by {@link #unload()}. Subclasses should
176
	 * override these methods to perform actions for this event.
177
	 * 
178
	 * @see DropTargetListener#drop(DropTargetEvent)
179
	 */
180
	public void drop(DropTargetEvent event) {
181
		setCurrentEvent(event);
182
		eraseTargetFeedback();
183
		handleDrop();
184
		unload();
185
	}
186

    
187
	/**
188
	 * Stores the current <code>DropTargetEvent</code> and does nothing. By
189
	 * default, the drop is accepted.
190
	 * 
191
	 * @see DropTargetListener#dropAccept(DropTargetEvent)
192
	 */
193
	public void dropAccept(DropTargetEvent event) {
194
		setCurrentEvent(event);
195
	}
196

    
197
	/**
198
	 * Calls <code>eraseTargetFeedback(Request)</code> on the current
199
	 * <i>target</i>, using the target Request. Does nothing if there is no
200
	 * target, or if the target has not been requested to show target feedback.
201
	 */
202
	protected void eraseTargetFeedback() {
203
		if (getTargetEditPart() != null && showingFeedback) {
204
			showingFeedback = false;
205
			getTargetEditPart().eraseTargetFeedback(getTargetRequest());
206
		}
207
	}
208

    
209
	/**
210
	 * Returns the current command from the target EditPart.
211
	 * 
212
	 * @return The current command from the target EditPart
213
	 */
214
	protected Command getCommand() {
215
		return getTargetEditPart().getCommand(getTargetRequest());
216
	}
217

    
218
	/**
219
	 * Returns the current <code>DropTargetEvent</code>.
220
	 * 
221
	 * @return the current event
222
	 */
223
	public DropTargetEvent getCurrentEvent() {
224
		return currentEvent;
225
	}
226

    
227
	/**
228
	 * Returns the current mouse location, as a {@link Point}. The location is
229
	 * relative to the control's client area.
230
	 * 
231
	 * @return the drop location
232
	 */
233
	protected Point getDropLocation() {
234
		org.eclipse.swt.graphics.Point swt;
235
		swt = new org.eclipse.swt.graphics.Point(getCurrentEvent().x,
236
				getCurrentEvent().y);
237
		DropTarget target = (DropTarget) getCurrentEvent().widget;
238
		swt = target.getControl().toControl(swt);
239
		return new Point(swt.x, swt.y);
240
	}
241

    
242
	/**
243
	 * Returns a Collection of {@link EditPart EditParts} that are to be
244
	 * excluded when searching for the target EditPart.
245
	 * 
246
	 * @return A Collection of EditParts to be excluded
247
	 */
248
	protected Collection getExclusionSet() {
249
		return Collections.EMPTY_LIST;
250
	}
251

    
252
	/**
253
	 * Returns the current <i>target</i> <code>EditPart</code>.
254
	 * 
255
	 * @return the target EditPart
256
	 */
257
	protected EditPart getTargetEditPart() {
258
		return target;
259
	}
260

    
261
	/**
262
	 * Returns the target <code>Request</code>. If the target Request is
263
	 * <code>null</code>, {@link #createTargetRequest()} is called and the newly
264
	 * created Request is returned.
265
	 * 
266
	 * @return the target Request
267
	 */
268
	protected Request getTargetRequest() {
269
		if (request == null)
270
			request = createTargetRequest();
271
		return request;
272
	}
273

    
274
	/**
275
	 * @see TransferDropTargetListener#getTransfer()
276
	 */
277
	public Transfer getTransfer() {
278
		return transfer;
279
	}
280

    
281
	/**
282
	 * Returns the <code>EditPartViewer</code> that is the target of the drop.
283
	 * 
284
	 * @return the EditPartViewer
285
	 */
286
	protected EditPartViewer getViewer() {
287
		return viewer;
288
	}
289

    
290
	/**
291
	 * Called when the user changes the Drag operation. By default, target
292
	 * feedback is erased. The target Request and target EditPart are updated,
293
	 * and target feedback is re-displayed on the new target.
294
	 */
295
	protected void handleDragOperationChanged() {
296
		// Erase any old feedback now, in case the request changes substantially
297
		eraseTargetFeedback();
298

    
299
		// Update request based on the new operation type
300
		updateTargetRequest();
301

    
302
		// Update the target based on the updated request
303
		updateTargetEditPart();
304
	}
305

    
306
	/**
307
	 * Called whenever the User drags over the target. By default, the target
308
	 * Request and target EditPart are updated, feedback is shown, and
309
	 * auto-expose occurs.
310
	 */
311
	protected void handleDragOver() {
312
		updateTargetRequest();
313
		updateTargetEditPart();
314
		showTargetFeedback();
315
		if (exposeHelper != null) {
316
			// If the expose helper does not wish to continue, set helper to
317
			// null.
318
			if (!exposeHelper.step(getDropLocation()))
319
				exposeHelper = null;
320
		}
321
	}
322

    
323
	/**
324
	 * Updates the target Request and target EditPart, and performs the drop. By
325
	 * default, the drop is performed by asking the target EditPart for a
326
	 * Command using the target Request. This Command is then executed on the
327
	 * CommandStack.
328
	 * <P>
329
	 * If there is no target EditPart or no executable Command, the event's
330
	 * <code>detail</code> field is set to <code>DND.DROP_NONE</code>.
331
	 */
332
	protected void handleDrop() {
333
		updateTargetRequest();
334
		updateTargetEditPart();
335

    
336
		if (getTargetEditPart() != null) {
337
			Command command = getCommand();
338
			if (command != null && command.canExecute())
339
				getViewer().getEditDomain().getCommandStack().execute(command);
340
			else
341
				getCurrentEvent().detail = DND.DROP_NONE;
342
		} else
343
			getCurrentEvent().detail = DND.DROP_NONE;
344
	}
345

    
346
	/**
347
	 * Called when a new target EditPart has been entered. By default, the new
348
	 * target is asked to show feedback.
349
	 */
350
	protected void handleEnteredEditPart() {
351
	}
352

    
353
	/**
354
	 * Called as the current target EditPart is being exited. By default, the
355
	 * target is asked to erase feedback.
356
	 */
357
	protected void handleExitingEditPart() {
358
		eraseTargetFeedback();
359
	}
360

    
361
	/**
362
	 * Called when the mouse hovers during drag and drop.
363
	 */
364
	protected void handleHover() {
365
		updateAutoexposeHelper();
366
	}
367

    
368
	/**
369
	 * Called when the mouse resumes motion after having hovered.
370
	 */
371
	protected void handleHoverStop() {
372
	}
373

    
374
	/**
375
	 * Returns <code>true</code> if this TransferDropTargetListener is enabled
376
	 * for the specified <code>DropTargetEvent</code>. By default, this is
377
	 * calculated by comparing the event's {@link DropTargetEvent#dataTypes
378
	 * dataTypes} with the <code>Transfer's</code> supported types (
379
	 * {@link Transfer#isSupportedType(TransferData)}). If a dataType is
380
	 * supported, an attempt is made to find a <i>target</i>
381
	 * <code>EditPart</code> at the current drop location. If a target
382
	 * <code>EditPart</code> is found, <code>true</code> is returned, and the
383
	 * DropTargetEvent's {@link DropTargetEvent#currentDataType} is set to the
384
	 * dataType that matched.
385
	 * 
386
	 * @param event
387
	 *            the DropTargetEvent
388
	 * @return <code>true</code> if this TransferDropTargetListener is enabled
389
	 *         for the given DropTargetEvent
390
	 */
391
	public boolean isEnabled(DropTargetEvent event) {
392
		for (int i = 0; i < event.dataTypes.length; i++) {
393
			if (getTransfer().isSupportedType(event.dataTypes[i])) {
394
				setCurrentEvent(event);
395
				event.currentDataType = event.dataTypes[i];
396
				updateTargetRequest();
397
				EditPart oldTarget = target;
398
				updateTargetEditPart();
399
				boolean result;
400
				if (target == null)
401
					result = false;
402
				else if (askForCommand) {
403
					Command command = getCommand();
404
					result = command != null && command.canExecute();
405
				} else
406
					result = true;
407
				request = null;
408
				target = oldTarget;
409
				return result;
410
			}
411
		}
412
		return false;
413
	}
414

    
415
	/**
416
	 * Returns <code>true</code> if {@link #isEnabled(DropTargetEvent)} is
417
	 * determined by asking the potential target for a Command.
418
	 * 
419
	 * @return <code>true</code> if the target will be queried for a
420
	 *         <code>Command</code>
421
	 * @since 3.1
422
	 */
423
	protected boolean isEnablementDeterminedByCommand() {
424
		return askForCommand;
425
	}
426

    
427
	private void resetHover() {
428
		if (hovering) {
429
			handleHoverStop();
430
			hovering = false;
431
			hoverStartTime = -1;
432
			prevMouseLoc = null;
433
		}
434
	}
435

    
436
	/**
437
	 * Sets the current autoexpose helper.
438
	 * 
439
	 * @param helper
440
	 *            the autoexpose helper
441
	 */
442
	protected void setAutoexposeHelper(AutoexposeHelper helper) {
443
		exposeHelper = helper;
444
	}
445

    
446
	/**
447
	 * Determines if the target editpart should be asked for a Command during
448
	 * {@link #isEnabled(DropTargetEvent)}. For most DND operations, the data is
449
	 * not available, thus asking for a command would not make sense. The
450
	 * default value is <code>false</code>.
451
	 * 
452
	 * @param value
453
	 *            <code>true</code> if a
454
	 * @since 3.1
455
	 */
456
	protected void setEnablementDeterminedByCommand(boolean value) {
457
		askForCommand = value;
458
	}
459

    
460
	/**
461
	 * Sets the current DropTargetEvent.
462
	 * 
463
	 * @param currentEvent
464
	 *            the DropTargetEvent
465
	 */
466
	public void setCurrentEvent(DropTargetEvent currentEvent) {
467
		this.currentEvent = currentEvent;
468
	}
469

    
470
	/**
471
	 * Sets the <i>target</i> <code>EditPart</code>. If the target is changing,
472
	 * {@link #handleExitingEditPart()} is called before the target changes, and
473
	 * {@link #handleEnteredEditPart()} is called afterwards.
474
	 * 
475
	 * @param ep
476
	 *            the new target EditPart
477
	 */
478
	protected void setTargetEditPart(EditPart ep) {
479
		if (ep != target) {
480
			if (target != null)
481
				handleExitingEditPart();
482
			target = ep;
483
			if (target != null)
484
				handleEnteredEditPart();
485
		}
486
	}
487

    
488
	/**
489
	 * Sets the Tranfer type that this listener can handle.
490
	 * 
491
	 * @param xfer
492
	 *            the Transfer
493
	 */
494
	protected void setTransfer(Transfer xfer) {
495
		transfer = xfer;
496
	}
497

    
498
	/**
499
	 * Sets the EditPartViewer.
500
	 * 
501
	 * @param viewer
502
	 *            the EditPartViewer
503
	 */
504
	protected void setViewer(EditPartViewer viewer) {
505
		this.viewer = viewer;
506
	}
507

    
508
	/**
509
	 * Asks the target <code>EditPart</code> to show target feedback if it is
510
	 * not <code>null</code>.
511
	 * 
512
	 * @see EditPart#showTargetFeedback(Request)
513
	 */
514
	protected void showTargetFeedback() {
515
		if (getTargetEditPart() != null) {
516
			showingFeedback = true;
517
			getTargetEditPart().showTargetFeedback(getTargetRequest());
518
		}
519
	}
520

    
521
	/**
522
	 * Tests whether the given event's location is different than the previous
523
	 * event's location, and sets the remembered location to the current event's
524
	 * location.
525
	 * 
526
	 * @param event
527
	 * @return boolean
528
	 */
529
	private boolean testAndSet(DropTargetEvent event) {
530
		boolean result = prevMouseLoc == null
531
				|| !(prevMouseLoc.x == event.x && prevMouseLoc.y == event.y);
532
		if (prevMouseLoc == null)
533
			prevMouseLoc = new Point();
534
		prevMouseLoc.x = event.x;
535
		prevMouseLoc.y = event.y;
536
		return result;
537
	}
538

    
539
	/**
540
	 * Erases target feedback and sets the request to <code>null</code>.
541
	 */
542
	protected void unload() {
543
		resetHover();
544
		eraseTargetFeedback();
545
		request = null;
546
		setTargetEditPart(null);
547
		setCurrentEvent(null);
548
		setAutoexposeHelper(null);
549
	}
550

    
551
	/**
552
	 * Updates the active {@link AutoexposeHelper}. Does nothing if there is
553
	 * still an active helper. Otherwise, obtains a new helper (possible
554
	 * <code>null</code>) at the current mouse location and calls
555
	 * {@link #setAutoexposeHelper(AutoexposeHelper)}.
556
	 */
557
	protected void updateAutoexposeHelper() {
558
		if (exposeHelper != null)
559
			return;
560
		AutoexposeHelper.Search search;
561
		search = new AutoexposeHelper.Search(getDropLocation());
562
		getViewer().findObjectAtExcluding(getDropLocation(),
563
				Collections.EMPTY_LIST, search);
564
		setAutoexposeHelper(search.result);
565
	}
566

    
567
	/**
568
	 * Updates the target EditPart.
569
	 */
570
	protected void updateTargetEditPart() {
571
		setTargetEditPart(calculateTargetEditPart());
572
	}
573

    
574
	/**
575
	 * Subclasses must implement this to update the target Request.
576
	 */
577
	protected abstract void updateTargetRequest();
578

    
579
}
(2-2/10)