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