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