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