1 /*
   2  * Copyright (c) 2010, 2013, 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.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.event.AWTEventListener;
  38 import java.awt.event.ComponentEvent;
  39 import java.awt.event.FocusEvent;
  40 import java.awt.event.HierarchyEvent;
  41 import java.awt.event.InputEvent;
  42 import java.awt.event.KeyEvent;
  43 import java.awt.event.MouseEvent;
  44 import java.awt.event.MouseWheelEvent;
  45 import java.awt.image.BufferedImage;
  46 import java.awt.image.DataBufferInt;
  47 import java.nio.IntBuffer;
  48 import java.security.AccessController;
  49 import java.security.PrivilegedAction;
  50 import java.util.concurrent.CountDownLatch;
  51 
  52 import javafx.application.Platform;
  53 import javafx.scene.Scene;
  54 
  55 import javax.swing.JComponent;
  56 import javax.swing.SwingUtilities;
  57 
  58 import com.sun.javafx.application.PlatformImpl;
  59 import com.sun.javafx.cursor.CursorFrame;
  60 import com.sun.javafx.embed.AbstractEvents;
  61 import com.sun.javafx.embed.EmbeddedSceneInterface;
  62 import com.sun.javafx.embed.EmbeddedStageInterface;
  63 import com.sun.javafx.embed.HostInterface;
  64 import com.sun.javafx.stage.EmbeddedWindow;
  65 import com.sun.javafx.tk.Toolkit;
  66 
  67 import java.util.concurrent.atomic.AtomicInteger;
  68 import sun.awt.CausedFocusEvent;
  69 import sun.awt.SunToolkit;
  70 
  71 /**
  72 * {@code JFXPanel} is a component to embed JavaFX content into
  73  * Swing applications. The content to be displayed is specified
  74  * with the {@link #setScene} method that accepts an instance of
  75  * JavaFX {@code Scene}. After the scene is assigned, it gets
  76  * repainted automatically. All the input and focus events are
  77  * forwarded to the scene transparently to the developer.
  78  * <p>
  79  * There are some restrictions related to {@code JFXPanel}. As a
  80  * Swing component, it should only be accessed from the event
  81  * dispatch thread, except the {@link #setScene} method, which can
  82  * be called either on the event dispatch thread or on the JavaFX
  83  * application thread.
  84  * <p>
  85  * Here is a typical pattern how {@code JFXPanel} can used:
  86  * <pre>
  87  *     public class Test {
  88  *
  89  *         private static void initAndShowGUI() {
  90  *             // This method is invoked on Swing thread
  91  *             JFrame frame = new JFrame("FX");
  92  *             final JFXPanel fxPanel = new JFXPanel();
  93  *             frame.add(fxPanel);
  94  *             frame.setVisible(true);
  95  *
  96  *             Platform.runLater(new Runnable() {
  97  *                 &#064;Override
  98  *                 public void run() {
  99  *                     initFX(fxPanel);
 100  *                 }
 101  *             });
 102  *         }
 103  *
 104  *         private static void initFX(JFXPanel fxPanel) {
 105  *             // This method is invoked on JavaFX thread
 106  *             Scene scene = createScene();
 107  *             fxPanel.setScene(scene);
 108  *         }
 109  *
 110  *         public static void main(String[] args) {
 111  *             SwingUtilities.invokeLater(new Runnable() {
 112  *                 &#064;Override
 113  *                 public void run() {
 114  *                     initAndShowGUI();
 115  *                 }
 116  *             });
 117  *         }
 118  *     }
 119  * </pre>
 120  *
 121  */
 122 public class JFXPanel extends JComponent {
 123 
 124     private static PlatformImpl.FinishListener finishListener;
 125     private static boolean firstPanelShown = false;
 126 
 127     private HostContainer hostContainer;
 128 
 129     private volatile EmbeddedWindow stage;
 130     private volatile Scene scene;
 131 
 132     private final SwingDnD dnd;
 133 
 134     private EmbeddedStageInterface stagePeer;
 135     private EmbeddedSceneInterface scenePeer;
 136 
 137     // Dimensions of back buffer used to draw FX content
 138     private int pWidth;
 139     private int pHeight;
 140 
 141     // Preferred size set from FX
 142     private volatile int pPreferredWidth = -1;
 143     private volatile int pPreferredHeight = -1;
 144 
 145     // Cached copy of this component's location on screen to avoid
 146     // calling getLocationOnScreen() under the tree lock on FX thread
 147     private volatile int screenX = 0;
 148     private volatile int screenY = 0;
 149 
 150     // accessed on EDT only
 151     private BufferedImage pixelsIm;
 152 
 153     private volatile float opacity = 1.0f;
 154 
 155     // Indicates how many times setFxEnabled(false) has been called.
 156     // A value of 0 means the component is enabled.
 157     private AtomicInteger disableCount = new AtomicInteger(0);
 158 
 159     private boolean isCapturingMouse = false;
 160 
 161     // Initialize FX runtime when the JFXPanel instance is constructed
 162     private synchronized static void initFx() {
 163         if (finishListener != null) {
 164             // Already registered
 165             return;
 166         }
 167         // Need to install a finish listener to catch calls to Platform.exit
 168         finishListener = new PlatformImpl.FinishListener() {
 169             @Override public void idle(boolean implicitExit) {
 170                 if (!firstPanelShown) {
 171                     return;
 172                 }
 173                 PlatformImpl.removeListener(finishListener);
 174                 finishListener = null;
 175                 if (implicitExit) {
 176                     Platform.exit();
 177                 }
 178             }
 179             @Override public void exitCalled() {
 180             }
 181         };
 182         PlatformImpl.addListener(finishListener);
 183         // Note that calling PlatformImpl.startup more than once is OK
 184         PlatformImpl.startup(new Runnable() {
 185             @Override public void run() {
 186                 // No need to do anything here
 187             }
 188         });
 189     }
 190 
 191     /**
 192      * Creates a new {@code JFXPanel} object.
 193      * <p>
 194      * <b>Implementation note</b>: when the first {@code JFXPanel} object
 195      * is created, it implicitly initializes the JavaFX runtime. This is the
 196      * preferred way to initialize JavaFX in Swing.
 197      */
 198     public JFXPanel() {
 199         super();
 200 
 201         initFx();
 202 
 203         hostContainer = new HostContainer();
 204 
 205         enableEvents(InputEvent.COMPONENT_EVENT_MASK |
 206                      InputEvent.FOCUS_EVENT_MASK |
 207                      InputEvent.HIERARCHY_BOUNDS_EVENT_MASK |
 208                      InputEvent.HIERARCHY_EVENT_MASK |
 209                      InputEvent.MOUSE_EVENT_MASK |
 210                      InputEvent.MOUSE_MOTION_EVENT_MASK |
 211                      InputEvent.MOUSE_WHEEL_EVENT_MASK |
 212                      InputEvent.KEY_EVENT_MASK);
 213 
 214         setFocusable(true);
 215         setFocusTraversalKeysEnabled(false);
 216 
 217         this.dnd = new SwingDnD(this, new SwingDnD.JFXPanelFacade() {
 218 
 219             @Override
 220             public EmbeddedSceneInterface getScene() {
 221                 return isFxEnabled() ? scenePeer : null;
 222             }
 223         });
 224     }
 225 
 226     /**
 227      * Returns the JavaFX scene attached to this {@code JFXPanel}.
 228      *
 229      * @return the {@code Scene} attached to this {@code JFXPanel}
 230      */
 231     public Scene getScene() {
 232         return scene;
 233     }
 234 
 235     /**
 236      * Attaches a {@code Scene} object to display in this {@code
 237      * JFXPanel}. This method can be called either on the event
 238      * dispatch thread or the JavaFX application thread.
 239      *
 240      * @param newScene a scene to display in this {@code JFXpanel}
 241      *
 242      * @see java.awt.EventQueue#isDispatchThread()
 243      * @see javafx.application.Platform#isFxApplicationThread()
 244      */
 245     public void setScene(final Scene newScene) {
 246         if (Toolkit.getToolkit().isFxUserThread()) {
 247             setSceneImpl(newScene);
 248         } else {
 249             final CountDownLatch initLatch = new CountDownLatch(1);
 250             Platform.runLater(new Runnable() {
 251                 @Override
 252                 public void run() {
 253                     setSceneImpl(newScene);
 254                     initLatch.countDown();
 255                 }
 256             });
 257             try {
 258                 initLatch.await();
 259             } catch (InterruptedException z) {
 260                 z.printStackTrace(System.err);
 261             }
 262         }
 263     }
 264 
 265     /*
 266      * Called on JavaFX app thread.
 267      */
 268     private void setSceneImpl(Scene newScene) {
 269         if ((stage != null) && (newScene == null)) {
 270             stage.hide();
 271             stage = null;
 272         }
 273         scene = newScene;
 274         if ((stage == null) && (newScene != null)) {
 275             stage = new EmbeddedWindow(hostContainer);
 276         }
 277         if (stage != null) {
 278             stage.setScene(newScene);
 279             if (!stage.isShowing()) {
 280                 stage.show();
 281                 firstPanelShown = true;
 282             }
 283         }
 284     }
 285 
 286     /**
 287      * {@code JFXPanel}'s opacity is controlled by the JavaFX content
 288      * which is displayed in this component, so this method overrides
 289      * {@link javax.swing.JComponent#setOpaque(boolean)} to only accept a
 290      * {@code false} value. If this method is called with a {@code true}
 291      * value, no action is performed.
 292      *
 293      * @param opaque must be {@code false}
 294      */
 295     @Override
 296     public final void setOpaque(boolean opaque) {
 297         // Don't let user control opacity
 298         if (!opaque) {
 299             super.setOpaque(opaque);
 300         }
 301     }
 302 
 303     /**
 304      * {@code JFXPanel}'s opacity is controlled by the JavaFX content
 305      * which is displayed in this component, so this method overrides
 306      * {@link javax.swing.JComponent#isOpaque()} to always return a
 307      * {@code false} value.
 308      *
 309      * @return a {@code false} value
 310      */
 311     @Override
 312     public final boolean isOpaque() {
 313         return false;
 314     }
 315 
 316     private void sendMouseEventToFX(MouseEvent e) {
 317         if (scenePeer == null || !isFxEnabled()) {
 318             return;
 319         }
 320 
 321         int extModifiers = e.getModifiersEx();
 322         // Fix for RT-15457: we should report no mouse button upon mouse release, so
 323         // *BtnDown values are calculated based on extMofifiers, not e.getButton()
 324         boolean primaryBtnDown = (extModifiers & MouseEvent.BUTTON1_DOWN_MASK) != 0;
 325         boolean middleBtnDown = (extModifiers & MouseEvent.BUTTON2_DOWN_MASK) != 0;
 326         boolean secondaryBtnDown = (extModifiers & MouseEvent.BUTTON3_DOWN_MASK) != 0;
 327         // Fix for RT-16558: if a PRESSED event is consumed, e.g. by a Swing Popup,
 328         // subsequent DRAGGED and RELEASED events should not be sent to FX as well
 329         if (e.getID() == MouseEvent.MOUSE_DRAGGED) {
 330             if (!isCapturingMouse) {
 331                 return;
 332             }
 333         } else if (e.getID() == MouseEvent.MOUSE_PRESSED) {
 334             isCapturingMouse = true;
 335         } else if (e.getID() == MouseEvent.MOUSE_RELEASED) {
 336             if (!isCapturingMouse) {
 337                 return;
 338             }
 339             isCapturingMouse = primaryBtnDown || middleBtnDown || secondaryBtnDown;
 340         }
 341         scenePeer.mouseEvent(
 342                 SwingEvents.mouseIDToEmbedMouseType(e.getID()),
 343                 SwingEvents.mouseButtonToEmbedMouseButton(e.getButton(), extModifiers),
 344                 primaryBtnDown, middleBtnDown, secondaryBtnDown,
 345                 e.getClickCount(),
 346                 e.getX(), e.getY(), e.getXOnScreen(), e.getYOnScreen(),
 347                 (extModifiers & MouseEvent.SHIFT_DOWN_MASK) != 0,
 348                 (extModifiers & MouseEvent.CTRL_DOWN_MASK) != 0,
 349                 (extModifiers & MouseEvent.ALT_DOWN_MASK) != 0,
 350                 (extModifiers & MouseEvent.META_DOWN_MASK) != 0,
 351                 SwingEvents.getWheelRotation(e), e.isPopupTrigger());
 352         if (e.isPopupTrigger()) {
 353             scenePeer.menuEvent(e.getX(), e.getY(), e.getXOnScreen(), e.getYOnScreen(), false);
 354         }
 355     }
 356 
 357     /**
 358      * Overrides the {@link java.awt.Component#processMouseEvent(MouseEvent)}
 359      * method to dispatch the mouse event to the JavaFX scene attached to this
 360      * {@code JFXPanel}.
 361      *
 362      * @param e the mouse event to dispatch to the JavaFX scene
 363      */
 364     @Override
 365     protected void processMouseEvent(MouseEvent e) {
 366         if ((e.getID() == MouseEvent.MOUSE_PRESSED) &&
 367             (e.getButton() == MouseEvent.BUTTON1)) {
 368             if (!hasFocus()) {
 369                 requestFocus();
 370             }
 371         }
 372 
 373         sendMouseEventToFX(e);
 374         super.processMouseEvent(e);
 375     }
 376 
 377     /**
 378      * Overrides the {@link java.awt.Component#processMouseMotionEvent(MouseEvent)}
 379      * method to dispatch the mouse motion event to the JavaFX scene attached to
 380      * this {@code JFXPanel}.
 381      *
 382      * @param e the mouse motion event to dispatch to the JavaFX scene
 383      */
 384     @Override
 385     protected void processMouseMotionEvent(MouseEvent e) {
 386         sendMouseEventToFX(e);
 387         super.processMouseMotionEvent(e);
 388     }
 389 
 390     /**
 391      * Overrides the
 392      * {@link java.awt.Component#processMouseWheelEvent(MouseWheelEvent)}
 393      * method to dispatch the mouse wheel event to the JavaFX scene attached
 394      * to this {@code JFXPanel}.
 395      *
 396      * @param e the mouse wheel event to dispatch to the JavaFX scene
 397      */
 398     @Override
 399     protected void processMouseWheelEvent(MouseWheelEvent e) {
 400         sendMouseEventToFX(e);
 401         super.processMouseWheelEvent(e);
 402     }
 403 
 404     private void sendKeyEventToFX(final KeyEvent e) {
 405         if (scenePeer == null || !isFxEnabled()) {
 406             return;
 407         }
 408 
 409         char[] chars = (e.getKeyChar() == KeyEvent.CHAR_UNDEFINED)
 410                        ? new char[] {}
 411                        : new char[] { e.getKeyChar() };
 412 
 413         scenePeer.keyEvent(
 414                 SwingEvents.keyIDToEmbedKeyType(e.getID()),
 415                 e.getKeyCode(), chars,
 416                 SwingEvents.keyModifiersToEmbedKeyModifiers(e.getModifiersEx()));
 417     }
 418 
 419     /**
 420      * Overrides the {@link java.awt.Component#processKeyEvent(KeyEvent)}
 421      * method to dispatch the key event to the JavaFX scene attached to this
 422      * {@code JFXPanel}.
 423      *
 424      * @param e the key event to dispatch to the JavaFX scene
 425      */
 426     @Override
 427     protected void processKeyEvent(KeyEvent e) {
 428         sendKeyEventToFX(e);
 429         super.processKeyEvent(e);
 430     }
 431 
 432     private void sendResizeEventToFX() {
 433         if (stagePeer != null) {
 434             stagePeer.setSize(pWidth, pHeight);
 435         }
 436         if (scenePeer != null) {
 437             scenePeer.setSize(pWidth, pHeight);
 438         }
 439     }
 440 
 441     /**
 442      * Overrides the
 443      * {@link java.awt.Component#processComponentEvent(ComponentEvent)}
 444      * method to dispatch {@link java.awt.event.ComponentEvent#COMPONENT_RESIZED}
 445      * events to the JavaFX scene attached to this {@code JFXPanel}. The JavaFX
 446      * scene object is then resized to match the {@code JFXPanel} size.
 447      *
 448      * @param e the component event to dispatch to the JavaFX scene
 449      */
 450     @Override
 451     protected void processComponentEvent(ComponentEvent e) {
 452         switch (e.getID()) {
 453             case ComponentEvent.COMPONENT_RESIZED: {
 454                 updateComponentSize();
 455                 break;
 456             }
 457             case ComponentEvent.COMPONENT_MOVED: {
 458                 if (updateScreenLocation()) {
 459                     sendMoveEventToFX();
 460                 }
 461                 break;
 462             }
 463             default: {
 464                 break;
 465             }
 466         }
 467         super.processComponentEvent(e);
 468     }
 469 
 470     // called on EDT only
 471     private void updateComponentSize() {
 472         int oldWidth = pWidth;
 473         int oldHeight = pHeight;
 474         // It's quite possible to get negative values here, this is not
 475         // what JavaFX embedded scenes/stages are ready to
 476         pWidth = Math.max(0, getWidth());
 477         pHeight = Math.max(0, getHeight());
 478         if (oldWidth != pWidth || oldHeight != pHeight) {
 479             resizePixels();
 480             sendResizeEventToFX();
 481         }
 482     }
 483 
 484     // This methods should only be called on EDT
 485     private boolean updateScreenLocation() {
 486         synchronized (getTreeLock()) {
 487             if (isShowing()) {
 488                 Point p = getLocationOnScreen();
 489                 screenX = p.x;
 490                 screenY = p.y;
 491                 return true;
 492             }
 493         }
 494         return false;
 495     }
 496 
 497     private void sendMoveEventToFX() {
 498         if (stagePeer == null) {
 499             return;
 500         }
 501 
 502         stagePeer.setLocation(screenX, screenY);
 503     }
 504 
 505     /**
 506      * Overrides the
 507      * {@link java.awt.Component#processHierarchyBoundsEvent(HierarchyEvent)}
 508      * method to process {@link java.awt.event.HierarchyEvent#ANCESTOR_MOVED}
 509      * events and update the JavaFX scene location to match the {@code
 510      * JFXPanel} location on the screen.
 511      *
 512      * @param e the hierarchy bounds event to process
 513      */
 514     @Override
 515     protected void processHierarchyBoundsEvent(HierarchyEvent e) {
 516         if (e.getID() == HierarchyEvent.ANCESTOR_MOVED) {
 517             if (updateScreenLocation()) {
 518                 sendMoveEventToFX();
 519             }
 520         }
 521         super.processHierarchyBoundsEvent(e);
 522     }
 523 
 524     @Override
 525     protected void processHierarchyEvent(HierarchyEvent e) {
 526         if ((e.getChangeFlags() & HierarchyEvent.SHOWING_CHANGED) != 0) {
 527             if (updateScreenLocation()) {
 528                 sendMoveEventToFX();
 529             }
 530         }
 531         super.processHierarchyEvent(e);
 532     }
 533 
 534     private void sendFocusEventToFX(final FocusEvent e) {
 535         if ((stage == null) || (stagePeer == null) || !isFxEnabled()) {
 536             return;
 537         }
 538 
 539         boolean focused = (e.getID() == FocusEvent.FOCUS_GAINED);
 540         int focusCause = (focused ? AbstractEvents.FOCUSEVENT_ACTIVATED :
 541                                       AbstractEvents.FOCUSEVENT_DEACTIVATED);
 542 
 543         if (focused && (e instanceof CausedFocusEvent)) {
 544             CausedFocusEvent ce = (CausedFocusEvent)e;
 545             if (ce.getCause() == CausedFocusEvent.Cause.TRAVERSAL_FORWARD) {
 546                 focusCause = AbstractEvents.FOCUSEVENT_TRAVERSED_FORWARD;
 547                         } else if (ce.getCause() == sun.awt.CausedFocusEvent.Cause.TRAVERSAL_BACKWARD) {
 548                 focusCause = AbstractEvents.FOCUSEVENT_TRAVERSED_BACKWARD;
 549                         }
 550                     }
 551         stagePeer.setFocused(focused, focusCause);
 552     }
 553 
 554     /**
 555      * Overrides the
 556      * {@link java.awt.Component#processFocusEvent(FocusEvent)}
 557      * method to dispatch focus events to the JavaFX scene attached to this
 558      * {@code JFXPanel}.
 559      *
 560      * @param e the focus event to dispatch to the JavaFX scene
 561      */
 562     @Override
 563     protected void processFocusEvent(FocusEvent e) {
 564         sendFocusEventToFX(e);
 565         super.processFocusEvent(e);
 566     }
 567 
 568     // called on EDT only
 569     private void resizePixels() {
 570         if ((pWidth <= 0) || (pHeight <= 0)) {
 571             pixelsIm = null;
 572         } else {
 573             BufferedImage oldIm = pixelsIm;
 574             pixelsIm = new BufferedImage(pWidth, pHeight, BufferedImage.TYPE_INT_ARGB);
 575             if (oldIm != null) {
 576                 Graphics g = pixelsIm.getGraphics();
 577                 try {
 578                     g.drawImage(oldIm, 0, 0, null);
 579                 } finally {
 580                     g.dispose();
 581                 }
 582             }
 583         }
 584     }
 585 
 586     /**
 587      * Overrides the {@link javax.swing.JComponent#paintComponent(Graphics)}
 588      * method to paint the content of the JavaFX scene attached to this
 589      * {@code JFXpanel}.
 590      *
 591      * @param g the Graphics context in which to paint
 592      *
 593      * @see #isOpaque()
 594      */
 595     @Override
 596     protected void paintComponent(Graphics g) {
 597         if ((scenePeer == null) || (pixelsIm == null)) {
 598             return;
 599         }
 600 
 601         DataBufferInt dataBuf = (DataBufferInt)pixelsIm.getRaster().getDataBuffer();
 602         int[] pixelsData = dataBuf.getData();
 603         IntBuffer buf = IntBuffer.wrap(pixelsData);
 604         if (!scenePeer.getPixels(buf, pWidth, pHeight)) {
 605             // May happen during early Quantum initialization
 606             return;
 607         }
 608 
 609         Graphics gg = null;
 610         try {
 611             gg = g.create();
 612             if ((opacity < 1.0f) && (gg instanceof Graphics2D)) {
 613                 Graphics2D g2d = (Graphics2D)gg;
 614                 AlphaComposite c = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, opacity);
 615                 g2d.setComposite(c);
 616             }
 617             gg.drawImage(pixelsIm, 0, 0, null);
 618         } catch (Throwable th) {
 619             th.printStackTrace();
 620         } finally {
 621             if (gg != null) {
 622                 gg.dispose();
 623             }
 624         }
 625     }
 626 
 627     /**
 628      * Returns the preferred size of this {@code JFXPanel}, either
 629      * previously set with {@link #setPreferredSize(Dimension)} or
 630      * based on the content of the JavaFX scene attached to this {@code
 631      * JFXPanel}.
 632      *
 633      * @return prefSize this {@code JFXPanel} preferred size
 634      */
 635     @Override
 636     public Dimension getPreferredSize() {
 637         if (isPreferredSizeSet() || scenePeer == null) {
 638             return super.getPreferredSize();
 639         }
 640         return new Dimension(pPreferredWidth, pPreferredHeight);
 641     }
 642 
 643     private boolean isFxEnabled() {
 644         return this.disableCount.get() == 0;
 645     }
 646 
 647     private void setFxEnabled(boolean enabled) {
 648         if (!enabled) {
 649             disableCount.incrementAndGet();
 650         } else {
 651             if (disableCount.get() == 0) {
 652                 //should report a warning about an extra enable call ?
 653                 return;
 654             }
 655             disableCount.decrementAndGet();
 656         }
 657     }
 658 
 659     private final AWTEventListener ungrabListener = new AWTEventListener() {
 660         @Override
 661         public void eventDispatched(AWTEvent event) {
 662             if (event instanceof sun.awt.UngrabEvent) {
 663                 SwingFXUtils.runOnFxThread(new Runnable() {
 664                     @Override
 665                     public void run() {
 666                         if (JFXPanel.this.stagePeer != null) {
 667                             JFXPanel.this.stagePeer.focusUngrab();
 668                         }
 669                     }
 670                 });
 671             }
 672         }
 673     };
 674 
 675     /**
 676      * Notifies this component that it now has a parent component. When this
 677      * method is invoked, the chain of parent components is set up with
 678      * KeyboardAction event listeners.
 679      */
 680     @Override
 681     public void addNotify() {
 682         super.addNotify();
 683 
 684         dnd.addNotify();
 685 
 686         AccessController.doPrivileged(new PrivilegedAction<Void>() {
 687             public Void run() {
 688                 JFXPanel.this.getToolkit().addAWTEventListener(ungrabListener,
 689                     sun.awt.SunToolkit.GRAB_EVENT_MASK);
 690                 return null;
 691             }
 692         });
 693 
 694         updateComponentSize(); // see RT-23603
 695 
 696         SwingFXUtils.runOnFxThread(new Runnable() {
 697             @Override
 698             public void run() {
 699                 if ((stage != null) && !stage.isShowing()) {
 700                     stage.show();
 701                     firstPanelShown = true;
 702                     sendMoveEventToFX();
 703                 }
 704             }
 705         });
 706     }
 707 
 708     /**
 709      * Notifies this component that it no longer has a parent component.
 710      * When this method is invoked, any KeyboardActions set up in the the
 711      * chain of parent components are removed.
 712      */
 713     @Override public void removeNotify() {
 714         SwingFXUtils.runOnFxThread(new Runnable() {
 715             @Override
 716             public void run() {
 717                 if ((stage != null) && stage.isShowing()) {
 718                     stage.hide();
 719                 }
 720             }
 721         });
 722 
 723         pixelsIm = null;
 724         pWidth = 0;
 725         pHeight = 0;
 726 
 727         super.removeNotify();
 728 
 729         AccessController.doPrivileged(new PrivilegedAction<Void>() {
 730             public Void run() {
 731                 JFXPanel.this.getToolkit().removeAWTEventListener(ungrabListener);
 732                 return null;
 733             }
 734         });
 735 
 736         dnd.removeNotify();
 737 
 738         /* see CR 4867453 */
 739         getInputContext().removeNotify(this);
 740     }
 741 
 742     private class HostContainer implements HostInterface {
 743 
 744         @Override
 745         public void setEmbeddedStage(EmbeddedStageInterface embeddedStage) {
 746             stagePeer = embeddedStage;
 747             if (stagePeer == null) {
 748                 return;
 749             }
 750             if (pWidth > 0 && pHeight > 0) {
 751                 stagePeer.setSize(pWidth, pHeight);
 752             }
 753             if (JFXPanel.this.isFocusOwner()) {
 754                 stagePeer.setFocused(true, AbstractEvents.FOCUSEVENT_ACTIVATED);
 755             }
 756             sendMoveEventToFX();
 757         }
 758 
 759         @Override
 760         public void setEmbeddedScene(EmbeddedSceneInterface embeddedScene) {
 761             scenePeer = embeddedScene;
 762             if (scenePeer == null) {
 763                 return;
 764             }
 765             if (pWidth > 0 && pHeight > 0) {
 766                 scenePeer.setSize(pWidth, pHeight);
 767             }
 768 
 769             // DnD-related calls on 'scenePeer' should go from AWT EDT.
 770             SwingUtilities.invokeLater(new Runnable() {
 771 
 772                 @Override
 773                 public void run() {
 774                     if (scenePeer != null) {
 775                         scenePeer.setDragStartListener(dnd.getDragStartListener());
 776                     }
 777                 }
 778             });
 779         }
 780 
 781         @Override
 782         public boolean requestFocus() {
 783             return requestFocusInWindow();
 784         }
 785 
 786         @Override
 787         public boolean traverseFocusOut(boolean forward) {
 788             KeyboardFocusManager kfm = KeyboardFocusManager.getCurrentKeyboardFocusManager();
 789             if (forward) {
 790                 kfm.focusNextComponent(JFXPanel.this);
 791             } else {
 792                 kfm.focusPreviousComponent(JFXPanel.this);
 793             }
 794             return true;
 795         }
 796 
 797         @Override
 798         public void setPreferredSize(final int width, final int height) {
 799             JFXPanel.this.pPreferredWidth = width;
 800             JFXPanel.this.pPreferredHeight = height;
 801             JFXPanel.this.revalidate();
 802         }
 803 
 804         @Override
 805         public void repaint() {
 806             JFXPanel.this.repaint();
 807         }
 808 
 809         @Override
 810         public void setEnabled(final boolean enabled) {
 811             JFXPanel.this.setFxEnabled(enabled);
 812         }
 813 
 814         @Override
 815         public void setCursor(CursorFrame cursorFrame) {
 816             final Cursor cursor = getPlatformCursor(cursorFrame);
 817             SwingUtilities.invokeLater(new Runnable() {
 818                 @Override
 819                 public void run() {
 820                     JFXPanel.this.setCursor(cursor);
 821                 }
 822             });
 823         }
 824 
 825         private Cursor getPlatformCursor(final CursorFrame cursorFrame) {
 826             final Cursor cachedPlatformCursor =
 827                     cursorFrame.getPlatformCursor(Cursor.class);
 828             if (cachedPlatformCursor != null) {
 829                 // platform cursor already cached
 830                 return cachedPlatformCursor;
 831             }
 832 
 833             // platform cursor not cached yet
 834             final Cursor platformCursor =
 835                     SwingCursors.embedCursorToCursor(cursorFrame);
 836             cursorFrame.setPlatforCursor(Cursor.class, platformCursor);
 837 
 838             return platformCursor;
 839         }
 840 
 841         @Override
 842         public boolean grabFocus() {
 843             SwingUtilities.invokeLater(new Runnable() {
 844                 @Override
 845                 public void run() {
 846                     Window window = SwingUtilities.getWindowAncestor(JFXPanel.this);
 847                     if (window != null) {
 848                         if (JFXPanel.this.getToolkit() instanceof SunToolkit) {
 849                             ((SunToolkit)JFXPanel.this.getToolkit()).grab(window);
 850                         }
 851                     }
 852                 }
 853             });
 854 
 855             return true; // Oh, well...
 856         }
 857 
 858         @Override
 859         public void ungrabFocus() {
 860             SwingUtilities.invokeLater(new Runnable() {
 861                 @Override
 862                 public void run() {
 863                     Window window = SwingUtilities.getWindowAncestor(JFXPanel.this);
 864                     if (window != null) {
 865                         if (JFXPanel.this.getToolkit() instanceof SunToolkit) {
 866                             ((SunToolkit)JFXPanel.this.getToolkit()).ungrab(window);
 867                         }
 868                     }
 869                 }
 870             });
 871         }
 872     }
 873 }