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);
106 *
107 * StackPane pane = new StackPane();
108 * pane.getChildren().add(swingNode);
112 * }
113 *
114 * private void createAndSetSwingContent(final SwingNode swingNode) {
115 * SwingUtilities.invokeLater(new Runnable() {
116 * @Override
117 * public void run() {
118 * swingNode.setContent(new JButton("Click me!"));
119 * }
120 * });
121 * }
122 *
123 * public static void main(String[] args) {
124 * launch(args);
125 * }
126 * }
127 * </pre>
128 * @since JavaFX 8.0
129 */
130 public class SwingNode extends Node {
131 private static boolean isThreadMerged;
132
133 static {
134 AccessController.doPrivileged(new PrivilegedAction<Object>() {
135 public Object run() {
136 isThreadMerged = Boolean.valueOf(
137 System.getProperty("javafx.embed.singleThread"));
138 return null;
139 }
140 });
141
142 // This is used by classes in different packages to get access to
143 // private and package private methods.
144 SwingNodeHelper.setSwingNodeAccessor(new SwingNodeHelper.SwingNodeAccessor() {
145 @Override
146 public NGNode doCreatePeer(Node node) {
147 return ((SwingNode) node).doCreatePeer();
148 }
149
150 @Override
151 public void doUpdatePeer(Node node) {
152 ((SwingNode) node).doUpdatePeer();
153 }
154
155 @Override
156 public BaseBounds doComputeGeomBounds(Node node,
157 BaseBounds bounds, BaseTransform tx) {
158 return ((SwingNode) node).doComputeGeomBounds(bounds, tx);
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 }
276
277 /**
278 * Returns the {@code JComponent} instance attached to this {@code SwingNode}.
279 * <p>
280 * The method can be called either on the JavaFX Application thread or the Event Dispatch thread.
281 * Note however, that access to a Swing component must occur from the Event Dispatch thread
282 * according to the Swing threading restrictions.
283 *
284 * @see java.awt.EventQueue#isDispatchThread()
285 * @see javafx.application.Platform#isFxApplicationThread()
286 *
287 * @return the Swing component attached to this {@code SwingNode}
288 */
289 public JComponent getContent() {
290 return content;
291 }
292
293 private static final class OptionalMethod<T> {
294 private final Method method;
313 return method != null;
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);
383
384 SwingNodeDisposer disposeRec = new SwingNodeDisposer(lwFrame);
385 Disposer.addRecord(this, disposeRec);
386
387 if (getScene() != null) {
388 notifyNativeHandle(getScene().getWindow());
389 }
390
391 SwingFXUtils.runOnFxThread(() -> {
392 locateLwFrame(); // initialize location
393
394 if (focusedProperty().get()) {
395 activateLwFrame(true);
396 }
397 });
398 }
399 }
400
401 private List<Runnable> peerRequests = new ArrayList<>();
402
403 /*
404 * Called on EDT
405 */
406 void setImageBuffer(final int[] data,
407 final int x, final int y,
408 final int w, final int h,
409 final int linestride,
410 final double scaleX,
411 final double scaleY)
412 {
413 Runnable r = () -> peer.setImageBuffer(IntBuffer.wrap(data), x, y, w, h,
414 w, h, linestride, scaleX, scaleY);
415 SwingFXUtils.runOnFxThread(() -> {
416 if (peer != null) {
417 r.run();
418 } else {
419 peerRequests.clear();
420 peerRequests.add(r);
421 }
422 });
423 }
424
425 /*
426 * Called on EDT
427 */
428 void setImageBounds(final int x, final int y, final int w, final int h) {
429 Runnable r = () -> peer.setImageBounds(x, y, w, h, w, h);
430 SwingFXUtils.runOnFxThread(() -> {
431 if (peer != null) {
432 r.run();
433 } else {
434 peerRequests.add(r);
435 }
436 });
437 }
438
439 /*
440 * Called on EDT
441 */
442 void repaintDirtyRegion(final int dirtyX, final int dirtyY, final int dirtyWidth, final int dirtyHeight) {
443 Runnable r = () -> {
444 peer.repaintDirtyRegion(dirtyX, dirtyY, dirtyWidth, dirtyHeight);
445 NodeHelper.markDirty(this, DirtyBits.NODE_CONTENTS);
446 };
447 SwingFXUtils.runOnFxThread(() -> {
448 if (peer != null) {
449 r.run();
450 } else {
451 peerRequests.add(r);
452 }
453 });
454 }
455
456 @Override public boolean isResizable() {
457 return true;
458 }
459
460 /**
461 * Invoked by the {@code SwingNode}'s parent during layout to set the {@code SwingNode}'s
462 * width and height. <b>Applications should not invoke this method directly</b>.
463 * If an application needs to directly set the size of the {@code SwingNode}, it should
464 * set the Swing component's minimum/preferred/maximum size constraints which will
465 * be propagated correspondingly to the {@code SwingNode} and it's parent will honor those
466 * settings during layout.
467 *
468 * @param width the target layout bounds width
469 * @param height the target layout bounds height
470 */
471 @Override public void resize(final double width, final double height) {
472 super.resize(width, height);
473 if (width != this.fxWidth || height != this.fxHeight) {
474 this.fxWidth = width;
475 this.fxHeight = height;
476 NodeHelper.geomChanged(this);
477 NodeHelper.markDirty(this, DirtyBits.NODE_GEOMETRY);
478 SwingFXUtils.runOnEDT(() -> {
479 if (lwFrame != null) {
480 locateLwFrame();
481 }
482 });
483 }
484 }
485
486 /**
487 * Returns the {@code SwingNode}'s preferred width for use in layout calculations.
488 * This value corresponds to the preferred width of the Swing component.
489 *
490 * @return the preferred width that the node should be resized to during layout
491 */
492 @Override
493 public double prefWidth(double height) {
494 return swingPrefWidth;
495 }
496
497 /**
498 * Returns the {@code SwingNode}'s preferred height for use in layout calculations.
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) {
655 NodeHelper.treeVisibleProperty(this).addListener((observable, oldValue, newValue) -> {
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());
701 final int frameW = (int) (fxWidth);
702 final int frameH = (int) (fxHeight);
703
704 SwingFXUtils.runOnEDT(() -> {
705 if (lwFrame != null) {
706 if (jlfNotifyDisplayChanged.isIntegerApi()) {
707 jlfNotifyDisplayChanged.invoke(lwFrame,
708 (int)Math.round(renderScaleX));
709 } else {
710 jlfNotifyDisplayChanged.invoke(lwFrame, renderScaleX,
711 renderScaleY);
712 }
713 lwFrame.setBounds(frameX, frameY, frameW, frameH);
714 jlfSetHostBounds.invoke(lwFrame, windowX, windowY,
715 windowW, windowH);
716 }
717 });
718 }
719
720 private void activateLwFrame(final boolean activate) {
721 if (lwFrame == null) {
722 return;
723 }
724 if (PlatformUtil.isLinux()) {
725 // Workaround to block FocusOut/FocusIn notifications from Unity
726 // focus grabbing upon Alt press
727 if (deactivate == null || !deactivate.isRunning()) {
728 if (!activate) {
729 deactivate = new Timer(50, (e) -> {
730 {
731 if (lwFrame != null) {
732 lwFrame.emulateActivation(false);
733 }
734 }
735 });
736 deactivate.start();
737 return;
738 }
739 } else {
740 deactivate.stop();
741 }
742 }
743
744 SwingFXUtils.runOnEDT(() -> {
745 if (lwFrame != null) {
746 lwFrame.emulateActivation(activate);
747 }
748 });
749 }
750
751 private void disposeLwFrame() {
752 if (lwFrame == null) {
753 return;
754 }
755 SwingFXUtils.runOnEDT(() -> {
756 if (lwFrame != null) {
757 lwFrame.dispose();
758 lwFrame = null;
759 }
760 });
761 }
762
763 private void setLwFrameVisible(final boolean visible) {
764 if (lwFrame == null) {
765 return;
766 }
767 SwingFXUtils.runOnEDT(() -> {
768 if (lwFrame != null) {
769 lwFrame.setVisible(visible);
770 }
771 });
772 }
773
774 private void setLwFrameScale(final double scaleX, final double scaleY) {
775 if (lwFrame == null) {
776 return;
777 }
778 SwingFXUtils.runOnEDT(() -> {
779 if (lwFrame != null) {
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 }
903 @Override
904 public void focusGrabbed() {
905 SwingFXUtils.runOnFxThread(() -> {
906 // On X11 grab is limited to a single XDisplay connection,
907 // so we can't delegate it to another GUI toolkit.
908 if (PlatformUtil.isLinux()) return;
909
910 SwingNode swingNode = swingNodeRef.get();
911 if (swingNode != null) {
912 Scene scene = swingNode.getScene();
913 if (scene != null &&
914 scene.getWindow() != null &&
915 WindowHelper.getPeer(scene.getWindow()) != null) {
916 WindowHelper.getPeer(scene.getWindow()).grabFocus();
917 swingNode.grabbed = true;
918 }
919 }
920 });
921 }
922 @Override
923 public void focusUngrabbed() {
924 SwingFXUtils.runOnFxThread(() -> {
925 SwingNode swingNode = swingNodeRef.get();
926 if (swingNode != null) {
927 swingNode.ungrabFocus(false);
928 }
929 });
930 }
931 @Override
932 public void preferredSizeChanged(final int width, final int height) {
933 SwingFXUtils.runOnFxThread(() -> {
934 SwingNode swingNode = swingNodeRef.get();
935 if (swingNode != null) {
936 swingNode.swingPrefWidth = width;
937 swingNode.swingPrefHeight = height;
938 NodeHelper.notifyLayoutBoundsChanged(swingNode);
939 }
940 });
941 }
942 @Override
943 public void maximumSizeChanged(final int width, final int height) {
944 SwingFXUtils.runOnFxThread(() -> {
945 SwingNode swingNode = swingNodeRef.get();
946 if (swingNode != null) {
947 swingNode.swingMaxWidth = width;
948 swingNode.swingMaxHeight = height;
949 NodeHelper.notifyLayoutBoundsChanged(swingNode);
950 }
951 });
952 }
953 @Override
954 public void minimumSizeChanged(final int width, final int height) {
955 SwingFXUtils.runOnFxThread(() -> {
956 SwingNode swingNode = swingNodeRef.get();
957 if (swingNode != null) {
958 swingNode.swingMinWidth = width;
959 swingNode.swingMinHeight = height;
960 NodeHelper.notifyLayoutBoundsChanged(swingNode);
961 }
962 });
963 }
964
965 //@Override
966 public void setCursor(Cursor cursor) {
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;
1031 }
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.embed.swing.SwingEvents;
84 import com.sun.javafx.embed.swing.SwingCursors;
85 import static javafx.stage.WindowEvent.WINDOW_HIDDEN;
86
87 import com.sun.javafx.embed.swing.InteropFactory;
88 import com.sun.javafx.embed.swing.SwingNodeInterop;
89
90 /**
91 * This class is used to embed a Swing content into a JavaFX application.
92 * The content to be displayed is specified with the {@link #setContent} method
93 * that accepts an instance of Swing {@code JComponent}. The hierarchy of components
94 * contained in the {@code JComponent} instance should not contain any heavyweight
95 * components, otherwise {@code SwingNode} may fail to paint it. The content gets
96 * repainted automatically. All the input and focus events are forwarded to the
97 * {@code JComponent} instance transparently to the developer.
98 * <p>
99 * Here is a typical pattern which demonstrates how {@code SwingNode} can be used:
100 * <pre>
101 * public class SwingFx extends Application {
102 *
103 * @Override
104 * public void start(Stage stage) {
105 * final SwingNode swingNode = new SwingNode();
106 * createAndSetSwingContent(swingNode);
107 *
108 * StackPane pane = new StackPane();
109 * pane.getChildren().add(swingNode);
113 * }
114 *
115 * private void createAndSetSwingContent(final SwingNode swingNode) {
116 * SwingUtilities.invokeLater(new Runnable() {
117 * @Override
118 * public void run() {
119 * swingNode.setContent(new JButton("Click me!"));
120 * }
121 * });
122 * }
123 *
124 * public static void main(String[] args) {
125 * launch(args);
126 * }
127 * }
128 * </pre>
129 * @since JavaFX 8.0
130 */
131 public class SwingNode extends Node {
132 private static boolean isThreadMerged;
133 private SwingNodeInterop swiop;
134 private static InteropFactory instance = null;
135
136 static {
137 try {
138 instance = InteropFactory.getInstance();
139 } catch (Exception e) {
140 throw new ExceptionInInitializerError(e);
141 }
142
143 AccessController.doPrivileged(new PrivilegedAction<Object>() {
144 public Object run() {
145 isThreadMerged = Boolean.valueOf(
146 System.getProperty("javafx.embed.singleThread"));
147 return null;
148 }
149 });
150
151
152 // This is used by classes in different packages to get access to
153 // private and package private methods.
154 SwingNodeHelper.setSwingNodeAccessor(new SwingNodeHelper.SwingNodeAccessor() {
155 @Override
156 public NGNode doCreatePeer(Node node) {
157 return ((SwingNode) node).doCreatePeer();
158 }
159
160 @Override
161 public void doUpdatePeer(Node node) {
162 ((SwingNode) node).doUpdatePeer();
163 }
164
165 @Override
166 public BaseBounds doComputeGeomBounds(Node node,
167 BaseBounds bounds, BaseTransform tx) {
168 return ((SwingNode) node).doComputeGeomBounds(bounds, tx);
169 }
170
171 @Override
172 public boolean doComputeContains(Node node, double localX, double localY) {
173 return ((SwingNode) node).doComputeContains(localX, localY);
174 }
175
176 @Override
177 public Object getLightweightFrame(SwingNode node) {
178 return node.getLightweightFrame();
179 }
180
181 @Override
182 public ReentrantLock getPaintLock(SwingNode node) {
183 return node.getPaintLock();
184 }
185
186 @Override
187 public void setImageBuffer(SwingNode node, final int[] data,
188 final int x, final int y,
189 final int w, final int h, final int linestride,
190 final double scaleX, final double scaleY) {
191 node.setImageBuffer(data, x, y, w, h, linestride, scaleX, scaleY);
192 }
193
194 @Override
195 public void setImageBounds(SwingNode node, final int x, final int y,
196 final int w, final int h) {
197 node.setImageBounds(x, y, w, h);
198 }
199
200 @Override
201 public void repaintDirtyRegion(SwingNode node, final int dirtyX, final int dirtyY,
202 final int dirtyWidth, final int dirtyHeight) {
203 node.repaintDirtyRegion(dirtyX, dirtyY, dirtyWidth, dirtyHeight);
204 }
205
206 @Override
207 public void ungrabFocus(SwingNode node, boolean postUngrabEvent) {
208 node.ungrabFocus(postUngrabEvent);
209 }
210
211 @Override
212 public void setSwingPrefWidth(SwingNode node, int swingPrefWidth) {
213 node.swingPrefWidth = swingPrefWidth;
214 }
215
216 @Override
217 public void setSwingPrefHeight(SwingNode node, int swingPrefHeight) {
218 node.swingPrefHeight = swingPrefHeight;
219 }
220
221 @Override
222 public void setSwingMaxWidth(SwingNode node, int swingMaxWidth) {
223 node.swingMaxWidth = swingMaxWidth;
224 }
225
226 @Override
227 public void setSwingMaxHeight(SwingNode node, int swingMaxHeight) {
228 node.swingMaxHeight = swingMaxHeight;
229 }
230
231 @Override
232 public void setSwingMinWidth(SwingNode node, int swingMinWidth) {
233 node.swingMinWidth = swingMinWidth;
234 }
235
236 @Override
237 public void setSwingMinHeight(SwingNode node, int swingMinHeight) {
238 node.swingMinHeight = swingMinHeight;
239 }
240
241 @Override
242 public void setGrabbed(SwingNode node, boolean grab) {
243 node.grabbed = grab;
244 }
245 });
246 }
247
248 private double fxWidth;
249 private double fxHeight;
250 private int swingPrefWidth;
251 private int swingPrefHeight;
252 private int swingMaxWidth;
253 private int swingMaxHeight;
254 private int swingMinWidth;
255 private int swingMinHeight;
256
257 private volatile JComponent content;
258 private volatile Object lwFrame;
259 private final Object getLightweightFrame() { return lwFrame; }
260
261 private NGExternalNode peer;
262
263 private final ReentrantLock paintLock = new ReentrantLock();
264
265 private ReentrantLock getPaintLock() {
266 return paintLock;
267 }
268
269 private boolean skipBackwardUnrgabNotification;
270 private boolean grabbed; // lwframe initiated grab
271 private Timer deactivate; // lwFrame deactivate delay for Linux
272
273 {
274 // To initialize the class helper at the begining each constructor of this class
275 SwingNodeHelper.initHelper(this);
276 }
277
278 /**
279 * Constructs a new instance of {@code SwingNode}.
280 */
281 public SwingNode() {
282 swiop = instance.createSwingNodeImpl();
283 setFocusTraversable(true);
284 setEventHandler(MouseEvent.ANY, new SwingMouseEventHandler());
285 setEventHandler(KeyEvent.ANY, new SwingKeyEventHandler());
286 setEventHandler(ScrollEvent.SCROLL, new SwingScrollEventHandler());
287
288 focusedProperty().addListener((observable, oldValue, newValue) -> {
289 activateLwFrame(newValue);
290 });
291
292 //Workaround for RT-34170
293 javafx.scene.text.Font.getFamilies();
294 }
295
296
297 private EventHandler windowHiddenHandler = (Event event) -> {
298 if (lwFrame != null && event.getTarget() instanceof Window) {
299 final Window w = (Window) event.getTarget();
300 TKStage tk = WindowHelper.getPeer(w);
301 if (tk != null) {
302 if (isThreadMerged) {
303 swiop.overrideNativeWindowHandle(0L, null);
304 } else {
305 // Postpone actual window closing to ensure that
306 // a native window handler is valid on a Swing side
307 tk.postponeClose();
308 SwingNodeHelper.runOnEDT(() -> {
309 swiop.overrideNativeWindowHandle(0L,
310 (Runnable) () -> SwingNodeHelper.runOnFxThread(
311 () -> tk.closePostponed()));
312 });
313 }
314 }
315 }
316
317 };
318
319 private Window hWindow = null;
320 private void notifyNativeHandle(Window window) {
321 if (hWindow != window) {
322 if (hWindow != null) {
323 hWindow.removeEventHandler(WINDOW_HIDDEN, windowHiddenHandler);
324 }
325 if (window != null) {
326 window.addEventHandler(WINDOW_HIDDEN, windowHiddenHandler);
327 }
328 hWindow = window;
329 }
330
331 if (lwFrame != null) {
332 long rawHandle = 0L;
333 if (window != null) {
334 TKStage tkStage = WindowHelper.getPeer(window);
335 if (tkStage != null) {
336 rawHandle = tkStage.getRawHandle();
337 }
338 }
339 swiop.overrideNativeWindowHandle(rawHandle, null);
340 }
341 }
342
343 /**
344 * Attaches a {@code JComponent} instance to display in this {@code SwingNode}.
345 * <p>
346 * The method can be called either on the JavaFX Application thread or the Event Dispatch thread.
347 * Note however, that access to a Swing component must occur from the Event Dispatch thread
348 * according to the Swing threading restrictions.
349 *
350 * @param content a Swing component to display in this {@code SwingNode}
351 *
352 * @see java.awt.EventQueue#isDispatchThread()
353 * @see javafx.application.Platform#isFxApplicationThread()
354 */
355 public void setContent(final JComponent content) {
356 this.content = content;
357
358 SwingNodeHelper.runOnEDT(() -> setContentImpl(content));
359 }
360
361 /**
362 * Returns the {@code JComponent} instance attached to this {@code SwingNode}.
363 * <p>
364 * The method can be called either on the JavaFX Application thread or the Event Dispatch thread.
365 * Note however, that access to a Swing component must occur from the Event Dispatch thread
366 * according to the Swing threading restrictions.
367 *
368 * @see java.awt.EventQueue#isDispatchThread()
369 * @see javafx.application.Platform#isFxApplicationThread()
370 *
371 * @return the Swing component attached to this {@code SwingNode}
372 */
373 public JComponent getContent() {
374 return content;
375 }
376
377 private static final class OptionalMethod<T> {
378 private final Method method;
397 return method != null;
398 }
399
400 public boolean isIntegerApi() {
401 return isIntegerAPI;
402 }
403
404 public Object invoke(T object, Object... args) {
405 if (method != null) {
406 try {
407 return method.invoke(object, args);
408 } catch (Throwable ex) {
409 throw new RuntimeException("Error when calling " + object.getClass().getName() + "." + method.getName() + "().", ex);
410 }
411 } else {
412 return null;
413 }
414 }
415 }
416
417 /*
418 * Called on EDT
419 */
420 private void setContentImpl(JComponent content) {
421 if (lwFrame != null) {
422 swiop.disposeFrame(lwFrame);
423 lwFrame = null;
424 }
425 if (content != null) {
426 lwFrame = swiop.createLightweightFrame();
427
428 SwingNodeWindowFocusListener snfListener =
429 new SwingNodeWindowFocusListener(this);
430 swiop.addWindowFocusListener(lwFrame, snfListener);
431
432 if (getScene() != null) {
433 Window window = getScene().getWindow();
434 if (window != null) {
435 swiop.notifyDisplayChanged(lwFrame, window.getRenderScaleX(),
436 window.getRenderScaleY());
437 }
438 }
439 swiop.setContent(lwFrame, swiop.createSwingNodeContent(content, this));
440 swiop.setVisible(lwFrame, true);
441
442 Disposer.addRecord(this, swiop.createSwingNodeDisposer(lwFrame));
443
444 if (getScene() != null) {
445 notifyNativeHandle(getScene().getWindow());
446 }
447
448 SwingNodeHelper.runOnFxThread(() -> {
449 locateLwFrame();// initialize location
450
451 if (focusedProperty().get()) {
452 activateLwFrame(true);
453 }
454 });
455 }
456 }
457
458 private List<Runnable> peerRequests = new ArrayList<>();
459
460 /*
461 * Called on EDT
462 */
463 void setImageBuffer(final int[] data,
464 final int x, final int y,
465 final int w, final int h,
466 final int linestride,
467 final double scaleX,
468 final double scaleY)
469 {
470 Runnable r = () -> peer.setImageBuffer(IntBuffer.wrap(data), x, y, w, h,
471 w, h, linestride, scaleX, scaleY);
472 SwingNodeHelper.runOnFxThread(() -> {
473 if (peer != null) {
474 r.run();
475 } else {
476 peerRequests.clear();
477 peerRequests.add(r);
478 }
479 });
480 }
481
482 /*
483 * Called on EDT
484 */
485 void setImageBounds(final int x, final int y, final int w, final int h) {
486 Runnable r = () -> peer.setImageBounds(x, y, w, h, w, h);
487 SwingNodeHelper.runOnFxThread(() -> {
488 if (peer != null) {
489 r.run();
490 } else {
491 peerRequests.add(r);
492 }
493 });
494 }
495
496 /*
497 * Called on EDT
498 */
499 void repaintDirtyRegion(final int dirtyX, final int dirtyY, final int dirtyWidth, final int dirtyHeight) {
500 Runnable r = () -> {
501 peer.repaintDirtyRegion(dirtyX, dirtyY, dirtyWidth, dirtyHeight);
502 NodeHelper.markDirty(this, DirtyBits.NODE_CONTENTS);
503 };
504 SwingNodeHelper.runOnFxThread(() -> {
505 if (peer != null) {
506 r.run();
507 } else {
508 peerRequests.add(r);
509 }
510 });
511 }
512
513 @Override public boolean isResizable() {
514 return true;
515 }
516
517 /**
518 * Invoked by the {@code SwingNode}'s parent during layout to set the {@code SwingNode}'s
519 * width and height. <b>Applications should not invoke this method directly</b>.
520 * If an application needs to directly set the size of the {@code SwingNode}, it should
521 * set the Swing component's minimum/preferred/maximum size constraints which will
522 * be propagated correspondingly to the {@code SwingNode} and it's parent will honor those
523 * settings during layout.
524 *
525 * @param width the target layout bounds width
526 * @param height the target layout bounds height
527 */
528 @Override public void resize(final double width, final double height) {
529 super.resize(width, height);
530 if (width != this.fxWidth || height != this.fxHeight) {
531 this.fxWidth = width;
532 this.fxHeight = height;
533 NodeHelper.geomChanged(this);
534 NodeHelper.markDirty(this, DirtyBits.NODE_GEOMETRY);
535 SwingNodeHelper.runOnEDT(() -> {
536 if (lwFrame != null) {
537 locateLwFrame();
538 }
539 });
540 }
541 }
542
543 /**
544 * Returns the {@code SwingNode}'s preferred width for use in layout calculations.
545 * This value corresponds to the preferred width of the Swing component.
546 *
547 * @return the preferred width that the node should be resized to during layout
548 */
549 @Override
550 public double prefWidth(double height) {
551 return swingPrefWidth;
552 }
553
554 /**
555 * Returns the {@code SwingNode}'s preferred height for use in layout calculations.
599 * @return the minimum height that the node should be resized to during layout
600 */
601 @Override public double minHeight(double width) {
602 return swingMinHeight;
603 }
604
605 /*
606 * Note: This method MUST only be called via its accessor method.
607 */
608 private boolean doComputeContains(double localX, double localY) {
609 return true;
610 }
611
612 private final InvalidationListener locationListener = observable -> {
613 locateLwFrame();
614 };
615
616 private final EventHandler<FocusUngrabEvent> ungrabHandler = event -> {
617 if (!skipBackwardUnrgabNotification) {
618 if (lwFrame != null) {
619 AccessController.doPrivileged(new PostEventAction(
620 swiop.createUngrabEvent(lwFrame)));
621 }
622 }
623 };
624
625 private final ChangeListener<Boolean> windowVisibleListener = (observable, oldValue, newValue) -> {
626 if (!newValue) {
627 disposeLwFrame();
628 } else {
629 setContent(content);
630 }
631 };
632
633 private final ChangeListener<Window> sceneWindowListener = (observable, oldValue, newValue) -> {
634 if (oldValue != null) {
635 removeWindowListeners(oldValue);
636 }
637
638 notifyNativeHandle(newValue);
639
640 if (newValue != null) {
713 NodeHelper.treeVisibleProperty(this).addListener((observable, oldValue, newValue) -> {
714 setLwFrameVisible(newValue);
715 });
716
717 return peer;
718 }
719
720 /*
721 * Note: This method MUST only be called via its accessor method.
722 */
723 private void doUpdatePeer() {
724 if (NodeHelper.isDirty(this, DirtyBits.NODE_VISIBLE)
725 || NodeHelper.isDirty(this, DirtyBits.NODE_BOUNDS)) {
726 locateLwFrame(); // initialize location
727 }
728 if (NodeHelper.isDirty(this, DirtyBits.NODE_CONTENTS)) {
729 peer.markContentDirty();
730 }
731 }
732
733 private void locateLwFrame() {
734 if (getScene() == null
735 || lwFrame == null
736 || getScene().getWindow() == null
737 || !getScene().getWindow().isShowing()) {
738 // Not initialized yet. Skip the update to set the real values later
739 return;
740 }
741 Window w = getScene().getWindow();
742 double renderScaleX = w.getRenderScaleX();
743 double renderScaleY = w.getRenderScaleY();
744 final Point2D loc = localToScene(0, 0);
745 final int windowX = (int) (w.getX());
746 final int windowY = (int) (w.getY());
747 final int windowW = (int) (w.getWidth());
748 final int windowH = (int) (w.getHeight());
749 final int frameX = (int) Math.round(w.getX() + getScene().getX() + loc.getX());
750 final int frameY = (int) Math.round(w.getY() + getScene().getY() + loc.getY());
751 final int frameW = (int) (fxWidth);
752 final int frameH = (int) (fxHeight);
753
754 SwingNodeHelper.runOnEDT(() -> {
755 if (lwFrame != null) {
756 swiop.notifyDisplayChanged(lwFrame, renderScaleX, renderScaleY);
757 swiop.setBounds(lwFrame, frameX, frameY, frameW, frameH);
758 swiop.setHostBounds(lwFrame, windowX, windowY, windowW, windowH);
759 }
760 });
761 }
762
763 private void activateLwFrame(final boolean activate) {
764 if (lwFrame == null) {
765 return;
766 }
767 if (PlatformUtil.isLinux()) {
768 // Workaround to block FocusOut/FocusIn notifications from Unity
769 // focus grabbing upon Alt press
770 if (deactivate == null || !deactivate.isRunning()) {
771 if (!activate) {
772 deactivate = new Timer(50, (e) -> {
773 {
774 if (lwFrame != null) {
775 swiop.emulateActivation(lwFrame, false);
776 }
777 }
778 });
779 deactivate.start();
780 return;
781 }
782 } else {
783 deactivate.stop();
784 }
785 }
786
787 SwingNodeHelper.runOnEDT(() -> {
788 if (lwFrame != null) {
789 swiop.emulateActivation(lwFrame, activate);
790 }
791 });
792 }
793
794 private void disposeLwFrame() {
795 if (lwFrame == null) {
796 return;
797 }
798 SwingNodeHelper.runOnEDT(() -> {
799 if (lwFrame != null) {
800 swiop.disposeFrame(lwFrame);
801 lwFrame = null;
802 }
803 });
804 }
805
806 private void setLwFrameVisible(final boolean visible) {
807 if (lwFrame == null) {
808 return;
809 }
810 SwingNodeHelper.runOnEDT(() -> {
811 if (lwFrame != null) {
812 swiop.setVisible(lwFrame, visible);
813 }
814 });
815 }
816
817 private void setLwFrameScale(final double scaleX, final double scaleY) {
818 if (lwFrame == null) {
819 return;
820 }
821 SwingNodeHelper.runOnEDT(() -> {
822 if (lwFrame != null) {
823 swiop.notifyDisplayChanged(lwFrame, scaleX, scaleY);
824 }
825 });
826 }
827
828 /*
829 * Note: This method MUST only be called via its accessor method.
830 */
831 private BaseBounds doComputeGeomBounds(BaseBounds bounds, BaseTransform tx) {
832 bounds.deriveWithNewBounds(0, 0, 0, (float)fxWidth, (float)fxHeight, 0);
833 tx.transform(bounds, bounds);
834 return bounds;
835 }
836
837
838 private static class SwingNodeWindowFocusListener implements WindowFocusListener {
839 private WeakReference<SwingNode> swingNodeRef;
840
841 SwingNodeWindowFocusListener(SwingNode swingNode) {
842 this.swingNodeRef = new WeakReference<SwingNode>(swingNode);
843 }
844
845 @Override
846 public void windowGainedFocus(WindowEvent e) {
847 SwingNodeHelper.runOnFxThread(() -> {
848 SwingNode swingNode = swingNodeRef.get();
849 if (swingNode != null) {
850 swingNode.requestFocus();
851 }
852 });
853 }
854
855 @Override
856 public void windowLostFocus(WindowEvent e) {
857 SwingNodeHelper.runOnFxThread(() -> {
858 SwingNode swingNode = swingNodeRef.get();
859 if (swingNode != null) {
860 swingNode.ungrabFocus(true);
861 }
862 });
863 }
864 }
865
866 private void ungrabFocus(boolean postUngrabEvent) {
867 // On X11 grab is limited to a single XDisplay connection,
868 // so we can't delegate it to another GUI toolkit.
869 if (PlatformUtil.isLinux()) return;
870
871 if (grabbed &&
872 getScene() != null &&
873 getScene().getWindow() != null &&
874 WindowHelper.getPeer(getScene().getWindow()) != null)
875 {
876 skipBackwardUnrgabNotification = !postUngrabEvent;
877 WindowHelper.getPeer(getScene().getWindow()).ungrabFocus();
878 skipBackwardUnrgabNotification = false;
879 grabbed = false;
880 }
881 }
882
883 private class PostEventAction implements PrivilegedAction<Void> {
884 private AWTEvent event;
885 PostEventAction(AWTEvent event) {
886 this.event = event;
887 }
888 @Override
889 public Void run() {
890 EventQueue eq = Toolkit.getDefaultToolkit().getSystemEventQueue();
891 eq.postEvent(event);
892 return null;
893 }
894 }
895
896 private class SwingMouseEventHandler implements EventHandler<MouseEvent> {
897 private final Set<MouseButton> mouseClickedAllowed = new HashSet<>();
898
899 @Override
900 public void handle(MouseEvent event) {
901 Object frame = swiop.getLightweightFrame();
902 if (frame == null) {
903 return;
904 }
905 int swingID = SwingEvents.fxMouseEventTypeToMouseID(event);
906 if (swingID < 0) {
907 return;
908 }
909
910 // Prevent ancestors of the SwingNode from stealing the focus
911 event.consume();
912
913 final EventType<?> type = event.getEventType();
914 if (type == MouseEvent.MOUSE_PRESSED) {
915 mouseClickedAllowed.add(event.getButton());
916 } else if (type == MouseEvent.MOUSE_RELEASED) {
917 // RELEASED comes before CLICKED, so we don't remove the button from the set
918 //mouseClickedAllowed.remove(event.getButton());
919 } else if (type == MouseEvent.MOUSE_DRAGGED) {
920 // This is what AWT/Swing do
921 mouseClickedAllowed.clear();
922 } else if (type == MouseEvent.MOUSE_CLICKED) {
923 if (event.getClickCount() == 1 && !mouseClickedAllowed.contains(event.getButton())) {
924 // RT-34610: In FX, CLICKED events are generated even after dragging the mouse pointer
925 // Note that this is only relevant for single clicks. Double clicks use a smudge factor.
926 return;
927 }
928 mouseClickedAllowed.remove(event.getButton());
929 }
930 int swingModifiers = SwingEvents.fxMouseModsToMouseMods(event);
931 boolean swingPopupTrigger = event.isPopupTrigger();
932 int swingButton = SwingEvents.fxMouseButtonToMouseButton(event);
933 long swingWhen = System.currentTimeMillis();
934 int relX = (int) Math.round(event.getX());
935 int relY = (int) Math.round(event.getY());
936 int absX = (int) Math.round(event.getScreenX());
937 int absY = (int) Math.round(event.getScreenY());
938 java.awt.event.MouseEvent mouseEvent =
939 swiop.createMouseEvent(
940 frame, swingID, swingWhen, swingModifiers,
941 relX, relY, absX, absY,
942 event.getClickCount(), swingPopupTrigger, swingButton);
943 AccessController.doPrivileged(new PostEventAction(mouseEvent));
944 }
945 }
946
947 private class SwingScrollEventHandler implements EventHandler<ScrollEvent> {
948 @Override
949 public void handle(ScrollEvent event) {
950 Object frame = swiop.getLightweightFrame();
951 if (frame == null) {
952 return;
953 }
954
955 int swingModifiers = SwingEvents.fxScrollModsToMouseWheelMods(event);
956 final boolean isShift = (swingModifiers & InputEvent.SHIFT_DOWN_MASK) != 0;
957
958 // Vertical scroll.
959 if (!isShift && event.getDeltaY() != 0.0) {
960 sendMouseWheelEvent(frame, event.getX(), event.getY(),
961 swingModifiers, event.getDeltaY() / event.getMultiplierY());
962 }
963 // Horizontal scroll or shirt+vertical scroll.
964 final double delta = isShift && event.getDeltaY() != 0.0
965 ? event.getDeltaY() / event.getMultiplierY()
966 : event.getDeltaX() / event.getMultiplierX();
967 if (delta != 0.0) {
968 swingModifiers |= InputEvent.SHIFT_DOWN_MASK;
969 sendMouseWheelEvent(frame, event.getX(), event.getY(),
970 swingModifiers, delta);
971 }
972 }
973
974 private void sendMouseWheelEvent(Object source, double fxX, double fxY, int swingModifiers, double delta) {
975 int wheelRotation = (int) delta;
976 int signum = (int) Math.signum(delta);
977 if (signum * delta < 1) {
978 wheelRotation = signum;
979 }
980 int x = (int) Math.round(fxX);
981 int y = (int) Math.round(fxY);
982 MouseWheelEvent mouseWheelEvent =
983 swiop.createMouseWheelEvent(source, swingModifiers, x, y, -wheelRotation);
984 AccessController.doPrivileged(new PostEventAction(mouseWheelEvent));
985 }
986 }
987
988 private class SwingKeyEventHandler implements EventHandler<KeyEvent> {
989 @Override
990 public void handle(KeyEvent event) {
991 Object frame = swiop.getLightweightFrame();
992 if (frame == null) {
993 return;
994 }
995 if (event.getCharacter().isEmpty()) {
996 // TODO: should we post an "empty" character?
997 return;
998 }
999 // Don't let Arrows, Tab, Shift+Tab traverse focus out.
1000 if (event.getCode() == KeyCode.LEFT ||
1001 event.getCode() == KeyCode.RIGHT ||
1002 event.getCode() == KeyCode.UP ||
1003 event.getCode() == KeyCode.DOWN ||
1004 event.getCode() == KeyCode.TAB)
1005 {
1006 event.consume();
1007 }
1008
1009 int swingID = SwingEvents.fxKeyEventTypeToKeyID(event);
1010 if (swingID < 0) {
1011 return;
1012 }
1013 int swingModifiers = SwingEvents.fxKeyModsToKeyMods(event);
1014 int swingKeyCode = event.getCode().getCode();
1015 char swingChar = event.getCharacter().charAt(0);
1016
1017 // A workaround. Some swing L&F's process mnemonics on KEY_PRESSED,
1018 // for which swing provides a keychar. Extracting it from the text.
1019 if (event.getEventType() == javafx.scene.input.KeyEvent.KEY_PRESSED) {
1020 String text = event.getText();
1021 if (text.length() == 1) {
1022 swingChar = text.charAt(0);
1023 }
1024 }
1025 long swingWhen = System.currentTimeMillis();
1026 java.awt.event.KeyEvent keyEvent = swiop.createKeyEvent(frame,
1027 swingID, swingWhen, swingModifiers, swingKeyCode,
1028 swingChar);
1029 AccessController.doPrivileged(new PostEventAction(keyEvent));
1030 }
1031 }
1032 }
1033
|