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