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