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