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