-
Notifications
You must be signed in to change notification settings - Fork 51
/
TargetingTool.java
501 lines (461 loc) · 14.5 KB
/
TargetingTool.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
/*******************************************************************************
* Copyright (c) 2000, 2024 IBM Corporation and others.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.gef.tools;
import java.util.Collection;
import java.util.Collections;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.KeyEvent;
import org.eclipse.swt.widgets.Display;
import org.eclipse.draw2d.IFigure;
import org.eclipse.draw2d.PositionConstants;
import org.eclipse.draw2d.geometry.Point;
import org.eclipse.gef.AutoexposeHelper;
import org.eclipse.gef.EditPart;
import org.eclipse.gef.EditPartViewer;
import org.eclipse.gef.Request;
import org.eclipse.gef.commands.Command;
import org.eclipse.gef.commands.UnexecutableCommand;
import org.eclipse.gef.requests.TargetRequest;
/**
* The base implementation for tools which perform targeting of editparts.
* Targeting tools may operate using either mouse drags or just mouse moves.
* Targeting tools work with a <i>target</i> request. This request is used along
* with the mouse location to obtain an active target from the current
* EditPartViewer. This target is then asked for the <code>Command</code> that
* performs the given request. The target is also asked to show target feedback.
* <P>
* TargetingTool also provides support for auto-expose (a.k.a. auto-scrolling).
* Subclasses that wish to commence auto-expose can do so by calling
* {@link #updateAutoexposeHelper()}. An an AutoExposeHelper is found,
* auto-scrolling begins. Whenever that helper scrolls the diagram of performs
* any other change, <code>handleMove</code> will be called as if the mouse had
* moved. This is because the target has probably moved, but there is no input
* event to trigger an update of the operation.
*/
public abstract class TargetingTool extends AbstractTool {
private static final int FLAG_LOCK_TARGET = AbstractTool.MAX_FLAG << 1;
private static final int FLAG_TARGET_FEEDBACK = AbstractTool.MAX_FLAG << 2;
/**
* The max flag.
*/
protected static final int MAX_FLAG = FLAG_TARGET_FEEDBACK;
private Request targetRequest;
private EditPart targetEditPart;
private AutoexposeHelper exposeHelper;
private int refreshRate = -1;
/**
* Creates the target request that will be used with the target editpart. This
* request will be cached and updated as needed.
*
* @see #getTargetRequest()
* @return the new target request
*/
protected Request createTargetRequest() {
Request request = new Request();
request.setType(getCommandName());
return request;
}
/**
* @see org.eclipse.gef.Tool#deactivate()
*/
@Override
public void deactivate() {
if (isHoverActive()) {
resetHover();
}
eraseTargetFeedback();
targetEditPart = null;
targetRequest = null;
setAutoexposeHelper(null);
super.deactivate();
}
/**
* Called to perform an iteration of the autoexpose process. If the expose
* helper is set, it will be asked to step at the current mouse location. If it
* returns true, another expose iteration will be queued. There is no delay
* between autoexpose events, other than the time required to perform the
* step().
*/
protected void doAutoexpose() {
if (exposeHelper == null) {
return;
}
if (exposeHelper.step(getLocation())) {
handleAutoexpose();
if (refreshRate <= 0) {
Display.getCurrent().asyncExec(new QueuedAutoexpose());
} else {
Display.getCurrent().timerExec(refreshRate, new QueuedAutoexpose());
}
} else {
setAutoexposeHelper(null);
}
}
/**
* Asks the current target editpart to erase target feedback using the target
* request. If target feedback is not being shown, this method does nothing and
* returns. Otherwise, the target feedback flag is reset to false, and the
* target editpart is asked to erase target feedback. This methods should rarely
* be overridden.
*/
protected void eraseTargetFeedback() {
if (!isShowingTargetFeedback()) {
return;
}
setFlag(FLAG_TARGET_FEEDBACK, false);
if (getTargetEditPart() != null) {
getTargetEditPart().eraseTargetFeedback(getTargetRequest());
}
}
/**
* Queries the target editpart for a command.
*
* @see org.eclipse.gef.tools.AbstractTool#getCommand()
*/
@Override
protected Command getCommand() {
if (getTargetEditPart() == null) {
return null;
}
return getTargetEditPart().getCommand(getTargetRequest());
}
/**
* Get the direction indication from the key event.
*
* @return PositionConstants.NORTH, PositionConstants.EAST,
* PositionConstants.SOUTH, PositionConstants.WEST, or 0
* @since 3.18
*/
protected int getDirection(KeyEvent event) {
return switch (event.keyCode) {
case SWT.ARROW_DOWN -> PositionConstants.SOUTH;
case SWT.ARROW_UP -> PositionConstants.NORTH;
case SWT.ARROW_RIGHT -> isCurrentViewerMirrored() ? PositionConstants.WEST : PositionConstants.EAST;
case SWT.ARROW_LEFT -> isCurrentViewerMirrored() ? PositionConstants.EAST : PositionConstants.WEST;
default -> 0;
};
}
/**
* Returns a List of figures that should be excluded as potential targets for
* the operation.
*
* @return the list of figures to be excluded as targets
*/
@SuppressWarnings("static-method") // to be overridden by children
protected Collection<IFigure> getExclusionSet() {
return Collections.emptyList();
}
/**
* Returns the conditional object used for obtaining the target editpart from
* the current viewer. By default, a conditional is returned that tests whether
* an editpart at the current mouse location indicates a target for the
* operation's request, using {@link EditPart#getTargetEditPart(Request)}. If
* <code>null</code> is returned, then the conditional fails, and the search
* continues.
*
* @see EditPartViewer#findObjectAtExcluding(Point, Collection,
* EditPartViewer.Conditional)
* @return the targeting conditional
*/
protected EditPartViewer.Conditional getTargetingConditional() {
return editpart -> editpart.getTargetEditPart(getTargetRequest()) != null;
}
/**
* Returns <code>null</code> or the current target editpart.
*
* @return <code>null</code> or a target part
*/
protected EditPart getTargetEditPart() {
return targetEditPart;
}
/**
* Lazily creates and returns the request used when communicating with the
* target editpart.
*
* @return the target request
*/
protected Request getTargetRequest() {
if (targetRequest == null) {
setTargetRequest(createTargetRequest());
}
return targetRequest;
}
/**
* This method is called whenever an autoexpose occurs. When an autoexpose
* occurs, it is possible that everything in the viewer has moved a little.
* Therefore, by default, {@link AbstractTool#handleMove() handleMove()} is
* called to simulate the mouse moving even though it didn't.
*/
protected void handleAutoexpose() {
handleMove();
}
/**
* Called whenever the target editpart has changed. By default, the target
* request is updated, and the new target is asked to show feedback. Subclasses
* may extend this method if needed.
*
* @return <code>true</code>
*/
protected boolean handleEnteredEditPart() {
updateTargetRequest();
showTargetFeedback();
return true;
}
/**
* Called whenever the target editpart is about to change. By default, hover is
* reset, in the case that a hover was showing something, and the target being
* exited is asked to erase its feedback.
*
* @return <code>true</code>
*/
protected boolean handleExitingEditPart() {
resetHover();
eraseTargetFeedback();
return true;
}
/**
* Called from resetHover() iff hover is active. Subclasses may extend this
* method to handle the hover stop event. Returns <code>true</code> if something
* was done in response to the call.
*
* @see AbstractTool#isHoverActive()
* @return <code>true</code> if the hover stop is processed in some way
*/
@SuppressWarnings("static-method")
protected boolean handleHoverStop() {
return false;
}
/**
* Called when invalid input is encountered. By default, feedback is erased, and
* the current command is set to the unexecutable command. The state does not
* change, so the caller must set the state to
* {@link AbstractTool#STATE_INVALID}.
*
* @return <code>true</code>
*/
@Override
protected boolean handleInvalidInput() {
eraseTargetFeedback();
setCurrentCommand(UnexecutableCommand.INSTANCE);
return true;
}
/**
* An archaic method name that has been left here to force use of the new name.
*
* @throws Exception exc
*/
protected final void handleLeavingEditPart() throws Exception {
}
/**
* Sets the target to <code>null</code>.
*
* @see org.eclipse.gef.tools.AbstractTool#handleViewerExited()
*/
@Override
protected boolean handleViewerExited() {
setTargetEditPart(null);
return true;
}
/**
* Returns <code>true</code> if target feedback is being shown.
*
* @return <code>true</code> if showing target feedback
*/
protected boolean isShowingTargetFeedback() {
return getFlag(FLAG_TARGET_FEEDBACK);
}
/**
* Return <code>true</code> if the current target is locked.
*
* @see #lockTargetEditPart(EditPart)
* @return <code>true</code> if the target is locked
*/
protected boolean isTargetLocked() {
return getFlag(FLAG_LOCK_TARGET);
}
/**
* Locks-in the given editpart as the target. Updating of the target will not
* occur until {@link #unlockTargetEditPart()} is called.
*
* @param editpart the target to be locked-in
*/
protected void lockTargetEditPart(EditPart editpart) {
if (editpart == null) {
unlockTargetEditPart();
return;
}
setFlag(FLAG_LOCK_TARGET, true);
setTargetEditPart(editpart);
}
/**
* Extended to reset the target lock flag.
*
* @see org.eclipse.gef.tools.AbstractTool#resetFlags()
* @see #lockTargetEditPart(EditPart)
*/
@Override
protected void resetFlags() {
setFlag(FLAG_LOCK_TARGET, false);
super.resetFlags();
}
/**
* Resets hovering to inactive.
*
* @since 3.4
*/
protected void resetHover() {
if (isHoverActive()) {
handleHoverStop();
}
setHoverActive(false);
}
class QueuedAutoexpose implements Runnable {
@Override
public void run() {
if (exposeHelper != null) {
doAutoexpose();
}
}
}
/**
* Sets the active autoexpose helper to the given helper, or <code>null</code>.
* If the helper is not <code>null</code>, a runnable is queued on the event
* thread that will trigger a subsequent {@link #doAutoexpose()}. The helper is
* typically updated only on a hover event.
*
* @param helper the new autoexpose helper or <code>null</code>
*/
protected void setAutoexposeHelper(AutoexposeHelper helper) {
exposeHelper = helper;
if (exposeHelper == null) {
return;
}
Display.getCurrent().asyncExec(new QueuedAutoexpose());
}
/**
* Sets the target editpart. If the target editpart is changing, this method
* will call {@link #handleExitingEditPart()} for the previous target if not
* <code>null</code>, and {@link #handleEnteredEditPart()} for the new target,
* if not <code>null</code>.
*
* @param editpart the new target
*/
protected void setTargetEditPart(EditPart editpart) {
if (editpart != targetEditPart) {
if (targetEditPart != null) {
handleExitingEditPart();
}
targetEditPart = editpart;
if (getTargetRequest() instanceof TargetRequest targetReq) {
targetReq.setTargetEditPart(targetEditPart);
}
handleEnteredEditPart();
}
}
/**
* Sets the target request. This method is typically not called; subclasses
* normally override {@link #createTargetRequest()}.
*
* @param req the target request
*/
protected void setTargetRequest(Request req) {
targetRequest = req;
}
/**
* Asks the target editpart to show target feedback and sets the target feedback
* flag.
*/
protected void showTargetFeedback() {
if (getTargetEditPart() != null) {
getTargetEditPart().showTargetFeedback(getTargetRequest());
}
setFlag(FLAG_TARGET_FEEDBACK, true);
}
/**
* Releases the targeting lock, and updates the target in case the mouse is
* already over a new target.
*/
protected void unlockTargetEditPart() {
setFlag(FLAG_LOCK_TARGET, false);
updateTargetUnderMouse();
}
/**
* Updates the active {@link AutoexposeHelper}. Does nothing if there is still
* an active helper. Otherwise, obtains a new helper (possible
* <code>null</code>) at the current mouse location and calls
* {@link #setAutoexposeHelper(AutoexposeHelper)}.
*/
protected void updateAutoexposeHelper() {
if (exposeHelper != null) {
return;
}
AutoexposeHelper.Search search;
search = new AutoexposeHelper.Search(getLocation());
getCurrentViewer().findObjectAtExcluding(getLocation(), Collections.emptyList(), search);
setAutoexposeHelper(search.result);
}
/**
* Subclasses should override to update the target request.
*/
protected void updateTargetRequest() {
}
/**
* Updates the target editpart and returns <code>true</code> if the target
* changes. The target is updated by using the target conditional and the target
* request. If the target has been locked, this method does nothing and returns
* <code>false</code>.
*
* @return <code>true</code> if the target was changed
*/
protected boolean updateTargetUnderMouse() {
if (isTargetLocked()) {
return false;
}
EditPart editPart = null;
if (getCurrentViewer() != null) {
editPart = getCurrentViewer().findObjectAtExcluding(getLocation(), getExclusionSet(),
getTargetingConditional());
}
if (editPart != null) {
editPart = editPart.getTargetEditPart(getTargetRequest());
}
boolean changed = getTargetEditPart() != editPart;
setTargetEditPart(editPart);
return changed;
}
/**
* Returns <code>null</code> or the current autoexpose helper.
*
* @return null or a helper
*/
protected AutoexposeHelper getAutoexposeHelper() {
return exposeHelper;
}
/**
* Sets the rate with which the auto-expose helper is evaluated. If set to
* either {@code 0} or a negative value, the evaluation is done as fast as
* possible (default behavior), otherwise every {@code refreshRate}ms.
*
* Example:
*
* <pre>
* setRefreshRate(500); // Validate every 500ms
* </pre>
*
* @param refreshRate The rate with which the auto-expose helper is validated.
* @since 3.20
*/
public void setRefreshRate(int refreshRate) {
this.refreshRate = refreshRate;
}
}