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