-
Notifications
You must be signed in to change notification settings - Fork 55
/
FXCanvasEx.java
182 lines (169 loc) · 6.36 KB
/
FXCanvasEx.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
/*******************************************************************************
* Copyright (c) 2014, 2016 itemis AG 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:
* Matthias Wienand (itemis AG) - initial API and implementation
* Alexander Nyßen (itemis AG) - Support for focus listener notification
* Jan Köhnlein (itemis AG) - Support for multi-touch gestures (#427106)
*
*******************************************************************************/
package org.eclipse.gef4.fx.swt.canvas;
import java.lang.reflect.Method;
import org.eclipse.gef4.common.reflect.ReflectionUtils;
import org.eclipse.gef4.fx.swt.gestures.SWT2FXEventConverter;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.TraverseEvent;
import org.eclipse.swt.events.TraverseListener;
import org.eclipse.swt.graphics.ImageData;
import org.eclipse.swt.widgets.Canvas;
import org.eclipse.swt.widgets.Composite;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.embed.swt.FXCanvas;
import javafx.embed.swt.SWTFXUtils;
import javafx.event.Event;
import javafx.event.EventDispatchChain;
import javafx.event.EventDispatcher;
import javafx.scene.Cursor;
import javafx.scene.ImageCursor;
import javafx.scene.Scene;
import javafx.stage.Window;
/**
* A replacement of {@link FXCanvas} that fixes the following issues:
* <ul>
* <li>https://bugs.openjdk.java.net/browse/JDK-8143596 (gesture events not
* forwarded) and horizontal mouse events not forwarded: fixed by forwarding of
* missing SWT events to JavaFX through an {@link SWT2FXEventConverter}.</li>
* <li>https://bugs.openjdk.java.net/browse/JDK-8088147 (image cursors not
* supported): fixed by adding support for image cursors.</li>
* </ul>
*
* @author anyssen
*
*/
public class FXCanvasEx extends FXCanvas {
private SWT2FXEventConverter gestureConverter;
private EventDispatcher initialEventDispatcher;
private EventDispatcher deferringEventDispatcher = new EventDispatcher() {
private static final int REDRAW_INTERVAL_MILLIS = 40; // i.e. 25 fps
private long lastRedrawMillis = System.currentTimeMillis();
@Override
public Event dispatchEvent(final Event event,
final EventDispatchChain tail) {
// dispatch the most recent event
Event returnedEvent = initialEventDispatcher.dispatchEvent(event,
tail);
// update UI
long millisNow = System.currentTimeMillis();
if (millisNow - lastRedrawMillis > REDRAW_INTERVAL_MILLIS) {
redraw();
update();
lastRedrawMillis = millisNow;
}
// return dispatched event
return returnedEvent;
}
};
private ChangeListener<Cursor> cursorChangeListener = new ChangeListener<Cursor>() {
@Override
public void changed(ObservableValue<? extends Cursor> observable,
Cursor oldCursor, Cursor newCursor) {
// XXX: SWTCursors does support image cursors yet
// (https://bugs.openjdk.java.net/browse/JDK-8088147); we compensate
// this here (using JDK-internal API)
if (newCursor instanceof ImageCursor) {
// custom cursor, convert image
ImageData imageData = SWTFXUtils.fromFXImage(
((ImageCursor) newCursor).getImage(), null);
double hotspotX = ((ImageCursor) newCursor).getHotspotX();
double hotspotY = ((ImageCursor) newCursor).getHotspotY();
org.eclipse.swt.graphics.Cursor swtCursor = new org.eclipse.swt.graphics.Cursor(
getDisplay(), imageData, (int) hotspotX,
(int) hotspotY);
// FIXME [JDK-internal]: Set platform cursor on CursorFrame so
// that it can be retrieved by FXCanvas' HostContainer (which
// ultimately sets the cursor on the FXCanvas); unfortunately,
// this is not possible using public API.
try {
Method currentCursorFrameAccessor = Cursor.class
.getDeclaredMethod("getCurrentFrame",
new Class[] {});
currentCursorFrameAccessor.setAccessible(true);
Object currentCursorFrame = currentCursorFrameAccessor
.invoke(newCursor, new Object[] {});
// there is a spelling-mistake in the internal API
// (setPlatformCursor -> setPlatforCursor)
Method platformCursorProvider = currentCursorFrame
.getClass().getMethod("setPlatforCursor",
new Class[] { Class.class, Object.class });
platformCursorProvider.setAccessible(true);
platformCursorProvider.invoke(currentCursorFrame,
org.eclipse.swt.graphics.Cursor.class, swtCursor);
} catch (Exception e) {
System.err.println(
"Failed to set platform cursor on the current cursor frame.");
e.printStackTrace();
}
}
}
};
private TraverseListener eclipseUiTraversalListener = new TraverseListener() {
@Override
public void keyTraversed(TraverseEvent e) {
if ((e.detail == SWT.TRAVERSE_TAB_NEXT
|| e.detail == SWT.TRAVERSE_TAB_PREVIOUS)
&& (e.stateMask & SWT.CTRL) != 0) {
e.doit = true;
}
}
};
/**
* Creates a new {@link FXCanvasEx} for the given parent and with the given
* style.
*
* @param parent
* The {@link Composite} to use as parent.
* @param style
* A combination of SWT styles to be applied. Note that the
* {@link FXCanvas} constructor will set the
* {@link SWT#NO_BACKGROUND} style before passing it to the
* {@link Canvas} constructor.
*/
public FXCanvasEx(Composite parent, int style) {
super(parent, style);
addTraverseListener(eclipseUiTraversalListener);
gestureConverter = new SWT2FXEventConverter(this);
}
@Override
public void dispose() {
gestureConverter.dispose();
super.dispose();
}
/**
* Returns the stage {@link Window} hold by this {@link FXCanvas}.
*
* @return The stage {@link Window}.
*/
public Window getStage() {
return ReflectionUtils.getPrivateFieldValue(this, "stage");
}
@Override
public void setScene(Scene newScene) {
Scene oldScene = getScene();
if (oldScene != null) {
oldScene.setEventDispatcher(initialEventDispatcher);
oldScene.cursorProperty().removeListener(cursorChangeListener);
}
super.setScene(newScene);
if (newScene != null) {
initialEventDispatcher = newScene.getEventDispatcher();
newScene.setEventDispatcher(deferringEventDispatcher);
newScene.cursorProperty().addListener(cursorChangeListener);
}
}
}