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