1 /*
   2  * Copyright (c) 2018, 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 com.sun.javafx.embed.swing.newimpl;
  27 
  28 import javafx.geometry.Point2D;
  29 import javafx.beans.InvalidationListener;
  30 import javafx.beans.value.ChangeListener;
  31 import javafx.stage.Window;
  32 import javafx.scene.Scene;
  33 import javafx.scene.input.KeyEvent;
  34 import javafx.scene.input.MouseButton;
  35 import javafx.scene.input.ScrollEvent;
  36 import java.awt.event.MouseWheelEvent;
  37 import javafx.event.EventHandler;
  38 import java.awt.EventQueue;
  39 import java.awt.Toolkit;
  40 import java.lang.ref.WeakReference;
  41 import java.lang.reflect.Method;
  42 import java.awt.Component;
  43 import java.awt.AWTEvent;
  44 import java.awt.Cursor;
  45 import java.nio.IntBuffer;
  46 import java.awt.event.WindowFocusListener;
  47 import java.awt.event.MouseEvent;
  48 import java.awt.event.WindowEvent;
  49 import java.awt.dnd.DragGestureEvent;
  50 import java.awt.dnd.DragGestureRecognizer;
  51 import java.awt.dnd.DragGestureListener;
  52 import java.awt.dnd.DragSource;
  53 import java.awt.dnd.DropTarget;
  54 import java.awt.dnd.InvalidDnDOperationException;
  55 import javax.swing.JComponent;
  56 import javax.swing.Timer;
  57 import java.security.PrivilegedAction;
  58 import java.util.ArrayList;
  59 import java.util.List;
  60 import java.util.Set;
  61 import java.util.concurrent.locks.ReentrantLock;
  62 import com.sun.javafx.stage.WindowHelper;
  63 import com.sun.javafx.stage.FocusUngrabEvent;
  64 import com.sun.javafx.sg.prism.NGNode;
  65 import com.sun.javafx.sg.prism.NGExternalNode;
  66 import com.sun.javafx.geom.BaseBounds;
  67 import com.sun.javafx.geom.transform.BaseTransform;
  68 import com.sun.javafx.scene.DirtyBits;
  69 import com.sun.javafx.embed.swing.DisposerRecord;
  70 import javafx.embed.swing.SwingNode;
  71 import com.sun.javafx.PlatformUtil;
  72 import com.sun.javafx.scene.NodeHelper;
  73 
  74 import com.sun.javafx.util.Utils;
  75 import jdk.swing.interop.LightweightFrameWrapper;
  76 import jdk.swing.interop.LightweightContentWrapper;
  77 import jdk.swing.interop.DragSourceContextWrapper;
  78 import com.sun.javafx.embed.swing.SwingNodeInterop;
  79 import com.sun.javafx.embed.swing.FXDnD;
  80 import com.sun.javafx.embed.swing.SwingCursors;
  81 import com.sun.javafx.embed.swing.SwingNodeHelper;
  82 
  83 public class SwingNodeInteropN extends SwingNodeInterop {
  84 
  85     private volatile LightweightFrameWrapper lwFrame;
  86 
  87     /**
  88      * Calls LightweightFrameWrapper.notifyDisplayChanged.
  89      * Must be called on EDT only.
  90      */
  91     private static OptionalMethod<LightweightFrameWrapper> jlfNotifyDisplayChanged;
  92     private static Class lwFrameWrapperClass = null;
  93     private static native void overrideNativeWindowHandle(Class lwFrameWrapperClass,
  94                                   LightweightFrameWrapper lwFrame, long handle,
  95                                                     Runnable closeWindow);
  96     static {
  97         jlfNotifyDisplayChanged = new OptionalMethod<>(LightweightFrameWrapper.class,
  98                 "notifyDisplayChanged", Double.TYPE, Double.TYPE);
  99         if (!jlfNotifyDisplayChanged.isSupported()) {
 100             jlfNotifyDisplayChanged = new OptionalMethod<>(
 101                   LightweightFrameWrapper.class,"notifyDisplayChanged", Integer.TYPE);
 102         }
 103 
 104         try {
 105             lwFrameWrapperClass = Class.forName("jdk.swing.interop.LightweightFrameWrapper");
 106         } catch (Throwable t) {}
 107 
 108         Utils.loadNativeSwingLibrary();
 109     }
 110 
 111     public LightweightFrameWrapper createLightweightFrame() {
 112         lwFrame = new LightweightFrameWrapper();
 113         return lwFrame;
 114     }
 115 
 116     public LightweightFrameWrapper getLightweightFrame() { return lwFrame; }
 117 
 118     public MouseEvent createMouseEvent(Object frame,
 119                             int swingID, long swingWhen, int swingModifiers,
 120                             int relX, int relY, int absX, int absY,
 121                             int clickCount, boolean swingPopupTrigger,
 122                             int swingButton) {
 123         LightweightFrameWrapper lwFrame = (LightweightFrameWrapper)frame;
 124         return lwFrame.createMouseEvent(lwFrame, swingID,
 125                 swingWhen, swingModifiers, relX, relY, absX, absY,
 126                 clickCount, swingPopupTrigger, swingButton);
 127     }
 128 
 129     public MouseWheelEvent createMouseWheelEvent(Object frame,
 130                             int swingModifiers, int x, int y, int wheelRotation) {
 131         LightweightFrameWrapper lwFrame = (LightweightFrameWrapper)frame;
 132         return lwFrame.createMouseWheelEvent(lwFrame,
 133                           swingModifiers, x, y, wheelRotation);
 134     }
 135 
 136     public java.awt.event.KeyEvent createKeyEvent(Object frame,
 137                                    int swingID, long swingWhen,
 138                                    int swingModifiers,
 139                                    int swingKeyCode, char swingChar) {
 140         LightweightFrameWrapper lwFrame = (LightweightFrameWrapper)frame;
 141         return lwFrame.createKeyEvent(lwFrame, swingID,
 142                     swingWhen, swingModifiers, swingKeyCode,
 143                 swingChar);
 144     }
 145 
 146     public AWTEvent createUngrabEvent(Object frame) {
 147         LightweightFrameWrapper lwFrame = (LightweightFrameWrapper)frame;
 148         return lwFrame.createUngrabEvent(lwFrame);
 149     }
 150 
 151     public void overrideNativeWindowHandle(Object frame, long handle, Runnable closeWindow) {
 152         LightweightFrameWrapper lwFrame = (LightweightFrameWrapper)frame;
 153         overrideNativeWindowHandle(lwFrameWrapperClass, lwFrame, handle, closeWindow);
 154     }
 155 
 156     public void notifyDisplayChanged(Object frame, double scaleX, double scaleY) {
 157         LightweightFrameWrapper lwFrame = (LightweightFrameWrapper)frame;
 158         if (jlfNotifyDisplayChanged.isIntegerApi()) {
 159             jlfNotifyDisplayChanged.invoke(lwFrame,
 160                     (int) Math.round(scaleX));
 161         } else {
 162             jlfNotifyDisplayChanged.invoke(lwFrame,
 163                             scaleX, scaleY);
 164         }
 165     }
 166 
 167     public void setHostBounds(Object frame, int windowX, int windowY, int windowW, int windowH) {
 168         LightweightFrameWrapper lwFrame = (LightweightFrameWrapper)frame;
 169         jlfSetHostBounds.invoke(lwFrame, windowX, windowY, windowW, windowH);
 170     }
 171 
 172     public void setContent(Object frame, Object cnt) {
 173         LightweightFrameWrapper lwFrame = (LightweightFrameWrapper)frame;
 174         LightweightContentWrapper content = (LightweightContentWrapper)cnt;
 175         lwFrame.setContent(content);
 176     }
 177 
 178     public void setVisible(Object frame, boolean visible) {
 179         LightweightFrameWrapper lwFrame = (LightweightFrameWrapper)frame;
 180         lwFrame.setVisible(visible);
 181     }
 182 
 183     public void setBounds(Object frame, int frameX, int frameY, int frameW, int frameH) {
 184         LightweightFrameWrapper lwFrame = (LightweightFrameWrapper)frame;
 185         lwFrame.setBounds(frameX, frameY, frameW, frameH);
 186     }
 187 
 188     public LightweightContentWrapper createSwingNodeContent(JComponent content, SwingNode node) {
 189         return (LightweightContentWrapper)new SwingNodeContent(content, node);
 190     }
 191 
 192     public DisposerRecord createSwingNodeDisposer(Object frame) {
 193         LightweightFrameWrapper lwFrame = (LightweightFrameWrapper)frame;
 194         return new SwingNodeDisposer(lwFrame);
 195     }
 196 
 197     private static final class OptionalMethod<T> {
 198         private final Method method;
 199         private final boolean isIntegerAPI;
 200 
 201         OptionalMethod(Class<T> cls, String name, Class<?>... args) {
 202             Method m;
 203             try {
 204                 m = cls.getMethod(name, args);
 205             } catch (NoSuchMethodException ignored) {
 206                 // This means we're running with older JDK, simply skip the call
 207                 m = null;
 208             } catch (Throwable ex) {
 209                 throw new RuntimeException("Error when calling " + cls.getName() + ".getMethod('" + name + "').", ex);
 210             }
 211             method = m;
 212             isIntegerAPI = args != null && args.length > 0 &&
 213                                                         args[0] == Integer.TYPE;
 214         }
 215 
 216         public boolean isSupported() {
 217             return method != null;
 218         }
 219 
 220         public boolean isIntegerApi() {
 221             return isIntegerAPI;
 222         }
 223 
 224         public Object invoke(T object, Object... args) {
 225             if (method != null) {
 226                 try {
 227                     return method.invoke(object, args);
 228                 } catch (Throwable ex) {
 229                     throw new RuntimeException("Error when calling " + object.getClass().getName() + "." + method.getName() + "().", ex);
 230                 }
 231             } else {
 232                 return null;
 233             }
 234         }
 235     }
 236 
 237     /**
 238      * Calls LightweightFrameWrapper.setHostBounds.
 239      * Must be called on EDT only.
 240      */
 241     private static final OptionalMethod<LightweightFrameWrapper> jlfSetHostBounds =
 242         new OptionalMethod<>(LightweightFrameWrapper.class, "setHostBounds",
 243                 Integer.TYPE, Integer.TYPE, Integer.TYPE, Integer.TYPE);
 244 
 245     public void emulateActivation(Object frame, boolean activate) {
 246         LightweightFrameWrapper lwFrame = (LightweightFrameWrapper)frame;
 247         lwFrame.emulateActivation(activate);
 248     }
 249 
 250     public void disposeFrame(Object frame) {
 251         LightweightFrameWrapper lwFrame = (LightweightFrameWrapper)frame;
 252         lwFrame.dispose();
 253     }
 254 
 255     public void addWindowFocusListener(Object frame, WindowFocusListener l) {
 256         LightweightFrameWrapper lwFrame = (LightweightFrameWrapper)frame;
 257         lwFrame.addWindowFocusListener(l);
 258     }
 259 
 260     private static class SwingNodeDisposer implements DisposerRecord {
 261          LightweightFrameWrapper lwFrame;
 262 
 263          SwingNodeDisposer(LightweightFrameWrapper ref) {
 264              this.lwFrame = ref;
 265          }
 266          public void dispose() {
 267              if (lwFrame != null) {
 268                  lwFrame.dispose();
 269                  lwFrame = null;
 270              }
 271          }
 272     }
 273 
 274     private static class SwingNodeContent extends LightweightContentWrapper {
 275         private JComponent comp;
 276         private volatile FXDnD dnd;
 277         private WeakReference<SwingNode> swingNodeRef;
 278 
 279         SwingNodeContent(JComponent comp, SwingNode swingNode) {
 280             this.comp = comp;
 281             this.swingNodeRef = new WeakReference<SwingNode>(swingNode);
 282         }
 283         @Override
 284         public JComponent getComponent() {
 285             return comp;
 286         }
 287         @Override
 288         public void paintLock() {
 289             SwingNode swingNode = swingNodeRef.get();
 290             if (swingNode != null) {
 291                 SwingNodeHelper.getPaintLock(swingNode).lock();
 292             }
 293         }
 294         @Override
 295         public void paintUnlock() {
 296             SwingNode swingNode = swingNodeRef.get();
 297             if (swingNode != null) {
 298                 SwingNodeHelper.getPaintLock(swingNode).unlock();
 299             }
 300         }
 301 
 302         @Override
 303         public void imageBufferReset(int[] data, int x, int y, int width, int height, int linestride) {
 304             imageBufferReset(data, x, y, width, height, linestride, 1);
 305         }
 306         //@Override
 307         public void imageBufferReset(int[] data, int x, int y, int width, int height, int linestride, int scale) {
 308             SwingNode swingNode = swingNodeRef.get();
 309             if (swingNode != null) {
 310                 SwingNodeHelper.setImageBuffer(swingNode, data, x, y, width, height, linestride, scale, scale);
 311             }
 312         }
 313         @Override
 314         public void imageBufferReset(int[] data, int x, int y, int width, int height, int linestride, double scaleX, double scaleY) {
 315             SwingNode swingNode = swingNodeRef.get();
 316             if (swingNode != null) {
 317                 SwingNodeHelper.setImageBuffer(swingNode, data, x, y, width, height, linestride, scaleX, scaleY);
 318             }
 319         }
 320         @Override
 321         public void imageReshaped(int x, int y, int width, int height) {
 322             SwingNode swingNode = swingNodeRef.get();
 323             if (swingNode != null) {
 324                 SwingNodeHelper.setImageBounds(swingNode, x, y, width, height);
 325             }
 326         }
 327         @Override
 328         public void imageUpdated(int dirtyX, int dirtyY, int dirtyWidth, int dirtyHeight) {
 329             SwingNode swingNode = swingNodeRef.get();
 330             if (swingNode != null) {
 331                 SwingNodeHelper.repaintDirtyRegion(swingNode, dirtyX, dirtyY, dirtyWidth, dirtyHeight);
 332             }
 333         }
 334         @Override
 335         public void focusGrabbed() {
 336             SwingNodeHelper.runOnFxThread(() -> {
 337                 // On X11 grab is limited to a single XDisplay connection,
 338                 // so we can't delegate it to another GUI toolkit.
 339                 if (PlatformUtil.isLinux()) return;
 340 
 341                 SwingNode swingNode = swingNodeRef.get();
 342                 if (swingNode != null) {
 343                     Scene scene = swingNode.getScene();
 344                     if (scene != null &&
 345                             scene.getWindow() != null &&
 346                             WindowHelper.getPeer(scene.getWindow()) != null) {
 347                         WindowHelper.getPeer(scene.getWindow()).grabFocus();
 348                         SwingNodeHelper.setGrabbed(swingNode, true);
 349                     }
 350                 }
 351             });
 352         }
 353         @Override
 354         public void focusUngrabbed() {
 355             SwingNodeHelper.runOnFxThread(() -> {
 356                 SwingNode swingNode = swingNodeRef.get();
 357                 if (swingNode != null) {
 358                     Scene scene = swingNode.getScene();
 359                     SwingNodeHelper.ungrabFocus(swingNode, false);
 360                 }
 361             });
 362         }
 363         @Override
 364         public void preferredSizeChanged(final int width, final int height) {
 365             SwingNodeHelper.runOnFxThread(() -> {
 366                 SwingNode swingNode = swingNodeRef.get();
 367                 if (swingNode != null) {
 368                     SwingNodeHelper.setSwingPrefWidth(swingNode, width);
 369                     SwingNodeHelper.setSwingPrefHeight(swingNode, height);
 370                     NodeHelper.notifyLayoutBoundsChanged(swingNode);
 371                 }
 372             });
 373         }
 374         @Override
 375         public void maximumSizeChanged(final int width, final int height) {
 376             SwingNodeHelper.runOnFxThread(() -> {
 377                 SwingNode swingNode = swingNodeRef.get();
 378                 if (swingNode != null) {
 379                     SwingNodeHelper.setSwingMaxWidth(swingNode, width);
 380                     SwingNodeHelper.setSwingMaxHeight(swingNode, height);
 381                     NodeHelper.notifyLayoutBoundsChanged(swingNode);
 382                 }
 383             });
 384         }
 385         @Override
 386         public void minimumSizeChanged(final int width, final int height) {
 387             SwingNodeHelper.runOnFxThread(() -> {
 388                 SwingNode swingNode = swingNodeRef.get();
 389                 if (swingNode != null) {
 390                     SwingNodeHelper.setSwingMinWidth(swingNode, width);
 391                     SwingNodeHelper.setSwingMinHeight(swingNode, height);
 392                     NodeHelper.notifyLayoutBoundsChanged(swingNode);
 393                 }
 394             });
 395         }
 396 
 397         //@Override
 398         public void setCursor(Cursor cursor) {
 399             SwingNodeHelper.runOnFxThread(() -> {
 400                 SwingNode swingNode = swingNodeRef.get();
 401                 if (swingNode != null) {
 402                     swingNode.setCursor(SwingCursors.embedCursorToCursor(cursor));
 403                 }
 404             });
 405         }
 406 
 407         private void initDnD() {
 408             // This is a part of AWT API, so the method may be invoked on any thread
 409             synchronized (SwingNodeContent.this) {
 410                 if (this.dnd == null) {
 411                     SwingNode swingNode = swingNodeRef.get();
 412                     if (swingNode != null) {
 413                         this.dnd = new FXDnD(swingNode);
 414                     }
 415                 }
 416             }
 417         }
 418 
 419         @Override
 420         public synchronized <T extends DragGestureRecognizer> T createDragGestureRecognizer(
 421                 Class<T> abstractRecognizerClass,
 422                 DragSource ds, Component c, int srcActions,
 423                 DragGestureListener dgl)
 424         {
 425             initDnD();
 426             return dnd.createDragGestureRecognizer(abstractRecognizerClass, ds, c, srcActions, dgl);
 427         }
 428 
 429         @Override
 430         public DragSourceContextWrapper createDragSourceContext(DragGestureEvent dge) throws InvalidDnDOperationException
 431         {
 432             initDnD();
 433             return (DragSourceContextWrapper)dnd.createDragSourceContext(dge);
 434         }
 435 
 436         @Override
 437         public void addDropTarget(DropTarget dt) {
 438             initDnD();
 439             dnd.addDropTarget(dt);
 440         }
 441 
 442         @Override
 443         public void removeDropTarget(DropTarget dt) {
 444             initDnD();
 445             dnd.removeDropTarget(dt);
 446         }
 447     }
 448 
 449 }