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 }