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 }