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