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