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.event.InputEvent; 57 import java.awt.event.MouseWheelEvent; 58 import java.awt.event.WindowEvent; 59 import java.awt.event.WindowFocusListener; 60 import java.lang.reflect.Method; 61 import java.nio.IntBuffer; 62 import java.security.AccessController; 63 import java.security.PrivilegedAction; 64 import java.util.ArrayList; 65 import java.util.HashSet; 66 import java.util.List; 67 import java.util.Set; 68 import java.util.concurrent.locks.ReentrantLock; 69 import com.sun.javafx.embed.swing.Disposer; 70 import com.sun.javafx.embed.swing.DisposerRecord; 71 import com.sun.javafx.geom.BaseBounds; 72 import com.sun.javafx.geom.transform.BaseTransform; 73 import com.sun.javafx.scene.DirtyBits; 74 import com.sun.javafx.sg.prism.NGExternalNode; 75 import com.sun.javafx.sg.prism.NGNode; 76 import com.sun.javafx.stage.FocusUngrabEvent; 77 import com.sun.javafx.stage.WindowHelper; 78 import com.sun.javafx.tk.TKStage; 79 import com.sun.javafx.PlatformUtil; 80 import com.sun.javafx.embed.swing.SwingNodeHelper; 81 import com.sun.javafx.scene.NodeHelper; 82 83 import jdk.swing.interop.LightweightFrameWrapper; 84 import jdk.swing.interop.LightweightContentWrapper; 85 import jdk.swing.interop.DragSourceContextWrapper; 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 LightweightFrameWrapper lwFrame; 180 final LightweightFrameWrapper 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 LightweightFrameWrapper.notifyDisplayChanged. 335 * Must be called on EDT only. 336 */ 337 private static OptionalMethod<LightweightFrameWrapper> jlfNotifyDisplayChanged; 338 private static OptionalMethod<LightweightFrameWrapper> jlfOverrideNativeWindowHandle; 339 340 static { 341 jlfNotifyDisplayChanged = new OptionalMethod<>(LightweightFrameWrapper.class, 342 "notifyDisplayChanged", Double.TYPE, Double.TYPE); 343 if (!jlfNotifyDisplayChanged.isSupported()) { 344 jlfNotifyDisplayChanged = new OptionalMethod<>( 345 LightweightFrameWrapper.class,"notifyDisplayChanged", Integer.TYPE); 346 } 347 348 jlfOverrideNativeWindowHandle = new OptionalMethod<>(LightweightFrameWrapper.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 LightweightFrameWrapper(); 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( 563 lwFrame.createUngrabEvent(lwFrame))); 564 } 565 } 566 }; 567 568 private final ChangeListener<Boolean> windowVisibleListener = (observable, oldValue, newValue) -> { 569 if (!newValue) { 570 disposeLwFrame(); 571 } else { 572 setContent(content); 573 } 574 }; 575 576 private final ChangeListener<Window> sceneWindowListener = (observable, oldValue, newValue) -> { 577 if (oldValue != null) { 578 removeWindowListeners(oldValue); 579 } 580 581 notifyNativeHandle(newValue); 582 583 if (newValue != null) { 584 addWindowListeners(newValue); 585 } 586 }; 587 588 private void removeSceneListeners(Scene scene) { 589 Window window = scene.getWindow(); 590 if (window != null) { 591 removeWindowListeners(window); 592 } 593 scene.windowProperty().removeListener(sceneWindowListener); 594 } 595 596 private void addSceneListeners(final Scene scene) { 597 Window window = scene.getWindow(); 598 if (window != null) { 599 addWindowListeners(window); 600 notifyNativeHandle(window); 601 } 602 scene.windowProperty().addListener(sceneWindowListener); 603 } 604 605 private void addWindowListeners(final Window window) { 606 window.xProperty().addListener(locationListener); 607 window.yProperty().addListener(locationListener); 608 window.widthProperty().addListener(locationListener); 609 window.heightProperty().addListener(locationListener); 610 window.renderScaleXProperty().addListener(locationListener); 611 window.addEventHandler(FocusUngrabEvent.FOCUS_UNGRAB, ungrabHandler); 612 window.showingProperty().addListener(windowVisibleListener); 613 setLwFrameScale(window.getRenderScaleX(), window.getRenderScaleY()); 614 } 615 616 private void removeWindowListeners(final Window window) { 617 window.xProperty().removeListener(locationListener); 618 window.yProperty().removeListener(locationListener); 619 window.widthProperty().removeListener(locationListener); 620 window.heightProperty().removeListener(locationListener); 621 window.renderScaleXProperty().removeListener(locationListener); 622 window.removeEventHandler(FocusUngrabEvent.FOCUS_UNGRAB, ungrabHandler); 623 window.showingProperty().removeListener(windowVisibleListener); 624 } 625 626 /* 627 * Note: This method MUST only be called via its accessor method. 628 */ 629 private NGNode doCreatePeer() { 630 peer = new NGExternalNode(); 631 peer.setLock(paintLock); 632 for (Runnable request : peerRequests) { 633 request.run(); 634 } 635 peerRequests = null; 636 637 if (getScene() != null) { 638 addSceneListeners(getScene()); 639 } 640 641 sceneProperty().addListener((observable, oldValue, newValue) -> { 642 if (oldValue != null) { 643 // Removed from scene 644 removeSceneListeners(oldValue); 645 disposeLwFrame(); 646 } 647 if (newValue != null) { 648 // Added to another scene 649 if (content != null && lwFrame == null) { 650 setContent(content); 651 } 652 addSceneListeners(newValue); 653 } 654 }); 655 656 NodeHelper.treeVisibleProperty(this).addListener((observable, oldValue, newValue) -> { 657 setLwFrameVisible(newValue); 658 }); 659 660 return peer; 661 } 662 663 /* 664 * Note: This method MUST only be called via its accessor method. 665 */ 666 private void doUpdatePeer() { 667 if (NodeHelper.isDirty(this, DirtyBits.NODE_VISIBLE) 668 || NodeHelper.isDirty(this, DirtyBits.NODE_BOUNDS)) { 669 locateLwFrame(); // initialize location 670 } 671 if (NodeHelper.isDirty(this, DirtyBits.NODE_CONTENTS)) { 672 peer.markContentDirty(); 673 } 674 } 675 676 /** 677 * Calls LightweightFrameWrapper.setHostBounds. 678 * Must be called on EDT only. 679 */ 680 private static final OptionalMethod<LightweightFrameWrapper> jlfSetHostBounds = 681 new OptionalMethod<>(LightweightFrameWrapper.class, "setHostBounds", 682 Integer.TYPE, Integer.TYPE, Integer.TYPE, Integer.TYPE); 683 684 private void locateLwFrame() { 685 if (getScene() == null 686 || lwFrame == null 687 || getScene().getWindow() == null 688 || !getScene().getWindow().isShowing()) { 689 // Not initialized yet. Skip the update to set the real values later 690 return; 691 } 692 Window w = getScene().getWindow(); 693 double renderScaleX = w.getRenderScaleX(); 694 double renderScaleY = w.getRenderScaleY(); 695 final Point2D loc = localToScene(0, 0); 696 final int windowX = (int) (w.getX()); 697 final int windowY = (int) (w.getY()); 698 final int windowW = (int) (w.getWidth()); 699 final int windowH = (int) (w.getHeight()); 700 final int frameX = (int) Math.round(w.getX() + getScene().getX() + loc.getX()); 701 final int frameY = (int) Math.round(w.getY() + getScene().getY() + loc.getY()); 702 final int frameW = (int) (fxWidth); 703 final int frameH = (int) (fxHeight); 704 705 SwingFXUtils.runOnEDT(() -> { 706 if (lwFrame != null) { 707 if (jlfNotifyDisplayChanged.isIntegerApi()) { 708 jlfNotifyDisplayChanged.invoke(lwFrame, 709 (int)Math.round(renderScaleX)); 710 } else { 711 jlfNotifyDisplayChanged.invoke(lwFrame, renderScaleX, 712 renderScaleY); 713 } 714 lwFrame.setBounds(frameX, frameY, frameW, frameH); 715 jlfSetHostBounds.invoke(lwFrame, windowX, windowY, 716 windowW, windowH); 717 } 718 }); 719 } 720 721 private void activateLwFrame(final boolean activate) { 722 if (lwFrame == null) { 723 return; 724 } 725 if (PlatformUtil.isLinux()) { 726 // Workaround to block FocusOut/FocusIn notifications from Unity 727 // focus grabbing upon Alt press 728 if (deactivate == null || !deactivate.isRunning()) { 729 if (!activate) { 730 deactivate = new Timer(50, (e) -> { 731 { 732 if (lwFrame != null) { 733 lwFrame.emulateActivation(false); 734 } 735 } 736 }); 737 deactivate.start(); 738 return; 739 } 740 } else { 741 deactivate.stop(); 742 } 743 } 744 745 SwingFXUtils.runOnEDT(() -> { 746 if (lwFrame != null) { 747 lwFrame.emulateActivation(activate); 748 } 749 }); 750 } 751 752 private void disposeLwFrame() { 753 if (lwFrame == null) { 754 return; 755 } 756 SwingFXUtils.runOnEDT(() -> { 757 if (lwFrame != null) { 758 lwFrame.dispose(); 759 lwFrame = null; 760 } 761 }); 762 } 763 764 private void setLwFrameVisible(final boolean visible) { 765 if (lwFrame == null) { 766 return; 767 } 768 SwingFXUtils.runOnEDT(() -> { 769 if (lwFrame != null) { 770 lwFrame.setVisible(visible); 771 } 772 }); 773 } 774 775 private void setLwFrameScale(final double scaleX, final double scaleY) { 776 if (lwFrame == null) { 777 return; 778 } 779 SwingFXUtils.runOnEDT(() -> { 780 if (lwFrame != null) { 781 if (jlfNotifyDisplayChanged.isIntegerApi()) { 782 jlfNotifyDisplayChanged.invoke(lwFrame, 783 (int)Math.round(scaleX)); 784 } else { 785 jlfNotifyDisplayChanged.invoke(lwFrame, scaleX, scaleY); 786 } 787 } 788 }); 789 } 790 791 /* 792 * Note: This method MUST only be called via its accessor method. 793 */ 794 private BaseBounds doComputeGeomBounds(BaseBounds bounds, BaseTransform tx) { 795 bounds.deriveWithNewBounds(0, 0, 0, (float)fxWidth, (float)fxHeight, 0); 796 tx.transform(bounds, bounds); 797 return bounds; 798 } 799 800 private static class SwingNodeDisposer implements DisposerRecord { 801 LightweightFrameWrapper lwFrame; 802 803 SwingNodeDisposer(LightweightFrameWrapper ref) { 804 this.lwFrame = ref; 805 } 806 public void dispose() { 807 if (lwFrame != null) { 808 lwFrame.dispose(); 809 lwFrame = null; 810 } 811 } 812 } 813 814 private static class SwingNodeWindowFocusListener implements WindowFocusListener { 815 private WeakReference<SwingNode> swingNodeRef; 816 817 SwingNodeWindowFocusListener(SwingNode swingNode) { 818 this.swingNodeRef = new WeakReference<SwingNode>(swingNode); 819 } 820 821 @Override 822 public void windowGainedFocus(WindowEvent e) { 823 SwingFXUtils.runOnFxThread(() -> { 824 SwingNode swingNode = swingNodeRef.get(); 825 if (swingNode != null) { 826 swingNode.requestFocus(); 827 } 828 }); 829 } 830 831 @Override 832 public void windowLostFocus(WindowEvent e) { 833 SwingFXUtils.runOnFxThread(() -> { 834 SwingNode swingNode = swingNodeRef.get(); 835 if (swingNode != null) { 836 swingNode.ungrabFocus(true); 837 } 838 }); 839 } 840 } 841 842 private static class SwingNodeContent extends LightweightContentWrapper { 843 private JComponent comp; 844 private volatile FXDnD dnd; 845 private WeakReference<SwingNode> swingNodeRef; 846 847 SwingNodeContent(JComponent comp, SwingNode swingNode) { 848 this.comp = comp; 849 this.swingNodeRef = new WeakReference<SwingNode>(swingNode); 850 } 851 @Override 852 public JComponent getComponent() { 853 return comp; 854 } 855 @Override 856 public void paintLock() { 857 SwingNode swingNode = swingNodeRef.get(); 858 if (swingNode != null) { 859 swingNode.paintLock.lock(); 860 } 861 } 862 @Override 863 public void paintUnlock() { 864 SwingNode swingNode = swingNodeRef.get(); 865 if (swingNode != null) { 866 swingNode.paintLock.unlock(); 867 } 868 } 869 870 @Override 871 public void imageBufferReset(int[] data, int x, int y, int width, int height, int linestride) { 872 imageBufferReset(data, x, y, width, height, linestride, 1); 873 } 874 //@Override 875 public void imageBufferReset(int[] data, int x, int y, int width, int height, int linestride, int scale) { 876 SwingNode swingNode = swingNodeRef.get(); 877 if (swingNode != null) { 878 swingNode.setImageBuffer(data, x, y, width, height, linestride, scale, scale); 879 } 880 } 881 @Override 882 public void imageBufferReset(int[] data, int x, int y, int width, int height, int linestride, double scaleX, double scaleY) { 883 SwingNode swingNode = swingNodeRef.get(); 884 if (swingNode != null) { 885 swingNode.setImageBuffer(data, x, y, width, height, linestride, scaleX, scaleY); 886 } 887 } 888 @Override 889 public void imageReshaped(int x, int y, int width, int height) { 890 SwingNode swingNode = swingNodeRef.get(); 891 if (swingNode != null) { 892 swingNode.setImageBounds(x, y, width, height); 893 } 894 } 895 @Override 896 public void imageUpdated(int dirtyX, int dirtyY, int dirtyWidth, int dirtyHeight) { 897 SwingNode swingNode = swingNodeRef.get(); 898 if (swingNode != null) { 899 swingNode.repaintDirtyRegion(dirtyX, dirtyY, dirtyWidth, dirtyHeight); 900 } 901 } 902 @Override 903 public void focusGrabbed() { 904 SwingFXUtils.runOnFxThread(() -> { 905 // On X11 grab is limited to a single XDisplay connection, 906 // so we can't delegate it to another GUI toolkit. 907 if (PlatformUtil.isLinux()) return; 908 909 SwingNode swingNode = swingNodeRef.get(); 910 if (swingNode != null) { 911 Scene scene = swingNode.getScene(); 912 if (scene != null && 913 scene.getWindow() != null && 914 WindowHelper.getPeer(scene.getWindow()) != null) { 915 WindowHelper.getPeer(scene.getWindow()).grabFocus(); 916 swingNode.grabbed = true; 917 } 918 } 919 }); 920 } 921 @Override 922 public void focusUngrabbed() { 923 SwingFXUtils.runOnFxThread(() -> { 924 SwingNode swingNode = swingNodeRef.get(); 925 if (swingNode != null) { 926 swingNode.ungrabFocus(false); 927 } 928 }); 929 } 930 @Override 931 public void preferredSizeChanged(final int width, final int height) { 932 SwingFXUtils.runOnFxThread(() -> { 933 SwingNode swingNode = swingNodeRef.get(); 934 if (swingNode != null) { 935 swingNode.swingPrefWidth = width; 936 swingNode.swingPrefHeight = height; 937 NodeHelper.notifyLayoutBoundsChanged(swingNode); 938 } 939 }); 940 } 941 @Override 942 public void maximumSizeChanged(final int width, final int height) { 943 SwingFXUtils.runOnFxThread(() -> { 944 SwingNode swingNode = swingNodeRef.get(); 945 if (swingNode != null) { 946 swingNode.swingMaxWidth = width; 947 swingNode.swingMaxHeight = height; 948 NodeHelper.notifyLayoutBoundsChanged(swingNode); 949 } 950 }); 951 } 952 @Override 953 public void minimumSizeChanged(final int width, final int height) { 954 SwingFXUtils.runOnFxThread(() -> { 955 SwingNode swingNode = swingNodeRef.get(); 956 if (swingNode != null) { 957 swingNode.swingMinWidth = width; 958 swingNode.swingMinHeight = height; 959 NodeHelper.notifyLayoutBoundsChanged(swingNode); 960 } 961 }); 962 } 963 964 //@Override 965 public void setCursor(Cursor cursor) { 966 SwingFXUtils.runOnFxThread(() -> { 967 SwingNode swingNode = swingNodeRef.get(); 968 if (swingNode != null) { 969 swingNode.setCursor(SwingCursors.embedCursorToCursor(cursor)); 970 } 971 }); 972 } 973 974 private void initDnD() { 975 // This is a part of AWT API, so the method may be invoked on any thread 976 synchronized (SwingNodeContent.this) { 977 if (this.dnd == null) { 978 SwingNode swingNode = swingNodeRef.get(); 979 if (swingNode != null) { 980 this.dnd = new FXDnD(swingNode); 981 } 982 } 983 } 984 } 985 986 @Override 987 public synchronized <T extends DragGestureRecognizer> T createDragGestureRecognizer( 988 Class<T> abstractRecognizerClass, 989 DragSource ds, Component c, int srcActions, 990 DragGestureListener dgl) 991 { 992 initDnD(); 993 return dnd.createDragGestureRecognizer(abstractRecognizerClass, ds, c, srcActions, dgl); 994 } 995 996 @Override 997 public DragSourceContextWrapper createDragSourceContextPeer(DragGestureEvent dge) throws InvalidDnDOperationException 998 { 999 initDnD(); 1000 return dnd.createDragSourceContextPeer(dge); 1001 } 1002 1003 @Override 1004 public void addDropTarget(DropTarget dt) { 1005 initDnD(); 1006 dnd.addDropTarget(dt); 1007 } 1008 1009 @Override 1010 public void removeDropTarget(DropTarget dt) { 1011 initDnD(); 1012 dnd.removeDropTarget(dt); 1013 } 1014 } 1015 1016 private void ungrabFocus(boolean postUngrabEvent) { 1017 // On X11 grab is limited to a single XDisplay connection, 1018 // so we can't delegate it to another GUI toolkit. 1019 if (PlatformUtil.isLinux()) return; 1020 1021 if (grabbed && 1022 getScene() != null && 1023 getScene().getWindow() != null && 1024 WindowHelper.getPeer(getScene().getWindow()) != null) 1025 { 1026 skipBackwardUnrgabNotification = !postUngrabEvent; 1027 WindowHelper.getPeer(getScene().getWindow()).ungrabFocus(); 1028 skipBackwardUnrgabNotification = false; 1029 grabbed = false; 1030 } 1031 } 1032 1033 private class PostEventAction implements PrivilegedAction<Void> { 1034 private AWTEvent event; 1035 PostEventAction(AWTEvent event) { 1036 this.event = event; 1037 } 1038 @Override 1039 public Void run() { 1040 EventQueue eq = Toolkit.getDefaultToolkit().getSystemEventQueue(); 1041 eq.postEvent(event); 1042 return null; 1043 } 1044 } 1045 1046 private class SwingMouseEventHandler implements EventHandler<MouseEvent> { 1047 private final Set<MouseButton> mouseClickedAllowed = new HashSet<>(); 1048 1049 @Override 1050 public void handle(MouseEvent event) { 1051 LightweightFrameWrapper frame = lwFrame; 1052 if (frame == null) { 1053 return; 1054 } 1055 int swingID = SwingEvents.fxMouseEventTypeToMouseID(event); 1056 if (swingID < 0) { 1057 return; 1058 } 1059 1060 // Prevent ancestors of the SwingNode from stealing the focus 1061 event.consume(); 1062 1063 final EventType<?> type = event.getEventType(); 1064 if (type == MouseEvent.MOUSE_PRESSED) { 1065 mouseClickedAllowed.add(event.getButton()); 1066 } else if (type == MouseEvent.MOUSE_RELEASED) { 1067 // RELEASED comes before CLICKED, so we don't remove the button from the set 1068 //mouseClickedAllowed.remove(event.getButton()); 1069 } else if (type == MouseEvent.MOUSE_DRAGGED) { 1070 // This is what AWT/Swing do 1071 mouseClickedAllowed.clear(); 1072 } else if (type == MouseEvent.MOUSE_CLICKED) { 1073 if (event.getClickCount() == 1 && !mouseClickedAllowed.contains(event.getButton())) { 1074 // RT-34610: In FX, CLICKED events are generated even after dragging the mouse pointer 1075 // Note that this is only relevant for single clicks. Double clicks use a smudge factor. 1076 return; 1077 } 1078 mouseClickedAllowed.remove(event.getButton()); 1079 } 1080 int swingModifiers = SwingEvents.fxMouseModsToMouseMods(event); 1081 boolean swingPopupTrigger = event.isPopupTrigger(); 1082 int swingButton = SwingEvents.fxMouseButtonToMouseButton(event); 1083 long swingWhen = System.currentTimeMillis(); 1084 int relX = (int) Math.round(event.getX()); 1085 int relY = (int) Math.round(event.getY()); 1086 int absX = (int) Math.round(event.getScreenX()); 1087 int absY = (int) Math.round(event.getScreenY()); 1088 java.awt.event.MouseEvent mouseEvent = 1089 frame.createMouseEvent( 1090 frame, swingID, swingWhen, swingModifiers, 1091 relX, relY, absX, absY, 1092 event.getClickCount(), swingPopupTrigger, swingButton); 1093 AccessController.doPrivileged(new PostEventAction(mouseEvent)); 1094 } 1095 } 1096 1097 private class SwingScrollEventHandler implements EventHandler<ScrollEvent> { 1098 @Override 1099 public void handle(ScrollEvent event) { 1100 LightweightFrameWrapper frame = lwFrame; 1101 if (frame == null) { 1102 return; 1103 } 1104 1105 int swingModifiers = SwingEvents.fxScrollModsToMouseWheelMods(event); 1106 final boolean isShift = (swingModifiers & InputEvent.SHIFT_DOWN_MASK) != 0; 1107 1108 // Vertical scroll. 1109 if (!isShift && event.getDeltaY() != 0.0) { 1110 sendMouseWheelEvent(frame, event.getX(), event.getY(), 1111 swingModifiers, event.getDeltaY() / event.getMultiplierY()); 1112 } 1113 // Horizontal scroll or shirt+vertical scroll. 1114 final double delta = isShift && event.getDeltaY() != 0.0 1115 ? event.getDeltaY() / event.getMultiplierY() 1116 : event.getDeltaX() / event.getMultiplierX(); 1117 if (delta != 0.0) { 1118 swingModifiers |= InputEvent.SHIFT_DOWN_MASK; 1119 sendMouseWheelEvent(frame, event.getX(), event.getY(), 1120 swingModifiers, delta); 1121 } 1122 } 1123 1124 private void sendMouseWheelEvent(LightweightFrameWrapper source, double fxX, double fxY, int swingModifiers, double delta) { 1125 int wheelRotation = (int) delta; 1126 int signum = (int) Math.signum(delta); 1127 if (signum * delta < 1) { 1128 wheelRotation = signum; 1129 } 1130 int x = (int) Math.round(fxX); 1131 int y = (int) Math.round(fxY); 1132 MouseWheelEvent mouseWheelEvent = 1133 lwFrame.createMouseWheelEvent(source, swingModifiers, x, y, -wheelRotation); 1134 AccessController.doPrivileged(new PostEventAction(mouseWheelEvent)); 1135 } 1136 } 1137 1138 private class SwingKeyEventHandler implements EventHandler<KeyEvent> { 1139 @Override 1140 public void handle(KeyEvent event) { 1141 LightweightFrameWrapper frame = lwFrame; 1142 if (frame == null) { 1143 return; 1144 } 1145 if (event.getCharacter().isEmpty()) { 1146 // TODO: should we post an "empty" character? 1147 return; 1148 } 1149 // Don't let Arrows, Tab, Shift+Tab traverse focus out. 1150 if (event.getCode() == KeyCode.LEFT || 1151 event.getCode() == KeyCode.RIGHT || 1152 event.getCode() == KeyCode.UP || 1153 event.getCode() == KeyCode.DOWN || 1154 event.getCode() == KeyCode.TAB) 1155 { 1156 event.consume(); 1157 } 1158 1159 int swingID = SwingEvents.fxKeyEventTypeToKeyID(event); 1160 if (swingID < 0) { 1161 return; 1162 } 1163 int swingModifiers = SwingEvents.fxKeyModsToKeyMods(event); 1164 int swingKeyCode = event.getCode().getCode(); 1165 char swingChar = event.getCharacter().charAt(0); 1166 1167 // A workaround. Some swing L&F's process mnemonics on KEY_PRESSED, 1168 // for which swing provides a keychar. Extracting it from the text. 1169 if (event.getEventType() == javafx.scene.input.KeyEvent.KEY_PRESSED) { 1170 String text = event.getText(); 1171 if (text.length() == 1) { 1172 swingChar = text.charAt(0); 1173 } 1174 } 1175 long swingWhen = System.currentTimeMillis(); 1176 java.awt.event.KeyEvent keyEvent = frame.createKeyEvent(frame, 1177 swingID, swingWhen, swingModifiers, swingKeyCode, 1178 swingChar); 1179 AccessController.doPrivileged(new PostEventAction(keyEvent)); 1180 } 1181 } 1182 }