1 /* 2 * Copyright (c) 2010, 2016, 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.tk.quantum; 27 28 import com.sun.javafx.embed.HostDragStartListener; 29 import javafx.application.Platform; 30 import javafx.collections.ObservableList; 31 import javafx.event.EventType; 32 import javafx.scene.input.InputMethodEvent; 33 import javafx.scene.input.InputMethodRequests; 34 import javafx.scene.input.InputMethodTextRun; 35 import javafx.scene.input.MouseEvent; 36 import javafx.scene.input.ScrollEvent; 37 import javafx.scene.image.PixelFormat; 38 import java.nio.IntBuffer; 39 import java.security.AccessController; 40 import java.security.PrivilegedAction; 41 import com.sun.javafx.cursor.CursorFrame; 42 import com.sun.javafx.embed.AbstractEvents; 43 import com.sun.javafx.embed.EmbeddedSceneDTInterface; 44 import com.sun.javafx.embed.EmbeddedSceneInterface; 45 import com.sun.javafx.embed.HostInterface; 46 import com.sun.javafx.scene.input.KeyCodeMap; 47 import com.sun.javafx.scene.traversal.Direction; 48 import com.sun.javafx.sg.prism.NGNode; 49 import com.sun.javafx.tk.TKClipboard; 50 import com.sun.javafx.tk.Toolkit; 51 import com.sun.prism.paint.Color; 52 import com.sun.prism.paint.Paint; 53 import com.sun.glass.ui.Pixels; 54 import java.nio.ByteOrder; 55 56 final class EmbeddedScene extends GlassScene implements EmbeddedSceneInterface { 57 58 // TODO: synchronize access to embedder from ET and RT 59 private HostInterface host; 60 61 private UploadingPainter painter; 62 private PaintRenderJob paintRenderJob; 63 private float renderScaleX; 64 private float renderScaleY; 65 66 private final EmbeddedSceneDnD embeddedDnD; 67 68 private volatile IntBuffer texBits; 69 private volatile int texLineStride; // pre-scaled 70 private volatile float texScaleFactorX = 1.0f; 71 private volatile float texScaleFactorY = 1.0f; 72 73 private volatile PixelFormat<?> pixelFormat; 74 75 public EmbeddedScene(HostInterface host, boolean depthBuffer, boolean msaa) { 76 super(depthBuffer, msaa); 77 sceneState = new EmbeddedState(this); 78 79 this.host = host; 80 this.embeddedDnD = new EmbeddedSceneDnD(this); 81 82 PaintCollector collector = PaintCollector.getInstance(); 83 painter = new UploadingPainter(this); 84 paintRenderJob = new PaintRenderJob(this, collector.getRendered(), painter); 85 86 int nativeFormat = Pixels.getNativeFormat(); 87 ByteOrder byteorder = ByteOrder.nativeOrder(); 88 89 if (nativeFormat == Pixels.Format.BYTE_BGRA_PRE && 90 byteorder == ByteOrder.LITTLE_ENDIAN) 91 { 92 pixelFormat = PixelFormat.getIntArgbPreInstance(); 93 94 } else if (nativeFormat == Pixels.Format.BYTE_ARGB && 95 byteorder == ByteOrder.BIG_ENDIAN) 96 { 97 pixelFormat = PixelFormat.getIntArgbInstance(); 98 } 99 assert pixelFormat != null; 100 } 101 102 @Override 103 public void dispose() { 104 assert host != null; 105 QuantumToolkit.runWithRenderLock(() -> { 106 host.setEmbeddedScene(null); 107 host = null; 108 updateSceneState(); 109 painter = null; 110 paintRenderJob = null; 111 texBits = null; 112 return null; 113 }); 114 super.dispose(); 115 } 116 117 @Override 118 void setStage(GlassStage stage) { 119 super.setStage(stage); 120 121 assert host != null; // setStage() is called before dispose() 122 host.setEmbeddedScene(stage != null ? this : null); 123 } 124 125 @Override protected boolean isSynchronous() { 126 return false; 127 } 128 129 @Override public void setRoot(NGNode root) { 130 super.setRoot(root); 131 painter.setRoot(root); 132 } 133 134 @Override 135 public TKClipboard createDragboard(boolean isDragSource) { 136 return embeddedDnD.createDragboard(isDragSource); 137 } 138 139 @Override 140 public void enableInputMethodEvents(boolean enable) { 141 if (QuantumToolkit.verbose) { 142 System.err.println("EmbeddedScene.enableInputMethodEvents " + enable); 143 } 144 } 145 146 @Override 147 public void finishInputMethodComposition() { 148 if (QuantumToolkit.verbose) { 149 System.err.println("EmbeddedScene.finishInputMethodComposition"); 150 } 151 } 152 153 @Override 154 public void setPixelScaleFactors(float scalex, float scaley) { 155 renderScaleX = scalex; 156 renderScaleY = scaley; 157 entireSceneNeedsRepaint(); 158 } 159 160 public float getRenderScaleX() { 161 return renderScaleX; 162 } 163 164 public float getRenderScaleY() { 165 return renderScaleY; 166 } 167 168 @Override 169 public PixelFormat<?> getPixelFormat() { 170 return pixelFormat; 171 } 172 173 // Called by EmbeddedPainter on the render thread under renderLock 174 void uploadPixels(Pixels pixels) { 175 texBits = (IntBuffer)pixels.getPixels(); 176 texLineStride = pixels.getWidthUnsafe(); 177 texScaleFactorX = pixels.getScaleXUnsafe(); 178 texScaleFactorY = pixels.getScaleYUnsafe(); 179 if (host != null) { 180 host.repaint(); 181 } 182 } 183 184 // EmbeddedSceneInterface methods 185 186 @Override 187 public void repaint() { 188 Toolkit tk = Toolkit.getToolkit(); 189 tk.addRenderJob(paintRenderJob); 190 } 191 192 @Override 193 public boolean traverseOut(Direction dir) { 194 if (dir == Direction.NEXT) { 195 return host.traverseFocusOut(true); 196 } else if (dir == Direction.PREVIOUS) { 197 return host.traverseFocusOut(false); 198 } 199 return false; 200 } 201 202 @Override 203 public void setSize(final int width, final int height) { 204 Platform.runLater(() -> { 205 AccessController.doPrivileged((PrivilegedAction<Void>) () -> { 206 if (sceneListener != null) { 207 sceneListener.changedSize(width, height); 208 } 209 return null; 210 }, getAccessControlContext()); 211 }); 212 } 213 214 /** 215 * @param dest the destination buffer 216 * @param width the logical width of the buffer 217 * @param height the logical height of the buffer 218 * @param scale the scale factor 219 * @return 220 */ 221 @Override 222 public boolean getPixels(final IntBuffer dest, final int width, final int height) { 223 return QuantumToolkit.runWithRenderLock(() -> { 224 int scaledWidth = width; 225 int scaledHeight = height; 226 227 // The dest buffer scale factor is expected to match painter.getPixelScaleFactor(). 228 if (getRenderScaleX() != texScaleFactorX || 229 getRenderScaleY() != texScaleFactorY || 230 texBits == null) 231 { 232 return false; 233 } 234 scaledWidth = Math.round(scaledWidth * texScaleFactorX); 235 scaledHeight = Math.round(scaledHeight * texScaleFactorY); 236 237 dest.rewind(); 238 texBits.rewind(); 239 if (dest.capacity() != texBits.capacity()) { 240 // Calculate the intersection of the dest & src images. 241 int w = Math.min(scaledWidth, texLineStride); 242 int h = Math.min(scaledHeight, texBits.capacity() / texLineStride); 243 244 // Copy the intersection to the dest. 245 // The backed array of the textureBits may not be available, 246 // so not relying on it. 247 int[] linebuf = new int[w]; 248 for (int i = 0; i < h; i++) { 249 texBits.position(i * texLineStride); 250 texBits.get(linebuf, 0, w); 251 dest.position(i * scaledWidth); 252 dest.put(linebuf); 253 } 254 return true; 255 } 256 dest.put(texBits); 257 return true; 258 }); 259 } 260 261 @Override 262 protected Color getClearColor() { 263 if (fillPaint != null && fillPaint.getType() == Paint.Type.COLOR && 264 ((Color)fillPaint).getAlpha() == 0f) 265 { 266 return (Color)fillPaint; 267 } 268 return super.getClearColor(); 269 } 270 271 @Override 272 public void mouseEvent(final int type, final int button, 273 final boolean primaryBtnDown, final boolean middleBtnDown, final boolean secondaryBtnDown, 274 final int x, final int y, final int xAbs, final int yAbs, 275 final boolean shift, final boolean ctrl, final boolean alt, final boolean meta, 276 final boolean popupTrigger) 277 { 278 Platform.runLater(() -> { 279 AccessController.doPrivileged((PrivilegedAction<Void>) () -> { 280 if (sceneListener == null) { 281 return null; 282 } 283 // Click events are generated in Scene, so we don't expect them here 284 assert type != AbstractEvents.MOUSEEVENT_CLICKED; 285 EventType<MouseEvent> eventType = AbstractEvents.mouseIDToFXEventID(type); 286 sceneListener.mouseEvent(eventType, x, y, xAbs, yAbs, 287 AbstractEvents.mouseButtonToFXMouseButton(button), 288 popupTrigger, false, // do we know if it's synthesized? RT-20142 289 shift, ctrl, alt, meta, 290 primaryBtnDown, middleBtnDown, secondaryBtnDown); 291 return null; 292 }, getAccessControlContext()); 293 }); 294 } 295 296 @Override 297 public void scrollEvent(final int type, final double scrollX, final double scrollY, 298 final double x, final double y, 299 final double xAbs, final double yAbs, 300 final boolean shift, final boolean ctrl, final boolean alt, final boolean meta) { 301 { 302 Platform.runLater(() -> { 303 AccessController.doPrivileged((PrivilegedAction<Void>) () -> { 304 if (sceneListener == null) { 305 return null; 306 } 307 sceneListener.scrollEvent(ScrollEvent.SCROLL, scrollX, scrollY, 0, 0, 40.0, 40.0, 308 0, 0, 0, 0, 0, 309 x, y, xAbs, yAbs, shift, ctrl, alt, meta, false, false); 310 return null; 311 }, getAccessControlContext()); 312 }); 313 } 314 } 315 316 @Override 317 public void inputMethodEvent(final EventType<InputMethodEvent> type, 318 final ObservableList<InputMethodTextRun> composed, final String committed, 319 final int caretPosition) { 320 Platform.runLater(() -> { 321 AccessController.doPrivileged((PrivilegedAction<Void>) () -> { 322 if (sceneListener != null) { 323 sceneListener.inputMethodEvent(type, composed, committed, caretPosition); 324 } 325 return null; 326 }); 327 }); 328 } 329 330 @Override 331 public void menuEvent(final int x, final int y, final int xAbs, final int yAbs, final boolean isKeyboardTrigger) { 332 Platform.runLater(() -> { 333 AccessController.doPrivileged((PrivilegedAction<Void>) () -> { 334 if (sceneListener != null) { 335 sceneListener.menuEvent(x, y, xAbs, yAbs, isKeyboardTrigger); 336 } 337 return null; 338 }, getAccessControlContext()); 339 }); 340 } 341 342 @Override 343 public void keyEvent(final int type, final int key, final char[] ch, final int modifiers) { 344 Platform.runLater(() -> { 345 AccessController.doPrivileged((PrivilegedAction<Void>) () -> { 346 if (sceneListener != null) { 347 boolean shiftDown = (modifiers & AbstractEvents.MODIFIER_SHIFT) != 0; 348 boolean controlDown = (modifiers & AbstractEvents.MODIFIER_CONTROL) != 0; 349 boolean altDown = (modifiers & AbstractEvents.MODIFIER_ALT) != 0; 350 boolean metaDown = (modifiers & AbstractEvents.MODIFIER_META) != 0; 351 352 String str = new String(ch); 353 String text = str; // TODO: this must be a text like "HOME", "F1", or "A" 354 javafx.scene.input.KeyEvent keyEvent = new javafx.scene.input.KeyEvent( 355 AbstractEvents.keyIDToFXEventType(type), 356 str, text, 357 KeyCodeMap.valueOf(key), 358 shiftDown, controlDown, altDown, metaDown); 359 sceneListener.keyEvent(keyEvent); 360 } 361 return null; 362 }, getAccessControlContext()); 363 }); 364 } 365 366 @Override 367 public void setCursor(final Object cursor) { 368 super.setCursor(cursor); 369 host.setCursor((CursorFrame) cursor); 370 } 371 372 @Override 373 public void setDragStartListener(HostDragStartListener l) { 374 embeddedDnD.setDragStartListener(l); 375 } 376 377 @Override 378 public EmbeddedSceneDTInterface createDropTarget() { 379 return embeddedDnD.createDropTarget(); 380 } 381 382 @Override 383 public InputMethodRequests getInputMethodRequests() { 384 return inputMethodRequests; 385 } 386 }