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