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 } 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>. 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) { 583 addWindowListeners(newValue); 584 } 585 }; 586 587 private void removeSceneListeners(Scene scene) { 588 Window window = scene.getWindow(); 589 if (window != null) { 592 scene.windowProperty().removeListener(sceneWindowListener); 593 } 594 595 private void addSceneListeners(final Scene scene) { 596 Window window = scene.getWindow(); 597 if (window != null) { 598 addWindowListeners(window); 599 notifyNativeHandle(window); 600 } 601 scene.windowProperty().addListener(sceneWindowListener); 602 } 603 604 private void addWindowListeners(final Window window) { 605 window.xProperty().addListener(locationListener); 606 window.yProperty().addListener(locationListener); 607 window.widthProperty().addListener(locationListener); 608 window.heightProperty().addListener(locationListener); 609 window.renderScaleXProperty().addListener(locationListener); 610 window.addEventHandler(FocusUngrabEvent.FOCUS_UNGRAB, ungrabHandler); 611 window.showingProperty().addListener(windowVisibleListener); 612 setLwFrameScale(window.getRenderScaleX(), window.getRenderScaleY()); 613 } 614 615 private void removeWindowListeners(final Window window) { 616 window.xProperty().removeListener(locationListener); 617 window.yProperty().removeListener(locationListener); 618 window.widthProperty().removeListener(locationListener); 619 window.heightProperty().removeListener(locationListener); 620 window.renderScaleXProperty().removeListener(locationListener); 621 window.removeEventHandler(FocusUngrabEvent.FOCUS_UNGRAB, ungrabHandler); 622 window.showingProperty().removeListener(windowVisibleListener); 623 } 624 625 /* 626 * Note: This method MUST only be called via its accessor method. 627 */ 628 private NGNode doCreatePeer() { 629 peer = new NGExternalNode(); 630 peer.setLock(paintLock); 631 for (Runnable request : peerRequests) { 632 request.run(); 633 } 634 peerRequests = null; 635 636 if (getScene() != null) { 637 addSceneListeners(getScene()); 638 } 639 640 sceneProperty().addListener((observable, oldValue, newValue) -> { 641 if (oldValue != null) { 642 // Removed from scene 643 removeSceneListeners(oldValue); 644 disposeLwFrame(); 645 } 646 if (newValue != null) { 647 // Added to another scene 648 if (content != null && lwFrame == null) { 649 setContent(content); 650 } 651 addSceneListeners(newValue); 652 } 653 }); 654 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 static SwingNodeInterop swiop; 134 135 static { 136 InteropFactory instance = null; 137 try { 138 instance = InteropFactory.getInstance(); 139 } catch (Exception e) { 140 throw new ExceptionInInitializerError(e); 141 } 142 swiop = instance.createSwingNodeImpl(); 143 144 AccessController.doPrivileged(new PrivilegedAction<Object>() { 145 public Object run() { 146 isThreadMerged = Boolean.valueOf( 147 System.getProperty("javafx.embed.singleThread")); 148 return null; 149 } 150 }); 151 152 153 // This is used by classes in different packages to get access to 154 // private and package private methods. 155 SwingNodeHelper.setSwingNodeAccessor(new SwingNodeHelper.SwingNodeAccessor() { 156 @Override 157 public NGNode doCreatePeer(Node node) { 158 return ((SwingNode) node).doCreatePeer(); 159 } 160 161 @Override 162 public void doUpdatePeer(Node node) { 163 ((SwingNode) node).doUpdatePeer(); 164 } 165 166 @Override 167 public BaseBounds doComputeGeomBounds(Node node, 168 BaseBounds bounds, BaseTransform tx) { 169 return ((SwingNode) node).doComputeGeomBounds(bounds, tx); 170 } 171 172 @Override 173 public boolean doComputeContains(Node node, double localX, double localY) { 174 return ((SwingNode) node).doComputeContains(localX, localY); 175 } 176 }); 177 } 178 179 private double fxWidth; 180 private double fxHeight; 181 182 public int swingPrefWidth; 183 public int swingPrefHeight; 184 public int swingMaxWidth; 185 public int swingMaxHeight; 186 public int swingMinWidth; 187 public int swingMinHeight; 188 189 private volatile JComponent content; 190 private volatile Object lwFrame; 191 public final Object getLightweightFrame() { return lwFrame; } 192 193 private NGExternalNode peer; 194 195 public final ReentrantLock paintLock = new ReentrantLock(); 196 197 private boolean skipBackwardUnrgabNotification; 198 public boolean grabbed; // lwframe initiated grab 199 200 { 201 // To initialize the class helper at the begining each constructor of this class 202 SwingNodeHelper.initHelper(this); 203 } 204 205 /** 206 * Constructs a new instance of {@code SwingNode}. 207 */ 208 public SwingNode() { 209 setFocusTraversable(true); 210 setEventHandler(MouseEvent.ANY, new SwingMouseEventHandler()); 211 setEventHandler(KeyEvent.ANY, new SwingKeyEventHandler()); 212 setEventHandler(ScrollEvent.SCROLL, new SwingScrollEventHandler()); 213 214 focusedProperty().addListener((observable, oldValue, newValue) -> { 215 swiop.activateLwFrame(lwFrame, newValue); 216 }); 217 218 //Workaround for RT-34170 219 javafx.scene.text.Font.getFamilies(); 220 } 221 222 223 private EventHandler windowHiddenHandler = (Event event) -> { 224 if (lwFrame != null && event.getTarget() instanceof Window) { 225 final Window w = (Window) event.getTarget(); 226 TKStage tk = WindowHelper.getPeer(w); 227 if (tk != null) { 228 if (isThreadMerged) { 229 swiop.overrideNativeWindowHandle(0L, null); 230 } else { 231 // Postpone actual window closing to ensure that 232 // a native window handler is valid on a Swing side 233 tk.postponeClose(); 234 SwingFXUtils.runOnEDT(() -> { 235 swiop.overrideNativeWindowHandle(0L, 236 (Runnable) () -> SwingFXUtils.runOnFxThread( 237 () -> tk.closePostponed())); 238 }); 239 } 240 } 241 } 242 243 }; 244 245 private Window hWindow = null; 246 private void notifyNativeHandle(Window window) { 247 if (hWindow != window) { 248 if (hWindow != null) { 249 hWindow.removeEventHandler(WINDOW_HIDDEN, windowHiddenHandler); 250 } 251 if (window != null) { 252 window.addEventHandler(WINDOW_HIDDEN, windowHiddenHandler); 253 } 254 hWindow = window; 255 } 256 257 if (lwFrame != null) { 258 long rawHandle = 0L; 259 if (window != null) { 260 TKStage tkStage = WindowHelper.getPeer(window); 261 if (tkStage != null) { 262 rawHandle = tkStage.getRawHandle(); 263 } 264 } 265 swiop.overrideNativeWindowHandle(rawHandle, null); 266 } 267 } 268 269 /** 270 * Attaches a {@code JComponent} instance to display in this {@code SwingNode}. 271 * <p> 272 * The method can be called either on the JavaFX Application thread or the Event Dispatch thread. 273 * Note however, that access to a Swing component must occur from the Event Dispatch thread 274 * according to the Swing threading restrictions. 275 * 276 * @param content a Swing component to display in this {@code SwingNode} 277 * 278 * @see java.awt.EventQueue#isDispatchThread() 279 * @see javafx.application.Platform#isFxApplicationThread() 280 */ 281 public void setContent(final JComponent content) { 282 this.content = content; 283 284 SwingFXUtils.runOnEDT(() -> setContentImpl(content)); 285 } 323 return method != null; 324 } 325 326 public boolean isIntegerApi() { 327 return isIntegerAPI; 328 } 329 330 public Object invoke(T object, Object... args) { 331 if (method != null) { 332 try { 333 return method.invoke(object, args); 334 } catch (Throwable ex) { 335 throw new RuntimeException("Error when calling " + object.getClass().getName() + "." + method.getName() + "().", ex); 336 } 337 } else { 338 return null; 339 } 340 } 341 } 342 343 /* 344 * Called on EDT 345 */ 346 private void setContentImpl(JComponent content) { 347 if (lwFrame != null) { 348 swiop.disposeLwFrame(); 349 lwFrame = null; 350 } 351 if (content != null) { 352 lwFrame = swiop.createLightweightFrame(); 353 354 SwingNodeWindowFocusListener snfListener = 355 new SwingNodeWindowFocusListener(this); 356 swiop.addWindowFocusListener(lwFrame, snfListener); 357 358 if (getScene() != null) { 359 Window window = getScene().getWindow(); 360 if (window != null) { 361 swiop.notifyDisplayChanged(lwFrame, window); 362 } 363 } 364 swiop.setContent(lwFrame, swiop.createSwingNodeContent(content, this)); 365 swiop.setVisible(lwFrame, true); 366 367 Disposer.addRecord(this, swiop.createSwingNodeDisposer(lwFrame)); 368 369 if (getScene() != null) { 370 notifyNativeHandle(getScene().getWindow()); 371 } 372 373 SwingFXUtils.runOnFxThread(() -> { 374 locateLwFrame();// initialize location 375 376 if (focusedProperty().get()) { 377 swiop.activateLwFrame(lwFrame, true); 378 } 379 }); 380 } 381 } 382 383 private List<Runnable> peerRequests = new ArrayList<>(); 384 385 /* 386 * Called on EDT 387 */ 388 public void setImageBuffer(final int[] data, 389 final int x, final int y, 390 final int w, final int h, 391 final int linestride, 392 final double scaleX, 393 final double scaleY) 394 { 395 Runnable r = () -> peer.setImageBuffer(IntBuffer.wrap(data), x, y, w, h, 396 w, h, linestride, scaleX, scaleY); 397 SwingFXUtils.runOnFxThread(() -> { 398 if (peer != null) { 399 r.run(); 400 } else { 401 peerRequests.clear(); 402 peerRequests.add(r); 403 } 404 }); 405 } 406 407 /* 408 * Called on EDT 409 */ 410 public void setImageBounds(final int x, final int y, final int w, final int h) { 411 Runnable r = () -> peer.setImageBounds(x, y, w, h, w, h); 412 SwingFXUtils.runOnFxThread(() -> { 413 if (peer != null) { 414 r.run(); 415 } else { 416 peerRequests.add(r); 417 } 418 }); 419 } 420 421 /* 422 * Called on EDT 423 */ 424 public void repaintDirtyRegion(final int dirtyX, final int dirtyY, final int dirtyWidth, final int dirtyHeight) { 425 Runnable r = () -> { 426 peer.repaintDirtyRegion(dirtyX, dirtyY, dirtyWidth, dirtyHeight); 427 NodeHelper.markDirty(this, DirtyBits.NODE_CONTENTS); 428 }; 429 SwingFXUtils.runOnFxThread(() -> { 430 if (peer != null) { 431 r.run(); 432 } else { 433 peerRequests.add(r); 434 } 435 }); 436 } 437 438 @Override public boolean isResizable() { 439 return true; 440 } 441 442 /** 443 * Invoked by the {@code SwingNode}'s parent during layout to set the {@code SwingNode}'s 444 * width and height. <b>Applications should not invoke this method directly</b>. 524 * @return the minimum height that the node should be resized to during layout 525 */ 526 @Override public double minHeight(double width) { 527 return swingMinHeight; 528 } 529 530 /* 531 * Note: This method MUST only be called via its accessor method. 532 */ 533 private boolean doComputeContains(double localX, double localY) { 534 return true; 535 } 536 537 private final InvalidationListener locationListener = observable -> { 538 locateLwFrame(); 539 }; 540 541 private final EventHandler<FocusUngrabEvent> ungrabHandler = event -> { 542 if (!skipBackwardUnrgabNotification) { 543 if (lwFrame != null) { 544 AccessController.doPrivileged(new PostEventAction( 545 swiop.createUngrabEvent(lwFrame))); 546 } 547 } 548 }; 549 550 private final ChangeListener<Boolean> windowVisibleListener = (observable, oldValue, newValue) -> { 551 if (!newValue) { 552 swiop.disposeLwFrame(); 553 } else { 554 setContent(content); 555 } 556 }; 557 558 private final ChangeListener<Window> sceneWindowListener = (observable, oldValue, newValue) -> { 559 if (oldValue != null) { 560 removeWindowListeners(oldValue); 561 } 562 563 notifyNativeHandle(newValue); 564 565 if (newValue != null) { 566 addWindowListeners(newValue); 567 } 568 }; 569 570 private void removeSceneListeners(Scene scene) { 571 Window window = scene.getWindow(); 572 if (window != null) { 575 scene.windowProperty().removeListener(sceneWindowListener); 576 } 577 578 private void addSceneListeners(final Scene scene) { 579 Window window = scene.getWindow(); 580 if (window != null) { 581 addWindowListeners(window); 582 notifyNativeHandle(window); 583 } 584 scene.windowProperty().addListener(sceneWindowListener); 585 } 586 587 private void addWindowListeners(final Window window) { 588 window.xProperty().addListener(locationListener); 589 window.yProperty().addListener(locationListener); 590 window.widthProperty().addListener(locationListener); 591 window.heightProperty().addListener(locationListener); 592 window.renderScaleXProperty().addListener(locationListener); 593 window.addEventHandler(FocusUngrabEvent.FOCUS_UNGRAB, ungrabHandler); 594 window.showingProperty().addListener(windowVisibleListener); 595 swiop.setLwFrameScale(window.getRenderScaleX(), window.getRenderScaleY()); 596 } 597 598 private void removeWindowListeners(final Window window) { 599 window.xProperty().removeListener(locationListener); 600 window.yProperty().removeListener(locationListener); 601 window.widthProperty().removeListener(locationListener); 602 window.heightProperty().removeListener(locationListener); 603 window.renderScaleXProperty().removeListener(locationListener); 604 window.removeEventHandler(FocusUngrabEvent.FOCUS_UNGRAB, ungrabHandler); 605 window.showingProperty().removeListener(windowVisibleListener); 606 } 607 608 /* 609 * Note: This method MUST only be called via its accessor method. 610 */ 611 private NGNode doCreatePeer() { 612 peer = new NGExternalNode(); 613 peer.setLock(paintLock); 614 for (Runnable request : peerRequests) { 615 request.run(); 616 } 617 peerRequests = null; 618 619 if (getScene() != null) { 620 addSceneListeners(getScene()); 621 } 622 623 sceneProperty().addListener((observable, oldValue, newValue) -> { 624 if (oldValue != null) { 625 // Removed from scene 626 removeSceneListeners(oldValue); 627 swiop.disposeLwFrame(); 628 } 629 if (newValue != null) { 630 // Added to another scene 631 if (content != null && lwFrame == null) { 632 setContent(content); 633 } 634 addSceneListeners(newValue); 635 } 636 }); 637 638 NodeHelper.treeVisibleProperty(this).addListener((observable, oldValue, newValue) -> { 639 swiop.setLwFrameVisible(newValue); 640 }); 641 642 return peer; 643 } 644 645 /* 646 * Note: This method MUST only be called via its accessor method. 647 */ 648 private void doUpdatePeer() { 649 if (NodeHelper.isDirty(this, DirtyBits.NODE_VISIBLE) 650 || NodeHelper.isDirty(this, DirtyBits.NODE_BOUNDS)) { 651 locateLwFrame(); // initialize location 652 } 653 if (NodeHelper.isDirty(this, DirtyBits.NODE_CONTENTS)) { 654 peer.markContentDirty(); 655 } 656 } 657 658 private void locateLwFrame() { 659 if (getScene() == null 660 || lwFrame == null 661 || getScene().getWindow() == null 662 || !getScene().getWindow().isShowing()) { 663 // Not initialized yet. Skip the update to set the real values later 664 return; 665 } 666 Window w = getScene().getWindow(); 667 double renderScaleX = w.getRenderScaleX(); 668 double renderScaleY = w.getRenderScaleY(); 669 final Point2D loc = localToScene(0, 0); 670 final int windowX = (int) (w.getX()); 671 final int windowY = (int) (w.getY()); 672 final int windowW = (int) (w.getWidth()); 673 final int windowH = (int) (w.getHeight()); 674 final int frameX = (int) Math.round(w.getX() + getScene().getX() + loc.getX()); 675 final int frameY = (int) Math.round(w.getY() + getScene().getY() + loc.getY()); 676 final int frameW = (int) (fxWidth); 677 final int frameH = (int) (fxHeight); 678 679 SwingFXUtils.runOnEDT(() -> { 680 if (lwFrame != null) { 681 swiop.notifyDisplayChanged(lwFrame, w); 682 swiop.setBounds(lwFrame, frameX, frameY, frameW, frameH); 683 swiop.setHostBounds(lwFrame, w); 684 } 685 }); 686 } 687 688 /* 689 * Note: This method MUST only be called via its accessor method. 690 */ 691 private BaseBounds doComputeGeomBounds(BaseBounds bounds, BaseTransform tx) { 692 bounds.deriveWithNewBounds(0, 0, 0, (float)fxWidth, (float)fxHeight, 0); 693 tx.transform(bounds, bounds); 694 return bounds; 695 } 696 697 698 private static class SwingNodeWindowFocusListener implements WindowFocusListener { 699 private WeakReference<SwingNode> swingNodeRef; 700 701 SwingNodeWindowFocusListener(SwingNode swingNode) { 702 this.swingNodeRef = new WeakReference<SwingNode>(swingNode); 703 } 704 705 @Override 706 public void windowGainedFocus(WindowEvent e) { 707 SwingFXUtils.runOnFxThread(() -> { 708 SwingNode swingNode = swingNodeRef.get(); 709 if (swingNode != null) { 710 swingNode.requestFocus(); 711 } 712 }); 713 } 714 715 @Override 716 public void windowLostFocus(WindowEvent e) { 717 SwingFXUtils.runOnFxThread(() -> { 718 SwingNode swingNode = swingNodeRef.get(); 719 if (swingNode != null) { 720 swingNode.ungrabFocus(true); 721 } 722 }); 723 } 724 } 725 726 public void ungrabFocus(boolean postUngrabEvent) { 727 // On X11 grab is limited to a single XDisplay connection, 728 // so we can't delegate it to another GUI toolkit. 729 if (PlatformUtil.isLinux()) return; 730 731 if (grabbed && 732 getScene() != null && 733 getScene().getWindow() != null && 734 WindowHelper.getPeer(getScene().getWindow()) != null) 735 { 736 skipBackwardUnrgabNotification = !postUngrabEvent; 737 WindowHelper.getPeer(getScene().getWindow()).ungrabFocus(); 738 skipBackwardUnrgabNotification = false; 739 grabbed = false; 740 } 741 } 742 743 private class PostEventAction implements PrivilegedAction<Void> { 744 private AWTEvent event; 745 PostEventAction(AWTEvent event) { 746 this.event = event; 747 } 748 @Override 749 public Void run() { 750 EventQueue eq = Toolkit.getDefaultToolkit().getSystemEventQueue(); 751 eq.postEvent(event); 752 return null; 753 } 754 } 755 756 private class SwingMouseEventHandler implements EventHandler<MouseEvent> { 757 private final Set<MouseButton> mouseClickedAllowed = new HashSet<>(); 758 759 @Override 760 public void handle(MouseEvent event) { 761 Object frame = swiop.getLightweightFrame(); 762 if (frame == null) { 763 return; 764 } 765 int swingID = SwingEvents.fxMouseEventTypeToMouseID(event); 766 if (swingID < 0) { 767 return; 768 } 769 770 // Prevent ancestors of the SwingNode from stealing the focus 771 event.consume(); 772 773 final EventType<?> type = event.getEventType(); 774 if (type == MouseEvent.MOUSE_PRESSED) { 775 mouseClickedAllowed.add(event.getButton()); 776 } else if (type == MouseEvent.MOUSE_RELEASED) { 777 // RELEASED comes before CLICKED, so we don't remove the button from the set 778 //mouseClickedAllowed.remove(event.getButton()); 779 } else if (type == MouseEvent.MOUSE_DRAGGED) { 780 // This is what AWT/Swing do 781 mouseClickedAllowed.clear(); 782 } else if (type == MouseEvent.MOUSE_CLICKED) { 783 if (event.getClickCount() == 1 && !mouseClickedAllowed.contains(event.getButton())) { 784 // RT-34610: In FX, CLICKED events are generated even after dragging the mouse pointer 785 // Note that this is only relevant for single clicks. Double clicks use a smudge factor. 786 return; 787 } 788 mouseClickedAllowed.remove(event.getButton()); 789 } 790 int swingModifiers = SwingEvents.fxMouseModsToMouseMods(event); 791 boolean swingPopupTrigger = event.isPopupTrigger(); 792 int swingButton = SwingEvents.fxMouseButtonToMouseButton(event); 793 long swingWhen = System.currentTimeMillis(); 794 int relX = (int) Math.round(event.getX()); 795 int relY = (int) Math.round(event.getY()); 796 int absX = (int) Math.round(event.getScreenX()); 797 int absY = (int) Math.round(event.getScreenY()); 798 java.awt.event.MouseEvent mouseEvent = 799 swiop.createMouseEvent( 800 frame, swingID, swingWhen, swingModifiers, 801 relX, relY, absX, absY, 802 event.getClickCount(), swingPopupTrigger, swingButton); 803 AccessController.doPrivileged(new PostEventAction(mouseEvent)); 804 } 805 } 806 807 private class SwingScrollEventHandler implements EventHandler<ScrollEvent> { 808 @Override 809 public void handle(ScrollEvent event) { 810 Object frame = swiop.getLightweightFrame(); 811 if (frame == null) { 812 return; 813 } 814 815 int swingModifiers = SwingEvents.fxScrollModsToMouseWheelMods(event); 816 final boolean isShift = (swingModifiers & InputEvent.SHIFT_DOWN_MASK) != 0; 817 818 // Vertical scroll. 819 if (!isShift && event.getDeltaY() != 0.0) { 820 sendMouseWheelEvent(frame, event.getX(), event.getY(), 821 swingModifiers, event.getDeltaY() / event.getMultiplierY()); 822 } 823 // Horizontal scroll or shirt+vertical scroll. 824 final double delta = isShift && event.getDeltaY() != 0.0 825 ? event.getDeltaY() / event.getMultiplierY() 826 : event.getDeltaX() / event.getMultiplierX(); 827 if (delta != 0.0) { 828 swingModifiers |= InputEvent.SHIFT_DOWN_MASK; 829 sendMouseWheelEvent(frame, event.getX(), event.getY(), 830 swingModifiers, delta); 831 } 832 } 833 834 private void sendMouseWheelEvent(Object source, double fxX, double fxY, int swingModifiers, double delta) { 835 int wheelRotation = (int) delta; 836 int signum = (int) Math.signum(delta); 837 if (signum * delta < 1) { 838 wheelRotation = signum; 839 } 840 int x = (int) Math.round(fxX); 841 int y = (int) Math.round(fxY); 842 MouseWheelEvent mouseWheelEvent = 843 swiop.createMouseWheelEvent(source, swingModifiers, x, y, -wheelRotation); 844 AccessController.doPrivileged(new PostEventAction(mouseWheelEvent)); 845 } 846 } 847 848 private class SwingKeyEventHandler implements EventHandler<KeyEvent> { 849 @Override 850 public void handle(KeyEvent event) { 851 Object frame = swiop.getLightweightFrame(); 852 if (frame == null) { 853 return; 854 } 855 if (event.getCharacter().isEmpty()) { 856 // TODO: should we post an "empty" character? 857 return; 858 } 859 // Don't let Arrows, Tab, Shift+Tab traverse focus out. 860 if (event.getCode() == KeyCode.LEFT || 861 event.getCode() == KeyCode.RIGHT || 862 event.getCode() == KeyCode.UP || 863 event.getCode() == KeyCode.DOWN || 864 event.getCode() == KeyCode.TAB) 865 { 866 event.consume(); 867 } 868 869 int swingID = SwingEvents.fxKeyEventTypeToKeyID(event); 870 if (swingID < 0) { 871 return; 872 } 873 int swingModifiers = SwingEvents.fxKeyModsToKeyMods(event); 874 int swingKeyCode = event.getCode().getCode(); 875 char swingChar = event.getCharacter().charAt(0); 876 877 // A workaround. Some swing L&F's process mnemonics on KEY_PRESSED, 878 // for which swing provides a keychar. Extracting it from the text. 879 if (event.getEventType() == javafx.scene.input.KeyEvent.KEY_PRESSED) { 880 String text = event.getText(); 881 if (text.length() == 1) { 882 swingChar = text.charAt(0); 883 } 884 } 885 long swingWhen = System.currentTimeMillis(); 886 java.awt.event.KeyEvent keyEvent = swiop.createKeyEvent(frame, 887 swingID, swingWhen, swingModifiers, swingKeyCode, 888 swingChar); 889 AccessController.doPrivileged(new PostEventAction(keyEvent)); 890 } 891 } 892 } 893 |