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