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