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