1 /* 2 * Copyright (c) 2013, 2015, 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 /* 283 * Called on EDT 284 */ 285 void setImageBuffer(final int[] data, 286 final int x, final int y, 287 final int w, final int h, 288 final int linestride, 289 final int scale) 290 { 291 Runnable r = () -> { 292 Window win = getScene().getWindow(); 293 float uiScale = WindowHelper.getWindowAccessor().getUIScale(win); 294 peer.setImageBuffer(IntBuffer.wrap(data), x, y, w, h, 295 w / uiScale, h / uiScale, linestride, scale); 296 }; 297 SwingFXUtils.runOnFxThread(() -> { 298 if (peer != null) { 299 r.run(); 300 } else { 301 peerRequests.clear(); 302 peerRequests.add(r); 303 } 304 }); 305 } 306 307 /* 308 * Called on EDT 309 */ 310 void setImageBounds(final int x, final int y, final int w, final int h) { 311 Runnable r = () -> { 312 Window win = getScene().getWindow(); 313 float uiScale = WindowHelper.getWindowAccessor().getUIScale(win); 314 peer.setImageBounds(x, y, w, h, w / uiScale, h / uiScale); 315 }; 316 SwingFXUtils.runOnFxThread(() -> { 317 if (peer != null) { 318 r.run(); 319 } else { 320 peerRequests.add(r); 321 } 322 }); 323 } 324 325 /* 326 * Called on EDT 327 */ 328 void repaintDirtyRegion(final int dirtyX, final int dirtyY, final int dirtyWidth, final int dirtyHeight) { 329 Runnable r = () -> { 330 peer.repaintDirtyRegion(dirtyX, dirtyY, dirtyWidth, dirtyHeight); 331 impl_markDirty(DirtyBits.NODE_CONTENTS); 332 }; 333 SwingFXUtils.runOnFxThread(() -> { 334 if (peer != null) { 335 r.run(); 336 } else { 337 peerRequests.add(r); 338 } 339 }); 340 } 341 342 @Override public boolean isResizable() { 343 return true; 344 } 345 346 /** 347 * Invoked by the {@code SwingNode}'s parent during layout to set the {@code SwingNode}'s 348 * width and height. <b>Applications should not invoke this method directly</b>. 349 * If an application needs to directly set the size of the {@code SwingNode}, it should 350 * set the Swing component's minimum/preferred/maximum size constraints which will 351 * be propagated correspondingly to the {@code SwingNode} and it's parent will honor those 352 * settings during layout. 353 * 354 * @param width the target layout bounds width 355 * @param height the target layout bounds height 356 */ 357 @Override public void resize(final double width, final double height) { 358 super.resize(width, height); 359 if (width != this.fxWidth || height != this.fxHeight) { 360 this.fxWidth = width; 361 this.fxHeight = height; 362 impl_geomChanged(); 363 impl_markDirty(DirtyBits.NODE_GEOMETRY); 364 SwingFXUtils.runOnEDT(() -> { 365 if (lwFrame != null) { 366 locateLwFrame(); 367 } 368 }); 369 } 370 } 371 372 /** 373 * Returns the {@code SwingNode}'s preferred width for use in layout calculations. 374 * This value corresponds to the preferred width of the Swing component. 375 * 376 * @return the preferred width that the node should be resized to during layout 377 */ 378 @Override 379 public double prefWidth(double height) { 380 float uiScale = WindowHelper.getWindowAccessor().getUIScale(getScene().getWindow()); 381 return swingPrefWidth / uiScale; 382 } 383 384 /** 385 * Returns the {@code SwingNode}'s preferred height for use in layout calculations. 386 * This value corresponds to the preferred height of the Swing component. 387 * 388 * @return the preferred height that the node should be resized to during layout 389 */ 390 @Override 391 public double prefHeight(double width) { 392 float uiScale = WindowHelper.getWindowAccessor().getUIScale(getScene().getWindow()); 393 return swingPrefHeight / uiScale; 394 } 395 396 /** 397 * Returns the {@code SwingNode}'s maximum width for use in layout calculations. 398 * This value corresponds to the maximum width of the Swing component. 399 * 400 * @return the maximum width that the node should be resized to during layout 401 */ 402 @Override public double maxWidth(double height) { 403 float uiScale = WindowHelper.getWindowAccessor().getUIScale(getScene().getWindow()); 404 return swingMaxWidth / uiScale; 405 } 406 407 /** 408 * Returns the {@code SwingNode}'s maximum height for use in layout calculations. 409 * This value corresponds to the maximum height of the Swing component. 410 * 411 * @return the maximum height that the node should be resized to during layout 412 */ 413 @Override public double maxHeight(double width) { 414 float uiScale = WindowHelper.getWindowAccessor().getUIScale(getScene().getWindow()); 415 return swingMaxHeight / uiScale; 416 } 417 418 /** 419 * Returns the {@code SwingNode}'s minimum width for use in layout calculations. 420 * This value corresponds to the minimum width of the Swing component. 421 * 422 * @return the minimum width that the node should be resized to during layout 423 */ 424 @Override public double minWidth(double height) { 425 float uiScale = WindowHelper.getWindowAccessor().getUIScale(getScene().getWindow()); 426 return swingMinWidth / uiScale; 427 } 428 429 /** 430 * Returns the {@code SwingNode}'s minimum height for use in layout calculations. 431 * This value corresponds to the minimum height of the Swing component. 432 * 433 * @return the minimum height that the node should be resized to during layout 434 */ 435 @Override public double minHeight(double width) { 436 float uiScale = WindowHelper.getWindowAccessor().getUIScale(getScene().getWindow()); 437 return swingMinHeight / uiScale; 438 } 439 440 /** 441 * @treatAsPrivate implementation detail 442 * @deprecated This is an internal API that is not intended for use and will be removed in the next version 443 */ 444 @Deprecated 445 @Override 446 protected boolean impl_computeContains(double localX, double localY) { 447 return true; 448 } 449 450 private final InvalidationListener locationListener = observable -> { 451 locateLwFrame(); 452 }; 453 454 private final EventHandler<FocusUngrabEvent> ungrabHandler = event -> { 455 if (!skipBackwardUnrgabNotification) { 456 if (lwFrame != null) { 457 AccessController.doPrivileged(new PostEventAction(new UngrabEvent(lwFrame))); 458 } 459 } 460 }; 461 462 private final ChangeListener<Boolean> windowVisibleListener = (observable, oldValue, newValue) -> { 463 if (!newValue) { 464 disposeLwFrame(); 465 } else { 466 setContent(content); 467 } 468 }; 469 470 private final ChangeListener<Window> sceneWindowListener = (observable, oldValue, newValue) -> { 471 if (oldValue != null) { 472 removeWindowListeners(oldValue); 473 } 474 if (newValue != null) { 475 addWindowListeners(newValue); 476 } 477 }; 478 479 private void removeSceneListeners(Scene scene) { 480 Window window = scene.getWindow(); 481 if (window != null) { 482 removeWindowListeners(window); 483 } 484 scene.windowProperty().removeListener(sceneWindowListener); 485 } 486 487 private void addSceneListeners(final Scene scene) { 488 Window window = scene.getWindow(); 489 if (window != null) { 490 addWindowListeners(window); 491 } 492 scene.windowProperty().addListener(sceneWindowListener); 493 } 494 495 private void addWindowListeners(final Window window) { 496 window.xProperty().addListener(locationListener); 497 window.yProperty().addListener(locationListener); 498 window.addEventHandler(FocusUngrabEvent.FOCUS_UNGRAB, ungrabHandler); 499 window.showingProperty().addListener(windowVisibleListener); 500 501 this.scale = Math.round(WindowHelper.getWindowAccessor().getRenderScale(window)); 502 setLwFrameScale(this.scale); 503 } 504 505 private void removeWindowListeners(final Window window) { 506 window.xProperty().removeListener(locationListener); 507 window.yProperty().removeListener(locationListener); 508 window.removeEventHandler(FocusUngrabEvent.FOCUS_UNGRAB, ungrabHandler); 509 window.showingProperty().removeListener(windowVisibleListener); 510 } 511 512 /** 513 * @treatAsPrivate implementation detail 514 * @deprecated This is an internal API that is not intended for use and will be removed in the next version 515 */ 516 @Deprecated 517 @Override 518 protected NGNode impl_createPeer() { 519 peer = new NGExternalNode(); 520 peer.setLock(paintLock); 521 for (Runnable request : peerRequests) { 522 request.run(); 523 } 524 peerRequests = null; 525 526 if (getScene() != null) { 527 addSceneListeners(getScene()); 528 } 529 530 sceneProperty().addListener((observable, oldValue, newValue) -> { 531 if (oldValue != null) { 532 // Removed from scene 533 removeSceneListeners(oldValue); 534 disposeLwFrame(); 535 } 536 if (newValue != null) { 537 // Added to another scene 538 if (content != null && lwFrame == null) { 539 setContent(content); 540 } 541 addSceneListeners(newValue); 542 } 543 }); 544 545 impl_treeVisibleProperty().addListener((observable, oldValue, newValue) -> { 546 setLwFrameVisible(newValue); 547 }); 548 549 return peer; 550 } 551 552 /** 553 * @treatAsPrivate implementation detail 554 * @deprecated This is an internal API that is not intended for use and will be removed in the next version 555 */ 556 @Deprecated 557 @Override 558 public void impl_updatePeer() { 559 super.impl_updatePeer(); 560 561 if (impl_isDirty(DirtyBits.NODE_VISIBLE) 562 || impl_isDirty(DirtyBits.NODE_BOUNDS)) { 563 locateLwFrame(); // initialize location 564 } 565 if (impl_isDirty(DirtyBits.NODE_CONTENTS)) { 566 peer.markContentDirty(); 567 } 568 } 569 570 /** 571 * Calls JLightweightFrame.setHostBounds. 572 * Must be called on EDT only. 573 */ 574 private static final OptionalMethod<JLightweightFrame> jlfSetHostBounds = 575 new OptionalMethod<>(JLightweightFrame.class, "setHostBounds", 576 Integer.TYPE, Integer.TYPE, Integer.TYPE, Integer.TYPE); 577 578 private void locateLwFrame() { 579 if (getScene() == null 580 || lwFrame == null 581 || getScene().getWindow() == null 582 || !getScene().getWindow().isShowing()) { 583 // Not initialized yet. Skip the update to set the real values later 584 return; 585 } 586 Window w = getScene().getWindow(); 587 float renderScale = WindowHelper.getWindowAccessor().getRenderScale(w); 588 float uiScale = WindowHelper.getWindowAccessor().getUIScale(w); 589 int lwScale = Math.round(renderScale); 590 boolean sendScale = (this.scale != lwScale); 591 this.scale = lwScale; 592 final Point2D loc = localToScene(0, 0); 593 final int windowX = (int) (w.getX() * uiScale); 594 final int windowY = (int) (w.getY() * uiScale); 595 final int windowW = (int) (w.getWidth() * uiScale); 596 final int windowH = (int) (w.getHeight() * uiScale); 597 final int frameX = (int) Math.round((w.getX() + getScene().getX() + loc.getX()) * uiScale); 598 final int frameY = (int) Math.round((w.getY() + getScene().getY() + loc.getY()) * uiScale); 599 final int frameW = (int) (fxWidth * uiScale); 600 final int frameH = (int) (fxHeight * uiScale); 601 602 SwingFXUtils.runOnEDT(() -> { 603 if (lwFrame != null) { 604 if (sendScale) { 605 jlfNotifyDisplayChanged.invoke(lwFrame, scale); 606 } 607 lwFrame.setSize(frameW, frameH); 608 lwFrame.setLocation(frameX, frameY); 609 jlfSetHostBounds.invoke(lwFrame, windowX, windowY, 610 windowW, windowH); 611 } 612 }); 613 } 614 615 private void activateLwFrame(final boolean activate) { 616 if (lwFrame == null) { 617 return; 618 } 619 SwingFXUtils.runOnEDT(() -> { 620 if (lwFrame != null) { 621 lwFrame.emulateActivation(activate); 622 } 623 }); 624 } 625 626 private void disposeLwFrame() { 627 if (lwFrame == null) { 628 return; 629 } 630 SwingFXUtils.runOnEDT(() -> { 631 if (lwFrame != null) { 632 lwFrame.dispose(); 633 lwFrame = null; 634 } 635 }); 636 } 637 638 private void setLwFrameVisible(final boolean visible) { 639 if (lwFrame == null) { 640 return; 641 } 642 SwingFXUtils.runOnEDT(() -> { 643 if (lwFrame != null) { 644 lwFrame.setVisible(visible); 645 } 646 }); 647 } 648 649 private void setLwFrameScale(final int scale) { 650 if (lwFrame == null) { 651 return; 652 } 653 SwingFXUtils.runOnEDT(new Runnable() { 654 @Override 655 public void run() { 656 if (lwFrame != null) { 657 jlfNotifyDisplayChanged.invoke(lwFrame, scale); 658 } 659 } 660 }); 661 } 662 663 /** 664 * @treatAsPrivate implementation detail 665 * @deprecated This is an internal API that is not intended for use and will be removed in the next version 666 */ 667 @Deprecated 668 @Override 669 public BaseBounds impl_computeGeomBounds(BaseBounds bounds, BaseTransform tx) { 670 bounds.deriveWithNewBounds(0, 0, 0, (float)fxWidth, (float)fxHeight, 0); 671 tx.transform(bounds, bounds); 672 return bounds; 673 } 674 675 /** 676 * @treatAsPrivate implementation detail 677 * @deprecated This is an internal API that is not intended for use and will be removed in the next version 678 */ 679 @Deprecated 680 @Override 681 public Object impl_processMXNode(MXNodeAlgorithm alg, MXNodeAlgorithmContext ctx) { 682 return alg.processLeafNode(this, ctx); 683 } 684 685 private class SwingNodeContent implements LightweightContent { 686 private JComponent comp; 687 private volatile FXDnD dnd; 688 689 public SwingNodeContent(JComponent comp) { 690 this.comp = comp; 691 } 692 @Override 693 public JComponent getComponent() { 694 return comp; 695 } 696 @Override 697 public void paintLock() { 698 paintLock.lock(); 699 } 700 @Override 701 public void paintUnlock() { 702 paintLock.unlock(); 703 } 704 705 // Note: we skip @Override annotation and implement both pre-hiDPI and post-hiDPI versions 706 // of the method for compatibility. 707 //@Override 708 public void imageBufferReset(int[] data, int x, int y, int width, int height, int linestride) { 709 imageBufferReset(data, x, y, width, height, linestride, 1); 710 } 711 //@Override 712 public void imageBufferReset(int[] data, int x, int y, int width, int height, int linestride, int scale) { 713 SwingNode.this.setImageBuffer(data, x, y, width, height, linestride, scale); 714 } 715 @Override 716 public void imageReshaped(int x, int y, int width, int height) { 717 SwingNode.this.setImageBounds(x, y, width, height); 718 } 719 @Override 720 public void imageUpdated(int dirtyX, int dirtyY, int dirtyWidth, int dirtyHeight) { 721 SwingNode.this.repaintDirtyRegion(dirtyX, dirtyY, dirtyWidth, dirtyHeight); 722 } 723 @Override 724 public void focusGrabbed() { 725 SwingFXUtils.runOnFxThread(() -> { 726 // On X11 grab is limited to a single XDisplay connection, 727 // so we can't delegate it to another GUI toolkit. 728 if (PlatformUtil.isLinux()) return; 729 730 if (getScene() != null && 731 getScene().getWindow() != null && 732 getScene().getWindow().impl_getPeer() != null) { 733 getScene().getWindow().impl_getPeer().grabFocus(); 734 grabbed = true; 735 } 736 }); 737 } 738 @Override 739 public void focusUngrabbed() { 740 SwingFXUtils.runOnFxThread(() -> { 741 ungrabFocus(false); 742 }); 743 } 744 @Override 745 public void preferredSizeChanged(final int width, final int height) { 746 SwingFXUtils.runOnFxThread(() -> { 747 SwingNode.this.swingPrefWidth = width; 748 SwingNode.this.swingPrefHeight = height; 749 SwingNode.this.impl_notifyLayoutBoundsChanged(); 750 }); 751 } 752 @Override 753 public void maximumSizeChanged(final int width, final int height) { 754 SwingFXUtils.runOnFxThread(() -> { 755 SwingNode.this.swingMaxWidth = width; 756 SwingNode.this.swingMaxHeight = height; 757 SwingNode.this.impl_notifyLayoutBoundsChanged(); 758 }); 759 } 760 @Override 761 public void minimumSizeChanged(final int width, final int height) { 762 SwingFXUtils.runOnFxThread(() -> { 763 SwingNode.this.swingMinWidth = width; 764 SwingNode.this.swingMinHeight = height; 765 SwingNode.this.impl_notifyLayoutBoundsChanged(); 766 }); 767 } 768 769 //@Override 770 public void setCursor(Cursor cursor) { 771 SwingFXUtils.runOnFxThread(() -> { 772 SwingNode.this.setCursor(SwingCursors.embedCursorToCursor(cursor)); 773 }); 774 } 775 776 private void initDnD() { 777 // This is a part of AWT API, so the method may be invoked on any thread 778 synchronized (SwingNodeContent.this) { 779 if (this.dnd == null) { 780 this.dnd = new FXDnD(SwingNode.this); 781 } 782 } 783 } 784 785 //@Override 786 public synchronized <T extends DragGestureRecognizer> T createDragGestureRecognizer( 787 Class<T> abstractRecognizerClass, 788 DragSource ds, Component c, int srcActions, 789 DragGestureListener dgl) 790 { 791 initDnD(); 792 return dnd.createDragGestureRecognizer(abstractRecognizerClass, ds, c, srcActions, dgl); 793 } 794 795 //@Override 796 public DragSourceContextPeer createDragSourceContextPeer(DragGestureEvent dge) throws InvalidDnDOperationException 797 { 798 initDnD(); 799 return dnd.createDragSourceContextPeer(dge); 800 } 801 802 //@Override 803 public void addDropTarget(DropTarget dt) { 804 initDnD(); 805 dnd.addDropTarget(dt); 806 } 807 808 //@Override 809 public void removeDropTarget(DropTarget dt) { 810 initDnD(); 811 dnd.removeDropTarget(dt); 812 } 813 } 814 815 private void ungrabFocus(boolean postUngrabEvent) { 816 // On X11 grab is limited to a single XDisplay connection, 817 // so we can't delegate it to another GUI toolkit. 818 if (PlatformUtil.isLinux()) return; 819 820 if (grabbed && 821 getScene() != null && 822 getScene().getWindow() != null && 823 getScene().getWindow().impl_getPeer() != null) 824 { 825 skipBackwardUnrgabNotification = !postUngrabEvent; 826 getScene().getWindow().impl_getPeer().ungrabFocus(); 827 skipBackwardUnrgabNotification = false; 828 grabbed = false; 829 } 830 } 831 832 private class PostEventAction implements PrivilegedAction<Void> { 833 private AWTEvent event; 834 public PostEventAction(AWTEvent event) { 835 this.event = event; 836 } 837 @Override 838 public Void run() { 839 EventQueue eq = Toolkit.getDefaultToolkit().getSystemEventQueue(); 840 eq.postEvent(event); 841 return null; 842 } 843 } 844 845 private class SwingMouseEventHandler implements EventHandler<MouseEvent> { 846 private final Set<MouseButton> mouseClickedAllowed = new HashSet<>(); 847 848 @Override 849 public void handle(MouseEvent event) { 850 JLightweightFrame frame = lwFrame; 851 if (frame == null) { 852 return; 853 } 854 int swingID = SwingEvents.fxMouseEventTypeToMouseID(event); 855 if (swingID < 0) { 856 return; 857 } 858 859 // Prevent ancestors of the SwingNode from stealing the focus 860 event.consume(); 861 862 final EventType<?> type = event.getEventType(); 863 if (type == MouseEvent.MOUSE_PRESSED) { 864 mouseClickedAllowed.add(event.getButton()); 865 } else if (type == MouseEvent.MOUSE_RELEASED) { 866 // RELEASED comes before CLICKED, so we don't remove the button from the set 867 //mouseClickedAllowed.remove(event.getButton()); 868 } else if (type == MouseEvent.MOUSE_DRAGGED) { 869 // This is what AWT/Swing do 870 mouseClickedAllowed.clear(); 871 } else if (type == MouseEvent.MOUSE_CLICKED) { 872 if (event.getClickCount() == 1 && !mouseClickedAllowed.contains(event.getButton())) { 873 // RT-34610: In FX, CLICKED events are generated even after dragging the mouse pointer 874 // Note that this is only relevant for single clicks. Double clicks use a smudge factor. 875 return; 876 } 877 mouseClickedAllowed.remove(event.getButton()); 878 } 879 int swingModifiers = SwingEvents.fxMouseModsToMouseMods(event); 880 boolean swingPopupTrigger = event.isPopupTrigger(); 881 int swingButton = SwingEvents.fxMouseButtonToMouseButton(event); 882 long swingWhen = System.currentTimeMillis(); 883 Window win = getScene().getWindow(); 884 float uiScale = WindowHelper.getWindowAccessor().getUIScale(win); 885 int relX = (int) Math.round(event.getX() * uiScale); 886 int relY = (int) Math.round(event.getY() * uiScale); 887 int absX = (int) Math.round(event.getScreenX() * uiScale); 888 int absY = (int) Math.round(event.getScreenY() * uiScale); 889 java.awt.event.MouseEvent mouseEvent = 890 new java.awt.event.MouseEvent( 891 frame, swingID, swingWhen, swingModifiers, 892 relX, relY, absX, absY, 893 event.getClickCount(), swingPopupTrigger, swingButton); 894 AccessController.doPrivileged(new PostEventAction(mouseEvent)); 895 } 896 } 897 898 private class SwingScrollEventHandler implements EventHandler<ScrollEvent> { 899 @Override 900 public void handle(ScrollEvent event) { 901 JLightweightFrame frame = lwFrame; 902 if (frame == null) { 903 return; 904 } 905 906 int swingModifiers = SwingEvents.fxScrollModsToMouseWheelMods(event); 907 final boolean isShift = (swingModifiers & InputEvent.SHIFT_DOWN_MASK) != 0; 908 909 // Vertical scroll. 910 if (!isShift && event.getDeltaY() != 0.0) { 911 sendMouseWheelEvent(frame, event.getX(), event.getY(), 912 swingModifiers, event.getDeltaY() / event.getMultiplierY()); 913 } 914 // Horizontal scroll or shirt+vertical scroll. 915 final double delta = isShift && event.getDeltaY() != 0.0 916 ? event.getDeltaY() / event.getMultiplierY() 917 : event.getDeltaX() / event.getMultiplierX(); 918 if (delta != 0.0) { 919 swingModifiers |= InputEvent.SHIFT_DOWN_MASK; 920 sendMouseWheelEvent(frame, event.getX(), event.getY(), 921 swingModifiers, delta); 922 } 923 } 924 925 private void sendMouseWheelEvent(Component source, double fxX, double fxY, int swingModifiers, double delta) { 926 int wheelRotation = (int) delta; 927 int signum = (int) Math.signum(delta); 928 if (signum * delta < 1) { 929 wheelRotation = signum; 930 } 931 Window w = getScene().getWindow(); 932 float uiScale = WindowHelper.getWindowAccessor().getUIScale(w); 933 int x = (int) Math.round(fxX * uiScale); 934 int y = (int) Math.round(fxY * uiScale); 935 MouseWheelEvent mouseWheelEvent = 936 new MouseWheelEvent(source, java.awt.event.MouseEvent.MOUSE_WHEEL, 937 System.currentTimeMillis(), swingModifiers, x, y, 0, 0, 938 0, false, MouseWheelEvent.WHEEL_UNIT_SCROLL, 1 , -wheelRotation); 939 AccessController.doPrivileged(new PostEventAction(mouseWheelEvent)); 940 } 941 } 942 943 private class SwingKeyEventHandler implements EventHandler<KeyEvent> { 944 @Override 945 public void handle(KeyEvent event) { 946 JLightweightFrame frame = lwFrame; 947 if (frame == null) { 948 return; 949 } 950 if (event.getCharacter().isEmpty()) { 951 // TODO: should we post an "empty" character? 952 return; 953 } 954 // Don't let Arrows, Tab, Shift+Tab traverse focus out. 955 if (event.getCode() == KeyCode.LEFT || 956 event.getCode() == KeyCode.RIGHT || 957 event.getCode() == KeyCode.UP || 958 event.getCode() == KeyCode.DOWN || 959 event.getCode() == KeyCode.TAB) 960 { 961 event.consume(); 962 } 963 964 int swingID = SwingEvents.fxKeyEventTypeToKeyID(event); 965 if (swingID < 0) { 966 return; 967 } 968 int swingModifiers = SwingEvents.fxKeyModsToKeyMods(event); 969 int swingKeyCode = event.getCode().getCode(); 970 char swingChar = event.getCharacter().charAt(0); 971 972 // A workaround. Some swing L&F's process mnemonics on KEY_PRESSED, 973 // for which swing provides a keychar. Extracting it from the text. 974 if (event.getEventType() == javafx.scene.input.KeyEvent.KEY_PRESSED) { 975 String text = event.getText(); 976 if (text.length() == 1) { 977 swingChar = text.charAt(0); 978 } 979 } 980 long swingWhen = System.currentTimeMillis(); 981 java.awt.event.KeyEvent keyEvent = new java.awt.event.KeyEvent( 982 frame, swingID, swingWhen, swingModifiers, 983 swingKeyCode, swingChar); 984 AccessController.doPrivileged(new PostEventAction(keyEvent)); 985 } 986 } 987 }