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 e.getX(), e.getY(), e.getXOnScreen(), e.getYOnScreen(), 377 (extModifiers & MouseEvent.SHIFT_DOWN_MASK) != 0, 378 (extModifiers & MouseEvent.CTRL_DOWN_MASK) != 0, 379 (extModifiers & MouseEvent.ALT_DOWN_MASK) != 0, 380 (extModifiers & MouseEvent.META_DOWN_MASK) != 0); 381 } else { 382 scenePeer.mouseEvent( 383 SwingEvents.mouseIDToEmbedMouseType(e.getID()), 384 SwingEvents.mouseButtonToEmbedMouseButton(e.getButton(), extModifiers), 385 primaryBtnDown, middleBtnDown, secondaryBtnDown, 386 e.getX(), e.getY(), e.getXOnScreen(), e.getYOnScreen(), 387 (extModifiers & MouseEvent.SHIFT_DOWN_MASK) != 0, 388 (extModifiers & MouseEvent.CTRL_DOWN_MASK) != 0, 389 (extModifiers & MouseEvent.ALT_DOWN_MASK) != 0, 390 (extModifiers & MouseEvent.META_DOWN_MASK) != 0, 391 popupTrigger); 392 } 393 if (e.isPopupTrigger()) { 394 scenePeer.menuEvent(e.getX(), e.getY(), e.getXOnScreen(), e.getYOnScreen(), false); 395 } 396 } 397 398 /** 399 * Overrides the {@link java.awt.Component#processMouseEvent(MouseEvent)} 400 * method to dispatch the mouse event to the JavaFX scene attached to this 401 * {@code JFXPanel}. 402 * 403 * @param e the mouse event to dispatch to the JavaFX scene 404 */ 405 @Override 406 protected void processMouseEvent(MouseEvent e) { 407 if ((e.getID() == MouseEvent.MOUSE_PRESSED) && 408 (e.getButton() == MouseEvent.BUTTON1)) { 409 if (!hasFocus()) { 410 requestFocus(); 411 } 412 } 413 414 sendMouseEventToFX(e); 415 super.processMouseEvent(e); 416 } 417 418 /** 419 * Overrides the {@link java.awt.Component#processMouseMotionEvent(MouseEvent)} 420 * method to dispatch the mouse motion event to the JavaFX scene attached to 421 * this {@code JFXPanel}. 422 * 423 * @param e the mouse motion event to dispatch to the JavaFX scene 424 */ 425 @Override 426 protected void processMouseMotionEvent(MouseEvent e) { 427 sendMouseEventToFX(e); 428 super.processMouseMotionEvent(e); 429 } 430 431 /** 432 * Overrides the 433 * {@link java.awt.Component#processMouseWheelEvent(MouseWheelEvent)} 434 * method to dispatch the mouse wheel event to the JavaFX scene attached 435 * to this {@code JFXPanel}. 436 * 437 * @param e the mouse wheel event to dispatch to the JavaFX scene 438 */ 439 @Override 440 protected void processMouseWheelEvent(MouseWheelEvent e) { 441 sendMouseEventToFX(e); 442 super.processMouseWheelEvent(e); 443 } 444 445 private void sendKeyEventToFX(final KeyEvent e) { 446 if (scenePeer == null || !isFxEnabled()) { 447 return; 448 } 449 450 char[] chars = (e.getKeyChar() == KeyEvent.CHAR_UNDEFINED) 451 ? new char[] {} 452 : new char[] { SwingEvents.keyCharToEmbedKeyChar(e.getKeyChar()) }; 453 454 scenePeer.keyEvent( 455 SwingEvents.keyIDToEmbedKeyType(e.getID()), 456 e.getKeyCode(), chars, 457 SwingEvents.keyModifiersToEmbedKeyModifiers(e.getModifiersEx())); 458 } 459 460 /** 461 * Overrides the {@link java.awt.Component#processKeyEvent(KeyEvent)} 462 * method to dispatch the key event to the JavaFX scene attached to this 463 * {@code JFXPanel}. 464 * 465 * @param e the key event to dispatch to the JavaFX scene 466 */ 467 @Override 468 protected void processKeyEvent(KeyEvent e) { 469 sendKeyEventToFX(e); 470 super.processKeyEvent(e); 471 } 472 473 private void sendResizeEventToFX() { 474 if (stagePeer != null) { 475 stagePeer.setSize(pWidth, pHeight); 476 } 477 if (scenePeer != null) { 478 scenePeer.setSize(pWidth, pHeight); 479 } 480 } 481 482 /** 483 * Overrides the 484 * {@link java.awt.Component#processComponentEvent(ComponentEvent)} 485 * method to dispatch {@link java.awt.event.ComponentEvent#COMPONENT_RESIZED} 486 * events to the JavaFX scene attached to this {@code JFXPanel}. The JavaFX 487 * scene object is then resized to match the {@code JFXPanel} size. 488 * 489 * @param e the component event to dispatch to the JavaFX scene 490 */ 491 @Override 492 protected void processComponentEvent(ComponentEvent e) { 493 switch (e.getID()) { 494 case ComponentEvent.COMPONENT_RESIZED: { 495 updateComponentSize(); 496 break; 497 } 498 case ComponentEvent.COMPONENT_MOVED: { 499 if (updateScreenLocation()) { 500 sendMoveEventToFX(); 501 } 502 break; 503 } 504 default: { 505 break; 506 } 507 } 508 super.processComponentEvent(e); 509 } 510 511 // called on EDT only 512 private void updateComponentSize() { 513 int oldWidth = pWidth; 514 int oldHeight = pHeight; 515 // It's quite possible to get negative values here, this is not 516 // what JavaFX embedded scenes/stages are ready to 517 pWidth = Math.max(0, getWidth()); 518 pHeight = Math.max(0, getHeight()); 519 if (getBorder() != null) { 520 Insets i = getBorder().getBorderInsets(this); 521 pWidth -= (i.left + i.right); 522 pHeight -= (i.top + i.bottom); 523 } 524 double newScaleFactorX = scaleFactorX; 525 double newScaleFactorY = scaleFactorY; 526 Graphics g = getGraphics(); 527 if (g instanceof SunGraphics2D) { 528 SurfaceData sd = ((SunGraphics2D) g).surfaceData; 529 newScaleFactorX = sd.getDefaultScaleX(); 530 newScaleFactorY = sd.getDefaultScaleY(); 531 } 532 if (oldWidth != pWidth || oldHeight != pHeight || 533 newScaleFactorX != scaleFactorX || newScaleFactorY != scaleFactorY) 534 { 535 createResizePixelBuffer(newScaleFactorX, newScaleFactorY); 536 if (scenePeer != null) { 537 scenePeer.setPixelScaleFactors((float) newScaleFactorX, 538 (float) newScaleFactorY); 539 } 540 scaleFactorX = newScaleFactorX; 541 scaleFactorY = newScaleFactorY; 542 sendResizeEventToFX(); 543 } 544 } 545 546 // This methods should only be called on EDT 547 private boolean updateScreenLocation() { 548 synchronized (getTreeLock()) { 549 if (isShowing()) { 550 Point p = getLocationOnScreen(); 551 screenX = p.x; 552 screenY = p.y; 553 return true; 554 } 555 } 556 return false; 557 } 558 559 private void sendMoveEventToFX() { 560 if (stagePeer == null) { 561 return; 562 } 563 564 stagePeer.setLocation(screenX, screenY); 565 } 566 567 /** 568 * Overrides the 569 * {@link java.awt.Component#processHierarchyBoundsEvent(HierarchyEvent)} 570 * method to process {@link java.awt.event.HierarchyEvent#ANCESTOR_MOVED} 571 * events and update the JavaFX scene location to match the {@code 572 * JFXPanel} location on the screen. 573 * 574 * @param e the hierarchy bounds event to process 575 */ 576 @Override 577 protected void processHierarchyBoundsEvent(HierarchyEvent e) { 578 if (e.getID() == HierarchyEvent.ANCESTOR_MOVED) { 579 if (updateScreenLocation()) { 580 sendMoveEventToFX(); 581 } 582 } 583 super.processHierarchyBoundsEvent(e); 584 } 585 586 @Override 587 protected void processHierarchyEvent(HierarchyEvent e) { 588 if ((e.getChangeFlags() & HierarchyEvent.SHOWING_CHANGED) != 0) { 589 if (updateScreenLocation()) { 590 sendMoveEventToFX(); 591 } 592 } 593 super.processHierarchyEvent(e); 594 } 595 596 private void sendFocusEventToFX(final FocusEvent e) { 597 if ((stage == null) || (stagePeer == null) || !isFxEnabled()) { 598 return; 599 } 600 601 boolean focused = (e.getID() == FocusEvent.FOCUS_GAINED); 602 int focusCause = (focused ? AbstractEvents.FOCUSEVENT_ACTIVATED : 603 AbstractEvents.FOCUSEVENT_DEACTIVATED); 604 605 // FIXME: JDK-8156592 -- replace this with FocusEvent.getCause() 606 // which is now public API, once we update to using a newer JDK. 607 // NOTE: the focusCause is unused at present. 608 /* 609 if (focused && (e instanceof CausedFocusEvent)) { 610 CausedFocusEvent ce = (CausedFocusEvent) e; 611 if (ce.getCause() == CausedFocusEvent.Cause.TRAVERSAL_FORWARD) { 612 focusCause = AbstractEvents.FOCUSEVENT_TRAVERSED_FORWARD; 613 } else if (ce.getCause() == sun.awt.CausedFocusEvent.Cause.TRAVERSAL_BACKWARD) { 614 focusCause = AbstractEvents.FOCUSEVENT_TRAVERSED_BACKWARD; 615 } 616 } 617 */ 618 stagePeer.setFocused(focused, focusCause); 619 } 620 621 /** 622 * Overrides the 623 * {@link java.awt.Component#processFocusEvent(FocusEvent)} 624 * method to dispatch focus events to the JavaFX scene attached to this 625 * {@code JFXPanel}. 626 * 627 * @param e the focus event to dispatch to the JavaFX scene 628 */ 629 @Override 630 protected void processFocusEvent(FocusEvent e) { 631 sendFocusEventToFX(e); 632 super.processFocusEvent(e); 633 } 634 635 // called on EDT only 636 private void createResizePixelBuffer(double newScaleFactorX, double newScaleFactorY) { 637 if (scenePeer == null || pWidth <= 0 || pHeight <= 0) { 638 pixelsIm = null; 639 } else { 640 BufferedImage oldIm = pixelsIm; 641 int newPixelW = (int) Math.ceil(pWidth * newScaleFactorX); 642 int newPixelH = (int) Math.ceil(pHeight * newScaleFactorY); 643 pixelsIm = new BufferedImage(newPixelW, newPixelH, 644 SwingFXUtils.getBestBufferedImageType( 645 scenePeer.getPixelFormat(), null)); 646 if (oldIm != null) { 647 double ratioX = newScaleFactorX / scaleFactorX; 648 double ratioY = newScaleFactorY / scaleFactorY; 649 // Transform old size to the new coordinate space. 650 int oldW = (int)Math.round(oldIm.getWidth() * ratioX); 651 int oldH = (int)Math.round(oldIm.getHeight() * ratioY); 652 653 Graphics g = pixelsIm.getGraphics(); 654 try { 655 g.drawImage(oldIm, 0, 0, oldW, oldH, null); 656 } finally { 657 g.dispose(); 658 } 659 } 660 } 661 } 662 663 @Override 664 protected void processInputMethodEvent(InputMethodEvent e) { 665 if (e.getID() == InputMethodEvent.INPUT_METHOD_TEXT_CHANGED) { 666 sendInputMethodEventToFX(e); 667 } 668 super.processInputMethodEvent(e); 669 } 670 671 private void sendInputMethodEventToFX(InputMethodEvent e) { 672 String t = InputMethodSupport.getTextForEvent(e); 673 674 scenePeer.inputMethodEvent( 675 javafx.scene.input.InputMethodEvent.INPUT_METHOD_TEXT_CHANGED, 676 InputMethodSupport.inputMethodEventComposed(t, e.getCommittedCharacterCount()), 677 t.substring(0, e.getCommittedCharacterCount()), 678 e.getCaret().getInsertionIndex()); 679 } 680 681 /** 682 * Overrides the {@link javax.swing.JComponent#paintComponent(Graphics)} 683 * method to paint the content of the JavaFX scene attached to this 684 * {@code JFXpanel}. 685 * 686 * @param g the Graphics context in which to paint 687 * 688 * @see #isOpaque() 689 */ 690 @Override 691 protected void paintComponent(Graphics g) { 692 if (scenePeer == null) { 693 return; 694 } 695 if (pixelsIm == null) { 696 createResizePixelBuffer(scaleFactorX, scaleFactorY); 697 if (pixelsIm == null) { 698 return; 699 } 700 } 701 DataBufferInt dataBuf = (DataBufferInt)pixelsIm.getRaster().getDataBuffer(); 702 int[] pixelsData = dataBuf.getData(); 703 IntBuffer buf = IntBuffer.wrap(pixelsData); 704 if (!scenePeer.getPixels(buf, pWidth, pHeight)) { 705 // In this case we just render what we have so far in the buffer. 706 } 707 708 Graphics gg = null; 709 try { 710 gg = g.create(); 711 if ((opacity < 1.0f) && (gg instanceof Graphics2D)) { 712 Graphics2D g2d = (Graphics2D)gg; 713 AlphaComposite c = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, opacity); 714 g2d.setComposite(c); 715 } 716 if (getBorder() != null) { 717 Insets i = getBorder().getBorderInsets(this); 718 gg.translate(i.left, i.top); 719 } 720 gg.drawImage(pixelsIm, 0, 0, pWidth, pHeight, null); 721 722 double newScaleFactorX = scaleFactorX; 723 double newScaleFactorY = scaleFactorY; 724 if (g instanceof SunGraphics2D) { 725 SurfaceData sd = ((SunGraphics2D)g).surfaceData; 726 newScaleFactorX = sd.getDefaultScaleX(); 727 newScaleFactorY = sd.getDefaultScaleY(); 728 } 729 if (scaleFactorX != newScaleFactorX || scaleFactorY != newScaleFactorY) { 730 createResizePixelBuffer(newScaleFactorX, newScaleFactorY); 731 // The scene will request repaint. 732 scenePeer.setPixelScaleFactors((float) newScaleFactorX, 733 (float) newScaleFactorY); 734 scaleFactorX = newScaleFactorX; 735 scaleFactorY = newScaleFactorY; 736 } 737 } catch (Throwable th) { 738 th.printStackTrace(); 739 } finally { 740 if (gg != null) { 741 gg.dispose(); 742 } 743 } 744 } 745 746 /** 747 * Returns the preferred size of this {@code JFXPanel}, either 748 * previously set with {@link #setPreferredSize(Dimension)} or 749 * based on the content of the JavaFX scene attached to this {@code 750 * JFXPanel}. 751 * 752 * @return prefSize this {@code JFXPanel} preferred size 753 */ 754 @Override 755 public Dimension getPreferredSize() { 756 if (isPreferredSizeSet() || scenePeer == null) { 757 return super.getPreferredSize(); 758 } 759 return new Dimension(pPreferredWidth, pPreferredHeight); 760 } 761 762 private boolean isFxEnabled() { 763 return this.disableCount.get() == 0; 764 } 765 766 private void setFxEnabled(boolean enabled) { 767 if (!enabled) { 768 if (disableCount.incrementAndGet() == 1) { 769 if (dnd != null) { 770 dnd.removeNotify(); 771 } 772 } 773 } else { 774 if (disableCount.get() == 0) { 775 //should report a warning about an extra enable call ? 776 return; 777 } 778 if (disableCount.decrementAndGet() == 0) { 779 if (dnd != null) { 780 dnd.addNotify(); 781 } 782 } 783 } 784 } 785 786 private final AWTEventListener ungrabListener = event -> { 787 if (event instanceof sun.awt.UngrabEvent) { 788 SwingFXUtils.runOnFxThread(() -> { 789 if (JFXPanel.this.stagePeer != null) { 790 JFXPanel.this.stagePeer.focusUngrab(); 791 } 792 }); 793 } 794 if (event instanceof MouseEvent) { 795 // Synthesize FOCUS_UNGRAB if user clicks the AWT top-level window 796 // that contains the JFXPanel. 797 if (event.getID() == MouseEvent.MOUSE_PRESSED && event.getSource() instanceof Component) { 798 final Window jfxPanelWindow = SwingUtilities.getWindowAncestor(JFXPanel.this); 799 final Component source = (Component)event.getSource(); 800 final Window eventWindow = source instanceof Window ? (Window)source : SwingUtilities.getWindowAncestor(source); 801 802 if (jfxPanelWindow == eventWindow) { 803 SwingFXUtils.runOnFxThread(() -> { 804 if (JFXPanel.this.stagePeer != null) { 805 // No need to check if grab is active or not. 806 // NoAutoHide popups don't request the grab and 807 // ignore the Ungrab event anyway. 808 // AutoHide popups actually should be hidden when 809 // user clicks some non-FX content, even if for 810 // some reason they didn't install the grab when 811 // they were shown. 812 JFXPanel.this.stagePeer.focusUngrab(); 813 } 814 }); 815 } 816 } 817 } 818 }; 819 820 /** 821 * Notifies this component that it now has a parent component. When this 822 * method is invoked, the chain of parent components is set up with 823 * KeyboardAction event listeners. 824 */ 825 @Override 826 public void addNotify() { 827 super.addNotify(); 828 829 registerFinishListener(); 830 831 AccessController.doPrivileged((PrivilegedAction<Void>) () -> { 832 JFXPanel.this.getToolkit().addAWTEventListener(ungrabListener, 833 SunToolkit.GRAB_EVENT_MASK | AWTEvent.MOUSE_EVENT_MASK); 834 return null; 835 }); 836 updateComponentSize(); // see RT-23603 837 SwingFXUtils.runOnFxThread(() -> { 838 if ((stage != null) && !stage.isShowing()) { 839 stage.show(); 840 sendMoveEventToFX(); 841 } 842 }); 843 } 844 845 @Override 846 public InputMethodRequests getInputMethodRequests() { 847 if (scenePeer == null) { 848 return null; 849 } 850 return new InputMethodSupport.InputMethodRequestsAdapter(scenePeer.getInputMethodRequests()); 851 } 852 853 /** 854 * Notifies this component that it no longer has a parent component. 855 * When this method is invoked, any KeyboardActions set up in the the 856 * chain of parent components are removed. 857 */ 858 @Override public void removeNotify() { 859 SwingFXUtils.runOnFxThread(() -> { 860 if ((stage != null) && stage.isShowing()) { 861 stage.hide(); 862 } 863 }); 864 865 pixelsIm = null; 866 pWidth = 0; 867 pHeight = 0; 868 869 super.removeNotify(); 870 871 AccessController.doPrivileged((PrivilegedAction<Void>) () -> { 872 JFXPanel.this.getToolkit().removeAWTEventListener(ungrabListener); 873 return null; 874 }); 875 876 /* see CR 4867453 */ 877 getInputContext().removeNotify(this); 878 879 deregisterFinishListener(); 880 } 881 882 private void invokeOnClientEDT(Runnable r) { 883 AppContext context = SunToolkit.targetToAppContext(this); 884 if (context == null) { 885 if (log.isLoggable(Level.FINE)) log.fine("null AppContext encountered!"); 886 return; 887 } 888 SunToolkit.postEvent(context, new InvocationEvent(this, r)); 889 } 890 891 private class HostContainer implements HostInterface { 892 893 @Override 894 public void setEmbeddedStage(EmbeddedStageInterface embeddedStage) { 895 stagePeer = embeddedStage; 896 if (stagePeer == null) { 897 return; 898 } 899 if (pWidth > 0 && pHeight > 0) { 900 stagePeer.setSize(pWidth, pHeight); 901 } 902 invokeOnClientEDT(() -> { 903 if (JFXPanel.this.isFocusOwner()) { 904 stagePeer.setFocused(true, AbstractEvents.FOCUSEVENT_ACTIVATED); 905 } 906 }); 907 sendMoveEventToFX(); 908 } 909 910 @Override 911 public void setEmbeddedScene(EmbeddedSceneInterface embeddedScene) { 912 if (scenePeer == embeddedScene) { 913 return; 914 } 915 scenePeer = embeddedScene; 916 if (scenePeer == null) { 917 invokeOnClientEDT(() -> { 918 if (dnd != null) { 919 dnd.removeNotify(); 920 dnd = null; 921 } 922 }); 923 return; 924 } 925 if (pWidth > 0 && pHeight > 0) { 926 scenePeer.setSize(pWidth, pHeight); 927 } 928 scenePeer.setPixelScaleFactors((float) scaleFactorX, (float) scaleFactorY); 929 930 invokeOnClientEDT(() -> { 931 dnd = new SwingDnD(JFXPanel.this, scenePeer); 932 dnd.addNotify(); 933 if (scenePeer != null) { 934 scenePeer.setDragStartListener(dnd.getDragStartListener()); 935 } 936 }); 937 } 938 939 @Override 940 public boolean requestFocus() { 941 return requestFocusInWindow(); 942 } 943 944 @Override 945 public boolean traverseFocusOut(boolean forward) { 946 KeyboardFocusManager kfm = KeyboardFocusManager.getCurrentKeyboardFocusManager(); 947 if (forward) { 948 kfm.focusNextComponent(JFXPanel.this); 949 } else { 950 kfm.focusPreviousComponent(JFXPanel.this); 951 } 952 return true; 953 } 954 955 @Override 956 public void setPreferredSize(final int width, final int height) { 957 invokeOnClientEDT(() -> { 958 JFXPanel.this.pPreferredWidth = width; 959 JFXPanel.this.pPreferredHeight = height; 960 JFXPanel.this.revalidate(); 961 }); 962 } 963 964 @Override 965 public void repaint() { 966 invokeOnClientEDT(() -> { 967 JFXPanel.this.repaint(); 968 }); 969 } 970 971 @Override 972 public void setEnabled(final boolean enabled) { 973 JFXPanel.this.setFxEnabled(enabled); 974 } 975 976 @Override 977 public void setCursor(CursorFrame cursorFrame) { 978 final Cursor cursor = getPlatformCursor(cursorFrame); 979 invokeOnClientEDT(() -> { 980 JFXPanel.this.setCursor(cursor); 981 }); 982 } 983 984 private Cursor getPlatformCursor(final CursorFrame cursorFrame) { 985 final Cursor cachedPlatformCursor = 986 cursorFrame.getPlatformCursor(Cursor.class); 987 if (cachedPlatformCursor != null) { 988 // platform cursor already cached 989 return cachedPlatformCursor; 990 } 991 992 // platform cursor not cached yet 993 final Cursor platformCursor = 994 SwingCursors.embedCursorToCursor(cursorFrame); 995 cursorFrame.setPlatforCursor(Cursor.class, platformCursor); 996 997 return platformCursor; 998 } 999 1000 @Override 1001 public boolean grabFocus() { 1002 // On X11 grab is limited to a single XDisplay connection, 1003 // so we can't delegate it to another GUI toolkit. 1004 if (PlatformUtil.isLinux()) return true; 1005 1006 invokeOnClientEDT(() -> { 1007 Window window = SwingUtilities.getWindowAncestor(JFXPanel.this); 1008 if (window != null) { 1009 if (JFXPanel.this.getToolkit() instanceof SunToolkit) { 1010 ((SunToolkit)JFXPanel.this.getToolkit()).grab(window); 1011 } 1012 } 1013 }); 1014 1015 return true; // Oh, well... 1016 } 1017 1018 @Override 1019 public void ungrabFocus() { 1020 // On X11 grab is limited to a single XDisplay connection, 1021 // so we can't delegate it to another GUI toolkit. 1022 if (PlatformUtil.isLinux()) return; 1023 1024 invokeOnClientEDT(() -> { 1025 Window window = SwingUtilities.getWindowAncestor(JFXPanel.this); 1026 if (window != null) { 1027 if (JFXPanel.this.getToolkit() instanceof SunToolkit) { 1028 ((SunToolkit)JFXPanel.this.getToolkit()).ungrab(window); 1029 } 1030 } 1031 }); 1032 } 1033 } 1034 }