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