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