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