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 }