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