1 /* 2 * Copyright (c) 2010, 2019, 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 // The extra simulated mouse pressed event is removed by making the JavaFX scene focused. 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 if(stagePeer != null) { 453 int focusCause = AbstractEvents.FOCUSEVENT_ACTIVATED; 454 stagePeer.setFocused(true, focusCause); 455 } 456 } 457 } 458 459 sendMouseEventToFX(e); 460 super.processMouseEvent(e); 461 } 462 463 /** 464 * Overrides the {@link java.awt.Component#processMouseMotionEvent(MouseEvent)} 465 * method to dispatch the mouse motion event to the JavaFX scene attached to 466 * this {@code JFXPanel}. 467 * 468 * @param e the mouse motion event to dispatch to the JavaFX scene 469 */ 470 @Override 471 protected void processMouseMotionEvent(MouseEvent e) { 472 sendMouseEventToFX(e); 473 super.processMouseMotionEvent(e); 474 } 475 476 /** 477 * Overrides the 478 * {@link java.awt.Component#processMouseWheelEvent(MouseWheelEvent)} 479 * method to dispatch the mouse wheel event to the JavaFX scene attached 480 * to this {@code JFXPanel}. 481 * 482 * @param e the mouse wheel event to dispatch to the JavaFX scene 483 */ 484 @Override 485 protected void processMouseWheelEvent(MouseWheelEvent e) { 486 sendMouseEventToFX(e); 487 super.processMouseWheelEvent(e); 488 } 489 490 private void sendKeyEventToFX(final KeyEvent e) { 491 if (scenePeer == null || !isFxEnabled()) { 492 return; 493 } 494 495 char[] chars = (e.getKeyChar() == KeyEvent.CHAR_UNDEFINED) 496 ? new char[] {} 497 : new char[] { SwingEvents.keyCharToEmbedKeyChar(e.getKeyChar()) }; 498 499 scenePeer.keyEvent( 500 SwingEvents.keyIDToEmbedKeyType(e.getID()), 501 e.getKeyCode(), chars, 502 SwingEvents.keyModifiersToEmbedKeyModifiers(e.getModifiersEx())); 503 } 504 505 /** 506 * Overrides the {@link java.awt.Component#processKeyEvent(KeyEvent)} 507 * method to dispatch the key event to the JavaFX scene attached to this 508 * {@code JFXPanel}. 509 * 510 * @param e the key event to dispatch to the JavaFX scene 511 */ 512 @Override 513 protected void processKeyEvent(KeyEvent e) { 514 sendKeyEventToFX(e); 515 super.processKeyEvent(e); 516 } 517 518 private void sendResizeEventToFX() { 519 if (stagePeer != null) { 520 stagePeer.setSize(pWidth, pHeight); 521 } 522 if (scenePeer != null) { 523 scenePeer.setSize(pWidth, pHeight); 524 } 525 } 526 527 /** 528 * Overrides the 529 * {@link java.awt.Component#processComponentEvent(ComponentEvent)} 530 * method to dispatch {@link java.awt.event.ComponentEvent#COMPONENT_RESIZED} 531 * events to the JavaFX scene attached to this {@code JFXPanel}. The JavaFX 532 * scene object is then resized to match the {@code JFXPanel} size. 533 * 534 * @param e the component event to dispatch to the JavaFX scene 535 */ 536 @Override 537 protected void processComponentEvent(ComponentEvent e) { 538 switch (e.getID()) { 539 case ComponentEvent.COMPONENT_RESIZED: { 540 updateComponentSize(); 541 break; 542 } 543 case ComponentEvent.COMPONENT_MOVED: { 544 if (updateScreenLocation()) { 545 sendMoveEventToFX(); 546 } 547 break; 548 } 549 default: { 550 break; 551 } 552 } 553 super.processComponentEvent(e); 554 } 555 556 // called on EDT only 557 private void updateComponentSize() { 558 int oldWidth = pWidth; 559 int oldHeight = pHeight; 560 // It's quite possible to get negative values here, this is not 561 // what JavaFX embedded scenes/stages are ready to 562 pWidth = Math.max(0, getWidth()); 563 pHeight = Math.max(0, getHeight()); 564 if (getBorder() != null) { 565 Insets i = getBorder().getBorderInsets(this); 566 pWidth -= (i.left + i.right); 567 pHeight -= (i.top + i.bottom); 568 } 569 double newScaleFactorX = scaleFactorX; 570 double newScaleFactorY = scaleFactorY; 571 Graphics g = getGraphics(); 572 newScaleFactorX = GraphicsEnvironment.getLocalGraphicsEnvironment(). 573 getDefaultScreenDevice().getDefaultConfiguration(). 574 getDefaultTransform().getScaleX(); 575 newScaleFactorY = GraphicsEnvironment.getLocalGraphicsEnvironment(). 576 getDefaultScreenDevice().getDefaultConfiguration(). 577 getDefaultTransform().getScaleY(); 578 if (oldWidth != pWidth || oldHeight != pHeight || 579 newScaleFactorX != scaleFactorX || newScaleFactorY != scaleFactorY) 580 { 581 createResizePixelBuffer(newScaleFactorX, newScaleFactorY); 582 if (scenePeer != null) { 583 scenePeer.setPixelScaleFactors((float) newScaleFactorX, 584 (float) newScaleFactorY); 585 } 586 scaleFactorX = newScaleFactorX; 587 scaleFactorY = newScaleFactorY; 588 sendResizeEventToFX(); 589 } 590 } 591 592 // This methods should only be called on EDT 593 private boolean updateScreenLocation() { 594 synchronized (getTreeLock()) { 595 if (isShowing()) { 596 Point p = getLocationOnScreen(); 597 screenX = p.x; 598 screenY = p.y; 599 return true; 600 } 601 } 602 return false; 603 } 604 605 private void sendMoveEventToFX() { 606 if (stagePeer == null) { 607 return; 608 } 609 610 stagePeer.setLocation(screenX, screenY); 611 } 612 613 /** 614 * Overrides the 615 * {@link java.awt.Component#processHierarchyBoundsEvent(HierarchyEvent)} 616 * method to process {@link java.awt.event.HierarchyEvent#ANCESTOR_MOVED} 617 * events and update the JavaFX scene location to match the {@code 618 * JFXPanel} location on the screen. 619 * 620 * @param e the hierarchy bounds event to process 621 */ 622 @Override 623 protected void processHierarchyBoundsEvent(HierarchyEvent e) { 624 if (e.getID() == HierarchyEvent.ANCESTOR_MOVED) { 625 if (updateScreenLocation()) { 626 sendMoveEventToFX(); 627 } 628 } 629 super.processHierarchyBoundsEvent(e); 630 } 631 632 @Override 633 protected void processHierarchyEvent(HierarchyEvent e) { 634 if ((e.getChangeFlags() & HierarchyEvent.SHOWING_CHANGED) != 0) { 635 if (updateScreenLocation()) { 636 sendMoveEventToFX(); 637 } 638 } 639 super.processHierarchyEvent(e); 640 } 641 642 private void sendFocusEventToFX(final FocusEvent e) { 643 if ((stage == null) || (stagePeer == null) || !isFxEnabled()) { 644 return; 645 } 646 647 boolean focused = (e.getID() == FocusEvent.FOCUS_GAINED); 648 int focusCause = (focused ? AbstractEvents.FOCUSEVENT_ACTIVATED : 649 AbstractEvents.FOCUSEVENT_DEACTIVATED); 650 651 if (focused) { 652 if (e.getCause() == FocusEvent.Cause.TRAVERSAL_FORWARD) { 653 focusCause = AbstractEvents.FOCUSEVENT_TRAVERSED_FORWARD; 654 } else if (e.getCause() == FocusEvent.Cause.TRAVERSAL_BACKWARD) { 655 focusCause = AbstractEvents.FOCUSEVENT_TRAVERSED_BACKWARD; 656 } 657 } 658 stagePeer.setFocused(focused, focusCause); 659 } 660 661 /** 662 * Overrides the 663 * {@link java.awt.Component#processFocusEvent(FocusEvent)} 664 * method to dispatch focus events to the JavaFX scene attached to this 665 * {@code JFXPanel}. 666 * 667 * @param e the focus event to dispatch to the JavaFX scene 668 */ 669 @Override 670 protected void processFocusEvent(FocusEvent e) { 671 sendFocusEventToFX(e); 672 super.processFocusEvent(e); 673 } 674 675 // called on EDT only 676 private void createResizePixelBuffer(double newScaleFactorX, double newScaleFactorY) { 677 if (scenePeer == null || pWidth <= 0 || pHeight <= 0) { 678 pixelsIm = null; 679 } else { 680 BufferedImage oldIm = pixelsIm; 681 int newPixelW = (int) Math.ceil(pWidth * newScaleFactorX); 682 int newPixelH = (int) Math.ceil(pHeight * newScaleFactorY); 683 pixelsIm = new BufferedImage(newPixelW, newPixelH, 684 SwingFXUtils.getBestBufferedImageType( 685 scenePeer.getPixelFormat(), null, false)); 686 if (oldIm != null) { 687 double ratioX = newScaleFactorX / scaleFactorX; 688 double ratioY = newScaleFactorY / scaleFactorY; 689 // Transform old size to the new coordinate space. 690 int oldW = (int)Math.round(oldIm.getWidth() * ratioX); 691 int oldH = (int)Math.round(oldIm.getHeight() * ratioY); 692 693 Graphics g = pixelsIm.getGraphics(); 694 try { 695 g.drawImage(oldIm, 0, 0, oldW, oldH, null); 696 } finally { 697 g.dispose(); 698 } 699 } 700 } 701 } 702 703 @Override 704 protected void processInputMethodEvent(InputMethodEvent e) { 705 if (e.getID() == InputMethodEvent.INPUT_METHOD_TEXT_CHANGED) { 706 sendInputMethodEventToFX(e); 707 } 708 super.processInputMethodEvent(e); 709 } 710 711 private void sendInputMethodEventToFX(InputMethodEvent e) { 712 String t = InputMethodSupport.getTextForEvent(e); 713 714 int insertionIndex = 0; 715 if (e.getCaret() != null) { 716 insertionIndex = e.getCaret().getInsertionIndex(); 717 } 718 scenePeer.inputMethodEvent( 719 javafx.scene.input.InputMethodEvent.INPUT_METHOD_TEXT_CHANGED, 720 InputMethodSupport.inputMethodEventComposed(t, e.getCommittedCharacterCount()), 721 t.substring(0, e.getCommittedCharacterCount()), 722 insertionIndex); 723 } 724 725 /** 726 * Overrides the {@link javax.swing.JComponent#paintComponent(Graphics)} 727 * method to paint the content of the JavaFX scene attached to this 728 * {@code JFXpanel}. 729 * 730 * @param g the Graphics context in which to paint 731 * 732 * @see #isOpaque() 733 */ 734 @Override 735 protected void paintComponent(Graphics g) { 736 if (scenePeer == null) { 737 return; 738 } 739 if (pixelsIm == null) { 740 createResizePixelBuffer(scaleFactorX, scaleFactorY); 741 if (pixelsIm == null) { 742 return; 743 } 744 } 745 DataBufferInt dataBuf = (DataBufferInt)pixelsIm.getRaster().getDataBuffer(); 746 int[] pixelsData = dataBuf.getData(); 747 IntBuffer buf = IntBuffer.wrap(pixelsData); 748 if (!scenePeer.getPixels(buf, pWidth, pHeight)) { 749 // In this case we just render what we have so far in the buffer. 750 } 751 752 Graphics gg = null; 753 try { 754 gg = g.create(); 755 if ((opacity < 1.0f) && (gg instanceof Graphics2D)) { 756 Graphics2D g2d = (Graphics2D)gg; 757 AlphaComposite c = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, opacity); 758 g2d.setComposite(c); 759 } 760 if (getBorder() != null) { 761 Insets i = getBorder().getBorderInsets(this); 762 gg.translate(i.left, i.top); 763 } 764 gg.drawImage(pixelsIm, 0, 0, pWidth, pHeight, null); 765 766 double newScaleFactorX = scaleFactorX; 767 double newScaleFactorY = scaleFactorY; 768 newScaleFactorX = GraphicsEnvironment.getLocalGraphicsEnvironment(). 769 getDefaultScreenDevice().getDefaultConfiguration(). 770 getDefaultTransform().getScaleX(); 771 newScaleFactorY = GraphicsEnvironment.getLocalGraphicsEnvironment(). 772 getDefaultScreenDevice().getDefaultConfiguration(). 773 getDefaultTransform().getScaleY(); 774 if (scaleFactorX != newScaleFactorX || scaleFactorY != newScaleFactorY) { 775 createResizePixelBuffer(newScaleFactorX, newScaleFactorY); 776 // The scene will request repaint. 777 scenePeer.setPixelScaleFactors((float) newScaleFactorX, 778 (float) newScaleFactorY); 779 scaleFactorX = newScaleFactorX; 780 scaleFactorY = newScaleFactorY; 781 } 782 } catch (Throwable th) { 783 th.printStackTrace(); 784 } finally { 785 if (gg != null) { 786 gg.dispose(); 787 } 788 } 789 } 790 791 /** 792 * Returns the preferred size of this {@code JFXPanel}, either 793 * previously set with {@link #setPreferredSize(Dimension)} or 794 * based on the content of the JavaFX scene attached to this {@code 795 * JFXPanel}. 796 * 797 * @return prefSize this {@code JFXPanel} preferred size 798 */ 799 @Override 800 public Dimension getPreferredSize() { 801 if (isPreferredSizeSet() || scenePeer == null) { 802 return super.getPreferredSize(); 803 } 804 return new Dimension(pPreferredWidth, pPreferredHeight); 805 } 806 807 private boolean isFxEnabled() { 808 return this.disableCount.get() == 0; 809 } 810 811 private void setFxEnabled(boolean enabled) { 812 if (!enabled) { 813 if (disableCount.incrementAndGet() == 1) { 814 if (dnd != null) { 815 dnd.removeNotify(); 816 } 817 } 818 } else { 819 if (disableCount.get() == 0) { 820 //should report a warning about an extra enable call ? 821 return; 822 } 823 if (disableCount.decrementAndGet() == 0) { 824 if (dnd != null) { 825 dnd.addNotify(); 826 } 827 } 828 } 829 } 830 831 private transient AWTEventListener ungrabListener = event -> { 832 if (jfxPanelIOP.isUngrabEvent(event)) { 833 SwingNodeHelper.runOnFxThread(() -> { 834 if (JFXPanel.this.stagePeer != null && 835 getScene() != null && 836 getScene().getFocusOwner() != null && 837 getScene().getFocusOwner().isFocused()) { 838 JFXPanel.this.stagePeer.focusUngrab(); 839 } 840 }); 841 } 842 if (event instanceof MouseEvent) { 843 // Synthesize FOCUS_UNGRAB if user clicks the AWT top-level window 844 // that contains the JFXPanel. 845 if (event.getID() == MouseEvent.MOUSE_PRESSED && event.getSource() instanceof Component) { 846 final Window jfxPanelWindow = SwingUtilities.getWindowAncestor(JFXPanel.this); 847 final Component source = (Component)event.getSource(); 848 final Window eventWindow = source instanceof Window ? (Window)source : SwingUtilities.getWindowAncestor(source); 849 850 if (jfxPanelWindow == eventWindow) { 851 SwingNodeHelper.runOnFxThread(() -> { 852 if (JFXPanel.this.stagePeer != null) { 853 // No need to check if grab is active or not. 854 // NoAutoHide popups don't request the grab and 855 // ignore the Ungrab event anyway. 856 // AutoHide popups actually should be hidden when 857 // user clicks some non-FX content, even if for 858 // some reason they didn't install the grab when 859 // they were shown. 860 JFXPanel.this.stagePeer.focusUngrab(); 861 } 862 }); 863 } 864 } 865 } 866 }; 867 868 /** 869 * Notifies this component that it now has a parent component. When this 870 * method is invoked, the chain of parent components is set up with 871 * KeyboardAction event listeners. 872 */ 873 @Override 874 public void addNotify() { 875 super.addNotify(); 876 877 registerFinishListener(); 878 879 AccessController.doPrivileged((PrivilegedAction<Void>) () -> { 880 JFXPanel.this.getToolkit().addAWTEventListener(ungrabListener, 881 jfxPanelIOP.getMask()); 882 return null; 883 }); 884 updateComponentSize(); // see RT-23603 885 SwingNodeHelper.runOnFxThread(() -> { 886 if ((stage != null) && !stage.isShowing()) { 887 stage.show(); 888 sendMoveEventToFX(); 889 } 890 }); 891 } 892 893 @Override 894 public InputMethodRequests getInputMethodRequests() { 895 EmbeddedSceneInterface scene = scenePeer; 896 if (scene == null) { 897 return null; 898 } 899 return new InputMethodSupport.InputMethodRequestsAdapter(scene.getInputMethodRequests()); 900 } 901 902 /** 903 * Notifies this component that it no longer has a parent component. 904 * When this method is invoked, any KeyboardActions set up in the the 905 * chain of parent components are removed. 906 */ 907 @Override public void removeNotify() { 908 SwingNodeHelper.runOnFxThread(() -> { 909 if ((stage != null) && stage.isShowing()) { 910 stage.hide(); 911 } 912 }); 913 914 pixelsIm = null; 915 pWidth = 0; 916 pHeight = 0; 917 918 super.removeNotify(); 919 920 AccessController.doPrivileged((PrivilegedAction<Void>) () -> { 921 JFXPanel.this.getToolkit().removeAWTEventListener(ungrabListener); 922 return null; 923 }); 924 925 /* see CR 4867453 */ 926 getInputContext().removeNotify(this); 927 928 deregisterFinishListener(); 929 } 930 931 private void invokeOnClientEDT(Runnable r) { 932 jfxPanelIOP.postEvent(this, new InvocationEvent(this, r)); 933 } 934 935 private class HostContainer implements HostInterface { 936 937 @Override 938 public void setEmbeddedStage(EmbeddedStageInterface embeddedStage) { 939 stagePeer = embeddedStage; 940 if (stagePeer == null) { 941 return; 942 } 943 if (pWidth > 0 && pHeight > 0) { 944 stagePeer.setSize(pWidth, pHeight); 945 } 946 invokeOnClientEDT(() -> { 947 if (stagePeer != null && JFXPanel.this.isFocusOwner()) { 948 stagePeer.setFocused(true, AbstractEvents.FOCUSEVENT_ACTIVATED); 949 } 950 }); 951 sendMoveEventToFX(); 952 } 953 954 @Override 955 public void setEmbeddedScene(EmbeddedSceneInterface embeddedScene) { 956 if (scenePeer == embeddedScene) { 957 return; 958 } 959 scenePeer = embeddedScene; 960 if (scenePeer == null) { 961 invokeOnClientEDT(() -> { 962 if (dnd != null) { 963 dnd.removeNotify(); 964 dnd = null; 965 } 966 }); 967 return; 968 } 969 if (pWidth > 0 && pHeight > 0) { 970 scenePeer.setSize(pWidth, pHeight); 971 } 972 scenePeer.setPixelScaleFactors((float) scaleFactorX, (float) scaleFactorY); 973 974 invokeOnClientEDT(() -> { 975 dnd = new SwingDnD(JFXPanel.this, scenePeer); 976 dnd.addNotify(); 977 if (scenePeer != null) { 978 scenePeer.setDragStartListener(dnd.getDragStartListener()); 979 } 980 }); 981 } 982 983 @Override 984 public boolean requestFocus() { 985 return requestFocusInWindow(); 986 } 987 988 @Override 989 public boolean traverseFocusOut(boolean forward) { 990 KeyboardFocusManager kfm = KeyboardFocusManager.getCurrentKeyboardFocusManager(); 991 if (forward) { 992 kfm.focusNextComponent(JFXPanel.this); 993 } else { 994 kfm.focusPreviousComponent(JFXPanel.this); 995 } 996 return true; 997 } 998 999 @Override 1000 public void setPreferredSize(final int width, final int height) { 1001 invokeOnClientEDT(() -> { 1002 JFXPanel.this.pPreferredWidth = width; 1003 JFXPanel.this.pPreferredHeight = height; 1004 JFXPanel.this.revalidate(); 1005 }); 1006 } 1007 1008 @Override 1009 public void repaint() { 1010 invokeOnClientEDT(() -> { 1011 JFXPanel.this.repaint(); 1012 }); 1013 } 1014 1015 @Override 1016 public void setEnabled(final boolean enabled) { 1017 JFXPanel.this.setFxEnabled(enabled); 1018 } 1019 1020 @Override 1021 public void setCursor(CursorFrame cursorFrame) { 1022 final Cursor cursor = getPlatformCursor(cursorFrame); 1023 invokeOnClientEDT(() -> { 1024 JFXPanel.this.setCursor(cursor); 1025 }); 1026 } 1027 1028 private Cursor getPlatformCursor(final CursorFrame cursorFrame) { 1029 final Cursor cachedPlatformCursor = 1030 cursorFrame.getPlatformCursor(Cursor.class); 1031 if (cachedPlatformCursor != null) { 1032 // platform cursor already cached 1033 return cachedPlatformCursor; 1034 } 1035 1036 // platform cursor not cached yet 1037 final Cursor platformCursor = 1038 SwingCursors.embedCursorToCursor(cursorFrame); 1039 cursorFrame.setPlatforCursor(Cursor.class, platformCursor); 1040 1041 return platformCursor; 1042 } 1043 1044 @Override 1045 public boolean grabFocus() { 1046 // On X11 grab is limited to a single XDisplay connection, 1047 // so we can't delegate it to another GUI toolkit. 1048 if (PlatformUtil.isLinux()) return true; 1049 1050 invokeOnClientEDT(() -> { 1051 Window window = SwingUtilities.getWindowAncestor(JFXPanel.this); 1052 if (window != null) { 1053 jfxPanelIOP.grab(JFXPanel.this.getToolkit(), window); 1054 } 1055 }); 1056 1057 return true; // Oh, well... 1058 } 1059 1060 @Override 1061 public void ungrabFocus() { 1062 // On X11 grab is limited to a single XDisplay connection, 1063 // so we can't delegate it to another GUI toolkit. 1064 if (PlatformUtil.isLinux()) return; 1065 1066 invokeOnClientEDT(() -> { 1067 Window window = SwingUtilities.getWindowAncestor(JFXPanel.this); 1068 if (window != null) { 1069 jfxPanelIOP.ungrab(JFXPanel.this.getToolkit(), window); 1070 } 1071 }); 1072 } 1073 } 1074 }