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