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