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