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