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