1 /*
   2  * Copyright (c) 2010, 2018, 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 java.awt.AlphaComposite;
  29 import java.awt.Component;
  30 import java.awt.Cursor;
  31 import java.awt.Dimension;
  32 import java.awt.Graphics;
  33 import java.awt.Graphics2D;
  34 import java.awt.KeyboardFocusManager;
  35 import java.awt.Point;
  36 import java.awt.Window;
  37 import java.awt.Insets;
  38 import java.awt.EventQueue;
  39 import java.awt.SecondaryLoop;
  40 import java.awt.GraphicsEnvironment;
  41 import java.awt.event.AWTEventListener;
  42 import java.awt.event.ComponentEvent;
  43 import java.awt.event.FocusEvent;
  44 import java.awt.event.HierarchyEvent;
  45 import java.awt.event.InputEvent;
  46 import java.awt.event.InputMethodEvent;
  47 import java.awt.event.KeyEvent;
  48 import java.awt.event.MouseEvent;
  49 import java.awt.event.MouseWheelEvent;
  50 import java.awt.event.InvocationEvent;
  51 import java.awt.im.InputMethodRequests;
  52 import java.awt.image.BufferedImage;
  53 import java.awt.image.DataBufferInt;
  54 import java.nio.IntBuffer;
  55 import java.util.concurrent.atomic.AtomicInteger;
  56 import java.security.AccessController;
  57 import java.security.PrivilegedAction;
  58 import javax.swing.JComponent;
  59 import javax.swing.SwingUtilities;
  60 
  61 import javafx.application.Platform;
  62 import javafx.scene.Scene;
  63 
  64 import com.sun.javafx.application.PlatformImpl;
  65 import com.sun.javafx.cursor.CursorFrame;
  66 import com.sun.javafx.stage.EmbeddedWindow;
  67 import com.sun.javafx.tk.Toolkit;
  68 import com.sun.javafx.PlatformUtil;
  69 
  70 import com.sun.javafx.logging.PlatformLogger;
  71 import com.sun.javafx.embed.AbstractEvents;
  72 import com.sun.javafx.embed.EmbeddedSceneInterface;
  73 import com.sun.javafx.embed.EmbeddedStageInterface;
  74 import com.sun.javafx.embed.HostInterface;
  75 
  76 import com.sun.javafx.embed.swing.InteropFactory;
  77 import com.sun.javafx.embed.swing.SwingDnD;
  78 import com.sun.javafx.embed.swing.SwingEvents;
  79 import com.sun.javafx.embed.swing.SwingCursors;
  80 import com.sun.javafx.embed.swing.SwingNodeHelper;
  81 import com.sun.javafx.embed.swing.JFXPanelInterop;
  82 
  83 /**
  84 * {@code JFXPanel} is a component to embed JavaFX content into
  85  * Swing applications. The content to be displayed is specified
  86  * with the {@link #setScene} method that accepts an instance of
  87  * JavaFX {@code Scene}. After the scene is assigned, it gets
  88  * repainted automatically. All the input and focus events are
  89  * forwarded to the scene transparently to the developer.
  90  * <p>
  91  * There are some restrictions related to {@code JFXPanel}. As a
  92  * Swing component, it should only be accessed from the event
  93  * dispatch thread, except the {@link #setScene} method, which can
  94  * be called either on the event dispatch thread or on the JavaFX
  95  * application thread.
  96  * <p>
  97  * Here is a typical pattern how {@code JFXPanel} can used:
  98  * <pre>
  99  *     public class Test {
 100  *
 101  *         private static void initAndShowGUI() {
 102  *             // This method is invoked on Swing thread
 103  *             JFrame frame = new JFrame("FX");
 104  *             final JFXPanel fxPanel = new JFXPanel();
 105  *             frame.add(fxPanel);
 106  *             frame.setVisible(true);
 107  *
 108  *             Platform.runLater(new Runnable() {
 109  *                 @Override
 110  *                 public void run() {
 111  *                     initFX(fxPanel);
 112  *                 }
 113  *             });
 114  *         }
 115  *
 116  *         private static void initFX(JFXPanel fxPanel) {
 117  *             // This method is invoked on JavaFX thread
 118  *             Scene scene = createScene();
 119  *             fxPanel.setScene(scene);
 120  *         }
 121  *
 122  *         public static void main(String[] args) {
 123  *             SwingUtilities.invokeLater(new Runnable() {
 124  *                 @Override
 125  *                 public void run() {
 126  *                     initAndShowGUI();
 127  *                 }
 128  *             });
 129  *         }
 130  *     }
 131  * </pre>
 132  *
 133  * @since JavaFX 2.0
 134  */
 135 public class JFXPanel extends JComponent {
 136 
 137     private final static PlatformLogger log = PlatformLogger.getLogger(JFXPanel.class.getName());
 138 
 139     private static AtomicInteger instanceCount = new AtomicInteger(0);
 140     private static PlatformImpl.FinishListener finishListener;
 141 
 142     private transient HostContainer hostContainer;
 143 
 144     private transient volatile EmbeddedWindow stage;
 145     private transient volatile Scene scene;
 146 
 147     // Accessed on EDT only
 148     private transient SwingDnD dnd;
 149 
 150     private transient EmbeddedStageInterface stagePeer;
 151     private transient EmbeddedSceneInterface scenePeer;
 152 
 153     // The logical size of the FX content
 154     private int pWidth;
 155     private int pHeight;
 156 
 157     // The scale factor, used to translate b/w the logical (the FX content dimension)
 158     // and physical (the back buffer's dimension) coordinate spaces
 159     private double scaleFactorX = 1.0;
 160     private double scaleFactorY = 1.0;
 161 
 162     // Preferred size set from FX
 163     private volatile int pPreferredWidth = -1;
 164     private volatile int pPreferredHeight = -1;
 165 
 166     // Cached copy of this component's location on screen to avoid
 167     // calling getLocationOnScreen() under the tree lock on FX thread
 168     private volatile int screenX = 0;
 169     private volatile int screenY = 0;
 170 
 171     // Accessed on EDT only
 172     private BufferedImage pixelsIm;
 173 
 174     private volatile float opacity = 1.0f;
 175 
 176     // Indicates how many times setFxEnabled(false) has been called.
 177     // A value of 0 means the component is enabled.
 178     private AtomicInteger disableCount = new AtomicInteger(0);
 179 
 180     private boolean isCapturingMouse = false;
 181 
 182     private static boolean fxInitialized;
 183 
 184     private static InteropFactory iopFactoryInstance = null;
 185     private JFXPanelInterop jfxPanelIOP;
 186 
 187     static {
 188         try {
 189             iopFactoryInstance = InteropFactory.getInstance();
 190         } catch (Exception e) {
 191             throw new ExceptionInInitializerError(e);
 192         }
 193     }
 194 
 195     private synchronized void registerFinishListener() {
 196         if (instanceCount.getAndIncrement() > 0) {
 197             // Already registered
 198             return;
 199         }
 200         // Need to install a finish listener to catch calls to Platform.exit
 201         finishListener = new PlatformImpl.FinishListener() {
 202             @Override public void idle(boolean implicitExit) {
 203             }
 204             @Override public void exitCalled() {
 205             }
 206         };
 207         PlatformImpl.addListener(finishListener);
 208     }
 209 
 210     private synchronized void deregisterFinishListener() {
 211         if (instanceCount.decrementAndGet() > 0) {
 212             // Other JFXPanels still alive
 213             return;
 214         }
 215         PlatformImpl.removeListener(finishListener);
 216         finishListener = null;
 217     }
 218 
 219     // Initialize FX runtime when the JFXPanel instance is constructed
 220     private synchronized static void initFx() {
 221         // Note that calling PlatformImpl.startup more than once is OK
 222         if (fxInitialized) {
 223             return;
 224         }
 225         EventQueue eventQueue = AccessController.doPrivileged(
 226                                 (PrivilegedAction<EventQueue>) java.awt.Toolkit
 227                                 .getDefaultToolkit()::getSystemEventQueue);
 228         if (eventQueue.isDispatchThread()) {
 229             // We won't block EDT by FX initialization
 230             SecondaryLoop secondaryLoop = eventQueue.createSecondaryLoop();
 231             final Throwable[] th = {null};
 232             new Thread(() -> {
 233                 try {
 234                     PlatformImpl.startup(() -> {});
 235                 } catch (Throwable t) {
 236                     th[0] = t;
 237                 } finally {
 238                     secondaryLoop.exit();
 239                 }
 240             }).start();
 241             secondaryLoop.enter();
 242             if (th[0] != null) {
 243                 if (th[0] instanceof RuntimeException) {
 244                     throw (RuntimeException) th[0];
 245                 } else if (th[0] instanceof Error) {
 246                     throw (Error) th[0];
 247                 }
 248                 throw new RuntimeException("FX initialization failed", th[0]);
 249             }
 250         } else {
 251             PlatformImpl.startup(() -> {});
 252         }
 253         fxInitialized = true;
 254     }
 255 
 256     /**
 257      * Creates a new {@code JFXPanel} object.
 258      * <p>
 259      * <b>Implementation note</b>: when the first {@code JFXPanel} object
 260      * is created, it implicitly initializes the JavaFX runtime. This is the
 261      * preferred way to initialize JavaFX in Swing.
 262      */
 263     public JFXPanel() {
 264         super();
 265 
 266         jfxPanelIOP = iopFactoryInstance.createJFXPanelImpl();
 267         initFx();
 268 
 269         hostContainer = new HostContainer();
 270 
 271         enableEvents(InputEvent.COMPONENT_EVENT_MASK |
 272                      InputEvent.FOCUS_EVENT_MASK |
 273                      InputEvent.HIERARCHY_BOUNDS_EVENT_MASK |
 274                      InputEvent.HIERARCHY_EVENT_MASK |
 275                      InputEvent.MOUSE_EVENT_MASK |
 276                      InputEvent.MOUSE_MOTION_EVENT_MASK |
 277                      InputEvent.MOUSE_WHEEL_EVENT_MASK |
 278                      InputEvent.KEY_EVENT_MASK |
 279                      InputEvent.INPUT_METHOD_EVENT_MASK);
 280 
 281         setFocusable(true);
 282         setFocusTraversalKeysEnabled(false);
 283     }
 284 
 285     /**
 286      * Returns the JavaFX scene attached to this {@code JFXPanel}.
 287      *
 288      * @return the {@code Scene} attached to this {@code JFXPanel}
 289      */
 290     public Scene getScene() {
 291         return scene;
 292     }
 293 
 294     /**
 295      * Attaches a {@code Scene} object to display in this {@code
 296      * JFXPanel}. This method can be called either on the event
 297      * dispatch thread or the JavaFX application thread.
 298      *
 299      * @param newScene a scene to display in this {@code JFXpanel}
 300      *
 301      * @see java.awt.EventQueue#isDispatchThread()
 302      * @see javafx.application.Platform#isFxApplicationThread()
 303      */
 304     public void setScene(final Scene newScene) {
 305         if (Toolkit.getToolkit().isFxUserThread()) {
 306             setSceneImpl(newScene);
 307         } else {
 308             EventQueue eventQueue = AccessController.doPrivileged(
 309                     (PrivilegedAction<EventQueue>) java.awt.Toolkit
 310                             .getDefaultToolkit()::getSystemEventQueue);
 311             SecondaryLoop secondaryLoop = eventQueue.createSecondaryLoop();
 312             if (secondaryLoop.enter()) {
 313                 Platform.runLater(() -> {
 314                     setSceneImpl(newScene);
 315                 });
 316                 secondaryLoop.exit();
 317             }
 318         }
 319     }
 320 
 321     /*
 322      * Called on JavaFX app thread.
 323      */
 324     private void setSceneImpl(Scene newScene) {
 325         if ((stage != null) && (newScene == null)) {
 326             stage.hide();
 327             stage = null;
 328         }
 329         scene = newScene;
 330         if ((stage == null) && (newScene != null)) {
 331             stage = new EmbeddedWindow(hostContainer);
 332         }
 333         if (stage != null) {
 334             stage.setScene(newScene);
 335             if (!stage.isShowing()) {
 336                 stage.show();
 337             }
 338         }
 339     }
 340 
 341     /**
 342      * {@code JFXPanel}'s opacity is controlled by the JavaFX content
 343      * which is displayed in this component, so this method overrides
 344      * {@link javax.swing.JComponent#setOpaque(boolean)} to only accept a
 345      * {@code false} value. If this method is called with a {@code true}
 346      * value, no action is performed.
 347      *
 348      * @param opaque must be {@code false}
 349      */
 350     @Override
 351     public final void setOpaque(boolean opaque) {
 352         // Don't let user control opacity
 353         if (!opaque) {
 354             super.setOpaque(opaque);
 355         }
 356     }
 357 
 358     /**
 359      * {@code JFXPanel}'s opacity is controlled by the JavaFX content
 360      * which is displayed in this component, so this method overrides
 361      * {@link javax.swing.JComponent#isOpaque()} to always return a
 362      * {@code false} value.
 363      *
 364      * @return a {@code false} value
 365      */
 366     @Override
 367     public final boolean isOpaque() {
 368         return false;
 369     }
 370 
 371     private void sendMouseEventToFX(MouseEvent e) {
 372         if (scenePeer == null || !isFxEnabled()) {
 373             return;
 374         }
 375 
 376         // FX only supports 3 buttons so don't send the event for other buttons
 377         switch (e.getID()) {
 378             case MouseEvent.MOUSE_DRAGGED:
 379             case MouseEvent.MOUSE_PRESSED:
 380             case MouseEvent.MOUSE_RELEASED:
 381                 if (e.getButton() > 3)  return;
 382                 break;
 383         }
 384 
 385         int extModifiers = e.getModifiersEx();
 386         // Fix for RT-15457: we should report no mouse button upon mouse release, so
 387         // *BtnDown values are calculated based on extMofifiers, not e.getButton()
 388         boolean primaryBtnDown = (extModifiers & MouseEvent.BUTTON1_DOWN_MASK) != 0;
 389         boolean middleBtnDown = (extModifiers & MouseEvent.BUTTON2_DOWN_MASK) != 0;
 390         boolean secondaryBtnDown = (extModifiers & MouseEvent.BUTTON3_DOWN_MASK) != 0;
 391         // Fix for RT-16558: if a PRESSED event is consumed, e.g. by a Swing Popup,
 392         // subsequent DRAGGED and RELEASED events should not be sent to FX as well
 393         if (e.getID() == MouseEvent.MOUSE_DRAGGED) {
 394             if (!isCapturingMouse) {
 395                 return;
 396             }
 397         } else if (e.getID() == MouseEvent.MOUSE_PRESSED) {
 398             isCapturingMouse = true;
 399         } else if (e.getID() == MouseEvent.MOUSE_RELEASED) {
 400             if (!isCapturingMouse) {
 401                 return;
 402             }
 403             isCapturingMouse = primaryBtnDown || middleBtnDown || secondaryBtnDown;
 404         } else if (e.getID() == MouseEvent.MOUSE_CLICKED) {
 405             // Don't send click events to FX, as they are generated in Scene
 406             return;
 407         }
 408         // A workaround until JDK-8065131 is fixed.
 409         boolean popupTrigger = false;
 410         if (e.getID() == MouseEvent.MOUSE_PRESSED || e.getID() == MouseEvent.MOUSE_RELEASED) {
 411             popupTrigger = e.isPopupTrigger();
 412         }
 413 
 414         if(e.getID() == MouseEvent.MOUSE_WHEEL) {
 415             scenePeer.scrollEvent(AbstractEvents.MOUSEEVENT_VERTICAL_WHEEL,
 416                     0, -SwingEvents.getWheelRotation(e),
 417                     0, 0, // total scroll
 418                     40, 40, // multiplier
 419                     e.getX(), e.getY(),
 420                     e.getXOnScreen(), e.getYOnScreen(),
 421                     (extModifiers & MouseEvent.SHIFT_DOWN_MASK) != 0,
 422                     (extModifiers & MouseEvent.CTRL_DOWN_MASK) != 0,
 423                     (extModifiers & MouseEvent.ALT_DOWN_MASK) != 0,
 424                     (extModifiers & MouseEvent.META_DOWN_MASK) != 0, false);
 425         } else {
 426             scenePeer.mouseEvent(
 427                     SwingEvents.mouseIDToEmbedMouseType(e.getID()),
 428                     SwingEvents.mouseButtonToEmbedMouseButton(e.getButton(), extModifiers),
 429                     primaryBtnDown, middleBtnDown, secondaryBtnDown,
 430                     e.getX(), e.getY(), e.getXOnScreen(), e.getYOnScreen(),
 431                     (extModifiers & MouseEvent.SHIFT_DOWN_MASK) != 0,
 432                     (extModifiers & MouseEvent.CTRL_DOWN_MASK) != 0,
 433                     (extModifiers & MouseEvent.ALT_DOWN_MASK) != 0,
 434                     (extModifiers & MouseEvent.META_DOWN_MASK) != 0,
 435                     popupTrigger);
 436         }
 437         if (e.isPopupTrigger()) {
 438             scenePeer.menuEvent(e.getX(), e.getY(), e.getXOnScreen(), e.getYOnScreen(), false);
 439         }
 440     }
 441 
 442     /**
 443      * Overrides the {@link java.awt.Component#processMouseEvent(MouseEvent)}
 444      * method to dispatch the mouse event to the JavaFX scene attached to this
 445      * {@code JFXPanel}.
 446      *
 447      * @param e the mouse event to dispatch to the JavaFX scene
 448      */
 449     @Override
 450     protected void processMouseEvent(MouseEvent e) {
 451         if ((e.getID() == MouseEvent.MOUSE_PRESSED) &&
 452             (e.getButton() == MouseEvent.BUTTON1)) {
 453             if (!hasFocus()) {
 454                 requestFocus();
 455                 // this focus request event goes to eventqueue and will be
 456                 // asynchronously handled so MOUSE_PRESSED event will not be
 457                 // honoured by FX immediately due to lack of focus in fx
 458                 // component. Fire the same MOUSE_PRESSED event after
 459                 // requestFocus() so that 2nd mouse press will be honoured
 460                 // since now fx have focus
 461                 jfxPanelIOP.postEvent(this, e);
 462             }
 463         }
 464 
 465         sendMouseEventToFX(e);
 466         super.processMouseEvent(e);
 467     }
 468 
 469     /**
 470      * Overrides the {@link java.awt.Component#processMouseMotionEvent(MouseEvent)}
 471      * method to dispatch the mouse motion event to the JavaFX scene attached to
 472      * this {@code JFXPanel}.
 473      *
 474      * @param e the mouse motion event to dispatch to the JavaFX scene
 475      */
 476     @Override
 477     protected void processMouseMotionEvent(MouseEvent e) {
 478         sendMouseEventToFX(e);
 479         super.processMouseMotionEvent(e);
 480     }
 481 
 482     /**
 483      * Overrides the
 484      * {@link java.awt.Component#processMouseWheelEvent(MouseWheelEvent)}
 485      * method to dispatch the mouse wheel event to the JavaFX scene attached
 486      * to this {@code JFXPanel}.
 487      *
 488      * @param e the mouse wheel event to dispatch to the JavaFX scene
 489      */
 490     @Override
 491     protected void processMouseWheelEvent(MouseWheelEvent e) {
 492         sendMouseEventToFX(e);
 493         super.processMouseWheelEvent(e);
 494     }
 495 
 496     private void sendKeyEventToFX(final KeyEvent e) {
 497         if (scenePeer == null || !isFxEnabled()) {
 498             return;
 499         }
 500 
 501         char[] chars = (e.getKeyChar() == KeyEvent.CHAR_UNDEFINED)
 502                        ? new char[] {}
 503                        : new char[] { SwingEvents.keyCharToEmbedKeyChar(e.getKeyChar()) };
 504 
 505         scenePeer.keyEvent(
 506                 SwingEvents.keyIDToEmbedKeyType(e.getID()),
 507                 e.getKeyCode(), chars,
 508                 SwingEvents.keyModifiersToEmbedKeyModifiers(e.getModifiersEx()));
 509     }
 510 
 511     /**
 512      * Overrides the {@link java.awt.Component#processKeyEvent(KeyEvent)}
 513      * method to dispatch the key event to the JavaFX scene attached to this
 514      * {@code JFXPanel}.
 515      *
 516      * @param e the key event to dispatch to the JavaFX scene
 517      */
 518     @Override
 519     protected void processKeyEvent(KeyEvent e) {
 520         sendKeyEventToFX(e);
 521         super.processKeyEvent(e);
 522     }
 523 
 524     private void sendResizeEventToFX() {
 525         if (stagePeer != null) {
 526             stagePeer.setSize(pWidth, pHeight);
 527         }
 528         if (scenePeer != null) {
 529             scenePeer.setSize(pWidth, pHeight);
 530         }
 531     }
 532 
 533     /**
 534      * Overrides the
 535      * {@link java.awt.Component#processComponentEvent(ComponentEvent)}
 536      * method to dispatch {@link java.awt.event.ComponentEvent#COMPONENT_RESIZED}
 537      * events to the JavaFX scene attached to this {@code JFXPanel}. The JavaFX
 538      * scene object is then resized to match the {@code JFXPanel} size.
 539      *
 540      * @param e the component event to dispatch to the JavaFX scene
 541      */
 542     @Override
 543     protected void processComponentEvent(ComponentEvent e) {
 544         switch (e.getID()) {
 545             case ComponentEvent.COMPONENT_RESIZED: {
 546                 updateComponentSize();
 547                 break;
 548             }
 549             case ComponentEvent.COMPONENT_MOVED: {
 550                 if (updateScreenLocation()) {
 551                     sendMoveEventToFX();
 552                 }
 553                 break;
 554             }
 555             default: {
 556                 break;
 557             }
 558         }
 559         super.processComponentEvent(e);
 560     }
 561 
 562     // called on EDT only
 563     private void updateComponentSize() {
 564         int oldWidth = pWidth;
 565         int oldHeight = pHeight;
 566         // It's quite possible to get negative values here, this is not
 567         // what JavaFX embedded scenes/stages are ready to
 568         pWidth = Math.max(0, getWidth());
 569         pHeight = Math.max(0, getHeight());
 570         if (getBorder() != null) {
 571             Insets i = getBorder().getBorderInsets(this);
 572             pWidth -= (i.left + i.right);
 573             pHeight -= (i.top + i.bottom);
 574         }
 575         double newScaleFactorX = scaleFactorX;
 576         double newScaleFactorY = scaleFactorY;
 577         Graphics g = getGraphics();
 578         newScaleFactorX = GraphicsEnvironment.getLocalGraphicsEnvironment().
 579                           getDefaultScreenDevice().getDefaultConfiguration().
 580                           getDefaultTransform().getScaleX();
 581         newScaleFactorY = GraphicsEnvironment.getLocalGraphicsEnvironment().
 582                           getDefaultScreenDevice().getDefaultConfiguration().
 583                           getDefaultTransform().getScaleY();
 584         if (oldWidth != pWidth || oldHeight != pHeight ||
 585             newScaleFactorX != scaleFactorX || newScaleFactorY != scaleFactorY)
 586         {
 587             createResizePixelBuffer(newScaleFactorX, newScaleFactorY);
 588             if (scenePeer != null) {
 589                 scenePeer.setPixelScaleFactors((float) newScaleFactorX,
 590                                                (float) newScaleFactorY);
 591             }
 592             scaleFactorX = newScaleFactorX;
 593             scaleFactorY = newScaleFactorY;
 594             sendResizeEventToFX();
 595         }
 596     }
 597 
 598     // This methods should only be called on EDT
 599     private boolean updateScreenLocation() {
 600         synchronized (getTreeLock()) {
 601             if (isShowing()) {
 602                 Point p = getLocationOnScreen();
 603                 screenX = p.x;
 604                 screenY = p.y;
 605                 return true;
 606             }
 607         }
 608         return false;
 609     }
 610 
 611     private void sendMoveEventToFX() {
 612         if (stagePeer == null) {
 613             return;
 614         }
 615 
 616         stagePeer.setLocation(screenX, screenY);
 617     }
 618 
 619     /**
 620      * Overrides the
 621      * {@link java.awt.Component#processHierarchyBoundsEvent(HierarchyEvent)}
 622      * method to process {@link java.awt.event.HierarchyEvent#ANCESTOR_MOVED}
 623      * events and update the JavaFX scene location to match the {@code
 624      * JFXPanel} location on the screen.
 625      *
 626      * @param e the hierarchy bounds event to process
 627      */
 628     @Override
 629     protected void processHierarchyBoundsEvent(HierarchyEvent e) {
 630         if (e.getID() == HierarchyEvent.ANCESTOR_MOVED) {
 631             if (updateScreenLocation()) {
 632                 sendMoveEventToFX();
 633             }
 634         }
 635         super.processHierarchyBoundsEvent(e);
 636     }
 637 
 638     @Override
 639     protected void processHierarchyEvent(HierarchyEvent e) {
 640         if ((e.getChangeFlags() & HierarchyEvent.SHOWING_CHANGED) != 0) {
 641             if (updateScreenLocation()) {
 642                 sendMoveEventToFX();
 643             }
 644         }
 645         super.processHierarchyEvent(e);
 646     }
 647 
 648     private void sendFocusEventToFX(final FocusEvent e) {
 649         if ((stage == null) || (stagePeer == null) || !isFxEnabled()) {
 650             return;
 651         }
 652 
 653         boolean focused = (e.getID() == FocusEvent.FOCUS_GAINED);
 654         int focusCause = (focused ? AbstractEvents.FOCUSEVENT_ACTIVATED :
 655                                       AbstractEvents.FOCUSEVENT_DEACTIVATED);
 656 
 657         if (focused) {
 658             if (e.getCause() == FocusEvent.Cause.TRAVERSAL_FORWARD) {
 659                 focusCause = AbstractEvents.FOCUSEVENT_TRAVERSED_FORWARD;
 660             } else if (e.getCause() == FocusEvent.Cause.TRAVERSAL_BACKWARD) {
 661                 focusCause = AbstractEvents.FOCUSEVENT_TRAVERSED_BACKWARD;
 662             }
 663         }
 664         stagePeer.setFocused(focused, focusCause);
 665     }
 666 
 667     /**
 668      * Overrides the
 669      * {@link java.awt.Component#processFocusEvent(FocusEvent)}
 670      * method to dispatch focus events to the JavaFX scene attached to this
 671      * {@code JFXPanel}.
 672      *
 673      * @param e the focus event to dispatch to the JavaFX scene
 674      */
 675     @Override
 676     protected void processFocusEvent(FocusEvent e) {
 677         sendFocusEventToFX(e);
 678         super.processFocusEvent(e);
 679     }
 680 
 681     // called on EDT only
 682     private void createResizePixelBuffer(double newScaleFactorX, double newScaleFactorY) {
 683         if (scenePeer == null || pWidth <= 0 || pHeight <= 0) {
 684             pixelsIm = null;
 685         } else {
 686             BufferedImage oldIm = pixelsIm;
 687             int newPixelW = (int) Math.ceil(pWidth * newScaleFactorX);
 688             int newPixelH = (int) Math.ceil(pHeight * newScaleFactorY);
 689             pixelsIm = new BufferedImage(newPixelW, newPixelH,
 690                                          SwingFXUtils.getBestBufferedImageType(
 691                                              scenePeer.getPixelFormat(), null, false));
 692             if (oldIm != null) {
 693                 double ratioX = newScaleFactorX / scaleFactorX;
 694                 double ratioY = newScaleFactorY / scaleFactorY;
 695                 // Transform old size to the new coordinate space.
 696                 int oldW = (int)Math.round(oldIm.getWidth() * ratioX);
 697                 int oldH = (int)Math.round(oldIm.getHeight() * ratioY);
 698 
 699                 Graphics g = pixelsIm.getGraphics();
 700                 try {
 701                     g.drawImage(oldIm, 0, 0, oldW, oldH, null);
 702                 } finally {
 703                     g.dispose();
 704                 }
 705             }
 706         }
 707     }
 708 
 709     @Override
 710     protected void processInputMethodEvent(InputMethodEvent e) {
 711         if (e.getID() == InputMethodEvent.INPUT_METHOD_TEXT_CHANGED) {
 712             sendInputMethodEventToFX(e);
 713         }
 714         super.processInputMethodEvent(e);
 715     }
 716 
 717     private void sendInputMethodEventToFX(InputMethodEvent e) {
 718         String t = InputMethodSupport.getTextForEvent(e);
 719 
 720         int insertionIndex = 0;
 721         if (e.getCaret() != null) {
 722             insertionIndex = e.getCaret().getInsertionIndex();
 723         }
 724         scenePeer.inputMethodEvent(
 725                 javafx.scene.input.InputMethodEvent.INPUT_METHOD_TEXT_CHANGED,
 726                 InputMethodSupport.inputMethodEventComposed(t, e.getCommittedCharacterCount()),
 727                 t.substring(0, e.getCommittedCharacterCount()),
 728                 insertionIndex);
 729     }
 730 
 731     /**
 732      * Overrides the {@link javax.swing.JComponent#paintComponent(Graphics)}
 733      * method to paint the content of the JavaFX scene attached to this
 734      * {@code JFXpanel}.
 735      *
 736      * @param g the Graphics context in which to paint
 737      *
 738      * @see #isOpaque()
 739      */
 740     @Override
 741     protected void paintComponent(Graphics g) {
 742         if (scenePeer == null) {
 743             return;
 744         }
 745         if (pixelsIm == null) {
 746             createResizePixelBuffer(scaleFactorX, scaleFactorY);
 747             if (pixelsIm == null) {
 748                 return;
 749             }
 750         }
 751         DataBufferInt dataBuf = (DataBufferInt)pixelsIm.getRaster().getDataBuffer();
 752         int[] pixelsData = dataBuf.getData();
 753         IntBuffer buf = IntBuffer.wrap(pixelsData);
 754         if (!scenePeer.getPixels(buf, pWidth, pHeight)) {
 755             // In this case we just render what we have so far in the buffer.
 756         }
 757 
 758         Graphics gg = null;
 759         try {
 760             gg = g.create();
 761             if ((opacity < 1.0f) && (gg instanceof Graphics2D)) {
 762                 Graphics2D g2d = (Graphics2D)gg;
 763                 AlphaComposite c = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, opacity);
 764                 g2d.setComposite(c);
 765             }
 766             if (getBorder() != null) {
 767                 Insets i = getBorder().getBorderInsets(this);
 768                 gg.translate(i.left, i.top);
 769             }
 770             gg.drawImage(pixelsIm, 0, 0, pWidth, pHeight, null);
 771 
 772             double newScaleFactorX = scaleFactorX;
 773             double newScaleFactorY = scaleFactorY;
 774             newScaleFactorX = GraphicsEnvironment.getLocalGraphicsEnvironment().
 775                               getDefaultScreenDevice().getDefaultConfiguration().
 776                               getDefaultTransform().getScaleX();
 777             newScaleFactorY = GraphicsEnvironment.getLocalGraphicsEnvironment().
 778                               getDefaultScreenDevice().getDefaultConfiguration().
 779                               getDefaultTransform().getScaleY();
 780             if (scaleFactorX != newScaleFactorX || scaleFactorY != newScaleFactorY) {
 781                 createResizePixelBuffer(newScaleFactorX, newScaleFactorY);
 782                 // The scene will request repaint.
 783                 scenePeer.setPixelScaleFactors((float) newScaleFactorX,
 784                                                (float) newScaleFactorY);
 785                 scaleFactorX = newScaleFactorX;
 786                 scaleFactorY = newScaleFactorY;
 787             }
 788         } catch (Throwable th) {
 789             th.printStackTrace();
 790         } finally {
 791             if (gg != null) {
 792                 gg.dispose();
 793             }
 794         }
 795     }
 796 
 797     /**
 798      * Returns the preferred size of this {@code JFXPanel}, either
 799      * previously set with {@link #setPreferredSize(Dimension)} or
 800      * based on the content of the JavaFX scene attached to this {@code
 801      * JFXPanel}.
 802      *
 803      * @return prefSize this {@code JFXPanel} preferred size
 804      */
 805     @Override
 806     public Dimension getPreferredSize() {
 807         if (isPreferredSizeSet() || scenePeer == null) {
 808             return super.getPreferredSize();
 809         }
 810         return new Dimension(pPreferredWidth, pPreferredHeight);
 811     }
 812 
 813     private boolean isFxEnabled() {
 814         return this.disableCount.get() == 0;
 815     }
 816 
 817     private void setFxEnabled(boolean enabled) {
 818         if (!enabled) {
 819             if (disableCount.incrementAndGet() == 1) {
 820                 if (dnd != null) {
 821                     dnd.removeNotify();
 822                 }
 823             }
 824         } else {
 825             if (disableCount.get() == 0) {
 826                 //should report a warning about an extra enable call ?
 827                 return;
 828             }
 829             if (disableCount.decrementAndGet() == 0) {
 830                 if (dnd != null) {
 831                     dnd.addNotify();
 832                 }
 833             }
 834         }
 835     }
 836 
 837     private transient  AWTEventListener ungrabListener = event -> {
 838         if (jfxPanelIOP.isUngrabEvent(event)) {
 839             SwingNodeHelper.runOnFxThread(() -> {
 840                 if (JFXPanel.this.stagePeer != null &&
 841                         getScene() != null &&
 842                         getScene().getFocusOwner() != null &&
 843                         getScene().getFocusOwner().isFocused()) {
 844                     JFXPanel.this.stagePeer.focusUngrab();
 845                 }
 846             });
 847         }
 848         if (event instanceof MouseEvent) {
 849             // Synthesize FOCUS_UNGRAB if user clicks the AWT top-level window
 850             // that contains the JFXPanel.
 851             if (event.getID() == MouseEvent.MOUSE_PRESSED && event.getSource() instanceof Component) {
 852                 final Window jfxPanelWindow = SwingUtilities.getWindowAncestor(JFXPanel.this);
 853                 final Component source = (Component)event.getSource();
 854                 final Window eventWindow = source instanceof Window ? (Window)source : SwingUtilities.getWindowAncestor(source);
 855 
 856                 if (jfxPanelWindow == eventWindow) {
 857                     SwingNodeHelper.runOnFxThread(() -> {
 858                         if (JFXPanel.this.stagePeer != null) {
 859                             // No need to check if grab is active or not.
 860                             // NoAutoHide popups don't request the grab and
 861                             // ignore the Ungrab event anyway.
 862                             // AutoHide popups actually should be hidden when
 863                             // user clicks some non-FX content, even if for
 864                             // some reason they didn't install the grab when
 865                             // they were shown.
 866                             JFXPanel.this.stagePeer.focusUngrab();
 867                         }
 868                     });
 869                 }
 870             }
 871         }
 872     };
 873 
 874     /**
 875      * Notifies this component that it now has a parent component. When this
 876      * method is invoked, the chain of parent components is set up with
 877      * KeyboardAction event listeners.
 878      */
 879     @Override
 880     public void addNotify() {
 881         super.addNotify();
 882 
 883         registerFinishListener();
 884 
 885         AccessController.doPrivileged((PrivilegedAction<Void>) () -> {
 886             JFXPanel.this.getToolkit().addAWTEventListener(ungrabListener,
 887                                                jfxPanelIOP.getMask());
 888             return null;
 889         });
 890         updateComponentSize(); // see RT-23603
 891         SwingNodeHelper.runOnFxThread(() -> {
 892             if ((stage != null) && !stage.isShowing()) {
 893                 stage.show();
 894                 sendMoveEventToFX();
 895             }
 896         });
 897     }
 898 
 899     @Override
 900     public InputMethodRequests getInputMethodRequests() {
 901         EmbeddedSceneInterface scene = scenePeer;
 902         if (scene == null) {
 903             return null;
 904         }
 905         return new InputMethodSupport.InputMethodRequestsAdapter(scene.getInputMethodRequests());
 906     }
 907 
 908     /**
 909      * Notifies this component that it no longer has a parent component.
 910      * When this method is invoked, any KeyboardActions set up in the the
 911      * chain of parent components are removed.
 912      */
 913     @Override public void removeNotify() {
 914         SwingNodeHelper.runOnFxThread(() -> {
 915             if ((stage != null) && stage.isShowing()) {
 916                 stage.hide();
 917             }
 918         });
 919 
 920         pixelsIm = null;
 921         pWidth = 0;
 922         pHeight = 0;
 923 
 924         super.removeNotify();
 925 
 926         AccessController.doPrivileged((PrivilegedAction<Void>) () -> {
 927             JFXPanel.this.getToolkit().removeAWTEventListener(ungrabListener);
 928             return null;
 929         });
 930 
 931         /* see CR 4867453 */
 932         getInputContext().removeNotify(this);
 933 
 934         deregisterFinishListener();
 935     }
 936 
 937     private void invokeOnClientEDT(Runnable r) {
 938         jfxPanelIOP.postEvent(this, new InvocationEvent(this, r));
 939     }
 940 
 941     private class HostContainer implements HostInterface {
 942 
 943         @Override
 944         public void setEmbeddedStage(EmbeddedStageInterface embeddedStage) {
 945             stagePeer = embeddedStage;
 946             if (stagePeer == null) {
 947                 return;
 948             }
 949             if (pWidth > 0 && pHeight > 0) {
 950                 stagePeer.setSize(pWidth, pHeight);
 951             }
 952             invokeOnClientEDT(() -> {
 953                 if (stagePeer != null && JFXPanel.this.isFocusOwner()) {
 954                     stagePeer.setFocused(true, AbstractEvents.FOCUSEVENT_ACTIVATED);
 955                 }
 956             });
 957             sendMoveEventToFX();
 958         }
 959 
 960         @Override
 961         public void setEmbeddedScene(EmbeddedSceneInterface embeddedScene) {
 962             if (scenePeer == embeddedScene) {
 963                 return;
 964             }
 965             scenePeer = embeddedScene;
 966             if (scenePeer == null) {
 967                 invokeOnClientEDT(() -> {
 968                     if (dnd != null) {
 969                         dnd.removeNotify();
 970                         dnd = null;
 971                     }
 972                 });
 973                 return;
 974             }
 975             if (pWidth > 0 && pHeight > 0) {
 976                 scenePeer.setSize(pWidth, pHeight);
 977             }
 978             scenePeer.setPixelScaleFactors((float) scaleFactorX, (float) scaleFactorY);
 979 
 980             invokeOnClientEDT(() -> {
 981                 dnd = new SwingDnD(JFXPanel.this, scenePeer);
 982                 dnd.addNotify();
 983                 if (scenePeer != null) {
 984                     scenePeer.setDragStartListener(dnd.getDragStartListener());
 985                 }
 986             });
 987         }
 988 
 989         @Override
 990         public boolean requestFocus() {
 991             return requestFocusInWindow();
 992         }
 993 
 994         @Override
 995         public boolean traverseFocusOut(boolean forward) {
 996             KeyboardFocusManager kfm = KeyboardFocusManager.getCurrentKeyboardFocusManager();
 997             if (forward) {
 998                 kfm.focusNextComponent(JFXPanel.this);
 999             } else {
1000                 kfm.focusPreviousComponent(JFXPanel.this);
1001             }
1002             return true;
1003         }
1004 
1005         @Override
1006         public void setPreferredSize(final int width, final int height) {
1007             invokeOnClientEDT(() -> {
1008                 JFXPanel.this.pPreferredWidth = width;
1009                 JFXPanel.this.pPreferredHeight = height;
1010                 JFXPanel.this.revalidate();
1011             });
1012         }
1013 
1014         @Override
1015         public void repaint() {
1016             invokeOnClientEDT(() -> {
1017                 JFXPanel.this.repaint();
1018             });
1019         }
1020 
1021         @Override
1022         public void setEnabled(final boolean enabled) {
1023             JFXPanel.this.setFxEnabled(enabled);
1024         }
1025 
1026         @Override
1027         public void setCursor(CursorFrame cursorFrame) {
1028             final Cursor cursor = getPlatformCursor(cursorFrame);
1029             invokeOnClientEDT(() -> {
1030                 JFXPanel.this.setCursor(cursor);
1031             });
1032         }
1033 
1034         private Cursor getPlatformCursor(final CursorFrame cursorFrame) {
1035             final Cursor cachedPlatformCursor =
1036                     cursorFrame.getPlatformCursor(Cursor.class);
1037             if (cachedPlatformCursor != null) {
1038                 // platform cursor already cached
1039                 return cachedPlatformCursor;
1040             }
1041 
1042             // platform cursor not cached yet
1043             final Cursor platformCursor =
1044                     SwingCursors.embedCursorToCursor(cursorFrame);
1045             cursorFrame.setPlatforCursor(Cursor.class, platformCursor);
1046 
1047             return platformCursor;
1048         }
1049 
1050         @Override
1051         public boolean grabFocus() {
1052             // On X11 grab is limited to a single XDisplay connection,
1053             // so we can't delegate it to another GUI toolkit.
1054             if (PlatformUtil.isLinux()) return true;
1055 
1056             invokeOnClientEDT(() -> {
1057                 Window window = SwingUtilities.getWindowAncestor(JFXPanel.this);
1058                 if (window != null) {
1059                     jfxPanelIOP.grab(JFXPanel.this.getToolkit(), window);
1060                 }
1061             });
1062 
1063             return true; // Oh, well...
1064         }
1065 
1066         @Override
1067         public void ungrabFocus() {
1068             // On X11 grab is limited to a single XDisplay connection,
1069             // so we can't delegate it to another GUI toolkit.
1070             if (PlatformUtil.isLinux()) return;
1071 
1072             invokeOnClientEDT(() -> {
1073                 Window window = SwingUtilities.getWindowAncestor(JFXPanel.this);
1074                 if (window != null) {
1075                     jfxPanelIOP.ungrab(JFXPanel.this.getToolkit(), window);
1076                 }
1077             });
1078         }
1079     }
1080 }