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