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