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 * @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 * @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 }