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