36 import javafx.scene.input.KeyCode;
37 import javafx.scene.input.KeyEvent;
38 import javafx.scene.input.MouseButton;
39 import javafx.scene.input.MouseEvent;
40 import javafx.scene.input.ScrollEvent;
41 import javafx.stage.Window;
42 import javax.swing.JComponent;
43 import javax.swing.Timer;
44 import java.awt.AWTEvent;
45 import java.awt.Component;
46 import java.awt.Cursor;
47 import java.awt.EventQueue;
48 import java.awt.Toolkit;
49 import java.lang.ref.WeakReference;
50 import java.awt.dnd.DragGestureEvent;
51 import java.awt.dnd.DragGestureListener;
52 import java.awt.dnd.DragGestureRecognizer;
53 import java.awt.dnd.DragSource;
54 import java.awt.dnd.DropTarget;
55 import java.awt.dnd.InvalidDnDOperationException;
56 import java.awt.dnd.peer.DragSourceContextPeer;
57 import java.awt.event.InputEvent;
58 import java.awt.event.MouseWheelEvent;
59 import java.awt.event.WindowEvent;
60 import java.awt.event.WindowFocusListener;
61 import java.lang.reflect.Method;
62 import java.nio.IntBuffer;
63 import java.security.AccessController;
64 import java.security.PrivilegedAction;
65 import java.util.ArrayList;
66 import java.util.HashSet;
67 import java.util.List;
68 import java.util.Set;
69 import java.util.concurrent.locks.ReentrantLock;
70 import com.sun.javafx.embed.swing.Disposer;
71 import com.sun.javafx.embed.swing.DisposerRecord;
72 import com.sun.javafx.geom.BaseBounds;
73 import com.sun.javafx.geom.transform.BaseTransform;
74 import com.sun.javafx.scene.DirtyBits;
75 import com.sun.javafx.sg.prism.NGExternalNode;
76 import com.sun.javafx.sg.prism.NGNode;
77 import com.sun.javafx.stage.FocusUngrabEvent;
78 import com.sun.javafx.stage.WindowHelper;
79 import com.sun.javafx.tk.TKStage;
80 import com.sun.javafx.PlatformUtil;
81 import com.sun.javafx.embed.swing.SwingNodeHelper;
82 import com.sun.javafx.scene.NodeHelper;
83 import sun.awt.UngrabEvent;
84 import sun.swing.JLightweightFrame;
85 import sun.swing.LightweightContent;
86
87 import static javafx.stage.WindowEvent.WINDOW_HIDDEN;
88
89 /**
90 * This class is used to embed a Swing content into a JavaFX application.
91 * The content to be displayed is specified with the {@link #setContent} method
92 * that accepts an instance of Swing {@code JComponent}. The hierarchy of components
93 * contained in the {@code JComponent} instance should not contain any heavyweight
94 * components, otherwise {@code SwingNode} may fail to paint it. The content gets
95 * repainted automatically. All the input and focus events are forwarded to the
96 * {@code JComponent} instance transparently to the developer.
97 * <p>
98 * Here is a typical pattern which demonstrates how {@code SwingNode} can be used:
99 * <pre>
100 * public class SwingFx extends Application {
101 *
102 * @Override
103 * public void start(Stage stage) {
104 * final SwingNode swingNode = new SwingNode();
105 * createAndSetSwingContent(swingNode);
159 }
160
161 @Override
162 public boolean doComputeContains(Node node, double localX, double localY) {
163 return ((SwingNode) node).doComputeContains(localX, localY);
164 }
165 });
166 }
167
168 private double fxWidth;
169 private double fxHeight;
170
171 private int swingPrefWidth;
172 private int swingPrefHeight;
173 private int swingMaxWidth;
174 private int swingMaxHeight;
175 private int swingMinWidth;
176 private int swingMinHeight;
177
178 private volatile JComponent content;
179 private volatile JLightweightFrame lwFrame;
180 final JLightweightFrame getLightweightFrame() { return lwFrame; }
181
182 private NGExternalNode peer;
183
184 private final ReentrantLock paintLock = new ReentrantLock();
185
186 private boolean skipBackwardUnrgabNotification;
187 private boolean grabbed; // lwframe initiated grab
188 private Timer deactivate; // lwFrame deactivate delay for Linux
189
190 {
191 // To initialize the class helper at the begining each constructor of this class
192 SwingNodeHelper.initHelper(this);
193 }
194
195 /**
196 * Constructs a new instance of {@code SwingNode}.
197 */
198 public SwingNode() {
199 setFocusTraversable(true);
200 setEventHandler(MouseEvent.ANY, new SwingMouseEventHandler());
201 setEventHandler(KeyEvent.ANY, new SwingKeyEventHandler());
202 setEventHandler(ScrollEvent.SCROLL, new SwingScrollEventHandler());
203
204 focusedProperty().addListener((observable, oldValue, newValue) -> {
205 activateLwFrame(newValue);
206 });
207
208 //Workaround for RT-34170
209 javafx.scene.text.Font.getFamilies();
210 }
211
212
213 private EventHandler windowHiddenHandler = (Event event) -> {
214 if (lwFrame != null && event.getTarget() instanceof Window) {
215 final Window w = (Window) event.getTarget();
216 TKStage tk = WindowHelper.getPeer(w);
217 if (tk != null) {
218 if (isThreadMerged) {
219 jlfOverrideNativeWindowHandle.invoke(lwFrame, 0L, null);
220 } else {
221 // Postpone actual window closing to ensure that
222 // a native window handler is valid on a Swing side
223 tk.postponeClose();
224 SwingFXUtils.runOnEDT(() -> {
225 jlfOverrideNativeWindowHandle.invoke(lwFrame, 0L,
226 (Runnable) () -> SwingFXUtils.runOnFxThread(
227 () -> tk.closePostponed()));
228 });
229 }
230 }
231 }
232
233 };
234
235 private Window hWindow = null;
236 private void notifyNativeHandle(Window window) {
237 if (hWindow != window) {
238 if (hWindow != null) {
239 hWindow.removeEventHandler(WINDOW_HIDDEN, windowHiddenHandler);
240 }
241 if (window != null) {
242 window.addEventHandler(WINDOW_HIDDEN, windowHiddenHandler);
243 }
244 hWindow = window;
245 }
246
247 if (lwFrame != null) {
248 long rawHandle = 0L;
249 if (window != null) {
250 TKStage tkStage = WindowHelper.getPeer(window);
251 if (tkStage != null) {
252 rawHandle = tkStage.getRawHandle();
253 }
254 }
255 jlfOverrideNativeWindowHandle.invoke(lwFrame, rawHandle, null);
256 }
257 }
258
259 /**
260 * Attaches a {@code JComponent} instance to display in this {@code SwingNode}.
261 * <p>
262 * The method can be called either on the JavaFX Application thread or the Event Dispatch thread.
263 * Note however, that access to a Swing component must occur from the Event Dispatch thread
264 * according to the Swing threading restrictions.
265 *
266 * @param content a Swing component to display in this {@code SwingNode}
267 *
268 * @see java.awt.EventQueue#isDispatchThread()
269 * @see javafx.application.Platform#isFxApplicationThread()
270 */
271 public void setContent(final JComponent content) {
272 this.content = content;
273
274 SwingFXUtils.runOnEDT(() -> setContentImpl(content));
275 }
314 }
315
316 public boolean isIntegerApi() {
317 return isIntegerAPI;
318 }
319
320 public Object invoke(T object, Object... args) {
321 if (method != null) {
322 try {
323 return method.invoke(object, args);
324 } catch (Throwable ex) {
325 throw new RuntimeException("Error when calling " + object.getClass().getName() + "." + method.getName() + "().", ex);
326 }
327 } else {
328 return null;
329 }
330 }
331 }
332
333 /**
334 * Calls JLightweightFrame.notifyDisplayChanged.
335 * Must be called on EDT only.
336 */
337 private static OptionalMethod<JLightweightFrame> jlfNotifyDisplayChanged;
338 private static OptionalMethod<JLightweightFrame> jlfOverrideNativeWindowHandle;
339
340 static {
341 jlfNotifyDisplayChanged = new OptionalMethod<>(JLightweightFrame.class,
342 "notifyDisplayChanged", Double.TYPE, Double.TYPE);
343 if (!jlfNotifyDisplayChanged.isSupported()) {
344 jlfNotifyDisplayChanged = new OptionalMethod<>(
345 JLightweightFrame.class,"notifyDisplayChanged", Integer.TYPE);
346 }
347
348 jlfOverrideNativeWindowHandle = new OptionalMethod<>(JLightweightFrame.class,
349 "overrideNativeWindowHandle", Long.TYPE, Runnable.class);
350
351 }
352
353 /*
354 * Called on EDT
355 */
356 private void setContentImpl(JComponent content) {
357 if (lwFrame != null) {
358 lwFrame.dispose();
359 lwFrame = null;
360 }
361 if (content != null) {
362 lwFrame = new JLightweightFrame();
363
364 SwingNodeWindowFocusListener snfListener =
365 new SwingNodeWindowFocusListener(this);
366 lwFrame.addWindowFocusListener(snfListener);
367
368 if (getScene() != null) {
369 Window window = getScene().getWindow();
370 if (window != null) {
371 if (jlfNotifyDisplayChanged.isIntegerApi()) {
372 jlfNotifyDisplayChanged.invoke(lwFrame,
373 (int) Math.round(window.getRenderScaleX()));
374 } else {
375 jlfNotifyDisplayChanged.invoke(lwFrame,
376 window.getRenderScaleX(),
377 window.getRenderScaleY());
378 }
379 }
380 }
381 lwFrame.setContent(new SwingNodeContent(content, this));
382 lwFrame.setVisible(true);
542 * @return the minimum height that the node should be resized to during layout
543 */
544 @Override public double minHeight(double width) {
545 return swingMinHeight;
546 }
547
548 /*
549 * Note: This method MUST only be called via its accessor method.
550 */
551 private boolean doComputeContains(double localX, double localY) {
552 return true;
553 }
554
555 private final InvalidationListener locationListener = observable -> {
556 locateLwFrame();
557 };
558
559 private final EventHandler<FocusUngrabEvent> ungrabHandler = event -> {
560 if (!skipBackwardUnrgabNotification) {
561 if (lwFrame != null) {
562 AccessController.doPrivileged(new PostEventAction(new UngrabEvent(lwFrame)));
563 }
564 }
565 };
566
567 private final ChangeListener<Boolean> windowVisibleListener = (observable, oldValue, newValue) -> {
568 if (!newValue) {
569 disposeLwFrame();
570 } else {
571 setContent(content);
572 }
573 };
574
575 private final ChangeListener<Window> sceneWindowListener = (observable, oldValue, newValue) -> {
576 if (oldValue != null) {
577 removeWindowListeners(oldValue);
578 }
579
580 notifyNativeHandle(newValue);
581
582 if (newValue != null) {
656 setLwFrameVisible(newValue);
657 });
658
659 return peer;
660 }
661
662 /*
663 * Note: This method MUST only be called via its accessor method.
664 */
665 private void doUpdatePeer() {
666 if (NodeHelper.isDirty(this, DirtyBits.NODE_VISIBLE)
667 || NodeHelper.isDirty(this, DirtyBits.NODE_BOUNDS)) {
668 locateLwFrame(); // initialize location
669 }
670 if (NodeHelper.isDirty(this, DirtyBits.NODE_CONTENTS)) {
671 peer.markContentDirty();
672 }
673 }
674
675 /**
676 * Calls JLightweightFrame.setHostBounds.
677 * Must be called on EDT only.
678 */
679 private static final OptionalMethod<JLightweightFrame> jlfSetHostBounds =
680 new OptionalMethod<>(JLightweightFrame.class, "setHostBounds",
681 Integer.TYPE, Integer.TYPE, Integer.TYPE, Integer.TYPE);
682
683 private void locateLwFrame() {
684 if (getScene() == null
685 || lwFrame == null
686 || getScene().getWindow() == null
687 || !getScene().getWindow().isShowing()) {
688 // Not initialized yet. Skip the update to set the real values later
689 return;
690 }
691 Window w = getScene().getWindow();
692 double renderScaleX = w.getRenderScaleX();
693 double renderScaleY = w.getRenderScaleY();
694 final Point2D loc = localToScene(0, 0);
695 final int windowX = (int) (w.getX());
696 final int windowY = (int) (w.getY());
697 final int windowW = (int) (w.getWidth());
698 final int windowH = (int) (w.getHeight());
699 final int frameX = (int) Math.round(w.getX() + getScene().getX() + loc.getX());
700 final int frameY = (int) Math.round(w.getY() + getScene().getY() + loc.getY());
780 if (jlfNotifyDisplayChanged.isIntegerApi()) {
781 jlfNotifyDisplayChanged.invoke(lwFrame,
782 (int)Math.round(scaleX));
783 } else {
784 jlfNotifyDisplayChanged.invoke(lwFrame, scaleX, scaleY);
785 }
786 }
787 });
788 }
789
790 /*
791 * Note: This method MUST only be called via its accessor method.
792 */
793 private BaseBounds doComputeGeomBounds(BaseBounds bounds, BaseTransform tx) {
794 bounds.deriveWithNewBounds(0, 0, 0, (float)fxWidth, (float)fxHeight, 0);
795 tx.transform(bounds, bounds);
796 return bounds;
797 }
798
799 private static class SwingNodeDisposer implements DisposerRecord {
800 JLightweightFrame lwFrame;
801
802 SwingNodeDisposer(JLightweightFrame ref) {
803 this.lwFrame = ref;
804 }
805 public void dispose() {
806 if (lwFrame != null) {
807 lwFrame.dispose();
808 lwFrame = null;
809 }
810 }
811 }
812
813 private static class SwingNodeWindowFocusListener implements WindowFocusListener {
814 private WeakReference<SwingNode> swingNodeRef;
815
816 SwingNodeWindowFocusListener(SwingNode swingNode) {
817 this.swingNodeRef = new WeakReference<SwingNode>(swingNode);
818 }
819
820 @Override
821 public void windowGainedFocus(WindowEvent e) {
822 SwingFXUtils.runOnFxThread(() -> {
823 SwingNode swingNode = swingNodeRef.get();
824 if (swingNode != null) {
825 swingNode.requestFocus();
826 }
827 });
828 }
829
830 @Override
831 public void windowLostFocus(WindowEvent e) {
832 SwingFXUtils.runOnFxThread(() -> {
833 SwingNode swingNode = swingNodeRef.get();
834 if (swingNode != null) {
835 swingNode.ungrabFocus(true);
836 }
837 });
838 }
839 }
840
841 private static class SwingNodeContent implements LightweightContent {
842 private JComponent comp;
843 private volatile FXDnD dnd;
844 private WeakReference<SwingNode> swingNodeRef;
845
846 SwingNodeContent(JComponent comp, SwingNode swingNode) {
847 this.comp = comp;
848 this.swingNodeRef = new WeakReference<SwingNode>(swingNode);
849 }
850 @Override
851 public JComponent getComponent() {
852 return comp;
853 }
854 @Override
855 public void paintLock() {
856 SwingNode swingNode = swingNodeRef.get();
857 if (swingNode != null) {
858 swingNode.paintLock.lock();
859 }
860 }
861 @Override
862 public void paintUnlock() {
863 SwingNode swingNode = swingNodeRef.get();
864 if (swingNode != null) {
865 swingNode.paintLock.unlock();
866 }
867 }
868
869 // Note: we skip @Override annotation and implement both pre-hiDPI and post-hiDPI versions
870 // of the method for compatibility.
871 //@Override
872 public void imageBufferReset(int[] data, int x, int y, int width, int height, int linestride) {
873 imageBufferReset(data, x, y, width, height, linestride, 1);
874 }
875 //@Override
876 public void imageBufferReset(int[] data, int x, int y, int width, int height, int linestride, int scale) {
877 SwingNode swingNode = swingNodeRef.get();
878 if (swingNode != null) {
879 swingNode.setImageBuffer(data, x, y, width, height, linestride, scale, scale);
880 }
881 }
882 //@Override
883 public void imageBufferReset(int[] data, int x, int y, int width, int height, int linestride, double scaleX, double scaleY) {
884 SwingNode swingNode = swingNodeRef.get();
885 if (swingNode != null) {
886 swingNode.setImageBuffer(data, x, y, width, height, linestride, scaleX, scaleY);
887 }
888 }
889 @Override
890 public void imageReshaped(int x, int y, int width, int height) {
891 SwingNode swingNode = swingNodeRef.get();
892 if (swingNode != null) {
893 swingNode.setImageBounds(x, y, width, height);
894 }
895 }
896 @Override
897 public void imageUpdated(int dirtyX, int dirtyY, int dirtyWidth, int dirtyHeight) {
898 SwingNode swingNode = swingNodeRef.get();
899 if (swingNode != null) {
900 swingNode.repaintDirtyRegion(dirtyX, dirtyY, dirtyWidth, dirtyHeight);
901 }
902 }
967 SwingFXUtils.runOnFxThread(() -> {
968 SwingNode swingNode = swingNodeRef.get();
969 if (swingNode != null) {
970 swingNode.setCursor(SwingCursors.embedCursorToCursor(cursor));
971 }
972 });
973 }
974
975 private void initDnD() {
976 // This is a part of AWT API, so the method may be invoked on any thread
977 synchronized (SwingNodeContent.this) {
978 if (this.dnd == null) {
979 SwingNode swingNode = swingNodeRef.get();
980 if (swingNode != null) {
981 this.dnd = new FXDnD(swingNode);
982 }
983 }
984 }
985 }
986
987 //@Override
988 public synchronized <T extends DragGestureRecognizer> T createDragGestureRecognizer(
989 Class<T> abstractRecognizerClass,
990 DragSource ds, Component c, int srcActions,
991 DragGestureListener dgl)
992 {
993 initDnD();
994 return dnd.createDragGestureRecognizer(abstractRecognizerClass, ds, c, srcActions, dgl);
995 }
996
997 //@Override
998 public DragSourceContextPeer createDragSourceContextPeer(DragGestureEvent dge) throws InvalidDnDOperationException
999 {
1000 initDnD();
1001 return dnd.createDragSourceContextPeer(dge);
1002 }
1003
1004 //@Override
1005 public void addDropTarget(DropTarget dt) {
1006 initDnD();
1007 dnd.addDropTarget(dt);
1008 }
1009
1010 //@Override
1011 public void removeDropTarget(DropTarget dt) {
1012 initDnD();
1013 dnd.removeDropTarget(dt);
1014 }
1015 }
1016
1017 private void ungrabFocus(boolean postUngrabEvent) {
1018 // On X11 grab is limited to a single XDisplay connection,
1019 // so we can't delegate it to another GUI toolkit.
1020 if (PlatformUtil.isLinux()) return;
1021
1022 if (grabbed &&
1023 getScene() != null &&
1024 getScene().getWindow() != null &&
1025 WindowHelper.getPeer(getScene().getWindow()) != null)
1026 {
1027 skipBackwardUnrgabNotification = !postUngrabEvent;
1028 WindowHelper.getPeer(getScene().getWindow()).ungrabFocus();
1029 skipBackwardUnrgabNotification = false;
1030 grabbed = false;
1032 }
1033
1034 private class PostEventAction implements PrivilegedAction<Void> {
1035 private AWTEvent event;
1036 PostEventAction(AWTEvent event) {
1037 this.event = event;
1038 }
1039 @Override
1040 public Void run() {
1041 EventQueue eq = Toolkit.getDefaultToolkit().getSystemEventQueue();
1042 eq.postEvent(event);
1043 return null;
1044 }
1045 }
1046
1047 private class SwingMouseEventHandler implements EventHandler<MouseEvent> {
1048 private final Set<MouseButton> mouseClickedAllowed = new HashSet<>();
1049
1050 @Override
1051 public void handle(MouseEvent event) {
1052 JLightweightFrame frame = lwFrame;
1053 if (frame == null) {
1054 return;
1055 }
1056 int swingID = SwingEvents.fxMouseEventTypeToMouseID(event);
1057 if (swingID < 0) {
1058 return;
1059 }
1060
1061 // Prevent ancestors of the SwingNode from stealing the focus
1062 event.consume();
1063
1064 final EventType<?> type = event.getEventType();
1065 if (type == MouseEvent.MOUSE_PRESSED) {
1066 mouseClickedAllowed.add(event.getButton());
1067 } else if (type == MouseEvent.MOUSE_RELEASED) {
1068 // RELEASED comes before CLICKED, so we don't remove the button from the set
1069 //mouseClickedAllowed.remove(event.getButton());
1070 } else if (type == MouseEvent.MOUSE_DRAGGED) {
1071 // This is what AWT/Swing do
1072 mouseClickedAllowed.clear();
1073 } else if (type == MouseEvent.MOUSE_CLICKED) {
1074 if (event.getClickCount() == 1 && !mouseClickedAllowed.contains(event.getButton())) {
1075 // RT-34610: In FX, CLICKED events are generated even after dragging the mouse pointer
1076 // Note that this is only relevant for single clicks. Double clicks use a smudge factor.
1077 return;
1078 }
1079 mouseClickedAllowed.remove(event.getButton());
1080 }
1081 int swingModifiers = SwingEvents.fxMouseModsToMouseMods(event);
1082 boolean swingPopupTrigger = event.isPopupTrigger();
1083 int swingButton = SwingEvents.fxMouseButtonToMouseButton(event);
1084 long swingWhen = System.currentTimeMillis();
1085 int relX = (int) Math.round(event.getX());
1086 int relY = (int) Math.round(event.getY());
1087 int absX = (int) Math.round(event.getScreenX());
1088 int absY = (int) Math.round(event.getScreenY());
1089 java.awt.event.MouseEvent mouseEvent =
1090 new java.awt.event.MouseEvent(
1091 frame, swingID, swingWhen, swingModifiers,
1092 relX, relY, absX, absY,
1093 event.getClickCount(), swingPopupTrigger, swingButton);
1094 AccessController.doPrivileged(new PostEventAction(mouseEvent));
1095 }
1096 }
1097
1098 private class SwingScrollEventHandler implements EventHandler<ScrollEvent> {
1099 @Override
1100 public void handle(ScrollEvent event) {
1101 JLightweightFrame frame = lwFrame;
1102 if (frame == null) {
1103 return;
1104 }
1105
1106 int swingModifiers = SwingEvents.fxScrollModsToMouseWheelMods(event);
1107 final boolean isShift = (swingModifiers & InputEvent.SHIFT_DOWN_MASK) != 0;
1108
1109 // Vertical scroll.
1110 if (!isShift && event.getDeltaY() != 0.0) {
1111 sendMouseWheelEvent(frame, event.getX(), event.getY(),
1112 swingModifiers, event.getDeltaY() / event.getMultiplierY());
1113 }
1114 // Horizontal scroll or shirt+vertical scroll.
1115 final double delta = isShift && event.getDeltaY() != 0.0
1116 ? event.getDeltaY() / event.getMultiplierY()
1117 : event.getDeltaX() / event.getMultiplierX();
1118 if (delta != 0.0) {
1119 swingModifiers |= InputEvent.SHIFT_DOWN_MASK;
1120 sendMouseWheelEvent(frame, event.getX(), event.getY(),
1121 swingModifiers, delta);
1122 }
1123 }
1124
1125 private void sendMouseWheelEvent(Component source, double fxX, double fxY, int swingModifiers, double delta) {
1126 int wheelRotation = (int) delta;
1127 int signum = (int) Math.signum(delta);
1128 if (signum * delta < 1) {
1129 wheelRotation = signum;
1130 }
1131 int x = (int) Math.round(fxX);
1132 int y = (int) Math.round(fxY);
1133 MouseWheelEvent mouseWheelEvent =
1134 new MouseWheelEvent(source, java.awt.event.MouseEvent.MOUSE_WHEEL,
1135 System.currentTimeMillis(), swingModifiers, x, y, 0, 0,
1136 0, false, MouseWheelEvent.WHEEL_UNIT_SCROLL, 1 , -wheelRotation);
1137 AccessController.doPrivileged(new PostEventAction(mouseWheelEvent));
1138 }
1139 }
1140
1141 private class SwingKeyEventHandler implements EventHandler<KeyEvent> {
1142 @Override
1143 public void handle(KeyEvent event) {
1144 JLightweightFrame frame = lwFrame;
1145 if (frame == null) {
1146 return;
1147 }
1148 if (event.getCharacter().isEmpty()) {
1149 // TODO: should we post an "empty" character?
1150 return;
1151 }
1152 // Don't let Arrows, Tab, Shift+Tab traverse focus out.
1153 if (event.getCode() == KeyCode.LEFT ||
1154 event.getCode() == KeyCode.RIGHT ||
1155 event.getCode() == KeyCode.UP ||
1156 event.getCode() == KeyCode.DOWN ||
1157 event.getCode() == KeyCode.TAB)
1158 {
1159 event.consume();
1160 }
1161
1162 int swingID = SwingEvents.fxKeyEventTypeToKeyID(event);
1163 if (swingID < 0) {
1164 return;
1165 }
1166 int swingModifiers = SwingEvents.fxKeyModsToKeyMods(event);
1167 int swingKeyCode = event.getCode().getCode();
1168 char swingChar = event.getCharacter().charAt(0);
1169
1170 // A workaround. Some swing L&F's process mnemonics on KEY_PRESSED,
1171 // for which swing provides a keychar. Extracting it from the text.
1172 if (event.getEventType() == javafx.scene.input.KeyEvent.KEY_PRESSED) {
1173 String text = event.getText();
1174 if (text.length() == 1) {
1175 swingChar = text.charAt(0);
1176 }
1177 }
1178 long swingWhen = System.currentTimeMillis();
1179 java.awt.event.KeyEvent keyEvent = new java.awt.event.KeyEvent(
1180 frame, swingID, swingWhen, swingModifiers,
1181 swingKeyCode, swingChar);
1182 AccessController.doPrivileged(new PostEventAction(keyEvent));
1183 }
1184 }
1185 }
|
36 import javafx.scene.input.KeyCode;
37 import javafx.scene.input.KeyEvent;
38 import javafx.scene.input.MouseButton;
39 import javafx.scene.input.MouseEvent;
40 import javafx.scene.input.ScrollEvent;
41 import javafx.stage.Window;
42 import javax.swing.JComponent;
43 import javax.swing.Timer;
44 import java.awt.AWTEvent;
45 import java.awt.Component;
46 import java.awt.Cursor;
47 import java.awt.EventQueue;
48 import java.awt.Toolkit;
49 import java.lang.ref.WeakReference;
50 import java.awt.dnd.DragGestureEvent;
51 import java.awt.dnd.DragGestureListener;
52 import java.awt.dnd.DragGestureRecognizer;
53 import java.awt.dnd.DragSource;
54 import java.awt.dnd.DropTarget;
55 import java.awt.dnd.InvalidDnDOperationException;
56 import java.awt.event.InputEvent;
57 import java.awt.event.MouseWheelEvent;
58 import java.awt.event.WindowEvent;
59 import java.awt.event.WindowFocusListener;
60 import java.lang.reflect.Method;
61 import java.nio.IntBuffer;
62 import java.security.AccessController;
63 import java.security.PrivilegedAction;
64 import java.util.ArrayList;
65 import java.util.HashSet;
66 import java.util.List;
67 import java.util.Set;
68 import java.util.concurrent.locks.ReentrantLock;
69 import com.sun.javafx.embed.swing.Disposer;
70 import com.sun.javafx.embed.swing.DisposerRecord;
71 import com.sun.javafx.geom.BaseBounds;
72 import com.sun.javafx.geom.transform.BaseTransform;
73 import com.sun.javafx.scene.DirtyBits;
74 import com.sun.javafx.sg.prism.NGExternalNode;
75 import com.sun.javafx.sg.prism.NGNode;
76 import com.sun.javafx.stage.FocusUngrabEvent;
77 import com.sun.javafx.stage.WindowHelper;
78 import com.sun.javafx.tk.TKStage;
79 import com.sun.javafx.PlatformUtil;
80 import com.sun.javafx.embed.swing.SwingNodeHelper;
81 import com.sun.javafx.scene.NodeHelper;
82
83 import com.sun.javafx.util.Utils;
84
85 import jdk.swing.interop.LightweightFrameWrapper;
86 import jdk.swing.interop.LightweightContentWrapper;
87 import jdk.swing.interop.DragSourceContextWrapper;
88
89 import static javafx.stage.WindowEvent.WINDOW_HIDDEN;
90
91 /**
92 * This class is used to embed a Swing content into a JavaFX application.
93 * The content to be displayed is specified with the {@link #setContent} method
94 * that accepts an instance of Swing {@code JComponent}. The hierarchy of components
95 * contained in the {@code JComponent} instance should not contain any heavyweight
96 * components, otherwise {@code SwingNode} may fail to paint it. The content gets
97 * repainted automatically. All the input and focus events are forwarded to the
98 * {@code JComponent} instance transparently to the developer.
99 * <p>
100 * Here is a typical pattern which demonstrates how {@code SwingNode} can be used:
101 * <pre>
102 * public class SwingFx extends Application {
103 *
104 * @Override
105 * public void start(Stage stage) {
106 * final SwingNode swingNode = new SwingNode();
107 * createAndSetSwingContent(swingNode);
161 }
162
163 @Override
164 public boolean doComputeContains(Node node, double localX, double localY) {
165 return ((SwingNode) node).doComputeContains(localX, localY);
166 }
167 });
168 }
169
170 private double fxWidth;
171 private double fxHeight;
172
173 private int swingPrefWidth;
174 private int swingPrefHeight;
175 private int swingMaxWidth;
176 private int swingMaxHeight;
177 private int swingMinWidth;
178 private int swingMinHeight;
179
180 private volatile JComponent content;
181 private volatile LightweightFrameWrapper lwFrame;
182 final LightweightFrameWrapper getLightweightFrame() { return lwFrame; }
183
184 private NGExternalNode peer;
185
186 private final ReentrantLock paintLock = new ReentrantLock();
187
188 private boolean skipBackwardUnrgabNotification;
189 private boolean grabbed; // lwframe initiated grab
190 private Timer deactivate; // lwFrame deactivate delay for Linux
191
192 {
193 // To initialize the class helper at the begining each constructor of this class
194 SwingNodeHelper.initHelper(this);
195 }
196
197 /**
198 * Constructs a new instance of {@code SwingNode}.
199 */
200 public SwingNode() {
201 setFocusTraversable(true);
202 setEventHandler(MouseEvent.ANY, new SwingMouseEventHandler());
203 setEventHandler(KeyEvent.ANY, new SwingKeyEventHandler());
204 setEventHandler(ScrollEvent.SCROLL, new SwingScrollEventHandler());
205
206 focusedProperty().addListener((observable, oldValue, newValue) -> {
207 activateLwFrame(newValue);
208 });
209
210 //Workaround for RT-34170
211 javafx.scene.text.Font.getFamilies();
212 }
213
214
215 private EventHandler windowHiddenHandler = (Event event) -> {
216 if (lwFrame != null && event.getTarget() instanceof Window) {
217 final Window w = (Window) event.getTarget();
218 TKStage tk = WindowHelper.getPeer(w);
219 if (tk != null) {
220 if (isThreadMerged) {
221 overrideNativeWindowHandle(lwFrameWrapperClass, 0L, null);
222 } else {
223 // Postpone actual window closing to ensure that
224 // a native window handler is valid on a Swing side
225 tk.postponeClose();
226 SwingFXUtils.runOnEDT(() -> {
227 overrideNativeWindowHandle(lwFrameWrapperClass, 0L,
228 (Runnable) () -> SwingFXUtils.runOnFxThread(
229 () -> tk.closePostponed()));
230 });
231 }
232 }
233 }
234
235 };
236
237 private Window hWindow = null;
238 private void notifyNativeHandle(Window window) {
239 if (hWindow != window) {
240 if (hWindow != null) {
241 hWindow.removeEventHandler(WINDOW_HIDDEN, windowHiddenHandler);
242 }
243 if (window != null) {
244 window.addEventHandler(WINDOW_HIDDEN, windowHiddenHandler);
245 }
246 hWindow = window;
247 }
248
249 if (lwFrame != null) {
250 long rawHandle = 0L;
251 if (window != null) {
252 TKStage tkStage = WindowHelper.getPeer(window);
253 if (tkStage != null) {
254 rawHandle = tkStage.getRawHandle();
255 }
256 }
257 overrideNativeWindowHandle(lwFrameWrapperClass, rawHandle, null);
258 }
259 }
260
261 /**
262 * Attaches a {@code JComponent} instance to display in this {@code SwingNode}.
263 * <p>
264 * The method can be called either on the JavaFX Application thread or the Event Dispatch thread.
265 * Note however, that access to a Swing component must occur from the Event Dispatch thread
266 * according to the Swing threading restrictions.
267 *
268 * @param content a Swing component to display in this {@code SwingNode}
269 *
270 * @see java.awt.EventQueue#isDispatchThread()
271 * @see javafx.application.Platform#isFxApplicationThread()
272 */
273 public void setContent(final JComponent content) {
274 this.content = content;
275
276 SwingFXUtils.runOnEDT(() -> setContentImpl(content));
277 }
316 }
317
318 public boolean isIntegerApi() {
319 return isIntegerAPI;
320 }
321
322 public Object invoke(T object, Object... args) {
323 if (method != null) {
324 try {
325 return method.invoke(object, args);
326 } catch (Throwable ex) {
327 throw new RuntimeException("Error when calling " + object.getClass().getName() + "." + method.getName() + "().", ex);
328 }
329 } else {
330 return null;
331 }
332 }
333 }
334
335 /**
336 * Calls LightweightFrameWrapper.notifyDisplayChanged.
337 * Must be called on EDT only.
338 */
339 private static OptionalMethod<LightweightFrameWrapper> jlfNotifyDisplayChanged;
340 private static Class lwFrameWrapperClass = null;
341 private static native void overrideNativeWindowHandle(Class lwFrameWrapperClass, long handle,
342 Runnable closeWindow);
343
344 static {
345 jlfNotifyDisplayChanged = new OptionalMethod<>(LightweightFrameWrapper.class,
346 "notifyDisplayChanged", Double.TYPE, Double.TYPE);
347 if (!jlfNotifyDisplayChanged.isSupported()) {
348 jlfNotifyDisplayChanged = new OptionalMethod<>(
349 LightweightFrameWrapper.class,"notifyDisplayChanged", Integer.TYPE);
350 }
351
352 try {
353 lwFrameWrapperClass = Class.forName("jdk.swing.interop.LightweightFrameWrapper");
354 } catch (Throwable t) {}
355
356 Utils.loadNativeSwingLibrary();
357 }
358
359 /*
360 * Called on EDT
361 */
362 private void setContentImpl(JComponent content) {
363 if (lwFrame != null) {
364 lwFrame.dispose();
365 lwFrame = null;
366 }
367 if (content != null) {
368 lwFrame = new LightweightFrameWrapper();
369
370 SwingNodeWindowFocusListener snfListener =
371 new SwingNodeWindowFocusListener(this);
372 lwFrame.addWindowFocusListener(snfListener);
373
374 if (getScene() != null) {
375 Window window = getScene().getWindow();
376 if (window != null) {
377 if (jlfNotifyDisplayChanged.isIntegerApi()) {
378 jlfNotifyDisplayChanged.invoke(lwFrame,
379 (int) Math.round(window.getRenderScaleX()));
380 } else {
381 jlfNotifyDisplayChanged.invoke(lwFrame,
382 window.getRenderScaleX(),
383 window.getRenderScaleY());
384 }
385 }
386 }
387 lwFrame.setContent(new SwingNodeContent(content, this));
388 lwFrame.setVisible(true);
548 * @return the minimum height that the node should be resized to during layout
549 */
550 @Override public double minHeight(double width) {
551 return swingMinHeight;
552 }
553
554 /*
555 * Note: This method MUST only be called via its accessor method.
556 */
557 private boolean doComputeContains(double localX, double localY) {
558 return true;
559 }
560
561 private final InvalidationListener locationListener = observable -> {
562 locateLwFrame();
563 };
564
565 private final EventHandler<FocusUngrabEvent> ungrabHandler = event -> {
566 if (!skipBackwardUnrgabNotification) {
567 if (lwFrame != null) {
568 AccessController.doPrivileged(new PostEventAction(
569 lwFrame.createUngrabEvent(lwFrame)));
570 }
571 }
572 };
573
574 private final ChangeListener<Boolean> windowVisibleListener = (observable, oldValue, newValue) -> {
575 if (!newValue) {
576 disposeLwFrame();
577 } else {
578 setContent(content);
579 }
580 };
581
582 private final ChangeListener<Window> sceneWindowListener = (observable, oldValue, newValue) -> {
583 if (oldValue != null) {
584 removeWindowListeners(oldValue);
585 }
586
587 notifyNativeHandle(newValue);
588
589 if (newValue != null) {
663 setLwFrameVisible(newValue);
664 });
665
666 return peer;
667 }
668
669 /*
670 * Note: This method MUST only be called via its accessor method.
671 */
672 private void doUpdatePeer() {
673 if (NodeHelper.isDirty(this, DirtyBits.NODE_VISIBLE)
674 || NodeHelper.isDirty(this, DirtyBits.NODE_BOUNDS)) {
675 locateLwFrame(); // initialize location
676 }
677 if (NodeHelper.isDirty(this, DirtyBits.NODE_CONTENTS)) {
678 peer.markContentDirty();
679 }
680 }
681
682 /**
683 * Calls LightweightFrameWrapper.setHostBounds.
684 * Must be called on EDT only.
685 */
686 private static final OptionalMethod<LightweightFrameWrapper> jlfSetHostBounds =
687 new OptionalMethod<>(LightweightFrameWrapper.class, "setHostBounds",
688 Integer.TYPE, Integer.TYPE, Integer.TYPE, Integer.TYPE);
689
690 private void locateLwFrame() {
691 if (getScene() == null
692 || lwFrame == null
693 || getScene().getWindow() == null
694 || !getScene().getWindow().isShowing()) {
695 // Not initialized yet. Skip the update to set the real values later
696 return;
697 }
698 Window w = getScene().getWindow();
699 double renderScaleX = w.getRenderScaleX();
700 double renderScaleY = w.getRenderScaleY();
701 final Point2D loc = localToScene(0, 0);
702 final int windowX = (int) (w.getX());
703 final int windowY = (int) (w.getY());
704 final int windowW = (int) (w.getWidth());
705 final int windowH = (int) (w.getHeight());
706 final int frameX = (int) Math.round(w.getX() + getScene().getX() + loc.getX());
707 final int frameY = (int) Math.round(w.getY() + getScene().getY() + loc.getY());
787 if (jlfNotifyDisplayChanged.isIntegerApi()) {
788 jlfNotifyDisplayChanged.invoke(lwFrame,
789 (int)Math.round(scaleX));
790 } else {
791 jlfNotifyDisplayChanged.invoke(lwFrame, scaleX, scaleY);
792 }
793 }
794 });
795 }
796
797 /*
798 * Note: This method MUST only be called via its accessor method.
799 */
800 private BaseBounds doComputeGeomBounds(BaseBounds bounds, BaseTransform tx) {
801 bounds.deriveWithNewBounds(0, 0, 0, (float)fxWidth, (float)fxHeight, 0);
802 tx.transform(bounds, bounds);
803 return bounds;
804 }
805
806 private static class SwingNodeDisposer implements DisposerRecord {
807 LightweightFrameWrapper lwFrame;
808
809 SwingNodeDisposer(LightweightFrameWrapper ref) {
810 this.lwFrame = ref;
811 }
812 public void dispose() {
813 if (lwFrame != null) {
814 lwFrame.dispose();
815 lwFrame = null;
816 }
817 }
818 }
819
820 private static class SwingNodeWindowFocusListener implements WindowFocusListener {
821 private WeakReference<SwingNode> swingNodeRef;
822
823 SwingNodeWindowFocusListener(SwingNode swingNode) {
824 this.swingNodeRef = new WeakReference<SwingNode>(swingNode);
825 }
826
827 @Override
828 public void windowGainedFocus(WindowEvent e) {
829 SwingFXUtils.runOnFxThread(() -> {
830 SwingNode swingNode = swingNodeRef.get();
831 if (swingNode != null) {
832 swingNode.requestFocus();
833 }
834 });
835 }
836
837 @Override
838 public void windowLostFocus(WindowEvent e) {
839 SwingFXUtils.runOnFxThread(() -> {
840 SwingNode swingNode = swingNodeRef.get();
841 if (swingNode != null) {
842 swingNode.ungrabFocus(true);
843 }
844 });
845 }
846 }
847
848 private static class SwingNodeContent extends LightweightContentWrapper {
849 private JComponent comp;
850 private volatile FXDnD dnd;
851 private WeakReference<SwingNode> swingNodeRef;
852
853 SwingNodeContent(JComponent comp, SwingNode swingNode) {
854 this.comp = comp;
855 this.swingNodeRef = new WeakReference<SwingNode>(swingNode);
856 }
857 @Override
858 public JComponent getComponent() {
859 return comp;
860 }
861 @Override
862 public void paintLock() {
863 SwingNode swingNode = swingNodeRef.get();
864 if (swingNode != null) {
865 swingNode.paintLock.lock();
866 }
867 }
868 @Override
869 public void paintUnlock() {
870 SwingNode swingNode = swingNodeRef.get();
871 if (swingNode != null) {
872 swingNode.paintLock.unlock();
873 }
874 }
875
876 @Override
877 public void imageBufferReset(int[] data, int x, int y, int width, int height, int linestride) {
878 imageBufferReset(data, x, y, width, height, linestride, 1);
879 }
880 //@Override
881 public void imageBufferReset(int[] data, int x, int y, int width, int height, int linestride, int scale) {
882 SwingNode swingNode = swingNodeRef.get();
883 if (swingNode != null) {
884 swingNode.setImageBuffer(data, x, y, width, height, linestride, scale, scale);
885 }
886 }
887 @Override
888 public void imageBufferReset(int[] data, int x, int y, int width, int height, int linestride, double scaleX, double scaleY) {
889 SwingNode swingNode = swingNodeRef.get();
890 if (swingNode != null) {
891 swingNode.setImageBuffer(data, x, y, width, height, linestride, scaleX, scaleY);
892 }
893 }
894 @Override
895 public void imageReshaped(int x, int y, int width, int height) {
896 SwingNode swingNode = swingNodeRef.get();
897 if (swingNode != null) {
898 swingNode.setImageBounds(x, y, width, height);
899 }
900 }
901 @Override
902 public void imageUpdated(int dirtyX, int dirtyY, int dirtyWidth, int dirtyHeight) {
903 SwingNode swingNode = swingNodeRef.get();
904 if (swingNode != null) {
905 swingNode.repaintDirtyRegion(dirtyX, dirtyY, dirtyWidth, dirtyHeight);
906 }
907 }
972 SwingFXUtils.runOnFxThread(() -> {
973 SwingNode swingNode = swingNodeRef.get();
974 if (swingNode != null) {
975 swingNode.setCursor(SwingCursors.embedCursorToCursor(cursor));
976 }
977 });
978 }
979
980 private void initDnD() {
981 // This is a part of AWT API, so the method may be invoked on any thread
982 synchronized (SwingNodeContent.this) {
983 if (this.dnd == null) {
984 SwingNode swingNode = swingNodeRef.get();
985 if (swingNode != null) {
986 this.dnd = new FXDnD(swingNode);
987 }
988 }
989 }
990 }
991
992 @Override
993 public synchronized <T extends DragGestureRecognizer> T createDragGestureRecognizer(
994 Class<T> abstractRecognizerClass,
995 DragSource ds, Component c, int srcActions,
996 DragGestureListener dgl)
997 {
998 initDnD();
999 return dnd.createDragGestureRecognizer(abstractRecognizerClass, ds, c, srcActions, dgl);
1000 }
1001
1002 @Override
1003 public DragSourceContextWrapper createDragSourceContext(DragGestureEvent dge) throws InvalidDnDOperationException
1004 {
1005 initDnD();
1006 return dnd.createDragSourceContext(dge);
1007 }
1008
1009 @Override
1010 public void addDropTarget(DropTarget dt) {
1011 initDnD();
1012 dnd.addDropTarget(dt);
1013 }
1014
1015 @Override
1016 public void removeDropTarget(DropTarget dt) {
1017 initDnD();
1018 dnd.removeDropTarget(dt);
1019 }
1020 }
1021
1022 private void ungrabFocus(boolean postUngrabEvent) {
1023 // On X11 grab is limited to a single XDisplay connection,
1024 // so we can't delegate it to another GUI toolkit.
1025 if (PlatformUtil.isLinux()) return;
1026
1027 if (grabbed &&
1028 getScene() != null &&
1029 getScene().getWindow() != null &&
1030 WindowHelper.getPeer(getScene().getWindow()) != null)
1031 {
1032 skipBackwardUnrgabNotification = !postUngrabEvent;
1033 WindowHelper.getPeer(getScene().getWindow()).ungrabFocus();
1034 skipBackwardUnrgabNotification = false;
1035 grabbed = false;
1037 }
1038
1039 private class PostEventAction implements PrivilegedAction<Void> {
1040 private AWTEvent event;
1041 PostEventAction(AWTEvent event) {
1042 this.event = event;
1043 }
1044 @Override
1045 public Void run() {
1046 EventQueue eq = Toolkit.getDefaultToolkit().getSystemEventQueue();
1047 eq.postEvent(event);
1048 return null;
1049 }
1050 }
1051
1052 private class SwingMouseEventHandler implements EventHandler<MouseEvent> {
1053 private final Set<MouseButton> mouseClickedAllowed = new HashSet<>();
1054
1055 @Override
1056 public void handle(MouseEvent event) {
1057 LightweightFrameWrapper frame = lwFrame;
1058 if (frame == null) {
1059 return;
1060 }
1061 int swingID = SwingEvents.fxMouseEventTypeToMouseID(event);
1062 if (swingID < 0) {
1063 return;
1064 }
1065
1066 // Prevent ancestors of the SwingNode from stealing the focus
1067 event.consume();
1068
1069 final EventType<?> type = event.getEventType();
1070 if (type == MouseEvent.MOUSE_PRESSED) {
1071 mouseClickedAllowed.add(event.getButton());
1072 } else if (type == MouseEvent.MOUSE_RELEASED) {
1073 // RELEASED comes before CLICKED, so we don't remove the button from the set
1074 //mouseClickedAllowed.remove(event.getButton());
1075 } else if (type == MouseEvent.MOUSE_DRAGGED) {
1076 // This is what AWT/Swing do
1077 mouseClickedAllowed.clear();
1078 } else if (type == MouseEvent.MOUSE_CLICKED) {
1079 if (event.getClickCount() == 1 && !mouseClickedAllowed.contains(event.getButton())) {
1080 // RT-34610: In FX, CLICKED events are generated even after dragging the mouse pointer
1081 // Note that this is only relevant for single clicks. Double clicks use a smudge factor.
1082 return;
1083 }
1084 mouseClickedAllowed.remove(event.getButton());
1085 }
1086 int swingModifiers = SwingEvents.fxMouseModsToMouseMods(event);
1087 boolean swingPopupTrigger = event.isPopupTrigger();
1088 int swingButton = SwingEvents.fxMouseButtonToMouseButton(event);
1089 long swingWhen = System.currentTimeMillis();
1090 int relX = (int) Math.round(event.getX());
1091 int relY = (int) Math.round(event.getY());
1092 int absX = (int) Math.round(event.getScreenX());
1093 int absY = (int) Math.round(event.getScreenY());
1094 java.awt.event.MouseEvent mouseEvent =
1095 frame.createMouseEvent(
1096 frame, swingID, swingWhen, swingModifiers,
1097 relX, relY, absX, absY,
1098 event.getClickCount(), swingPopupTrigger, swingButton);
1099 AccessController.doPrivileged(new PostEventAction(mouseEvent));
1100 }
1101 }
1102
1103 private class SwingScrollEventHandler implements EventHandler<ScrollEvent> {
1104 @Override
1105 public void handle(ScrollEvent event) {
1106 LightweightFrameWrapper frame = lwFrame;
1107 if (frame == null) {
1108 return;
1109 }
1110
1111 int swingModifiers = SwingEvents.fxScrollModsToMouseWheelMods(event);
1112 final boolean isShift = (swingModifiers & InputEvent.SHIFT_DOWN_MASK) != 0;
1113
1114 // Vertical scroll.
1115 if (!isShift && event.getDeltaY() != 0.0) {
1116 sendMouseWheelEvent(frame, event.getX(), event.getY(),
1117 swingModifiers, event.getDeltaY() / event.getMultiplierY());
1118 }
1119 // Horizontal scroll or shirt+vertical scroll.
1120 final double delta = isShift && event.getDeltaY() != 0.0
1121 ? event.getDeltaY() / event.getMultiplierY()
1122 : event.getDeltaX() / event.getMultiplierX();
1123 if (delta != 0.0) {
1124 swingModifiers |= InputEvent.SHIFT_DOWN_MASK;
1125 sendMouseWheelEvent(frame, event.getX(), event.getY(),
1126 swingModifiers, delta);
1127 }
1128 }
1129
1130 private void sendMouseWheelEvent(LightweightFrameWrapper source, double fxX, double fxY, int swingModifiers, double delta) {
1131 int wheelRotation = (int) delta;
1132 int signum = (int) Math.signum(delta);
1133 if (signum * delta < 1) {
1134 wheelRotation = signum;
1135 }
1136 int x = (int) Math.round(fxX);
1137 int y = (int) Math.round(fxY);
1138 MouseWheelEvent mouseWheelEvent =
1139 lwFrame.createMouseWheelEvent(source, swingModifiers, x, y, -wheelRotation);
1140 AccessController.doPrivileged(new PostEventAction(mouseWheelEvent));
1141 }
1142 }
1143
1144 private class SwingKeyEventHandler implements EventHandler<KeyEvent> {
1145 @Override
1146 public void handle(KeyEvent event) {
1147 LightweightFrameWrapper frame = lwFrame;
1148 if (frame == null) {
1149 return;
1150 }
1151 if (event.getCharacter().isEmpty()) {
1152 // TODO: should we post an "empty" character?
1153 return;
1154 }
1155 // Don't let Arrows, Tab, Shift+Tab traverse focus out.
1156 if (event.getCode() == KeyCode.LEFT ||
1157 event.getCode() == KeyCode.RIGHT ||
1158 event.getCode() == KeyCode.UP ||
1159 event.getCode() == KeyCode.DOWN ||
1160 event.getCode() == KeyCode.TAB)
1161 {
1162 event.consume();
1163 }
1164
1165 int swingID = SwingEvents.fxKeyEventTypeToKeyID(event);
1166 if (swingID < 0) {
1167 return;
1168 }
1169 int swingModifiers = SwingEvents.fxKeyModsToKeyMods(event);
1170 int swingKeyCode = event.getCode().getCode();
1171 char swingChar = event.getCharacter().charAt(0);
1172
1173 // A workaround. Some swing L&F's process mnemonics on KEY_PRESSED,
1174 // for which swing provides a keychar. Extracting it from the text.
1175 if (event.getEventType() == javafx.scene.input.KeyEvent.KEY_PRESSED) {
1176 String text = event.getText();
1177 if (text.length() == 1) {
1178 swingChar = text.charAt(0);
1179 }
1180 }
1181 long swingWhen = System.currentTimeMillis();
1182 java.awt.event.KeyEvent keyEvent = frame.createKeyEvent(frame,
1183 swingID, swingWhen, swingModifiers, swingKeyCode,
1184 swingChar);
1185 AccessController.doPrivileged(new PostEventAction(keyEvent));
1186 }
1187 }
1188 }
|