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 }