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