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