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