1 /* 2 * Copyright (c) 2013, 2017, 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 javafx.beans.InvalidationListener; 29 import javafx.beans.value.ChangeListener; 30 import javafx.event.Event; 31 import javafx.event.EventHandler; 32 import javafx.event.EventType; 33 import javafx.geometry.Point2D; 34 import javafx.scene.Node; 35 import javafx.scene.Scene; 36 import javafx.scene.input.KeyCode; 37 import javafx.scene.input.KeyEvent; 38 import javafx.scene.input.MouseButton; 39 import javafx.scene.input.MouseEvent; 40 import javafx.scene.input.ScrollEvent; 41 import javafx.stage.Window; 42 import javax.swing.JComponent; 43 import javax.swing.Timer; 44 import java.awt.AWTEvent; 45 import java.awt.Component; 46 import java.awt.Cursor; 47 import java.awt.EventQueue; 48 import java.awt.Toolkit; 49 import java.lang.ref.WeakReference; 50 import java.awt.dnd.DragGestureEvent; 51 import java.awt.dnd.DragGestureListener; 52 import java.awt.dnd.DragGestureRecognizer; 53 import java.awt.dnd.DragSource; 54 import java.awt.dnd.DropTarget; 55 import java.awt.dnd.InvalidDnDOperationException; 56 import java.awt.dnd.peer.DragSourceContextPeer; 57 import java.awt.event.InputEvent; 58 import java.awt.event.MouseWheelEvent; 59 import java.awt.event.WindowEvent; 60 import java.awt.event.WindowFocusListener; 61 import java.lang.reflect.Method; 62 import java.nio.IntBuffer; 63 import java.security.AccessController; 64 import java.security.PrivilegedAction; 65 import java.util.ArrayList; 66 import java.util.HashSet; 67 import java.util.List; 68 import java.util.Set; 69 import java.util.concurrent.locks.ReentrantLock; 70 import com.sun.javafx.embed.swing.Disposer; 71 import com.sun.javafx.embed.swing.DisposerRecord; 72 import com.sun.javafx.geom.BaseBounds; 73 import com.sun.javafx.geom.transform.BaseTransform; 74 import com.sun.javafx.scene.DirtyBits; 75 import com.sun.javafx.sg.prism.NGExternalNode; 76 import com.sun.javafx.sg.prism.NGNode; 77 import com.sun.javafx.stage.FocusUngrabEvent; 78 import com.sun.javafx.stage.WindowHelper; 79 import com.sun.javafx.tk.TKStage; 80 import com.sun.javafx.PlatformUtil; 81 import com.sun.javafx.embed.swing.SwingNodeHelper; 82 import com.sun.javafx.scene.NodeHelper; 83 import sun.awt.UngrabEvent; 84 import sun.swing.JLightweightFrame; 85 import sun.swing.LightweightContent; 86 87 import static javafx.stage.WindowEvent.WINDOW_HIDDEN; 88 89 /** 90 * This class is used to embed a Swing content into a JavaFX application. 91 * The content to be displayed is specified with the {@link #setContent} method 92 * that accepts an instance of Swing {@code JComponent}. The hierarchy of components 93 * contained in the {@code JComponent} instance should not contain any heavyweight 94 * components, otherwise {@code SwingNode} may fail to paint it. The content gets 95 * repainted automatically. All the input and focus events are forwarded to the 96 * {@code JComponent} instance transparently to the developer. 97 * <p> 98 * Here is a typical pattern which demonstrates how {@code SwingNode} can be used: 99 * <pre> 100 * public class SwingFx extends Application { 101 * 102 * @Override 103 * public void start(Stage stage) { 104 * final SwingNode swingNode = new SwingNode(); 105 * createAndSetSwingContent(swingNode); 106 * 107 * StackPane pane = new StackPane(); 108 * pane.getChildren().add(swingNode); 109 * 110 * stage.setScene(new Scene(pane, 100, 50)); 111 * stage.show(); 112 * } 113 * 114 * private void createAndSetSwingContent(final SwingNode swingNode) { 115 * SwingUtilities.invokeLater(new Runnable() { 116 * @Override 117 * public void run() { 118 * swingNode.setContent(new JButton("Click me!")); 119 * } 120 * }); 121 * } 122 * 123 * public static void main(String[] args) { 124 * launch(args); 125 * } 126 * } 127 * </pre> 128 * @since JavaFX 8.0 129 */ 130 public class SwingNode extends Node { 131 private static boolean isThreadMerged; 132 133 static { 134 AccessController.doPrivileged(new PrivilegedAction<Object>() { 135 public Object run() { 136 isThreadMerged = Boolean.valueOf( 137 System.getProperty("javafx.embed.singleThread")); 138 return null; 139 } 140 }); 141 142 // This is used by classes in different packages to get access to 143 // private and package private methods. 144 SwingNodeHelper.setSwingNodeAccessor(new SwingNodeHelper.SwingNodeAccessor() { 145 @Override 146 public NGNode doCreatePeer(Node node) { 147 return ((SwingNode) node).doCreatePeer(); 148 } 149 150 @Override 151 public void doUpdatePeer(Node node) { 152 ((SwingNode) node).doUpdatePeer(); 153 } 154 155 @Override 156 public BaseBounds doComputeGeomBounds(Node node, 157 BaseBounds bounds, BaseTransform tx) { 158 return ((SwingNode) node).doComputeGeomBounds(bounds, tx); 159 } 160 161 @Override 162 public boolean doComputeContains(Node node, double localX, double localY) { 163 return ((SwingNode) node).doComputeContains(localX, localY); 164 } 165 }); 166 } 167 168 private double fxWidth; 169 private double fxHeight; 170 171 private int swingPrefWidth; 172 private int swingPrefHeight; 173 private int swingMaxWidth; 174 private int swingMaxHeight; 175 private int swingMinWidth; 176 private int swingMinHeight; 177 178 private volatile JComponent content; 179 private volatile JLightweightFrame lwFrame; 180 final JLightweightFrame getLightweightFrame() { return lwFrame; } 181 182 private NGExternalNode peer; 183 184 private final ReentrantLock paintLock = new ReentrantLock(); 185 186 private boolean skipBackwardUnrgabNotification; 187 private boolean grabbed; // lwframe initiated grab 188 private Timer deactivate; // lwFrame deactivate delay for Linux 189 190 { 191 // To initialize the class helper at the begining each constructor of this class 192 SwingNodeHelper.initHelper(this); 193 } 194 195 /** 196 * Constructs a new instance of {@code SwingNode}. 197 */ 198 public SwingNode() { 199 setFocusTraversable(true); 200 setEventHandler(MouseEvent.ANY, new SwingMouseEventHandler()); 201 setEventHandler(KeyEvent.ANY, new SwingKeyEventHandler()); 202 setEventHandler(ScrollEvent.SCROLL, new SwingScrollEventHandler()); 203 204 focusedProperty().addListener((observable, oldValue, newValue) -> { 205 activateLwFrame(newValue); 206 }); 207 208 //Workaround for RT-34170 209 javafx.scene.text.Font.getFamilies(); 210 } 211 212 213 private EventHandler windowHiddenHandler = (Event event) -> { 214 if (lwFrame != null && event.getTarget() instanceof Window) { 215 final Window w = (Window) event.getTarget(); 216 TKStage tk = WindowHelper.getPeer(w); 217 if (tk != null) { 218 if (isThreadMerged) { 219 jlfOverrideNativeWindowHandle.invoke(lwFrame, 0L, null); 220 } else { 221 // Postpone actual window closing to ensure that 222 // a native window handler is valid on a Swing side 223 tk.postponeClose(); 224 SwingFXUtils.runOnEDT(() -> { 225 jlfOverrideNativeWindowHandle.invoke(lwFrame, 0L, 226 (Runnable) () -> SwingFXUtils.runOnFxThread( 227 () -> tk.closePostponed())); 228 }); 229 } 230 } 231 } 232 233 }; 234 235 private Window hWindow = null; 236 private void notifyNativeHandle(Window window) { 237 if (hWindow != window) { 238 if (hWindow != null) { 239 hWindow.removeEventHandler(WINDOW_HIDDEN, windowHiddenHandler); 240 } 241 if (window != null) { 242 window.addEventHandler(WINDOW_HIDDEN, windowHiddenHandler); 243 } 244 hWindow = window; 245 } 246 247 if (lwFrame != null) { 248 long rawHandle = 0L; 249 if (window != null) { 250 TKStage tkStage = WindowHelper.getPeer(window); 251 if (tkStage != null) { 252 rawHandle = tkStage.getRawHandle(); 253 } 254 } 255 jlfOverrideNativeWindowHandle.invoke(lwFrame, rawHandle, null); 256 } 257 } 258 259 /** 260 * Attaches a {@code JComponent} instance to display in this {@code SwingNode}. 261 * <p> 262 * The method can be called either on the JavaFX Application thread or the Event Dispatch thread. 263 * Note however, that access to a Swing component must occur from the Event Dispatch thread 264 * according to the Swing threading restrictions. 265 * 266 * @param content a Swing component to display in this {@code SwingNode} 267 * 268 * @see java.awt.EventQueue#isDispatchThread() 269 * @see javafx.application.Platform#isFxApplicationThread() 270 */ 271 public void setContent(final JComponent content) { 272 this.content = content; 273 274 SwingFXUtils.runOnEDT(() -> setContentImpl(content)); 275 } 276 277 /** 278 * Returns the {@code JComponent} instance attached to this {@code SwingNode}. 279 * <p> 280 * The method can be called either on the JavaFX Application thread or the Event Dispatch thread. 281 * Note however, that access to a Swing component must occur from the Event Dispatch thread 282 * according to the Swing threading restrictions. 283 * 284 * @see java.awt.EventQueue#isDispatchThread() 285 * @see javafx.application.Platform#isFxApplicationThread() 286 * 287 * @return the Swing component attached to this {@code SwingNode} 288 */ 289 public JComponent getContent() { 290 return content; 291 } 292 293 private static final class OptionalMethod<T> { 294 private final Method method; 295 private final boolean isIntegerAPI; 296 297 OptionalMethod(Class<T> cls, String name, Class<?>... args) { 298 Method m; 299 try { 300 m = cls.getMethod(name, args); 301 } catch (NoSuchMethodException ignored) { 302 // This means we're running with older JDK, simply skip the call 303 m = null; 304 } catch (Throwable ex) { 305 throw new RuntimeException("Error when calling " + cls.getName() + ".getMethod('" + name + "').", ex); 306 } 307 method = m; 308 isIntegerAPI = args != null && args.length > 0 && 309 args[0] == Integer.TYPE; 310 } 311 312 public boolean isSupported() { 313 return method != null; 314 } 315 316 public boolean isIntegerApi() { 317 return isIntegerAPI; 318 } 319 320 public Object invoke(T object, Object... args) { 321 if (method != null) { 322 try { 323 return method.invoke(object, args); 324 } catch (Throwable ex) { 325 throw new RuntimeException("Error when calling " + object.getClass().getName() + "." + method.getName() + "().", ex); 326 } 327 } else { 328 return null; 329 } 330 } 331 } 332 333 /** 334 * Calls JLightweightFrame.notifyDisplayChanged. 335 * Must be called on EDT only. 336 */ 337 private static OptionalMethod<JLightweightFrame> jlfNotifyDisplayChanged; 338 private static OptionalMethod<JLightweightFrame> jlfOverrideNativeWindowHandle; 339 340 static { 341 jlfNotifyDisplayChanged = new OptionalMethod<>(JLightweightFrame.class, 342 "notifyDisplayChanged", Double.TYPE, Double.TYPE); 343 if (!jlfNotifyDisplayChanged.isSupported()) { 344 jlfNotifyDisplayChanged = new OptionalMethod<>( 345 JLightweightFrame.class,"notifyDisplayChanged", Integer.TYPE); 346 } 347 348 jlfOverrideNativeWindowHandle = new OptionalMethod<>(JLightweightFrame.class, 349 "overrideNativeWindowHandle", Long.TYPE, Runnable.class); 350 351 } 352 353 /* 354 * Called on EDT 355 */ 356 private void setContentImpl(JComponent content) { 357 if (lwFrame != null) { 358 lwFrame.dispose(); 359 lwFrame = null; 360 } 361 if (content != null) { 362 lwFrame = new JLightweightFrame(); 363 364 SwingNodeWindowFocusListener snfListener = 365 new SwingNodeWindowFocusListener(this); 366 lwFrame.addWindowFocusListener(snfListener); 367 368 if (getScene() != null) { 369 Window window = getScene().getWindow(); 370 if (window != null) { 371 if (jlfNotifyDisplayChanged.isIntegerApi()) { 372 jlfNotifyDisplayChanged.invoke(lwFrame, 373 (int) Math.round(window.getRenderScaleX())); 374 } else { 375 jlfNotifyDisplayChanged.invoke(lwFrame, 376 window.getRenderScaleX(), 377 window.getRenderScaleY()); 378 } 379 } 380 } 381 lwFrame.setContent(new SwingNodeContent(content, this)); 382 lwFrame.setVisible(true); 383 384 SwingNodeDisposer disposeRec = new SwingNodeDisposer(lwFrame); 385 Disposer.addRecord(this, disposeRec); 386 387 if (getScene() != null) { 388 notifyNativeHandle(getScene().getWindow()); 389 } 390 391 SwingFXUtils.runOnFxThread(() -> { 392 locateLwFrame(); // initialize location 393 394 if (focusedProperty().get()) { 395 activateLwFrame(true); 396 } 397 }); 398 } 399 } 400 401 private List<Runnable> peerRequests = new ArrayList<>(); 402 403 /* 404 * Called on EDT 405 */ 406 void setImageBuffer(final int[] data, 407 final int x, final int y, 408 final int w, final int h, 409 final int linestride, 410 final double scaleX, 411 final double scaleY) 412 { 413 Runnable r = () -> peer.setImageBuffer(IntBuffer.wrap(data), x, y, w, h, 414 w, h, linestride, scaleX, scaleY); 415 SwingFXUtils.runOnFxThread(() -> { 416 if (peer != null) { 417 r.run(); 418 } else { 419 peerRequests.clear(); 420 peerRequests.add(r); 421 } 422 }); 423 } 424 425 /* 426 * Called on EDT 427 */ 428 void setImageBounds(final int x, final int y, final int w, final int h) { 429 Runnable r = () -> peer.setImageBounds(x, y, w, h, w, h); 430 SwingFXUtils.runOnFxThread(() -> { 431 if (peer != null) { 432 r.run(); 433 } else { 434 peerRequests.add(r); 435 } 436 }); 437 } 438 439 /* 440 * Called on EDT 441 */ 442 void repaintDirtyRegion(final int dirtyX, final int dirtyY, final int dirtyWidth, final int dirtyHeight) { 443 Runnable r = () -> { 444 peer.repaintDirtyRegion(dirtyX, dirtyY, dirtyWidth, dirtyHeight); 445 NodeHelper.markDirty(this, DirtyBits.NODE_CONTENTS); 446 }; 447 SwingFXUtils.runOnFxThread(() -> { 448 if (peer != null) { 449 r.run(); 450 } else { 451 peerRequests.add(r); 452 } 453 }); 454 } 455 456 @Override public boolean isResizable() { 457 return true; 458 } 459 460 /** 461 * Invoked by the {@code SwingNode}'s parent during layout to set the {@code SwingNode}'s 462 * width and height. <b>Applications should not invoke this method directly</b>. 463 * If an application needs to directly set the size of the {@code SwingNode}, it should 464 * set the Swing component's minimum/preferred/maximum size constraints which will 465 * be propagated correspondingly to the {@code SwingNode} and it's parent will honor those 466 * settings during layout. 467 * 468 * @param width the target layout bounds width 469 * @param height the target layout bounds height 470 */ 471 @Override public void resize(final double width, final double height) { 472 super.resize(width, height); 473 if (width != this.fxWidth || height != this.fxHeight) { 474 this.fxWidth = width; 475 this.fxHeight = height; 476 NodeHelper.geomChanged(this); 477 NodeHelper.markDirty(this, DirtyBits.NODE_GEOMETRY); 478 SwingFXUtils.runOnEDT(() -> { 479 if (lwFrame != null) { 480 locateLwFrame(); 481 } 482 }); 483 } 484 } 485 486 /** 487 * Returns the {@code SwingNode}'s preferred width for use in layout calculations. 488 * This value corresponds to the preferred width of the Swing component. 489 * 490 * @return the preferred width that the node should be resized to during layout 491 */ 492 @Override 493 public double prefWidth(double height) { 494 return swingPrefWidth; 495 } 496 497 /** 498 * Returns the {@code SwingNode}'s preferred height for use in layout calculations. 499 * This value corresponds to the preferred height of the Swing component. 500 * 501 * @return the preferred height that the node should be resized to during layout 502 */ 503 @Override 504 public double prefHeight(double width) { 505 return swingPrefHeight; 506 } 507 508 /** 509 * Returns the {@code SwingNode}'s maximum width for use in layout calculations. 510 * This value corresponds to the maximum width of the Swing component. 511 * 512 * @return the maximum width that the node should be resized to during layout 513 */ 514 @Override public double maxWidth(double height) { 515 return swingMaxWidth; 516 } 517 518 /** 519 * Returns the {@code SwingNode}'s maximum height for use in layout calculations. 520 * This value corresponds to the maximum height of the Swing component. 521 * 522 * @return the maximum height that the node should be resized to during layout 523 */ 524 @Override public double maxHeight(double width) { 525 return swingMaxHeight; 526 } 527 528 /** 529 * Returns the {@code SwingNode}'s minimum width for use in layout calculations. 530 * This value corresponds to the minimum width of the Swing component. 531 * 532 * @return the minimum width that the node should be resized to during layout 533 */ 534 @Override public double minWidth(double height) { 535 return swingMinWidth; 536 } 537 538 /** 539 * Returns the {@code SwingNode}'s minimum height for use in layout calculations. 540 * This value corresponds to the minimum height of the Swing component. 541 * 542 * @return the minimum height that the node should be resized to during layout 543 */ 544 @Override public double minHeight(double width) { 545 return swingMinHeight; 546 } 547 548 /* 549 * Note: This method MUST only be called via its accessor method. 550 */ 551 private boolean doComputeContains(double localX, double localY) { 552 return true; 553 } 554 555 private final InvalidationListener locationListener = observable -> { 556 locateLwFrame(); 557 }; 558 559 private final EventHandler<FocusUngrabEvent> ungrabHandler = event -> { 560 if (!skipBackwardUnrgabNotification) { 561 if (lwFrame != null) { 562 AccessController.doPrivileged(new PostEventAction(new UngrabEvent(lwFrame))); 563 } 564 } 565 }; 566 567 private final ChangeListener<Boolean> windowVisibleListener = (observable, oldValue, newValue) -> { 568 if (!newValue) { 569 disposeLwFrame(); 570 } else { 571 setContent(content); 572 } 573 }; 574 575 private final ChangeListener<Window> sceneWindowListener = (observable, oldValue, newValue) -> { 576 if (oldValue != null) { 577 removeWindowListeners(oldValue); 578 } 579 580 notifyNativeHandle(newValue); 581 582 if (newValue != null) { 583 addWindowListeners(newValue); 584 } 585 }; 586 587 private void removeSceneListeners(Scene scene) { 588 Window window = scene.getWindow(); 589 if (window != null) { 590 removeWindowListeners(window); 591 } 592 scene.windowProperty().removeListener(sceneWindowListener); 593 } 594 595 private void addSceneListeners(final Scene scene) { 596 Window window = scene.getWindow(); 597 if (window != null) { 598 addWindowListeners(window); 599 notifyNativeHandle(window); 600 } 601 scene.windowProperty().addListener(sceneWindowListener); 602 } 603 604 private void addWindowListeners(final Window window) { 605 window.xProperty().addListener(locationListener); 606 window.yProperty().addListener(locationListener); 607 window.widthProperty().addListener(locationListener); 608 window.heightProperty().addListener(locationListener); 609 window.renderScaleXProperty().addListener(locationListener); 610 window.addEventHandler(FocusUngrabEvent.FOCUS_UNGRAB, ungrabHandler); 611 window.showingProperty().addListener(windowVisibleListener); 612 setLwFrameScale(window.getRenderScaleX(), window.getRenderScaleY()); 613 } 614 615 private void removeWindowListeners(final Window window) { 616 window.xProperty().removeListener(locationListener); 617 window.yProperty().removeListener(locationListener); 618 window.widthProperty().removeListener(locationListener); 619 window.heightProperty().removeListener(locationListener); 620 window.renderScaleXProperty().removeListener(locationListener); 621 window.removeEventHandler(FocusUngrabEvent.FOCUS_UNGRAB, ungrabHandler); 622 window.showingProperty().removeListener(windowVisibleListener); 623 } 624 625 /* 626 * Note: This method MUST only be called via its accessor method. 627 */ 628 private NGNode doCreatePeer() { 629 peer = new NGExternalNode(); 630 peer.setLock(paintLock); 631 for (Runnable request : peerRequests) { 632 request.run(); 633 } 634 peerRequests = null; 635 636 if (getScene() != null) { 637 addSceneListeners(getScene()); 638 } 639 640 sceneProperty().addListener((observable, oldValue, newValue) -> { 641 if (oldValue != null) { 642 // Removed from scene 643 removeSceneListeners(oldValue); 644 disposeLwFrame(); 645 } 646 if (newValue != null) { 647 // Added to another scene 648 if (content != null && lwFrame == null) { 649 setContent(content); 650 } 651 addSceneListeners(newValue); 652 } 653 }); 654 655 NodeHelper.treeVisibleProperty(this).addListener((observable, oldValue, newValue) -> { 656 setLwFrameVisible(newValue); 657 }); 658 659 return peer; 660 } 661 662 /* 663 * Note: This method MUST only be called via its accessor method. 664 */ 665 private void doUpdatePeer() { 666 if (NodeHelper.isDirty(this, DirtyBits.NODE_VISIBLE) 667 || NodeHelper.isDirty(this, DirtyBits.NODE_BOUNDS)) { 668 locateLwFrame(); // initialize location 669 } 670 if (NodeHelper.isDirty(this, DirtyBits.NODE_CONTENTS)) { 671 peer.markContentDirty(); 672 } 673 } 674 675 /** 676 * Calls JLightweightFrame.setHostBounds. 677 * Must be called on EDT only. 678 */ 679 private static final OptionalMethod<JLightweightFrame> jlfSetHostBounds = 680 new OptionalMethod<>(JLightweightFrame.class, "setHostBounds", 681 Integer.TYPE, Integer.TYPE, Integer.TYPE, Integer.TYPE); 682 683 private void locateLwFrame() { 684 if (getScene() == null 685 || lwFrame == null 686 || getScene().getWindow() == null 687 || !getScene().getWindow().isShowing()) { 688 // Not initialized yet. Skip the update to set the real values later 689 return; 690 } 691 Window w = getScene().getWindow(); 692 double renderScaleX = w.getRenderScaleX(); 693 double renderScaleY = w.getRenderScaleY(); 694 final Point2D loc = localToScene(0, 0); 695 final int windowX = (int) (w.getX()); 696 final int windowY = (int) (w.getY()); 697 final int windowW = (int) (w.getWidth()); 698 final int windowH = (int) (w.getHeight()); 699 final int frameX = (int) Math.round(w.getX() + getScene().getX() + loc.getX()); 700 final int frameY = (int) Math.round(w.getY() + getScene().getY() + loc.getY()); 701 final int frameW = (int) (fxWidth); 702 final int frameH = (int) (fxHeight); 703 704 SwingFXUtils.runOnEDT(() -> { 705 if (lwFrame != null) { 706 if (jlfNotifyDisplayChanged.isIntegerApi()) { 707 jlfNotifyDisplayChanged.invoke(lwFrame, 708 (int)Math.round(renderScaleX)); 709 } else { 710 jlfNotifyDisplayChanged.invoke(lwFrame, renderScaleX, 711 renderScaleY); 712 } 713 lwFrame.setBounds(frameX, frameY, frameW, frameH); 714 jlfSetHostBounds.invoke(lwFrame, windowX, windowY, 715 windowW, windowH); 716 } 717 }); 718 } 719 720 private void activateLwFrame(final boolean activate) { 721 if (lwFrame == null) { 722 return; 723 } 724 if (PlatformUtil.isLinux()) { 725 // Workaround to block FocusOut/FocusIn notifications from Unity 726 // focus grabbing upon Alt press 727 if (deactivate == null || !deactivate.isRunning()) { 728 if (!activate) { 729 deactivate = new Timer(50, (e) -> { 730 { 731 if (lwFrame != null) { 732 lwFrame.emulateActivation(false); 733 } 734 } 735 }); 736 deactivate.start(); 737 return; 738 } 739 } else { 740 deactivate.stop(); 741 } 742 } 743 744 SwingFXUtils.runOnEDT(() -> { 745 if (lwFrame != null) { 746 lwFrame.emulateActivation(activate); 747 } 748 }); 749 } 750 751 private void disposeLwFrame() { 752 if (lwFrame == null) { 753 return; 754 } 755 SwingFXUtils.runOnEDT(() -> { 756 if (lwFrame != null) { 757 lwFrame.dispose(); 758 lwFrame = null; 759 } 760 }); 761 } 762 763 private void setLwFrameVisible(final boolean visible) { 764 if (lwFrame == null) { 765 return; 766 } 767 SwingFXUtils.runOnEDT(() -> { 768 if (lwFrame != null) { 769 lwFrame.setVisible(visible); 770 } 771 }); 772 } 773 774 private void setLwFrameScale(final double scaleX, final double scaleY) { 775 if (lwFrame == null) { 776 return; 777 } 778 SwingFXUtils.runOnEDT(() -> { 779 if (lwFrame != null) { 780 if (jlfNotifyDisplayChanged.isIntegerApi()) { 781 jlfNotifyDisplayChanged.invoke(lwFrame, 782 (int)Math.round(scaleX)); 783 } else { 784 jlfNotifyDisplayChanged.invoke(lwFrame, scaleX, scaleY); 785 } 786 } 787 }); 788 } 789 790 /* 791 * Note: This method MUST only be called via its accessor method. 792 */ 793 private BaseBounds doComputeGeomBounds(BaseBounds bounds, BaseTransform tx) { 794 bounds.deriveWithNewBounds(0, 0, 0, (float)fxWidth, (float)fxHeight, 0); 795 tx.transform(bounds, bounds); 796 return bounds; 797 } 798 799 private static class SwingNodeDisposer implements DisposerRecord { 800 JLightweightFrame lwFrame; 801 802 SwingNodeDisposer(JLightweightFrame ref) { 803 this.lwFrame = ref; 804 } 805 public void dispose() { 806 if (lwFrame != null) { 807 lwFrame.dispose(); 808 lwFrame = null; 809 } 810 } 811 } 812 813 private static class SwingNodeWindowFocusListener implements WindowFocusListener { 814 private WeakReference<SwingNode> swingNodeRef; 815 816 SwingNodeWindowFocusListener(SwingNode swingNode) { 817 this.swingNodeRef = new WeakReference<SwingNode>(swingNode); 818 } 819 820 @Override 821 public void windowGainedFocus(WindowEvent e) { 822 SwingFXUtils.runOnFxThread(() -> { 823 SwingNode swingNode = swingNodeRef.get(); 824 if (swingNode != null) { 825 swingNode.requestFocus(); 826 } 827 }); 828 } 829 830 @Override 831 public void windowLostFocus(WindowEvent e) { 832 SwingFXUtils.runOnFxThread(() -> { 833 SwingNode swingNode = swingNodeRef.get(); 834 if (swingNode != null) { 835 swingNode.ungrabFocus(true); 836 } 837 }); 838 } 839 } 840 841 private static class SwingNodeContent implements LightweightContent { 842 private JComponent comp; 843 private volatile FXDnD dnd; 844 private WeakReference<SwingNode> swingNodeRef; 845 846 SwingNodeContent(JComponent comp, SwingNode swingNode) { 847 this.comp = comp; 848 this.swingNodeRef = new WeakReference<SwingNode>(swingNode); 849 } 850 @Override 851 public JComponent getComponent() { 852 return comp; 853 } 854 @Override 855 public void paintLock() { 856 SwingNode swingNode = swingNodeRef.get(); 857 if (swingNode != null) { 858 swingNode.paintLock.lock(); 859 } 860 } 861 @Override 862 public void paintUnlock() { 863 SwingNode swingNode = swingNodeRef.get(); 864 if (swingNode != null) { 865 swingNode.paintLock.unlock(); 866 } 867 } 868 869 // Note: we skip @Override annotation and implement both pre-hiDPI and post-hiDPI versions 870 // of the method for compatibility. 871 //@Override 872 public void imageBufferReset(int[] data, int x, int y, int width, int height, int linestride) { 873 imageBufferReset(data, x, y, width, height, linestride, 1); 874 } 875 //@Override 876 public void imageBufferReset(int[] data, int x, int y, int width, int height, int linestride, int scale) { 877 SwingNode swingNode = swingNodeRef.get(); 878 if (swingNode != null) { 879 swingNode.setImageBuffer(data, x, y, width, height, linestride, scale, scale); 880 } 881 } 882 //@Override 883 public void imageBufferReset(int[] data, int x, int y, int width, int height, int linestride, double scaleX, double scaleY) { 884 SwingNode swingNode = swingNodeRef.get(); 885 if (swingNode != null) { 886 swingNode.setImageBuffer(data, x, y, width, height, linestride, scaleX, scaleY); 887 } 888 } 889 @Override 890 public void imageReshaped(int x, int y, int width, int height) { 891 SwingNode swingNode = swingNodeRef.get(); 892 if (swingNode != null) { 893 swingNode.setImageBounds(x, y, width, height); 894 } 895 } 896 @Override 897 public void imageUpdated(int dirtyX, int dirtyY, int dirtyWidth, int dirtyHeight) { 898 SwingNode swingNode = swingNodeRef.get(); 899 if (swingNode != null) { 900 swingNode.repaintDirtyRegion(dirtyX, dirtyY, dirtyWidth, dirtyHeight); 901 } 902 } 903 @Override 904 public void focusGrabbed() { 905 SwingFXUtils.runOnFxThread(() -> { 906 // On X11 grab is limited to a single XDisplay connection, 907 // so we can't delegate it to another GUI toolkit. 908 if (PlatformUtil.isLinux()) return; 909 910 SwingNode swingNode = swingNodeRef.get(); 911 if (swingNode != null) { 912 Scene scene = swingNode.getScene(); 913 if (scene != null && 914 scene.getWindow() != null && 915 WindowHelper.getPeer(scene.getWindow()) != null) { 916 WindowHelper.getPeer(scene.getWindow()).grabFocus(); 917 swingNode.grabbed = true; 918 } 919 } 920 }); 921 } 922 @Override 923 public void focusUngrabbed() { 924 SwingFXUtils.runOnFxThread(() -> { 925 SwingNode swingNode = swingNodeRef.get(); 926 if (swingNode != null) { 927 swingNode.ungrabFocus(false); 928 } 929 }); 930 } 931 @Override 932 public void preferredSizeChanged(final int width, final int height) { 933 SwingFXUtils.runOnFxThread(() -> { 934 SwingNode swingNode = swingNodeRef.get(); 935 if (swingNode != null) { 936 swingNode.swingPrefWidth = width; 937 swingNode.swingPrefHeight = height; 938 NodeHelper.notifyLayoutBoundsChanged(swingNode); 939 } 940 }); 941 } 942 @Override 943 public void maximumSizeChanged(final int width, final int height) { 944 SwingFXUtils.runOnFxThread(() -> { 945 SwingNode swingNode = swingNodeRef.get(); 946 if (swingNode != null) { 947 swingNode.swingMaxWidth = width; 948 swingNode.swingMaxHeight = height; 949 NodeHelper.notifyLayoutBoundsChanged(swingNode); 950 } 951 }); 952 } 953 @Override 954 public void minimumSizeChanged(final int width, final int height) { 955 SwingFXUtils.runOnFxThread(() -> { 956 SwingNode swingNode = swingNodeRef.get(); 957 if (swingNode != null) { 958 swingNode.swingMinWidth = width; 959 swingNode.swingMinHeight = height; 960 NodeHelper.notifyLayoutBoundsChanged(swingNode); 961 } 962 }); 963 } 964 965 //@Override 966 public void setCursor(Cursor cursor) { 967 SwingFXUtils.runOnFxThread(() -> { 968 SwingNode swingNode = swingNodeRef.get(); 969 if (swingNode != null) { 970 swingNode.setCursor(SwingCursors.embedCursorToCursor(cursor)); 971 } 972 }); 973 } 974 975 private void initDnD() { 976 // This is a part of AWT API, so the method may be invoked on any thread 977 synchronized (SwingNodeContent.this) { 978 if (this.dnd == null) { 979 SwingNode swingNode = swingNodeRef.get(); 980 if (swingNode != null) { 981 this.dnd = new FXDnD(swingNode); 982 } 983 } 984 } 985 } 986 987 //@Override 988 public synchronized <T extends DragGestureRecognizer> T createDragGestureRecognizer( 989 Class<T> abstractRecognizerClass, 990 DragSource ds, Component c, int srcActions, 991 DragGestureListener dgl) 992 { 993 initDnD(); 994 return dnd.createDragGestureRecognizer(abstractRecognizerClass, ds, c, srcActions, dgl); 995 } 996 997 //@Override 998 public DragSourceContextPeer createDragSourceContextPeer(DragGestureEvent dge) throws InvalidDnDOperationException 999 { 1000 initDnD(); 1001 return dnd.createDragSourceContextPeer(dge); 1002 } 1003 1004 //@Override 1005 public void addDropTarget(DropTarget dt) { 1006 initDnD(); 1007 dnd.addDropTarget(dt); 1008 } 1009 1010 //@Override 1011 public void removeDropTarget(DropTarget dt) { 1012 initDnD(); 1013 dnd.removeDropTarget(dt); 1014 } 1015 } 1016 1017 private void ungrabFocus(boolean postUngrabEvent) { 1018 // On X11 grab is limited to a single XDisplay connection, 1019 // so we can't delegate it to another GUI toolkit. 1020 if (PlatformUtil.isLinux()) return; 1021 1022 if (grabbed && 1023 getScene() != null && 1024 getScene().getWindow() != null && 1025 WindowHelper.getPeer(getScene().getWindow()) != null) 1026 { 1027 skipBackwardUnrgabNotification = !postUngrabEvent; 1028 WindowHelper.getPeer(getScene().getWindow()).ungrabFocus(); 1029 skipBackwardUnrgabNotification = false; 1030 grabbed = false; 1031 } 1032 } 1033 1034 private class PostEventAction implements PrivilegedAction<Void> { 1035 private AWTEvent event; 1036 PostEventAction(AWTEvent event) { 1037 this.event = event; 1038 } 1039 @Override 1040 public Void run() { 1041 EventQueue eq = Toolkit.getDefaultToolkit().getSystemEventQueue(); 1042 eq.postEvent(event); 1043 return null; 1044 } 1045 } 1046 1047 private class SwingMouseEventHandler implements EventHandler<MouseEvent> { 1048 private final Set<MouseButton> mouseClickedAllowed = new HashSet<>(); 1049 1050 @Override 1051 public void handle(MouseEvent event) { 1052 JLightweightFrame frame = lwFrame; 1053 if (frame == null) { 1054 return; 1055 } 1056 int swingID = SwingEvents.fxMouseEventTypeToMouseID(event); 1057 if (swingID < 0) { 1058 return; 1059 } 1060 1061 // Prevent ancestors of the SwingNode from stealing the focus 1062 event.consume(); 1063 1064 final EventType<?> type = event.getEventType(); 1065 if (type == MouseEvent.MOUSE_PRESSED) { 1066 mouseClickedAllowed.add(event.getButton()); 1067 } else if (type == MouseEvent.MOUSE_RELEASED) { 1068 // RELEASED comes before CLICKED, so we don't remove the button from the set 1069 //mouseClickedAllowed.remove(event.getButton()); 1070 } else if (type == MouseEvent.MOUSE_DRAGGED) { 1071 // This is what AWT/Swing do 1072 mouseClickedAllowed.clear(); 1073 } else if (type == MouseEvent.MOUSE_CLICKED) { 1074 if (event.getClickCount() == 1 && !mouseClickedAllowed.contains(event.getButton())) { 1075 // RT-34610: In FX, CLICKED events are generated even after dragging the mouse pointer 1076 // Note that this is only relevant for single clicks. Double clicks use a smudge factor. 1077 return; 1078 } 1079 mouseClickedAllowed.remove(event.getButton()); 1080 } 1081 int swingModifiers = SwingEvents.fxMouseModsToMouseMods(event); 1082 boolean swingPopupTrigger = event.isPopupTrigger(); 1083 int swingButton = SwingEvents.fxMouseButtonToMouseButton(event); 1084 long swingWhen = System.currentTimeMillis(); 1085 int relX = (int) Math.round(event.getX()); 1086 int relY = (int) Math.round(event.getY()); 1087 int absX = (int) Math.round(event.getScreenX()); 1088 int absY = (int) Math.round(event.getScreenY()); 1089 java.awt.event.MouseEvent mouseEvent = 1090 new java.awt.event.MouseEvent( 1091 frame, swingID, swingWhen, swingModifiers, 1092 relX, relY, absX, absY, 1093 event.getClickCount(), swingPopupTrigger, swingButton); 1094 AccessController.doPrivileged(new PostEventAction(mouseEvent)); 1095 } 1096 } 1097 1098 private class SwingScrollEventHandler implements EventHandler<ScrollEvent> { 1099 @Override 1100 public void handle(ScrollEvent event) { 1101 JLightweightFrame frame = lwFrame; 1102 if (frame == null) { 1103 return; 1104 } 1105 1106 int swingModifiers = SwingEvents.fxScrollModsToMouseWheelMods(event); 1107 final boolean isShift = (swingModifiers & InputEvent.SHIFT_DOWN_MASK) != 0; 1108 1109 // Vertical scroll. 1110 if (!isShift && event.getDeltaY() != 0.0) { 1111 sendMouseWheelEvent(frame, event.getX(), event.getY(), 1112 swingModifiers, event.getDeltaY() / event.getMultiplierY()); 1113 } 1114 // Horizontal scroll or shirt+vertical scroll. 1115 final double delta = isShift && event.getDeltaY() != 0.0 1116 ? event.getDeltaY() / event.getMultiplierY() 1117 : event.getDeltaX() / event.getMultiplierX(); 1118 if (delta != 0.0) { 1119 swingModifiers |= InputEvent.SHIFT_DOWN_MASK; 1120 sendMouseWheelEvent(frame, event.getX(), event.getY(), 1121 swingModifiers, delta); 1122 } 1123 } 1124 1125 private void sendMouseWheelEvent(Component source, double fxX, double fxY, int swingModifiers, double delta) { 1126 int wheelRotation = (int) delta; 1127 int signum = (int) Math.signum(delta); 1128 if (signum * delta < 1) { 1129 wheelRotation = signum; 1130 } 1131 int x = (int) Math.round(fxX); 1132 int y = (int) Math.round(fxY); 1133 MouseWheelEvent mouseWheelEvent = 1134 new MouseWheelEvent(source, java.awt.event.MouseEvent.MOUSE_WHEEL, 1135 System.currentTimeMillis(), swingModifiers, x, y, 0, 0, 1136 0, false, MouseWheelEvent.WHEEL_UNIT_SCROLL, 1 , -wheelRotation); 1137 AccessController.doPrivileged(new PostEventAction(mouseWheelEvent)); 1138 } 1139 } 1140 1141 private class SwingKeyEventHandler implements EventHandler<KeyEvent> { 1142 @Override 1143 public void handle(KeyEvent event) { 1144 JLightweightFrame frame = lwFrame; 1145 if (frame == null) { 1146 return; 1147 } 1148 if (event.getCharacter().isEmpty()) { 1149 // TODO: should we post an "empty" character? 1150 return; 1151 } 1152 // Don't let Arrows, Tab, Shift+Tab traverse focus out. 1153 if (event.getCode() == KeyCode.LEFT || 1154 event.getCode() == KeyCode.RIGHT || 1155 event.getCode() == KeyCode.UP || 1156 event.getCode() == KeyCode.DOWN || 1157 event.getCode() == KeyCode.TAB) 1158 { 1159 event.consume(); 1160 } 1161 1162 int swingID = SwingEvents.fxKeyEventTypeToKeyID(event); 1163 if (swingID < 0) { 1164 return; 1165 } 1166 int swingModifiers = SwingEvents.fxKeyModsToKeyMods(event); 1167 int swingKeyCode = event.getCode().getCode(); 1168 char swingChar = event.getCharacter().charAt(0); 1169 1170 // A workaround. Some swing L&F's process mnemonics on KEY_PRESSED, 1171 // for which swing provides a keychar. Extracting it from the text. 1172 if (event.getEventType() == javafx.scene.input.KeyEvent.KEY_PRESSED) { 1173 String text = event.getText(); 1174 if (text.length() == 1) { 1175 swingChar = text.charAt(0); 1176 } 1177 } 1178 long swingWhen = System.currentTimeMillis(); 1179 java.awt.event.KeyEvent keyEvent = new java.awt.event.KeyEvent( 1180 frame, swingID, swingWhen, swingModifiers, 1181 swingKeyCode, swingChar); 1182 AccessController.doPrivileged(new PostEventAction(keyEvent)); 1183 } 1184 } 1185 }