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