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