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,
 298                             final double scrollX, final double scrollY,
 299                             final double totalScrollX, final double totalScrollY,
 300                             double xMultiplier, double yMultiplier,
 301                             final double x, final double y,
 302                             final double xAbs, final double yAbs,
 303                             final boolean shift, final boolean ctrl, final boolean alt, final boolean meta,
 304                             final boolean inertia) {
 305         {
 306             Platform.runLater(() -> {
 307                 AccessController.doPrivileged((PrivilegedAction<Void>) () -> {
 308                     if (sceneListener == null) {
 309                         return null;
 310                     }
 311                     sceneListener.scrollEvent(AbstractEvents.scrollIDToFXEventType(type), scrollX, scrollY, totalScrollX, totalScrollY, xMultiplier, yMultiplier,
 312                             0, 0, 0, 0, 0, x, y, xAbs, yAbs, shift, ctrl, alt, meta, false, inertia);
 313                     return null;
 314                 }, getAccessControlContext());
 315             });
 316         }
 317     }
 318 
 319     @Override
 320     public void inputMethodEvent(final EventType<InputMethodEvent> type,
 321                                  final ObservableList<InputMethodTextRun> composed, final String committed,
 322                                  final int caretPosition) {
 323         Platform.runLater(() -> {
 324             AccessController.doPrivileged((PrivilegedAction<Void>) () -> {
 325                 if (sceneListener != null) {
 326                     sceneListener.inputMethodEvent(type, composed, committed, caretPosition);
 327                 }
 328                 return null;
 329             });
 330         });
 331     }
 332 
 333     @Override
 334     public void menuEvent(final int x, final int y, final int xAbs, final int yAbs, final boolean isKeyboardTrigger) {
 335         Platform.runLater(() -> {
 336             AccessController.doPrivileged((PrivilegedAction<Void>) () -> {
 337                 if (sceneListener != null) {
 338                     sceneListener.menuEvent(x, y, xAbs, yAbs, isKeyboardTrigger);
 339                 }
 340                 return null;
 341             }, getAccessControlContext());
 342         });
 343     }
 344 
 345     @Override
 346     public void keyEvent(final int type, final int key, final char[] ch, final int modifiers) {
 347         Platform.runLater(() -> {
 348             AccessController.doPrivileged((PrivilegedAction<Void>) () -> {
 349                 if (sceneListener != null) {
 350                     boolean shiftDown = (modifiers & AbstractEvents.MODIFIER_SHIFT) != 0;
 351                     boolean controlDown = (modifiers & AbstractEvents.MODIFIER_CONTROL) != 0;
 352                     boolean altDown = (modifiers & AbstractEvents.MODIFIER_ALT) != 0;
 353                     boolean metaDown = (modifiers & AbstractEvents.MODIFIER_META) != 0;
 354 
 355                     String str = new String(ch);
 356                     String text = str; // TODO: this must be a text like "HOME", "F1", or "A"
 357                     javafx.scene.input.KeyEvent keyEvent = new javafx.scene.input.KeyEvent(
 358                             AbstractEvents.keyIDToFXEventType(type),
 359                             str, text,
 360                             KeyCodeMap.valueOf(key),
 361                             shiftDown, controlDown, altDown, metaDown);
 362                     sceneListener.keyEvent(keyEvent);
 363                 }
 364                 return null;
 365             }, getAccessControlContext());
 366         });
 367     }
 368 
 369     @Override
 370     public void zoomEvent(final int type, final double zoomFactor, final double totalZoomFactor,
 371                           final double x, final double y, final double screenX, final double screenY,
 372                           boolean shift, boolean ctrl, boolean alt, boolean meta, boolean inertia)
 373     {
 374         Platform.runLater(() -> {
 375             AccessController.doPrivileged((PrivilegedAction<Void>) () -> {
 376                 if (sceneListener == null) {
 377                     return null;
 378                 }
 379                 sceneListener.zoomEvent(AbstractEvents.zoomIDToFXEventType(type),
 380                         zoomFactor, totalZoomFactor,
 381                         x, y, screenX, screenY,
 382                         shift, ctrl, alt, meta, false, inertia);
 383                 return null;
 384             }, getAccessControlContext());
 385         });
 386     }
 387 
 388     @Override
 389     public void rotateEvent(final int type, final double angle, final double totalAngle,
 390                           final double x, final double y, final double screenX, final double screenY,
 391                           boolean shift, boolean ctrl, boolean alt, boolean meta, boolean inertia)
 392     {
 393         Platform.runLater(() -> {
 394             AccessController.doPrivileged((PrivilegedAction<Void>) () -> {
 395                 if (sceneListener == null) {
 396                     return null;
 397                 }
 398                 sceneListener.rotateEvent(AbstractEvents.rotateIDToFXEventType(type),
 399                         angle, totalAngle,
 400                         x, y, screenX, screenY,
 401                         shift, ctrl, alt, meta, false, inertia);
 402                 return null;
 403             }, getAccessControlContext());
 404         });
 405     }
 406 
 407     @Override
 408     public void swipeEvent(final int type, final double x, final double y, final double screenX, final double screenY,
 409                             boolean shift, boolean ctrl, boolean alt, boolean meta)
 410     {
 411         Platform.runLater(() -> {
 412             AccessController.doPrivileged((PrivilegedAction<Void>) () -> {
 413                 if (sceneListener == null) {
 414                     return null;
 415                 }
 416                 sceneListener.swipeEvent(AbstractEvents.swipeIDToFXEventType(type),
 417                         0, x, y, screenX, screenY,
 418                         shift, ctrl, alt, meta, false);
 419                 return null;
 420             }, getAccessControlContext());
 421         });
 422     }
 423 
 424     @Override
 425     public void setCursor(final Object cursor) {
 426         super.setCursor(cursor);
 427         host.setCursor((CursorFrame) cursor);
 428     }
 429 
 430     @Override
 431     public void setDragStartListener(HostDragStartListener l) {
 432         embeddedDnD.setDragStartListener(l);
 433     }
 434 
 435     @Override
 436     public EmbeddedSceneDTInterface createDropTarget() {
 437         return embeddedDnD.createDropTarget();
 438     }
 439 
 440     @Override
 441     public InputMethodRequests getInputMethodRequests() {
 442         return inputMethodRequests;
 443     }
 444 }