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