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  *         &#064;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  *                 &#064;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 }