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