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