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