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