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