1 /*
2 * Copyright (c) 2013, 2017, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation. Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26 package javafx.embed.swing;
27
28 import javafx.beans.InvalidationListener;
29 import javafx.beans.value.ChangeListener;
30 import javafx.event.Event;
31 import javafx.event.EventHandler;
32 import javafx.event.EventType;
33 import javafx.geometry.Point2D;
34 import javafx.scene.Node;
35 import javafx.scene.Scene;
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;
295 private final boolean isIntegerAPI;
296
297 OptionalMethod(Class<T> cls, String name, Class<?>... args) {
298 Method m;
299 try {
300 m = cls.getMethod(name, args);
301 } catch (NoSuchMethodException ignored) {
302 // This means we're running with older JDK, simply skip the call
303 m = null;
304 } catch (Throwable ex) {
305 throw new RuntimeException("Error when calling " + cls.getName() + ".getMethod('" + name + "').", ex);
306 }
307 method = m;
308 isIntegerAPI = args != null && args.length > 0 &&
309 args[0] == Integer.TYPE;
310 }
311
312 public boolean isSupported() {
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 }
|
1 /*
2 * Copyright (c) 2013, 2018, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation. Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26 package javafx.embed.swing;
27
28 import javax.swing.JComponent;
29 import javax.swing.Timer;
30 import java.awt.AWTEvent;
31 import java.awt.EventQueue;
32 import java.awt.Toolkit;
33 import java.awt.event.InputEvent;
34 import java.awt.event.MouseWheelEvent;
35 import java.awt.event.WindowEvent;
36 import java.awt.event.WindowFocusListener;
37 import java.util.ArrayList;
38 import java.util.HashSet;
39 import java.util.List;
40 import java.util.Set;
41 import java.util.concurrent.locks.ReentrantLock;
42 import javafx.beans.InvalidationListener;
43 import javafx.beans.value.ChangeListener;
44 import javafx.event.Event;
45 import javafx.event.EventHandler;
46 import javafx.event.EventType;
47 import javafx.geometry.Point2D;
48 import javafx.scene.Node;
49 import javafx.scene.Scene;
50 import javafx.scene.input.KeyCode;
51 import javafx.scene.input.KeyEvent;
52 import javafx.scene.input.MouseButton;
53 import javafx.scene.input.MouseEvent;
54 import javafx.scene.input.ScrollEvent;
55 import javafx.stage.Window;
56 import java.lang.ref.WeakReference;
57 import java.nio.IntBuffer;
58 import java.security.AccessController;
59 import java.security.PrivilegedAction;
60 import com.sun.javafx.embed.swing.Disposer;
61 import com.sun.javafx.embed.swing.DisposerRecord;
62 import com.sun.javafx.geom.BaseBounds;
63 import com.sun.javafx.geom.transform.BaseTransform;
64 import com.sun.javafx.scene.DirtyBits;
65 import com.sun.javafx.sg.prism.NGExternalNode;
66 import com.sun.javafx.sg.prism.NGNode;
67 import com.sun.javafx.stage.FocusUngrabEvent;
68 import com.sun.javafx.stage.WindowHelper;
69 import com.sun.javafx.tk.TKStage;
70 import com.sun.javafx.PlatformUtil;
71 import com.sun.javafx.scene.NodeHelper;
72 import static javafx.stage.WindowEvent.WINDOW_HIDDEN;
73
74 import com.sun.javafx.embed.swing.SwingNodeHelper;
75 import com.sun.javafx.embed.swing.SwingEvents;
76 import com.sun.javafx.embed.swing.InteropFactory;
77 import com.sun.javafx.embed.swing.SwingNodeInterop;
78
79 /**
80 * This class is used to embed a Swing content into a JavaFX application.
81 * The content to be displayed is specified with the {@link #setContent} method
82 * that accepts an instance of Swing {@code JComponent}. The hierarchy of components
83 * contained in the {@code JComponent} instance should not contain any heavyweight
84 * components, otherwise {@code SwingNode} may fail to paint it. The content gets
85 * repainted automatically. All the input and focus events are forwarded to the
86 * {@code JComponent} instance transparently to the developer.
87 * <p>
88 * Here is a typical pattern which demonstrates how {@code SwingNode} can be used:
89 * <pre>
90 * public class SwingFx extends Application {
91 *
92 * @Override
93 * public void start(Stage stage) {
94 * final SwingNode swingNode = new SwingNode();
95 * createAndSetSwingContent(swingNode);
96 *
97 * StackPane pane = new StackPane();
98 * pane.getChildren().add(swingNode);
102 * }
103 *
104 * private void createAndSetSwingContent(final SwingNode swingNode) {
105 * SwingUtilities.invokeLater(new Runnable() {
106 * @Override
107 * public void run() {
108 * swingNode.setContent(new JButton("Click me!"));
109 * }
110 * });
111 * }
112 *
113 * public static void main(String[] args) {
114 * launch(args);
115 * }
116 * }
117 * </pre>
118 * @since JavaFX 8.0
119 */
120 public class SwingNode extends Node {
121 private static boolean isThreadMerged;
122 private SwingNodeInterop swNodeIOP;
123 private static InteropFactory iopFactoryInstance = null;
124
125 static {
126 try {
127 iopFactoryInstance = InteropFactory.getInstance();
128 } catch (Exception e) {
129 throw new ExceptionInInitializerError(e);
130 }
131
132 AccessController.doPrivileged(new PrivilegedAction<Object>() {
133 public Object run() {
134 isThreadMerged = Boolean.valueOf(
135 System.getProperty("javafx.embed.singleThread"));
136 return null;
137 }
138 });
139
140
141 // This is used by classes in different packages to get access to
142 // private and package private methods.
143 SwingNodeHelper.setSwingNodeAccessor(new SwingNodeHelper.SwingNodeAccessor() {
144 @Override
145 public NGNode doCreatePeer(Node node) {
146 return ((SwingNode) node).doCreatePeer();
147 }
148
149 @Override
150 public void doUpdatePeer(Node node) {
151 ((SwingNode) node).doUpdatePeer();
152 }
153
154 @Override
155 public BaseBounds doComputeGeomBounds(Node node,
156 BaseBounds bounds, BaseTransform tx) {
157 return ((SwingNode) node).doComputeGeomBounds(bounds, tx);
158 }
159
160 @Override
161 public boolean doComputeContains(Node node, double localX, double localY) {
162 return ((SwingNode) node).doComputeContains(localX, localY);
163 }
164
165 @Override
166 public Object getLightweightFrame(SwingNode node) {
167 return node.getLightweightFrame();
168 }
169
170 @Override
171 public ReentrantLock getPaintLock(SwingNode node) {
172 return node.getPaintLock();
173 }
174
175 @Override
176 public void setImageBuffer(SwingNode node, final int[] data,
177 final int x, final int y,
178 final int w, final int h, final int linestride,
179 final double scaleX, final double scaleY) {
180 node.setImageBuffer(data, x, y, w, h, linestride, scaleX, scaleY);
181 }
182
183 @Override
184 public void setImageBounds(SwingNode node, final int x, final int y,
185 final int w, final int h) {
186 node.setImageBounds(x, y, w, h);
187 }
188
189 @Override
190 public void repaintDirtyRegion(SwingNode node, final int dirtyX, final int dirtyY,
191 final int dirtyWidth, final int dirtyHeight) {
192 node.repaintDirtyRegion(dirtyX, dirtyY, dirtyWidth, dirtyHeight);
193 }
194
195 @Override
196 public void ungrabFocus(SwingNode node, boolean postUngrabEvent) {
197 node.ungrabFocus(postUngrabEvent);
198 }
199
200 @Override
201 public void setSwingPrefWidth(SwingNode node, int swingPrefWidth) {
202 node.swingPrefWidth = swingPrefWidth;
203 }
204
205 @Override
206 public void setSwingPrefHeight(SwingNode node, int swingPrefHeight) {
207 node.swingPrefHeight = swingPrefHeight;
208 }
209
210 @Override
211 public void setSwingMaxWidth(SwingNode node, int swingMaxWidth) {
212 node.swingMaxWidth = swingMaxWidth;
213 }
214
215 @Override
216 public void setSwingMaxHeight(SwingNode node, int swingMaxHeight) {
217 node.swingMaxHeight = swingMaxHeight;
218 }
219
220 @Override
221 public void setSwingMinWidth(SwingNode node, int swingMinWidth) {
222 node.swingMinWidth = swingMinWidth;
223 }
224
225 @Override
226 public void setSwingMinHeight(SwingNode node, int swingMinHeight) {
227 node.swingMinHeight = swingMinHeight;
228 }
229
230 @Override
231 public void setGrabbed(SwingNode node, boolean grab) {
232 node.grabbed = grab;
233 }
234 });
235 }
236
237 private double fxWidth;
238 private double fxHeight;
239 private int swingPrefWidth;
240 private int swingPrefHeight;
241 private int swingMaxWidth;
242 private int swingMaxHeight;
243 private int swingMinWidth;
244 private int swingMinHeight;
245
246 private volatile JComponent content;
247 private volatile Object lwFrame;
248 private final Object getLightweightFrame() { return lwFrame; }
249
250 private NGExternalNode peer;
251
252 private final ReentrantLock paintLock = new ReentrantLock();
253
254 private ReentrantLock getPaintLock() {
255 return paintLock;
256 }
257
258 private boolean skipBackwardUnrgabNotification;
259 private boolean grabbed; // lwframe initiated grab
260 private Timer deactivate; // lwFrame deactivate delay for Linux
261
262 {
263 // To initialize the class helper at the begining each constructor of this class
264 SwingNodeHelper.initHelper(this);
265 }
266
267 /**
268 * Constructs a new instance of {@code SwingNode}.
269 */
270 public SwingNode() {
271 swNodeIOP = iopFactoryInstance.createSwingNodeImpl();
272 setFocusTraversable(true);
273 setEventHandler(MouseEvent.ANY, new SwingMouseEventHandler());
274 setEventHandler(KeyEvent.ANY, new SwingKeyEventHandler());
275 setEventHandler(ScrollEvent.SCROLL, new SwingScrollEventHandler());
276
277 focusedProperty().addListener((observable, oldValue, newValue) -> {
278 activateLwFrame(newValue);
279 });
280
281 //Workaround for RT-34170
282 javafx.scene.text.Font.getFamilies();
283 }
284
285
286 private EventHandler windowHiddenHandler = (Event event) -> {
287 if (lwFrame != null && event.getTarget() instanceof Window) {
288 final Window w = (Window) event.getTarget();
289 TKStage tk = WindowHelper.getPeer(w);
290 if (tk != null) {
291 if (isThreadMerged) {
292 swNodeIOP.overrideNativeWindowHandle(lwFrame, 0L, null);
293 } else {
294 // Postpone actual window closing to ensure that
295 // a native window handler is valid on a Swing side
296 tk.postponeClose();
297 SwingNodeHelper.runOnEDT(() -> {
298 swNodeIOP.overrideNativeWindowHandle(lwFrame, 0L,
299 (Runnable) () -> SwingNodeHelper.runOnFxThread(
300 () -> tk.closePostponed()));
301 });
302 }
303 }
304 }
305
306 };
307
308 private Window hWindow = null;
309 private void notifyNativeHandle(Window window) {
310 if (hWindow != window) {
311 if (hWindow != null) {
312 hWindow.removeEventHandler(WINDOW_HIDDEN, windowHiddenHandler);
313 }
314 if (window != null) {
315 window.addEventHandler(WINDOW_HIDDEN, windowHiddenHandler);
316 }
317 hWindow = window;
318 }
319
320 if (lwFrame != null) {
321 long rawHandle = 0L;
322 if (window != null) {
323 TKStage tkStage = WindowHelper.getPeer(window);
324 if (tkStage != null) {
325 rawHandle = tkStage.getRawHandle();
326 }
327 }
328 swNodeIOP.overrideNativeWindowHandle(lwFrame, rawHandle, null);
329 }
330 }
331
332 /**
333 * Attaches a {@code JComponent} instance to display in this {@code SwingNode}.
334 * <p>
335 * The method can be called either on the JavaFX Application thread or the Event Dispatch thread.
336 * Note however, that access to a Swing component must occur from the Event Dispatch thread
337 * according to the Swing threading restrictions.
338 *
339 * @param content a Swing component to display in this {@code SwingNode}
340 *
341 * @see java.awt.EventQueue#isDispatchThread()
342 * @see javafx.application.Platform#isFxApplicationThread()
343 */
344 public void setContent(final JComponent content) {
345 this.content = content;
346
347 SwingNodeHelper.runOnEDT(() -> setContentImpl(content));
348 }
349
350 /**
351 * Returns the {@code JComponent} instance attached to this {@code SwingNode}.
352 * <p>
353 * The method can be called either on the JavaFX Application thread or the Event Dispatch thread.
354 * Note however, that access to a Swing component must occur from the Event Dispatch thread
355 * according to the Swing threading restrictions.
356 *
357 * @see java.awt.EventQueue#isDispatchThread()
358 * @see javafx.application.Platform#isFxApplicationThread()
359 *
360 * @return the Swing component attached to this {@code SwingNode}
361 */
362 public JComponent getContent() {
363 return content;
364 }
365
366 /*
367 * Called on EDT
368 */
369 private void setContentImpl(JComponent content) {
370 if (lwFrame != null) {
371 swNodeIOP.disposeFrame(lwFrame);
372 lwFrame = null;
373 }
374 if (content != null) {
375 lwFrame = swNodeIOP.createLightweightFrame();
376
377 SwingNodeWindowFocusListener snfListener =
378 new SwingNodeWindowFocusListener(this);
379 swNodeIOP.addWindowFocusListener(lwFrame, snfListener);
380
381 if (getScene() != null) {
382 Window window = getScene().getWindow();
383 if (window != null) {
384 swNodeIOP.notifyDisplayChanged(lwFrame, window.getRenderScaleX(),
385 window.getRenderScaleY());
386 }
387 }
388 swNodeIOP.setContent(lwFrame, swNodeIOP.createSwingNodeContent(content, this));
389 swNodeIOP.setVisible(lwFrame, true);
390
391 Disposer.addRecord(this, swNodeIOP.createSwingNodeDisposer(lwFrame));
392
393 if (getScene() != null) {
394 notifyNativeHandle(getScene().getWindow());
395 }
396
397 SwingNodeHelper.runOnFxThread(() -> {
398 locateLwFrame();// initialize location
399
400 if (focusedProperty().get()) {
401 activateLwFrame(true);
402 }
403 });
404 }
405 }
406
407 private List<Runnable> peerRequests = new ArrayList<>();
408
409 /*
410 * Called on EDT
411 */
412 void setImageBuffer(final int[] data,
413 final int x, final int y,
414 final int w, final int h,
415 final int linestride,
416 final double scaleX,
417 final double scaleY)
418 {
419 Runnable r = () -> peer.setImageBuffer(IntBuffer.wrap(data), x, y, w, h,
420 w, h, linestride, scaleX, scaleY);
421 SwingNodeHelper.runOnFxThread(() -> {
422 if (peer != null) {
423 r.run();
424 } else {
425 peerRequests.clear();
426 peerRequests.add(r);
427 }
428 });
429 }
430
431 /*
432 * Called on EDT
433 */
434 void setImageBounds(final int x, final int y, final int w, final int h) {
435 Runnable r = () -> peer.setImageBounds(x, y, w, h, w, h);
436 SwingNodeHelper.runOnFxThread(() -> {
437 if (peer != null) {
438 r.run();
439 } else {
440 peerRequests.add(r);
441 }
442 });
443 }
444
445 /*
446 * Called on EDT
447 */
448 void repaintDirtyRegion(final int dirtyX, final int dirtyY, final int dirtyWidth, final int dirtyHeight) {
449 Runnable r = () -> {
450 peer.repaintDirtyRegion(dirtyX, dirtyY, dirtyWidth, dirtyHeight);
451 NodeHelper.markDirty(this, DirtyBits.NODE_CONTENTS);
452 };
453 SwingNodeHelper.runOnFxThread(() -> {
454 if (peer != null) {
455 r.run();
456 } else {
457 peerRequests.add(r);
458 }
459 });
460 }
461
462 @Override public boolean isResizable() {
463 return true;
464 }
465
466 /**
467 * Invoked by the {@code SwingNode}'s parent during layout to set the {@code SwingNode}'s
468 * width and height. <b>Applications should not invoke this method directly</b>.
469 * If an application needs to directly set the size of the {@code SwingNode}, it should
470 * set the Swing component's minimum/preferred/maximum size constraints which will
471 * be propagated correspondingly to the {@code SwingNode} and it's parent will honor those
472 * settings during layout.
473 *
474 * @param width the target layout bounds width
475 * @param height the target layout bounds height
476 */
477 @Override public void resize(final double width, final double height) {
478 super.resize(width, height);
479 if (width != this.fxWidth || height != this.fxHeight) {
480 this.fxWidth = width;
481 this.fxHeight = height;
482 NodeHelper.geomChanged(this);
483 NodeHelper.markDirty(this, DirtyBits.NODE_GEOMETRY);
484 SwingNodeHelper.runOnEDT(() -> {
485 if (lwFrame != null) {
486 locateLwFrame();
487 }
488 });
489 }
490 }
491
492 /**
493 * Returns the {@code SwingNode}'s preferred width for use in layout calculations.
494 * This value corresponds to the preferred width of the Swing component.
495 *
496 * @return the preferred width that the node should be resized to during layout
497 */
498 @Override
499 public double prefWidth(double height) {
500 return swingPrefWidth;
501 }
502
503 /**
504 * Returns the {@code SwingNode}'s preferred height for use in layout calculations.
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 swNodeIOP.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) {
662 NodeHelper.treeVisibleProperty(this).addListener((observable, oldValue, newValue) -> {
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 private void locateLwFrame() {
683 if (getScene() == null
684 || lwFrame == null
685 || getScene().getWindow() == null
686 || !getScene().getWindow().isShowing()) {
687 // Not initialized yet. Skip the update to set the real values later
688 return;
689 }
690 Window w = getScene().getWindow();
691 double renderScaleX = w.getRenderScaleX();
692 double renderScaleY = w.getRenderScaleY();
693 final Point2D loc = localToScene(0, 0);
694 final int windowX = (int) (w.getX());
695 final int windowY = (int) (w.getY());
696 final int windowW = (int) (w.getWidth());
697 final int windowH = (int) (w.getHeight());
698 final int frameX = (int) Math.round(w.getX() + getScene().getX() + loc.getX());
699 final int frameY = (int) Math.round(w.getY() + getScene().getY() + loc.getY());
700 final int frameW = (int) (fxWidth);
701 final int frameH = (int) (fxHeight);
702
703 SwingNodeHelper.runOnEDT(() -> {
704 if (lwFrame != null) {
705 swNodeIOP.notifyDisplayChanged(lwFrame, renderScaleX, renderScaleY);
706 swNodeIOP.setBounds(lwFrame, frameX, frameY, frameW, frameH);
707 swNodeIOP.setHostBounds(lwFrame, windowX, windowY, windowW, windowH);
708 }
709 });
710 }
711
712 private void activateLwFrame(final boolean activate) {
713 if (lwFrame == null) {
714 return;
715 }
716 if (PlatformUtil.isLinux()) {
717 // Workaround to block FocusOut/FocusIn notifications from Unity
718 // focus grabbing upon Alt press
719 if (deactivate == null || !deactivate.isRunning()) {
720 if (!activate) {
721 deactivate = new Timer(50, (e) -> {
722 {
723 if (lwFrame != null) {
724 swNodeIOP.emulateActivation(lwFrame, false);
725 }
726 }
727 });
728 deactivate.start();
729 return;
730 }
731 } else {
732 deactivate.stop();
733 }
734 }
735
736 SwingNodeHelper.runOnEDT(() -> {
737 if (lwFrame != null) {
738 swNodeIOP.emulateActivation(lwFrame, activate);
739 }
740 });
741 }
742
743 private void disposeLwFrame() {
744 if (lwFrame == null) {
745 return;
746 }
747 SwingNodeHelper.runOnEDT(() -> {
748 if (lwFrame != null) {
749 swNodeIOP.disposeFrame(lwFrame);
750 lwFrame = null;
751 }
752 });
753 }
754
755 private void setLwFrameVisible(final boolean visible) {
756 if (lwFrame == null) {
757 return;
758 }
759 SwingNodeHelper.runOnEDT(() -> {
760 if (lwFrame != null) {
761 swNodeIOP.setVisible(lwFrame, visible);
762 }
763 });
764 }
765
766 private void setLwFrameScale(final double scaleX, final double scaleY) {
767 if (lwFrame == null) {
768 return;
769 }
770 SwingNodeHelper.runOnEDT(() -> {
771 if (lwFrame != null) {
772 swNodeIOP.notifyDisplayChanged(lwFrame, scaleX, scaleY);
773 }
774 });
775 }
776
777 /*
778 * Note: This method MUST only be called via its accessor method.
779 */
780 private BaseBounds doComputeGeomBounds(BaseBounds bounds, BaseTransform tx) {
781 bounds.deriveWithNewBounds(0, 0, 0, (float)fxWidth, (float)fxHeight, 0);
782 tx.transform(bounds, bounds);
783 return bounds;
784 }
785
786
787 private static class SwingNodeWindowFocusListener implements WindowFocusListener {
788 private WeakReference<SwingNode> swingNodeRef;
789
790 SwingNodeWindowFocusListener(SwingNode swingNode) {
791 this.swingNodeRef = new WeakReference<SwingNode>(swingNode);
792 }
793
794 @Override
795 public void windowGainedFocus(WindowEvent e) {
796 SwingNodeHelper.runOnFxThread(() -> {
797 SwingNode swingNode = swingNodeRef.get();
798 if (swingNode != null) {
799 swingNode.requestFocus();
800 }
801 });
802 }
803
804 @Override
805 public void windowLostFocus(WindowEvent e) {
806 SwingNodeHelper.runOnFxThread(() -> {
807 SwingNode swingNode = swingNodeRef.get();
808 if (swingNode != null) {
809 swingNode.ungrabFocus(true);
810 }
811 });
812 }
813 }
814
815 private void ungrabFocus(boolean postUngrabEvent) {
816 // On X11 grab is limited to a single XDisplay connection,
817 // so we can't delegate it to another GUI toolkit.
818 if (PlatformUtil.isLinux()) return;
819
820 if (grabbed &&
821 getScene() != null &&
822 getScene().getWindow() != null &&
823 WindowHelper.getPeer(getScene().getWindow()) != null)
824 {
825 skipBackwardUnrgabNotification = !postUngrabEvent;
826 WindowHelper.getPeer(getScene().getWindow()).ungrabFocus();
827 skipBackwardUnrgabNotification = false;
828 grabbed = false;
829 }
830 }
831
832 private class PostEventAction implements PrivilegedAction<Void> {
833 private AWTEvent event;
834 PostEventAction(AWTEvent event) {
835 this.event = event;
836 }
837 @Override
838 public Void run() {
839 EventQueue eq = Toolkit.getDefaultToolkit().getSystemEventQueue();
840 eq.postEvent(event);
841 return null;
842 }
843 }
844
845 private class SwingMouseEventHandler implements EventHandler<MouseEvent> {
846 private final Set<MouseButton> mouseClickedAllowed = new HashSet<>();
847
848 @Override
849 public void handle(MouseEvent event) {
850 Object frame = swNodeIOP.getLightweightFrame();
851 if (frame == null) {
852 return;
853 }
854 int swingID = SwingEvents.fxMouseEventTypeToMouseID(event);
855 if (swingID < 0) {
856 return;
857 }
858
859 // Prevent ancestors of the SwingNode from stealing the focus
860 event.consume();
861
862 final EventType<?> type = event.getEventType();
863 if (type == MouseEvent.MOUSE_PRESSED) {
864 mouseClickedAllowed.add(event.getButton());
865 } else if (type == MouseEvent.MOUSE_RELEASED) {
866 // RELEASED comes before CLICKED, so we don't remove the button from the set
867 //mouseClickedAllowed.remove(event.getButton());
868 } else if (type == MouseEvent.MOUSE_DRAGGED) {
869 // This is what AWT/Swing do
870 mouseClickedAllowed.clear();
871 } else if (type == MouseEvent.MOUSE_CLICKED) {
872 if (event.getClickCount() == 1 && !mouseClickedAllowed.contains(event.getButton())) {
873 // RT-34610: In FX, CLICKED events are generated even after dragging the mouse pointer
874 // Note that this is only relevant for single clicks. Double clicks use a smudge factor.
875 return;
876 }
877 mouseClickedAllowed.remove(event.getButton());
878 }
879 int swingModifiers = SwingEvents.fxMouseModsToMouseMods(event);
880 boolean swingPopupTrigger = event.isPopupTrigger();
881 int swingButton = SwingEvents.fxMouseButtonToMouseButton(event);
882 long swingWhen = System.currentTimeMillis();
883 int relX = (int) Math.round(event.getX());
884 int relY = (int) Math.round(event.getY());
885 int absX = (int) Math.round(event.getScreenX());
886 int absY = (int) Math.round(event.getScreenY());
887 java.awt.event.MouseEvent mouseEvent =
888 swNodeIOP.createMouseEvent(
889 frame, swingID, swingWhen, swingModifiers,
890 relX, relY, absX, absY,
891 event.getClickCount(), swingPopupTrigger, swingButton);
892 AccessController.doPrivileged(new PostEventAction(mouseEvent));
893 }
894 }
895
896 private class SwingScrollEventHandler implements EventHandler<ScrollEvent> {
897 @Override
898 public void handle(ScrollEvent event) {
899 Object frame = swNodeIOP.getLightweightFrame();
900 if (frame == null) {
901 return;
902 }
903
904 int swingModifiers = SwingEvents.fxScrollModsToMouseWheelMods(event);
905 final boolean isShift = (swingModifiers & InputEvent.SHIFT_DOWN_MASK) != 0;
906
907 // Vertical scroll.
908 if (!isShift && event.getDeltaY() != 0.0) {
909 sendMouseWheelEvent(frame, event.getX(), event.getY(),
910 swingModifiers, event.getDeltaY() / event.getMultiplierY());
911 }
912 // Horizontal scroll or shirt+vertical scroll.
913 final double delta = isShift && event.getDeltaY() != 0.0
914 ? event.getDeltaY() / event.getMultiplierY()
915 : event.getDeltaX() / event.getMultiplierX();
916 if (delta != 0.0) {
917 swingModifiers |= InputEvent.SHIFT_DOWN_MASK;
918 sendMouseWheelEvent(frame, event.getX(), event.getY(),
919 swingModifiers, delta);
920 }
921 }
922
923 private void sendMouseWheelEvent(Object source, double fxX, double fxY, int swingModifiers, double delta) {
924 int wheelRotation = (int) delta;
925 int signum = (int) Math.signum(delta);
926 if (signum * delta < 1) {
927 wheelRotation = signum;
928 }
929 int x = (int) Math.round(fxX);
930 int y = (int) Math.round(fxY);
931 MouseWheelEvent mouseWheelEvent =
932 swNodeIOP.createMouseWheelEvent(source, swingModifiers, x, y, -wheelRotation);
933 AccessController.doPrivileged(new PostEventAction(mouseWheelEvent));
934 }
935 }
936
937 private class SwingKeyEventHandler implements EventHandler<KeyEvent> {
938 @Override
939 public void handle(KeyEvent event) {
940 Object frame = swNodeIOP.getLightweightFrame();
941 if (frame == null) {
942 return;
943 }
944 if (event.getCharacter().isEmpty()) {
945 // TODO: should we post an "empty" character?
946 return;
947 }
948 // Don't let Arrows, Tab, Shift+Tab traverse focus out.
949 if (event.getCode() == KeyCode.LEFT ||
950 event.getCode() == KeyCode.RIGHT ||
951 event.getCode() == KeyCode.UP ||
952 event.getCode() == KeyCode.DOWN ||
953 event.getCode() == KeyCode.TAB)
954 {
955 event.consume();
956 }
957
958 int swingID = SwingEvents.fxKeyEventTypeToKeyID(event);
959 if (swingID < 0) {
960 return;
961 }
962 int swingModifiers = SwingEvents.fxKeyModsToKeyMods(event);
963 int swingKeyCode = event.getCode().getCode();
964 char swingChar = event.getCharacter().charAt(0);
965
966 // A workaround. Some swing L&F's process mnemonics on KEY_PRESSED,
967 // for which swing provides a keychar. Extracting it from the text.
968 if (event.getEventType() == javafx.scene.input.KeyEvent.KEY_PRESSED) {
969 String text = event.getText();
970 if (text.length() == 1) {
971 swingChar = text.charAt(0);
972 }
973 }
974 long swingWhen = System.currentTimeMillis();
975 java.awt.event.KeyEvent keyEvent = swNodeIOP.createKeyEvent(frame,
976 swingID, swingWhen, swingModifiers, swingKeyCode,
977 swingChar);
978 AccessController.doPrivileged(new PostEventAction(keyEvent));
979 }
980 }
981 }
982
983
|