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 javafx.embed.swing.SwingFXUtils;
  72 import com.sun.javafx.PlatformUtil;
  73 import com.sun.javafx.scene.NodeHelper;
  74 
  75 import com.sun.javafx.util.Utils;
  76 import jdk.swing.interop.LightweightFrameWrapper;
  77 import jdk.swing.interop.LightweightContentWrapper;
  78 import jdk.swing.interop.DragSourceContextWrapper;
  79 import com.sun.javafx.embed.swing.SwingNodeInterop;
  80 import com.sun.javafx.embed.swing.FXDnD;
  81 import com.sun.javafx.embed.swing.SwingCursors;
  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, long handle,
  94                                                     Runnable closeWindow);
  95     static {
  96         jlfNotifyDisplayChanged = new OptionalMethod<>(LightweightFrameWrapper.class,
  97                 "notifyDisplayChanged", Double.TYPE, Double.TYPE);
  98         if (!jlfNotifyDisplayChanged.isSupported()) {
  99             jlfNotifyDisplayChanged = new OptionalMethod<>(
 100                   LightweightFrameWrapper.class,"notifyDisplayChanged", Integer.TYPE);
 101         }
 102 
 103         try {
 104             lwFrameWrapperClass = Class.forName("jdk.swing.interop.LightweightFrameWrapper");
 105         } catch (Throwable t) {}
 106 
 107         Utils.loadNativeSwingLibrary();
 108     }
 109 
 110     public LightweightFrameWrapper createLightweightFrame() {
 111         lwFrame = new LightweightFrameWrapper();
 112         return lwFrame;
 113     }
 114 
 115     public LightweightFrameWrapper getLightweightFrame() { return lwFrame; }
 116 
 117     public MouseEvent createMouseEvent(Object frame,
 118                             int swingID, long swingWhen, int swingModifiers,
 119                             int relX, int relY, int absX, int absY,
 120                             int clickCount, boolean swingPopupTrigger,
 121                             int swingButton) {
 122     LightweightFrameWrapper lwFrame = (LightweightFrameWrapper)frame;
 123         return lwFrame.createMouseEvent(lwFrame, swingID,
 124                 swingWhen, swingModifiers, relX, relY, absX, absY,
 125                 clickCount, swingPopupTrigger, swingButton);
 126     }
 127 
 128     public MouseWheelEvent createMouseWheelEvent(Object frame,
 129                             int swingModifiers, int x, int y, int wheelRotation) {
 130         LightweightFrameWrapper lwFrame = (LightweightFrameWrapper)frame;
 131         return lwFrame.createMouseWheelEvent(lwFrame,
 132                           swingModifiers, x, y, wheelRotation);
 133     }
 134 
 135     public java.awt.event.KeyEvent createKeyEvent(Object frame,
 136                                    int swingID, long swingWhen,
 137                                    int swingModifiers,
 138                                    int swingKeyCode, char swingChar) {
 139     LightweightFrameWrapper lwFrame = (LightweightFrameWrapper)frame;
 140         return lwFrame.createKeyEvent(lwFrame, swingID,
 141                     swingWhen, swingModifiers, swingKeyCode,
 142                 swingChar);
 143     }
 144 
 145     public AWTEvent createUngrabEvent(Object frame) {
 146         LightweightFrameWrapper lwFrame = (LightweightFrameWrapper)frame;
 147         return lwFrame.createUngrabEvent(lwFrame);
 148     }
 149 
 150     public void overrideNativeWindowHandle(long handle, Runnable closeWindow) {
 151         overrideNativeWindowHandle(lwFrameWrapperClass, handle, closeWindow);
 152     }
 153 
 154     public void notifyDisplayChanged(Object frame, Window w) {
 155         LightweightFrameWrapper lwFrame = (LightweightFrameWrapper)frame;
 156         if (jlfNotifyDisplayChanged.isIntegerApi()) {
 157             jlfNotifyDisplayChanged.invoke(lwFrame,
 158                     (int) Math.round(w.getRenderScaleX()));
 159         } else {
 160             jlfNotifyDisplayChanged.invoke(lwFrame,
 161                             w.getRenderScaleX(),
 162                             w.getRenderScaleY());
 163         }
 164     }
 165 
 166     public void setHostBounds(Object frame, Window w) {
 167         LightweightFrameWrapper lwFrame = (LightweightFrameWrapper)frame;
 168         jlfSetHostBounds.invoke(lwFrame, (int)w.getX(), (int)w.getY(), (int)w.getWidth(), (int)w.getHeight());
 169     }
 170 
 171     public void setContent(Object frame, Object cnt) {
 172         LightweightFrameWrapper lwFrame = (LightweightFrameWrapper)frame;
 173         LightweightContentWrapper content = (LightweightContentWrapper)cnt;
 174         lwFrame.setContent(content);
 175     }
 176 
 177     public void setVisible(Object frame, boolean visible) {
 178         LightweightFrameWrapper lwFrame = (LightweightFrameWrapper)frame;
 179         lwFrame.setVisible(visible);
 180     }
 181 
 182     public void setBounds(Object frame, int frameX, int frameY, int frameW, int frameH) {
 183         LightweightFrameWrapper lwFrame = (LightweightFrameWrapper)frame;
 184         lwFrame.setBounds(frameX, frameY, frameW, frameH);
 185     }
 186 
 187     public LightweightContentWrapper createSwingNodeContent(JComponent content, SwingNode node) {
 188         return (LightweightContentWrapper)new SwingNodeContent(content, node);
 189     }
 190 
 191     public DisposerRecord createSwingNodeDisposer(Object frame) {
 192         LightweightFrameWrapper lwFrame = (LightweightFrameWrapper)frame;
 193         return new SwingNodeDisposer(lwFrame);
 194     }
 195 
 196     private Timer deactivate; // lwFrame deactivate delay for Linux
 197 
 198     private static final class OptionalMethod<T> {
 199         private final Method method;
 200         private final boolean isIntegerAPI;
 201 
 202         OptionalMethod(Class<T> cls, String name, Class<?>... args) {
 203             Method m;
 204             try {
 205                 m = cls.getMethod(name, args);
 206             } catch (NoSuchMethodException ignored) {
 207                 // This means we're running with older JDK, simply skip the call
 208                 m = null;
 209             } catch (Throwable ex) {
 210                 throw new RuntimeException("Error when calling " + cls.getName() + ".getMethod('" + name + "').", ex);
 211             }
 212             method = m;
 213             isIntegerAPI = args != null && args.length > 0 &&
 214                                                         args[0] == Integer.TYPE;
 215         }
 216 
 217         public boolean isSupported() {
 218             return method != null;
 219         }
 220 
 221         public boolean isIntegerApi() {
 222             return isIntegerAPI;
 223         }
 224 
 225         public Object invoke(T object, Object... args) {
 226             if (method != null) {
 227                 try {
 228                     return method.invoke(object, args);
 229                 } catch (Throwable ex) {
 230                     throw new RuntimeException("Error when calling " + object.getClass().getName() + "." + method.getName() + "().", ex);
 231                 }
 232             } else {
 233                 return null;
 234             }
 235         }
 236     }
 237 
 238     /**
 239      * Calls LightweightFrameWrapper.setHostBounds.
 240      * Must be called on EDT only.
 241      */
 242     private static final OptionalMethod<LightweightFrameWrapper> jlfSetHostBounds =
 243         new OptionalMethod<>(LightweightFrameWrapper.class, "setHostBounds",
 244                 Integer.TYPE, Integer.TYPE, Integer.TYPE, Integer.TYPE);
 245 
 246     public void activateLwFrame(Object frame, final boolean activate) {
 247         LightweightFrameWrapper lwFrame = (LightweightFrameWrapper)frame;
 248         if (lwFrame == null) {
 249             return;
 250         }
 251         if (PlatformUtil.isLinux()) {
 252             // Workaround to block FocusOut/FocusIn notifications from Unity
 253             // focus grabbing upon Alt press
 254             if (deactivate == null || !deactivate.isRunning()) {
 255                 if (!activate) {
 256                     deactivate = new Timer(50, (e) -> {
 257                         {
 258                             if (lwFrame != null) {
 259                                 lwFrame.emulateActivation(false);
 260                             }
 261                         }
 262                     });
 263                     deactivate.start();
 264                     return;
 265                 }
 266             } else {
 267                 deactivate.stop();
 268             }
 269         }
 270 
 271         SwingFXUtils.runOnEDT(() -> {
 272             if (lwFrame != null) {
 273                 lwFrame.emulateActivation(activate);
 274             }
 275         });
 276     }
 277 
 278     public void disposeFrame(Object frame) {
 279         LightweightFrameWrapper lwFrame = (LightweightFrameWrapper)frame;
 280         lwFrame.dispose();
 281     }
 282 
 283     public void addWindowFocusListener(Object frame, WindowFocusListener l) {
 284         LightweightFrameWrapper lwFrame = (LightweightFrameWrapper)frame;
 285         lwFrame.addWindowFocusListener(l);
 286     }
 287 
 288     public void disposeLwFrame() {
 289         if (lwFrame == null) {
 290             return;
 291         }
 292         SwingFXUtils.runOnEDT(() -> {
 293             if (lwFrame != null) {
 294                 lwFrame.dispose();
 295                 lwFrame = null;
 296             }
 297         });
 298     }
 299 
 300     public void setLwFrameVisible(final boolean visible) {
 301         if (lwFrame == null) {
 302             return;
 303         }
 304         SwingFXUtils.runOnEDT(() -> {
 305             if (lwFrame != null) {
 306                 lwFrame.setVisible(visible);
 307             }
 308         });
 309     }
 310 
 311     public void setLwFrameScale(final double scaleX, final double scaleY) {
 312         if (lwFrame == null) {
 313             return;
 314         }
 315         SwingFXUtils.runOnEDT(() -> {
 316             if (lwFrame != null) {
 317                 if (jlfNotifyDisplayChanged.isIntegerApi()) {
 318                     jlfNotifyDisplayChanged.invoke(lwFrame,
 319                                                        (int)Math.round(scaleX));
 320                 } else {
 321                     jlfNotifyDisplayChanged.invoke(lwFrame, scaleX, scaleY);
 322                 }
 323             }
 324         });
 325     }
 326 
 327     private static class SwingNodeDisposer implements DisposerRecord {
 328          LightweightFrameWrapper lwFrame;
 329 
 330          SwingNodeDisposer(LightweightFrameWrapper ref) {
 331              this.lwFrame = ref;
 332          }
 333          public void dispose() {
 334              if (lwFrame != null) {
 335                  lwFrame.dispose();
 336                  lwFrame = null;
 337              }
 338          }
 339     }
 340 
 341     private class SwingNodeContent extends LightweightContentWrapper {
 342         private JComponent comp;
 343         private volatile FXDnD dnd;
 344         private WeakReference<SwingNode> swingNodeRef;
 345 
 346         SwingNodeContent(JComponent comp, SwingNode swingNode) {
 347             this.comp = comp;
 348             this.swingNodeRef = new WeakReference<SwingNode>(swingNode);
 349         }
 350         @Override
 351         public JComponent getComponent() {
 352             return comp;
 353         }
 354         @Override
 355         public void paintLock() {
 356             SwingNode swingNode = swingNodeRef.get();
 357             if (swingNode != null) {
 358                 swingNode.paintLock.lock();
 359             }
 360         }
 361         @Override
 362         public void paintUnlock() {
 363             SwingNode swingNode = swingNodeRef.get();
 364             if (swingNode != null) {
 365                 swingNode.paintLock.unlock();
 366             }
 367         }
 368 
 369         @Override
 370         public void imageBufferReset(int[] data, int x, int y, int width, int height, int linestride) {
 371             imageBufferReset(data, x, y, width, height, linestride, 1);
 372         }
 373         //@Override
 374         public void imageBufferReset(int[] data, int x, int y, int width, int height, int linestride, int scale) {
 375             SwingNode swingNode = swingNodeRef.get();
 376             if (swingNode != null) {
 377                 swingNode.setImageBuffer(data, x, y, width, height, linestride, scale, scale);
 378             }
 379         }
 380         @Override
 381         public void imageBufferReset(int[] data, int x, int y, int width, int height, int linestride, double scaleX, double scaleY) {
 382             SwingNode swingNode = swingNodeRef.get();
 383             if (swingNode != null) {
 384                 swingNode.setImageBuffer(data, x, y, width, height, linestride, scaleX, scaleY);
 385             }
 386         }
 387         @Override
 388         public void imageReshaped(int x, int y, int width, int height) {
 389             SwingNode swingNode = swingNodeRef.get();
 390             if (swingNode != null) {
 391                 swingNode.setImageBounds(x, y, width, height);
 392             }
 393         }
 394         @Override
 395         public void imageUpdated(int dirtyX, int dirtyY, int dirtyWidth, int dirtyHeight) {
 396             SwingNode swingNode = swingNodeRef.get();
 397             if (swingNode != null) {
 398                 swingNode.repaintDirtyRegion(dirtyX, dirtyY, dirtyWidth, dirtyHeight);
 399             }
 400         }
 401         @Override
 402         public void focusGrabbed() {
 403             SwingFXUtils.runOnFxThread(() -> {
 404                 // On X11 grab is limited to a single XDisplay connection,
 405                 // so we can't delegate it to another GUI toolkit.
 406                 if (PlatformUtil.isLinux()) return;
 407 
 408                 SwingNode swingNode = swingNodeRef.get();
 409                 if (swingNode != null) {
 410                     Scene scene = swingNode.getScene();
 411                     if (scene != null &&
 412                             scene.getWindow() != null &&
 413                             WindowHelper.getPeer(scene.getWindow()) != null) {
 414                         WindowHelper.getPeer(scene.getWindow()).grabFocus();
 415             swingNode.grabbed = true;
 416                     }
 417                 }
 418             });
 419         }
 420         @Override
 421         public void focusUngrabbed() {
 422             SwingFXUtils.runOnFxThread(() -> {
 423                 SwingNode swingNode = swingNodeRef.get();
 424                 if (swingNode != null) {
 425             Scene scene = swingNode.getScene();
 426                     swingNode.ungrabFocus(false);
 427                 }
 428             });
 429         }
 430         @Override
 431         public void preferredSizeChanged(final int width, final int height) {
 432             SwingFXUtils.runOnFxThread(() -> {
 433                 SwingNode swingNode = swingNodeRef.get();
 434                 if (swingNode != null) {
 435                     swingNode.swingPrefWidth = width;
 436                     swingNode.swingPrefHeight = height;
 437                     NodeHelper.notifyLayoutBoundsChanged(swingNode);
 438                 }
 439             });
 440         }
 441         @Override
 442         public void maximumSizeChanged(final int width, final int height) {
 443             SwingFXUtils.runOnFxThread(() -> {
 444                 SwingNode swingNode = swingNodeRef.get();
 445                 if (swingNode != null) {
 446                     swingNode.swingMaxWidth = width;
 447                     swingNode.swingMaxHeight = height;
 448                     NodeHelper.notifyLayoutBoundsChanged(swingNode);
 449                 }
 450             });
 451         }
 452         @Override
 453         public void minimumSizeChanged(final int width, final int height) {
 454             SwingFXUtils.runOnFxThread(() -> {
 455                 SwingNode swingNode = swingNodeRef.get();
 456                 if (swingNode != null) {
 457                     swingNode.swingMinWidth = width;
 458                     swingNode.swingMinHeight = height;
 459                     NodeHelper.notifyLayoutBoundsChanged(swingNode);
 460                 }
 461             });
 462         }
 463 
 464         //@Override
 465         public void setCursor(Cursor cursor) {
 466             SwingFXUtils.runOnFxThread(() -> {
 467                 SwingNode swingNode = swingNodeRef.get();
 468                 if (swingNode != null) {
 469                     swingNode.setCursor(SwingCursors.embedCursorToCursor(cursor));
 470                 }
 471             });
 472         }
 473 
 474         private void initDnD() {
 475             // This is a part of AWT API, so the method may be invoked on any thread
 476             synchronized (SwingNodeContent.this) {
 477                 if (this.dnd == null) {
 478                     SwingNode swingNode = swingNodeRef.get();
 479                     if (swingNode != null) {
 480                         this.dnd = new FXDnD(swingNode);
 481                     }
 482                 }
 483             }
 484         }
 485 
 486         @Override
 487         public synchronized <T extends DragGestureRecognizer> T createDragGestureRecognizer(
 488                 Class<T> abstractRecognizerClass,
 489                 DragSource ds, Component c, int srcActions,
 490                 DragGestureListener dgl)
 491         {
 492             initDnD();
 493             return dnd.createDragGestureRecognizer(abstractRecognizerClass, ds, c, srcActions, dgl);
 494         }
 495 
 496         @Override
 497         public DragSourceContextWrapper createDragSourceContext(DragGestureEvent dge) throws InvalidDnDOperationException
 498         {
 499             initDnD();
 500             return (DragSourceContextWrapper)dnd.createDragSourceContext(dge);
 501         }
 502 
 503         @Override
 504         public void addDropTarget(DropTarget dt) {
 505             initDnD();
 506             dnd.addDropTarget(dt);
 507         }
 508 
 509         @Override
 510         public void removeDropTarget(DropTarget dt) {
 511             initDnD();
 512             dnd.removeDropTarget(dt);
 513         }
 514     }
 515 
 516 }