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