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