1 /* 2 * Copyright (c) 2013, 2014, 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.Observable; 30 import javafx.beans.value.ChangeListener; 31 import javafx.beans.value.ObservableValue; 32 import javafx.event.EventHandler; 33 import javafx.event.EventType; 34 import javafx.geometry.Point2D; 35 import javafx.scene.Node; 36 import javafx.scene.Scene; 37 import javafx.scene.input.KeyCode; 38 import javafx.scene.input.KeyEvent; 39 import javafx.scene.input.MouseButton; 40 import javafx.scene.input.MouseEvent; 41 import javafx.scene.input.ScrollEvent; 42 import javafx.stage.Screen; 43 import javafx.stage.Window; 44 import javax.swing.JComponent; 45 import java.awt.AWTEvent; 46 import java.awt.Component; 47 import java.awt.Cursor; 48 import java.awt.EventQueue; 49 import java.awt.Toolkit; 50 import java.awt.event.InputEvent; 51 import java.awt.event.MouseWheelEvent; 52 import java.awt.event.WindowEvent; 53 import java.awt.event.WindowFocusListener; 54 import java.lang.reflect.InvocationTargetException; 55 import java.lang.reflect.Method; 56 import java.nio.IntBuffer; 57 import java.security.AccessController; 58 import java.security.PrivilegedAction; 59 import java.util.ArrayList; 60 import java.util.HashSet; 61 import java.util.List; 62 import java.util.Set; 63 import java.util.concurrent.locks.ReentrantLock; 64 import com.sun.javafx.geom.BaseBounds; 65 import com.sun.javafx.geom.transform.BaseTransform; 66 import com.sun.javafx.jmx.MXNodeAlgorithm; 67 import com.sun.javafx.jmx.MXNodeAlgorithmContext; 68 import com.sun.javafx.scene.DirtyBits; 69 import com.sun.javafx.sg.prism.NGExternalNode; 70 import com.sun.javafx.sg.prism.NGNode; 71 import com.sun.javafx.stage.FocusUngrabEvent; 72 import com.sun.javafx.stage.ScreenHelper; 73 import com.sun.javafx.stage.WindowHelper; 74 import com.sun.javafx.PlatformUtil; 75 import sun.awt.UngrabEvent; 76 import sun.awt.LightweightFrame; 77 import sun.swing.JLightweightFrame; 78 import sun.swing.LightweightContent; 79 80 /** 81 * This class is used to embed a Swing content into a JavaFX application. 82 * The content to be displayed is specified with the {@link #setContent} method 83 * that accepts an instance of Swing {@code JComponent}. The hierarchy of components 84 * contained in the {@code JComponent} instance should not contain any heavyweight 85 * components, otherwise {@code SwingNode} may fail to paint it. The content gets 86 * repainted automatically. All the input and focus events are forwarded to the 87 * {@code JComponent} instance transparently to the developer. 88 * <p> 89 * Here is a typical pattern which demonstrates how {@code SwingNode} can be used: 90 * <pre> 91 * public class SwingFx extends Application { 92 * 93 * @Override 94 * public void start(Stage stage) { 95 * final SwingNode swingNode = new SwingNode(); 96 * createAndSetSwingContent(swingNode); 97 * 98 * StackPane pane = new StackPane(); 99 * pane.getChildren().add(swingNode); 100 * 101 * stage.setScene(new Scene(pane, 100, 50)); 102 * stage.show(); 103 * } 104 * 105 * private void createAndSetSwingContent(final SwingNode swingNode) { 106 * SwingUtilities.invokeLater(new Runnable() { 107 * @Override 108 * public void run() { 109 * swingNode.setContent(new JButton("Click me!")); 110 * } 111 * }); 112 * } 113 * 114 * public static void main(String[] args) { 115 * launch(args); 116 * } 117 * } 118 * </pre> 119 * @since JavaFX 8.0 120 */ 121 public class SwingNode extends Node { 122 123 private double width; 124 private double height; 125 126 private double prefWidth; 127 private double prefHeight; 128 private double maxWidth; 129 private double maxHeight; 130 private double minWidth; 131 private double minHeight; 132 133 private volatile JComponent content; 134 private volatile JLightweightFrame lwFrame; 135 136 private volatile NGExternalNode peer; 137 138 private final ReentrantLock paintLock = new ReentrantLock(); 139 140 private boolean skipBackwardUnrgabNotification; 141 private boolean grabbed; // lwframe initiated grab 142 143 private volatile int scale = 1; 144 145 /** 146 * Constructs a new instance of {@code SwingNode}. 147 */ 148 public SwingNode() { 149 setFocusTraversable(true); 150 setEventHandler(MouseEvent.ANY, new SwingMouseEventHandler()); 151 setEventHandler(KeyEvent.ANY, new SwingKeyEventHandler()); 152 setEventHandler(ScrollEvent.SCROLL, new SwingScrollEventHandler()); 153 154 focusedProperty().addListener((observable, oldValue, newValue) -> { 155 activateLwFrame(newValue); 156 }); 157 158 //Workaround for RT-34170 159 javafx.scene.text.Font.getFamilies(); 160 } 161 162 /** 163 * Attaches a {@code JComponent} instance to display in this {@code SwingNode}. 164 * <p> 165 * The method can be called either on the JavaFX Application thread or the Event Dispatch thread. 166 * Note however, that access to a Swing component must occur from the Event Dispatch thread 167 * according to the Swing threading restrictions. 168 * 169 * @param content a Swing component to display in this {@code SwingNode} 170 * 171 * @see java.awt.EventQueue#isDispatchThread() 172 * @see javafx.application.Platform#isFxApplicationThread() 173 */ 174 public void setContent(final JComponent content) { 175 this.content = content; 176 177 SwingFXUtils.runOnEDT(() -> { 178 setContentImpl(content); 179 }); 180 } 181 182 /** 183 * Returns the {@code JComponent} instance attached to this {@code SwingNode}. 184 * <p> 185 * The method can be called either on the JavaFX Application thread or the Event Dispatch thread. 186 * Note however, that access to a Swing component must occur from the Event Dispatch thread 187 * according to the Swing threading restrictions. 188 * 189 * @see java.awt.EventQueue#isDispatchThread() 190 * @see javafx.application.Platform#isFxApplicationThread() 191 * 192 * @return the Swing component attached to this {@code SwingNode} 193 */ 194 public JComponent getContent() { 195 return content; 196 } 197 198 private static final class OptionalMethod<T> { 199 private final Method method; 200 201 public OptionalMethod(Class<T> cls, String name, Class<?>... args) { 202 Method m; 203 try { 204 m = cls.getMethod(name, args); 205 } catch (NoSuchMethodException ignored) { 206 // This means we're running with older JDK, simply skip the call 207 m = null; 208 } catch (Throwable ex) { 209 throw new RuntimeException("Error when calling " + cls.getName() + ".getMethod('" + name + "').", ex); 210 } 211 method = m; 212 } 213 214 public boolean isSupported() { 215 return method != null; 216 } 217 218 public Object invoke(T object, Object... args) { 219 if (method != null) { 220 try { 221 return method.invoke(object, args); 222 } catch (Throwable ex) { 223 throw new RuntimeException("Error when calling " + object.getClass().getName() + "." + method.getName() + "().", ex); 224 } 225 } else { 226 return null; 227 } 228 } 229 } 230 231 /** 232 * Calls JLightweightFrame.notifyDisplayChanged. 233 * Must be called on EDT only. 234 */ 235 private static final OptionalMethod<JLightweightFrame> jlfNotifyDisplayChanged = 236 new OptionalMethod<>(JLightweightFrame.class, "notifyDisplayChanged", Integer.TYPE); 237 238 /* 239 * Called on EDT 240 */ 241 private void setContentImpl(JComponent content) { 242 if (lwFrame != null) { 243 lwFrame.dispose(); 244 lwFrame = null; 245 } 246 if (content != null) { 247 lwFrame = new JLightweightFrame(); 248 249 lwFrame.addWindowFocusListener(new WindowFocusListener() { 250 @Override 251 public void windowGainedFocus(WindowEvent e) { 252 SwingFXUtils.runOnFxThread(() -> { 253 SwingNode.this.requestFocus(); 254 }); 255 } 256 @Override 257 public void windowLostFocus(WindowEvent e) { 258 SwingFXUtils.runOnFxThread(() -> { 259 ungrabFocus(true); 260 }); 261 } 262 }); 263 264 jlfNotifyDisplayChanged.invoke(lwFrame, Math.round(scale)); 265 lwFrame.setContent(new SwingNodeContent(content)); 266 lwFrame.setSize((int)width, (int)height); 267 lwFrame.setVisible(true); 268 269 SwingFXUtils.runOnFxThread(() -> { 270 locateLwFrame(); // initialize location 271 272 if (focusedProperty().get()) { 273 activateLwFrame(true); 274 } 275 }); 276 } 277 } 278 279 private List<Runnable> peerRequests = new ArrayList<>(); 280 281 /* 282 * Called on EDT 283 */ 284 void setImageBuffer(final int[] data, 285 final int x, final int y, 286 final int w, final int h, 287 final int linestride, 288 final int scale) 289 { 290 Runnable r = () -> { 291 peer.setImageBuffer(IntBuffer.wrap(data), x, y, w, h, linestride, scale); 292 }; 293 if (peer != null) { 294 SwingFXUtils.runOnFxThread(r); 295 } else { 296 peerRequests.clear(); 297 peerRequests.add(r); 298 } 299 } 300 301 /* 302 * Called on EDT 303 */ 304 void setImageBounds(final int x, final int y, final int w, final int h) { 305 Runnable r = () -> { 306 peer.setImageBounds(x, y, w, h); 307 }; 308 if (peer != null) { 309 SwingFXUtils.runOnFxThread(r); 310 } else { 311 peerRequests.add(r); 312 } 313 } 314 315 /* 316 * Called on EDT 317 */ 318 void repaintDirtyRegion(final int dirtyX, final int dirtyY, final int dirtyWidth, final int dirtyHeight) { 319 Runnable r = () -> { 320 peer.repaintDirtyRegion(dirtyX, dirtyY, dirtyWidth, dirtyHeight); 321 impl_markDirty(DirtyBits.NODE_CONTENTS); 322 }; 323 if (peer != null) { 324 SwingFXUtils.runOnFxThread(r); 325 } else { 326 peerRequests.add(r); 327 } 328 } 329 330 @Override public boolean isResizable() { 331 return true; 332 } 333 334 /** 335 * Invoked by the {@code SwingNode}'s parent during layout to set the {@code SwingNode}'s 336 * width and height. <b>Applications should not invoke this method directly</b>. 337 * If an application needs to directly set the size of the {@code SwingNode}, it should 338 * set the Swing component's minimum/preferred/maximum size constraints which will 339 * be propagated correspondingly to the {@code SwingNode} and it's parent will honor those 340 * settings during layout. 341 * 342 * @param width the target layout bounds width 343 * @param height the target layout bounds height 344 */ 345 @Override public void resize(final double width, final double height) { 346 super.resize(width, height); 347 if (width != this.width || height != this.height) { 348 this.width = width; 349 this.height = height; 350 impl_geomChanged(); 351 impl_markDirty(DirtyBits.NODE_GEOMETRY); 352 SwingFXUtils.runOnEDT(() -> { 353 if (lwFrame != null) { 354 lwFrame.setSize((int) width, (int) height); 355 } 356 }); 357 } 358 } 359 360 /** 361 * Returns the {@code SwingNode}'s preferred width for use in layout calculations. 362 * This value corresponds to the preferred width of the Swing component. 363 * 364 * @return the preferred width that the node should be resized to during layout 365 */ 366 @Override 367 public double prefWidth(double height) { 368 return prefWidth; 369 } 370 371 /** 372 * Returns the {@code SwingNode}'s preferred height for use in layout calculations. 373 * This value corresponds to the preferred height of the Swing component. 374 * 375 * @return the preferred height that the node should be resized to during layout 376 */ 377 @Override 378 public double prefHeight(double width) { 379 return prefHeight; 380 } 381 382 /** 383 * Returns the {@code SwingNode}'s maximum width for use in layout calculations. 384 * This value corresponds to the maximum width of the Swing component. 385 * 386 * @return the maximum width that the node should be resized to during layout 387 */ 388 @Override public double maxWidth(double height) { 389 return maxWidth; 390 } 391 392 /** 393 * Returns the {@code SwingNode}'s maximum height for use in layout calculations. 394 * This value corresponds to the maximum height of the Swing component. 395 * 396 * @return the maximum height that the node should be resized to during layout 397 */ 398 @Override public double maxHeight(double width) { 399 return maxHeight; 400 } 401 402 /** 403 * Returns the {@code SwingNode}'s minimum width for use in layout calculations. 404 * This value corresponds to the minimum width of the Swing component. 405 * 406 * @return the minimum width that the node should be resized to during layout 407 */ 408 @Override public double minWidth(double height) { 409 return minWidth; 410 } 411 412 /** 413 * Returns the {@code SwingNode}'s minimum height for use in layout calculations. 414 * This value corresponds to the minimum height of the Swing component. 415 * 416 * @return the minimum height that the node should be resized to during layout 417 */ 418 @Override public double minHeight(double width) { 419 return minHeight; 420 } 421 422 @Override 423 protected boolean impl_computeContains(double localX, double localY) { 424 return true; 425 } 426 427 private final InvalidationListener locationListener = observable -> { 428 locateLwFrame(); 429 }; 430 431 private final ChangeListener<Screen> screenListener = (observable, oldValue, newValue) -> { 432 SwingNode.this.scale = Math.round(ScreenHelper.getScreenAccessor().getScale(newValue)); 433 setLwFrameScale(SwingNode.this.scale); 434 }; 435 436 private final EventHandler<FocusUngrabEvent> ungrabHandler = event -> { 437 if (!skipBackwardUnrgabNotification) { 438 AccessController.doPrivileged(new PostEventAction(new UngrabEvent(lwFrame))); 439 } 440 }; 441 442 private final ChangeListener<Boolean> windowVisibleListener = (observable, oldValue, newValue) -> { 443 if (!newValue) { 444 disposeLwFrame(); 445 } else { 446 setContent(content); 447 } 448 }; 449 450 private final ChangeListener<Window> sceneWindowListener = (observable, oldValue, newValue) -> { 451 if (oldValue != null) { 452 removeWindowListeners(oldValue); 453 } 454 if (newValue != null) { 455 addWindowListeners(newValue); 456 } 457 }; 458 459 private void removeSceneListeners(Scene scene) { 460 Window window = scene.getWindow(); 461 if (window != null) { 462 removeWindowListeners(window); 463 } 464 scene.windowProperty().removeListener(sceneWindowListener); 465 } 466 467 private void addSceneListeners(final Scene scene) { 468 Window window = scene.getWindow(); 469 if (window != null) { 470 addWindowListeners(window); 471 } 472 scene.windowProperty().addListener(sceneWindowListener); 473 } 474 475 private void addWindowListeners(final Window window) { 476 window.xProperty().addListener(locationListener); 477 window.yProperty().addListener(locationListener); 478 window.addEventHandler(FocusUngrabEvent.FOCUS_UNGRAB, ungrabHandler); 479 window.showingProperty().addListener(windowVisibleListener); 480 WindowHelper.getWindowAccessor().screenProperty(window).addListener(screenListener); 481 482 this.scale = Math.round(ScreenHelper.getScreenAccessor().getScale( 483 WindowHelper.getWindowAccessor().screenProperty(window).get())); 484 setLwFrameScale(this.scale); 485 } 486 487 private void removeWindowListeners(final Window window) { 488 window.xProperty().removeListener(locationListener); 489 window.yProperty().removeListener(locationListener); 490 window.removeEventHandler(FocusUngrabEvent.FOCUS_UNGRAB, ungrabHandler); 491 window.showingProperty().removeListener(windowVisibleListener); 492 WindowHelper.getWindowAccessor().screenProperty(window).removeListener(screenListener); 493 } 494 495 @Override 496 protected NGNode impl_createPeer() { 497 peer = new NGExternalNode(); 498 peer.setLock(paintLock); 499 for (Runnable request : peerRequests) { 500 request.run(); 501 } 502 peerRequests = null; 503 504 if (getScene() != null) { 505 addSceneListeners(getScene()); 506 } 507 508 sceneProperty().addListener((observable, oldValue, newValue) -> { 509 if (oldValue != null) { 510 // Removed from scene 511 removeSceneListeners(oldValue); 512 disposeLwFrame(); 513 } 514 if (newValue != null) { 515 // Added to another scene 516 if (content != null && lwFrame == null) { 517 setContent(content); 518 } 519 addSceneListeners(newValue); 520 } 521 }); 522 523 impl_treeVisibleProperty().addListener((observable, oldValue, newValue) -> { 524 setLwFrameVisible(newValue); 525 }); 526 527 return peer; 528 } 529 530 @Override 531 public void impl_updatePeer() { 532 super.impl_updatePeer(); 533 534 if (impl_isDirty(DirtyBits.NODE_VISIBLE) 535 || impl_isDirty(DirtyBits.NODE_BOUNDS)) { 536 locateLwFrame(); // initialize location 537 } 538 if (impl_isDirty(DirtyBits.NODE_CONTENTS)) { 539 peer.markContentDirty(); 540 } 541 } 542 543 /** 544 * Calls JLightweightFrame.setHostBounds. 545 * Must be called on EDT only. 546 */ 547 private static final OptionalMethod<JLightweightFrame> jlfSetHostBounds = 548 new OptionalMethod<>(JLightweightFrame.class, "setHostBounds", 549 Integer.TYPE, Integer.TYPE, Integer.TYPE, Integer.TYPE); 550 551 private void locateLwFrame() { 552 if (getScene() == null 553 || lwFrame == null 554 || getScene().getWindow() == null 555 || !getScene().getWindow().isShowing()) { 556 // Not initialized yet. Skip the update to set the real values later 557 return; 558 } 559 final Point2D loc = localToScene(0, 0); 560 final int windowX = (int)getScene().getWindow().getX(); 561 final int windowY = (int)getScene().getWindow().getY(); 562 final int windowW = (int)getScene().getWindow().getWidth(); 563 final int windowH = (int)getScene().getWindow().getHeight(); 564 final int sceneX = (int)getScene().getX(); 565 final int sceneY = (int)getScene().getY(); 566 567 SwingFXUtils.runOnEDT(() -> { 568 if (lwFrame != null) { 569 lwFrame.setLocation(windowX + sceneX + (int) loc.getX(), 570 windowY + sceneY + (int) loc.getY()); 571 jlfSetHostBounds.invoke(lwFrame, windowX, windowY, 572 windowW, windowH); 573 } 574 }); 575 } 576 577 private void activateLwFrame(final boolean activate) { 578 if (lwFrame == null) { 579 return; 580 } 581 SwingFXUtils.runOnEDT(() -> { 582 if (lwFrame != null) { 583 lwFrame.emulateActivation(activate); 584 } 585 }); 586 } 587 588 private void disposeLwFrame() { 589 if (lwFrame == null) { 590 return; 591 } 592 SwingFXUtils.runOnEDT(() -> { 593 if (lwFrame != null) { 594 lwFrame.dispose(); 595 lwFrame = null; 596 } 597 }); 598 } 599 600 private void setLwFrameVisible(final boolean visible) { 601 if (lwFrame == null) { 602 return; 603 } 604 SwingFXUtils.runOnEDT(() -> { 605 if (lwFrame != null) { 606 lwFrame.setVisible(visible); 607 } 608 }); 609 } 610 611 private void setLwFrameScale(final int scale) { 612 if (lwFrame == null) { 613 return; 614 } 615 SwingFXUtils.runOnEDT(new Runnable() { 616 @Override 617 public void run() { 618 if (lwFrame != null) { 619 jlfNotifyDisplayChanged.invoke(lwFrame, scale); 620 } 621 } 622 }); 623 } 624 625 @Override 626 public BaseBounds impl_computeGeomBounds(BaseBounds bounds, BaseTransform tx) { 627 bounds.deriveWithNewBounds(0, 0, 0, (float)width, (float)height, 0); 628 tx.transform(bounds, bounds); 629 return bounds; 630 } 631 632 @Override 633 public Object impl_processMXNode(MXNodeAlgorithm alg, MXNodeAlgorithmContext ctx) { 634 return alg.processLeafNode(this, ctx); 635 } 636 637 private class SwingNodeContent implements LightweightContent { 638 private JComponent comp; 639 public SwingNodeContent(JComponent comp) { 640 this.comp = comp; 641 } 642 @Override 643 public JComponent getComponent() { 644 return comp; 645 } 646 @Override 647 public void paintLock() { 648 paintLock.lock(); 649 } 650 @Override 651 public void paintUnlock() { 652 paintLock.unlock(); 653 } 654 655 // Note: we skip @Override annotation and implement both pre-hiDPI and post-hiDPI versions 656 // of the method for compatibility. 657 public void imageBufferReset(int[] data, int x, int y, int width, int height, int linestride) { 658 imageBufferReset(data, x, y, width, height, linestride, 1); 659 } 660 public void imageBufferReset(int[] data, int x, int y, int width, int height, int linestride, int scale) { 661 SwingNode.this.setImageBuffer(data, x, y, width, height, linestride, scale); 662 } 663 @Override 664 public void imageReshaped(int x, int y, int width, int height) { 665 SwingNode.this.setImageBounds(x, y, width, height); 666 } 667 @Override 668 public void imageUpdated(int dirtyX, int dirtyY, int dirtyWidth, int dirtyHeight) { 669 SwingNode.this.repaintDirtyRegion(dirtyX, dirtyY, dirtyWidth, dirtyHeight); 670 } 671 @Override 672 public void focusGrabbed() { 673 SwingFXUtils.runOnFxThread(() -> { 674 // On X11 grab is limited to a single XDisplay connection, 675 // so we can't delegate it to another GUI toolkit. 676 if (PlatformUtil.isLinux()) return; 677 678 if (getScene() != null && 679 getScene().getWindow() != null && 680 getScene().getWindow().impl_getPeer() != null) { 681 getScene().getWindow().impl_getPeer().grabFocus(); 682 grabbed = true; 683 } 684 }); 685 } 686 @Override 687 public void focusUngrabbed() { 688 SwingFXUtils.runOnFxThread(() -> { 689 ungrabFocus(false); 690 }); 691 } 692 @Override 693 public void preferredSizeChanged(final int width, final int height) { 694 SwingFXUtils.runOnFxThread(() -> { 695 SwingNode.this.prefWidth = width; 696 SwingNode.this.prefHeight = height; 697 SwingNode.this.impl_notifyLayoutBoundsChanged(); 698 }); 699 } 700 @Override 701 public void maximumSizeChanged(final int width, final int height) { 702 SwingFXUtils.runOnFxThread(() -> { 703 SwingNode.this.maxWidth = width; 704 SwingNode.this.maxHeight = height; 705 SwingNode.this.impl_notifyLayoutBoundsChanged(); 706 }); 707 } 708 @Override 709 public void minimumSizeChanged(final int width, final int height) { 710 SwingFXUtils.runOnFxThread(() -> { 711 SwingNode.this.minWidth = width; 712 SwingNode.this.minHeight = height; 713 SwingNode.this.impl_notifyLayoutBoundsChanged(); 714 }); 715 } 716 717 public void setCursor(Cursor cursor) { 718 SwingFXUtils.runOnFxThread(() -> { 719 SwingNode.this.setCursor(SwingCursors.embedCursorToCursor(cursor)); 720 }); 721 } 722 } 723 724 private void ungrabFocus(boolean postUngrabEvent) { 725 // On X11 grab is limited to a single XDisplay connection, 726 // so we can't delegate it to another GUI toolkit. 727 if (PlatformUtil.isLinux()) return; 728 729 if (grabbed && 730 getScene() != null && 731 getScene().getWindow() != null && 732 getScene().getWindow().impl_getPeer() != null) 733 { 734 skipBackwardUnrgabNotification = !postUngrabEvent; 735 getScene().getWindow().impl_getPeer().ungrabFocus(); 736 skipBackwardUnrgabNotification = false; 737 grabbed = false; 738 } 739 } 740 741 private class PostEventAction implements PrivilegedAction<Void> { 742 private AWTEvent event; 743 public PostEventAction(AWTEvent event) { 744 this.event = event; 745 } 746 @Override 747 public Void run() { 748 EventQueue eq = Toolkit.getDefaultToolkit().getSystemEventQueue(); 749 eq.postEvent(event); 750 return null; 751 } 752 } 753 754 private class SwingMouseEventHandler implements EventHandler<MouseEvent> { 755 private final Set<MouseButton> mouseClickedAllowed = new HashSet<>(); 756 757 @Override 758 public void handle(MouseEvent event) { 759 JLightweightFrame frame = lwFrame; 760 if (frame == null) { 761 return; 762 } 763 int swingID = SwingEvents.fxMouseEventTypeToMouseID(event); 764 if (swingID < 0) { 765 return; 766 } 767 768 // Prevent ancestors of the SwingNode from stealing the focus 769 event.consume(); 770 771 final EventType<?> type = event.getEventType(); 772 if (type == MouseEvent.MOUSE_PRESSED) { 773 mouseClickedAllowed.add(event.getButton()); 774 } else if (type == MouseEvent.MOUSE_RELEASED) { 775 // RELEASED comes before CLICKED, so we don't remove the button from the set 776 //mouseClickedAllowed.remove(event.getButton()); 777 } else if (type == MouseEvent.MOUSE_DRAGGED) { 778 // This is what AWT/Swing do 779 mouseClickedAllowed.clear(); 780 } else if (type == MouseEvent.MOUSE_CLICKED) { 781 if (event.getClickCount() == 1 && !mouseClickedAllowed.contains(event.getButton())) { 782 // RT-34610: In FX, CLICKED events are generated even after dragging the mouse pointer 783 // Note that this is only relevant for single clicks. Double clicks use a smudge factor. 784 return; 785 } 786 mouseClickedAllowed.remove(event.getButton()); 787 } 788 int swingModifiers = SwingEvents.fxMouseModsToMouseMods(event); 789 boolean swingPopupTrigger = event.isPopupTrigger(); 790 int swingButton = SwingEvents.fxMouseButtonToMouseButton(event); 791 long swingWhen = System.currentTimeMillis(); 792 java.awt.event.MouseEvent mouseEvent = 793 new java.awt.event.MouseEvent( 794 frame, swingID, swingWhen, swingModifiers, 795 (int)event.getX(), (int)event.getY(), (int)event.getScreenX(), (int)event.getSceneY(), 796 event.getClickCount(), swingPopupTrigger, swingButton); 797 AccessController.doPrivileged(new PostEventAction(mouseEvent)); 798 } 799 } 800 801 private class SwingScrollEventHandler implements EventHandler<ScrollEvent> { 802 @Override 803 public void handle(ScrollEvent event) { 804 JLightweightFrame frame = lwFrame; 805 if (frame == null) { 806 return; 807 } 808 809 int swingModifiers = SwingEvents.fxScrollModsToMouseWheelMods(event); 810 final boolean isShift = (swingModifiers & InputEvent.SHIFT_DOWN_MASK) != 0; 811 812 // Vertical scroll. 813 if (!isShift && event.getDeltaY() != 0.0) { 814 sendMouseWheelEvent(frame, (int)event.getX(), (int)event.getY(), 815 swingModifiers, event.getDeltaY() / event.getMultiplierY()); 816 } 817 // Horizontal scroll or shirt+vertical scroll. 818 final double delta = isShift && event.getDeltaY() != 0.0 819 ? event.getDeltaY() / event.getMultiplierY() 820 : event.getDeltaX() / event.getMultiplierX(); 821 if (delta != 0.0) { 822 swingModifiers |= InputEvent.SHIFT_DOWN_MASK; 823 sendMouseWheelEvent(frame, (int)event.getX(), (int)event.getY(), 824 swingModifiers, delta); 825 } 826 } 827 828 private void sendMouseWheelEvent(Component source, int x, int y, int swingModifiers, double delta) { 829 int wheelRotation = (int) delta; 830 int signum = (int) Math.signum(delta); 831 if (signum * delta < 1) { 832 wheelRotation = signum; 833 } 834 MouseWheelEvent mouseWheelEvent = 835 new MouseWheelEvent(source, java.awt.event.MouseEvent.MOUSE_WHEEL, 836 System.currentTimeMillis(), swingModifiers, x, y, 0, 0, 837 0, false, MouseWheelEvent.WHEEL_UNIT_SCROLL, 1 , -wheelRotation); 838 AccessController.doPrivileged(new PostEventAction(mouseWheelEvent)); 839 } 840 } 841 842 private class SwingKeyEventHandler implements EventHandler<KeyEvent> { 843 @Override 844 public void handle(KeyEvent event) { 845 JLightweightFrame frame = lwFrame; 846 if (frame == null) { 847 return; 848 } 849 if (event.getCharacter().isEmpty()) { 850 // TODO: should we post an "empty" character? 851 return; 852 } 853 // Don't let Arrows, Tab, Shift+Tab traverse focus out. 854 if (event.getCode() == KeyCode.LEFT || 855 event.getCode() == KeyCode.RIGHT || 856 event.getCode() == KeyCode.UP || 857 event.getCode() == KeyCode.DOWN || 858 event.getCode() == KeyCode.TAB) 859 { 860 event.consume(); 861 } 862 863 int swingID = SwingEvents.fxKeyEventTypeToKeyID(event); 864 if (swingID < 0) { 865 return; 866 } 867 int swingModifiers = SwingEvents.fxKeyModsToKeyMods(event); 868 int swingKeyCode = event.getCode().impl_getCode(); 869 char swingChar = event.getCharacter().charAt(0); 870 871 // A workaround. Some swing L&F's process mnemonics on KEY_PRESSED, 872 // for which swing provides a keychar. Extracting it from the text. 873 if (event.getEventType() == javafx.scene.input.KeyEvent.KEY_PRESSED) { 874 String text = event.getText(); 875 if (text.length() == 1) { 876 swingChar = text.charAt(0); 877 } 878 } 879 long swingWhen = System.currentTimeMillis(); 880 java.awt.event.KeyEvent keyEvent = new java.awt.event.KeyEvent( 881 frame, swingID, swingWhen, swingModifiers, 882 swingKeyCode, swingChar); 883 AccessController.doPrivileged(new PostEventAction(keyEvent)); 884 } 885 } 886 }